The emulated VPX board now draws frames in real time. The FIFO burst assembler feeds an in-process DPL scene store; each vr_draw_scene publishes a frame snapshot to a dedicated WGL window thread that renders it fixed-function. - vpxlog.cpp: scene_burst() decode + VScene store + rt_main GL window thread; glFrustum from the view-node window rect, glScalef(-1,1,1) for Division's mirrored screen-x, action-31 camera as modelview. No build change (opengl32 already linked). - Validated: flyk divrgb.scn opens the "VPX VelociRender (emulated)" window and draws the SMPTE bars live via the real camera.spl spline camera (divrgb-live-gl.png), matching the offline render_capture.py decode. - Game path (alpha1.conf) opens the window + draws background but hits the pre-existing production btdpl.ini vr_sync timeout (sends_wo_rcv) -- not a 3b issue. Remaining work tracked in PHASE3-PROGRESS.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
6.1 KiB
Markdown
122 lines
6.1 KiB
Markdown
# Phase 3 — Render Backend: Progress
|
||
|
||
**Status (2026-07-03): Phase 3a + 3b complete. 3a — the render command stream
|
||
is fully captured and decoded, and a captured DIVRGB frame reconstructed to
|
||
pixels offline. 3b — a live OpenGL window built into the emulated board draws
|
||
each frame in real time inside DOSBox-X. First images ever produced from the
|
||
Rel 4.10 VPX protocol without a real board.**
|
||
|
||

|
||
|
||
That is `flyk divrgb.scn` — Division's SMPTE color-bar calibration scene —
|
||
rendered *entirely from the FIFO command stream captured by the emulated VPX
|
||
board*: geometry, connectivity, materials, camera, viewport and background all
|
||
come from the wire, none from the scene files. `divrgb-frame0.png` is the same
|
||
capture drawn with the actual frame-0 spline camera the app sent.
|
||
|
||
## 3a. Full FIFO capture
|
||
|
||
`vpxlog.cpp` now records every FIFO burst when `VPX_FIFODUMP=<path>` is set
|
||
(records: `'VPXM'` magic, u32 length, raw bytes; one record per burst between
|
||
`outputData` tag writes). The capture used here: `divrgb.fifodump`
|
||
(1297 messages) from `divrgb.conf`.
|
||
|
||
Tools:
|
||
|
||
- `decode_fifodump.py <dump> [--hex N] [--action A]` — action census + payload
|
||
hexdumps.
|
||
- `render_capture.py <dump> [-o out.png] [--frame N] [--eye x,y,z]` —
|
||
reconstructs the scene graph and software-renders the frame each
|
||
`draw_scene` commits.
|
||
|
||
## The Rel 4.10 wire protocol (established from this capture)
|
||
|
||
One burst per message: `[action:4][payload]`, packetized at 508 bytes.
|
||
The i860 image download itself rides the FIFO in this build (actions 18–20,
|
||
`VREND.MNG`). The DPL3 `vr_action` enum holds for 0–23 but Rel 4.10 extends
|
||
and re-purposes the tail:
|
||
|
||
| action | meaning | payload |
|
||
|---|---|---|
|
||
| 0 | init | args string (`/device~0x150~/video~svga~…`) |
|
||
| 1 | create | `[type][name]` — **the host assigns node names** (1,2,3…); the board's create reply node value is ignored |
|
||
| 3 | flush | `[name][type][node struct]` (see node types below) |
|
||
| 7 | dcs_link | `[parent][child]` |
|
||
| 9 | draw_scene | `[view?][0][0][1][1.0f]` — commits the frame |
|
||
| 11 | list_add | `[parent][child]` |
|
||
| 18–21 | 860code/data/bss/args | i860 download (FIFO in this build) |
|
||
| 23 | **set_geom_verts** (Rel4.10; DPL3 had 22) | hdr `[name][0][n_verts][3][n_blocks][1][5][n_verts][1.0f]`, then float32 x,y,z per vertex |
|
||
| 25 | **set_geom_conns** (new) | hdr `[name][n_polys][loop_len][0]`, then indices; each poly a closed loop (last=first) |
|
||
| 31 | **camera** (new) | `[?][view][3×3 rotation, row-major][eye x,y,z]` per frame |
|
||
| 45 (0x2D) | sync | token ping (see PHASE2) |
|
||
|
||
Node types (from create/flush pairs in this capture):
|
||
|
||
| type | node | flush payload highlights |
|
||
|---|---|---|
|
||
| 2 | texture/ramp? | one word, `0xFFFFFFFF` / `0x0FFFFFFF` |
|
||
| 3 | view | window l,b,r,t (±1 × ±0.6154), window-plane distance 1.3, viewport 832×512, near 2, far 12000, background RGB |
|
||
| 4 | light | |
|
||
| 5 | dcs | 4×4 matrix |
|
||
| 6 | material (old-style) | RGB floats |
|
||
| 7 | object | |
|
||
| 8 | lod | 2×4 bounds + lists |
|
||
| 9 | geogroup | bounds; **payload int 14 = material node name** |
|
||
| 10 | geometry | `[geo_type][n_polys][n_verts]…[radius]` |
|
||
| 11 | material | **floats 10–12 (payload) = diffuse RGB** |
|
||
|
||
Graph: `list_add` links object→lod→geogroup→geometry (and zone-level nodes to
|
||
node 0); geogroups bind materials by name. The 13 DIVRGB bars decode to the
|
||
textbook SMPTE pattern — 7 color bars, the reverse strip, and the −I/white/+Q
|
||
PLUGE row — which validates vertices, connectivity, materials, camera and
|
||
window mapping in one image. (Division screen x runs opposite to a GL-style
|
||
eye space; without negating x the pattern comes out mirrored.)
|
||
|
||
## 3b. Live OpenGL backend (DONE)
|
||
|
||

