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;
+ }
+ }
}
}