/* Headless harness for px68k's GVRAM write and display model. * * Tests FINDINGS 46.6 -- the packed 1.0 byte/pixel layout -- on a SECOND * emulator, the way tools/bench/c68k does for the CPU core. It links px68k's * real x68k/gvram.c: the address decode, the CRTC R20 bit-11 buffer-mode write * path, the page-byte selection, the scroll wrap and the index-0 transparency * test are all px68k's own code, not a reimplementation. * * What IS glue here, and is declared as such: the ~12 lines of page-ordering * from x11/windraw.c's 256-colour case (which page is drawn opaque and which * transparent, as a function of the video controller's priority register). * windraw.c is SDL-bound and cannot be linked headless, so that dispatch is * mirrored. It is quoted verbatim in pick_order() so the mirroring is * auditable. * * GrphPal is set to the IDENTITY, so what lands in Grp_LineBuf is the 8-bit * palette INDEX rather than a host pixel. That keeps the harness out of * px68k's host-format colour conversion, and it is faithful: px68k's * transparency test is on the index (`if (v != 0x00)`), before the lookup. */ #include #include #include #include "common.h" #include "gvram.h" /* --- the globals gvram.c expects from the rest of px68k -------------------- */ BYTE CRTC_Regs[48]; WORD CRTC_FastClrMask; DWORD GrphScrollX[4], GrphScrollY[4]; WORD GrphPal[256]; BYTE TextDirtyLine[1024]; DWORD TextDotX, TextDotY; DWORD VLINE; BYTE Pal_Regs[1024]; WORD Pal16[65536]; WORD Ibit, Pal_HalfMask, Pal_Ix2; extern BYTE GVRAM[0x80000]; extern WORD Grp_LineBuf[1024]; #define W 256 #define H 256 /* 68000 word write: two byte writes, high byte first, as the bus does. */ static void wr16(DWORD adr, WORD v) { GVRAM_Write(adr, (BYTE)(v >> 8)); GVRAM_Write(adr + 1, (BYTE)(v & 0xff)); } static void set_r20(WORD r20) /* CRTC R20 = byte pair 0x28/0x29 */ { CRTC_Regs[0x28] = (BYTE)(r20 >> 8); CRTC_Regs[0x29] = (BYTE)(r20 & 0xff); } /* Mirrors x11/windraw.c, 256-colour case: * * if ( (VCReg1[1]&3) <= ((VCReg1[1]>>4)&3) ) { * ... Grp_DrawLine8(1, 1); opaq = 0; * ... Grp_DrawLine8(0, opaq); * } else { * ... Grp_DrawLine8(0, 1); opaq = 0; * ... Grp_DrawLine8(1, opaq); * } * * i.e. the first page drawn is OPAQUE (the bottom) and the second is drawn * with opaq=0 (the transparent top). */ static void draw_line(BYTE vcreg1_lo) { int bottom = ((vcreg1_lo & 3) <= ((vcreg1_lo >> 4) & 3)) ? 1 : 0; Grp_DrawLine8(bottom, 1); Grp_DrawLine8(bottom ^ 1, 0); } int main(int argc, char **argv) { const char *blob = argc > 1 ? argv[1] : "tmp/frame256p.bin"; const char *out = argc > 2 ? argv[2] : "tmp/gvpack_px68k.raw"; int packed = !(argc > 3 && !strcmp(argv[3], "--unpacked")); BYTE vc1 = (BYTE)(argc > 4 ? strtol(argv[4], NULL, 0) : 0x02); /* --nobuffer: run the packed layout WITHOUT CRTC R20 bit 11, to show the * bit is load-bearing here and not decoration. */ int buffer = !(argc > 5 && !strcmp(argv[5], "--nobuffer")); int scroll = !(argc > 5 && !strcmp(argv[5], "--noscroll")); /* --keepbuffer: leave R20 bit 11 SET while drawing. MAME blanks the * graphics layer in that state; does px68k? */ int keepbuf = (argc > 5 && !strcmp(argv[5], "--keepbuffer")); FILE *f = fopen(blob, "rb"); if (!f) { perror(blob); return 2; } static BYTE d[8 + 768 + 256 * 256]; size_t n = fread(d, 1, sizeof d, f); fclose(f); int iw = (d[4] << 8) | d[5], ih = (d[6] << 8) | d[7]; if (n < (size_t)(8 + 768 + iw * ih)) { fprintf(stderr, "short blob\n"); return 2; } const BYTE *pix = d + 8 + 768; /* 'DLXQ' -- the blob is PRE-INTERLEAVED: `pix` is already the bytes a DLXP1 * record carries, in GVRAM order. The ordinary 'DLXR' path computes the * interleave here, which tests the LAYOUT; this path tests the CONTAINER, * by writing its bytes verbatim and asking px68k's own gvram.c what they * display as. The two agreeing is the claim ROADMAP K2 has to make: the * encoder's byte order is the one 47.2 verified as a picture. */ int prepacked = (d[3] == 'Q'); if (prepacked && !packed) { fprintf(stderr, "a pre-interleaved blob has no unpacked form\n"); return 2; } int yoff = (H - ih) / 2; const BYTE BLACK = 255; #define PIX(y, x) ((y) < yoff || (y) >= yoff + ih ? BLACK : pix[((y) - yoff) * iw + (x)]) memset(GVRAM, 0, sizeof GVRAM); for (int i = 0; i < 256; i++) GrphPal[i] = (WORD)i; /* identity */ TextDotX = W; TextDotY = H; /* 256x256, 256 colours -- the same R20 tools/bench/crtc_mode.lua applies */ const WORD R20_DISPLAY = 0x0110; set_r20(R20_DISPLAY); /* page 0 -> scroll sets 0,1; page 1 -> scroll sets 2,3 */ GrphScrollX[0] = GrphScrollX[1] = 0; GrphScrollY[0] = GrphScrollY[1] = 0; GrphScrollX[2] = GrphScrollX[3] = (packed && scroll) ? 384 : 0; GrphScrollY[2] = GrphScrollY[3] = 0; if (packed) { if (buffer) set_r20(R20_DISPLAY | 0x0800); /* buffer mode: unmasked */ for (int y = 0; y < H; y++) { DWORD base = 0xC00000 + y * 1024; for (int i = 128; i < 512; i++) wr16(base + i * 2, 0); for (int i = 0; i < 128; i++) { WORD w; if (prepacked) { /* The letterbox rows are STATIC SETUP and are not in a * record (dlxp.py), so they are supplied here, the way a * player's scene setup supplies them: both halves BLACK. */ if (y < yoff || y >= yoff + ih) w = (WORD)((BLACK << 8) | BLACK); else { const BYTE *row = pix + (y - yoff) * iw; w = (WORD)((row[i * 2] << 8) | row[i * 2 + 1]); } } else { w = (WORD)((PIX(y, i + 128) << 8) | PIX(y, i)); } wr16(base + i * 2, w); } } if (!keepbuf) set_r20(R20_DISPLAY); /* back to display */ } else { /* the ordinary 2.0 B/pixel path, for a control */ for (int y = 0; y < H; y++) { DWORD base = 0xC00000 + y * 1024; for (int x = 0; x < W; x++) wr16(base + x * 2, PIX(y, x)); } } FILE *o = fopen(out, "wb"); if (!o) { perror(out); return 2; } for (int y = 0; y < H; y++) { VLINE = (DWORD)y; memset(Grp_LineBuf, 0, sizeof Grp_LineBuf); draw_line(vc1); static BYTE row[W]; for (int x = 0; x < W; x++) row[x] = (BYTE)(Grp_LineBuf[x] & 0xff); fwrite(row, 1, W, o); } fclose(o); fprintf(stderr, "[GVPACK] px68k model: %s, vcreg1=%02X, bottom page=%d -> %s\n", packed ? (buffer ? "PACKED 1.0 B/px" : "PACKED but bit11 OFF") : "unpacked 2.0 B/px", vc1, ((vc1 & 3) <= ((vc1 >> 4) & 3)) ? 1 : 0, out); return 0; }