1/*
2 * Mesa 3-D graphics library
3 *
4 * Copyright (C) 2009  VMware, Inc.  All Rights Reserved.
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included
14 * in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
19 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 * OTHER DEALINGS IN THE SOFTWARE.
23 */
24
25
26/**
27 * \file viewport.c
28 * glViewport and glDepthRange functions.
29 */
30
31
32#include "context.h"
33#include "enums.h"
34#include "macros.h"
35#include "mtypes.h"
36#include "viewport.h"
37
38static void
39clamp_viewport(struct gl_context *ctx, GLfloat *x, GLfloat *y,
40               GLfloat *width, GLfloat *height)
41{
42   /* clamp width and height to the implementation dependent range */
43   *width  = MIN2(*width, (GLfloat) ctx->Const.MaxViewportWidth);
44   *height = MIN2(*height, (GLfloat) ctx->Const.MaxViewportHeight);
45
46   /* The GL_ARB_viewport_array spec says:
47    *
48    *     "The location of the viewport's bottom-left corner, given by (x,y),
49    *     are clamped to be within the implementation-dependent viewport
50    *     bounds range.  The viewport bounds range [min, max] tuple may be
51    *     determined by calling GetFloatv with the symbolic constant
52    *     VIEWPORT_BOUNDS_RANGE (see section 6.1)."
53    */
54   if (_mesa_has_ARB_viewport_array(ctx) ||
55       _mesa_has_OES_viewport_array(ctx)) {
56      *x = CLAMP(*x,
57                 ctx->Const.ViewportBounds.Min, ctx->Const.ViewportBounds.Max);
58      *y = CLAMP(*y,
59                 ctx->Const.ViewportBounds.Min, ctx->Const.ViewportBounds.Max);
60   }
61}
62
63static void
64set_viewport_no_notify(struct gl_context *ctx, unsigned idx,
65                       GLfloat x, GLfloat y,
66                       GLfloat width, GLfloat height)
67{
68   if (ctx->ViewportArray[idx].X == x &&
69       ctx->ViewportArray[idx].Width == width &&
70       ctx->ViewportArray[idx].Y == y &&
71       ctx->ViewportArray[idx].Height == height)
72      return;
73
74   FLUSH_VERTICES(ctx, ctx->DriverFlags.NewViewport ? 0 : _NEW_VIEWPORT);
75   ctx->NewDriverState |= ctx->DriverFlags.NewViewport;
76
77   ctx->ViewportArray[idx].X = x;
78   ctx->ViewportArray[idx].Width = width;
79   ctx->ViewportArray[idx].Y = y;
80   ctx->ViewportArray[idx].Height = height;
81}
82
83struct gl_viewport_inputs {
84   GLfloat X, Y;                /**< position */
85   GLfloat Width, Height;       /**< size */
86};
87
88struct gl_depthrange_inputs {
89   GLdouble Near, Far;          /**< Depth buffer range */
90};
91
92static void
93viewport(struct gl_context *ctx, GLint x, GLint y, GLsizei width,
94         GLsizei height)
95{
96   struct gl_viewport_inputs input = { x, y, width, height };
97
98   /* Clamp the viewport to the implementation dependent values. */
99   clamp_viewport(ctx, &input.X, &input.Y, &input.Width, &input.Height);
100
101   /* The GL_ARB_viewport_array spec says:
102    *
103    *     "Viewport sets the parameters for all viewports to the same values
104    *     and is equivalent (assuming no errors are generated) to:
105    *
106    *     for (uint i = 0; i < MAX_VIEWPORTS; i++)
107    *         ViewportIndexedf(i, 1, (float)x, (float)y, (float)w, (float)h);"
108    *
109    * Set all of the viewports supported by the implementation, but only
110    * signal the driver once at the end.
111    */
112   for (unsigned i = 0; i < ctx->Const.MaxViewports; i++)
113      set_viewport_no_notify(ctx, i, input.X, input.Y, input.Width, input.Height);
114
115   if (ctx->Driver.Viewport)
116      ctx->Driver.Viewport(ctx);
117}
118
119/**
120 * Set the viewport.
121 * \sa Called via glViewport() or display list execution.
122 *
123 * Flushes the vertices and calls _mesa_set_viewport() with the given
124 * parameters.
125 */
126void GLAPIENTRY
127_mesa_Viewport_no_error(GLint x, GLint y, GLsizei width, GLsizei height)
128{
129   GET_CURRENT_CONTEXT(ctx);
130   viewport(ctx, x, y, width, height);
131}
132
133void GLAPIENTRY
134_mesa_Viewport(GLint x, GLint y, GLsizei width, GLsizei height)
135{
136   GET_CURRENT_CONTEXT(ctx);
137
138   if (MESA_VERBOSE & VERBOSE_API)
139      _mesa_debug(ctx, "glViewport %d %d %d %d\n", x, y, width, height);
140
141   if (width < 0 || height < 0) {
142      _mesa_error(ctx,  GL_INVALID_VALUE,
143                   "glViewport(%d, %d, %d, %d)", x, y, width, height);
144      return;
145   }
146
147   viewport(ctx, x, y, width, height);
148}
149
150
151/**
152 * Set new viewport parameters and update derived state.
153 * Usually called from _mesa_Viewport().
154 *
155 * \param ctx GL context.
156 * \param idx    Index of the viewport to be updated.
157 * \param x, y coordinates of the lower left corner of the viewport rectangle.
158 * \param width width of the viewport rectangle.
159 * \param height height of the viewport rectangle.
160 */
161void
162_mesa_set_viewport(struct gl_context *ctx, unsigned idx, GLfloat x, GLfloat y,
163                    GLfloat width, GLfloat height)
164{
165   clamp_viewport(ctx, &x, &y, &width, &height);
166   set_viewport_no_notify(ctx, idx, x, y, width, height);
167
168   if (ctx->Driver.Viewport)
169      ctx->Driver.Viewport(ctx);
170}
171
172static void
173viewport_array(struct gl_context *ctx, GLuint first, GLsizei count,
174               struct gl_viewport_inputs *inputs)
175{
176   for (GLsizei i = 0; i < count; i++) {
177      clamp_viewport(ctx, &inputs[i].X, &inputs[i].Y,
178                     &inputs[i].Width, &inputs[i].Height);
179
180      set_viewport_no_notify(ctx, i + first, inputs[i].X, inputs[i].Y,
181                             inputs[i].Width, inputs[i].Height);
182   }
183
184   if (ctx->Driver.Viewport)
185      ctx->Driver.Viewport(ctx);
186}
187
188void GLAPIENTRY
189_mesa_ViewportArrayv_no_error(GLuint first, GLsizei count, const GLfloat *v)
190{
191   GET_CURRENT_CONTEXT(ctx);
192
193   struct gl_viewport_inputs *p = (struct gl_viewport_inputs *)v;
194   viewport_array(ctx, first, count, p);
195}
196
197void GLAPIENTRY
198_mesa_ViewportArrayv(GLuint first, GLsizei count, const GLfloat *v)
199{
200   int i;
201   struct gl_viewport_inputs *p = (struct gl_viewport_inputs *)v;
202   GET_CURRENT_CONTEXT(ctx);
203
204   if (MESA_VERBOSE & VERBOSE_API)
205      _mesa_debug(ctx, "glViewportArrayv %d %d\n", first, count);
206
207   if ((first + count) > ctx->Const.MaxViewports) {
208      _mesa_error(ctx, GL_INVALID_VALUE,
209                  "glViewportArrayv: first (%d) + count (%d) > MaxViewports "
210                  "(%d)",
211                  first, count, ctx->Const.MaxViewports);
212      return;
213   }
214
215   /* Verify width & height */
216   for (i = 0; i < count; i++) {
217      if (p[i].Width < 0 || p[i].Height < 0) {
218         _mesa_error(ctx, GL_INVALID_VALUE,
219                     "glViewportArrayv: index (%d) width or height < 0 "
220                     "(%f, %f)",
221                     i + first, p[i].Width, p[i].Height);
222         return;
223      }
224   }
225
226   viewport_array(ctx, first, count, p);
227}
228
229static void
230viewport_indexed_err(struct gl_context *ctx, GLuint index, GLfloat x, GLfloat y,
231                     GLfloat w, GLfloat h, const char *function)
232{
233   if (MESA_VERBOSE & VERBOSE_API)
234      _mesa_debug(ctx, "%s(%d, %f, %f, %f, %f)\n",
235                  function, index, x, y, w, h);
236
237   if (index >= ctx->Const.MaxViewports) {
238      _mesa_error(ctx, GL_INVALID_VALUE,
239                  "%s: index (%d) >= MaxViewports (%d)",
240                  function, index, ctx->Const.MaxViewports);
241      return;
242   }
243
244   /* Verify width & height */
245   if (w < 0 || h < 0) {
246      _mesa_error(ctx, GL_INVALID_VALUE,
247                  "%s: index (%d) width or height < 0 (%f, %f)",
248                  function, index, w, h);
249      return;
250   }
251
252   _mesa_set_viewport(ctx, index, x, y, w, h);
253}
254
255void GLAPIENTRY
256_mesa_ViewportIndexedf_no_error(GLuint index, GLfloat x, GLfloat y,
257                                GLfloat w, GLfloat h)
258{
259   GET_CURRENT_CONTEXT(ctx);
260   _mesa_set_viewport(ctx, index, x, y, w, h);
261}
262
263void GLAPIENTRY
264_mesa_ViewportIndexedf(GLuint index, GLfloat x, GLfloat y,
265                       GLfloat w, GLfloat h)
266{
267   GET_CURRENT_CONTEXT(ctx);
268   viewport_indexed_err(ctx, index, x, y, w, h, "glViewportIndexedf");
269}
270
271void GLAPIENTRY
272_mesa_ViewportIndexedfv_no_error(GLuint index, const GLfloat *v)
273{
274   GET_CURRENT_CONTEXT(ctx);
275   _mesa_set_viewport(ctx, index, v[0], v[1], v[2], v[3]);
276}
277
278void GLAPIENTRY
279_mesa_ViewportIndexedfv(GLuint index, const GLfloat *v)
280{
281   GET_CURRENT_CONTEXT(ctx);
282   viewport_indexed_err(ctx, index, v[0], v[1], v[2], v[3],
283                        "glViewportIndexedfv");
284}
285
286static void
287set_depth_range_no_notify(struct gl_context *ctx, unsigned idx,
288                          GLclampd nearval, GLclampd farval)
289{
290   if (ctx->ViewportArray[idx].Near == nearval &&
291       ctx->ViewportArray[idx].Far == farval)
292      return;
293
294   /* The depth range is needed by program state constants. */
295   FLUSH_VERTICES(ctx, _NEW_VIEWPORT);
296   ctx->NewDriverState |= ctx->DriverFlags.NewViewport;
297
298   ctx->ViewportArray[idx].Near = CLAMP(nearval, 0.0, 1.0);
299   ctx->ViewportArray[idx].Far = CLAMP(farval, 0.0, 1.0);
300}
301
302void
303_mesa_set_depth_range(struct gl_context *ctx, unsigned idx,
304                      GLclampd nearval, GLclampd farval)
305{
306   set_depth_range_no_notify(ctx, idx, nearval, farval);
307
308   if (ctx->Driver.DepthRange)
309      ctx->Driver.DepthRange(ctx);
310}
311
312/**
313 * Called by glDepthRange
314 *
315 * \param nearval  specifies the Z buffer value which should correspond to
316 *                 the near clip plane
317 * \param farval  specifies the Z buffer value which should correspond to
318 *                the far clip plane
319 */
320void GLAPIENTRY
321_mesa_DepthRange(GLclampd nearval, GLclampd farval)
322{
323   unsigned i;
324   GET_CURRENT_CONTEXT(ctx);
325
326   if (MESA_VERBOSE&VERBOSE_API)
327      _mesa_debug(ctx, "glDepthRange %f %f\n", nearval, farval);
328
329   /* The GL_ARB_viewport_array spec says:
330    *
331    *     "DepthRange sets the depth range for all viewports to the same
332    *     values and is equivalent (assuming no errors are generated) to:
333    *
334    *     for (uint i = 0; i < MAX_VIEWPORTS; i++)
335    *         DepthRangeIndexed(i, n, f);"
336    *
337    * Set the depth range for all of the viewports supported by the
338    * implementation, but only signal the driver once at the end.
339    */
340   for (i = 0; i < ctx->Const.MaxViewports; i++)
341      set_depth_range_no_notify(ctx, i, nearval, farval);
342
343   if (ctx->Driver.DepthRange) {
344      ctx->Driver.DepthRange(ctx);
345   }
346}
347
348void GLAPIENTRY
349_mesa_DepthRangef(GLclampf nearval, GLclampf farval)
350{
351   _mesa_DepthRange(nearval, farval);
352}
353
354/**
355 * Update a range DepthRange values
356 *
357 * \param first   starting array index
358 * \param count   count of DepthRange items to update
359 * \param v       pointer to memory containing
360 *                GLclampd near and far clip-plane values
361 */
362static ALWAYS_INLINE void
363depth_range_arrayv(struct gl_context *ctx, GLuint first, GLsizei count,
364                   const struct gl_depthrange_inputs *const inputs)
365{
366   for (GLsizei i = 0; i < count; i++)
367      set_depth_range_no_notify(ctx, i + first, inputs[i].Near, inputs[i].Far);
368
369   if (ctx->Driver.DepthRange)
370      ctx->Driver.DepthRange(ctx);
371}
372
373void GLAPIENTRY
374_mesa_DepthRangeArrayv_no_error(GLuint first, GLsizei count, const GLclampd *v)
375{
376   GET_CURRENT_CONTEXT(ctx);
377
378   const struct gl_depthrange_inputs *const p =
379      (struct gl_depthrange_inputs *)v;
380   depth_range_arrayv(ctx, first, count, p);
381}
382
383void GLAPIENTRY
384_mesa_DepthRangeArrayv(GLuint first, GLsizei count, const GLclampd *v)
385{
386   const struct gl_depthrange_inputs *const p =
387      (struct gl_depthrange_inputs *) v;
388   GET_CURRENT_CONTEXT(ctx);
389
390   if (MESA_VERBOSE & VERBOSE_API)
391      _mesa_debug(ctx, "glDepthRangeArrayv %d %d\n", first, count);
392
393   if ((first + count) > ctx->Const.MaxViewports) {
394      _mesa_error(ctx, GL_INVALID_VALUE,
395                  "glDepthRangev: first (%d) + count (%d) >= MaxViewports (%d)",
396                  first, count, ctx->Const.MaxViewports);
397      return;
398   }
399
400   depth_range_arrayv(ctx, first, count, p);
401}
402
403void GLAPIENTRY
404_mesa_DepthRangeArrayfvOES(GLuint first, GLsizei count, const GLfloat *v)
405{
406   int i;
407   GET_CURRENT_CONTEXT(ctx);
408
409   if (MESA_VERBOSE & VERBOSE_API)
410      _mesa_debug(ctx, "glDepthRangeArrayfv %d %d\n", first, count);
411
412   if ((first + count) > ctx->Const.MaxViewports) {
413      _mesa_error(ctx, GL_INVALID_VALUE,
414                  "glDepthRangeArrayfv: first (%d) + count (%d) >= MaxViewports (%d)",
415                  first, count, ctx->Const.MaxViewports);
416      return;
417   }
418
419   for (i = 0; i < count; i++)
420      set_depth_range_no_notify(ctx, i + first, v[i * 2], v[i * 2 + 1]);
421
422   if (ctx->Driver.DepthRange)
423      ctx->Driver.DepthRange(ctx);
424}
425
426/**
427 * Update a single DepthRange
428 *
429 * \param index    array index to update
430 * \param nearval  specifies the Z buffer value which should correspond to
431 *                 the near clip plane
432 * \param farval   specifies the Z buffer value which should correspond to
433 *                 the far clip plane
434 */
435void GLAPIENTRY
436_mesa_DepthRangeIndexed_no_error(GLuint index, GLclampd nearval,
437                                 GLclampd farval)
438{
439   GET_CURRENT_CONTEXT(ctx);
440   _mesa_set_depth_range(ctx, index, nearval, farval);
441}
442
443
444void GLAPIENTRY
445_mesa_DepthRangeIndexed(GLuint index, GLclampd nearval, GLclampd farval)
446{
447   GET_CURRENT_CONTEXT(ctx);
448
449   if (MESA_VERBOSE & VERBOSE_API)
450      _mesa_debug(ctx, "glDepthRangeIndexed(%d, %f, %f)\n",
451                  index, nearval, farval);
452
453   if (index >= ctx->Const.MaxViewports) {
454      _mesa_error(ctx, GL_INVALID_VALUE,
455                  "glDepthRangeIndexed: index (%d) >= MaxViewports (%d)",
456                  index, ctx->Const.MaxViewports);
457      return;
458   }
459
460   _mesa_set_depth_range(ctx, index, nearval, farval);
461}
462
463void GLAPIENTRY
464_mesa_DepthRangeIndexedfOES(GLuint index, GLfloat nearval, GLfloat farval)
465{
466   _mesa_DepthRangeIndexed(index, nearval, farval);
467}
468
469/**
470 * Initialize the context viewport attribute group.
471 * \param ctx  the GL context.
472 */
473void _mesa_init_viewport(struct gl_context *ctx)
474{
475   unsigned i;
476
477   ctx->Transform.ClipOrigin = GL_LOWER_LEFT;
478   ctx->Transform.ClipDepthMode = GL_NEGATIVE_ONE_TO_ONE;
479
480   /* Note: ctx->Const.MaxViewports may not have been set by the driver yet,
481    * so just initialize all of them.
482    */
483   for (i = 0; i < MAX_VIEWPORTS; i++) {
484      /* Viewport group */
485      ctx->ViewportArray[i].X = 0;
486      ctx->ViewportArray[i].Y = 0;
487      ctx->ViewportArray[i].Width = 0;
488      ctx->ViewportArray[i].Height = 0;
489      ctx->ViewportArray[i].Near = 0.0;
490      ctx->ViewportArray[i].Far = 1.0;
491   }
492
493   ctx->SubpixelPrecisionBias[0] = 0;
494   ctx->SubpixelPrecisionBias[1] = 0;
495}
496
497
498static ALWAYS_INLINE void
499clip_control(struct gl_context *ctx, GLenum origin, GLenum depth, bool no_error)
500{
501   if (ctx->Transform.ClipOrigin == origin &&
502       ctx->Transform.ClipDepthMode == depth)
503      return;
504
505   if (!no_error &&
506       origin != GL_LOWER_LEFT && origin != GL_UPPER_LEFT) {
507      _mesa_error(ctx, GL_INVALID_ENUM, "glClipControl");
508      return;
509   }
510
511   if (!no_error &&
512       depth != GL_NEGATIVE_ONE_TO_ONE && depth != GL_ZERO_TO_ONE) {
513      _mesa_error(ctx, GL_INVALID_ENUM, "glClipControl");
514      return;
515   }
516
517   /* Affects transform state and the viewport transform */
518   FLUSH_VERTICES(ctx, ctx->DriverFlags.NewClipControl ? 0 :
519                  _NEW_TRANSFORM | _NEW_VIEWPORT);
520   ctx->NewDriverState |= ctx->DriverFlags.NewClipControl;
521
522   if (ctx->Transform.ClipOrigin != origin) {
523      ctx->Transform.ClipOrigin = origin;
524
525      /* Affects the winding order of the front face. */
526      if (ctx->DriverFlags.NewPolygonState)
527         ctx->NewDriverState |= ctx->DriverFlags.NewPolygonState;
528      else
529         ctx->NewState |= _NEW_POLYGON;
530
531      if (ctx->Driver.FrontFace)
532         ctx->Driver.FrontFace(ctx, ctx->Polygon.FrontFace);
533   }
534
535   if (ctx->Transform.ClipDepthMode != depth) {
536      ctx->Transform.ClipDepthMode = depth;
537
538      if (ctx->Driver.DepthRange)
539         ctx->Driver.DepthRange(ctx);
540   }
541}
542
543
544void GLAPIENTRY
545_mesa_ClipControl_no_error(GLenum origin, GLenum depth)
546{
547   GET_CURRENT_CONTEXT(ctx);
548   clip_control(ctx, origin, depth, true);
549}
550
551
552void GLAPIENTRY
553_mesa_ClipControl(GLenum origin, GLenum depth)
554{
555   GET_CURRENT_CONTEXT(ctx);
556
557   if (MESA_VERBOSE & VERBOSE_API)
558      _mesa_debug(ctx, "glClipControl(%s, %s)\n",
559	          _mesa_enum_to_string(origin),
560                  _mesa_enum_to_string(depth));
561
562   ASSERT_OUTSIDE_BEGIN_END(ctx);
563
564   if (!ctx->Extensions.ARB_clip_control) {
565      _mesa_error(ctx, GL_INVALID_OPERATION, "glClipControl");
566      return;
567   }
568
569   clip_control(ctx, origin, depth, false);
570}
571
572/**
573 * Computes the scaling and the translation part of the
574 * viewport transform matrix of the \param i-th viewport
575 * and writes that into \param scale and \param translate.
576 */
577void
578_mesa_get_viewport_xform(struct gl_context *ctx, unsigned i,
579                         float scale[3], float translate[3])
580{
581   float x = ctx->ViewportArray[i].X;
582   float y = ctx->ViewportArray[i].Y;
583   float half_width = 0.5f * ctx->ViewportArray[i].Width;
584   float half_height = 0.5f * ctx->ViewportArray[i].Height;
585   double n = ctx->ViewportArray[i].Near;
586   double f = ctx->ViewportArray[i].Far;
587
588   scale[0] = half_width;
589   translate[0] = half_width + x;
590   if (ctx->Transform.ClipOrigin == GL_UPPER_LEFT) {
591      scale[1] = -half_height;
592   } else {
593      scale[1] = half_height;
594   }
595   translate[1] = half_height + y;
596
597   if (ctx->Transform.ClipDepthMode == GL_NEGATIVE_ONE_TO_ONE) {
598      scale[2] = 0.5 * (f - n);
599      translate[2] = 0.5 * (n + f);
600   } else {
601      scale[2] = f - n;
602      translate[2] = n;
603   }
604}
605
606
607static void
608subpixel_precision_bias(struct gl_context *ctx, GLuint xbits, GLuint ybits)
609{
610   if (MESA_VERBOSE & VERBOSE_API)
611      _mesa_debug(ctx, "glSubpixelPrecisionBiasNV(%u, %u)\n", xbits, ybits);
612
613   ctx->SubpixelPrecisionBias[0] = xbits;
614   ctx->SubpixelPrecisionBias[1] = ybits;
615
616   FLUSH_VERTICES(ctx, 0);
617   ctx->NewDriverState |=
618      ctx->DriverFlags.NewNvConservativeRasterizationParams;
619}
620
621void GLAPIENTRY
622_mesa_SubpixelPrecisionBiasNV_no_error(GLuint xbits, GLuint ybits)
623{
624   GET_CURRENT_CONTEXT(ctx);
625
626   if (MESA_VERBOSE & VERBOSE_API)
627      _mesa_debug(ctx, "glSubpixelPrecisionBiasNV(%u, %u)\n", xbits, ybits);
628
629   subpixel_precision_bias(ctx, xbits, ybits);
630}
631
632void GLAPIENTRY
633_mesa_SubpixelPrecisionBiasNV(GLuint xbits, GLuint ybits)
634{
635   GET_CURRENT_CONTEXT(ctx);
636
637   if (MESA_VERBOSE & VERBOSE_API)
638      _mesa_debug(ctx, "glSubpixelPrecisionBiasNV(%u, %u)\n", xbits, ybits);
639
640   ASSERT_OUTSIDE_BEGIN_END(ctx);
641
642   if (!ctx->Extensions.NV_conservative_raster) {
643      _mesa_error(ctx, GL_INVALID_OPERATION,
644                  "glSubpixelPrecisionBiasNV not supported");
645      return;
646   }
647
648   if (xbits > ctx->Const.MaxSubpixelPrecisionBiasBits) {
649      _mesa_error(ctx, GL_INVALID_VALUE, "glSubpixelPrecisionBiasNV");
650      return;
651   }
652
653   if (ybits > ctx->Const.MaxSubpixelPrecisionBiasBits) {
654      _mesa_error(ctx, GL_INVALID_VALUE, "glSubpixelPrecisionBiasNV");
655      return;
656   }
657
658   subpixel_precision_bias(ctx, xbits, ybits);
659}
660