/* * 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 #include #include #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); } }