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<g>/VPX_MAIN as usual;
  win<g>.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 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-04 00:46:54 -05:00
co-authored by Claude Fable 5
parent 377047f37d
commit 301f381305
+102 -17
View File
@@ -659,6 +659,9 @@ static void rt_draw(HDC dc, const VFrame &f, int cw, int ch) {
* current RGB buffer to "<dir>/win<g>.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;
unsigned idx; int pg, chn = -1;
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 */
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];
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<g> = "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<g>.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<g>.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;
}
}
}
}