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:
+106
-21
@@ -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;
|
||||
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<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user