Files
prosolis f1007a0dbc Put the frame in a container with no decoder, and find the palette is not free
ROADMAP K2. DLXP1: a 49,664 B record that is 97 sectors exactly, no index and
no length word, because a packed record's length is geometry rather than
content. 582.0 KB/s, which is what FINDINGS 61.9 predicted to the tenth, and it
encodes in 3.3 s because there is no k-means in it.

px68k's own x68k/gvram.c renders the container's bytes index-exact with the
harness computing no interleave -- the only test that can catch an encoder whose
byte order is wrong, since a container round-trips against its own inverse
either way. Both negative controls fail as they must.

The picture is re-derived against this project's builder rather than PIL's
(34.05 dB against 61.9's 34.08) and the GGGGGRRRRRBBBBBI word is charged for the
first time in this tree: 0.53 dB, on every row, so it moves no comparison.

What the control found is the finding. A packed container on a SCENE palette
lands exactly on the codec's ceiling, so the whole +2.31 dB is the per-frame
palette and nothing else -- and 231 of 256 entries change every frame, which
makes a mismatched paint 12.8 dB worse than the correct pairing, on screen for
roughly half of every frame slot if buffer mode does not blank. So B2 now
decides which packed CONTAINER ships, not only which player. The fallback is
already a flag: --scene-palette --no-palette is 30.79 dB, zero churn, 576.0 KB/s
and still +2.07 dB on the shipping codec.

62.5 is priced and is a wash: palette first 20.32 dB, palette last 20.33.

Claude-Session: https://claude.ai/code/session_01194oWYW8DQXK1SZ2DnChW6
2026-08-25 07:31:05 -07:00

179 lines
6.9 KiB
C

/* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#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;
}