aboutsummaryrefslogtreecommitdiff
path: root/src/mesa/drivers/verite/vrtris.c
diff options
context:
space:
mode:
authorRadosław Kujawa <radoslaw.kujawa@c0ff33.net>2026-07-17 14:54:32 +0200
committerRadosław Kujawa <radoslaw.kujawa@c0ff33.net>2026-07-17 14:54:32 +0200
commit2702c90d7a39fca2a3319c5e915fd290cb31fc70 (patch)
tree283c4d851efef7e3a7cd03ba80c87e2bffcdbb2c /src/mesa/drivers/verite/vrtris.c
parent836167945ec2c7ad5acdf0aa17ce35ec1b9428a6 (diff)
Initial import.
Diffstat (limited to 'src/mesa/drivers/verite/vrtris.c')
-rw-r--r--src/mesa/drivers/verite/vrtris.c1085
1 files changed, 1085 insertions, 0 deletions
diff --git a/src/mesa/drivers/verite/vrtris.c b/src/mesa/drivers/verite/vrtris.c
new file mode 100644
index 0000000..48c270f
--- /dev/null
+++ b/src/mesa/drivers/verite/vrtris.c
@@ -0,0 +1,1085 @@
+/*
+ * Copyright (c) 2026 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Radoslaw Kujawa.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * veritemesa hardware triangles.
+ */
+
+#include <math.h>
+#include <stdlib.h>
+
+#include "vrdrv.h"
+
+#include "context.h"
+#include "imports.h"
+#include "macros.h"
+#include "colormac.h"
+#include "swrast/swrast.h"
+#include "swrast_setup/swrast_setup.h"
+#include "swrast_setup/ss_context.h"
+#include "tnl/tnl.h"
+#include "tnl/t_context.h"
+
+static unsigned long g_vr_nat_polys, g_vr_nat_tris;
+static unsigned long g_vr_pt_polys, g_vr_pt_tris;
+static unsigned long g_vr_cb_tris;
+
+static int g_vr_nocache;
+static int g_vr_noblendfan;
+static float g_vr_zbias;
+
+/*
+ * Window-Z finalizer.
+ */
+#define VR_ZSCALE (65000.0F / 65535.0F)
+
+static GLfloat
+vr_winz(GLfloat mesa_z)
+{
+ GLfloat z = mesa_z * VR_ZSCALE;
+
+ if (z < 0.0F)
+ return 0.0F;
+ if (z > 65000.0F)
+ return 65000.0F;
+ return z;
+}
+
+static void
+vr_emit_at(vrmesa_context *vmesa, GLfloat wx, GLfloat wy,
+ const SWvertex *v, const GLchan color[4], struct rlgl_vtx *o)
+{
+ const GLfloat w = (GLfloat)vmesa->rl.v.width;
+ const GLfloat h = (GLfloat)vmesa->rl.v.height;
+ GLfloat x = wx, y;
+
+ o->r = (GLfloat)color[0];
+ o->g = (GLfloat)color[1];
+ o->b = (GLfloat)color[2];
+
+ if (x < 0.0F) x = 0.0F;
+ if (x > w) x = w;
+ o->x = x;
+ y = h - wy;
+ if (y < 0.0F) y = 0.0F;
+ if (y > h) y = h;
+ o->y = y;
+ o->z = vr_winz(v->win[2]); /* rescale 65535->65000, see vr_winz */
+ /*
+ * u/v in texels, q = 1/w
+ */
+ if (vmesa->tex_on) {
+ o->u = v->texcoord[0][0] * vmesa->tex_w - vmesa->tex_off;
+ o->v = v->texcoord[0][1] * vmesa->tex_h - vmesa->tex_off;
+ } else {
+ o->u = 0.0F;
+ o->v = 0.0F;
+ }
+ o->q = v->win[3];
+ /*
+ * Fog factor for the KFXYZUVQ path.
+ */
+ o->f = CLAMP(v->fog, 0.0F, 1.0F) * 255.0F;
+}
+
+static void
+vr_emit_vtx(vrmesa_context *vmesa, const SWvertex *v,
+ const GLchan color[4], struct rlgl_vtx *o)
+{
+
+ vr_emit_at(vmesa, v->win[0], v->win[1], v, color, o);
+}
+
+/*
+ * PE blend factor mapping
+ */
+static GLboolean
+vr_blend_factor(GLenum f, GLboolean is_src, GLuint *code,
+ GLboolean *needs_alpha)
+{
+
+ switch (f) {
+ case GL_ZERO: *code = RLGL_BL_ZERO; break;
+ case GL_ONE: *code = RLGL_BL_ONE; break;
+ case GL_DST_ALPHA: *code = RLGL_BL_ONE; break;
+ case GL_ONE_MINUS_DST_ALPHA: *code = RLGL_BL_ZERO; break;
+ case GL_SRC_ALPHA:
+ *code = RLGL_BL_SRCALPHA;
+ *needs_alpha = GL_TRUE;
+ break;
+ case GL_ONE_MINUS_SRC_ALPHA:
+ *code = RLGL_BL_INVSRCALPHA;
+ *needs_alpha = GL_TRUE;
+ break;
+ /*
+ * Color-factor codes are SIDE-RELATIVE
+ */
+ case GL_SRC_COLOR:
+ *code = is_src ? RLGL_BL_OWNCOLOR : RLGL_BL_OTHERCOLOR;
+ break;
+ case GL_ONE_MINUS_SRC_COLOR:
+ *code = is_src ? RLGL_BL_INVOWNCOLOR :
+ RLGL_BL_INVOTHERCOLOR;
+ break;
+ case GL_DST_COLOR:
+ *code = is_src ? RLGL_BL_OTHERCOLOR : RLGL_BL_OWNCOLOR;
+ break;
+ case GL_ONE_MINUS_DST_COLOR:
+ *code = is_src ? RLGL_BL_INVOTHERCOLOR :
+ RLGL_BL_INVOWNCOLOR;
+ break;
+ default:
+ return GL_FALSE;
+ }
+ return GL_TRUE;
+}
+
+GLboolean
+vrBlendMap(const GLcontext *ctx, GLuint *srcf, GLuint *dstf,
+ GLboolean *needs_alpha)
+{
+
+ *needs_alpha = GL_FALSE;
+ if (ctx->Color.BlendEquationRGB != GL_FUNC_ADD)
+ return GL_FALSE;
+ if (!vr_blend_factor(ctx->Color.BlendSrcRGB, GL_TRUE, srcf,
+ needs_alpha))
+ return GL_FALSE;
+ if (!vr_blend_factor(ctx->Color.BlendDstRGB, GL_FALSE, dstf,
+ needs_alpha))
+ return GL_FALSE;
+ return GL_TRUE;
+}
+
+/*
+ * Clamp a Mesa-space window Z
+ */
+static GLfloat
+vr_clampz(GLfloat z)
+{
+
+ if (z < 0.0F)
+ return 0.0F;
+ if (z > 65535.0F)
+ return 65535.0F;
+ return z;
+}
+
+/*
+ * Polygon offset (glPolygonOffset) computed exactly as swrast does at
+ * setup
+ */
+static GLfloat
+vr_poly_offset(GLcontext *ctx, const SWvertex *v0, const SWvertex *v1,
+ const SWvertex *v2)
+{
+ GLfloat ex = v0->win[0] - v2->win[0], ey = v0->win[1] - v2->win[1];
+ GLfloat fx = v1->win[0] - v2->win[0], fy = v1->win[1] - v2->win[1];
+ GLfloat cc = ex * fy - ey * fx;
+ GLfloat off = ctx->Polygon.OffsetUnits; /* * MRD (= 1) */
+
+ if (cc * cc > 1e-16F) {
+ GLfloat ez = v0->win[2] - v2->win[2];
+ GLfloat fz = v1->win[2] - v2->win[2];
+ GLfloat ooa = 1.0F / cc;
+ GLfloat dzdx = (ey * fz - ez * fy) * ooa;
+ GLfloat dzdy = (ez * fx - ex * fz) * ooa;
+ GLfloat m, nz;
+
+ if (dzdx < 0.0F) dzdx = -dzdx;
+ if (dzdy < 0.0F) dzdy = -dzdy;
+ m = dzdx > dzdy ? dzdx : dzdy;
+ off += m * ctx->Polygon.OffsetFactor;
+ /* clamp so no vertex Z goes below 0 (matches swrast) */
+ nz = -v0->win[2]; if (off < nz) off = nz;
+ nz = -v1->win[2]; if (off < nz) off = nz;
+ nz = -v2->win[2]; if (off < nz) off = nz;
+ }
+ return off;
+}
+
+static void
+vr_tri(GLcontext *ctx, GLuint e0, GLuint e1, GLuint e2, GLuint eprov)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ const SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ const SWvertex *v0 = &verts[e0], *v1 = &verts[e1], *v2 = &verts[e2];
+ struct rlgl_vtx a, b, c;
+ const GLboolean flat = (ctx->Light.ShadeModel == GL_FLAT);
+ const GLchan *pc = verts[eprov].color;
+
+ if (vmesa->blend_alpha || vmesa->atest_alpha) {
+ GLuint a8 = flat ? pc[3] : v0->color[3];
+
+ if (!flat && (v1->color[3] != a8 || v2->color[3] != a8)) {
+ VRP_FALLBACK_HOT(VRP_FB_TRI_ALPHAGRAD);
+ _swrast_Triangle(ctx, v0, v1, v2);
+ return;
+ }
+ if (a8 != vmesa->fg_alpha) {
+ v3d_st(&vmesa->rl.v, V3D_PE_FGCOLOR, a8 << 24);
+ vmesa->fg_alpha = a8;
+ VRP_HOT(fgcolor_writes);
+ }
+ }
+
+ if (ctx->Polygon.CullFlag) {
+ /* window y is flipped on the PE: GL-CCW = negative area */
+ GLfloat area =
+ (v1->win[0] - v0->win[0]) * (v0->win[1] - v2->win[1]) -
+ (v2->win[0] - v0->win[0]) * (v0->win[1] - v1->win[1]);
+ GLboolean front = (area < 0.0F) ^
+ (ctx->Polygon.FrontFace == GL_CW);
+
+ if (ctx->Polygon.CullFaceMode == GL_FRONT_AND_BACK)
+ return;
+ if (ctx->Polygon.CullFaceMode == GL_BACK && !front)
+ return;
+ if (ctx->Polygon.CullFaceMode == GL_FRONT && front)
+ return;
+ }
+
+ /*
+ * Per-polygon mipmapping
+ */
+ if (vmesa->tex_on && vmesa->tex_mip) {
+ const GLfloat bw = vmesa->tex_base_w;
+ const GLfloat bh = vmesa->tex_base_h;
+ GLfloat du1 = (v1->texcoord[0][0] - v0->texcoord[0][0]) * bw;
+ GLfloat dv1 = (v1->texcoord[0][1] - v0->texcoord[0][1]) * bh;
+ GLfloat du2 = (v2->texcoord[0][0] - v0->texcoord[0][0]) * bw;
+ GLfloat dv2 = (v2->texcoord[0][1] - v0->texcoord[0][1]) * bh;
+ GLfloat auv = du1 * dv2 - du2 * dv1;
+ GLfloat axy =
+ (v1->win[0] - v0->win[0]) * (v2->win[1] - v0->win[1]) -
+ (v2->win[0] - v0->win[0]) * (v1->win[1] - v0->win[1]);
+ GLfloat rho2;
+ GLint level = 0, nl = vrTexNumLevels(vmesa->tex_cur);
+ GLint bilin;
+ GLint lw, lh;
+
+ if (auv < 0.0F) auv = -auv;
+ if (axy < 0.0F) axy = -axy;
+ rho2 = (axy > 1e-12F) ? auv / axy : 0.0F;
+ if (rho2 > 1.0F) {
+ GLfloat lod = 0.5F * LOG2(rho2);
+
+ level = (GLint)(lod + 0.5F);
+ if (level >= nl)
+ level = nl - 1;
+ }
+ bilin = (level == 0 && rho2 <= 1.0F) ?
+ vmesa->tex_mag_bilin : vmesa->tex_min_bilin;
+ if (g_vr_nocache || level != vmesa->cur_level) {
+ rlgl_tex_bind(&vmesa->rl,
+ vrTexLevel(vmesa->tex_cur, level));
+ vmesa->cur_level = level;
+ VRP_HOT(mip_binds);
+ }
+ if (g_vr_nocache || bilin != vmesa->cur_bilin) {
+ rlgl_tex_filter(&vmesa->rl, bilin);
+ vmesa->cur_bilin = bilin;
+ VRP_HOT(filt_changes);
+ }
+ lw = (GLint)bw >> level;
+ lh = (GLint)bh >> level;
+ vmesa->tex_w = (GLfloat)(lw > 0 ? lw : 1);
+ vmesa->tex_h = (GLfloat)(lh > 0 ? lh : 1);
+ vmesa->tex_off = bilin ? 0.5F : 0.0F;
+ }
+
+ VRP_ZE3(VRP_Z_TRI_EMIT);
+ vr_emit_vtx(vmesa, v0, flat ? pc : v0->color, &a);
+ vr_emit_vtx(vmesa, v1, flat ? pc : v1->color, &b);
+ vr_emit_vtx(vmesa, v2, flat ? pc : v2->color, &c);
+ /* polygon offset: per-poly window-Z bias over the emitted z */
+ if (ctx->Polygon.OffsetFill) {
+ GLfloat off = vr_poly_offset(ctx, v0, v1, v2);
+
+ a.z = vr_winz(v0->win[2] + off);
+ b.z = vr_winz(v1->win[2] + off);
+ c.z = vr_winz(v2->win[2] + off);
+ }
+ rlgl_tri(&vmesa->rl, &a, &b, &c);
+ vmesa->engine_dirty = GL_TRUE;
+ VRP_ZX3(VRP_Z_TRI_EMIT);
+}
+
+static void
+vr_triangle(GLcontext *ctx, GLuint e0, GLuint e1, GLuint e2)
+{
+
+ g_vr_cb_tris++; /* clipped path / any Render.Triangle caller */
+ vr_tri(ctx, e0, e1, e2, e2);
+}
+
+static void
+vr_quad(GLcontext *ctx, GLuint e0, GLuint e1, GLuint e2, GLuint e3)
+{
+
+ vr_tri(ctx, e0, e1, e2, e3);
+ vr_tri(ctx, e0, e2, e3, e3);
+}
+
+/* longest run collapsed to one native fan/strip, larger falls to per-tri */
+#define VR_MAX_RUN 128
+
+static void
+vr_strip_stat(void)
+{
+
+ if (_mesa_getenv("VRMESA_STRIPSTAT"))
+ _mesa_printf("VRSTRIP native_polys=%lu native_tris=%lu "
+ "pertri_polys=%lu pertri_tris=%lu clipped_tris=%lu\n",
+ g_vr_nat_polys, g_vr_nat_tris, g_vr_pt_polys, g_vr_pt_tris,
+ g_vr_cb_tris);
+}
+
+static GLuint
+vidx(const GLuint *elt, GLuint j)
+{
+
+ return elt ? elt[j] : j;
+}
+
+static GLboolean
+vr_native_run(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count,
+ GLboolean cull_uniform, GLuint flat_prov)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ const SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ const GLchan *fc = (flat_prov != ~0U) ?
+ verts[vidx(elt, flat_prov)].color : NULL;
+ struct rlgl_vtx buf[VR_MAX_RUN];
+ GLuint n = count - start, i;
+
+ if (n < 3 || n > VR_MAX_RUN)
+ return GL_FALSE;
+ if (ctx->Polygon.CullFlag) {
+ const SWvertex *v0, *v1, *v2;
+ GLfloat area;
+ GLboolean front;
+
+ /*
+ * A tri-strip's winding alternates per triangle, and a
+ * non-convex tri-fan's can flip
+ */
+ if (!cull_uniform)
+ return GL_FALSE;
+ v0 = &verts[vidx(elt, start)];
+ v1 = &verts[vidx(elt, start + 1)];
+ v2 = &verts[vidx(elt, start + 2)];
+ area = (v1->win[0] - v0->win[0]) * (v0->win[1] - v2->win[1]) -
+ (v2->win[0] - v0->win[0]) * (v0->win[1] - v1->win[1]);
+ front = (area < 0.0F) ^ (ctx->Polygon.FrontFace == GL_CW);
+ if (ctx->Polygon.CullFaceMode == GL_FRONT_AND_BACK)
+ return GL_TRUE; /* whole run culled */
+ if (ctx->Polygon.CullFaceMode == GL_BACK && !front)
+ return GL_TRUE;
+ if (ctx->Polygon.CullFaceMode == GL_FRONT && front)
+ return GL_TRUE;
+ }
+ for (i = 0; i < n; i++) {
+ const SWvertex *v = &verts[vidx(elt, start + i)];
+
+ vr_emit_vtx(vmesa, v, fc ? fc : v->color, &buf[i]);
+ if (g_vr_zbias != 0.0F) {
+ buf[i].z -= g_vr_zbias;
+ if (buf[i].z < 0.0F)
+ buf[i].z = 0.0F;
+ }
+ }
+ rlgl_trifan(&vmesa->rl, buf, (int)n);
+ g_vr_nat_polys++;
+ g_vr_nat_tris += n - 2;
+ vmesa->engine_dirty = GL_TRUE;
+ return GL_TRUE;
+}
+
+static void
+vr_c_triangles(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count)
+{
+ GLuint j;
+
+ for (j = start + 2; j < count; j += 3)
+ vr_tri(ctx, vidx(elt, j - 2), vidx(elt, j - 1), vidx(elt, j),
+ vidx(elt, j));
+}
+
+static void
+vr_c_tri_strip(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count)
+{
+ GLuint j, parity = 0;
+
+ for (j = start + 2; j < count; j++, parity ^= 1)
+ vr_tri(ctx, vidx(elt, j - 2 + parity), vidx(elt, j - 1 - parity),
+ vidx(elt, j), vidx(elt, j));
+}
+
+static void
+vr_c_tri_fan(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ GLuint j;
+
+ if (vmesa->strips_ok && ctx->Light.ShadeModel == GL_SMOOTH &&
+ vr_native_run(ctx, elt, start, count, GL_FALSE, ~0U))
+ return;
+ g_vr_pt_polys++;
+ g_vr_pt_tris += count - start - 2;
+ for (j = start + 2; j < count; j++)
+ vr_tri(ctx, vidx(elt, start), vidx(elt, j - 1), vidx(elt, j),
+ vidx(elt, j));
+}
+
+static void
+vr_c_poly(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ GLboolean flat = (ctx->Light.ShadeModel == GL_FLAT);
+ GLuint j;
+
+ if (vmesa->strips_ok && vr_native_run(ctx, elt, start, count,
+ GL_TRUE, flat ? start : ~0U)) /* cull_uniform: convex */
+ return;
+ g_vr_pt_polys++;
+ g_vr_pt_tris += count - start - 2;
+ for (j = start + 2; j < count; j++)
+ vr_tri(ctx, vidx(elt, j - 1), vidx(elt, j), vidx(elt, start),
+ vidx(elt, start));
+}
+
+static void
+vr_c_quads(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count)
+{
+ GLuint j;
+
+ for (j = start + 3; j < count; j += 4)
+ vr_quad(ctx, vidx(elt, j - 3), vidx(elt, j - 2),
+ vidx(elt, j - 1), vidx(elt, j));
+}
+
+static void
+vr_c_quad_strip(GLcontext *ctx, const GLuint *elt, GLuint start, GLuint count)
+{
+ GLuint j;
+
+ for (j = start + 3; j < count; j += 2)
+ vr_quad(ctx, vidx(elt, j - 1), vidx(elt, j - 3),
+ vidx(elt, j - 2), vidx(elt, j));
+}
+
+/* generate the {verts,elts} PrimTab wrappers around each run core */
+#define VR_TAB(core) \
+static void core##_verts(GLcontext *ctx, GLuint s, GLuint c, GLuint f) \
+{ \
+ (void)f; \
+ core(ctx, NULL, s, c); \
+} \
+static void core##_elts(GLcontext *ctx, GLuint s, GLuint c, GLuint f) \
+{ \
+ (void)f; \
+ core(ctx, TNL_CONTEXT(ctx)->vb.Elts, s, c); \
+}
+
+VR_TAB(vr_c_triangles)
+VR_TAB(vr_c_tri_strip)
+VR_TAB(vr_c_tri_fan)
+VR_TAB(vr_c_poly)
+VR_TAB(vr_c_quads)
+VR_TAB(vr_c_quad_strip)
+
+static tnl_render_func vr_tab_verts[GL_POLYGON + 2];
+static tnl_render_func vr_tab_elts[GL_POLYGON + 2];
+static GLboolean vr_tab_ready = GL_FALSE;
+
+static void
+vr_build_tables(TNLcontext *tnl)
+{
+ GLuint i;
+
+ if (vr_tab_ready)
+ return;
+ for (i = 0; i < GL_POLYGON + 2; i++) {
+ vr_tab_verts[i] = tnl->Driver.Render.PrimTabVerts[i];
+ vr_tab_elts[i] = tnl->Driver.Render.PrimTabElts[i];
+ }
+ vr_tab_verts[GL_TRIANGLES] = vr_c_triangles_verts;
+ vr_tab_verts[GL_TRIANGLE_STRIP] = vr_c_tri_strip_verts;
+ vr_tab_verts[GL_TRIANGLE_FAN] = vr_c_tri_fan_verts;
+ vr_tab_verts[GL_POLYGON] = vr_c_poly_verts;
+ vr_tab_verts[GL_QUADS] = vr_c_quads_verts;
+ vr_tab_verts[GL_QUAD_STRIP] = vr_c_quad_strip_verts;
+ vr_tab_elts[GL_TRIANGLES] = vr_c_triangles_elts;
+ vr_tab_elts[GL_TRIANGLE_STRIP] = vr_c_tri_strip_elts;
+ vr_tab_elts[GL_TRIANGLE_FAN] = vr_c_tri_fan_elts;
+ vr_tab_elts[GL_POLYGON] = vr_c_poly_elts;
+ vr_tab_elts[GL_QUADS] = vr_c_quads_elts;
+ vr_tab_elts[GL_QUAD_STRIP] = vr_c_quad_strip_elts;
+ vr_tab_ready = GL_TRUE;
+}
+
+/*
+ * Line as a perpendicular-offset quad (2 tris), width = Line._Width.
+ */
+static void
+vr_line(GLcontext *ctx, GLuint e0, GLuint e1)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ const SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ const SWvertex *v0 = &verts[e0], *v1 = &verts[e1];
+ const GLboolean flat = (ctx->Light.ShadeModel == GL_FLAT);
+ const GLchan *c0 = flat ? v1->color : v0->color;
+ const GLchan *c1 = v1->color; /* provoking = last vertex */
+ GLfloat dx = v1->win[0] - v0->win[0];
+ GLfloat dy = v1->win[1] - v0->win[1];
+ GLfloat len = (GLfloat)sqrt(dx * dx + dy * dy);
+ GLfloat hw = ctx->Line._Width * 0.5F;
+ GLfloat px, py;
+ struct rlgl_vtx a, b, c, d;
+
+ if (len < 1e-4F)
+ return; /* degenerate */
+ if (hw < 0.75F) {
+ GLfloat adx = dx < 0.0F ? -dx : dx;
+ GLfloat ady = dy < 0.0F ? -dy : dy;
+
+ if (adx > 0.5F && ady > 0.5F)
+ hw = 0.75F;
+ }
+ px = -dy / len * hw; /* perpendicular * half-width */
+ py = dx / len * hw;
+ vr_emit_at(vmesa, v0->win[0] + px, v0->win[1] + py, v0, c0, &a);
+ vr_emit_at(vmesa, v0->win[0] - px, v0->win[1] - py, v0, c0, &b);
+ vr_emit_at(vmesa, v1->win[0] - px, v1->win[1] - py, v1, c1, &c);
+ vr_emit_at(vmesa, v1->win[0] + px, v1->win[1] + py, v1, c1, &d);
+ rlgl_tri(&vmesa->rl, &a, &b, &c);
+ rlgl_tri(&vmesa->rl, &a, &c, &d);
+ vmesa->engine_dirty = GL_TRUE;
+}
+
+/*
+ * Native hardware line via the microcode's own line drawers
+ */
+static void
+vr_line_native(GLcontext *ctx, GLuint e0, GLuint e1, GLboolean aa)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ const SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ const SWvertex *v0 = &verts[e0], *v1 = &verts[e1];
+ const GLboolean flat = (ctx->Light.ShadeModel == GL_FLAT);
+ const GLchan *c0 = flat ? v1->color : v0->color;
+ const GLchan *c1 = v1->color; /* provoking = last vertex */
+ struct rlgl_vtx a, b;
+
+ vr_emit_vtx(vmesa, v0, c0, &a);
+ vr_emit_vtx(vmesa, v1, c1, &b);
+ a.x -= 0.5F; a.y -= 0.5F;
+ b.x -= 0.5F; b.y -= 0.5F;
+ if (aa)
+ rlgl_aaline(&vmesa->rl, &a, &b);
+ else
+ rlgl_line(&vmesa->rl, &a, &b);
+ vmesa->engine_dirty = GL_TRUE;
+}
+
+static void
+vr_hwline(GLcontext *ctx, GLuint e0, GLuint e1)
+{
+
+ vr_line_native(ctx, e0, e1, GL_FALSE);
+}
+
+static void
+vr_aaline(GLcontext *ctx, GLuint e0, GLuint e1)
+{
+
+ vr_line_native(ctx, e0, e1, GL_TRUE);
+}
+
+/* One point as a centered square (2 tris), half-side hs = Point._Size/2. */
+static void
+vr_point_emit(vrmesa_context *vmesa, const SWvertex *v, GLfloat hs)
+{
+ struct rlgl_vtx a, b, c, d;
+ GLfloat x = v->win[0], y = v->win[1];
+
+ vr_emit_at(vmesa, x - hs, y - hs, v, v->color, &a);
+ vr_emit_at(vmesa, x + hs, y - hs, v, v->color, &b);
+ vr_emit_at(vmesa, x + hs, y + hs, v, v->color, &c);
+ vr_emit_at(vmesa, x - hs, y + hs, v, v->color, &d);
+ rlgl_tri(&vmesa->rl, &a, &b, &c);
+ rlgl_tri(&vmesa->rl, &a, &c, &d);
+}
+
+static void
+vr_points(GLcontext *ctx, GLuint first, GLuint last)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ const SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ struct vertex_buffer *VB = &TNL_CONTEXT(ctx)->vb;
+ const GLfloat hs = ctx->Point._Size * 0.5F;
+ GLuint i;
+
+ for (i = first; i < last; i++) {
+ GLuint j = VB->Elts ? VB->Elts[i] : i;
+
+ if (VB->ClipMask[j])
+ continue;
+ vr_point_emit(vmesa, &verts[j], hs);
+ }
+ vmesa->engine_dirty = GL_TRUE;
+}
+
+/*
+ * Native anti-aliased points (GL_POINT_SMOOTH)
+ */
+static void
+vr_aapoints(GLcontext *ctx, GLuint first, GLuint last)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ const SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ struct vertex_buffer *VB = &TNL_CONTEXT(ctx)->vb;
+ const GLfloat sz = ctx->Point._Size;
+ GLuint i;
+
+ rlgl_aapoint_begin(&vmesa->rl);
+ for (i = first; i < last; i++) {
+ GLuint j = VB->Elts ? VB->Elts[i] : i;
+ struct rlgl_vtx a;
+
+ if (VB->ClipMask[j])
+ continue;
+ vr_emit_vtx(vmesa, &verts[j], verts[j].color, &a);
+ rlgl_aapoint(&vmesa->rl, &a, sz);
+ }
+ rlgl_aapoint_end(&vmesa->rl);
+ rlgl_depth_func(&vmesa->rl,
+ ctx->Depth.Test && ctx->Depth.Func != GL_ALWAYS,
+ ctx->Depth.Func == GL_LESS ? RLGL_Z_LT : RLGL_Z_LE);
+ /* Z-write only with the test enabled (see vr_update_state) */
+ rlgl_depth_mask(&vmesa->rl, ctx->Depth.Test && ctx->Depth.Mask);
+ vmesa->engine_dirty = GL_TRUE;
+}
+
+/*
+ * Unfilled triangle (glPolygonMode GL_LINE / GL_POINT).
+ */
+static void
+vr_unfilled_tri(GLcontext *ctx, GLuint e0, GLuint e1, GLuint e2)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ SWvertex *verts = SWSETUP_CONTEXT(ctx)->verts;
+ SWvertex *v0 = &verts[e0], *v1 = &verts[e1], *v2 = &verts[e2];
+ const GLubyte *ef = TNL_CONTEXT(ctx)->vb.EdgeFlag;
+ GLfloat ex = v0->win[0] - v2->win[0], ey = v0->win[1] - v2->win[1];
+ GLfloat fx = v1->win[0] - v2->win[0], fy = v1->win[1] - v2->win[1];
+ GLfloat cc = ex * fy - ey * fx;
+ GLuint facing = (cc < 0.0F) ^ ctx->Polygon._FrontBit;
+ GLenum mode = facing ? ctx->Polygon.BackMode : ctx->Polygon.FrontMode;
+ GLchan s0[4], s1[4];
+ GLfloat z0 = 0, z1 = 0, z2 = 0;
+ GLboolean flat, offed = GL_FALSE;
+
+ if (mode == GL_FILL) { /* mixed modes: fill this face */
+ vr_tri(ctx, e0, e1, e2, e2);
+ return;
+ }
+ if (ctx->Polygon.CullFlag) { /* whole-face cull, per swsetup */
+ if (facing == 1 && ctx->Polygon.CullFaceMode != GL_FRONT)
+ return;
+ if (facing == 0 && ctx->Polygon.CullFaceMode != GL_BACK)
+ return;
+ }
+
+ flat = (ctx->Light.ShadeModel == GL_FLAT);
+ if (flat) {
+ COPY_CHAN4(s0, v0->color);
+ COPY_CHAN4(s1, v1->color);
+ COPY_CHAN4(v0->color, v2->color);
+ COPY_CHAN4(v1->color, v2->color);
+ }
+ if ((mode == GL_LINE && ctx->Polygon.OffsetLine) ||
+ (mode == GL_POINT && ctx->Polygon.OffsetPoint)) {
+ GLfloat off = vr_poly_offset(ctx, v0, v1, v2);
+
+ z0 = v0->win[2]; z1 = v1->win[2]; z2 = v2->win[2];
+ v0->win[2] = vr_clampz(z0 + off);
+ v1->win[2] = vr_clampz(z1 + off);
+ v2->win[2] = vr_clampz(z2 + off);
+ offed = GL_TRUE;
+ }
+
+ if (mode == GL_POINT) {
+ GLfloat hs = ctx->Point._Size * 0.5F;
+
+ if (ef[e0]) vr_point_emit(vmesa, v0, hs);
+ if (ef[e1]) vr_point_emit(vmesa, v1, hs);
+ if (ef[e2]) vr_point_emit(vmesa, v2, hs);
+ } else { /* GL_LINE */
+ if (ef[e0]) vr_line(ctx, e0, e1);
+ if (ef[e1]) vr_line(ctx, e1, e2);
+ if (ef[e2]) vr_line(ctx, e2, e0);
+ }
+ vmesa->engine_dirty = GL_TRUE;
+
+ if (offed) {
+ v0->win[2] = z0;
+ v1->win[2] = z1;
+ v2->win[2] = z2;
+ }
+ if (flat) {
+ COPY_CHAN4(v0->color, s0);
+ COPY_CHAN4(v1->color, s1);
+ }
+}
+
+/*
+ * Unfilled quad
+ */
+static void
+vr_unfilled_quad(GLcontext *ctx, GLuint v0, GLuint v1, GLuint v2, GLuint v3)
+{
+ GLubyte *ef = TNL_CONTEXT(ctx)->vb.EdgeFlag;
+ GLubyte ef1 = ef[v1], ef3 = ef[v3];
+
+ ef[v1] = 0;
+ vr_unfilled_tri(ctx, v0, v1, v3);
+ ef[v1] = ef1;
+ ef[v3] = 0;
+ vr_unfilled_tri(ctx, v1, v2, v3);
+ ef[v3] = ef3;
+}
+
+/*
+ * State gate for the v1 hardware path.
+ */
+void
+vrCheckHwRender(GLcontext *ctx)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ GLboolean hw = GL_TRUE;
+
+ vmesa->blend_alpha = GL_FALSE;
+ vmesa->atest_alpha = GL_FALSE;
+ if (ctx->Texture._EnabledUnits != 0 && !vrTexHwOk(ctx)) {
+ hw = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_TEX_HWOK);
+ }
+ if (ctx->Color.BlendEnabled) {
+ GLuint srcf, dstf;
+ GLboolean na;
+
+ if (!vrBlendMap(ctx, &srcf, &dstf, &na)) {
+ hw = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_BLEND);
+ } else
+ vmesa->blend_alpha = na;
+ }
+ if (ctx->Color.AlphaEnabled) {
+ /*
+ * TranspReject rejects SrcAlpha <= AlphaThres, and
+ * v_TranspReject is enable-only (redline), DUH.
+ */
+ GLenum f = ctx->Color.AlphaFunc;
+
+ if (f != GL_GREATER && f != GL_GEQUAL &&
+ f != GL_NEVER && f != GL_ALWAYS) {
+ hw = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_ALPHAFUNC);
+ } else if (f != GL_ALWAYS &&
+ !(ctx->Texture._EnabledUnits == 0x1 &&
+ ctx->Texture.Unit[0].EnvMode == GL_REPLACE))
+ vmesa->atest_alpha = GL_TRUE;
+ }
+ /* fog now rides the KFXYZUVQ per-vertex f term + PE FogColor/En */
+ if (ctx->Depth.Test && ctx->Depth.Func != GL_LESS &&
+ ctx->Depth.Func != GL_LEQUAL && ctx->Depth.Func != GL_ALWAYS) {
+ hw = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_DEPTHFUNC);
+ }
+ if (ctx->_TriangleCaps & (DD_TRI_STIPPLE | DD_TRI_SMOOTH |
+ DD_TRI_LIGHT_TWOSIDE | DD_SEPARATE_SPECULAR)) {
+ hw = GL_FALSE; /* offset rides a per-tri Z bias now */
+ /* split the mask so the histogram shows which cap gated */
+ if (ctx->_TriangleCaps & DD_TRI_STIPPLE)
+ VRP_FALLBACK(VRP_FB_STIPPLE);
+ if (ctx->_TriangleCaps & DD_TRI_SMOOTH)
+ VRP_FALLBACK(VRP_FB_SMOOTH);
+ if (ctx->_TriangleCaps & DD_TRI_LIGHT_TWOSIDE)
+ VRP_FALLBACK(VRP_FB_TWOSIDE);
+ if (ctx->_TriangleCaps & DD_SEPARATE_SPECULAR)
+ VRP_FALLBACK(VRP_FB_SPECULAR);
+ }
+ if ((ctx->_TriangleCaps & DD_TRI_UNFILLED) &&
+ (ctx->Line.SmoothFlag || ctx->Line.StippleFlag ||
+ ctx->Point.SmoothFlag || ctx->Point._Attenuated ||
+ ctx->Point.PointSprite)) {
+ hw = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_UNFILLED_SUB);
+ }
+ if (ctx->RenderMode != GL_RENDER) {
+ hw = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_RENDERMODE);
+ }
+
+ vmesa->hw_tris = hw;
+}
+
+static void
+vr_build_vertices(GLcontext *ctx, GLuint start, GLuint end, GLuint newinputs)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ TNLcontext *tnl = TNL_CONTEXT(ctx);
+ struct vertex_buffer *VB = &tnl->vb;
+ struct tnl_clipspace *vtx = &tnl->clipspace;
+ const GLfloat *m = ctx->Viewport._WindowMap.m;
+ SWvertex *vbuf;
+ const GLubyte *pin, *cin, *tin = NULL, *fin = NULL;
+ GLuint pstr, cstr, tstr = 0, fstr = 0, csize, tsize = 0, g;
+
+ if (!vmesa->fastbuild_ok) {
+ vmesa->ss_build_vertices(ctx, start, end, newinputs);
+ return;
+ }
+
+ newinputs |= vtx->new_inputs;
+ vtx->new_inputs = 0;
+ if (!newinputs)
+ return;
+
+ vbuf = (SWvertex *)vtx->vertex_buf;
+
+ pin = (const GLubyte *)VB->NdcPtr->data;
+ pstr = VB->NdcPtr->stride;
+ cin = (const GLubyte *)VB->ColorPtr[0]->data;
+ cstr = VB->ColorPtr[0]->stride;
+ csize = VB->ColorPtr[0]->size;
+ if (vmesa->fb_tex) {
+ tin = (const GLubyte *)VB->TexCoordPtr[0]->data;
+ tstr = VB->TexCoordPtr[0]->stride;
+ tsize = VB->TexCoordPtr[0]->size;
+ }
+ if (vmesa->fb_fog) {
+ fin = (const GLubyte *)VB->FogCoordPtr->data;
+ fstr = VB->FogCoordPtr->stride;
+ }
+
+ for (g = start; g < end; g++) {
+ SWvertex *o = &vbuf[g];
+ const GLfloat *ndc = (const GLfloat *)(pin + g * pstr);
+ const GLfloat *col = (const GLfloat *)(cin + g * cstr);
+
+ /* POS: EMIT_4F_VIEWPORT */
+ o->win[0] = m[0] * ndc[0] + m[12];
+ o->win[1] = m[5] * ndc[1] + m[13];
+ o->win[2] = m[10] * ndc[2] + m[14];
+ o->win[3] = ndc[3];
+
+ /* COLOR0: EMIT_4CHAN_4F_RGBA */
+ UNCLAMPED_FLOAT_TO_CHAN(o->color[0], col[0]);
+ UNCLAMPED_FLOAT_TO_CHAN(o->color[1], col[1]);
+ UNCLAMPED_FLOAT_TO_CHAN(o->color[2], col[2]);
+ if (csize == 4)
+ UNCLAMPED_FLOAT_TO_CHAN(o->color[3], col[3]);
+ else
+ o->color[3] = CHAN_MAX;
+
+ /* TEX0: EMIT_4F (insert_4f_{4,3,2,1} padding) */
+ if (vmesa->fb_tex) {
+ const GLfloat *t = (const GLfloat *)(tin + g * tstr);
+
+ o->texcoord[0][0] = t[0];
+ o->texcoord[0][1] = tsize >= 2 ? t[1] : 0.0F;
+ o->texcoord[0][2] = tsize >= 3 ? t[2] : 0.0F;
+ o->texcoord[0][3] = tsize >= 4 ? t[3] : 1.0F;
+ }
+
+ /* FOG: EMIT_1F */
+ if (vmesa->fb_fog)
+ o->fog = *(const GLfloat *)(fin + g * fstr);
+ }
+}
+
+static void
+vr_render_start(GLcontext *ctx)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ TNLcontext *tnl = TNL_CONTEXT(ctx);
+
+ VRP_ZE(VRP_Z_RENDER_START);
+
+ /* swsetup validation: builds vertices, may re-choose funcs */
+ vmesa->ss_render_start(ctx);
+
+ {
+ const GLuint ri = tnl->render_inputs;
+ const GLuint allowed = _TNL_BIT_POS | _TNL_BIT_COLOR0 |
+ _TNL_BIT_FOG | _TNL_BIT_TEX(0);
+
+ vmesa->fb_tex = (ri & _TNL_BIT_TEX(0)) != 0;
+ vmesa->fb_fog = (ri & _TNL_BIT_FOG) != 0;
+ vmesa->fastbuild_ok = vmesa->use_fastbuild &&
+ (ri & _TNL_BIT_POS) && (ri & _TNL_BIT_COLOR0) &&
+ (ri & ~allowed) == 0 &&
+ tnl->clipspace.vertex_size == sizeof(SWvertex) &&
+ tnl->vb.NdcPtr != NULL &&
+ (tnl->vb.ColorPtr[0]->size == 3 ||
+ tnl->vb.ColorPtr[0]->size == 4);
+ }
+
+ if (_mesa_getenv("VRMESA_DEBUG"))
+ _mesa_printf("vr_render_start: hw=%d texen=%x mip=%d "
+ "texw=%.0f\n", vmesa->hw_tris,
+ ctx->Texture._EnabledUnits, vmesa->tex_mip,
+ vmesa->tex_w);
+
+ if (tnl->Driver.Render.Triangle != vr_triangle &&
+ tnl->Driver.Render.Triangle != vr_unfilled_tri) {
+ vmesa->ss_triangle = tnl->Driver.Render.Triangle;
+ vmesa->ss_quad = tnl->Driver.Render.Quad;
+ }
+ if (tnl->Driver.Render.Line != vr_line &&
+ tnl->Driver.Render.Line != vr_hwline &&
+ tnl->Driver.Render.Line != vr_aaline)
+ vmesa->ss_line = tnl->Driver.Render.Line;
+ if (tnl->Driver.Render.Points != vr_points &&
+ tnl->Driver.Render.Points != vr_aapoints)
+ vmesa->ss_points = tnl->Driver.Render.Points;
+
+ if (vmesa->hw_tris) {
+ if (ctx->Texture._EnabledUnits != 0) {
+ /* gate passed, but residency can still fail */
+ if (!vrTexValidate(ctx)) {
+ vmesa->tex_on = GL_FALSE;
+ VRP_FALLBACK(VRP_FB_TEX_RESIDENCY);
+ goto fallback;
+ }
+ vmesa->tex_on = GL_TRUE;
+ } else {
+ rlgl_tex_bind(&vmesa->rl, NULL);
+ vmesa->tex_on = GL_FALSE;
+ }
+ if (ctx->_TriangleCaps & DD_TRI_UNFILLED) {
+ tnl->Driver.Render.Triangle = vr_unfilled_tri;
+ tnl->Driver.Render.Quad = vr_unfilled_quad;
+ tnl->Driver.Render.PrimTabVerts = vmesa->ss_prim_verts;
+ tnl->Driver.Render.PrimTabElts = vmesa->ss_prim_elts;
+ vmesa->strips_ok = GL_FALSE;
+ } else {
+ tnl->Driver.Render.Triangle = vr_triangle;
+ tnl->Driver.Render.Quad = vr_quad;
+ tnl->Driver.Render.PrimTabVerts = vr_tab_verts;
+ tnl->Driver.Render.PrimTabElts = vr_tab_elts;
+ vmesa->strips_ok = vmesa->use_strips &&
+ !vmesa->tex_mip &&
+ !vmesa->blend_alpha && !vmesa->atest_alpha &&
+ !ctx->Polygon.OffsetFill &&
+ !(g_vr_noblendfan && ctx->Color.BlendEnabled);
+ }
+ /*
+ * Line dispatch:
+ * - stippled -> swrast
+ * - smooth + narrow + -> vr_aaline
+ * blending enabled
+ * - smooth + wide/ -> swrast (HW AA line is ~1px)
+ * no blend
+ * - plain + narrow -> vr_hwline
+ * - plain + wide -> vr_line thin-quad
+ */
+ if (ctx->Line.StippleFlag)
+ tnl->Driver.Render.Line = vmesa->ss_line;
+ else if (ctx->Line.SmoothFlag)
+ tnl->Driver.Render.Line =
+ (ctx->Color.BlendEnabled &&
+ ctx->Line._Width <= 1.5F) ?
+ vr_aaline : vmesa->ss_line;
+ else
+ tnl->Driver.Render.Line =
+ (ctx->Line._Width <= 1.5F) ? vr_hwline : vr_line;
+ /*
+ * Points: native AA point
+ */
+ if (ctx->Point.SmoothFlag && ctx->Color.BlendEnabled &&
+ ctx->Texture._EnabledUnits == 0 &&
+ !ctx->Point._Attenuated && !ctx->Point.PointSprite)
+ tnl->Driver.Render.Points = vr_aapoints;
+ else if (!ctx->Point.SmoothFlag && !ctx->Point._Attenuated &&
+ !ctx->Point.PointSprite)
+ tnl->Driver.Render.Points = vr_points;
+ else
+ tnl->Driver.Render.Points = vmesa->ss_points;
+ VRP_ZX(VRP_Z_RENDER_START);
+ return;
+ }
+fallback:
+ tnl->Driver.Render.Triangle = vmesa->ss_triangle;
+ tnl->Driver.Render.Quad = vmesa->ss_quad;
+ tnl->Driver.Render.Line = vmesa->ss_line;
+ tnl->Driver.Render.Points = vmesa->ss_points;
+ /* swrast fallback: everything dispatches through the restored funcs */
+ tnl->Driver.Render.PrimTabVerts = vmesa->ss_prim_verts;
+ tnl->Driver.Render.PrimTabElts = vmesa->ss_prim_elts;
+ vmesa->strips_ok = GL_FALSE;
+ VRP_ZX(VRP_Z_RENDER_START);
+}
+
+void
+vrInitTriFuncs(GLcontext *ctx)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ TNLcontext *tnl = TNL_CONTEXT(ctx);
+
+ /* capture swsetup's Start (installed by _swsetup_Wakeup) */
+ vmesa->ss_render_start = tnl->Driver.Render.Start;
+ tnl->Driver.Render.Start = vr_render_start;
+
+ vmesa->ss_build_vertices = tnl->Driver.Render.BuildVertices;
+ tnl->Driver.Render.BuildVertices = vr_build_vertices;
+ vmesa->use_fastbuild = (_mesa_getenv("VRMESA_NOFASTBUILD") == NULL);
+
+ /*
+ * Capture swsetup's default per-mode render tables, then
+ * build our batch tables from them.
+ */
+ vmesa->ss_prim_verts = tnl->Driver.Render.PrimTabVerts;
+ vmesa->ss_prim_elts = tnl->Driver.Render.PrimTabElts;
+ vr_build_tables(tnl);
+ vmesa->use_strips = (_mesa_getenv("VRMESA_STRIPS") != NULL);
+ g_vr_nocache = (_mesa_getenv("VRMESA_NOCACHE") != NULL);
+ rlgl_force_rebind = g_vr_nocache;
+ g_vr_noblendfan = (_mesa_getenv("VRMESA_NOBLENDFAN") != NULL);
+ {
+ const char *zb = _mesa_getenv("VRMESA_ZBIAS");
+ g_vr_zbias = zb ? (float)atoi(zb) : 0.0F;
+ }
+ atexit(vr_strip_stat); /* VRMESA_STRIPSTAT fire-rate report */
+
+ vrCheckHwRender(ctx);
+}