Phase 3a: decode captured VPX render stream to pixels (SMPTE bars)

- vpxlog.cpp: VPX_FIFODUMP=<path> records every FIFO burst ('VPXM' records)
- decode_fifodump.py: action census + payload dumps of a capture
- render_capture.py: reconstruct the DPL scene graph from a capture and
  software-render each draw_scene frame (camera, view, materials, geometry
  all taken from the wire)
- divrgb.conf + divrgb.fifodump: flyk divrgb.scn capture fixture
- divrgb-decoded.png / divrgb-frame0.png: first images ever produced from
  the Rel 4.10 VPX protocol without a real board -- the textbook SMPTE
  color-bar pattern, validating verts/conns/materials/camera in one shot
- PHASE3-PROGRESS.md: the established Rel 4.10 wire protocol (action map,
  node types, message layouts); RENDER-HARNESS.md updated

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-03 14:13:02 -05:00
co-authored by Claude Opus 4.8
parent 2c29bf928d
commit 4b6d910f7b
11 changed files with 527 additions and 8 deletions
+44
View File
@@ -129,6 +129,40 @@ static bool frame_outstanding = false; /* draw_scene sent, frame-ack owed
static void fifo_arm_action(void) { fifo_arm = true; fifo_cap_pos = 0; fifo_cap = 0; }
/* ---- Phase 3: full FIFO message dump (VPX_FIFODUMP=<path>) -------------- *
* The FIFO wire format (VR_COMMS.C velocirender_transmit + OUTSW.ASM): each
* transmit is TWO tag-delimited bursts. The C caller fires the first 3 bytes
* of the protocol word at outputData, outsw() sends 0x40 as its final byte
* and REP OUTSWs the payload into the FIFO port. Burst 1 carries the 4-byte
* action, burst 2 the data (protocol word (0xff<<16)|(data+4)). So recording
* every FIFO byte between outputData writes yields alternating action/data
* records that a decoder can pair back into [action][data] messages.
* Record format: 'VPXM' u32 magic, u32 length LE, then the raw burst bytes. */
static FILE *fifo_dump_fp = NULL;
static unsigned char *fifo_buf = NULL;
static size_t fifo_buf_len = 0, fifo_buf_cap = 0;
static void fifo_buf_push(unsigned char v) {
if (fifo_dump_fp == NULL) return;
if (fifo_buf_len == fifo_buf_cap) {
size_t ncap = fifo_buf_cap ? fifo_buf_cap * 2 : 4096;
unsigned char *nb = (unsigned char *)realloc(fifo_buf, ncap);
if (nb == NULL) return; /* drop byte rather than crash */
fifo_buf = nb; fifo_buf_cap = ncap;
}
fifo_buf[fifo_buf_len++] = v;
}
static void fifo_flush_record(void) {
if (fifo_dump_fp == NULL || fifo_buf_len == 0) return;
unsigned char hdr[8] = { 'V','P','X','M',
(unsigned char)(fifo_buf_len), (unsigned char)(fifo_buf_len >> 8),
(unsigned char)(fifo_buf_len >> 16), (unsigned char)(fifo_buf_len >> 24) };
fwrite(hdr, 1, sizeof hdr, fifo_dump_fp);
fwrite(fifo_buf, 1, fifo_buf_len, fifo_dump_fp);
fflush(fifo_dump_fp);
fifo_buf_len = 0;
}
static void parse_fifo_byte(unsigned char v) {
if (!fifo_arm) return;
fifo_cap |= ((unsigned)v) << (fifo_cap_pos * 8);
@@ -175,6 +209,7 @@ static int vpx_max_postboot_acks = 200; /* safety cap */
static int empty_polls = 0;
static const int POLL_THRESHOLD = 6; /* consecutive empty polls => blocking receive */
static void queue_render_ack_node(unsigned char action, unsigned node) {
fifo_flush_record(); /* a receive means the outstanding burst is complete */
unsigned char m[12] = {
0x08, 0x00, 0x00, 0x00, /* length_word 0x00000008 (nb=8) */
action, 0x00, 0x00, 0x00, /* payload[0..3] = action (LE) */
@@ -342,13 +377,16 @@ static void vpx_write(Bitu port, Bitu val, Bitu iolen) {
wf_payload_left = -1; wf_action_pos = 0; wf_action = 0; last_action = 0;
fifo_arm = false; fifo_cap_pos = 0; fifo_cap = 0;
expect_sync_token = false; sync_pending = false; sync_token = 0; frame_outstanding = false;
fifo_flush_record();
note("board reset");
} else if (off == 1) {
saw_write = true; /* outputData: a download/response byte */
empty_polls = 0; /* a write means the game isn't blocking-reading */
fifo_flush_record(); /* outputData write = FIFO burst boundary */
if (parse_frames) { parse_out_byte((unsigned char)val); fifo_arm_action(); }
} else if (off == 4 || off == 5) {
/* FIFO data port (link B): payload words, low/high bytes. */
fifo_buf_push((unsigned char)val);
if (parse_frames) parse_fifo_byte((unsigned char)val);
}
}
@@ -367,6 +405,12 @@ void VPXLOG_Init(void) {
const char *h = getenv("VPX_HANDSHAKES");
if (h && atoi(h) > 0) vpx_max_handshakes = atoi(h);
const char *fd = getenv("VPX_FIFODUMP");
if (fd && fd[0]) {
fifo_dump_fp = fopen(fd, "wb");
if (fifo_dump_fp == NULL) LOG_MSG("VPXLOG: cannot open fifodump '%s'", fd);
}
IO_RegisterReadHandler(VPX_BASE, vpx_read, IO_MB, 18);
IO_RegisterWriteHandler(VPX_BASE, vpx_write, IO_MB, 18);