From e02a346bb030d4c323e7c79f3ec3a63e0714e77c Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 17 Jul 2026 07:30:09 -0500 Subject: [PATCH] Emulator: VDB heads mirror plain-VGA modes (analog splitter pass-through) The VDB is an analog splitter: the game's packed-16bpp trick routes the two framebuffer bytes through the board palettes at 0x300/8/10, but any plain VGA mode passes the S3's own DAC signal to every head -- no VDB register traffic at all. The VGL_LABS test suite draws its secondary-display patterns in 640x480x8 and programs nothing at 0x300-0x31A (verified from a full session log), so the packed-only decode showed black heads. pal_draw now branches: M_LIN8 decodes through the VGA DAC (full color on the color heads, single-wire luminance on the mono MFD windows); 16bpp keeps the packed VDB-palette decode. Co-Authored-By: Claude Fable 5 --- emulator/vpx-device/vpxlog.cpp | 52 ++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 987c12f..e864d9a 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -1384,6 +1384,18 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) { fflush(vpx_fp); } } + /* The VDB is an ANALOG splitter: the game's packed-16bpp trick routes the + * two framebuffer bytes through the BOARD's palettes (0x300/8/10), but any + * plain VGA mode (the VGL_LABS test suite draws its secondary-display + * patterns in 640x480x8) passes through the S3's OWN DAC and every head + * simply mirrors that signal -- no VDB register traffic at all (verified + * 2026-07-17: TSTALL programs nothing at 0x300-0x31A). Model both: 16bpp + * = packed decode via vdb_pal; 8bpp = VGA-DAC mirror, with the mono MFD + * heads showing their single color wire's luminance. */ + const bool lin8 = (vga.mode == M_LIN8); + if (lin8) stride = (vga.draw.address_add >= (Bitu)W && + vga.draw.address_add <= (Bitu)W * 4) + ? vga.draw.address_add : (Bitu)W; if (fb) { /* win0 = framebuffer LOW byte (bits 0-7) through pal0; win3/win4 = * framebuffer HIGH byte (bits 8-15) through pal1/pal2. All as 8-bit @@ -1393,22 +1405,32 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) { for (int y = 0; y < H; y++) { Bitu ofs = start + (Bitu)y * stride; unsigned char *d = &img[(size_t)y * W * 3]; - for (int x = 0; x < W; x++, ofs += 2, d += 3) { - uint16_t px = *(const uint16_t *)(fb + (ofs & mask)); - unsigned idx; int pg, chn = -1; - if (g <= 2) { idx = px & 0xFFu; pg = g; } /* bits 0-7 via pal0/1/2 */ - else if (g <= 4) { idx = (px >> 8u) & 0xFFu; pg = g - 2; } /* bits 8-15 via pal1/2 */ - else { idx = (px >> 8u) & 0xFFu; /* wins 5-9: one mono MFD */ - pg = (g <= 6) ? 1 : 2; /* = one color wire of a */ - chn = (g <= 6) ? g - 5 : g - 7; } /* head (pentapus split) */ - if (apply_mask) idx &= vdb_pal[pg].mask; - const unsigned char *e = &vdb_pal[pg].ram[idx * 3u]; - if (chn < 0) { - d[0] = (unsigned char)((e[0] << 2) | (e[0] >> 4)); /* 6-bit DAC -> 8-bit R */ - d[1] = (unsigned char)((e[1] << 2) | (e[1] >> 4)); /* G */ - d[2] = (unsigned char)((e[2] << 2) | (e[2] >> 4)); /* B */ + for (int x = 0; x < W; x++, ofs += (lin8 ? 1 : 2), d += 3) { + unsigned char e6[3]; int chn = -1; + if (g > 4) chn = (g <= 6) ? g - 5 : g - 7; /* mono wire # */ + if (lin8) { + /* analog mirror: byte -> the VGA's own DAC */ + unsigned idx = fb[ofs & mask]; + e6[0] = (unsigned char)vga.dac.rgb[idx].red; + e6[1] = (unsigned char)vga.dac.rgb[idx].green; + e6[2] = (unsigned char)vga.dac.rgb[idx].blue; } else { - unsigned char v = (unsigned char)((e[chn] << 2) | (e[chn] >> 4)); + uint16_t px = *(const uint16_t *)(fb + (ofs & mask)); + unsigned idx; int pg; + if (g <= 2) { idx = px & 0xFFu; pg = g; } /* bits 0-7 via pal0/1/2 */ + else if (g <= 4) { idx = (px >> 8u) & 0xFFu; pg = g - 2; } /* bits 8-15 via pal1/2 */ + else { idx = (px >> 8u) & 0xFFu; /* wins 5-9: one mono MFD */ + pg = (g <= 6) ? 1 : 2; } /* = one wire of a head */ + if (apply_mask) idx &= vdb_pal[pg].mask; + const unsigned char *e = &vdb_pal[pg].ram[idx * 3u]; + e6[0] = e[0]; e6[1] = e[1]; e6[2] = e[2]; + } + if (chn < 0) { + d[0] = (unsigned char)((e6[0] << 2) | (e6[0] >> 4)); /* 6-bit DAC -> 8-bit R */ + d[1] = (unsigned char)((e6[1] << 2) | (e6[1] >> 4)); /* G */ + d[2] = (unsigned char)((e6[2] << 2) | (e6[2] >> 4)); /* B */ + } else { + unsigned char v = (unsigned char)((e6[chn] << 2) | (e6[chn] >> 4)); d[0] = (unsigned char)(v >> 3); /* mono MFD: green-phosphor tube, */ d[1] = v; /* wire level = beam brightness */ d[2] = (unsigned char)(v >> 3);