/* * Mesa 3-D graphics library * * Copyright (C) 2009 VMware, Inc. All Rights Reserved. * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /** * \file viewport.c * glViewport and glDepthRange functions. */ #include "context.h" #include "enums.h" #include "macros.h" #include "mtypes.h" #include "viewport.h" static void clamp_viewport(struct gl_context *ctx, GLfloat *x, GLfloat *y, GLfloat *width, GLfloat *height) { /* clamp width and height to the implementation dependent range */ *width = MIN2(*width, (GLfloat) ctx->Const.MaxViewportWidth); *height = MIN2(*height, (GLfloat) ctx->Const.MaxViewportHeight); /* The GL_ARB_viewport_array spec says: * * "The location of the viewport's bottom-left corner, given by (x,y), * are clamped to be within the implementation-dependent viewport * bounds range. The viewport bounds range [min, max] tuple may be * determined by calling GetFloatv with the symbolic constant * VIEWPORT_BOUNDS_RANGE (see section 6.1)." */ if (_mesa_has_ARB_viewport_array(ctx) || _mesa_has_OES_viewport_array(ctx)) { *x = CLAMP(*x, ctx->Const.ViewportBounds.Min, ctx->Const.ViewportBounds.Max); *y = CLAMP(*y, ctx->Const.ViewportBounds.Min, ctx->Const.ViewportBounds.Max); } } static void set_viewport_no_notify(struct gl_context *ctx, unsigned idx, GLfloat x, GLfloat y, GLfloat width, GLfloat height) { if (ctx->ViewportArray[idx].X == x && ctx->ViewportArray[idx].Width == width && ctx->ViewportArray[idx].Y == y && ctx->ViewportArray[idx].Height == height) return; FLUSH_VERTICES(ctx, ctx->DriverFlags.NewViewport ? 0 : _NEW_VIEWPORT); ctx->NewDriverState |= ctx->DriverFlags.NewViewport; ctx->ViewportArray[idx].X = x; ctx->ViewportArray[idx].Width = width; ctx->ViewportArray[idx].Y = y; ctx->ViewportArray[idx].Height = height; } struct gl_viewport_inputs { GLfloat X, Y; /**< position */ GLfloat Width, Height; /**< size */ }; struct gl_depthrange_inputs { GLdouble Near, Far; /**< Depth buffer range */ }; static void viewport(struct gl_context *ctx, GLint x, GLint y, GLsizei width, GLsizei height) { struct gl_viewport_inputs input = { x, y, width, height }; /* Clamp the viewport to the implementation dependent values. */ clamp_viewport(ctx, &input.X, &input.Y, &input.Width, &input.Height); /* The GL_ARB_viewport_array spec says: * * "Viewport sets the parameters for all viewports to the same values * and is equivalent (assuming no errors are generated) to: * * for (uint i = 0; i < MAX_VIEWPORTS; i++) * ViewportIndexedf(i, 1, (float)x, (float)y, (float)w, (float)h);" * * Set all of the viewports supported by the implementation, but only * signal the driver once at the end. */ for (unsigned i = 0; i < ctx->Const.MaxViewports; i++) set_viewport_no_notify(ctx, i, input.X, input.Y, input.Width, input.Height); if (ctx->Driver.Viewport) ctx->Driver.Viewport(ctx); } /** * Set the viewport. * \sa Called via glViewport() or display list execution. * * Flushes the vertices and calls _mesa_set_viewport() with the given * parameters. */ void GLAPIENTRY _mesa_Viewport_no_error(GLint x, GLint y, GLsizei width, GLsizei height) { GET_CURRENT_CONTEXT(ctx); viewport(ctx, x, y, width, height); } void GLAPIENTRY _mesa_Viewport(GLint x, GLint y, GLsizei width, GLsizei height) { GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glViewport %d %d %d %d\n", x, y, width, height); if (width < 0 || height < 0) { _mesa_error(ctx, GL_INVALID_VALUE, "glViewport(%d, %d, %d, %d)", x, y, width, height); return; } viewport(ctx, x, y, width, height); } /** * Set new viewport parameters and update derived state. * Usually called from _mesa_Viewport(). * * \param ctx GL context. * \param idx Index of the viewport to be updated. * \param x, y coordinates of the lower left corner of the viewport rectangle. * \param width width of the viewport rectangle. * \param height height of the viewport rectangle. */ void _mesa_set_viewport(struct gl_context *ctx, unsigned idx, GLfloat x, GLfloat y, GLfloat width, GLfloat height) { clamp_viewport(ctx, &x, &y, &width, &height); set_viewport_no_notify(ctx, idx, x, y, width, height); if (ctx->Driver.Viewport) ctx->Driver.Viewport(ctx); } static void viewport_array(struct gl_context *ctx, GLuint first, GLsizei count, struct gl_viewport_inputs *inputs) { for (GLsizei i = 0; i < count; i++) { clamp_viewport(ctx, &inputs[i].X, &inputs[i].Y, &inputs[i].Width, &inputs[i].Height); set_viewport_no_notify(ctx, i + first, inputs[i].X, inputs[i].Y, inputs[i].Width, inputs[i].Height); } if (ctx->Driver.Viewport) ctx->Driver.Viewport(ctx); } void GLAPIENTRY _mesa_ViewportArrayv_no_error(GLuint first, GLsizei count, const GLfloat *v) { GET_CURRENT_CONTEXT(ctx); struct gl_viewport_inputs *p = (struct gl_viewport_inputs *)v; viewport_array(ctx, first, count, p); } void GLAPIENTRY _mesa_ViewportArrayv(GLuint first, GLsizei count, const GLfloat *v) { int i; struct gl_viewport_inputs *p = (struct gl_viewport_inputs *)v; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glViewportArrayv %d %d\n", first, count); if ((first + count) > ctx->Const.MaxViewports) { _mesa_error(ctx, GL_INVALID_VALUE, "glViewportArrayv: first (%d) + count (%d) > MaxViewports " "(%d)", first, count, ctx->Const.MaxViewports); return; } /* Verify width & height */ for (i = 0; i < count; i++) { if (p[i].Width < 0 || p[i].Height < 0) { _mesa_error(ctx, GL_INVALID_VALUE, "glViewportArrayv: index (%d) width or height < 0 " "(%f, %f)", i + first, p[i].Width, p[i].Height); return; } } viewport_array(ctx, first, count, p); } static void viewport_indexed_err(struct gl_context *ctx, GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h, const char *function) { if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "%s(%d, %f, %f, %f, %f)\n", function, index, x, y, w, h); if (index >= ctx->Const.MaxViewports) { _mesa_error(ctx, GL_INVALID_VALUE, "%s: index (%d) >= MaxViewports (%d)", function, index, ctx->Const.MaxViewports); return; } /* Verify width & height */ if (w < 0 || h < 0) { _mesa_error(ctx, GL_INVALID_VALUE, "%s: index (%d) width or height < 0 (%f, %f)", function, index, w, h); return; } _mesa_set_viewport(ctx, index, x, y, w, h); } void GLAPIENTRY _mesa_ViewportIndexedf_no_error(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h) { GET_CURRENT_CONTEXT(ctx); _mesa_set_viewport(ctx, index, x, y, w, h); } void GLAPIENTRY _mesa_ViewportIndexedf(GLuint index, GLfloat x, GLfloat y, GLfloat w, GLfloat h) { GET_CURRENT_CONTEXT(ctx); viewport_indexed_err(ctx, index, x, y, w, h, "glViewportIndexedf"); } void GLAPIENTRY _mesa_ViewportIndexedfv_no_error(GLuint index, const GLfloat *v) { GET_CURRENT_CONTEXT(ctx); _mesa_set_viewport(ctx, index, v[0], v[1], v[2], v[3]); } void GLAPIENTRY _mesa_ViewportIndexedfv(GLuint index, const GLfloat *v) { GET_CURRENT_CONTEXT(ctx); viewport_indexed_err(ctx, index, v[0], v[1], v[2], v[3], "glViewportIndexedfv"); } static void set_depth_range_no_notify(struct gl_context *ctx, unsigned idx, GLclampd nearval, GLclampd farval) { if (ctx->ViewportArray[idx].Near == nearval && ctx->ViewportArray[idx].Far == farval) return; /* The depth range is needed by program state constants. */ FLUSH_VERTICES(ctx, _NEW_VIEWPORT); ctx->NewDriverState |= ctx->DriverFlags.NewViewport; ctx->ViewportArray[idx].Near = CLAMP(nearval, 0.0, 1.0); ctx->ViewportArray[idx].Far = CLAMP(farval, 0.0, 1.0); } void _mesa_set_depth_range(struct gl_context *ctx, unsigned idx, GLclampd nearval, GLclampd farval) { set_depth_range_no_notify(ctx, idx, nearval, farval); if (ctx->Driver.DepthRange) ctx->Driver.DepthRange(ctx); } /** * Called by glDepthRange * * \param nearval specifies the Z buffer value which should correspond to * the near clip plane * \param farval specifies the Z buffer value which should correspond to * the far clip plane */ void GLAPIENTRY _mesa_DepthRange(GLclampd nearval, GLclampd farval) { unsigned i; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE&VERBOSE_API) _mesa_debug(ctx, "glDepthRange %f %f\n", nearval, farval); /* The GL_ARB_viewport_array spec says: * * "DepthRange sets the depth range for all viewports to the same * values and is equivalent (assuming no errors are generated) to: * * for (uint i = 0; i < MAX_VIEWPORTS; i++) * DepthRangeIndexed(i, n, f);" * * Set the depth range for all of the viewports supported by the * implementation, but only signal the driver once at the end. */ for (i = 0; i < ctx->Const.MaxViewports; i++) set_depth_range_no_notify(ctx, i, nearval, farval); if (ctx->Driver.DepthRange) { ctx->Driver.DepthRange(ctx); } } void GLAPIENTRY _mesa_DepthRangef(GLclampf nearval, GLclampf farval) { _mesa_DepthRange(nearval, farval); } /** * Update a range DepthRange values * * \param first starting array index * \param count count of DepthRange items to update * \param v pointer to memory containing * GLclampd near and far clip-plane values */ static ALWAYS_INLINE void depth_range_arrayv(struct gl_context *ctx, GLuint first, GLsizei count, const struct gl_depthrange_inputs *const inputs) { for (GLsizei i = 0; i < count; i++) set_depth_range_no_notify(ctx, i + first, inputs[i].Near, inputs[i].Far); if (ctx->Driver.DepthRange) ctx->Driver.DepthRange(ctx); } void GLAPIENTRY _mesa_DepthRangeArrayv_no_error(GLuint first, GLsizei count, const GLclampd *v) { GET_CURRENT_CONTEXT(ctx); const struct gl_depthrange_inputs *const p = (struct gl_depthrange_inputs *)v; depth_range_arrayv(ctx, first, count, p); } void GLAPIENTRY _mesa_DepthRangeArrayv(GLuint first, GLsizei count, const GLclampd *v) { const struct gl_depthrange_inputs *const p = (struct gl_depthrange_inputs *) v; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glDepthRangeArrayv %d %d\n", first, count); if ((first + count) > ctx->Const.MaxViewports) { _mesa_error(ctx, GL_INVALID_VALUE, "glDepthRangev: first (%d) + count (%d) >= MaxViewports (%d)", first, count, ctx->Const.MaxViewports); return; } depth_range_arrayv(ctx, first, count, p); } void GLAPIENTRY _mesa_DepthRangeArrayfvOES(GLuint first, GLsizei count, const GLfloat *v) { int i; GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glDepthRangeArrayfv %d %d\n", first, count); if ((first + count) > ctx->Const.MaxViewports) { _mesa_error(ctx, GL_INVALID_VALUE, "glDepthRangeArrayfv: first (%d) + count (%d) >= MaxViewports (%d)", first, count, ctx->Const.MaxViewports); return; } for (i = 0; i < count; i++) set_depth_range_no_notify(ctx, i + first, v[i * 2], v[i * 2 + 1]); if (ctx->Driver.DepthRange) ctx->Driver.DepthRange(ctx); } /** * Update a single DepthRange * * \param index array index to update * \param nearval specifies the Z buffer value which should correspond to * the near clip plane * \param farval specifies the Z buffer value which should correspond to * the far clip plane */ void GLAPIENTRY _mesa_DepthRangeIndexed_no_error(GLuint index, GLclampd nearval, GLclampd farval) { GET_CURRENT_CONTEXT(ctx); _mesa_set_depth_range(ctx, index, nearval, farval); } void GLAPIENTRY _mesa_DepthRangeIndexed(GLuint index, GLclampd nearval, GLclampd farval) { GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glDepthRangeIndexed(%d, %f, %f)\n", index, nearval, farval); if (index >= ctx->Const.MaxViewports) { _mesa_error(ctx, GL_INVALID_VALUE, "glDepthRangeIndexed: index (%d) >= MaxViewports (%d)", index, ctx->Const.MaxViewports); return; } _mesa_set_depth_range(ctx, index, nearval, farval); } void GLAPIENTRY _mesa_DepthRangeIndexedfOES(GLuint index, GLfloat nearval, GLfloat farval) { _mesa_DepthRangeIndexed(index, nearval, farval); } /** * Initialize the context viewport attribute group. * \param ctx the GL context. */ void _mesa_init_viewport(struct gl_context *ctx) { unsigned i; ctx->Transform.ClipOrigin = GL_LOWER_LEFT; ctx->Transform.ClipDepthMode = GL_NEGATIVE_ONE_TO_ONE; /* Note: ctx->Const.MaxViewports may not have been set by the driver yet, * so just initialize all of them. */ for (i = 0; i < MAX_VIEWPORTS; i++) { /* Viewport group */ ctx->ViewportArray[i].X = 0; ctx->ViewportArray[i].Y = 0; ctx->ViewportArray[i].Width = 0; ctx->ViewportArray[i].Height = 0; ctx->ViewportArray[i].Near = 0.0; ctx->ViewportArray[i].Far = 1.0; } } static ALWAYS_INLINE void clip_control(struct gl_context *ctx, GLenum origin, GLenum depth, bool no_error) { if (ctx->Transform.ClipOrigin == origin && ctx->Transform.ClipDepthMode == depth) return; if (!no_error && origin != GL_LOWER_LEFT && origin != GL_UPPER_LEFT) { _mesa_error(ctx, GL_INVALID_ENUM, "glClipControl"); return; } if (!no_error && depth != GL_NEGATIVE_ONE_TO_ONE && depth != GL_ZERO_TO_ONE) { _mesa_error(ctx, GL_INVALID_ENUM, "glClipControl"); return; } /* Affects transform state and the viewport transform */ FLUSH_VERTICES(ctx, ctx->DriverFlags.NewClipControl ? 0 : _NEW_TRANSFORM | _NEW_VIEWPORT); ctx->NewDriverState |= ctx->DriverFlags.NewClipControl; if (ctx->Transform.ClipOrigin != origin) { ctx->Transform.ClipOrigin = origin; /* Affects the winding order of the front face. */ if (ctx->DriverFlags.NewPolygonState) ctx->NewDriverState |= ctx->DriverFlags.NewPolygonState; else ctx->NewState |= _NEW_POLYGON; if (ctx->Driver.FrontFace) ctx->Driver.FrontFace(ctx, ctx->Polygon.FrontFace); } if (ctx->Transform.ClipDepthMode != depth) { ctx->Transform.ClipDepthMode = depth; if (ctx->Driver.DepthRange) ctx->Driver.DepthRange(ctx); } } void GLAPIENTRY _mesa_ClipControl_no_error(GLenum origin, GLenum depth) { GET_CURRENT_CONTEXT(ctx); clip_control(ctx, origin, depth, true); } void GLAPIENTRY _mesa_ClipControl(GLenum origin, GLenum depth) { GET_CURRENT_CONTEXT(ctx); if (MESA_VERBOSE & VERBOSE_API) _mesa_debug(ctx, "glClipControl(%s, %s)\n", _mesa_enum_to_string(origin), _mesa_enum_to_string(depth)); ASSERT_OUTSIDE_BEGIN_END(ctx); if (!ctx->Extensions.ARB_clip_control) { _mesa_error(ctx, GL_INVALID_OPERATION, "glClipControl"); return; } clip_control(ctx, origin, depth, false); } /** * Computes the scaling and the translation part of the * viewport transform matrix of the \param i-th viewport * and writes that into \param scale and \param translate. */ void _mesa_get_viewport_xform(struct gl_context *ctx, unsigned i, float scale[3], float translate[3]) { float x = ctx->ViewportArray[i].X; float y = ctx->ViewportArray[i].Y; float half_width = 0.5f * ctx->ViewportArray[i].Width; float half_height = 0.5f * ctx->ViewportArray[i].Height; double n = ctx->ViewportArray[i].Near; double f = ctx->ViewportArray[i].Far; scale[0] = half_width; translate[0] = half_width + x; if (ctx->Transform.ClipOrigin == GL_UPPER_LEFT) { scale[1] = -half_height; } else { scale[1] = half_height; } translate[1] = half_height + y; if (ctx->Transform.ClipDepthMode == GL_NEGATIVE_ONE_TO_ONE) { scale[2] = 0.5 * (f - n); translate[2] = 0.5 * (n + f); } else { scale[2] = f - n; translate[2] = n; } }