Phase 3b: live OpenGL render backend in DOSBox-X (VPX_RENDER=1)
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>
This commit is contained in:
@@ -5,6 +5,7 @@ image/
|
||||
# Phase 3 evidence renders (keep: decoded from the captured VPX stream)
|
||||
!divrgb-decoded.png
|
||||
!divrgb-frame0.png
|
||||
!divrgb-live-gl.png
|
||||
dbx_out.txt
|
||||
vpx*.txt
|
||||
sweep_*.txt
|
||||
|
||||
+51
-12
@@ -1,9 +1,10 @@
|
||||
# Phase 3 — Render Backend: Progress
|
||||
|
||||
**Status (2026-07-03): Phase 3a complete — the render command stream is fully
|
||||
captured and decoded, and a captured DIVRGB frame has been reconstructed to
|
||||
pixels. First image ever produced from the Rel 4.10 VPX protocol without a
|
||||
real board.**
|
||||
**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.**
|
||||
|
||||

|
||||
|
||||
@@ -71,12 +72,50 @@ 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.)
|
||||
|
||||
## Next: 3b — live OpenGL backend
|
||||
## 3b. Live OpenGL backend (DONE)
|
||||
|
||||
Feed the same decode path a live stream inside DOSBox-X instead of a dump:
|
||||
maintain the node store in `vpxlog.cpp` (or a sibling `vpxrender.cpp`), upload
|
||||
geometry on set_geom_verts/conns, draw on draw_scene into an OpenGL window
|
||||
(or the DOSBox surface). The `divrgb.fifodump` fixture allows developing the
|
||||
backend offline; `flyk yip.scn` (CYCLE, Red Planet geometry) is the next
|
||||
fixture up once the DOS/4GW sync variant is handled, then the game itself
|
||||
(`buttee.bgf` etc. via the production content path).
|
||||

