From 981d98d784a49faee7666d850774a7f6669c2741 Mon Sep 17 00:00:00 2001 From: Cyd Date: Fri, 3 Jul 2026 18:27:10 -0500 Subject: [PATCH] VDB: dump captured palettes (VDB_PALDUMP) + swatch renderer VDB_PALDUMP= writes each completed 768-byte palette load to N.rgb (256 RGB entries per VDB display group). render_vdb_palettes.py renders each as a 16x16 swatch grid + linear ramp. First capture: pal1 (aux1) is a structured red/green/olive/black color map (2-bit R x 2-bit G) -- likely the color display's status coloring; pal0 (secondary, dynamic ~29 reloads) and pal2 (aux2) are grayscale, sampled black mid-animation. Palettes are the per-display color maps the VDB applies when splitting the framebuffer to the six monitors. Co-Authored-By: Claude Opus 4.8 --- emulator/render_vdb_palettes.py | 67 +++++++++++++++++++++++++++++++++ emulator/vpx-device/vpxlog.cpp | 24 +++++++++--- 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 emulator/render_vdb_palettes.py diff --git a/emulator/render_vdb_palettes.py b/emulator/render_vdb_palettes.py new file mode 100644 index 0000000..791ba87 --- /dev/null +++ b/emulator/render_vdb_palettes.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Render captured VDB palettes (VDB_PALDUMP N.rgb) as swatch images. + +Each .rgb is 256 RGB triplets (768 bytes) -- one of the VDB's three display +palette groups (secondary / aux1 / aux2). For each, emit: + N.png -- a 16x16 grid of the 256 colors + a 256-wide linear ramp. + +Usage: render_vdb_palettes.py (e.g. .../scratchpad/vdbpal) +""" +import sys + +from PIL import Image, ImageDraw, ImageFont + +NAMES = {0: "secondary (dynamic)", 1: "aux 1", 2: "aux 2"} + + +def render_one(path, out, title): + data = open(path, "rb").read() + if len(data) < 768: + data = data + b"\x00" * (768 - len(data)) + cols = [(data[i * 3], data[i * 3 + 1], data[i * 3 + 2]) for i in range(256)] + + cell, grid = 28, 16 + gw = cell * grid + ramp_h = 40 + pad, top = 12, 34 + W = gw + pad * 2 + H = top + gw + 10 + ramp_h + pad + img = Image.new("RGB", (W, H), (24, 24, 28)) + d = ImageDraw.Draw(img) + try: + font = ImageFont.truetype("consola.ttf", 16) + except Exception: + font = ImageFont.load_default() + d.text((pad, 8), title, fill=(230, 230, 235), font=font) + + # 16x16 swatch grid (index 0 top-left, row-major) + for i, c in enumerate(cols): + x = pad + (i % grid) * cell + y = top + (i // grid) * cell + d.rectangle([x, y, x + cell - 1, y + cell - 1], fill=c) + # linear ramp strip below + ry = top + gw + 10 + for i, c in enumerate(cols): + x0 = pad + i * (gw / 256.0) + x1 = pad + (i + 1) * (gw / 256.0) + d.rectangle([x0, ry, x1, ry + ramp_h], fill=c) + d.rectangle([pad, ry, pad + gw, ry + ramp_h], outline=(80, 80, 88)) + img.save(out) + # quick stats + grays = sum(1 for r, g, b in cols if abs(r - g) < 6 and abs(g - b) < 6) + print(f"{out}: {grays}/256 grayscale-ish entries") + + +def main(): + prefix = sys.argv[1] + for g in (0, 1, 2): + p = f"{prefix}{g}.rgb" + try: + render_one(p, f"{prefix}{g}.png", + f"VDB palette {g} - {NAMES.get(g, '')}") + except FileNotFoundError: + print(f"(no {p})") + + +if __name__ == "__main__": + main() diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index cd302cc..897a316 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -1089,6 +1089,10 @@ static bool vdb_splitter_on = false; /* lazy coalescing of palette data-byte writes so a 768-byte load is one line */ static int vdb_data_group = -1; static unsigned long vdb_data_count = 0; +/* VDB_PALDUMP=: on each completed palette load, write the group's + * 768-byte RGB palette to N.rgb so the display color maps can be + * visualized. */ +static const char *vdb_paldump = NULL; /* Palette groups sit at Adam's-base + 2 (see L4VB16.CPP / L4SVGA16.ASM): * secondary 0x302-0x305, aux1 0x30A-0x30D, aux2 0x312-0x315. Within a group @@ -1103,11 +1107,19 @@ static int vdb_group_of(io_port_t off) { } static int vdb_group_base(int g) { return 2 + 8 * g; } static void vdb_flush_data(void) { - if (vpx_fp && vdb_data_group >= 0 && vdb_data_count) { - flush_run(); - fprintf(vpx_fp, "# VDB pal%d loaded %lu data bytes\n", - vdb_data_group, vdb_data_count); - fflush(vpx_fp); + if (vdb_data_group >= 0 && vdb_data_count) { + if (vpx_fp) { + flush_run(); + fprintf(vpx_fp, "# VDB pal%d loaded %lu data bytes\n", + vdb_data_group, vdb_data_count); + fflush(vpx_fp); + } + if (vdb_paldump && vdb_data_count >= 768) { + char path[600]; + snprintf(path, sizeof path, "%s%d.rgb", vdb_paldump, vdb_data_group); + FILE *pf = fopen(path, "wb"); + if (pf) { fwrite(vdb_pal[vdb_data_group].ram, 1, 768, pf); fclose(pf); } + } } vdb_data_group = -1; vdb_data_count = 0; } @@ -1196,6 +1208,8 @@ void VPXLOG_Init(void) { * logging, unless VDB=0. Records palette + splitter-clock state. */ const char *vdbenv = getenv("VDB"); if (!(vdbenv && vdbenv[0] == '0')) { + const char *pd = getenv("VDB_PALDUMP"); + if (pd && pd[0]) vdb_paldump = pd; vdb_reset(); IO_RegisterReadHandler(VDB_BASE, vdb_read, IO_MB, 0x1B); /* 0x300-0x31A */ IO_RegisterWriteHandler(VDB_BASE, vdb_write, IO_MB, 0x1B);