m4b_gpu.py moves the per-draw rasterization to a GPU compute shader (nearest-z
winner per pixel) with the verified M5 texel decode as a vectorized numpy
post-pass. Output matches the M5-verified CPU render (frame_*.png) bit-for-bit
across all 12 frames (differ>24 = 0.000%), at 1.4s/12 frames vs 69.9s (~50x;
the firmware's 3.7s now dominates -- the render keeps up with real-time).
Reaching bit-identity took finding two real bugs (honest trail in M4B-RESULTS.md):
1. 4-edge clip: the shader tested only edges[:4] while the CPU clips with ALL
edges (up to 6); 5-6-edge polys bled past their boundary and overwrote
neighbours -- the 18% region divergence on the receding walls/floor. Fixed
with 6 edge slots.
2. float32 planes: A*x+B*y+C in float32 flipped the z-test winner vs the CPU's
float64 at contested depths. Fixed with fp64 in the shader.
exp_precision.py is the diagnostic that REJECTED int-truncation as a reconciler
(it worsens float32/64 sensitivity to ~60%; true fixed-point width is a separate
spec item). Verified honestly by measuring, not eyeballing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
3.7 KiB
M4b — offline end-to-end seam: RESULTS (2026-07-20)
emu860c/m4b_frames.py — the whole renderer chain, offline, no DOSBox:
fifodump (VPXM wire) -> production VREND.MNG on the C i860 core (351x)
-> per-draw coefficient program captured LIVE from C memory (0x08158000..)
-> M5 faithful render (verified perspective divide + real-texture texel decode)
-> frame_NNNN.png sequence + timing
Run (netdeath-20260708.fifodump, 12 frames)
queued 53088 records
13 textures decoded from the wire
frame 0: 26 quads (cmd 4584)
frame 2: 48 quads (cmd 5155)
frame 4: 78 quads (cmd 6234)
frame 6: 87 quads (cmd 8046)
frame 10: 127 quads (cmd 10273)
frame 11: 127 quads (cmd 10478)
done: 12 faithful frames in 73.6s (3.7s firmware, 69.9s render), cmd 10479
The chain works end-to-end. Frames show the scene assembling draw by draw (26 -> 48 -> 78 -> 87 -> 127 quads): frame 0 is ceiling + horizon before the floor lands; frame 11 is a coherent, perspective-correct arena interior — tiled floor receding to a vanishing point, paneled ceiling, side structures, with green/blue detail where distinctive textures (emblems/labels) land.
The one finding: render is the bottleneck, not the firmware
- Firmware: 3.7 s for 10,479 wire commands producing 12 frames — real-time-capable.
- Render: 69.9 s — the CPU numpy per-poly path (O(quads x 832 x 512) float ops per draw). ~5.8 s/frame. This is the gap to close for the live seam.
The fix already exists: the GPU tile path (igc_gpu / igc_gpu_frame,
M1/M2-conformant) does the raster on the RTX in the compute shader. Moving the
per-draw raster onto it — reading the full pixel fields (texz/texu/texv/texid)
from the tile output and running the same verified texel decode as a vectorized
post-pass — is the M4b->M4c bridge to real-time.
Honest scope
- Verified: geometry, winding, perspective divide, texel decode, live per-draw program capture, full-mission drive, frame sequencing.
- Best-effort (documented M5-B limit): exact per-surface texid->texture handle. Palette shifts with the mapping (grayscale here vs teal on the static pkl); both are real decoded textures, best-effort placement.
M4c-raster DONE (m4b_gpu.py) — GPU render, BIT-IDENTICAL to the CPU reference
The per-draw rasterization moved to a GPU compute shader; the verified M5 texel decode runs as one vectorized numpy post-pass. Result vs the CPU reference (frame_*.png from m4b_frames.py):
frame 00: differ>24 0.000% frame 05: 0.000% frame 11: 0.000% (all frames)
render: 69.9s -> 1.4s for 12 frames (~50x; firmware's 3.7s now dominates)
Two bugs found and fixed to reach bit-identity (honest trail):
- 4-edge clip: the shader tested only edges[:4]; the CPU clips with ALL edges (up to 6). 5-6-edge polys bled past their true boundary and overwrote neighbours — the ~18% region divergence on the receding walls/floor. Fixed: 6 edge slots (10 dvec4/prim).
- float32 planes: the shader evaluated Ax+By+C in float32 vs the CPU's float64, flipping the z-test winner at contested depths. Fixed: fp64 in the shader (GL_ARB_gpu_shader_fp64), so GPU == CPU exactly. (Int-truncation was TESTED as a "reconciler" and REJECTED — exp_precision.py showed it worsens float32/64 sensitivity to ~60%; the hardware's true fixed-point width is a separate spec item, orthogonal to matching the ref.)
So the renderer now runs real-time AND is provably the same image as the M5-verified path. The firmware (3.7s) is the remaining time; the render keeps up.
Next (M4c-device / M4d)
- The C012 link device in DOSBox-X + socket bridge (wire from the live game instead of a file — identical pipeline downstream).
- Present path (render-bridge window / vr_readpixels) + frame pacing.