From 301f381305b49e23082b1a008997f3b77b468606 Mon Sep 17 00:00:00 2001 From: Cyd Date: Sat, 4 Jul 2026 00:46:54 -0500 Subject: [PATCH] VDB: exploded 7-display desktop mode (VPX_EXPLODE=1) New layout mode showing every cockpit display as its own desktop window -- the two MFD heads split into their individual color wires, which is what the pentapus cable does on the real pod: - wins 5-9 = the five mono MFDs (win5 LL = HEAD A red, win6 LR = HEAD A green, win7 UL = HEAD B red, win8 UC = HEAD B green, win9 UR = HEAD B blue), each decoding framebuffer bits 8-15 through its head's palette and rendering its single wire as green-phosphor brightness (G=v, R=B=v/8). - Radar (win0) rotated 90 degrees clockwise and shown portrait 480x640 -- the pod's radar CRT is mounted sideways -- centered between the two lower MFDs. - All displays at native size: MFDs 640x480, uppers at (20,20)/(680,20)/(1340,20), lowers at (20,560)/(1340,560) aligned under the outer uppers; radar at (760,560); Division main 800x600 at (2020,20). - The DOSBox SDL main screen is auto-parked centered under the Division window (one-shot EnumWindows by title + SetWindowPos). - Geometry overridable per window via VPX_WIN/VPX_MAIN as usual; win.bmp dumps cover g=5-9, radar dumps rotated as displayed. VPX_COCKPIT takes precedence if both modes are set; cockpit and debug layouts unchanged (regression-checked). Co-Authored-By: Claude Fable 5 --- emulator/vpx-device/vpxlog.cpp | 127 +++++++++++++++++++++++++++------ 1 file changed, 106 insertions(+), 21 deletions(-) diff --git a/emulator/vpx-device/vpxlog.cpp b/emulator/vpx-device/vpxlog.cpp index 558b634..13adf64 100644 --- a/emulator/vpx-device/vpxlog.cpp +++ b/emulator/vpx-device/vpxlog.cpp @@ -659,6 +659,9 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) { * current RGB buffer to "/win.bmp" (checked once per render tick). Lets * the host grab pixel-perfect captures without moving/raising live windows. */ static char pal_dump_dir[512] = ""; +/* Explode mode: the pod's radar CRT is mounted sideways, so the encoded + * frame is 90deg off; rotate clockwise to view it upright on a desktop. */ +static bool pal_radar_cw = false; static void write_bmp(const char *path, const unsigned char *rgb, int W, int H) { FILE *f = fopen(path, "wb"); @@ -714,29 +717,53 @@ static void pal_draw(HDC dc, int g, int cw, int ch, bool dump) { unsigned char *d = &img[(size_t)y * W * 3]; for (int x = 0; x < W; x++, ofs += 2, d += 3) { uint16_t px = *(const uint16_t *)(fb + (ofs & mask)); - unsigned idx; int pg; - if (g <= 2) { idx = px & 0xFFu; pg = g; } /* bits 0-7 via pal0/1/2 */ - else { idx = (px >> 8u) & 0xFFu; pg = g - 2; } /* bits 8-15 via pal1/2 */ + unsigned idx; int pg, chn = -1; + if (g <= 2) { idx = px & 0xFFu; pg = g; } /* bits 0-7 via pal0/1/2 */ + else if (g <= 4) { idx = (px >> 8u) & 0xFFu; pg = g - 2; } /* bits 8-15 via pal1/2 */ + else { idx = (px >> 8u) & 0xFFu; /* wins 5-9: one mono MFD */ + pg = (g <= 6) ? 1 : 2; /* = one color wire of a */ + chn = (g <= 6) ? g - 5 : g - 7; } /* head (pentapus split) */ const unsigned char *e = &vdb_pal[pg].ram[idx * 3u]; - d[0] = (unsigned char)((e[0] << 2) | (e[0] >> 4)); /* 6-bit DAC -> 8-bit R */ - d[1] = (unsigned char)((e[1] << 2) | (e[1] >> 4)); /* G */ - d[2] = (unsigned char)((e[2] << 2) | (e[2] >> 4)); /* B */ + if (chn < 0) { + d[0] = (unsigned char)((e[0] << 2) | (e[0] >> 4)); /* 6-bit DAC -> 8-bit R */ + d[1] = (unsigned char)((e[1] << 2) | (e[1] >> 4)); /* G */ + d[2] = (unsigned char)((e[2] << 2) | (e[2] >> 4)); /* B */ + } else { + unsigned char v = (unsigned char)((e[chn] << 2) | (e[chn] >> 4)); + d[0] = (unsigned char)(v >> 3); /* mono MFD: green-phosphor tube, */ + d[1] = v; /* wire level = beam brightness */ + d[2] = (unsigned char)(v >> 3); + } } } } else { memset(img, 0, sizeof img); } + int DW = W, DH = H; + const unsigned char *out = img; + if (g == 0 && pal_radar_cw) { + static unsigned char rot[640 * 480 * 3]; /* 90deg CW: 480x640 */ + for (int dy = 0; dy < W; dy++) { + unsigned char *o = &rot[(size_t)dy * H * 3]; + for (int dx = 0; dx < H; dx++, o += 3) { + const unsigned char *s = + &img[((size_t)(H - 1 - dx) * W + dy) * 3]; + o[0] = s[0]; o[1] = s[1]; o[2] = s[2]; + } + } + out = rot; DW = H; DH = W; + } if (dump && pal_dump_dir[0]) { char p[600]; snprintf(p, sizeof p, "%s/win%d.bmp", pal_dump_dir, g); - write_bmp(p, img, W, H); + write_bmp(p, out, DW, DH); } glViewport(0, 0, cw, ch); glClearColor(0, 0, 0, 1); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_DEPTH_TEST); glDisable(GL_TEXTURE_2D); glDisable(GL_LIGHTING); - glPixelZoom((float)cw / W, -(float)ch / H); /* scale + flip y */ + glPixelZoom((float)cw / DW, -(float)ch / DH); /* scale + flip y */ glRasterPos2f(-1.0f, 1.0f); - glDrawPixels(W, H, GL_RGB, GL_UNSIGNED_BYTE, img); + glDrawPixels(DW, DH, GL_RGB, GL_UNSIGNED_BYTE, out); SwapBuffers(dc); } @@ -745,6 +772,19 @@ static LRESULT CALLBACK rt_wndproc(HWND w, UINT msg, WPARAM wp, LPARAM lp) { return DefWindowProcA(w, msg, wp, lp); } +/* Find this process's DOSBox main (SDL) window: visible, not one of our + * VPXGL windows, "DOSBox" in the title. */ +static BOOL CALLBACK find_dosbox_wnd(HWND h, LPARAM lp) { + DWORD pid; GetWindowThreadProcessId(h, &pid); + if (pid != GetCurrentProcessId() || !IsWindowVisible(h)) return TRUE; + char cls[64]; GetClassNameA(h, cls, sizeof cls); + if (strcmp(cls, "VPXGL") == 0) return TRUE; + char t[160]; GetWindowTextA(h, t, sizeof t); + if (!strstr(t, "DOSBox")) return TRUE; + *(HWND *)lp = h; + return FALSE; +} + /* Create a visible window with its own GL context (each window gets its own * so we can render several from one thread by wglMakeCurrent-ing each). */ static bool make_gl_window(const char *title, int w, int h, int x, int y, @@ -793,36 +833,63 @@ static DWORD WINAPI rt_main(LPVOID) { * the two MFD heads (win3/win4) 640x480 @ 1440,0 and 2080,0 (those two * outputs are driver-spanned into one 1280x480 canvas starting at * x=1440). VPX_MAIN / VPX_WIN = "x,y[,w,h]" override any window. */ + /* Layout modes (VPX_COCKPIT wins if both are set): + * VPX_COCKPIT=1 four borderless windows on the rig's VGA heads. + * VPX_EXPLODE=1 all 7 cockpit displays on one desktop: the two MFD + * heads split into their individual R/G/B wires (what + * the pentapus cable does), plus radar and main. + * (neither) framed debug row of the 3 raw heads. */ const char *ck = getenv("VPX_COCKPIT"); bool cockpit = (ck && ck[0] && ck[0] != '0'); + const char *ex = getenv("VPX_EXPLODE"); + bool explode = !cockpit && ex && ex[0] && ex[0] != '0'; HWND wnd; HDC dc; HGLRC gl; int mx = 40, my = 40, mw = 832, mh = 512; if (cockpit) { mx = 0; my = 0; mw = 800; mh = 600; } + else if (explode) { mx = 2020; my = 20; mw = 800; mh = 600; } env_rect("VPX_MAIN", &mx, &my, &mw, &mh); if (!make_gl_window("VPX VelociRender (emulated)", mw, mh, mx, my, cockpit, &wnd, &dc, &gl)) return 1; - /* Real VGA heads only: win0 = bits 0-7 via pal0 (color radar); win3/win4 - * = bits 8-15 via pal1/pal2 (the two MFD heads). The former exploratory - * win1/win2 (bits 0-7 via the static palettes) are removed; g keeps the - * original 0..4 numbering so pal_draw's bit/palette selection and the - * win.bmp dump names stay stable. */ - const int NPAL = 5; - static const char *pal_titles[5] = { + /* Display windows: win0 = bits 0-7 via pal0 (color radar); win3/win4 = + * bits 8-15 via pal1/pal2 (the two raw MFD heads, channels superimposed); + * wins 5-9 = the five mono MFDs, one head color-wire each (pentapus + * split). The former exploratory win1/win2 are removed; g numbering is + * stable so pal_draw's decode and the win.bmp dump names hold. */ + const int NWIN = 10; + static const char *pal_titles[10] = { "radar (HEAD C) - bits 0-7 via pal0", "", "", "MFD head A, 2 lower (bits 8-15 via pal1)", - "MFD head B, 3 upper (bits 8-15 via pal2)" }; + "MFD head B, 3 upper (bits 8-15 via pal2)", + "MFD lower-left (HEAD A / red wire)", + "MFD lower-right (HEAD A / green wire)", + "MFD upper-left (HEAD B / red wire)", + "MFD upper-center (HEAD B / green wire)", + "MFD upper-right (HEAD B / blue wire)" }; static const int ck_x[5] = { 800, 0, 0, 1440, 2080 }; - HWND pwnd[5]; HDC pdc[5]; HGLRC pgl[5]; - bool phave[5]; + /* explode grid, all displays at native 640x480 (radar 480x640 + * portrait, rotated upright): upper MFD row on top; below it the two + * lower MFDs in the outer columns (LR aligned under UR) with the radar + * centered between them; main in the right-hand column (mx/my above). */ + pal_radar_cw = explode; + static const int ex_x[10] = { 760, 0,0,0,0, 20, 1340, 20, 680, 1340 }; + static const int ex_y[10] = { 560, 0,0,0,0, 560, 560, 20, 20, 20 }; + HWND pwnd[10]; HDC pdc[10]; HGLRC pgl[10]; + bool phave[10]; int slot = 0; /* debug-grid position counter */ - for (int g = 0; g < NPAL; g++) { + for (int g = 0; g < NWIN; g++) { phave[g] = false; if (g == 1 || g == 2) continue; /* exploratory decodes, removed */ + bool want = explode ? (g == 0 || g >= 5) /* radar + 5 mono MFDs */ + : (g <= 4); /* radar + 2 raw heads */ + if (!want) continue; int x, y, w, h; if (cockpit) { x = ck_x[g]; y = 0; w = 640; h = 480; } + else if (explode) { x = ex_x[g]; y = ex_y[g]; + if (g == 0) { w = 480; h = 640; } /* portrait radar */ + else { w = 640; h = 480; } } else { x = 700 + (slot % 3) * 500; y = 20 + (slot / 3) * 400; w = 480; h = 360; } slot++; @@ -833,6 +900,7 @@ static DWORD WINAPI rt_main(LPVOID) { } VFrame cur; + bool dosbox_parked = false; for (;;) { /* 50ms timeout so the palette windows animate even between VPX * frames (pal0 is rewritten continuously by the game). */ @@ -862,12 +930,29 @@ static DWORD WINAPI rt_main(LPVOID) { FILE *tf = fopen(trig, "rb"); if (tf) { fclose(tf); remove(trig); dump = true; } } - for (int g = 0; g < NPAL; g++) { + for (int g = 0; g < NWIN; g++) { if (!phave[g]) continue; wglMakeCurrent(pdc[g], pgl[g]); RECT cr; GetClientRect(pwnd[g], &cr); pal_draw(pdc[g], g, cr.right, cr.bottom, dump); } + /* explode mode: once the DOSBox main screen exists, park it + * centered under the Division window (one-shot; user can move it + * afterwards). */ + if (explode && !dosbox_parked) { + HWND dos = NULL; + EnumWindows(find_dosbox_wnd, (LPARAM)&dos); + if (dos) { + RECT dv, db; + GetWindowRect(wnd, &dv); + GetWindowRect(dos, &db); + int x = (dv.left + dv.right) / 2 - (int)(db.right - db.left) / 2; + int y = dv.bottom + 10; + SetWindowPos(dos, NULL, x, y, 0, 0, + SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + dosbox_parked = true; + } + } } }