From 0c964c0ee7fdfb6d35905363a1be47288bba9227 Mon Sep 17 00:00:00 2001 From: Cyd Date: Thu, 16 Jul 2026 20:39:41 -0500 Subject: [PATCH] Cockpit: 1920x1080 single-window MFD arrangement (L4MFDSPLIT=1, RP412 layout) The RP412 single-window cockpit, adapted to reuse BT's working D3D surface compositor (DrawDevSurface) instead of RP's GDI child-window panes -- same visual outcome, far lower risk (no L4VIDEO present-routing rewrite): - L4VB16.cpp: kBTCockpitSurfaces places the six instrument surfaces at RP412's exact 1920x1080 fractional positions (Heat=UL, Mfd2=UC, Comm=UR, Mfd1=LL, Mfd3=LR, radar=center-bottom portrait); BTDrawGaugeSurfaces parameterized by surface table; BTDrawCockpitPanel composites them over the full-window 3D as the pod bezels frame the viewscreen. Hooked in L4VIDEO before EndScene (L4MFDSPLIT-gated). - btl4main.cpp: L4MFDSPLIT sizes the window to the 1920x1080 canvas (scaled to fit the work area; -res overrides) and wakes the gauge renderer (L4GAUGE) + PAD controls. Fixed two more pre-existing latent gauge crashes that only fire when the gauge renderer is woken off-pod (would hit the real pod too), previously gated only on BT_DEV_GAUGES -- extended the safety nets to L4MFDSPLIT: * gauge.h: NULL gauge-attribute-source binds a static zero instead of AV'ing in GaugeConnectionDirectOf ctor (crashed building HeatSink cluster's VertTwoPartBar at ConfigureForModel). * GAUGE.cpp: SEH-guarded gauge Execute (disable a faulting gauge, not crash). GAUGREND.cpp: skip unresolved primitives + activate all MFD pages so every surface renders. Verified: L4MFDSPLIT boots the cockpit; all six surfaces render framing the 3D viewscreen; survives coolant-loop + gauge activation. Co-Authored-By: Claude Fable 5 --- engine/MUNGA/GAUGE.cpp | 3 +- engine/MUNGA/GAUGREND.cpp | 5 +-- engine/MUNGA_L4/L4VB16.cpp | 61 +++++++++++++++++++++++++++++++++++-- engine/MUNGA_L4/L4VIDEO.cpp | 5 +++ game/btl4main.cpp | 36 ++++++++++++++++++++++ 5 files changed, 105 insertions(+), 5 deletions(-) diff --git a/engine/MUNGA/GAUGE.cpp b/engine/MUNGA/GAUGE.cpp index ed5b682..b6eb730 100644 --- a/engine/MUNGA/GAUGE.cpp +++ b/engine/MUNGA/GAUGE.cpp @@ -575,7 +575,8 @@ Logical // BT_DEV_GAUGES; the pod build calls Execute() directly. static int s_devGaugesUpd = -1; if (s_devGaugesUpd < 0) - s_devGaugesUpd = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0; + s_devGaugesUpd = (getenv("BT_DEV_GAUGES") != NULL || + getenv("L4MFDSPLIT") != NULL) ? 1 : 0; if (s_devGaugesUpd) { if (!GuardedExecute()) diff --git a/engine/MUNGA/GAUGREND.cpp b/engine/MUNGA/GAUGREND.cpp index 61978fe..48f70d8 100644 --- a/engine/MUNGA/GAUGREND.cpp +++ b/engine/MUNGA/GAUGREND.cpp @@ -1347,7 +1347,7 @@ void // widgets). Gated on BT_DEV_GAUGES so the POD path keeps the // strict Fail (it needs a complete, real method table). // - if (getenv("BT_DEV_GAUGES") != NULL) + if (getenv("BT_DEV_GAUGES") != NULL || getenv("L4MFDSPLIT") != NULL) { if (getenv("BT_GAUGE_SKIP_LOG")) DEBUG_STREAM << "[gskip] unresolved primitive '" @@ -3689,7 +3689,8 @@ void { static int s_devAllPages = -1; if (s_devAllPages < 0) - s_devAllPages = (getenv("BT_DEV_GAUGES") != NULL) ? 1 : 0; + s_devAllPages = (getenv("BT_DEV_GAUGES") != NULL || + getenv("L4MFDSPLIT") != NULL) ? 1 : 0; if (s_devAllPages) current_mode_mask |= (ModeMask)0x7FFF; } diff --git a/engine/MUNGA_L4/L4VB16.cpp b/engine/MUNGA_L4/L4VB16.cpp index ecec317..cfbaac8 100644 --- a/engine/MUNGA_L4/L4VB16.cpp +++ b/engine/MUNGA_L4/L4VB16.cpp @@ -299,14 +299,51 @@ static const BTGaugeSurfaceDesc kBTGaugeSurfaces[6] = { "Comm", 0xFFFF, 1000.0f / kBTPanelW, 0.0f, 320.0f / kBTPanelW, 0.5f, 0 }, }; +// +// SINGLE-WINDOW COCKPIT (L4MFDSPLIT=1) -- the RP412 arrangement: the 3D +// viewscreen fills the whole window and the six instrument surfaces frame it +// like the pod's bezels (they occlude the corners of the main display by +// design). Positions are fractions of a 1920x1080 canvas, matching RP412's +// SVGA16 split layout (top row of three MFDs, bottom corners, radar centered +// on the bottom edge): +// [ Heat ][ ][ Mfd2 ][ ][ Comm ] <- top row (3D shows between) +// [ Mfd1 ] [ radar (portrait) ] [ Mfd3 ] +// BT's five mono MFDs + the palette radar map onto RP's UL/UC/UR/LL/LR + map. +// +static const float kMfdW = 320.0f / 1920.0f; // 0.16667 +static const float kMfdH = 240.0f / 1080.0f; // 0.22222 +static const float kRadW = 324.0f / 1920.0f; // 0.16875 +static const float kRadH = 432.0f / 1080.0f; // 0.40000 +static const BTGaugeSurfaceDesc kBTCockpitSurfaces[6] = +{ + { "Heat", 0xFFFF, 0.0f, 0.0f, kMfdW, kMfdH, 0 }, // UL + { "Mfd2", 0xFFFF, (1.0f - kMfdW) * 0.5f, 0.0f, kMfdW, kMfdH, 0 }, // UC + { "Comm", 0xFFFF, 1.0f - kMfdW, 0.0f, kMfdW, kMfdH, 0 }, // UR + { "Mfd1", 0xFFFF, 0.0f, 1.0f - kMfdH, kMfdW, kMfdH, 0 }, // LL + { "Mfd3", 0xFFFF, 1.0f - kMfdW, 1.0f - kMfdH, kMfdW, kMfdH, 0 }, // LR + { "sec", -1, (1.0f - kRadW) * 0.5f, 1.0f - kRadH, kRadW, kRadH, 1 }, // radar, bottom center +}; + +// L4MFDSPLIT: the single-window cockpit (RP412's flag). Distinct from the +// dev-gauge dock (BT_DEV_GAUGES) -- here the 3D fills the window and the +// surfaces frame it, rather than a strip below a restricted world viewport. +static bool DevCockpitPanel() +{ + static int v = -1; + if (v < 0) v = (getenv("L4MFDSPLIT") != NULL) ? 1 : 0; + return v != 0; +} + // // Composite ALL SIX gauge surfaces into the CURRENT render target, tiled within the // panel rect (px,py,pw,ph). Reaches the ONE shared SVGA16 via any resolvable port and // extracts each surface by its own bit-plane mask. Ports that don't resolve are skipped. // -void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw, float ph) +void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw, float ph, + const BTGaugeSurfaceDesc *surfaces = 0) { if (device == NULL) return; + if (surfaces == NULL) surfaces = kBTGaugeSurfaces; GaugeRenderer *gr = application ? application->GetGaugeRenderer() : NULL; if (gr == NULL) return; @@ -347,7 +384,7 @@ void BTDrawGaugeSurfaces(LPDIRECT3DDEVICE9 device, float px, float py, float pw, for (int i = 0; i < 6; i++) { - const BTGaugeSurfaceDesc &d = kBTGaugeSurfaces[i]; + const BTGaugeSurfaceDesc &d = surfaces[i]; L4GraphicsPort *port = static_cast(gr->GetGraphicsPort(d.portName)); if (port == NULL) continue; // this surface's port isn't configured -> skip // Secondary-CRT unrotation direction is env-flippable (BT_GAUGE_SEC_ROT=3 @@ -406,6 +443,26 @@ void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device) BTDrawGaugeSurfaces(device, 2.0f, 400.0f, 540.0f, 540.0f / 2.75f); } +// +// SINGLE-WINDOW COCKPIT (L4MFDSPLIT=1): the 3D fills the whole window; frame +// it with the six instrument surfaces at the RP412 arrangement (kBTCockpitSurfaces), +// composited OVER the world as the last draw before EndScene. The world pass +// runs at the FULL viewport in this mode (no strip carve-out), so the surfaces +// occlude the corners like the pod bezels. Called from L4VIDEO alongside the +// dev-gauge inset; the two modes are mutually exclusive. +// +void BTDrawCockpitPanel(LPDIRECT3DDEVICE9 device) +{ + if (!DevCockpitPanel() || device == NULL) return; + IDirect3DSurface9 *rt = NULL; + if (FAILED(device->GetRenderTarget(0, &rt)) || rt == NULL) return; + D3DSURFACE_DESC d; + rt->GetDesc(&d); + rt->Release(); + BTDrawGaugeSurfaces(device, 0.0f, 0.0f, (float)d.Width, (float)d.Height, + kBTCockpitSurfaces); +} + //===========================================================================// // DEV-COMPOSITE -- the SEPARATE cockpit-MFD window (the default under BT_DEV_GAUGES). // A second top-level window fed by an ADDITIONAL SWAP CHAIN on the existing main diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index bbc9ef3..1ead582 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -8560,6 +8560,11 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte extern void BTDrawGaugeInset(LPDIRECT3DDEVICE9 device); BTDrawGaugeInset(mDevice); + // SINGLE-WINDOW COCKPIT (L4MFDSPLIT=1): frame the 3D with the six instrument + // surfaces in the RP412 arrangement (no-op unless L4MFDSPLIT is set). + extern void BTDrawCockpitPanel(LPDIRECT3DDEVICE9 device); + BTDrawCockpitPanel(mDevice); + hr = mDevice->EndScene(); // DEV-COMPOSITE: default mode -- render the 6 cockpit surfaces into a SEPARATE window diff --git a/game/btl4main.cpp b/game/btl4main.cpp index cb747df..e4932a2 100644 --- a/game/btl4main.cpp +++ b/game/btl4main.cpp @@ -293,6 +293,17 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine if (getenv("BT_DEV_GAUGES") != NULL && getenv("L4GAUGE") == NULL) putenv("L4GAUGE=640x480x16"); + // SINGLE-WINDOW COCKPIT (L4MFDSPLIT=1, RP412's flag): the 3D viewscreen fills + // the window and the six instrument surfaces frame it (BTDrawCockpitPanel). + // Like the dev-gauge path it needs the gauge renderer awake (L4GAUGE); the + // window itself is sized to the 1920x1080 cockpit canvas below. PAD controls + // come along for the ride (a desktop cockpit has no serial RIO). + if (getenv("L4MFDSPLIT") != NULL) + { + if (getenv("L4GAUGE") == NULL) putenv("L4GAUGE=640x480x16"); + if (getenv("L4CONTROLS") == NULL) putenv("L4CONTROLS=PAD"); + } + std::cout << "[boot] platform profile: " << (gBTPlatformPod ? "POD (RIO cockpit input; multi-surface gauges/MFDs via pod hardware or explicit L4GAUGE)" : "DEV (single window + keyboard)") @@ -413,6 +424,31 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine } } } + // SINGLE-WINDOW COCKPIT (L4MFDSPLIT=1): default the window to the 1920x1080 + // cockpit canvas (RP412), scaled DOWN uniformly to fit the monitor work area + // so the whole cockpit is visible. -res still overrides (use `-res 1920 1080` + // for native 1:1 pixels on a large display). The 3D backbuffer follows the + // window client size via the engine's own resize path. + if (getenv("L4MFDSPLIT") != NULL && (lpCmdLine == 0 || strstr(lpCmdLine, "-res") == 0)) + { + int canvas_w = 1920, canvas_h = 1080; + RECT work = { 0, 0, canvas_w, canvas_h }; + SystemParametersInfo(SPI_GETWORKAREA, 0, &work, 0); + int avail_w = work.right - work.left - 16; // leave room for chrome + int avail_h = work.bottom - work.top - 48; + int scale = 100; + if (avail_w < canvas_w || avail_h < canvas_h) + { + int fw = (avail_w * 100) / canvas_w; + int fh = (avail_h * 100) / canvas_h; + scale = (fw < fh) ? fw : fh; + if (scale < 25) scale = 25; + } + winW = (canvas_w * scale) / 100; + winH = (canvas_h * scale) / 100; + std::cout << "[cockpit] L4MFDSPLIT: 1920x1080 canvas at " << scale + << "% -> window " << winW << "x" << winH << std::endl << std::flush; + } // DOCK-BOTTOM (single-window gauges, 2026-07-12): under BT_DEV_GAUGES the // gauge strip is APPENDED below the world view in THIS window (strip height // = width x 480/1320, the panel's design aspect); BT_DEV_GAUGES_WINDOW=1