aboutsummaryrefslogtreecommitdiff
path: root/src/mesa/drivers/verite
diff options
context:
space:
mode:
Diffstat (limited to 'src/mesa/drivers/verite')
-rw-r--r--src/mesa/drivers/verite/.gdbinit41
-rw-r--r--src/mesa/drivers/verite/README5
-rw-r--r--src/mesa/drivers/verite/libv3d.c400
-rw-r--r--src/mesa/drivers/verite/libv3d.h138
-rw-r--r--src/mesa/drivers/verite/rlgl.c982
-rw-r--r--src/mesa/drivers/verite/rlgl.h194
-rw-r--r--src/mesa/drivers/verite/verite3dio.h107
-rw-r--r--src/mesa/drivers/verite/vrapi.c319
-rw-r--r--src/mesa/drivers/verite/vrdd.c382
-rw-r--r--src/mesa/drivers/verite/vrdrv.h125
-rw-r--r--src/mesa/drivers/verite/vrperf.h161
-rw-r--r--src/mesa/drivers/verite/vrspan.c202
-rw-r--r--src/mesa/drivers/verite/vrtex.c501
-rw-r--r--src/mesa/drivers/verite/vrtris.c1085
14 files changed, 4642 insertions, 0 deletions
diff --git a/src/mesa/drivers/verite/.gdbinit b/src/mesa/drivers/verite/.gdbinit
new file mode 100644
index 0000000..a735046
--- /dev/null
+++ b/src/mesa/drivers/verite/.gdbinit
@@ -0,0 +1,41 @@
+# veritemesa perf-counter inspection helpers
+#
+# Requires libGL.a built with VERITE_INSTRUMENT=1 !
+#
+# $ gdbserver :2345 ./glquake +timedemo demo1
+# $ gdb glquake
+# (gdb) target remote localhost:2345
+# (gdb) break vrMesaSwapBuffers
+# (gdb) run
+# (gdb) vrp
+# (gdb) vrpwatch
+
+define vrp
+ printf "VRPERF(live) frames=%llu\n", g_vrp.frames
+ printf " fallback: tex=%llu blend=%llu afunc=%llu zfunc=%llu\n", \
+ g_vrp.fb[0], g_vrp.fb[1], g_vrp.fb[2], g_vrp.fb[3]
+ printf " stipple=%llu smooth=%llu twoside=%llu spec=%llu\n", \
+ g_vrp.fb[4], g_vrp.fb[5], g_vrp.fb[6], g_vrp.fb[7]
+ printf " unfilled=%llu rmode=%llu texres=%llu alphagrad=%llu\n", \
+ g_vrp.fb[8], g_vrp.fb[9], g_vrp.fb[10], g_vrp.fb[11]
+ printf " texval: first=%llu evict=%llu dirty=%llu bindonly=%llu failv=%llu\n", \
+ g_vrp.tv_first, g_vrp.tv_evict, g_vrp.tv_dirty, g_vrp.tv_bindonly, g_vrp.tv_failv
+ printf " churn: fgcolor=%llu mipbind=%llu filt=%llu fences=%llu\n", \
+ g_vrp.fgcolor_writes, g_vrp.mip_binds, g_vrp.filt_changes, g_vrp.fences
+end
+document vrp
+Dump the veritemesa driver perf counters at the current stop.
+end
+
+# Break at each swap, print the counters, and keep going.
+define vrpwatch
+ break vrMesaSwapBuffers
+ commands
+ silent
+ vrp
+ continue
+ end
+end
+document vrpwatch
+Install a breakpoint at vrMesaSwapBuffers that prints g_vrp every frame.
+end
diff --git a/src/mesa/drivers/verite/README b/src/mesa/drivers/verite/README
new file mode 100644
index 0000000..38417fa
--- /dev/null
+++ b/src/mesa/drivers/verite/README
@@ -0,0 +1,5 @@
+veritemesa - Mesa 6.2 driver for the Rendition Verite V2200
+
+NetBSD, no X, /dev/verite3d kernel interface.
+
+Build: configs/netbsd-verite
diff --git a/src/mesa/drivers/verite/libv3d.c b/src/mesa/drivers/verite/libv3d.c
new file mode 100644
index 0000000..b09f960
--- /dev/null
+++ b/src/mesa/drivers/verite/libv3d.c
@@ -0,0 +1,400 @@
+/*
+ * 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.
+ */
+
+/* libv3d - thin userland layer over /dev/verite3d. */
+#include <sys/types.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "libv3d.h"
+
+uint64_t
+v3d_now_ns(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec;
+}
+
+/* 0 when timing is off: the accumulators just stay zero */
+static uint64_t
+v3d_t0(const struct v3d *v)
+{
+
+ return v->opt_stats ? v3d_now_ns() : 0;
+}
+
+int
+v3d_open(struct v3d *v, const char *devpath, const char *ucpath)
+{
+ struct v3d_init vi;
+ struct v3d_mode vm;
+ static uint8_t uc[1024 * 1024];
+ FILE *f;
+ size_t sz;
+ uint32_t w;
+ int s;
+
+ memset(v, 0, sizeof(*v));
+ if (getenv("RLGL_STATS") != NULL)
+ v->opt_stats = atoi(getenv("RLGL_STATS"));
+ v->opt_nulldraw = getenv("RLGL_NULLDRAW") != NULL;
+ v->opt_nosync = getenv("RLGL_NOSYNC") != NULL;
+ v->opt_noflip = getenv("RLGL_NOFLIP") != NULL;
+ v->opt_tearflip = getenv("RLGL_TEARFLIP") != NULL;
+ if (v->opt_nulldraw || v->opt_nosync || v->opt_noflip ||
+ v->opt_tearflip)
+ fprintf(stderr, "libv3d: EXPERIMENT MODE nulldraw=%d "
+ "nosync=%d noflip=%d tearflip=%d\n", v->opt_nulldraw,
+ v->opt_nosync, v->opt_noflip, v->opt_tearflip);
+ v->fd = open(devpath, O_RDWR);
+ if (v->fd == -1) {
+ perror(devpath);
+ return -1;
+ }
+
+ f = fopen(ucpath, "rb");
+ if (f == NULL) {
+ perror(ucpath);
+ goto fail;
+ }
+ sz = fread(uc, 1, sizeof(uc), f);
+ fclose(f);
+
+ vi.vi_ucode = (uint64_t)(uintptr_t)uc;
+ vi.vi_size = (uint32_t)sz;
+ if (ioctl(v->fd, V3D_INIT, &vi) == -1) {
+ perror("V3D_INIT");
+ goto fail;
+ }
+ v->ctx_base = vi.vi_ctx_base;
+ v->memsize = vi.vi_memsize;
+ v->pool_base = vi.vi_pool_base;
+
+ for (s = 0; s < V3D_RING_SLOTS; s++) {
+ v->slot[s] = mmap(NULL, V3D_SLOT_SIZE,
+ PROT_READ | PROT_WRITE, MAP_SHARED, v->fd,
+ (off_t)s * V3D_SLOT_SIZE);
+ if (v->slot[s] == MAP_FAILED) {
+ perror("mmap slot");
+ goto fail;
+ }
+ }
+
+ /* GL context up; fence it */
+ usleep(10000);
+ v3d_emit1(v, V3D_GL_CTX_INIT);
+ if (v3d_sync(v) != 0)
+ goto fail;
+
+ /* mode query fills geometry */
+ memset(&vm, 0, sizeof(vm));
+ if (ioctl(v->fd, V3D_MODE, &vm) == -1)
+ goto fail;
+ v->width = vm.vm_width;
+ v->height = vm.vm_height;
+ v->stride = vm.vm_stride;
+ v->pe_stride = vm.vm_pe_stride;
+ (void)w;
+ return 0;
+fail:
+ close(v->fd);
+ v->fd = -1;
+ return -1;
+}
+
+void
+v3d_close(struct v3d *v)
+{
+
+ if (v->fd == -1)
+ return;
+ /* park politely */
+ v3d_emit1(v, V3D_GL_EXIT);
+ (void)v3d_flush(v);
+ if (v->vram != NULL) {
+ munmap(v->vram, v->memsize);
+ v->vram = NULL;
+ }
+ close(v->fd);
+ v->fd = -1;
+}
+
+const uint8_t *
+v3d_vram(struct v3d *v)
+{
+
+ if (v->vram == NULL) {
+ /* RW for span writes (Mesa swrast), RO fallback */
+ v->vram = mmap(NULL, v->memsize, PROT_READ | PROT_WRITE,
+ MAP_SHARED, v->fd, V3D_VRAM_MMAP_OFF);
+ if (v->vram == MAP_FAILED)
+ v->vram = mmap(NULL, v->memsize, PROT_READ,
+ MAP_SHARED, v->fd, V3D_VRAM_MMAP_OFF);
+ if (v->vram == MAP_FAILED) {
+ perror("mmap vram");
+ v->vram = NULL;
+ }
+ }
+ return v->vram;
+}
+
+void
+v3d_emit(struct v3d *v, const uint32_t *w, uint32_t n)
+{
+
+ if (v->n + n > V3D_SLOT_SIZE / 4) {
+ if (v3d_flush(v) != 0)
+ return;
+ }
+ memcpy(v->slot[v->cur] + v->n, w, n * 4);
+ v->n += n;
+ v->st.words += n;
+}
+
+void
+v3d_emit1(struct v3d *v, uint32_t w)
+{
+
+ v3d_emit(v, &w, 1);
+}
+
+uint32_t *
+v3d_reserve(struct v3d *v, uint32_t nwords)
+{
+
+ if (nwords > V3D_SLOT_SIZE / 4)
+ return NULL;
+ if (v->n + nwords > V3D_SLOT_SIZE / 4) {
+ if (v3d_flush(v) != 0)
+ return NULL;
+ }
+ return v->slot[v->cur] + v->n;
+}
+
+void
+v3d_commit(struct v3d *v, uint32_t nwords)
+{
+
+ v->n += nwords;
+ v->st.words += nwords;
+}
+
+void
+v3d_emitf(struct v3d *v, float f)
+{
+ union { float f; uint32_t u; } u;
+
+ u.f = f;
+ v3d_emit1(v, u.u);
+}
+
+int
+v3d_flush(struct v3d *v)
+{
+ struct v3d_submit vs;
+ uint64_t t0;
+
+ if (v->n == 0)
+ return 0;
+ vs.vs_slot = (uint32_t)v->cur;
+ vs.vs_len = v->n * 4;
+ vs.vs_swap = 1; /* XXX: host-native words */
+ v->n = 0;
+ v->cur = (v->cur + 1) % V3D_RING_SLOTS;
+ v->st.submits++;
+ v->st.submit_bytes += vs.vs_len;
+ if (v->opt_nulldraw)
+ return 0;
+ t0 = v3d_t0(v);
+ if (ioctl(v->fd, V3D_SUBMIT, &vs) == -1) {
+ perror("V3D_SUBMIT");
+ return -1;
+ }
+ if (t0 != 0)
+ v->st.submit_ns += v3d_now_ns() - t0;
+ return 0;
+}
+
+int
+v3d_sync(struct v3d *v)
+{
+ uint32_t word = V3D_GL_SYNC;
+ uint64_t t0;
+
+ if (v3d_flush(v) != 0)
+ return -1;
+ v->st.syncs++;
+ if (v->opt_nulldraw)
+ return 0;
+ t0 = v3d_t0(v);
+ if (ioctl(v->fd, V3D_SYNC, &word) == -1) {
+ perror("V3D_SYNC");
+ return -1;
+ }
+ if (t0 != 0)
+ v->st.sync_ns += v3d_now_ns() - t0;
+ return 0;
+}
+
+int
+v3d_alloc(struct v3d *v, uint32_t size, uint32_t align, uint32_t *addr)
+{
+ struct v3d_alloc va;
+
+ va.va_size = size;
+ va.va_align = align;
+ if (ioctl(v->fd, V3D_ALLOC, &va) == -1)
+ return -1;
+ *addr = va.va_addr;
+ return 0;
+}
+
+int
+v3d_mode(struct v3d *v, uint32_t depth, uint32_t frame_base)
+{
+ struct v3d_mode vm;
+
+ memset(&vm, 0, sizeof(vm));
+ vm.vm_depth = depth;
+ vm.vm_frame_base = frame_base;
+ if (ioctl(v->fd, V3D_MODE, &vm) == -1)
+ return -1;
+ v->width = vm.vm_width;
+ v->height = vm.vm_height;
+ v->stride = vm.vm_stride;
+ v->pe_stride = vm.vm_pe_stride;
+ return 0;
+}
+
+int
+v3d_flip(struct v3d *v, uint32_t frame_base)
+{
+ uint64_t t0;
+ int r;
+
+ if (v3d_flush(v) != 0)
+ return -1;
+ v->st.flips++;
+ if (v->opt_nulldraw || v->opt_noflip)
+ return 0;
+ if (v->opt_tearflip)
+ frame_base |= V3D_FLIP_NOWAIT;
+ t0 = v3d_t0(v);
+ r = ioctl(v->fd, V3D_FLIP, &frame_base);
+ if (t0 != 0)
+ v->st.flip_ns += v3d_now_ns() - t0;
+ return r;
+}
+
+void
+v3d_st(struct v3d *v, uint32_t reg, uint32_t val)
+{
+ uint32_t w[2];
+
+ w[0] = (1 << 12) | reg;
+ w[1] = val;
+ v->st.st_calls++;
+ v3d_emit(v, w, 2);
+}
+
+void
+v3d_texbind(struct v3d *v, uint32_t addr, uint32_t srcstride,
+ uint32_t w, uint32_t h, uint32_t uscale, uint32_t vscale)
+{
+ uint32_t c[6];
+
+ c[0] = (4 << 12) | V3D_PE_SRCBASE;
+ c[1] = addr;
+ c[2] = srcstride;
+ c[3] = ((h - 1) << 16) | (w - 1);
+ c[4] = uscale;
+ c[5] = vscale;
+ v->st.texbinds++;
+ v3d_emit(v, c, 6);
+}
+
+/* below this many payload bytes of slot room, flush for a fresh slot */
+#define V3D_UPLOAD_MIN 4096
+
+int
+v3d_upload(struct v3d *v, uint32_t dst, const void *data, uint32_t bytes)
+{
+ const uint32_t *p = data;
+ uint32_t chunk, room, hdr[3];
+
+ v->st.uploads++;
+ v->st.upload_bytes += bytes;
+ while (bytes > 0) {
+ /*
+ * Batch into the current slot alongside whatever is
+ * already queued
+ */
+ room = (V3D_SLOT_SIZE / 4 - v->n) * 4;
+ if (room < V3D_UPLOAD_MIN + 3 * 4) {
+ if (v3d_flush(v) != 0)
+ return -1;
+ room = V3D_SLOT_SIZE;
+ }
+ chunk = bytes;
+ if (chunk > room - 3 * 4)
+ chunk = room - 3 * 4;
+ hdr[0] = V3D_GL_MEM_WRITE;
+ hdr[1] = dst;
+ hdr[2] = chunk;
+ v3d_emit(v, hdr, 3);
+ v3d_emit(v, p, chunk / 4);
+ p += chunk / 4;
+ dst += chunk;
+ bytes -= chunk;
+ }
+ return 0;
+}
+
+void
+v3d_clear(struct v3d *v, uint32_t addr, uint32_t stride, uint32_t wbytes,
+ uint32_t h, uint32_t val)
+{
+ uint32_t c[6];
+
+ c[0] = V3D_GL_CLEAR_RECT;
+ c[1] = addr;
+ c[2] = stride;
+ c[3] = wbytes;
+ c[4] = h;
+ c[5] = val;
+ v3d_emit(v, c, 6);
+}
diff --git a/src/mesa/drivers/verite/libv3d.h b/src/mesa/drivers/verite/libv3d.h
new file mode 100644
index 0000000..4ee44b1
--- /dev/null
+++ b/src/mesa/drivers/verite/libv3d.h
@@ -0,0 +1,138 @@
+/*
+ * 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.
+ */
+
+/*
+ * libv3d - thin userland layer over /dev/verite3d (verite3dio.h),
+ * shaped after the RRedline command-buffer model. Uses the v2000gl.uc
+ * command numbers (sync=8, context_init=2, exit=0) and the decoded
+ * state-class words.
+ */
+#ifndef LIBV3D_H
+#define LIBV3D_H
+
+#include <stdint.h>
+#include "verite3dio.h"
+
+/*
+ * Perf counters
+ */
+struct v3d_stats {
+ uint64_t words; /* words emitted into slots */
+ uint64_t submits, submit_bytes, submit_ns;
+ uint64_t syncs, sync_ns;
+ uint64_t flips, flip_ns;
+ uint64_t uploads, upload_bytes;
+ uint64_t st_calls, texbinds;
+};
+
+struct v3d {
+ int fd;
+ uint32_t *slot[V3D_RING_SLOTS];
+ uint8_t *vram; /* lazy full-VRAM read mapping */
+ int cur; /* slot being filled */
+ uint32_t n; /* words used in cur */
+ /* geometry from the kernel */
+ uint32_t ctx_base, memsize, pool_base;
+ uint32_t width, height, stride, pe_stride;
+ /* RLGL_STATS / RLGL_NULLDRAW / RLGL_NOSYNC / RLGL_NOFLIP /
+ * RLGL_TEARFLIP */
+ int opt_stats, opt_nulldraw, opt_nosync, opt_noflip;
+ int opt_tearflip;
+ struct v3d_stats st;
+};
+
+/* GL blob FIFO ABI */
+#define V3D_GL_EXIT 0
+#define V3D_GL_CTX_INIT 2
+#define V3D_GL_SYNC 8
+#define V3D_GL_MEM_WRITE 9
+#define V3D_GL_CLEAR_RECT 13
+#define V3D_GL_LINE 16 /* hw Bresenham line (rdraw2) */
+#define V3D_GL_AALINE 20 /* coverage-graded AA line */
+#define V3D_GL_TRIANGLE 24
+#define V3D_GL_TRISTRIP 26 /* hw tri-strip start (cont word 48) */
+#define V3D_GL_TRIFAN 27 /* hw tri-fan start (cont word 49) */
+#define V3D_GL_AAPOINT 50 /* coverage-graded AA point */
+#define V3D_GL_STRIP_CONT 48 /* tristrip_third_vertex continuation */
+#define V3D_GL_FAN_CONT 49 /* trifan continuation (single vtx) */
+
+/* PE register indices (state class 1 writes, value pre-shifted) */
+#define V3D_PE_SRCBASE 0
+#define V3D_PE_STRIDE 3
+#define V3D_PE_DSTBASE 4
+#define V3D_PE_DSTFMT 6
+#define V3D_PE_PMASK 7
+#define V3D_PE_SCISSORX 14
+#define V3D_PE_SCISSORY 15
+#define V3D_PE_ZBASE 16
+#define V3D_PE_FGCOLOR 19
+#define V3D_PE_FOGCOLOR 21 /* 0x00RRGGBB */
+#define V3D_PE_SRCFMT 48
+#define V3D_PE_SRCFUNC 49 /* value << 4 */
+#define V3D_PE_SRCFILTER 50 /* value << 7 */
+#define V3D_PE_ALUMODE 64 /* bits 3:0 */
+#define V3D_PE_BLENDSRCFUNC 65 /* value << 4 */
+#define V3D_PE_BLENDDSTFUNC 66 /* value << 8 */
+#define V3D_PE_ZBUFMODE 67 /* value << 12 */
+#define V3D_PE_ZBUFWRMODE 68 /* value << 16 */
+#define V3D_PE_BLENDENABLE 70 /* value << 19 */
+#define V3D_PE_DITHEREN 71 /* value << 20 */
+#define V3D_PE_FOGEN 72 /* value << 21 */
+#define V3D_PE_DSTRDDIS 74 /* value << 23 */
+
+uint64_t v3d_now_ns(void);
+int v3d_open(struct v3d *, const char *devpath, const char *ucpath);
+void v3d_close(struct v3d *);
+/*
+ * VRAM window
+ */
+const uint8_t *v3d_vram(struct v3d *);
+void v3d_emit(struct v3d *, const uint32_t *, uint32_t);
+void v3d_emit1(struct v3d *, uint32_t);
+void v3d_emitf(struct v3d *, float);
+
+uint32_t *v3d_reserve(struct v3d *, uint32_t nwords);
+void v3d_commit(struct v3d *, uint32_t nwords);
+int v3d_flush(struct v3d *);
+int v3d_sync(struct v3d *);
+int v3d_alloc(struct v3d *, uint32_t, uint32_t, uint32_t *);
+int v3d_mode(struct v3d *, uint32_t depth, uint32_t frame_base);
+int v3d_flip(struct v3d *, uint32_t frame_base);
+
+/* state class 1: single PE register write (pre-shifted value) */
+void v3d_st(struct v3d *, uint32_t reg, uint32_t val);
+/* state class 4: full texture bind */
+void v3d_texbind(struct v3d *, uint32_t addr, uint32_t srcstride,
+ uint32_t w, uint32_t h, uint32_t uscale, uint32_t vscale);
+/* mem_write upload (chunks as needed) and engine-side clear */
+int v3d_upload(struct v3d *, uint32_t dst, const void *, uint32_t);
+void v3d_clear(struct v3d *, uint32_t addr, uint32_t stride,
+ uint32_t wbytes, uint32_t h, uint32_t val);
+
+#endif /* LIBV3D_H */
diff --git a/src/mesa/drivers/verite/rlgl.c b/src/mesa/drivers/verite/rlgl.c
new file mode 100644
index 0000000..1da14f2
--- /dev/null
+++ b/src/mesa/drivers/verite/rlgl.c
@@ -0,0 +1,982 @@
+/*
+ * 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.
+ */
+
+#include <sys/ioctl.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "rlgl.h"
+#include "rlvec.h"
+
+int rlgl_force_rebind = 0;
+
+/*
+ * Line-bytes -> encoded stride nibble pair
+ */
+static uint32_t
+stride_code(uint32_t linebytes)
+{
+ static const struct { uint32_t bytes, s0, s1; } tab[] = {
+ { 16, 0, 1 }, { 32, 0, 2 }, { 64, 0, 3 }, { 128, 0, 4 },
+ { 256, 1, 0 }, { 512, 2, 0 }, { 1024, 3, 0 },
+ { 2048, 0, 6 }, { 4096, 0, 7 },
+ };
+ unsigned i;
+
+ for (i = 0; i < sizeof(tab) / sizeof(tab[0]); i++)
+ if (tab[i].bytes == linebytes)
+ return (tab[i].s1 << 4) | tab[i].s0;
+ return (uint32_t)-1;
+}
+
+int
+rlgl_init(struct rlgl *rl, const char *ucpath)
+{
+ struct v3d *v = &rl->v;
+
+ memset(rl, 0, sizeof(*rl));
+ rlvec_init();
+ if (v3d_open(v, "/dev/verite3d", ucpath) != 0)
+ return -1;
+ if (v3d_mode(v, 16, 0) != 0)
+ return -1;
+ if (v3d_alloc(v, v->stride * v->height, 4096, &rl->fb[0]) != 0 ||
+ v3d_alloc(v, v->stride * v->height, 4096, &rl->fb[1]) != 0 ||
+ v3d_alloc(v, v->stride * v->height, 4096, &rl->zbuf) != 0)
+ return -1;
+ /* tiny throwaway surface for the glCopyPixels source-read seed
+ * (128px * 8 rows * 2B = 2KB, addressed at a 256B stride) */
+ if (v3d_alloc(v, 128 * 8 * 2, 4096, &rl->scratch) != 0)
+ return -1;
+
+ /* constant state */
+ v3d_st(v, V3D_PE_DSTFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SCISSORX, v->width);
+ v3d_st(v, V3D_PE_SCISSORY, v->height);
+ v3d_st(v, V3D_PE_ALUMODE, 0xC);
+ v3d_st(v, V3D_PE_DSTRDDIS, 0);
+ v3d_st(v, V3D_PE_ZBASE, rl->zbuf);
+ /*
+ * Zero every mode the GL microcode's context_init leaves
+ * undefined
+ */
+ v3d_st(v, 69, 0); /* YUV2RGB */
+ v3d_st(v, V3D_PE_DITHEREN, 0); /* DitherEnable */
+ v3d_st(v, 72, 0); /* FogEnable */
+ v3d_st(v, 73, 0); /* DstColorNoPad */
+ v3d_st(v, 75, 0); /* DstBGR */
+ v3d_st(v, 76, 0); /* TranspReject */
+ v3d_st(v, 55, 0); /* SrcBGR */
+ v3d_st(v, 81, 0); /* PatEnable */
+ v3d_st(v, 83, 0); /* SpecularEn */
+ v3d_st(v, 62, 0); /* UClamp (wrap) */
+ v3d_st(v, 63, 0); /* VClamp (wrap) */
+ v3d_st(v, V3D_PE_PMASK, 0xFFFFFFFF); /* all planes */
+ /* dst + Z stride identical, src patched by tex_bind */
+ v3d_st(v, V3D_PE_STRIDE, v->pe_stride |
+ ((v->pe_stride >> 8) << 16));
+ v3d_st(v, V3D_PE_DSTBASE, rl->fb[0]);
+ rl->cur_tex = (uint32_t)-1;
+ rl->cur_func = -1;
+ rl->env_mod = RLGL_ENV_REPLACE;
+ rl->fog_on = 0;
+ rlgl_depth_test(rl, 0);
+ rlgl_depth_mask(rl, 1);
+ rlgl_blend(rl, 0, 0, 0);
+ rlgl_alpha_test(rl, 0, 0);
+ rlgl_tex_bind(rl, NULL);
+
+ /*
+ * Claim the glass: clear both color buffers + Z, then point
+ * scanout at the FRONT buffer.
+ */
+ v3d_clear(v, rl->fb[0], v->stride, v->width * 2, v->height, 0);
+ v3d_clear(v, rl->fb[1], v->stride, v->width * 2, v->height, 0);
+ v3d_clear(v, rl->zbuf, v->stride, v->width * 2, v->height,
+ 0xFFFFFFFFU);
+ if (v3d_sync(v) != 0)
+ return -1;
+ return v3d_flip(v, rl->fb[rl->back ^ 1]);
+}
+
+void
+rlgl_shutdown(struct rlgl *rl)
+{
+
+ rlgl_stats_print(rl);
+ v3d_close(&rl->v);
+}
+
+int
+rlgl_tex_init(struct rlgl_tex *t, int w, int h, uint32_t fmt)
+{
+ uint32_t code = stride_code((uint32_t)w * 2);
+
+ if (code == (uint32_t)-1 || (w & (w - 1)) || (h & (h - 1)))
+ return -1;
+ t->w = (uint16_t)w;
+ t->h = (uint16_t)h;
+ t->fmt = fmt;
+ t->srcstride = code;
+ t->addr = 0;
+ return 0;
+}
+
+static uint32_t
+tex_align(uint32_t w)
+{
+ uint32_t a = w * 2;
+
+ return a < 64 ? 64 : a;
+}
+
+int
+rlgl_tex_alloc(struct rlgl *rl, struct rlgl_tex *t)
+{
+
+ return v3d_alloc(&rl->v, (uint32_t)t->w * t->h * 2,
+ tex_align(t->w), &t->addr);
+}
+
+/*
+ * Allocate a whole mip pyramid as ONE contiguous VRAM block,
+ * like 3dfx does.
+ */
+int
+rlgl_tex_pyramid(struct rlgl *rl, struct rlgl_tex *levs, int nlev)
+{
+ uint32_t total = 0, base;
+ int i;
+
+ for (i = 0; i < nlev; i++) {
+ uint32_t a = tex_align(levs[i].w);
+
+ total = (total + a - 1) & ~(a - 1);
+ total += (uint32_t)levs[i].w * (uint32_t)levs[i].h * 2;
+ }
+ if (v3d_alloc(&rl->v, total, tex_align(levs[0].w), &base) != 0)
+ return -1;
+ total = 0;
+ for (i = 0; i < nlev; i++) {
+ uint32_t a = tex_align(levs[i].w);
+
+ total = (total + a - 1) & ~(a - 1);
+ levs[i].addr = base + total;
+ total += (uint32_t)levs[i].w * (uint32_t)levs[i].h * 2;
+ }
+ return 0;
+}
+
+int
+rlgl_tex_create(struct rlgl *rl, struct rlgl_tex *t, int w, int h,
+ uint32_t fmt)
+{
+
+ if (rlgl_tex_init(t, w, h, fmt) != 0)
+ return -1;
+ return rlgl_tex_alloc(rl, t);
+}
+
+int
+rlgl_tex_upload_rows(struct rlgl *rl, struct rlgl_tex *t,
+ const uint16_t *texels, int y, int rows)
+{
+ static uint32_t buf[512 * 512 / 2];
+ uint32_t n = (uint32_t)t->w * (uint32_t)rows;
+ uint32_t base = (uint32_t)t->w * (uint32_t)y;
+ uint32_t i;
+
+ if (n / 2 > sizeof(buf) / 4)
+ return -1;
+ for (i = 0; i < n; i += 2)
+ buf[i / 2] = ((uint32_t)texels[base + i] << 16) |
+ (uint32_t)texels[base + i + 1];
+ return v3d_upload(&rl->v, t->addr + base * 2, buf, n * 2);
+}
+
+int
+rlgl_tex_upload(struct rlgl *rl, struct rlgl_tex *t, const uint16_t *texels)
+{
+
+ return rlgl_tex_upload_rows(rl, t, texels, 0, t->h);
+}
+
+static void
+set_srcfunc(struct rlgl *rl, int func)
+{
+
+ if (rlgl_force_rebind || rl->cur_func != func) {
+ v3d_st(&rl->v, V3D_PE_SRCFUNC, (uint32_t)func << 4);
+ rl->cur_func = func;
+ }
+}
+
+void
+rlgl_tex_bind(struct rlgl *rl, const struct rlgl_tex *t)
+{
+ struct v3d *v = &rl->v;
+
+ if (t == NULL) {
+ set_srcfunc(rl, 0); /* NOTEXTURE */
+ return;
+ }
+ if (rlgl_force_rebind || rl->cur_tex != t->addr) {
+ v3d_texbind(v, t->addr, t->srcstride, t->w, t->h,
+ 0x10000, 0x10000);
+ v3d_st(v, V3D_PE_SRCFMT, t->fmt);
+ rl->cur_tex = t->addr;
+ }
+ set_srcfunc(rl, rl->env_mod);
+}
+
+void
+rlgl_tex_env(struct rlgl *rl, int modulate)
+{
+
+ rlgl_tex_env_code(rl, modulate ? RLGL_ENV_MODULATE :
+ RLGL_ENV_REPLACE);
+}
+
+void
+rlgl_tex_env_code(struct rlgl *rl, int code)
+{
+
+ rl->env_mod = code;
+ if (rl->cur_func != 0) /* texturing active: apply now */
+ set_srcfunc(rl, code);
+}
+
+void
+rlgl_tex_wrap(struct rlgl *rl, int uclamp, int vclamp)
+{
+
+ v3d_st(&rl->v, 62, uclamp ? 1 << 15 : 0); /* UClamp */
+ v3d_st(&rl->v, 63, vclamp ? 1 << 16 : 0); /* VClamp */
+}
+
+void
+rlgl_tex_free(struct rlgl *rl, struct rlgl_tex *t)
+{
+ uint32_t addr = t->addr;
+
+ if (addr == 0)
+ return;
+ if (rl->cur_tex == addr)
+ rl->cur_tex = (uint32_t)-1;
+ (void)ioctl(rl->v.fd, V3D_FREE, &addr);
+ t->addr = 0;
+}
+
+void
+rlgl_tex_filter(struct rlgl *rl, int bilinear)
+{
+
+ v3d_st(&rl->v, V3D_PE_SRCFILTER, bilinear ? 1 << 7 : 0);
+}
+
+void
+rlgl_depth_test(struct rlgl *rl, int enable)
+{
+
+ /* LE keeps coplanar multi-pass (lightmaps) stable */
+ rlgl_depth_func(rl, enable, RLGL_Z_LE);
+}
+
+void
+rlgl_depth_func(struct rlgl *rl, int enable, int pe_code)
+{
+
+ v3d_st(&rl->v, V3D_PE_ZBUFMODE, enable ? pe_code << 12 : 0);
+}
+
+void
+rlgl_depth_mask(struct rlgl *rl, int write)
+{
+
+ v3d_st(&rl->v, V3D_PE_ZBUFWRMODE, write ? 1 << 16 : 0);
+}
+
+void
+rlgl_blend(struct rlgl *rl, int enable, uint32_t srcf, uint32_t dstf)
+{
+ struct v3d *v = &rl->v;
+
+ v3d_st(v, V3D_PE_BLENDENABLE, enable ? 1 << 19 : 0);
+ if (enable) {
+ v3d_st(v, V3D_PE_BLENDSRCFUNC, srcf << 4);
+ v3d_st(v, V3D_PE_BLENDDSTFUNC, dstf << 8);
+ }
+}
+
+void
+rlgl_alpha_test(struct rlgl *rl, int enable, uint32_t thresh8)
+{
+ struct v3d *v = &rl->v;
+
+ /* TranspReject: reject texel alpha <= AlphaThres */
+ v3d_st(v, 76, enable ? 1 << 25 : 0);
+ if (enable)
+ v3d_st(v, 29, thresh8); /* AlphaThres */
+}
+
+/*
+ * Hardware scissor.
+ */
+void
+rlgl_scissor(struct rlgl *rl, int x, int y, int w, int h)
+{
+ struct v3d *v = &rl->v;
+ int W = v->width, H = v->height;
+ int left = x, right = x + w;
+ int top = H - (y + h), bottom = H - y;
+
+ if (left < 0) left = 0; if (left > W) left = W;
+ if (right < 0) right = 0; if (right > W) right = W;
+ if (top < 0) top = 0; if (top > H) top = H;
+ if (bottom < 0) bottom = 0; if (bottom > H) bottom = H;
+ if (right < left) right = left; /* off-screen -> empty box */
+ if (bottom < top) bottom = top;
+ v3d_st(v, V3D_PE_SCISSORX, ((uint32_t)left << 16) | (uint32_t)right);
+ v3d_st(v, V3D_PE_SCISSORY, ((uint32_t)top << 16) | (uint32_t)bottom);
+}
+
+/*
+ * Hardware color mask
+ */
+void
+rlgl_color_mask(struct rlgl *rl, int r, int g, int b)
+{
+ uint32_t m = (r ? 0xF800u : 0) | (g ? 0x07E0u : 0) | (b ? 0x001Fu : 0);
+
+ v3d_st(&rl->v, V3D_PE_PMASK, m | (m << 16));
+}
+
+/*
+ * Hardware logic op (glLogicOp).
+ */
+void
+rlgl_logic_op(struct rlgl *rl, int enable, int code)
+{
+ v3d_st(&rl->v, V3D_PE_ALUMODE, enable ? (uint32_t)(code & 0xF) : 0xC);
+}
+
+/*
+ * Hardware dither (GL_DITHER).
+ */
+void
+rlgl_dither(struct rlgl *rl, int enable)
+{
+ v3d_st(&rl->v, V3D_PE_DITHEREN, enable ? (1u << 20) : 0);
+}
+
+/*
+ * Per-vertex fog (GL_FOG).
+ */
+void
+rlgl_fog(struct rlgl *rl, int enable, uint32_t rgb888)
+{
+ v3d_st(&rl->v, V3D_PE_FOGCOLOR, rgb888 & 0x00FFFFFFu);
+ v3d_st(&rl->v, V3D_PE_FOGEN, enable ? (1u << 21) : 0);
+ rl->fog_on = enable ? 1 : 0;
+}
+
+static int
+stride_codes(uint32_t bytes, uint32_t *s0code, uint32_t *s1code)
+{
+ static const struct { uint32_t code, bytes; } s0[] = {
+ { 0, 0 }, { 4, 4 }, { 1, 256 }, { 2, 512 }, { 3, 1024 },
+ };
+ static const struct { uint32_t code, bytes; } s1[] = {
+ { 0, 0 }, { 1, 16 }, { 2, 32 }, { 3, 64 }, { 4, 128 },
+ { 5, 1024 }, { 6, 2048 }, { 7, 4096 },
+ };
+ unsigned i, j;
+
+ for (i = 0; i < sizeof(s0) / sizeof(s0[0]); i++)
+ for (j = 0; j < sizeof(s1) / sizeof(s1[0]); j++)
+ if (s0[i].bytes + s1[j].bytes == bytes) {
+ *s0code = s0[i].code;
+ *s1code = s1[j].code;
+ return 0;
+ }
+ return -1;
+}
+
+int
+rlgl_copy_rect(struct rlgl *rl, uint32_t src_base, int src_w,
+ uint32_t dst_base, int dst_w, int sx, int sy, int dx, int dy,
+ int w, int h)
+{
+ struct v3d *v = &rl->v;
+ uint32_t ss0, ss1, ds0, ds1, reg58, reg59, sbase, c[6], b[4];
+ static const float seed[3][9] = {
+ { 255, 255, 255, 0, 0, 100, 0, 0, 1 },
+ { 255, 255, 255, 2, 0, 100, 0, 0, 1 },
+ { 255, 255, 255, 0, 2, 100, 0, 0, 1 },
+ };
+ int i, k;
+
+ if (w <= 0 || h <= 0)
+ return 0;
+ if (stride_codes((uint32_t)src_w * 2, &ss0, &ss1) != 0 ||
+ stride_codes((uint32_t)dst_w * 2, &ds0, &ds1) != 0)
+ return -1;
+ reg58 = (ss1 << 4) | ss0; /* SrcStride subfields */
+ reg59 = (ds1 << 12) | (ds0 << 8); /* DstStride subfields */
+ sbase = src_base + (uint32_t)sy * ((uint32_t)src_w * 2) +
+ (uint32_t)sx * 2;
+
+ c[0] = (4 << 12) | V3D_PE_SRCBASE; /* class-4 texture bind */
+ c[1] = sbase;
+ c[2] = reg58;
+ c[3] = (0x7FFu << 16) | 0x7FFu; /* UMask/VMask big (no wrap) */
+ c[4] = 0x10000; /* uscale 1.0 */
+ c[5] = 0x10000; /* vscale 1.0 */
+ v3d_emit(v, c, 6);
+ v3d_st(v, V3D_PE_SRCFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SRCFUNC, 1 << 4); /* REPLACE (copy source) */
+ v3d_st(v, V3D_PE_SRCFILTER, 0); /* point sample / BitBlt */
+ v3d_st(v, V3D_PE_DSTBASE, rl->scratch);
+ v3d_st(v, 59, 0x0100); /* scratch DstStride 256B */
+ v3d_st(v, V3D_PE_DSTFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SCISSORX, (0u << 16) | 128);
+ v3d_st(v, V3D_PE_SCISSORY, (0u << 16) | 8);
+ v3d_st(v, V3D_PE_ALUMODE, 0xC);
+ v3d_st(v, V3D_PE_ZBUFMODE, 0);
+ v3d_st(v, V3D_PE_ZBUFWRMODE, 0);
+ v3d_emit1(v, (20 << 16) | V3D_GL_TRIANGLE); /* KXYZUVQ */
+ for (i = 0; i < 3; i++)
+ for (k = 0; k < 9; k++)
+ v3d_emitf(v, seed[i][k]);
+
+ v3d_st(v, V3D_PE_DSTBASE, dst_base);
+ v3d_st(v, 59, reg59); /* DstStride dual-shift */
+ v3d_st(v, V3D_PE_DSTFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SCISSORX, (0u << 16) | (uint32_t)dst_w);
+ v3d_st(v, V3D_PE_SCISSORY, (0u << 16) | (uint32_t)v->height);
+ v3d_st(v, V3D_PE_PMASK, 0xFFFFFFFF);
+ v3d_st(v, 62, 0); /* UClamp off */
+ v3d_st(v, 63, 0); /* VClamp off */
+ v3d_st(v, V3D_PE_ALUMODE, 0xC);
+ v3d_st(v, V3D_PE_BLENDENABLE, 0);
+ v3d_st(v, V3D_PE_DSTRDDIS, 0);
+ v3d_st(v, 76, 0); /* TranspReject off */
+ b[0] = 30; /* microcode bitblt */
+ b[1] = ((uint32_t)dx << 16) | (uint32_t)dy;
+ b[2] = ((uint32_t)w << 16) | (uint32_t)h;
+ b[3] = 0; /* srcX=srcY=0 (base pre-offset) */
+ v3d_emit(v, b, 4);
+
+ v3d_st(v, V3D_PE_STRIDE, v->pe_stride | ((v->pe_stride >> 8) << 16));
+ v3d_st(v, V3D_PE_DSTBASE, rl->fb[rl->back]);
+ v3d_st(v, V3D_PE_SCISSORX, (0u << 16) | v->width);
+ v3d_st(v, V3D_PE_SCISSORY, (0u << 16) | v->height);
+ rl->cur_tex = (uint32_t)-1;
+ rl->cur_func = -1;
+ return 0;
+}
+
+int
+rlgl_copy_to_tex(struct rlgl *rl, uint32_t fb_base, int fb_h,
+ uint32_t tex_base, int tex_aw, int sx, int sy_gl, int dx, int dy,
+ int w, int h)
+{
+ struct v3d *v = &rl->v;
+ uint32_t fs0, fs1, ts0, ts1, reg58, reg59, c[6], b[4];
+ static const float seed[3][9] = {
+ { 255, 255, 255, 0, 0, 100, 0, 0, 1 },
+ { 255, 255, 255, 2, 0, 100, 0, 0, 1 },
+ { 255, 255, 255, 0, 2, 100, 0, 0, 1 },
+ };
+ int i, k, j;
+
+ if (w <= 0 || h <= 0)
+ return 0;
+ if (stride_codes((uint32_t)v->width * 2, &fs0, &fs1) != 0 ||
+ stride_codes((uint32_t)tex_aw * 2, &ts0, &ts1) != 0)
+ return -1;
+ reg58 = (fs1 << 4) | fs0; /* fb SrcStride */
+ reg59 = (ts1 << 12) | (ts0 << 8); /* texture DstStride */
+
+ c[0] = (4 << 12) | V3D_PE_SRCBASE;
+ c[1] = fb_base;
+ c[2] = reg58;
+ c[3] = (0x7FFu << 16) | 0x7FFu;
+ c[4] = 0x10000;
+ c[5] = 0x10000;
+ v3d_emit(v, c, 6);
+ v3d_st(v, V3D_PE_SRCFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SRCFUNC, 1 << 4); /* REPLACE */
+ v3d_st(v, V3D_PE_SRCFILTER, 0); /* point / BitBlt */
+ v3d_st(v, V3D_PE_DSTBASE, rl->scratch);
+ v3d_st(v, 59, 0x0100); /* scratch DstStride 256B */
+ v3d_st(v, V3D_PE_DSTFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SCISSORX, (0u << 16) | 128);
+ v3d_st(v, V3D_PE_SCISSORY, (0u << 16) | 8);
+ v3d_st(v, V3D_PE_ALUMODE, 0xC);
+ v3d_st(v, V3D_PE_ZBUFMODE, 0);
+ v3d_st(v, V3D_PE_ZBUFWRMODE, 0);
+ v3d_emit1(v, (20 << 16) | V3D_GL_TRIANGLE);
+ for (i = 0; i < 3; i++)
+ for (k = 0; k < 9; k++)
+ v3d_emitf(v, seed[i][k]);
+
+ /* dest state = the texture surface */
+ v3d_st(v, V3D_PE_DSTBASE, tex_base);
+ v3d_st(v, 59, reg59);
+ v3d_st(v, V3D_PE_DSTFMT, RLGL_FMT_565);
+ v3d_st(v, V3D_PE_SCISSORX, (0u << 16) | (uint32_t)tex_aw);
+ v3d_st(v, V3D_PE_SCISSORY, (0u << 16) | (uint32_t)(dy + h));
+ v3d_st(v, V3D_PE_PMASK, 0xFFFFFFFF);
+ v3d_st(v, 62, 0);
+ v3d_st(v, 63, 0);
+ v3d_st(v, V3D_PE_ALUMODE, 0xC);
+ v3d_st(v, V3D_PE_BLENDENABLE, 0);
+ v3d_st(v, V3D_PE_DSTRDDIS, 0);
+ v3d_st(v, 76, 0);
+
+ for (j = 0; j < h; j++) {
+ b[0] = 30;
+ b[1] = ((uint32_t)dx << 16) | (uint32_t)(dy + j);
+ b[2] = ((uint32_t)w << 16) | 1u;
+ b[3] = ((uint32_t)sx << 16) |
+ (uint32_t)(fb_h - 1 - sy_gl - j);
+ v3d_emit(v, b, 4);
+ }
+
+ /* restore persistent draw state (see rlgl_copy_rect) */
+ v3d_st(v, V3D_PE_STRIDE, v->pe_stride | ((v->pe_stride >> 8) << 16));
+ v3d_st(v, V3D_PE_DSTBASE, rl->fb[rl->back]);
+ v3d_st(v, V3D_PE_SCISSORX, (0u << 16) | v->width);
+ v3d_st(v, V3D_PE_SCISSORY, (0u << 16) | v->height);
+ rl->cur_tex = (uint32_t)-1;
+ rl->cur_func = -1;
+ return 0;
+}
+
+void
+rlgl_clear(struct rlgl *rl, int color, uint32_t rgb565, int depth)
+{
+ struct v3d *v = &rl->v;
+ uint32_t val = rgb565 | (rgb565 << 16);
+
+ /* spec 2.8: clamps must be off for parallel-draw fills */
+ rlgl_tex_wrap(rl, 0, 0);
+ if (color)
+ v3d_clear(v, rl->fb[rl->back], v->stride, v->width * 2,
+ v->height, val);
+ if (depth)
+ v3d_clear(v, rl->zbuf, v->stride, v->width * 2,
+ v->height, 0xFFFFFFFFU);
+}
+
+/*
+ * Blit one row of w 565 pixels to the back buffer at window (x, wy).
+ */
+int
+rlgl_draw_row(struct rlgl *rl, int x, int wy, int w, const uint16_t *row565)
+{
+ struct v3d *v = &rl->v;
+ static uint32_t buf[2048 / 2];
+ uint32_t fbrow;
+ int i;
+
+ if ((x & 1) || (w & 1) || w <= 0 || w > 2048 ||
+ wy < 0 || wy >= (int)v->height || x < 0 || x + w > (int)v->width)
+ return -1;
+ fbrow = (uint32_t)((int)v->height - 1 - wy);
+ for (i = 0; i < w; i += 2)
+ buf[i / 2] = ((uint32_t)row565[i] << 16) | (uint32_t)row565[i + 1];
+ return v3d_upload(v, rl->fb[rl->back] + fbrow * v->stride +
+ (uint32_t)x * 2, buf, (uint32_t)w * 2);
+}
+
+void
+rlgl_tri(struct rlgl *rl, const struct rlgl_vtx *a,
+ const struct rlgl_vtx *b, const struct rlgl_vtx *c)
+{
+ struct v3d *v = &rl->v;
+ const struct rlgl_vtx *p[3];
+ int i;
+
+ p[0] = a; p[1] = b; p[2] = c;
+ rl->tris++;
+ /*
+ * KXYZUVQ (type 20) normally, with fog on, KFXYZUVQ (type 23)
+ */
+ v3d_emit1(v, ((rl->fog_on ? 23 : 20) << 16) | V3D_GL_TRIANGLE);
+ for (i = 0; i < 3; i++) {
+ v3d_emitf(v, p[i]->r);
+ v3d_emitf(v, p[i]->g);
+ v3d_emitf(v, p[i]->b);
+ if (rl->fog_on)
+ v3d_emitf(v, p[i]->f);
+ v3d_emitf(v, p[i]->x);
+ v3d_emitf(v, p[i]->y);
+ v3d_emitf(v, p[i]->z);
+ v3d_emitf(v, p[i]->u);
+ v3d_emitf(v, p[i]->v);
+ v3d_emitf(v, p[i]->q);
+ }
+}
+
+/*
+ * Native hardware lines.
+ */
+static void
+rlgl_emit_line(struct rlgl *rl, uint32_t cmd, const struct rlgl_vtx *a,
+ const struct rlgl_vtx *b)
+{
+ struct v3d *v = &rl->v;
+ const struct rlgl_vtx *p[2];
+ int i;
+
+ p[0] = a; p[1] = b;
+ v3d_emit1(v, (8 << 16) | cmd); /* KXYZ (type 8) */
+ for (i = 0; i < 2; i++) {
+ v3d_emitf(v, p[i]->r);
+ v3d_emitf(v, p[i]->g);
+ v3d_emitf(v, p[i]->b);
+ v3d_emitf(v, p[i]->x);
+ v3d_emitf(v, p[i]->y);
+ v3d_emitf(v, p[i]->z);
+ }
+}
+
+void
+rlgl_line(struct rlgl *rl, const struct rlgl_vtx *a, const struct rlgl_vtx *b)
+{
+
+ rl->tris++;
+ rlgl_emit_line(rl, V3D_GL_LINE, a, b);
+}
+
+void
+rlgl_aaline(struct rlgl *rl, const struct rlgl_vtx *a, const struct rlgl_vtx *b)
+{
+
+ rl->tris++;
+ rlgl_emit_line(rl, V3D_GL_AALINE, a, b);
+}
+
+/*
+ * Anti-aliased points (GL_POINT_SMOOTH) via microcode aapoint (cmd 50).
+ */
+#define RLGL_PTTEX_DIM 32
+#define RLGL_PTTEX_USCALE 0x1F8000 /* ~31.5 in 16.16 -> span [0,1] */
+
+static int
+rlgl_pt_tex_ensure(struct rlgl *rl)
+{
+ static uint16_t tex[RLGL_PTTEX_DIM * RLGL_PTTEX_DIM];
+ const float c = RLGL_PTTEX_DIM / 2.0f - 0.5f; /* texel centre */
+ const float rad = RLGL_PTTEX_DIM / 2.0f - 1.5f; /* disc radius */
+ int x, y;
+
+ if (rl->pt_tex.addr != 0)
+ return 0;
+ if (rlgl_tex_create(rl, &rl->pt_tex, RLGL_PTTEX_DIM, RLGL_PTTEX_DIM,
+ RLGL_FMT_4444) != 0)
+ return -1;
+ for (y = 0; y < RLGL_PTTEX_DIM; y++)
+ for (x = 0; x < RLGL_PTTEX_DIM; x++) {
+ float dx = (float)x - c, dy = (float)y - c;
+ float d2 = dx * dx + dy * dy; /* no sqrt: compare r^2 */
+ int a;
+
+ /*
+ * Radial alpha: opaque core, a several-texel linear
+ * coverage ramp at the rim
+ */
+ const float ramp = 3.0f; /* rim width, texels */
+ if (d2 <= (rad - ramp) * (rad - ramp))
+ a = 15; /* opaque core */
+ else if (d2 >= rad * rad)
+ a = 0; /* transparent */
+ else {
+ /* linear in true distance across [rad-ramp,rad] */
+ float d = (d2 - (rad - ramp) * (rad - ramp)) /
+ ((rad * rad) - (rad - ramp) * (rad - ramp));
+ a = (int)(15.0f * (1.0f - d) + 0.5f);
+ if (a < 0) a = 0;
+ if (a > 15) a = 15;
+ }
+ /* ARGB4444: alpha = coverage, RGB = white */
+ tex[y * RLGL_PTTEX_DIM + x] =
+ (uint16_t)((a << 12) | 0x0FFF);
+ }
+ return rlgl_tex_upload(rl, &rl->pt_tex, tex);
+}
+
+void
+rlgl_aapoint_begin(struct rlgl *rl)
+{
+ struct v3d *v = &rl->v;
+ static const float seed[3][9] = {
+ { 255, 255, 255, 1, 1, 100, 0, 0, 1 },
+ { 255, 255, 255, 3, 1, 100, 0, 0, 1 },
+ { 255, 255, 255, 1, 3, 100, 0, 0, 1 },
+ };
+ int i, k;
+
+ if (rlgl_pt_tex_ensure(rl) != 0)
+ return;
+ /* bind the coverage texture, uscale/vscale map [0,1] over any size */
+ v3d_texbind(v, rl->pt_tex.addr, rl->pt_tex.srcstride,
+ rl->pt_tex.w, rl->pt_tex.h, RLGL_PTTEX_USCALE, RLGL_PTTEX_USCALE);
+ v3d_st(v, V3D_PE_SRCFMT, RLGL_FMT_4444);
+ v3d_st(v, V3D_PE_SRCFILTER, 1 << 7); /* bilinear: smooth the rim */
+ rl->cur_tex = rl->pt_tex.addr;
+ set_srcfunc(rl, RLGL_ENV_MODULATE); /* colour = FGColor x white tex,
+ * SrcAlpha = TexAlpha (coverage)
+ * x FGColor-alpha (0xff) */
+ v3d_st(v, V3D_PE_ZBUFMODE, 0);
+ v3d_st(v, V3D_PE_ZBUFWRMODE, 0);
+ v3d_emit1(v, (20 << 16) | V3D_GL_TRIANGLE); /* KXYZUVQ */
+ for (i = 0; i < 3; i++)
+ for (k = 0; k < 9; k++)
+ v3d_emitf(v, seed[i][k]);
+}
+
+void
+rlgl_aapoint(struct rlgl *rl, const struct rlgl_vtx *a, float size)
+{
+ struct v3d *v = &rl->v;
+ int r = (int)(a->r + 0.5f), g = (int)(a->g + 0.5f);
+ int b = (int)(a->b + 0.5f);
+ uint32_t fg;
+
+ if (r < 0) r = 0; if (r > 255) r = 255;
+ if (g < 0) g = 0; if (g > 255) g = 255;
+ if (b < 0) b = 0; if (b > 255) b = 255;
+ /* FGColor 0xAARRGGBB: alpha 0xff so MODULATE SrcAlpha = TexAlpha */
+ fg = 0xFF000000u | ((uint32_t)r << 16) | ((uint32_t)g << 8) |
+ (uint32_t)b;
+ rl->tris++;
+ v3d_st(v, V3D_PE_FGCOLOR, fg);
+ v3d_emit1(v, (8 << 16) | V3D_GL_AAPOINT); /* KXYZ */
+ v3d_emitf(v, size); /* diameter */
+ v3d_emitf(v, a->r); v3d_emitf(v, a->g); v3d_emitf(v, a->b);
+ v3d_emitf(v, a->x); v3d_emitf(v, a->y); v3d_emitf(v, a->z);
+}
+
+void
+rlgl_aapoint_end(struct rlgl *rl)
+{
+
+ /* release the coverage-texture bind: force the next textured draw to
+ * rebind (which re-selects its own filter/env), and untextured draws
+ * to re-select NOTEXTURE; drop the bilinear filter we forced. */
+ v3d_st(&rl->v, V3D_PE_SRCFILTER, 0);
+ rl->cur_tex = 0;
+ set_srcfunc(rl, 0);
+}
+
+/*
+ * Emit one KXYZUVQ (or KFXYZUVQ when fog_on) vertex record
+ */
+static void
+rlgl_emit_vtx(struct v3d *v, const struct rlgl_vtx *p, int fog_on)
+{
+
+ v3d_emitf(v, p->r);
+ v3d_emitf(v, p->g);
+ v3d_emitf(v, p->b);
+ if (fog_on)
+ v3d_emitf(v, p->f);
+ v3d_emitf(v, p->x);
+ v3d_emitf(v, p->y);
+ v3d_emitf(v, p->z);
+ v3d_emitf(v, p->u);
+ v3d_emitf(v, p->v);
+ v3d_emitf(v, p->q);
+}
+
+/*
+ * Native strip/fan common core.
+ */
+static void
+rlgl_emit_run(struct rlgl *rl, uint32_t startcmd, uint32_t contword,
+ const struct rlgl_vtx *v, int n)
+{
+ struct v3d *vv = &rl->v;
+ int i;
+
+ if (n < 3)
+ return;
+ v3d_emit1(vv, ((rl->fog_on ? 23 : 20) << 16) | startcmd);
+ rlgl_emit_vtx(vv, &v[0], rl->fog_on);
+ rlgl_emit_vtx(vv, &v[1], rl->fog_on);
+ rlgl_emit_vtx(vv, &v[2], rl->fog_on);
+ for (i = 3; i < n; i++) {
+ v3d_emit1(vv, contword);
+ rlgl_emit_vtx(vv, &v[i], rl->fog_on);
+ }
+ rl->tris += (uint64_t)(n - 2);
+}
+
+void
+rlgl_tristrip(struct rlgl *rl, const struct rlgl_vtx *v, int n)
+{
+
+ rlgl_emit_run(rl, V3D_GL_TRISTRIP, V3D_GL_STRIP_CONT, v, n);
+}
+
+void
+rlgl_trifan(struct rlgl *rl, const struct rlgl_vtx *v, int n)
+{
+
+ rlgl_emit_run(rl, V3D_GL_TRIFAN, V3D_GL_FAN_CONT, v, n);
+}
+
+void
+rlgl_tris_idx(struct rlgl *rl, const float *verts9, const uint8_t *idx,
+ int ntris)
+{
+ struct v3d *v = &rl->v;
+ uint32_t *p;
+
+ if (ntris <= 0)
+ return;
+ p = v3d_reserve(v, (uint32_t)ntris * 28);
+ if (p == NULL)
+ return;
+ (void)rlvec.emit_tris(p, verts9, idx, ntris,
+ (20 << 16) | V3D_GL_TRIANGLE); /* KXYZUVQ */
+ v3d_commit(v, (uint32_t)ntris * 28);
+ rl->tris += (uint64_t)ntris;
+}
+
+int
+rlgl_finish(struct rlgl *rl)
+{
+
+ return v3d_sync(&rl->v);
+}
+
+int
+rlgl_swap(struct rlgl *rl)
+{
+ struct v3d *v = &rl->v;
+
+ if (!v->opt_nosync) {
+ if (v3d_sync(v) != 0)
+ return -1;
+ }
+ if (v3d_flip(v, rl->fb[rl->back]) != 0)
+ return -1;
+ rl->back ^= 1;
+ v3d_st(v, V3D_PE_DSTBASE, rl->fb[rl->back]);
+ rl->frames++;
+
+ if (v->opt_stats) {
+ uint64_t now = v3d_now_ns();
+
+ if (rl->last_swap_ns != 0) {
+ uint64_t d = now - rl->last_swap_ns;
+
+ rl->frame_ns_tot += d;
+ if (rl->frame_ns_min == 0 || d < rl->frame_ns_min)
+ rl->frame_ns_min = d;
+ if (d > rl->frame_ns_max)
+ rl->frame_ns_max = d;
+ }
+ rl->last_swap_ns = now;
+ /*
+ * Cumulative snapshot lines: diffing two RLGLSNAPs
+ * isolates the demo window from load/console frames.
+ */
+ if (v->opt_stats >= 2 && (rl->frames & 127) == 0)
+ printf("RLGLSNAP frames=%d ns=%llu submit_ns=%llu "
+ "sync_ns=%llu flip_ns=%llu submits=%llu "
+ "bytes=%llu tris=%llu uploads=%llu "
+ "upl_bytes=%llu\n",
+ rl->frames,
+ (unsigned long long)rl->frame_ns_tot,
+ (unsigned long long)v->st.submit_ns,
+ (unsigned long long)v->st.sync_ns,
+ (unsigned long long)v->st.flip_ns,
+ (unsigned long long)v->st.submits,
+ (unsigned long long)v->st.submit_bytes,
+ (unsigned long long)rl->tris,
+ (unsigned long long)v->st.uploads,
+ (unsigned long long)v->st.upload_bytes);
+ }
+ return 0;
+}
+
+void
+rlgl_stats_print(struct rlgl *rl)
+{
+ struct v3d_stats *s = &rl->v.st;
+ double nf = rl->frames > 0 ? (double)rl->frames : 1.0;
+ double ms = 1e6;
+
+ if (rl->stats_printed)
+ return;
+ rl->stats_printed = 1;
+ printf("RLGL frames=%d tris=%llu submits=%llu syncs=%llu "
+ "flips=%llu\n", rl->frames,
+ (unsigned long long)rl->tris,
+ (unsigned long long)s->submits,
+ (unsigned long long)s->syncs,
+ (unsigned long long)s->flips);
+ printf("RLGL words=%llu submit_bytes=%llu uploads=%llu "
+ "upload_bytes=%llu st_calls=%llu texbinds=%llu\n",
+ (unsigned long long)s->words,
+ (unsigned long long)s->submit_bytes,
+ (unsigned long long)s->uploads,
+ (unsigned long long)s->upload_bytes,
+ (unsigned long long)s->st_calls,
+ (unsigned long long)s->texbinds);
+ printf("RLGL per_frame tris=%.1f submits=%.2f bytes=%.0f "
+ "upload_bytes=%.0f\n", (double)rl->tris / nf,
+ (double)s->submits / nf, (double)s->submit_bytes / nf,
+ (double)s->upload_bytes / nf);
+ if (rl->v.opt_stats && rl->frames > 1) {
+ double blocked = (double)(s->submit_ns + s->sync_ns +
+ s->flip_ns);
+ double tot = (double)rl->frame_ns_tot;
+ /* first swap has no predecessor: nf-1 timed frames */
+ double tf = (double)(rl->frames - 1);
+
+ printf("RLGL avg_frame_ms=%.2f min=%.2f max=%.2f\n",
+ tot / tf / ms,
+ (double)rl->frame_ns_min / ms,
+ (double)rl->frame_ns_max / ms);
+ printf("RLGL avg_submit_ms=%.2f avg_sync_ms=%.2f "
+ "avg_flip_ms=%.2f avg_blocked_ms=%.2f "
+ "avg_build_ms=%.2f\n",
+ (double)s->submit_ns / nf / ms,
+ (double)s->sync_ns / nf / ms,
+ (double)s->flip_ns / nf / ms,
+ blocked / nf / ms,
+ (tot / tf - blocked / nf) / ms);
+ }
+}
diff --git a/src/mesa/drivers/verite/rlgl.h b/src/mesa/drivers/verite/rlgl.h
new file mode 100644
index 0000000..002134c
--- /dev/null
+++ b/src/mesa/drivers/verite/rlgl.h
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+
+#ifndef RLGL_H
+#define RLGL_H
+
+#include <stdint.h>
+#include "libv3d.h"
+
+/* texel formats (PE SrcFmt/DstFmt encodings) */
+#define RLGL_FMT_565 4
+#define RLGL_FMT_4444 5
+#define RLGL_FMT_1555 6
+
+/*
+ * blend factors
+ */
+#define RLGL_BL_ZERO 6 /* Blend0 */
+#define RLGL_BL_ONE 7 /* Blend1 */
+#define RLGL_BL_SRCALPHA 2
+#define RLGL_BL_INVSRCALPHA 3
+#define RLGL_BL_OTHERCOLOR 0 /* dst side: src color; src: dst */
+#define RLGL_BL_INVOTHERCOLOR 1
+#define RLGL_BL_OWNCOLOR 10 /* own color - no GL equivalent */
+#define RLGL_BL_INVOWNCOLOR 11
+/* legacy names (dst-side view), kept for minigl */
+#define RLGL_BL_SRCCOLOR RLGL_BL_OTHERCOLOR
+#define RLGL_BL_INVSRCCOLOR RLGL_BL_INVOTHERCOLOR
+
+struct rlgl_tex {
+ uint32_t addr; /* VRAM; 0 = not resident */
+ uint16_t w, h; /* texels, powers of two */
+ uint32_t srcstride; /* encoded (Src1<<4)|Src0 */
+ uint32_t fmt; /* RLGL_FMT_* */
+};
+
+struct rlgl {
+ struct v3d v;
+ uint32_t fb[2], zbuf;
+ uint32_t scratch; /* glCopyPixels seed scratch surface */
+ int back;
+ struct rlgl_tex pt_tex; /* AA-point radial coverage tex (lazy;
+ * addr 0 = not yet created) */
+ /* redundant-state cache */
+ uint32_t cur_tex;
+ int cur_func; /* PE SrcFunc currently set */
+ int env_mod; /* SrcFunc code for textured draws */
+ int fog_on; /* emit the KFXYZUVQ fog channel */
+ int frames;
+ /* perf: swap-to-swap frame timing (RLGL_STATS) + counters */
+ uint64_t tris;
+ uint64_t frame_ns_tot, frame_ns_min, frame_ns_max;
+ uint64_t last_swap_ns;
+ int stats_printed;
+};
+
+/* per-vertex layout handed to rlgl_tri: the KXYZUVQ order */
+struct rlgl_vtx {
+ float r, g, b; /* 0..255 */
+ float x, y; /* screen pixels */
+ float z; /* 0..65535, smaller = nearer */
+ float u, v; /* texels */
+ float q; /* 1/w (any consistent scale) */
+ float f; /* fog factor 0..255, 255 = no fog (fog_on only) */
+};
+
+/* diagnostic: 1 = disable redundant-state caches (VRMESA_NOCACHE probe) */
+extern int rlgl_force_rebind;
+
+int rlgl_init(struct rlgl *, const char *ucpath);
+void rlgl_shutdown(struct rlgl *);
+
+int rlgl_tex_init(struct rlgl_tex *, int w, int h, uint32_t fmt);
+int rlgl_tex_alloc(struct rlgl *, struct rlgl_tex *);
+int rlgl_tex_pyramid(struct rlgl *, struct rlgl_tex *levs, int nlev);
+int rlgl_tex_create(struct rlgl *, struct rlgl_tex *, int w, int h,
+ uint32_t fmt);
+void rlgl_tex_free(struct rlgl *, struct rlgl_tex *);
+int rlgl_tex_upload(struct rlgl *, struct rlgl_tex *,
+ const uint16_t *texels);
+int rlgl_tex_upload_rows(struct rlgl *, struct rlgl_tex *,
+ const uint16_t *texels, int y, int rows);
+void rlgl_tex_bind(struct rlgl *, const struct rlgl_tex *);
+void rlgl_tex_env(struct rlgl *, int modulate);
+/* PE SrcFunc codes (spec table: 1=replace 2=decal 3=modulate) */
+#define RLGL_ENV_REPLACE 1
+#define RLGL_ENV_DECAL 2
+#define RLGL_ENV_MODULATE 3
+void rlgl_tex_env_code(struct rlgl *, int srcfunc_code);
+/*
+ * Per-axis clamp-to-edge (UClamp b15 / VClamp b16 of SrcMode).
+ */
+void rlgl_tex_wrap(struct rlgl *, int uclamp, int vclamp);
+void rlgl_tex_filter(struct rlgl *, int bilinear);
+
+/* PE ZBufMode compare codes (silicon-proven: LT ztest, LE GLQuake) */
+#define RLGL_Z_LT 1
+#define RLGL_Z_LE 3
+
+void rlgl_depth_test(struct rlgl *, int enable_lt_or_le);
+void rlgl_depth_func(struct rlgl *, int enable, int pe_code);
+void rlgl_depth_mask(struct rlgl *, int write);
+void rlgl_blend(struct rlgl *, int enable, uint32_t srcf, uint32_t dstf);
+void rlgl_alpha_test(struct rlgl *, int enable, uint32_t thresh8);
+/* GL window-coord box (bottom-left origin); flips to the PE top-left */
+void rlgl_scissor(struct rlgl *, int x, int y, int w, int h);
+/* 565 plane write mask; args are glColorMask R/G/B booleans (alpha n/a) */
+void rlgl_color_mask(struct rlgl *, int r, int g, int b);
+/* PE raster op (ALUMode 4-bit ROP code); disabled restores COPY */
+void rlgl_logic_op(struct rlgl *, int enable, int code);
+/* PE 4x4 ordered dither on 565 writes (GL_DITHER) */
+void rlgl_dither(struct rlgl *, int enable);
+/*
+ * Per-vertex fog (GL_FOG)
+ */
+void rlgl_fog(struct rlgl *, int enable, uint32_t rgb888);
+
+void rlgl_clear(struct rlgl *, int color, uint32_t rgb565,
+ int depth);
+/*
+ * glDrawPixels fast path
+ */
+int rlgl_draw_row(struct rlgl *, int x, int wy, int w,
+ const uint16_t *row565);
+/*
+ * Hardware glCopyPixels (GL_COLOR)
+ */
+int rlgl_copy_rect(struct rlgl *, uint32_t src_base, int src_w,
+ uint32_t dst_base, int dst_w, int sx, int sy, int dx, int dy,
+ int w, int h);
+/*
+ * Hardware glCopyTexSubImage
+ */
+int rlgl_copy_to_tex(struct rlgl *, uint32_t fb_base, int fb_h,
+ uint32_t tex_base, int tex_aw, int sx, int sy_gl, int dx, int dy,
+ int w, int h);
+void rlgl_tri(struct rlgl *, const struct rlgl_vtx *,
+ const struct rlgl_vtx *, const struct rlgl_vtx *);
+/*
+ * Native hardware lines from a 2-vertex KXYZ stream (only r,g,b,x,y,z
+ * used).
+ */
+void rlgl_line(struct rlgl *, const struct rlgl_vtx *,
+ const struct rlgl_vtx *);
+void rlgl_aaline(struct rlgl *, const struct rlgl_vtx *,
+ const struct rlgl_vtx *);
+/*
+ * Native anti-aliased points (GL_POINT_SMOOTH)
+ */
+void rlgl_aapoint_begin(struct rlgl *);
+void rlgl_aapoint(struct rlgl *, const struct rlgl_vtx *, float size);
+void rlgl_aapoint_end(struct rlgl *);
+/*
+ * Native hardware primitive strips/fans
+ */
+void rlgl_tristrip(struct rlgl *, const struct rlgl_vtx *v, int n);
+void rlgl_trifan(struct rlgl *, const struct rlgl_vtx *v, int n);
+/*
+ * Bulk indexed emit: verts9 = packed 9-float wire records
+ * (r,g,b,x,y,z,u,v,q), idx = 3*ntris vertex indices into verts9.
+ */
+void rlgl_tris_idx(struct rlgl *, const float *verts9,
+ const uint8_t *idx, int ntris);
+int rlgl_swap(struct rlgl *);
+int rlgl_finish(struct rlgl *);
+void rlgl_stats_print(struct rlgl *);
+
+#endif /* RLGL_H */
diff --git a/src/mesa/drivers/verite/verite3dio.h b/src/mesa/drivers/verite/verite3dio.h
new file mode 100644
index 0000000..1337dc3
--- /dev/null
+++ b/src/mesa/drivers/verite/verite3dio.h
@@ -0,0 +1,107 @@
+/*
+ * Vendored from src/sys/dev/pci/verite3dio.h
+ */
+
+/* $NetBSD: verite3dio.h,v 1.1 2026/07/15 20:53:22 rkujawa Exp $ */
+
+/*
+ * 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 AUTHOR ``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 AUTHOR 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.
+ */
+
+
+/*
+ * /dev/verite3d - expierimental 3D interface for veritefb(4)
+ *
+ * By design, it is completely independent of wsdisplay(4). It supports
+ * one exclusive client - opening the device stops the 2D microcode.
+ * Closing the device stops the 3D microcode and restores the console,
+ * in the state it was left behind.
+ *
+ * The client supplies its own RISC microcode (V3D_INIT) and owns
+ * the FIFO command stream. The kernel moves bytes (DMA) and tracks VRAM,
+ * but does NOT interpret the command stream. That is responsibility of
+ * the microcode (on the hardware side) and the userland process.
+ */
+
+#ifndef VERITE3DIO_H
+#define VERITE3DIO_H
+
+#include <sys/ioccom.h>
+
+#define V3D_RING_SLOTS 4
+#define V3D_SLOT_SIZE 65536
+
+#define V3D_VRAM_MMAP_OFF ((off_t)V3D_RING_SLOTS * V3D_SLOT_SIZE)
+
+/* Load client microcode into the foreign slot and cold-start it. */
+struct v3d_init {
+ uint64_t vi_ucode; /* in: user pointer to image */
+ uint32_t vi_size; /* in: bytes */
+ uint32_t vi_ctx_base; /* out: context save area (VRAM) */
+ uint32_t vi_memsize; /* out: total VRAM bytes */
+ uint32_t vi_pool_base; /* out: first V3D_ALLOC-managed byte */
+};
+#define V3D_INIT _IOWR('V', 0, struct v3d_init)
+
+struct v3d_submit {
+ uint32_t vs_slot; /* 0 to V3D_RING_SLOTS-1 */
+ uint32_t vs_len; /* bytes, word multiple */
+ uint32_t vs_swap; /* descriptor swap code 0..3 */
+};
+#define V3D_SUBMIT _IOW('V', 1, struct v3d_submit)
+
+/*
+ * Fence: send one word (the client microcode's sync command) and
+ * wait for the response word. In/out: word to send / word received.
+ */
+#define V3D_SYNC _IOWR('V', 2, uint32_t)
+
+struct v3d_alloc {
+ uint32_t va_size; /* in: bytes */
+ uint32_t va_align; /* in: byte alignment (0 = 4) */
+ uint32_t va_addr; /* out: VRAM byte address */
+};
+#define V3D_ALLOC _IOWR('V', 3, struct v3d_alloc)
+#define V3D_FREE _IOW('V', 4, uint32_t)
+
+struct v3d_mode {
+ uint32_t vm_depth; /* in: 8 or 16; 0 = query only */
+ uint32_t vm_frame_base; /* in: scanout base; 0 = keep */
+ uint32_t vm_width; /* out */
+ uint32_t vm_height; /* out */
+ uint32_t vm_stride; /* out: scanout line bytes */
+ uint32_t vm_pe_stride; /* out: PE Stride(3) dst encoding */
+};
+#define V3D_MODE _IOWR('V', 5, struct v3d_mode)
+
+/*
+ * vsync-waited FRAMEBASE flip.
+ */
+#define V3D_FLIP_NOWAIT 0x80000000U
+#define V3D_FLIP _IOW('V', 6, uint32_t)
+
+#endif /* VERITE3DIO_H */
diff --git a/src/mesa/drivers/verite/vrapi.c b/src/mesa/drivers/verite/vrapi.c
new file mode 100644
index 0000000..b2aa74d
--- /dev/null
+++ b/src/mesa/drivers/verite/vrapi.c
@@ -0,0 +1,319 @@
+/*
+ * 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 public API
+ */
+
+#include <stdlib.h>
+#ifdef VERITE_INSTRUMENT
+#include <stdio.h>
+#endif
+
+#include "vrdrv.h"
+
+#include "GL/vrmesa.h"
+#include "context.h"
+#include "extensions.h"
+#include "imports.h"
+#include "array_cache/acache.h"
+#include "swrast/swrast.h"
+#include "swrast_setup/swrast_setup.h"
+#include "tnl/tnl.h"
+#include "tnl/t_context.h"
+#include "tnl/t_pipeline.h"
+#include "drivers/common/driverfuncs.h"
+
+#ifdef VERITE_INSTRUMENT
+/*
+ * The single driver-layer perf accumulator (vrperf.h).
+ */
+struct vrperf g_vrp;
+
+/*
+ * RunPipeline wrapper
+ */
+static void
+vr_run_pipeline(GLcontext *ctx)
+{
+
+ VRP_ZE(VRP_Z_PIPELINE);
+ _tnl_run_pipeline(ctx);
+ VRP_ZX(VRP_Z_PIPELINE);
+}
+
+void
+vrPerfInit(vrmesa_context *vm)
+{
+ const char *p = getenv("VRMESA_PROF");
+
+ g_vrp.level = p ? atoi(p) : 0;
+ g_vrp.on = g_vrp.level >= 1;
+ g_vrp.rl = &vm->rl;
+ g_vrp.tb0 = vrp_tb64();
+ g_vrp.ns0 = v3d_now_ns();
+ /*
+ * Bridge the librlgl/libv3d _ns timers into VRPERF
+ */
+ if (g_vrp.on && vm->rl.v.opt_stats == 0)
+ vm->rl.v.opt_stats = 1;
+ atexit(vrPerfDump);
+}
+
+void
+vrPerfSwap(vrmesa_context *vm)
+{
+
+ (void)vm;
+ g_vrp.frames++;
+ /* cumulative snapshot, diff two lines to isolate a demo window */
+ if (g_vrp.level >= 2 && (g_vrp.frames & 127) == 0)
+ printf("VRPERFSNAP frame=%llu texres=%llu zfunc=%llu "
+ "blend=%llu fgcolor=%llu fences=%llu\n",
+ (unsigned long long)g_vrp.frames,
+ (unsigned long long)g_vrp.fb[VRP_FB_TEX_RESIDENCY],
+ (unsigned long long)g_vrp.fb[VRP_FB_DEPTHFUNC],
+ (unsigned long long)g_vrp.fb[VRP_FB_BLEND],
+ (unsigned long long)g_vrp.fgcolor_writes,
+ (unsigned long long)g_vrp.fences);
+}
+
+void
+vrPerfDump(void)
+{
+ static const char *zname[VRP_Z__N] = {
+ "pipeline", "render_start", "texvalidate", "tri_emit"
+ };
+ struct rlgl *rl = g_vrp.rl;
+ double tick_ns, nf;
+ uint64_t dtb;
+ int i;
+
+ if (g_vrp.printed || g_vrp.level < 1)
+ return;
+ g_vrp.printed = 1;
+
+ dtb = vrp_tb64() - g_vrp.tb0;
+ tick_ns = dtb ? (double)(v3d_now_ns() - g_vrp.ns0) / (double)dtb : 0.0;
+ nf = g_vrp.frames > 0 ? (double)g_vrp.frames : 1.0;
+
+ printf("VRPERF frames=%llu\n", (unsigned long long)g_vrp.frames);
+ printf("VRPERF fallback tex=%llu blend=%llu afunc=%llu zfunc=%llu "
+ "stipple=%llu smooth=%llu twoside=%llu specular=%llu "
+ "unfilled=%llu rmode=%llu texres=%llu alphagrad=%llu\n",
+ (unsigned long long)g_vrp.fb[VRP_FB_TEX_HWOK],
+ (unsigned long long)g_vrp.fb[VRP_FB_BLEND],
+ (unsigned long long)g_vrp.fb[VRP_FB_ALPHAFUNC],
+ (unsigned long long)g_vrp.fb[VRP_FB_DEPTHFUNC],
+ (unsigned long long)g_vrp.fb[VRP_FB_STIPPLE],
+ (unsigned long long)g_vrp.fb[VRP_FB_SMOOTH],
+ (unsigned long long)g_vrp.fb[VRP_FB_TWOSIDE],
+ (unsigned long long)g_vrp.fb[VRP_FB_SPECULAR],
+ (unsigned long long)g_vrp.fb[VRP_FB_UNFILLED_SUB],
+ (unsigned long long)g_vrp.fb[VRP_FB_RENDERMODE],
+ (unsigned long long)g_vrp.fb[VRP_FB_TEX_RESIDENCY],
+ (unsigned long long)g_vrp.fb[VRP_FB_TRI_ALPHAGRAD]);
+ printf("VRPERF texval dirty=%llu nlvl=%llu evict=%llu first=%llu "
+ "bindonly=%llu creates=%llu failv=%llu failu=%llu\n",
+ (unsigned long long)g_vrp.tv_dirty,
+ (unsigned long long)g_vrp.tv_nlevels,
+ (unsigned long long)g_vrp.tv_evict,
+ (unsigned long long)g_vrp.tv_first,
+ (unsigned long long)g_vrp.tv_bindonly,
+ (unsigned long long)g_vrp.tv_creates,
+ (unsigned long long)g_vrp.tv_failv,
+ (unsigned long long)g_vrp.tv_failu);
+ printf("VRPERF churn fgcolor=%llu mipbind=%llu filt=%llu fences=%llu\n",
+ (unsigned long long)g_vrp.fgcolor_writes,
+ (unsigned long long)g_vrp.mip_binds,
+ (unsigned long long)g_vrp.filt_changes,
+ (unsigned long long)g_vrp.fences);
+ printf("VRPERF cpu_ms_per_frame tick_ns=%.3f", tick_ns);
+ for (i = 0; i < VRP_Z__N; i++)
+ printf(" %s=%.2f", zname[i],
+ (double)g_vrp.zone[i] * tick_ns / nf / 1e6);
+ printf("\n");
+ if (rl != NULL)
+ printf("VRPERF gpu_wait_ms_per_frame submit=%.2f sync=%.2f "
+ "flip=%.2f | tris=%llu st_calls=%llu texbinds=%llu "
+ "submit_kb=%llu\n",
+ (double)rl->v.st.submit_ns / nf / 1e6,
+ (double)rl->v.st.sync_ns / nf / 1e6,
+ (double)rl->v.st.flip_ns / nf / 1e6,
+ (unsigned long long)rl->tris,
+ (unsigned long long)rl->v.st.st_calls,
+ (unsigned long long)rl->v.st.texbinds,
+ (unsigned long long)(rl->v.st.submit_bytes / 1024));
+}
+#endif /* VERITE_INSTRUMENT */
+
+vrMesaContext
+vrMesaCreateContext(const char *ucodepath)
+{
+ vrmesa_context *vmesa;
+ GLcontext *ctx;
+ struct dd_function_table functions;
+
+ if (ucodepath == NULL)
+ ucodepath = getenv("VERITE_UCODE");
+ if (ucodepath == NULL) {
+ _mesa_printf("vrMesaCreateContext: no microcode "
+ "(pass a path or set VERITE_UCODE)\n");
+ return NULL;
+ }
+
+ vmesa = CALLOC_STRUCT(vrmesa_context);
+ if (vmesa == NULL)
+ return NULL;
+
+ if (rlgl_init(&vmesa->rl, ucodepath) != 0) {
+ _mesa_free(vmesa);
+ return NULL;
+ }
+ vmesa->fg_alpha = ~0u; /* FGColor alpha cache: unset */
+
+ if (!_mesa_initialize_visual(&vmesa->glvis,
+ GL_TRUE, /* rgb */
+ GL_TRUE, /* double buffered */
+ GL_FALSE, /* stereo */
+ 5, 6, 5, 0, /* rgba bits */
+ 0, /* index bits */
+ 16, /* depth bits */
+ 0, /* stencil */
+ 0, 0, 0, 0, /* accum */
+ 0)) { /* samples */
+ rlgl_shutdown(&vmesa->rl);
+ _mesa_free(vmesa);
+ return NULL;
+ }
+
+ _mesa_init_driver_functions(&functions);
+ vrInitDDFuncs(&functions);
+
+ if (!_mesa_initialize_context(&vmesa->glctx, &vmesa->glvis, NULL,
+ &functions, (void *)vmesa)) {
+ rlgl_shutdown(&vmesa->rl);
+ _mesa_free(vmesa);
+ return NULL;
+ }
+ ctx = &vmesa->glctx;
+
+ vrInitTexConsts(ctx);
+
+ /* hardware depth+color, no software stencil/accum/alpha planes */
+ _mesa_initialize_framebuffer(&vmesa->glfb, &vmesa->glvis,
+ GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+
+ _swrast_CreateContext(ctx);
+ _ac_CreateContext(ctx);
+ _tnl_CreateContext(ctx);
+ _swsetup_CreateContext(ctx);
+ _swsetup_Wakeup(ctx);
+
+ /*
+ * The PE fog unit iterates a per-vertex factor (KFXYZUVQ f term)
+ */
+ _tnl_allow_vertex_fog(ctx, GL_TRUE);
+ _tnl_allow_pixel_fog(ctx, GL_FALSE);
+
+ vrInitSpanFuncs(ctx);
+ vrInitTriFuncs(ctx);
+
+#ifdef VERITE_INSTRUMENT
+ TNL_CONTEXT(ctx)->Driver.RunPipeline = vr_run_pipeline;
+#else
+ TNL_CONTEXT(ctx)->Driver.RunPipeline = _tnl_run_pipeline;
+#endif
+
+ _mesa_enable_sw_extensions(ctx);
+
+ vrPerfInit(vmesa);
+
+ return (vrMesaContext)vmesa;
+}
+
+void
+vrMesaDestroyContext(vrMesaContext vrctx)
+{
+ vrmesa_context *vmesa = (vrmesa_context *)vrctx;
+ GLcontext *ctx;
+
+ if (vmesa == NULL)
+ return;
+ ctx = &vmesa->glctx;
+
+ if (vrMesaGetCurrentContext() == vrctx)
+ _mesa_make_current2(NULL, NULL, NULL);
+
+ _swsetup_DestroyContext(ctx);
+ _tnl_DestroyContext(ctx);
+ _ac_DestroyContext(ctx);
+ _swrast_DestroyContext(ctx);
+
+ _mesa_free_framebuffer_data(&vmesa->glfb);
+ _mesa_free_context_data(ctx);
+ vrPerfDump(); /* while g_vrp.rl is still valid */
+ rlgl_shutdown(&vmesa->rl);
+ _mesa_free(vmesa);
+}
+
+vrMesaContext
+vrMesaGetCurrentContext(void)
+{
+ GET_CURRENT_CONTEXT(ctx);
+ return (vrMesaContext)ctx;
+}
+
+void
+vrMesaMakeCurrent(vrMesaContext vrctx)
+{
+ vrmesa_context *vmesa = (vrmesa_context *)vrctx;
+
+ if (vmesa == NULL) {
+ _mesa_make_current2(NULL, NULL, NULL);
+ return;
+ }
+ _mesa_make_current2(&vmesa->glctx, &vmesa->glfb, &vmesa->glfb);
+}
+
+void
+vrMesaSwapBuffers(void)
+{
+ GET_CURRENT_CONTEXT(ctx);
+ vrmesa_context *vmesa;
+
+ if (ctx == NULL)
+ return;
+ vmesa = VRMESA_CONTEXT(ctx);
+ _mesa_notifySwapBuffers(ctx);
+ rlgl_swap(&vmesa->rl);
+ /* rlgl_swap fences the engine before flipping */
+ vmesa->engine_dirty = GL_FALSE;
+ vrPerfSwap(vmesa);
+}
diff --git a/src/mesa/drivers/verite/vrdd.c b/src/mesa/drivers/verite/vrdd.c
new file mode 100644
index 0000000..36daf2b
--- /dev/null
+++ b/src/mesa/drivers/verite/vrdd.c
@@ -0,0 +1,382 @@
+/*
+ * 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 device-driver functions
+ */
+
+#include "vrdrv.h"
+
+#include "context.h"
+#include "swrast/swrast.h"
+#include "swrast/s_context.h" /* SWRAST_CONTEXT / _RasterMask */
+#include "swrast_setup/swrast_setup.h"
+#include "array_cache/acache.h"
+#include "tnl/tnl.h"
+
+static const GLubyte *
+vr_get_string(GLcontext *ctx, GLenum pname)
+{
+ (void)ctx;
+ switch (pname) {
+ case GL_VENDOR:
+ return (const GLubyte *)"Rendition";
+ case GL_RENDERER:
+ return (const GLubyte *)"Mesa Rendition Verite V2200";
+ default:
+ return NULL;
+ }
+}
+
+static void
+vr_get_buffer_size(GLframebuffer *buffer, GLuint *width, GLuint *height)
+{
+ GET_CURRENT_CONTEXT(ctx);
+ vrmesa_context *vmesa;
+
+ (void)buffer;
+ if (ctx == NULL)
+ return;
+ vmesa = VRMESA_CONTEXT(ctx);
+ *width = vmesa->rl.v.width;
+ *height = vmesa->rl.v.height;
+}
+
+/* GL_CLEAR..GL_SET (0x1500..0x150F) -> PE ALUMode 4-bit ROP */
+static const GLubyte vr_rop[16] = {
+ 0x0, 0x8, 0x4, 0xC, 0x2, 0xA, 0x6, 0xE,
+ 0x1, 0x9, 0x5, 0xD, 0x3, 0xB, 0x7, 0xF,
+};
+
+static void
+vr_update_state(GLcontext *ctx, GLuint new_state)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ struct rlgl *rl = &vmesa->rl;
+
+ _swrast_InvalidateState(ctx, new_state);
+ _swsetup_InvalidateState(ctx, new_state);
+ _ac_InvalidateState(ctx, new_state);
+ _tnl_InvalidateState(ctx, new_state);
+
+ /*
+ * PE state used only by the hardware triangle path
+ */
+ if (new_state & _NEW_DEPTH) {
+ /* GL_ALWAYS = compare off; writes are ZBufWrMode's job */
+ rlgl_depth_func(rl,
+ ctx->Depth.Test && ctx->Depth.Func != GL_ALWAYS,
+ ctx->Depth.Func == GL_LESS ? RLGL_Z_LT : RLGL_Z_LE);
+ rlgl_depth_mask(rl, ctx->Depth.Test && ctx->Depth.Mask);
+ }
+ if (new_state & _NEW_COLOR) {
+ GLuint srcf, dstf;
+ GLboolean na;
+
+ rlgl_color_mask(rl, ctx->Color.ColorMask[0],
+ ctx->Color.ColorMask[1], ctx->Color.ColorMask[2]);
+
+ /* honor GL_DITHER (default on): PE 4x4 dither on 565 */
+ rlgl_dither(rl, ctx->Color.DitherFlag);
+
+ if (ctx->Color._LogicOpEnabled) {
+ rlgl_logic_op(rl, 1,
+ vr_rop[ctx->Color.LogicOp - GL_CLEAR]);
+ rlgl_blend(rl, 0, 0, 0);
+ } else {
+ rlgl_logic_op(rl, 0, 0);
+ if (ctx->Color.BlendEnabled &&
+ vrBlendMap(ctx, &srcf, &dstf, &na))
+ rlgl_blend(rl, 1, srcf, dstf);
+ else
+ rlgl_blend(rl, 0, 0, 0);
+ }
+
+ /* TranspReject: reject SrcAlpha <= thresh */
+ if (ctx->Color.AlphaEnabled) {
+ GLint ref = (GLint)(ctx->Color.AlphaRef *
+ 255.0F + 0.5F);
+
+ switch (ctx->Color.AlphaFunc) {
+ case GL_GREATER:
+ rlgl_alpha_test(rl, 1, (uint32_t)ref);
+ break;
+ case GL_GEQUAL:
+ /* pass a >= ref == reject a <= ref-1 */
+ if (ref <= 0)
+ rlgl_alpha_test(rl, 0, 0);
+ else
+ rlgl_alpha_test(rl, 1,
+ (uint32_t)(ref - 1));
+ break;
+ case GL_NEVER:
+ rlgl_alpha_test(rl, 1, 255);
+ break;
+ default: /* ALWAYS or gated shapes */
+ rlgl_alpha_test(rl, 0, 0);
+ break;
+ }
+ } else
+ rlgl_alpha_test(rl, 0, 0);
+ }
+
+ if (new_state & _NEW_FOG) {
+ if (ctx->Fog.Enabled) {
+ GLubyte c[3];
+
+ UNCLAMPED_FLOAT_TO_UBYTE(c[0], ctx->Fog.Color[0]);
+ UNCLAMPED_FLOAT_TO_UBYTE(c[1], ctx->Fog.Color[1]);
+ UNCLAMPED_FLOAT_TO_UBYTE(c[2], ctx->Fog.Color[2]);
+ rlgl_fog(rl, 1, ((uint32_t)c[0] << 16) |
+ ((uint32_t)c[1] << 8) | (uint32_t)c[2]);
+ } else
+ rlgl_fog(rl, 0, 0);
+ }
+
+ if (new_state & _NEW_SCISSOR) {
+ if (ctx->Scissor.Enabled)
+ rlgl_scissor(rl, ctx->Scissor.X, ctx->Scissor.Y,
+ ctx->Scissor.Width, ctx->Scissor.Height);
+ else
+ rlgl_scissor(rl, 0, 0, rl->v.width, rl->v.height);
+ }
+
+ vrCheckHwRender(ctx);
+}
+
+static void
+vr_clear(GLcontext *ctx, GLbitfield mask, GLboolean all,
+ GLint x, GLint y, GLint width, GLint height)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ GLbitfield rest = mask;
+ int cc = 0, cz = 0;
+
+ /* frame boundary for texture evict-pinning (once per GLQuake frame) */
+ vrTexNewFrame();
+
+ if (all && !ctx->Scissor.Enabled) {
+ if ((mask & DD_BACK_LEFT_BIT) != 0 &&
+ ctx->Color.ColorMask[0] && ctx->Color.ColorMask[1] &&
+ ctx->Color.ColorMask[2]) {
+ cc = 1;
+ rest &= ~DD_BACK_LEFT_BIT;
+ }
+ /* engine Z clear writes the max-Z pattern = depth 1.0 */
+ if ((mask & DD_DEPTH_BIT) != 0 && ctx->Depth.Mask &&
+ ctx->Depth.Clear == 1.0) {
+ cz = 1;
+ rest &= ~DD_DEPTH_BIT;
+ }
+ }
+
+ if (cc || cz) {
+ const GLfloat *c = ctx->Color.ClearColor;
+ GLuint r = (GLuint)(c[0] * 255.0F + 0.5F);
+ GLuint g = (GLuint)(c[1] * 255.0F + 0.5F);
+ GLuint b = (GLuint)(c[2] * 255.0F + 0.5F);
+ uint32_t rgb565;
+
+ if (r > 255) r = 255;
+ if (g > 255) g = 255;
+ if (b > 255) b = 255;
+ rgb565 = ((r & 0xf8) << 8) | ((g & 0xfc) << 3) | (b >> 3);
+ rlgl_clear(&vmesa->rl, cc, rgb565, cz);
+ vmesa->engine_dirty = GL_TRUE;
+ }
+
+ if (rest != 0)
+ _swrast_Clear(ctx, rest, all, x, y, width, height);
+}
+
+/*
+ * Hardware glDrawPixels.
+ */
+static void
+vr_drawpixels(GLcontext *ctx, GLint x, GLint y, GLsizei width, GLsizei height,
+ GLenum format, GLenum type, const struct gl_pixelstore_attrib *unpack,
+ const GLvoid *pixels)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ static GLushort row565[2048];
+ GLint destX, destY, drawW, drawH, skipPix, skipRows, rowLen, r, i, ch;
+ const GLchan *src;
+
+ if (!ctx->Current.RasterPosValid)
+ return;
+ if ((format != GL_RGBA && format != GL_RGB) || type != CHAN_TYPE ||
+ ctx->_ImageTransferState != 0 || !ctx->Visual.rgbMode ||
+ (SWRAST_CONTEXT(ctx)->_RasterMask & ~CLIP_BIT) != 0 ||
+ ctx->Texture._EnabledCoordUnits != 0 ||
+ unpack->Alignment != 1 || unpack->SwapBytes || unpack->LsbFirst ||
+ ctx->Pixel.ZoomX != 1.0F || ctx->Pixel.ZoomY != 1.0F)
+ goto slow;
+
+ destX = x; destY = y; drawW = width; drawH = height;
+ skipPix = unpack->SkipPixels; skipRows = unpack->SkipRows;
+ rowLen = unpack->RowLength > 0 ? unpack->RowLength : width;
+
+ /* clip to the draw buffer (mirrors swrast fast_draw_pixels) */
+ if (destX < ctx->DrawBuffer->_Xmin) {
+ skipPix += ctx->DrawBuffer->_Xmin - destX;
+ drawW -= ctx->DrawBuffer->_Xmin - destX;
+ destX = ctx->DrawBuffer->_Xmin;
+ }
+ if (destX + drawW > ctx->DrawBuffer->_Xmax)
+ drawW -= destX + drawW - ctx->DrawBuffer->_Xmax;
+ if (drawW <= 0)
+ return;
+ if (destY < ctx->DrawBuffer->_Ymin) {
+ skipRows += ctx->DrawBuffer->_Ymin - destY;
+ drawH -= ctx->DrawBuffer->_Ymin - destY;
+ destY = ctx->DrawBuffer->_Ymin;
+ }
+ if (destY + drawH > ctx->DrawBuffer->_Ymax)
+ drawH -= destY + drawH - ctx->DrawBuffer->_Ymax;
+ if (drawH <= 0)
+ return;
+
+ /* mem_write needs 32-bit alignment: even x and even width */
+ if ((destX & 1) || (drawW & 1) || drawW > 2048)
+ goto slow;
+
+ ch = (format == GL_RGBA) ? 4 : 3;
+ src = (const GLchan *)pixels + (skipRows * rowLen + skipPix) * ch;
+ for (r = 0; r < drawH; r++) {
+ const GLchan *p = src + (GLint)r * rowLen * ch;
+
+ for (i = 0; i < drawW; i++) {
+ GLchan cr = p[i * ch], cg = p[i * ch + 1],
+ cb = p[i * ch + 2];
+
+ row565[i] = (GLushort)(((cr & 0xf8) << 8) |
+ ((cg & 0xfc) << 3) | (cb >> 3));
+ }
+ (void)rlgl_draw_row(&vmesa->rl, destX, destY + r, drawW, row565);
+ }
+ vmesa->engine_dirty = GL_TRUE;
+ return;
+slow:
+ _swrast_DrawPixels(ctx, x, y, width, height, format, type, unpack,
+ pixels);
+}
+
+/* rl.fb[back] is the draw target, the other buffer is scanned out */
+uint32_t
+vr_fb_base(struct rlgl *rl, GLenum buffer)
+{
+
+ if (buffer == GL_FRONT || buffer == GL_FRONT_LEFT)
+ return rl->fb[rl->back ^ 1];
+ return rl->fb[rl->back]; /* GL_BACK / default */
+}
+
+/*
+ * Hardware glCopyPixels(GL_COLOR).
+ */
+static void
+vr_copypixels(GLcontext *ctx, GLint srcx, GLint srcy, GLsizei width,
+ GLsizei height, GLint destx, GLint desty, GLenum type)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ struct rlgl *rl = &vmesa->rl;
+ GLint fbW = (GLint)rl->v.width, fbH = (GLint)rl->v.height;
+ uint32_t sbase, dbase;
+ GLint sy_pe, dy_pe;
+ GLboolean overlap;
+
+ if (type != GL_COLOR || ctx->_ImageTransferState != 0 ||
+ !ctx->Visual.rgbMode ||
+ (SWRAST_CONTEXT(ctx)->_RasterMask & ~CLIP_BIT) != 0 ||
+ ctx->Pixel.ZoomX != 1.0F || ctx->Pixel.ZoomY != 1.0F)
+ goto slow;
+
+ /* both rects must lie fully inside their buffers (swrast handles
+ * the clipped/border cases correctly) */
+ if (width <= 0 || height <= 0 ||
+ srcx < 0 || srcy < 0 || srcx + width > fbW || srcy + height > fbH ||
+ destx < 0 || desty < 0 || destx + width > fbW ||
+ desty + height > fbH)
+ goto slow;
+
+ sbase = vr_fb_base(rl, ctx->Pixel.ReadBuffer);
+ dbase = vr_fb_base(rl, ctx->Color.DrawBuffer);
+
+ /*
+ * The copy hides the true src/dst relationship from the microcode's
+ * overlap-direction logic
+ */
+ overlap = (sbase == dbase) &&
+ !(destx >= srcx + width || destx + width <= srcx ||
+ desty >= srcy + height || desty + height <= srcy);
+ if (overlap)
+ goto slow;
+
+ /* GL window (bottom-left) -> PE surface (top-left) top edge */
+ sy_pe = fbH - srcy - height;
+ dy_pe = fbH - desty - height;
+ if (rlgl_copy_rect(rl, sbase, fbW, dbase, fbW, srcx, sy_pe,
+ destx, dy_pe, width, height) != 0)
+ goto slow;
+ vmesa->engine_dirty = GL_TRUE;
+ return;
+slow:
+ _swrast_CopyPixels(ctx, srcx, srcy, destx, desty, width, height, type);
+}
+
+static void
+vr_finish(GLcontext *ctx)
+{
+
+ vrFence(VRMESA_CONTEXT(ctx));
+}
+
+static void
+vr_flush(GLcontext *ctx)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+
+ if (vmesa->engine_dirty)
+ (void)v3d_flush(&vmesa->rl.v);
+}
+
+void
+vrInitDDFuncs(struct dd_function_table *functions)
+{
+
+ functions->GetString = vr_get_string;
+ functions->GetBufferSize = vr_get_buffer_size;
+ functions->UpdateState = vr_update_state;
+ functions->Clear = vr_clear;
+ functions->Finish = vr_finish;
+ functions->Flush = vr_flush;
+ functions->DrawPixels = vr_drawpixels;
+ functions->CopyPixels = vr_copypixels;
+ vrInitTexFuncs(functions);
+}
diff --git a/src/mesa/drivers/verite/vrdrv.h b/src/mesa/drivers/verite/vrdrv.h
new file mode 100644
index 0000000..9762e95
--- /dev/null
+++ b/src/mesa/drivers/verite/vrdrv.h
@@ -0,0 +1,125 @@
+/*
+ * 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.
+ */
+
+/*
+ * Mesa driver for the Rendition Verite V2200 over librlgl
+ * (NetBSD /dev/verite3d) - "veritemesa".
+ */
+
+#ifndef VRDRV_H
+#define VRDRV_H
+
+#include "glheader.h"
+#include "mtypes.h"
+
+#include "rlgl.h"
+
+#include "vrperf.h"
+
+typedef struct vrmesa_context {
+ GLcontext glctx; /* base class -- must be first */
+ GLvisual glvis;
+ GLframebuffer glfb;
+
+ struct rlgl rl;
+
+ GLboolean hw_tris; /* state allows PE triangles */
+ GLboolean engine_dirty; /* PE work queued, spans must fence */
+ GLboolean span_front; /* swrast SetBuffer chose front */
+
+ /* hardware texture stage (vrtex.c) */
+ struct vr_texture *textures; /* all driver texture data */
+ GLuint lru_tick;
+ GLboolean tex_on; /* this render run is textured */
+ GLfloat tex_w, tex_h; /* texel scales for u/v emit */
+ GLfloat tex_off; /* 0.5 iff bilinear (GL footprint) */
+ GLfloat tex_base_w, tex_base_h; /* level-0 dims (LOD) */
+
+ /* per-polygon mipmapping (vrtris picks a level per triangle) */
+ struct vr_texture *tex_cur; /* validated binding */
+ GLboolean tex_mip; /* MIN filter is a mipmap one */
+ GLboolean tex_min_bilin; /* LINEAR_MIPMAP_* */
+ GLboolean tex_mag_bilin; /* MagFilter == LINEAR */
+ GLint cur_level; /* bound level (bind cache) */
+ GLint cur_bilin; /* SrcFilter cache (-1 unset) */
+
+ /* hardware blend: source alpha rides PE FGColor bits 31:24 */
+ GLboolean blend_alpha; /* hw blend factors read src alpha */
+ GLboolean atest_alpha; /* alpha test needs composed ColorAlpha */
+ GLuint fg_alpha; /* cached FGColor alpha (~0 = unset) */
+
+ /* swsetup's Render.Start, wrapped by vr_render_start */
+ void (*ss_render_start)(GLcontext *);
+ /* swsetup's current tri/quad/line/point funcs (restored on fallback) */
+ void (*ss_triangle)(GLcontext *, GLuint, GLuint, GLuint);
+ void (*ss_quad)(GLcontext *, GLuint, GLuint, GLuint,
+ GLuint);
+ void (*ss_line)(GLcontext *, GLuint, GLuint);
+ void (*ss_points)(GLcontext *, GLuint, GLuint);
+
+ void *ss_prim_verts, *ss_prim_elts;
+ GLboolean use_strips; /* VRMESA_STRIPS: opt-in native tri-fans */
+ GLboolean strips_ok; /* this run's state permits them */
+
+ void (*ss_build_vertices)(GLcontext *, GLuint, GLuint,
+ GLuint);
+ GLboolean use_fastbuild; /* VRMESA_NOFASTBUILD unset (default on) */
+ GLboolean fastbuild_ok; /* this run's inputs are fast-pathable */
+ GLboolean fb_tex, fb_fog; /* fast build emits texcoord0 / fog */
+} vrmesa_context;
+
+#define VRMESA_CONTEXT(ctx) ((vrmesa_context *)(ctx))
+
+/* vrdd.c */
+extern void vrInitDDFuncs(struct dd_function_table *functions);
+/* VRAM base of a GL_FRONT/GL_BACK color buffer (rl.fb[back] = draw) */
+extern uint32_t vr_fb_base(struct rlgl *rl, GLenum buffer);
+
+/* vrspan.c */
+extern void vrInitSpanFuncs(GLcontext *ctx);
+extern void vrFence(vrmesa_context *vmesa);
+
+/* vrtex.c */
+struct vr_texture;
+struct rlgl_tex;
+extern void vrInitTexConsts(GLcontext *ctx);
+extern void vrInitTexFuncs(struct dd_function_table *functions);
+extern GLboolean vrTexHwOk(GLcontext *ctx);
+extern GLboolean vrTexValidate(GLcontext *ctx);
+extern struct rlgl_tex *vrTexLevel(struct vr_texture *t, GLint level);
+extern GLint vrTexNumLevels(struct vr_texture *t);
+extern void vrTexNewFrame(void);
+
+/* vrtris.c */
+extern void vrInitTriFuncs(GLcontext *ctx);
+extern void vrCheckHwRender(GLcontext *ctx);
+extern GLboolean vrBlendMap(const GLcontext *ctx, GLuint *srcf,
+ GLuint *dstf, GLboolean *needs_alpha);
+
+#endif /* VRDRV_H */
diff --git a/src/mesa/drivers/verite/vrperf.h b/src/mesa/drivers/verite/vrperf.h
new file mode 100644
index 0000000..27c5487
--- /dev/null
+++ b/src/mesa/drivers/verite/vrperf.h
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+/*
+ * vrperf - veritemesa driver-layer performance instrumentation.
+ */
+#ifndef VRPERF_H
+#define VRPERF_H
+
+enum {
+ VRP_FB_TEX_HWOK, /* texture enabled but not PE-expressible */
+ VRP_FB_BLEND, /* blend func not in the PE map */
+ VRP_FB_ALPHAFUNC, /* alpha func not GREATER/GEQUAL/NEVER/ALWAYS */
+ VRP_FB_DEPTHFUNC, /* depth func not LESS/LEQUAL/ALWAYS */
+ VRP_FB_STIPPLE, /* DD_TRI_STIPPLE */
+ VRP_FB_SMOOTH, /* DD_TRI_SMOOTH */
+ VRP_FB_TWOSIDE, /* DD_TRI_LIGHT_TWOSIDE */
+ VRP_FB_SPECULAR, /* DD_SEPARATE_SPECULAR */
+ VRP_FB_UNFILLED_SUB, /* unfilled poly + a sub-mode HW can't do */
+ VRP_FB_RENDERMODE, /* RenderMode != GL_RENDER */
+ VRP_FB_TEX_RESIDENCY, /* gate passed but vrTexValidate could not fit */
+ VRP_FB_TRI_ALPHAGRAD, /* per-tri: real alpha gradient -> _swrast_Triangle */
+ VRP_FB__N
+};
+
+enum {
+ VRP_Z_PIPELINE, /* whole _tnl_run_pipeline (all CPU/frame) */
+ VRP_Z_RENDER_START, /* vr_render_start: our validate + texvalidate */
+ VRP_Z_TEXVALIDATE, /* vrTexValidate: texture convert/upload CPU */
+ VRP_Z_TRI_EMIT, /* per-tri vr_tri (level >= 3 only) */
+ VRP_Z__N
+};
+
+#ifdef VERITE_INSTRUMENT
+
+#include <stdint.h>
+
+struct vrperf {
+ int on; /* VRMESA_PROF >= 1 (gates hot sites) */
+ int level; /* full VRMESA_PROF value */
+ int printed; /* atexit/destroy double-print guard */
+ struct rlgl *rl; /* borrowed: surface librlgl/libv3d stats */
+
+ uint64_t fb[VRP_FB__N]; /* swrast-fallback histogram */
+ uint64_t frames; /* our own swap count */
+
+ /* state-churn / stall counters */
+ uint64_t fgcolor_writes; /* per-tri FGColor writes (vrtris.c) */
+ uint64_t mip_binds; /* per-poly level binds */
+ uint64_t filt_changes; /* per-poly filter switches */
+ uint64_t fences; /* vrFence real PE stalls (vrspan.c) */
+
+ /* texture-validate classification (the 1->19 fps diagnostic set) */
+ uint64_t tv_dirty; /* data changed -> re-upload */
+ uint64_t tv_nlevels; /* resident but fewer levels than wanted */
+ uint64_t tv_evict; /* an LRU victim was unloaded */
+ uint64_t tv_first; /* never-resident first upload */
+ uint64_t tv_bindonly; /* resident, no upload (fast path) */
+ uint64_t tv_creates; /* new vr_texture objects made */
+ uint64_t tv_failv; /* validate failed: could not fit base */
+ uint64_t tv_failu; /* validate failed: an upload errored */
+
+ uint64_t zone[VRP_Z__N];
+ uint64_t zone_t0[VRP_Z__N];
+ uint64_t tb0, ns0; /* one-time ns calibration anchors */
+};
+
+extern struct vrperf g_vrp;
+
+#if defined(__powerpc__)
+static __inline uint32_t
+vrp_tb(void)
+{
+ uint32_t t;
+
+ __asm__ volatile("mftb %0" : "=r"(t));
+ return t;
+}
+
+static __inline uint64_t
+vrp_tb64(void)
+{
+ uint32_t hi, lo, hi2;
+
+ do {
+ __asm__ volatile("mftbu %0" : "=r"(hi));
+ __asm__ volatile("mftb %0" : "=r"(lo));
+ __asm__ volatile("mftbu %0" : "=r"(hi2));
+ } while (hi != hi2);
+ return ((uint64_t)hi << 32) | lo;
+}
+#else
+static __inline uint32_t vrp_tb(void) { return (uint32_t)v3d_now_ns(); }
+static __inline uint64_t vrp_tb64(void) { return v3d_now_ns(); }
+#endif
+
+/*
+ * Counter tiers.
+ */
+#define VRP_COUNT(f) (g_vrp.f++)
+#define VRP_ADD(f, n) (g_vrp.f += (uint64_t)(n))
+#define VRP_FALLBACK(r) (g_vrp.fb[r]++)
+/* gated fallback bump for per-primitive sites (vr_tri alpha gradient) */
+#define VRP_FALLBACK_HOT(r) do { if (g_vrp.on) g_vrp.fb[r]++; } while (0)
+#define VRP_HOT(f) do { if (g_vrp.on) g_vrp.f++; } while (0)
+#define VRP_ZE(z) do { if (g_vrp.on) g_vrp.zone_t0[z] = vrp_tb(); } while (0)
+#define VRP_ZX(z) do { if (g_vrp.on) \
+ g_vrp.zone[z] += (uint32_t)(vrp_tb() - g_vrp.zone_t0[z]); } while (0)
+#define VRP_ZE3(z) do { if (g_vrp.level >= 3) g_vrp.zone_t0[z] = vrp_tb(); } while (0)
+#define VRP_ZX3(z) do { if (g_vrp.level >= 3) \
+ g_vrp.zone[z] += (uint32_t)(vrp_tb() - g_vrp.zone_t0[z]); } while (0)
+
+struct vrmesa_context;
+extern void vrPerfInit(struct vrmesa_context *);
+extern void vrPerfSwap(struct vrmesa_context *);
+extern void vrPerfDump(void);
+
+#else /* !VERITE_INSTRUMENT */
+
+#define VRP_COUNT(f) ((void)0)
+#define VRP_ADD(f, n) ((void)0)
+#define VRP_FALLBACK(r) ((void)0)
+#define VRP_FALLBACK_HOT(r) ((void)0)
+#define VRP_HOT(f) ((void)0)
+#define VRP_ZE(z) ((void)0)
+#define VRP_ZX(z) ((void)0)
+#define VRP_ZE3(z) ((void)0)
+#define VRP_ZX3(z) ((void)0)
+#define vrPerfInit(vm) ((void)0)
+#define vrPerfSwap(vm) ((void)0)
+#define vrPerfDump() ((void)0)
+
+#endif /* VERITE_INSTRUMENT */
+
+#endif /* VRPERF_H */
diff --git a/src/mesa/drivers/verite/vrspan.c b/src/mesa/drivers/verite/vrspan.c
new file mode 100644
index 0000000..82a9f73
--- /dev/null
+++ b/src/mesa/drivers/verite/vrspan.c
@@ -0,0 +1,202 @@
+/*
+ * 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 swrast spans over the /dev/verite3d VRAM mmap window.
+ */
+
+#include "vrdrv.h"
+
+#include "imports.h"
+#include "swrast/swrast.h"
+
+void
+vrFence(vrmesa_context *vmesa)
+{
+
+ if (vmesa->engine_dirty) {
+ rlgl_finish(&vmesa->rl);
+ vmesa->engine_dirty = GL_FALSE;
+ VRP_COUNT(fences);
+ }
+}
+
+/*
+ * Base of the buffer swrast is currently addressing.
+ */
+static GLubyte *
+vr_span_base(vrmesa_context *vmesa)
+{
+ struct rlgl *rl = &vmesa->rl;
+ uint32_t fb;
+
+ vrFence(vmesa);
+ fb = vmesa->span_front ? rl->fb[rl->back ^ 1] : rl->fb[rl->back];
+ return (GLubyte *)(uintptr_t)v3d_vram(&rl->v) + fb;
+}
+
+static GLushort *
+vr_zrow(vrmesa_context *vmesa, GLint y)
+{
+ struct rlgl *rl = &vmesa->rl;
+
+ vrFence(vmesa);
+ return (GLushort *)((GLubyte *)(uintptr_t)v3d_vram(&rl->v) +
+ rl->zbuf + (size_t)(rl->v.height - 1 - y) * rl->v.stride);
+}
+
+static void
+vr_set_buffer(GLcontext *ctx, GLframebuffer *buffer, GLuint bufferBit)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+
+ (void)buffer;
+ switch (bufferBit) {
+ case DD_FRONT_LEFT_BIT:
+ vmesa->span_front = GL_TRUE;
+ break;
+ case DD_BACK_LEFT_BIT:
+ vmesa->span_front = GL_FALSE;
+ break;
+ default:
+ _mesa_problem(ctx, "bad bufferBit in vr_set_buffer");
+ }
+}
+
+#define VR_SWAP16(x) ((GLushort)((((x) & 0xff) << 8) | ((x) >> 8)))
+
+#define NAME(PREFIX) PREFIX##_565
+#define SPAN_VARS \
+ vrmesa_context *vmesa = VRMESA_CONTEXT((GLcontext *)ctx); \
+ GLubyte *vrbuf = vr_span_base(vmesa); \
+ const GLuint vrstride = vmesa->rl.v.stride; \
+ const GLint vrheight = vmesa->rl.v.height;
+#define INIT_PIXEL_PTR(P, X, Y) \
+ GLushort *P = (GLushort *)(vrbuf + \
+ (size_t)(vrheight - 1 - (Y)) * vrstride + (X) * 2)
+#define INC_PIXEL_PTR(P) P += 1
+#define STORE_RGB_PIXEL(P, X, Y, R, G, B) \
+ *P = VR_SWAP16((((R) & 0xf8) << 8) | (((G) & 0xfc) << 3) | \
+ ((B) >> 3))
+#define STORE_RGBA_PIXEL(P, X, Y, R, G, B, A) \
+ *P = VR_SWAP16((((R) & 0xf8) << 8) | (((G) & 0xfc) << 3) | \
+ ((B) >> 3))
+#define FETCH_RGBA_PIXEL(R, G, B, A, P) \
+ do { \
+ GLushort px_ = VR_SWAP16(*P); \
+ R = (((px_ >> 8) & 0xf8) | ((px_ >> 11) & 0x7)); \
+ G = (((px_ >> 3) & 0xfc) | ((px_ >> 5) & 0x3)); \
+ B = (((px_ << 3) & 0xf8) | ((px_ ) & 0x7)); \
+ A = CHAN_MAX; \
+ } while (0)
+
+#include "swrast/s_spantemp.h"
+
+static void
+vr_write_depth_span(GLcontext *ctx, GLuint n, GLint x, GLint y,
+ const GLdepth depth[], const GLubyte mask[])
+{
+ GLushort *zrow = vr_zrow(VRMESA_CONTEXT(ctx), y) + x;
+ GLuint i;
+
+ for (i = 0; i < n; i++)
+ if (mask == NULL || mask[i])
+ zrow[i] = VR_SWAP16((GLushort)depth[i]);
+}
+
+static void
+vr_write_mono_depth_span(GLcontext *ctx, GLuint n, GLint x, GLint y,
+ const GLdepth depth, const GLubyte mask[])
+{
+ GLushort *zrow = vr_zrow(VRMESA_CONTEXT(ctx), y) + x;
+ GLuint i;
+
+ for (i = 0; i < n; i++)
+ if (mask == NULL || mask[i])
+ zrow[i] = VR_SWAP16((GLushort)depth);
+}
+
+static void
+vr_read_depth_span(GLcontext *ctx, GLuint n, GLint x, GLint y,
+ GLdepth depth[])
+{
+ const GLushort *zrow = vr_zrow(VRMESA_CONTEXT(ctx), y) + x;
+ GLuint i;
+
+ for (i = 0; i < n; i++)
+ depth[i] = VR_SWAP16(zrow[i]);
+}
+
+static void
+vr_write_depth_pixels(GLcontext *ctx, GLuint n,
+ const GLint x[], const GLint y[], const GLdepth depth[],
+ const GLubyte mask[])
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ GLuint i;
+
+ for (i = 0; i < n; i++)
+ if (mask == NULL || mask[i])
+ vr_zrow(vmesa, y[i])[x[i]] =
+ VR_SWAP16((GLushort)depth[i]);
+}
+
+static void
+vr_read_depth_pixels(GLcontext *ctx, GLuint n,
+ const GLint x[], const GLint y[], GLdepth depth[])
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ GLuint i;
+
+ for (i = 0; i < n; i++)
+ depth[i] = VR_SWAP16(vr_zrow(vmesa, y[i])[x[i]]);
+}
+
+void
+vrInitSpanFuncs(GLcontext *ctx)
+{
+ struct swrast_device_driver *swdd =
+ _swrast_GetDeviceDriverReference(ctx);
+
+ swdd->SetBuffer = vr_set_buffer;
+
+ swdd->WriteRGBASpan = write_rgba_span_565;
+ swdd->WriteRGBSpan = write_rgb_span_565;
+ swdd->WriteMonoRGBASpan = write_monorgba_span_565;
+ swdd->WriteRGBAPixels = write_rgba_pixels_565;
+ swdd->WriteMonoRGBAPixels = write_monorgba_pixels_565;
+ swdd->ReadRGBASpan = read_rgba_span_565;
+ swdd->ReadRGBAPixels = read_rgba_pixels_565;
+
+ swdd->WriteDepthSpan = vr_write_depth_span;
+ swdd->WriteMonoDepthSpan = vr_write_mono_depth_span;
+ swdd->ReadDepthSpan = vr_read_depth_span;
+ swdd->WriteDepthPixels = vr_write_depth_pixels;
+ swdd->ReadDepthPixels = vr_read_depth_pixels;
+}
diff --git a/src/mesa/drivers/verite/vrtex.c b/src/mesa/drivers/verite/vrtex.c
new file mode 100644
index 0000000..4f71dd3
--- /dev/null
+++ b/src/mesa/drivers/verite/vrtex.c
@@ -0,0 +1,501 @@
+/*
+ * 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 texture stage
+ */
+
+#include "vrdrv.h"
+
+#include "context.h"
+#include "imports.h"
+#include "texstore.h"
+#include "texobj.h"
+#include "swrast/swrast.h"
+
+#define VR_MAX_LEVELS 9 /* 256x256 .. 1x1 */
+
+struct vr_texture {
+ /* hw[0].addr == 0 -> not resident; mip chain in hw[1..] */
+ struct rlgl_tex hw[VR_MAX_LEVELS];
+ GLint nlevels; /* resident levels (1 = no mips) */
+ GLboolean has_alpha; /* uploaded as 4444, else 565 */
+ GLboolean dirty; /* core image newer than VRAM */
+ GLboolean capped; /* nlevels is all that fit VRAM;
+ * do not retry for more (anti-thrash) */
+ GLuint lru; /* last validate tick */
+ GLuint used_frame; /* frame last bound (evict pinning) */
+ struct gl_texture_object *obj;
+ struct vr_texture *next;
+};
+
+/*
+ * Frame counter for evict-pinning
+ */
+static GLuint g_vr_frame = 1;
+
+void
+vrTexNewFrame(void)
+{
+
+ g_vr_frame++;
+}
+
+/* vrtris.c reads the level array for per-triangle mip binds */
+struct rlgl_tex *
+vrTexLevel(struct vr_texture *t, GLint level)
+{
+
+ return &t->hw[level];
+}
+
+GLint
+vrTexNumLevels(struct vr_texture *t)
+{
+
+ return t->nlevels;
+}
+
+void
+vrInitTexConsts(GLcontext *ctx)
+{
+
+ ctx->Const.MaxTextureUnits = 1;
+ ctx->Const.MaxTextureImageUnits = 1;
+ ctx->Const.MaxTextureCoordUnits = 1;
+ /* 2^(9-1) = 256: mesaconf tex-256 scene proves the PE takes it */
+ ctx->Const.MaxTextureLevels = 9;
+}
+
+static struct vr_texture *
+vr_tex_data(GLcontext *ctx, struct gl_texture_object *tObj)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ struct vr_texture *t = tObj->DriverData;
+
+ if (t == NULL) {
+ t = CALLOC_STRUCT(vr_texture);
+ if (t == NULL)
+ return NULL;
+ t->obj = tObj;
+ t->dirty = GL_TRUE;
+ t->next = vmesa->textures;
+ vmesa->textures = t;
+ tObj->DriverData = t;
+ VRP_COUNT(tv_creates);
+ }
+ return t;
+}
+
+static void
+vr_tex_unload(vrmesa_context *vmesa, struct vr_texture *t)
+{
+ GLint i;
+
+ /*
+ * The mip pyramid is ONE contiguous allocation
+ */
+ if (t->hw[0].addr != 0)
+ rlgl_tex_free(&vmesa->rl, &t->hw[0]);
+ for (i = 1; i < VR_MAX_LEVELS; i++)
+ t->hw[i].addr = 0;
+ t->nlevels = 0;
+ /* addresses may be recycled: kill the bind cache */
+ vmesa->rl.cur_tex = 0;
+}
+
+static void
+vr_teximage2d(GLcontext *ctx, GLenum target, GLint level,
+ GLint internalFormat, GLint width, GLint height, GLint border,
+ GLenum format, GLenum type, const GLvoid *pixels,
+ const struct gl_pixelstore_attrib *packing,
+ struct gl_texture_object *texObj, struct gl_texture_image *texImage)
+{
+ struct vr_texture *t;
+
+ _mesa_store_teximage2d(ctx, target, level, internalFormat,
+ width, height, border, format, type, pixels, packing,
+ texObj, texImage);
+ t = vr_tex_data(ctx, texObj);
+ if (t != NULL)
+ t->dirty = GL_TRUE;
+}
+
+static void
+vr_texsubimage2d(GLcontext *ctx, GLenum target, GLint level,
+ GLint xoffset, GLint yoffset, GLsizei width, GLsizei height,
+ GLenum format, GLenum type, const GLvoid *pixels,
+ const struct gl_pixelstore_attrib *packing,
+ struct gl_texture_object *texObj, struct gl_texture_image *texImage)
+{
+ struct vr_texture *t;
+
+ _mesa_store_texsubimage2d(ctx, target, level, xoffset, yoffset,
+ width, height, format, type, pixels, packing, texObj,
+ texImage);
+ t = vr_tex_data(ctx, texObj);
+ if (t != NULL)
+ t->dirty = GL_TRUE; /* v1: full re-upload */
+}
+
+static void
+vr_delete_texture(GLcontext *ctx, struct gl_texture_object *tObj)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ struct vr_texture *t = tObj->DriverData;
+
+ if (t != NULL) {
+ struct vr_texture **pp;
+
+ vr_tex_unload(vmesa, t);
+ for (pp = &vmesa->textures; *pp != NULL;
+ pp = &(*pp)->next)
+ if (*pp == t) {
+ *pp = t->next;
+ break;
+ }
+ _mesa_free(t);
+ tObj->DriverData = NULL;
+ }
+ _mesa_delete_texture_object(ctx, tObj);
+}
+
+/*
+ * Hardware glCopyTexSubImage2D.
+ */
+static void
+vr_copytexsubimage2d(GLcontext *ctx, GLenum target, GLint level,
+ GLint xoffset, GLint yoffset, GLint x, GLint y, GLsizei width,
+ GLsizei height)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ struct rlgl *rl = &vmesa->rl;
+ struct gl_texture_object *tObj = ctx->Texture.Unit[0].Current2D;
+ GLint fbW = (GLint)rl->v.width, fbH = (GLint)rl->v.height;
+ struct vr_texture *t = (tObj != NULL) ? tObj->DriverData : NULL;
+ GLboolean was_dirty = (t != NULL) ? t->dirty : GL_TRUE;
+
+ /*
+ * Keep the canonical core image correct in every case.
+ */
+ _swrast_copy_texsubimage2d(ctx, target, level, xoffset, yoffset,
+ x, y, width, height);
+
+ if (t == NULL)
+ return;
+
+ if (target != GL_TEXTURE_2D || level != tObj->BaseLevel ||
+ t->hw[0].addr == 0 || t->has_alpha || t->nlevels != 1 ||
+ was_dirty || ctx->_ImageTransferState != 0 ||
+ width <= 0 || height <= 0 ||
+ x < 0 || y < 0 || x + width > fbW || y + height > fbH) {
+ t->dirty = GL_TRUE; /* re-upload from the updated core */
+ return;
+ }
+
+ if (rlgl_copy_to_tex(rl, vr_fb_base(rl, ctx->Pixel.ReadBuffer), fbH,
+ t->hw[0].addr, t->hw[0].w, x, y, xoffset, yoffset,
+ width, height) != 0) {
+ t->dirty = GL_TRUE;
+ return;
+ }
+ t->dirty = GL_FALSE; /* VRAM matches the core copy */
+ vmesa->engine_dirty = GL_TRUE;
+}
+
+void
+vrInitTexFuncs(struct dd_function_table *functions)
+{
+
+ functions->TexImage2D = vr_teximage2d;
+ functions->TexSubImage2D = vr_texsubimage2d;
+ functions->CopyTexSubImage2D = vr_copytexsubimage2d;
+ functions->DeleteTexture = vr_delete_texture;
+}
+
+/*
+ * Can the PE render the CURRENT texture binding?
+ */
+GLboolean
+vrTexHwOk(GLcontext *ctx)
+{
+ const struct gl_texture_unit *unit = &ctx->Texture.Unit[0];
+ const struct gl_texture_object *tObj;
+ const struct gl_texture_image *img;
+
+ if (ctx->Texture._EnabledUnits != 0x1)
+ return GL_FALSE;
+ if (unit->_ReallyEnabled != TEXTURE_2D_BIT)
+ return GL_FALSE;
+ tObj = unit->Current2D;
+ if (tObj == NULL)
+ return GL_FALSE;
+ img = tObj->Image[0][tObj->BaseLevel];
+ if (img == NULL || img->Border != 0 ||
+ img->Width > 256 || img->Height > 256)
+ return GL_FALSE;
+ if (tObj->WrapS != GL_REPEAT && tObj->WrapS != GL_CLAMP_TO_EDGE &&
+ tObj->WrapS != GL_CLAMP)
+ return GL_FALSE;
+ if (tObj->WrapT != GL_REPEAT && tObj->WrapT != GL_CLAMP_TO_EDGE &&
+ tObj->WrapT != GL_CLAMP)
+ return GL_FALSE;
+ /*
+ * GL_CLAMP equals CLAMP_TO_EDGE under NEAREST sampling, but
+ * a LINEAR footprint mixes the border color at the edge
+ */
+ if (tObj->WrapS == GL_CLAMP || tObj->WrapT == GL_CLAMP) {
+ if (tObj->MagFilter == GL_LINEAR ||
+ tObj->MinFilter == GL_LINEAR ||
+ tObj->MinFilter == GL_LINEAR_MIPMAP_NEAREST ||
+ tObj->MinFilter == GL_LINEAR_MIPMAP_LINEAR)
+ return GL_FALSE;
+ }
+ /* all six MIN filters: mipmap ones ride per-poly selection */
+ if (tObj->MagFilter != GL_NEAREST && tObj->MagFilter != GL_LINEAR)
+ return GL_FALSE;
+ if (unit->EnvMode != GL_MODULATE && unit->EnvMode != GL_REPLACE &&
+ unit->EnvMode != GL_DECAL)
+ return GL_FALSE;
+ return GL_TRUE;
+}
+
+static GLuint
+vr_alloc_width(GLuint w)
+{
+
+ if (w < 2)
+ return 2;
+ if (w == 4)
+ return 8;
+ return w;
+}
+
+static int
+vr_tex_upload(vrmesa_context *vmesa, struct vr_texture *t,
+ const struct gl_texture_image *img, GLint level)
+{
+ GLuint w = img->Width, h = img->Height;
+ GLuint aw = vr_alloc_width(w);
+ GLushort *buf;
+ GLuint x, y;
+ int error;
+
+ if (img->FetchTexelc == NULL)
+ return -1;
+ buf = _mesa_malloc(aw * h * sizeof(GLushort));
+ if (buf == NULL)
+ return -1;
+ for (y = 0; y < h; y++)
+ for (x = 0; x < aw; x++) {
+ GLchan texel[4];
+
+ img->FetchTexelc((struct gl_texture_image *)img,
+ x < w ? x : w - 1, y, 0, texel);
+ buf[y * aw + x] = t->has_alpha ? (GLushort)
+ (((texel[3] & 0xf0) << 8) |
+ ((texel[0] & 0xf0) << 4) |
+ (texel[1] & 0xf0) | (texel[2] >> 4)) :
+ (GLushort)
+ (((texel[0] & 0xf8) << 8) |
+ ((texel[1] & 0xfc) << 3) | (texel[2] >> 3));
+ }
+ error = rlgl_tex_upload(&vmesa->rl, &t->hw[level], buf);
+ _mesa_free(buf);
+ return error;
+}
+
+static GLboolean
+vr_min_is_mip(GLenum f)
+{
+
+ return f != GL_NEAREST && f != GL_LINEAR;
+}
+
+static GLboolean
+vr_base_has_alpha(const struct gl_texture_image *img)
+{
+
+ switch (img->TexFormat->BaseFormat) {
+ case GL_RGBA:
+ case GL_ALPHA:
+ case GL_LUMINANCE_ALPHA:
+ case GL_INTENSITY:
+ return GL_TRUE;
+ default:
+ return GL_FALSE;
+ }
+}
+
+/*
+ * Make the current binding resident and bound
+ */
+GLboolean
+vrTexValidate(GLcontext *ctx)
+{
+ vrmesa_context *vmesa = VRMESA_CONTEXT(ctx);
+ struct gl_texture_object *tObj = ctx->Texture.Unit[0].Current2D;
+ const struct gl_texture_image *img =
+ tObj->Image[0][tObj->BaseLevel];
+ struct vr_texture *t = vr_tex_data(ctx, tObj);
+
+ GLint want, i;
+ GLboolean was_resident;
+
+ VRP_ZE(VRP_Z_TEXVALIDATE);
+ if (t == NULL) {
+ VRP_COUNT(tv_failv);
+ VRP_ZX(VRP_Z_TEXVALIDATE);
+ return GL_FALSE;
+ }
+ t->lru = ++vmesa->lru_tick;
+ t->used_frame = g_vr_frame; /* pin: not evictable this frame */
+ was_resident = (t->hw[0].addr != 0);
+
+ /* levels wanted: full chain for mipmap MIN filters */
+ if (vr_min_is_mip(tObj->MinFilter)) {
+ want = 0;
+ while (want < VR_MAX_LEVELS - 1 &&
+ tObj->Image[0][tObj->BaseLevel + want] != NULL &&
+ (tObj->Image[0][tObj->BaseLevel + want]->Width > 1 ||
+ tObj->Image[0][tObj->BaseLevel + want]->Height > 1))
+ want++;
+ want++; /* include the 1x1 (or last) level */
+ } else
+ want = 1;
+
+ if (t->hw[0].addr != 0 &&
+ (t->dirty || (t->nlevels < want && !t->capped))) {
+ if (t->dirty)
+ VRP_COUNT(tv_dirty);
+ else
+ VRP_COUNT(tv_nlevels);
+ vr_tex_unload(vmesa, t);
+ }
+
+ if (t->hw[0].addr == 0) {
+ int nl;
+
+ if (!was_resident)
+ VRP_COUNT(tv_first);
+ t->has_alpha = vr_base_has_alpha(img);
+
+ /*
+ * Allocate the whole pyramid as ONE contiguous block
+ */
+ for (nl = want; nl >= 1; nl--) {
+ for (i = 0; i < nl; i++) {
+ const struct gl_texture_image *li =
+ tObj->Image[0][tObj->BaseLevel + i];
+
+ if (li == NULL ||
+ rlgl_tex_init(&t->hw[i],
+ vr_alloc_width(li->Width), li->Height,
+ t->has_alpha ? RLGL_FMT_4444 :
+ RLGL_FMT_565) != 0)
+ break;
+ }
+ if (i < nl) /* a level was missing/bad */
+ continue;
+
+ while (rlgl_tex_pyramid(&vmesa->rl, t->hw, nl) != 0) {
+ struct vr_texture *victim = NULL, *it;
+
+ for (it = vmesa->textures; it != NULL;
+ it = it->next)
+ if (it != t &&
+ it->hw[0].addr != 0 &&
+ it->used_frame != g_vr_frame &&
+ (victim == NULL ||
+ it->lru < victim->lru))
+ victim = it;
+ if (victim == NULL)
+ break; /* try fewer levels */
+ vr_tex_unload(vmesa, victim);
+ VRP_COUNT(tv_evict);
+ }
+ if (t->hw[0].addr != 0)
+ break; /* nl levels allocated */
+ }
+ if (t->hw[0].addr == 0) {
+ VRP_COUNT(tv_failv);
+ VRP_ZX(VRP_Z_TEXVALIDATE);
+ return GL_FALSE; /* not even the base fit */
+ }
+
+ for (i = 0; i < nl; i++) {
+ const struct gl_texture_image *li =
+ tObj->Image[0][tObj->BaseLevel + i];
+
+ if (vr_tex_upload(vmesa, t, li, i) != 0) {
+ vr_tex_unload(vmesa, t);
+ VRP_COUNT(tv_failu);
+ VRP_ZX(VRP_Z_TEXVALIDATE);
+ return GL_FALSE;
+ }
+ }
+ t->nlevels = nl;
+ t->capped = (nl < want);
+ t->dirty = GL_FALSE;
+ vmesa->engine_dirty = GL_TRUE;
+ } else {
+ VRP_COUNT(tv_bindonly); /* resident, no upload */
+ }
+
+ rlgl_tex_env_code(&vmesa->rl,
+ ctx->Texture.Unit[0].EnvMode == GL_MODULATE ?
+ RLGL_ENV_MODULATE :
+ (ctx->Texture.Unit[0].EnvMode == GL_DECAL ?
+ RLGL_ENV_DECAL : RLGL_ENV_REPLACE));
+ rlgl_tex_filter(&vmesa->rl, tObj->MagFilter == GL_LINEAR);
+ rlgl_tex_bind(&vmesa->rl, &t->hw[0]);
+ rlgl_tex_wrap(&vmesa->rl, tObj->WrapS != GL_REPEAT,
+ tObj->WrapT != GL_REPEAT);
+
+ if (_mesa_getenv("VRMESA_DEBUG"))
+ _mesa_printf("vrTexValidate: obj=%u nlvl=%d addr0=%x "
+ "min=%x\n", tObj->Name, t->nlevels, t->hw[0].addr,
+ tObj->MinFilter);
+
+ vmesa->tex_cur = t;
+ vmesa->tex_mip = vr_min_is_mip(tObj->MinFilter) && t->nlevels > 1;
+ vmesa->tex_min_bilin =
+ (tObj->MinFilter == GL_LINEAR_MIPMAP_NEAREST ||
+ tObj->MinFilter == GL_LINEAR_MIPMAP_LINEAR ||
+ tObj->MinFilter == GL_LINEAR);
+ vmesa->tex_mag_bilin = (tObj->MagFilter == GL_LINEAR);
+ vmesa->cur_level = 0;
+ vmesa->cur_bilin = vmesa->tex_mag_bilin;
+
+ vmesa->tex_w = vmesa->tex_base_w = (GLfloat)img->Width;
+ vmesa->tex_h = vmesa->tex_base_h = (GLfloat)img->Height;
+ vmesa->tex_off = vmesa->tex_mag_bilin ? 0.5F : 0.0F;
+ VRP_ZX(VRP_Z_TEXVALIDATE);
+ return GL_TRUE;
+}
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);
+}