/* mgatest.c - drawing-engine experiments on the GXT120P. Writes to the card. * Claude Opus 5.5, 2026-10-13. * usage: mgatest MODE out.raw */ #include #include #include #include #include "mga.h" typedef struct { int x, y; double z, r, g, b; } vtx; static vtx V(int x, int y, double z, double r, double g, double b); static int nodraw = 0; static int zorg = 640 * 2 * 800; /* Z buffer = 16-bit rows 800..1279 at pitch 640 */ /* XAA-style edge: x and Bresenham error at line y, for the edge (x0,y0)-(x1,y1) */ static void edge_at(int x0, int y0, int x1, int y1, int y, int *x, int *e) { int dx = x1 - x0, dy = y1 - y0, adx, k, step, sign, ex, xx; if (dx >= 0) { sign = 1; adx = dx; ex = 0; } else { sign = -1; adx = -dx; ex = 1 - dy; } step = (adx / dy) * sign; adx %= dy; xx = x0; for (k = y0; k < y; k++) { /* Step() from xaaFillPoly.c */ xx += step; if ((ex += adx) > 0) { xx += sign; ex -= dy; } } *x = xx; *e = ex; } static int fx(double v) { return (int) floor(v * 32768.0 + 0.5); } /* 9.15 / 16.15 */ /* one trapezoid between lines ya and yb (exclusive), edges L and R */ static void trap(unsigned int cmd, int ya, int yb, int lx0, int ly0, int lx1, int ly1, int rx0, int ry0, int rx1, int ry1, const double *p0, const double *dpdx, const double *dpdy, int px, int py) { int xl, el, xr, er, dxl = lx1 - lx0, dxr = rx1 - rx0, sdxl, sdxr, ar2, ar5; double v[4]; int i; if (yb <= ya) return; if (nodraw) { edge_at(lx0, ly0, lx1, ly1, ya, &xl, &el); edge_at(rx0, ry0, rx1, ry1, ya, &xr, &er); return; } edge_at(lx0, ly0, lx1, ly1, ya, &xl, &el); edge_at(rx0, ry0, rx1, ry1, ya, &xr, &er); sdxl = dxl < 0; ar2 = sdxl ? dxl : -dxl; sdxr = dxr < 0; ar5 = sdxr ? dxr : -dxr; /* interpolants at the trapezoid's start point (xl, ya) */ for (i = 0; i < 4; i++) v[i] = p0[i] + (xl - px) * dpdx[i] + (ya - py) * dpdy[i]; mga_fifo(26); mga_out(DWGCTL, cmd); mga_out(DR0, fx(v[0])); mga_out(DR2, fx(dpdx[0])); mga_out(DR3, fx(dpdy[0])); mga_out(DR4, fx(v[1])); mga_out(DR6, fx(dpdx[1])); mga_out(DR7, fx(dpdy[1])); mga_out(DR8, fx(v[2])); mga_out(DR10, fx(dpdx[2])); mga_out(DR11, fx(dpdy[2])); mga_out(DR12, fx(v[3])); mga_out(DR14, fx(dpdx[3])); mga_out(DR15, fx(dpdy[3])); mga_out(AR0, ly1 - ly0); mga_out(AR1, ar2 - el); mga_out(AR2, ar2); mga_out(AR4, ar5 - er); mga_out(AR5, ar5); mga_out(AR6, ry1 - ry0); mga_out(SGN, (sdxl << 1) | (sdxr << 5)); mga_out(FXBNDRY, ((xr & 0xffff) << 16) | (xl & 0xffff)); mga_out(YDSTLEN + GO, (ya << 16) | (yb - ya)); } /* Gouraud (and optionally Z) triangle; spans are [xl, xr) like X */ static void tri(vtx a, vtx b, vtx c, int zbuf) { vtx t; double p0[4], dpdx[4], dpdy[4], e1[4], e2[4], det; unsigned int cmd = OP_TRAP | SHFTZERO | BOP_COPY | (zbuf ? AT_ZI | Z_ZLT : AT_I); int i, longleft; if (a.y > b.y) { t = a; a = b; b = t; } if (b.y > c.y) { t = b; b = c; c = t; } if (a.y > b.y) { t = a; a = b; b = t; } if (a.y == c.y) return; det = (double)(b.x - a.x) * (c.y - a.y) - (double)(c.x - a.x) * (b.y - a.y); if (det == 0) return; e1[0] = b.z - a.z; e1[1] = b.r - a.r; e1[2] = b.g - a.g; e1[3] = b.b - a.b; e2[0] = c.z - a.z; e2[1] = c.r - a.r; e2[2] = c.g - a.g; e2[3] = c.b - a.b; p0[0] = a.z; p0[1] = a.r; p0[2] = a.g; p0[3] = a.b; for (i = 0; i < 4; i++) { dpdx[i] = (e1[i] * (c.y - a.y) - e2[i] * (b.y - a.y)) / det; dpdy[i] = (e2[i] * (b.x - a.x) - e1[i] * (c.x - a.x)) / det; } longleft = det > 0; /* long edge a-c is on the left when b is to its right */ if (longleft) { trap(cmd, a.y, b.y, a.x, a.y, c.x, c.y, a.x, a.y, b.x, b.y, p0, dpdx, dpdy, a.x, a.y); trap(cmd, b.y, c.y, a.x, a.y, c.x, c.y, b.x, b.y, c.x, c.y, p0, dpdx, dpdy, a.x, a.y); } else { trap(cmd, a.y, b.y, a.x, a.y, b.x, b.y, a.x, a.y, c.x, c.y, p0, dpdx, dpdy, a.x, a.y); trap(cmd, b.y, c.y, b.x, b.y, c.x, c.y, a.x, a.y, c.x, c.y, p0, dpdx, dpdy, a.x, a.y); } } #define TU 32 #define TV 16 #define NFR 72 static double PI = 3.14159265358979; static void hue(double h, double *r, double *g, double *b) { double k[3]; int i; for (i = 0; i < 3; i++) { double x = fmod(h * 6 + 4 - 2 * i + 6, 6.0); /* r: h*6+4, g: +2, b: +0 */ double v = fabs(x - 3) - 1; k[i] = v < 0 ? 0 : v > 1 ? 1 : v; } *r = k[0]; *g = k[1]; *b = k[2]; } /* draw one frame of the torus at time f (0..NFR); returns triangles drawn */ static int frame(int f) { static vtx sv[TU][TV]; static double nz[TU][TV]; double ay = 2 * PI * f / NFR, ax = 1.05 + 0.35 * sin(2 * PI * 2 * f / NFR); double cy = cos(ay), sy = sin(ay), cx = cos(ax), sx = sin(ax); double L[3], H[3], len; int u, v, n = 0; L[0] = -0.5; L[1] = 0.6; L[2] = -0.65; /* towards the light: upper left, in front */ len = sqrt(L[0]*L[0] + L[1]*L[1] + L[2]*L[2]); L[0] /= len; L[1] /= len; L[2] /= len; H[0] = L[0]; H[1] = L[1]; H[2] = L[2] - 1; /* half vector with the view direction (0,0,-1) */ len = sqrt(H[0]*H[0] + H[1]*H[1] + H[2]*H[2]); H[0] /= len; H[1] /= len; H[2] /= len; for (u = 0; u < TU; u++) for (v = 0; v < TV; v++) { double a = 2 * PI * u / TU, b = 2 * PI * v / TV; double p[3], q[3], m[3], t, dif, spec, r, g, bl, k, z; p[0] = (1 + 0.42 * cos(b)) * cos(a); p[1] = 0.42 * sin(b); p[2] = (1 + 0.42 * cos(b)) * sin(a); m[0] = cos(b) * cos(a); m[1] = sin(b); m[2] = cos(b) * sin(a); /* rotate about x (tilt), then y */ t = p[1] * cx - p[2] * sx; p[2] = p[1] * sx + p[2] * cx; p[1] = t; t = m[1] * cx - m[2] * sx; m[2] = m[1] * sx + m[2] * cx; m[1] = t; t = p[0] * cy + p[2] * sy; p[2] = -p[0] * sy + p[2] * cy; p[0] = t; t = m[0] * cy + m[2] * sy; m[2] = -m[0] * sy + m[2] * cy; m[0] = t; z = p[2] + 3.6; k = 520 / z; dif = m[0]*L[0] + m[1]*L[1] + m[2]*L[2]; if (dif < 0) dif = 0; spec = m[0]*H[0] + m[1]*H[1] + m[2]*H[2]; spec = spec < 0 ? 0 : pow(spec, 28); hue((double) u / TU, &r, &g, &bl); r = 255 * (r * (0.18 + 0.72 * dif) + 0.85 * spec); g = 255 * (g * (0.18 + 0.72 * dif) + 0.85 * spec); bl = 255 * (bl * (0.18 + 0.72 * dif) + 0.85 * spec); sv[u][v] = V((int) floor(320 + p[0] * k + 0.5), (int) floor(240 - p[1] * k + 0.5), (z - 2.0) * 20000, r > 255 ? 255 : r, g > 255 ? 255 : g, bl > 255 ? 255 : bl); nz[u][v] = m[2]; } for (u = 0; u < TU; u++) for (v = 0; v < TV; v++) { int u1 = (u + 1) % TU, v1 = (v + 1) % TV; vtx a = sv[u][v], b = sv[u1][v], c = sv[u1][v1], d = sv[u][v1]; /* cull quads whose four normals all face away */ if (nz[u][v] > 0.2 && nz[u1][v] > 0.2 && nz[u1][v1] > 0.2 && nz[u][v1] > 0.2) continue; tri(a, b, c, 1); tri(a, c, d, 1); n += 2; } return n; } static void background(void) { /* sky: Gouraud gradient, drawn without Z */ tri(V(0, 0, 0, 20, 24, 60), V(640, 0, 0, 20, 24, 60), V(0, 480, 0, 120, 70, 110), 0); tri(V(640, 0, 0, 20, 24, 60), V(640, 480, 0, 120, 70, 110), V(0, 480, 0, 120, 70, 110), 0); } static int cmpd(const void *a, const void *b) { double x = *(const double *) a, y = *(const double *) b; return x < y ? -1 : x > y; } static vtx V(int x, int y, double z, double r, double g, double b) { vtx v; v.x = x; v.y = y; v.z = z; v.r = r; v.g = g; v.b = b; return v; } static int W = 1280, BPP = 1; static void fill(int x, int y, int w, int h, unsigned int col); static void dump(const char *fn) { unsigned char pal[768], *buf = malloc(VRAM_SIZE); FILE *f; int i; mga_idle(); mga_getpal(pal); for (i = 0; i < VRAM_SIZE; i += 4) *(unsigned int *)(buf + i) = *(volatile unsigned int *)(mga_fb + i); f = fopen(fn, "wb"); fwrite(pal, 1, 768, f); fwrite(buf, 1, VRAM_SIZE, f); fclose(f); free(buf); } /* set up pixel size, pitch, origin and a clip covering all of VRAM */ static void setup(int pw, int pitch) { int lines; W = pitch; BPP = pw == PW8 ? 1 : pw == PW16 ? 2 : 4; lines = VRAM_SIZE / (pitch * BPP); mga_fifo(8); mga_out(MACCESS, pw); mga_out(PITCH, pitch); mga_out(YDSTORG, 0); mga_out(PLNWT, 0xffffffff); mga_out(CXLEFT, 0); mga_out(CXRIGHT, pitch - 1); mga_out(YTOP, 0); mga_out(YBOT, (lines - 1) * pitch); } static void fill(int x, int y, int w, int h, unsigned int col) { mga_fifo(5); mga_out(DWGCTL, OP_TRAP | AT_RPL | SOLID | ARZERO | SGNZERO | SHFTZERO | BOP_COPY); mga_out(FCOL, col); mga_out(FXBNDRY, ((x + w) << 16) | x); mga_out(YDSTLEN + GO, (y << 16) | h); } int main(int argc, char **argv) { double t; int i; if (argc < 3) { fprintf(stderr, "usage: mgatest fill out.raw\n"); return 2; } if (mga_open()) return 1; if (!strcmp(argv[1], "fill")) { setup(PW8, 1280); fill(100, 100, 200, 100, 0x05050505); fill(120, 300, 33, 7, 0x0c0c0c0c); if (mga_idle()) goto out; t = mga_now(); for (i = 0; i < 100; i++) fill(400, 400, 640, 480, 0x03030303 * (i & 1 ? 1 : 0) + 0x0e0e0e0e * !(i & 1)); mga_idle(); t = mga_now() - t; printf("100 fills of 640x480x8: %.3f s, %.1f Mpixel/s\n", t, 100 * 640.0 * 480 / t / 1e6); } if (!strcmp(argv[1], "gouraud")) { setup(PW16, 640); mga_fifo(1); mga_out(ZORG, zorg); fill(0, 0, 640, 480, 0x00000000); fill(0, 0, 8, 8, 0xf800f800); /* byte-order marker: pure red */ /* x-only red ramp, y-only green ramp, as rectangles made of two triangles */ tri(V(20, 20, 0, 0, 0, 0), V(276, 20, 0, 255, 0, 0), V(20, 100, 0, 0, 0, 0), 0); tri(V(276, 20, 0, 255, 0, 0), V(276, 100, 0, 255, 0, 0), V(20, 100, 0, 0, 0, 0), 0); tri(V(300, 20, 0, 0, 0, 0), V(400, 20, 0, 0, 0, 0), V(300, 276, 0, 0, 255, 0), 0); tri(V(400, 20, 0, 0, 0, 0), V(400, 276, 0, 0, 255, 0), V(300, 276, 0, 0, 255, 0), 0); /* an RGB triangle */ tri(V(100, 150, 0, 255, 0, 0), V(250, 420, 0, 0, 255, 0), V(30, 380, 0, 0, 0, 255), 0); if (mga_idle()) goto out; } if (!strcmp(argv[1], "ztest")) { setup(PW16, 640); mga_fifo(1); mga_out(ZORG, zorg); fill(0, 0, 640, 480, 0x00000000); fill(0, 800, 640, 480, 0xffffffff); /* Z = 0xffff everywhere */ /* red triangle tilted in depth one way, blue the other: they must cross */ tri(V(100, 100, 10000, 255, 60, 60), V(540, 100, 10000, 255, 60, 60), V(320, 400, 60000, 120, 0, 0), 1); tri(V(100, 380, 5000, 60, 60, 255), V(540, 380, 5000, 60, 60, 255), V(320, 60, 60000, 0, 0, 120), 1); if (mga_idle()) goto out; } if (!strcmp(argv[1], "demo")) { double tf[NFR], tc[NFR]; int f, ntri = 0; FILE *o; unsigned char *buf = malloc(640 * 480 * 2); setup(PW16, 640); mga_fifo(1); mga_out(ZORG, zorg); for (f = 0; f < NFR; f++) { /* timing: engine + CPU */ t = mga_now(); background(); fill(0, 800, 640, 480, 0xffffffff); ntri += frame(f); mga_idle(); tf[f] = mga_now() - t; } nodraw = 1; for (f = 0; f < NFR; f++) { /* timing: CPU only, no register writes */ t = mga_now(); background(); frame(f); tc[f] = mga_now() - t; } nodraw = 0; qsort(tf, NFR, sizeof(double), cmpd); qsort(tc, NFR, sizeof(double), cmpd); printf("%d frames, %d triangles/frame avg\n", NFR, ntri / NFR); printf("frame (draw+wait): min %.1f ms, median %.1f ms -> %.1f fps\n", tf[0] * 1e3, tf[NFR / 2] * 1e3, 1 / tf[NFR / 2]); printf("CPU setup only: min %.1f ms, median %.1f ms\n", tc[0] * 1e3, tc[NFR / 2] * 1e3); o = fopen(argv[2], "wb"); for (f = 0; f < NFR; f++) { /* capture */ background(); fill(0, 800, 640, 480, 0xffffffff); frame(f); mga_idle(); for (i = 0; i < 640 * 480 * 2; i += 4) *(unsigned int *)(buf + i) = *(volatile unsigned int *)(mga_fb + i); fwrite(buf, 1, 640 * 480 * 2, o); } fclose(o); goto out; } dump(argv[2]); out: mga_close(); return 0; }