|
||
|
||
`VPX_RENDER=1` turns on a live render backend built into `vpxlog.cpp`
|
||
(Windows/WGL). The same burst assembler that feeds `VPX_FIFODUMP` now also
|
||
feeds an in-process scene store (`scene_burst()` → `struct VScene`), and each
|
||
`vr_draw_scene` publishes a frame snapshot to a dedicated OpenGL window thread
|
||
(`rt_main`) that draws it with fixed-function GL. No build-system change was
|
||
needed — `opengl32` was already linked, and `vpxlog.cpp` is already in
|
||
`hardware/`.
|
||
|
||
Validated: `flyk divrgb.scn` (`divrgb.conf` with `VPX_RENDER=1`) opens the
|
||
"VPX VelociRender (emulated)" window and draws the SMPTE bars live, framed by
|
||
the real `camera.spl` spline camera the app sends — matching the offline
|
||
`render_capture.py` decode. The window runs on its own thread, so it keeps
|
||
displaying the last frame after the DOS app exits.
|
||
|
||
Design notes:
|
||
- Multi-burst payloads (`set_geom_verts`/`conns` continuations) are tracked
|
||
with `geom_active` / `conn_active` state, same as the offline decoder.
|
||
- Projection: `glFrustum` from the view-node window rect scaled by
|
||
`near/window_distance`; a `glScalef(-1,1,1)` handles Division's mirrored
|
||
screen-x. Camera is the row-major 3×3 from action 31 loaded as the
|
||
modelview rotation, then `glTranslatef(-eye)`.
|
||
- Frame handoff is a critical-section-guarded `VFrame` + auto-reset event;
|
||
the emulator thread never blocks on GL.
|
||
|
||
Game path (`alpha1.conf`, full BattleTech v4.10): the window opens and draws
|
||
the view background, but the run hits the **pre-existing production-path sync
|
||
timeout** (`velocirender_receive timed out — sends_wo_rcv=3`, then
|
||
`failed in vr_sync`) — the `btdpl.ini`/netnub launch bursts several sends
|
||
before its first receive, which the POLL_THRESHOLD gating stalls. This is not
|
||
a 3b issue (the `flyk` clean-launch path renders fine); it is the same
|
||
production-sync item still open from Phase 2.
|
||
|
||
## Remaining
|
||
|
||
1. **Production-path vr_sync timeout** (`sends_wo_rcv`): the game's
|
||
`btdpl.ini`/netnub launch path. Needed before the game itself renders.
|
||
2. **Content**: `test.egg` carries no models, so even past sync the game has
|
||
no `buttee.bgf`/`mslr.bgf` geometry to draw — needs a real mission/content
|
||
tree from the pod image.
|
||
3. **DOS/4GW sync variant** for the CYCLE `flyk yip.scn` fixture (Red Planet
|
||
geometry) — the older action-check sync.
|
||
4. Texturing (`set_texmap_texels`, action 23-data + SVT), lighting, and
|
||
depth/material niceties; current backend is flat-shaded untextured polys.
|