|
||||
|
||||
`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.
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.5 KiB |
@@ -141,9 +141,13 @@ static void fifo_arm_action(void) { fifo_arm = true; fifo_cap_pos = 0; fifo_cap
|
||||
static FILE *fifo_dump_fp = NULL;
|
||||
static unsigned char *fifo_buf = NULL;
|
||||
static size_t fifo_buf_len = 0, fifo_buf_cap = 0;
|
||||
static bool vpx_render = false; /* Phase 3b live GL backend */
|
||||
static void scene_burst(const unsigned char *p, size_t n); /* fwd (3b) */
|
||||
static void scene_reset(void); /* fwd (3b) */
|
||||
|
||||
static void fifo_buf_push(unsigned char v) {
|
||||
if (fifo_dump_fp == NULL) return;
|
||||
if (fifo_dump_fp == NULL && !vpx_render) return;
|
||||
if (fifo_buf_len >= (1u << 20)) return; /* runaway guard */
|
||||
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);
|
||||
@@ -153,13 +157,16 @@ static void fifo_buf_push(unsigned char v) {
|
||||
fifo_buf[fifo_buf_len++] = v;
|
||||
}
|
||||
static void fifo_flush_record(void) {
|
||||
if (fifo_dump_fp == NULL || fifo_buf_len == 0) return;
|
||||
if (fifo_buf_len == 0) return;
|
||||
if (fifo_dump_fp) {
|
||||
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);
|
||||
}
|
||||
if (vpx_render) scene_burst(fifo_buf, fifo_buf_len);
|
||||
fifo_buf_len = 0;
|
||||
}
|
||||
|
||||
@@ -378,6 +385,7 @@ static void vpx_write(Bitu port, Bitu val, Bitu iolen) {
|
||||
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();
|
||||
scene_reset();
|
||||
note("board reset");
|
||||
} else if (off == 1) {
|
||||
saw_write = true; /* outputData: a download/response byte */
|
||||
@@ -391,6 +399,317 @@ static void vpx_write(Bitu port, Bitu val, Bitu iolen) {
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= Phase 3b: live render backend (VPX_RENDER=1) ==========
|
||||
* Reconstructs the DPL scene graph from the FIFO message stream (protocol
|
||||
* established in PHASE3-PROGRESS.md / render_capture.py) and draws each
|
||||
* vr_draw_scene frame in a native OpenGL window on a dedicated thread.
|
||||
* Windows-only for now (WGL); the scene decode itself is portable. */
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#define VPX_RENDER_SUPPORTED 1
|
||||
#endif
|
||||
|
||||
#ifdef VPX_RENDER_SUPPORTED
|
||||
#ifndef WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <GL/gl.h>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
static unsigned rd_u32(const unsigned char *p) {
|
||||
return (unsigned)p[0] | ((unsigned)p[1] << 8) |
|
||||
((unsigned)p[2] << 16) | ((unsigned)p[3] << 24);
|
||||
}
|
||||
static float rd_f32(const unsigned char *p) {
|
||||
float f; unsigned u = rd_u32(p); memcpy(&f, &u, 4); return f;
|
||||
}
|
||||
|
||||
struct VCol { float c[3]; };
|
||||
struct VPoly { float rgb[3]; std::vector<float> xyz; }; /* x,y,z triples */
|
||||
struct VFrame {
|
||||
bool valid;
|
||||
float bg[3];
|
||||
float win[5]; /* l, b, r, t, window-plane distance */
|
||||
float nearp, farp;
|
||||
int vw, vh;
|
||||
bool has_cam;
|
||||
float rot[9], eye[3]; /* row-major rotation; eye = R*(p - e) */
|
||||
std::vector<VPoly> polys;
|
||||
VFrame() : valid(false), nearp(2), farp(12000), vw(832), vh(512),
|
||||
has_cam(false) {
|
||||
bg[0] = bg[1] = bg[2] = 0;
|
||||
win[0] = -1; win[1] = -0.6153846f; win[2] = 1; win[3] = 0.6153846f;
|
||||
win[4] = 1.3f;
|
||||
}
|
||||
};
|
||||
|
||||
static struct VScene {
|
||||
std::map<unsigned, unsigned> type; /* name -> node type */
|
||||
std::map<unsigned, std::vector<float> > verts; /* geometry -> xyz */
|
||||
std::map<unsigned, std::vector<std::vector<int> > > polys;
|
||||
std::map<unsigned, VCol> mat; /* material -> RGB */
|
||||
std::map<unsigned, unsigned> ggmat; /* geogroup -> material */
|
||||
std::map<unsigned, std::vector<unsigned> > children;
|
||||
VFrame view; /* view/bg/camera state */
|
||||
/* multi-burst assembly */
|
||||
unsigned geom_node; size_t geom_need; bool geom_active;
|
||||
unsigned conn_node, conn_npolys, conn_loop; bool conn_active;
|
||||
} S;
|
||||
|
||||
/* ---- render thread ------------------------------------------------------ */
|
||||
static HANDLE rt_thread = NULL, rt_event = NULL;
|
||||
static CRITICAL_SECTION rt_lock;
|
||||
static VFrame rt_pending;
|
||||
static bool rt_new = false;
|
||||
static unsigned long rt_frames = 0;
|
||||
|
||||
static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
|
||||
glViewport(0, 0, cw, ch);
|
||||
glClearColor(f.bg[0], f.bg[1], f.bg[2], 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
if (f.has_cam && !f.polys.empty()) {
|
||||
double n = f.nearp > 0 ? f.nearp : 2.0;
|
||||
double fa = f.farp > n ? f.farp : 12000.0;
|
||||
double wd = f.win[4] != 0 ? f.win[4] : 1.3;
|
||||
glMatrixMode(GL_PROJECTION);
|
||||
glLoadIdentity();
|
||||
/* Division screen x runs opposite to GL eye x (SMPTE pattern comes
|
||||
* out mirrored otherwise) -- flip x after projection. */
|
||||
glScalef(-1.0f, 1.0f, 1.0f);
|
||||
glFrustum(f.win[0] * n / wd, f.win[2] * n / wd,
|
||||
f.win[1] * n / wd, f.win[3] * n / wd, n, fa);
|
||||
glMatrixMode(GL_MODELVIEW);
|
||||
GLfloat m[16];
|
||||
for (int r = 0; r < 3; r++)
|
||||
for (int c = 0; c < 4; c++)
|
||||
m[c * 4 + r] = (c < 3) ? f.rot[r * 3 + c] : 0.0f;
|
||||
m[3] = m[7] = m[11] = 0.0f; m[15] = 1.0f;
|
||||
glLoadMatrixf(m);
|
||||
glTranslatef(-f.eye[0], -f.eye[1], -f.eye[2]);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_LIGHTING);
|
||||
glShadeModel(GL_FLAT);
|
||||
for (size_t i = 0; i < f.polys.size(); i++) {
|
||||
const VPoly &p = f.polys[i];
|
||||
glColor3f(p.rgb[0], p.rgb[1], p.rgb[2]);
|
||||
glBegin(GL_POLYGON);
|
||||
for (size_t v = 0; v + 2 < p.xyz.size(); v += 3)
|
||||
glVertex3f(p.xyz[v], p.xyz[v + 1], p.xyz[v + 2]);
|
||||
glEnd();
|
||||
}
|
||||
}
|
||||
SwapBuffers(dc);
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK rt_wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) {
|
||||
if (msg == WM_CLOSE) { ShowWindow(w, SW_MINIMIZE); return 0; }
|
||||
return DefWindowProcA(w, msg, wp, lp);
|
||||
}
|
||||
|
||||
static DWORD WINAPI rt_main(LPVOID) {
|
||||
WNDCLASSA wc; memset(&wc, 0, sizeof wc);
|
||||
wc.style = CS_OWNDC;
|
||||
wc.lpfnWndProc = rt_wndproc;
|
||||
wc.hInstance = GetModuleHandleA(NULL);
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.lpszClassName = "VPXGL";
|
||||
RegisterClassA(&wc);
|
||||
RECT r = { 0, 0, 832, 512 };
|
||||
AdjustWindowRect(&r, WS_OVERLAPPEDWINDOW, FALSE);
|
||||
HWND wnd = CreateWindowA("VPXGL", "VPX VelociRender (emulated)",
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE, 40, 40,
|
||||
r.right - r.left, r.bottom - r.top, NULL, NULL, wc.hInstance, NULL);
|
||||
if (!wnd) return 1;
|
||||
HDC dc = GetDC(wnd);
|
||||
PIXELFORMATDESCRIPTOR pfd; memset(&pfd, 0, sizeof pfd);
|
||||
pfd.nSize = sizeof pfd; pfd.nVersion = 1;
|
||||
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
|
||||
pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cDepthBits = 24;
|
||||
int pf = ChoosePixelFormat(dc, &pfd);
|
||||
SetPixelFormat(dc, pf, &pfd);
|
||||
HGLRC gl = wglCreateContext(dc);
|
||||
if (!gl) return 1;
|
||||
wglMakeCurrent(dc, gl);
|
||||
VFrame cur;
|
||||
for (;;) {
|
||||
DWORD w = MsgWaitForMultipleObjects(1, &rt_event, FALSE, INFINITE,
|
||||
QS_ALLINPUT);
|
||||
MSG msg;
|
||||
while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageA(&msg);
|
||||
}
|
||||
bool redraw = false;
|
||||
if (w == WAIT_OBJECT_0) {
|
||||
EnterCriticalSection(&rt_lock);
|
||||
if (rt_new) { cur = rt_pending; rt_new = false; redraw = true; }
|
||||
LeaveCriticalSection(&rt_lock);
|
||||
}
|
||||
if (redraw && cur.valid) {
|
||||
RECT cr; GetClientRect(wnd, &cr);
|
||||
rt_draw(dc, cur, cr.right, cr.bottom);
|
||||
rt_frames++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ---- scene-graph message decode ----------------------------------------- */
|
||||
static void scene_publish_frame(void) {
|
||||
VFrame f = S.view;
|
||||
f.valid = true;
|
||||
for (std::map<unsigned, unsigned>::const_iterator gi = S.ggmat.begin();
|
||||
gi != S.ggmat.end(); ++gi) {
|
||||
VCol col = { { 1.0f, 0.0f, 1.0f } }; /* missing = magenta */
|
||||
std::map<unsigned, VCol>::const_iterator mi = S.mat.find(gi->second);
|
||||
if (mi != S.mat.end()) col = mi->second;
|
||||
std::map<unsigned, std::vector<unsigned> >::const_iterator ci =
|
||||
S.children.find(gi->first);
|
||||
if (ci == S.children.end()) continue;
|
||||
for (size_t k = 0; k < ci->second.size(); k++) {
|
||||
unsigned geo = ci->second[k];
|
||||
std::map<unsigned, std::vector<float> >::const_iterator vi =
|
||||
S.verts.find(geo);
|
||||
std::map<unsigned, std::vector<std::vector<int> > >::const_iterator
|
||||
pi = S.polys.find(geo);
|
||||
if (vi == S.verts.end() || pi == S.polys.end()) continue;
|
||||
const std::vector<float> &vv = vi->second;
|
||||
for (size_t q = 0; q < pi->second.size(); q++) {
|
||||
const std::vector<int> &idx = pi->second[q];
|
||||
VPoly poly;
|
||||
memcpy(poly.rgb, col.c, sizeof poly.rgb);
|
||||
for (size_t j = 0; j < idx.size(); j++) {
|
||||
size_t o = (size_t)idx[j] * 3;
|
||||
if (o + 2 >= vv.size()) continue;
|
||||
poly.xyz.push_back(vv[o]);
|
||||
poly.xyz.push_back(vv[o + 1]);
|
||||
poly.xyz.push_back(vv[o + 2]);
|
||||
}
|
||||
if (poly.xyz.size() >= 9) f.polys.push_back(poly);
|
||||
}
|
||||
}
|
||||
}
|
||||
EnterCriticalSection(&rt_lock);
|
||||
rt_pending = f;
|
||||
rt_new = true;
|
||||
LeaveCriticalSection(&rt_lock);
|
||||
SetEvent(rt_event);
|
||||
}
|
||||
|
||||
static void scene_burst(const unsigned char *p, size_t n) {
|
||||
if (n < 4) return;
|
||||
unsigned action = rd_u32(p);
|
||||
const unsigned char *d = p + 4;
|
||||
size_t nb = n - 4;
|
||||
|
||||
/* multi-burst payload continuations take priority over new headers */
|
||||
if (S.geom_active && action == 23) {
|
||||
std::vector<float> &vl = S.verts[S.geom_node];
|
||||
for (size_t o = 0; o + 11 < nb; o += 12) {
|
||||
vl.push_back(rd_f32(d + o));
|
||||
vl.push_back(rd_f32(d + o + 4));
|
||||
vl.push_back(rd_f32(d + o + 8));
|
||||
}
|
||||
if (vl.size() / 3 >= S.geom_need) S.geom_active = false;
|
||||
return;
|
||||
}
|
||||
if (S.conn_active && action == 25) {
|
||||
std::vector<std::vector<int> > &pl = S.polys[S.conn_node];
|
||||
size_t nw = nb / 4;
|
||||
for (size_t o = 0; o + S.conn_loop <= nw; o += S.conn_loop) {
|
||||
std::vector<int> loop;
|
||||
for (unsigned j = 0; j + 1 < S.conn_loop; j++) /* drop closing dup */
|
||||
loop.push_back((int)rd_u32(d + (o + j) * 4));
|
||||
pl.push_back(loop);
|
||||
}
|
||||
if (pl.size() >= S.conn_npolys) S.conn_active = false;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case 1: /* create [type][name] */
|
||||
if (nb >= 8) S.type[rd_u32(d + 4)] = rd_u32(d);
|
||||
break;
|
||||
case 3: { /* flush [name][type][struct] */
|
||||
if (nb < 8) break;
|
||||
unsigned name = rd_u32(d), t = rd_u32(d + 4);
|
||||
if (t == 11 && nb >= 92) { /* material diffuse */
|
||||
VCol c = { { rd_f32(d + 48), rd_f32(d + 52), rd_f32(d + 56) } };
|
||||
S.mat[name] = c;
|
||||
} else if (t == 9 && nb >= 80) { /* geogroup material */
|
||||
S.ggmat[name] = rd_u32(d + 64);
|
||||
} else if (t == 3 && nb >= 104) { /* view */
|
||||
S.view.win[0] = rd_f32(d + 24); S.view.win[1] = rd_f32(d + 28);
|
||||
S.view.win[2] = rd_f32(d + 32); S.view.win[3] = rd_f32(d + 36);
|
||||
S.view.win[4] = rd_f32(d + 40);
|
||||
S.view.vw = (int)rd_f32(d + 44); S.view.vh = (int)rd_f32(d + 48);
|
||||
S.view.nearp = rd_f32(d + 52); S.view.farp = rd_f32(d + 56);
|
||||
S.view.bg[0] = rd_f32(d + 60); S.view.bg[1] = rd_f32(d + 64);
|
||||
S.view.bg[2] = rd_f32(d + 68);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 11: /* list_add [parent][child] */
|
||||
if (nb >= 8) S.children[rd_u32(d)].push_back(rd_u32(d + 4));
|
||||
break;
|
||||
case 23: /* set_geom_verts header */
|
||||
if (nb >= 36) {
|
||||
S.geom_node = rd_u32(d);
|
||||
S.geom_need = rd_u32(d + 8);
|
||||
S.geom_active = S.geom_need > 0;
|
||||
S.verts[S.geom_node].clear();
|
||||
}
|
||||
break;
|
||||
case 25: /* set_geom_conns header [name][n_polys][loop_len][0] */
|
||||
if (nb >= 16) {
|
||||
S.conn_node = rd_u32(d);
|
||||
S.conn_npolys = rd_u32(d + 4);
|
||||
S.conn_loop = rd_u32(d + 8);
|
||||
S.conn_active = (S.conn_npolys > 0 && S.conn_loop >= 2 &&
|
||||
S.conn_loop <= 16);
|
||||
S.polys[S.conn_node].clear();
|
||||
}
|
||||
break;
|
||||
case 31: /* per-frame camera [?][view][3x3 rows][eye] */
|
||||
if (nb >= 56) {
|
||||
for (int i = 0; i < 9; i++)
|
||||
S.view.rot[i] = rd_f32(d + 8 + i * 4);
|
||||
for (int i = 0; i < 3; i++)
|
||||
S.view.eye[i] = rd_f32(d + 44 + i * 4);
|
||||
S.view.has_cam = true;
|
||||
}
|
||||
break;
|
||||
case 9: /* draw_scene: commit */
|
||||
scene_publish_frame();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void scene_reset(void) {
|
||||
S.type.clear(); S.verts.clear(); S.polys.clear(); S.mat.clear();
|
||||
S.ggmat.clear(); S.children.clear();
|
||||
S.view = VFrame();
|
||||
S.geom_active = false; S.conn_active = false;
|
||||
}
|
||||
|
||||
static void vpx_render_start(void) {
|
||||
InitializeCriticalSection(&rt_lock);
|
||||
rt_event = CreateEventA(NULL, FALSE, FALSE, NULL);
|
||||
rt_thread = CreateThread(NULL, 0, rt_main, NULL, 0, NULL);
|
||||
vpx_render = (rt_thread != NULL);
|
||||
}
|
||||
#else /* !VPX_RENDER_SUPPORTED */
|
||||
static void scene_burst(const unsigned char *, size_t) {}
|
||||
static void scene_reset(void) {}
|
||||
static void vpx_render_start(void) {}
|
||||
#endif
|
||||
|
||||
void VPXLOG_Init(void) {
|
||||
const char *env = getenv("VPXLOG");
|
||||
if (env == NULL || env[0] == '\0') return;
|
||||
@@ -411,6 +730,13 @@ void VPXLOG_Init(void) {
|
||||
if (fifo_dump_fp == NULL) LOG_MSG("VPXLOG: cannot open fifodump '%s'", fd);
|
||||
}
|
||||
|
||||
const char *rn = getenv("VPX_RENDER");
|
||||
if (rn && rn[0] && rn[0] != '0') {
|
||||
vpx_render_start();
|
||||
LOG_MSG("VPXLOG: live render backend %s",
|
||||
vpx_render ? "started" : "unavailable");
|
||||
}
|
||||
|
||||
IO_RegisterReadHandler(VPX_BASE, vpx_read, IO_MB, 18);
|
||||
IO_RegisterWriteHandler(VPX_BASE, vpx_write, IO_MB, 18);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user