1920x1080 internal cockpit canvas with pod-bezel occlusion

The cockpit is now a fixed 1920x1080 canvas (uniformly scaled down on
smaller monitors, min 25%):

  [ MFD UL 640 ][ MFD UC 640 ][ MFD UR 640 ]  <- native glass, 1920 wide
             [ viewscreen 800x600 centered ]
  [ MFD LL ]       [ Map ]        [ MFD LR ]  <- bottom edge

Playtest direction: the original pods MFDs partially occluded the main
display, and that is used here on purpose - the panes overlap the
viewscreen and render over it (viewscreen pinned to the bottom of the
sibling z-order, WS_CLIPSIBLINGS everywhere, so the 3D present clips
around every pane). Top-row glasses are gauge-canvas native 640x480 -
three across is exactly 1920. Red MFD buttons at half height; the amber
map columns stack contiguous like the pod strips. The auto-fit
scale/L4MFDSCALE machinery is gone - the canvas is the design space.

Verified live at 100% scale: contiguous native top row over the
centered viewscreen, bottom cluster overlapping its lower edge, map and
gauges updating (BOOST values moving, presets lit).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-12 15:26:22 -05:00
co-authored by Claude Fable 5
parent 6a06187d38
commit 1e15519e90
4 changed files with 171 additions and 154 deletions
+16 -10
View File
@@ -127,17 +127,23 @@ Two new environment options remove the hardware dependency entirely:
[ MFD LL ] [ Map ] [ MFD LR ] [ MFD LL ] [ Map ] [ MFD LR ]
``` ```
MFDs render green-screen and the map full-color/rotated, CPU-side from The cockpit is a fixed **1920×1080 internal canvas** (scaled down
the shared gauge canvas (the packed D3D windows stay hidden); the 3D uniformly on smaller work areas): the top row is three
scene presents into the viewscreen pane via `Present`'s native-resolution 640×480 glasses spanning exactly 1920, the 800×600
`hDestWindowOverride` (`gMainPresentWindow`), so no swap-chain changes. viewscreen (launch with `-res 800 600`) sits centered, and the lower
MFDs (corners) + portrait map (bottom center) anchor to the bottom
edge. The panes deliberately **overlap the viewscreen and render over
it** — in the pod the MFD bezels partially occluded the viewport, and
the same trick lets a big 3D view and full-size gauges share the
canvas. MFDs render green-screen and the map full-color/rotated,
CPU-side from the shared gauge canvas (the packed D3D windows stay
hidden); the 3D scene presents into the viewscreen pane via
`Present`'s `hDestWindowOverride` (`gMainPresentWindow`), clipped
around the panes (viewscreen pinned to the bottom of the z-order).
Mouse clicks over the viewscreen fall through to the game window Mouse clicks over the viewscreen fall through to the game window
(STATIC pane hit-test transparency). The shell **auto-fits the monitor (STATIC pane hit-test transparency). The plasma glass is currently
work area**: the pane scale steps down and the viewscreen shrinks (the hidden in cockpit mode. This replaces the external BitBlt-mirror
present stretches) until the whole cockpit is on screen. `L4MFDSCALE` launcher wrapper.
sets the *requested* MFD/map pane size in percent of the gauge canvas
(default 50). The plasma glass is currently hidden in cockpit mode.
This replaces the external BitBlt-mirror launcher wrapper.
Each split window also carries its display's **physical button bank** Each split window also carries its display's **physical button bank**
(geometry per vRIO's `CockpitLayout`): 4 red buttons above and below (geometry per vRIO's `CockpitLayout`): 4 red buttons above and below
+23 -10
View File
@@ -165,8 +165,9 @@ MFDSplitView::MFDSplitView(
if (parent != NULL) if (parent != NULL)
{ {
// chrome-less pane inside the cockpit window // chrome-less pane inside the cockpit window; clips siblings so
style = WS_CHILD | WS_VISIBLE; // overlapping panes and the viewscreen never paint over it
style = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS;
window_w = client_width; window_w = client_width;
window_h = client_height; window_h = client_height;
} }
@@ -253,9 +254,9 @@ void
// 4 buttons above the glass, 4 below; RIO addresses descend // 4 buttons above the glass, 4 below; RIO addresses descend
// from the anchor, row-major (vRIO CockpitLayout::Mfd). // from the anchor, row-major (vRIO CockpitLayout::Mfd).
//----------------------------------------------------------- //-----------------------------------------------------------
int strip_h = display_height / 8; int strip_h = display_height / 16;
if (strip_h < 18) strip_h = 18; if (strip_h < 9) strip_h = 9;
if (strip_h > 40) strip_h = 40; if (strip_h > 20) strip_h = 20;
int button_w = (display_width - 5 * buttonGap) / 4; int button_w = (display_width - 5 * buttonGap) / 4;
@@ -279,15 +280,16 @@ void
else if (button_style == SideColumns) else if (button_style == SideColumns)
{ {
//----------------------------------------------------------- //-----------------------------------------------------------
// 6 buttons down each side of the glass (the pod wires only // 6 buttons down each side of the glass, stacked contiguous
// 6 of each column's 8 addresses to buttons; the rest are // like the pod's strips (the pod wires only 6 of each
// Tesla relays). Left = anchorA ascending, right = anchorB. // column's 8 addresses to buttons; the rest are Tesla
// relays). Left = anchorA ascending, right = anchorB.
//----------------------------------------------------------- //-----------------------------------------------------------
int col_w = display_width / 8; int col_w = display_width / 8;
if (col_w < 20) col_w = 20; if (col_w < 20) col_w = 20;
if (col_w > 40) col_w = 40; if (col_w > 40) col_w = 40;
int button_h = (display_height - 7 * buttonGap) / 6; int button_h = display_height / 6;
displayX = col_w + 2 * buttonGap; displayX = col_w + 2 * buttonGap;
*client_width = display_width + 2 * (col_w + 2 * buttonGap); *client_width = display_width + 2 * (col_w + 2 * buttonGap);
@@ -298,7 +300,7 @@ void
left->unit = anchorA + i; left->unit = anchorA + i;
left->amber = 1; left->amber = 1;
left->x = buttonGap; left->x = buttonGap;
left->y = buttonGap + i * (button_h + buttonGap); left->y = i * button_h;
left->w = col_w; left->w = col_w;
left->h = button_h; left->h = button_h;
@@ -397,6 +399,17 @@ void
// one message per frame, and queued WM_PAINTs (lowest priority) starve // one message per frame, and queued WM_PAINTs (lowest priority) starve
// behind it - panes would freeze on their first frame. // behind it - panes would freeze on their first frame.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
MFDSplitView::SetPosition(int x, int y)
{
Check_Pointer(this);
if (window != NULL)
{
SetWindowPos((HWND) window, NULL, x, y, 0, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
}
void void
MFDSplitView::Repaint() MFDSplitView::Repaint()
{ {
+6
View File
@@ -59,6 +59,12 @@ public:
ClientHeight() const ClientHeight() const
{ return clientHeight; } { return clientHeight; }
// Move the pane (child mode: parent client coords). Panes overlap
// the viewscreen like the pod's bezels, so exact placement happens
// after construction from the measured client sizes.
void
SetPosition(int x, int y);
~MFDSplitView(); ~MFDSplitView();
Logical Logical
+126 -134
View File
@@ -3839,38 +3839,20 @@ SVGA16::SVGA16(
if (splitViews) if (splitViews)
{ {
//--------------------------------------------------------------- //---------------------------------------------------------------
// One window per cockpit display. L4MFDSCALE is the view size in // The 1920x1080 internal cockpit canvas.
// percent of the gauge canvas (default 50). Default layout is the //
// pod grid to the right of the main view: // [ MFD UL 640 ][ MFD UC 640 ][ MFD UR 640 ] <- native glass,
// [ main ] [ UL ] [ C ] [ UR ] // exactly 1920
// [ LL ] [ map ] [ LR ] // [ viewscreen 800x600, centered ]
// [ MFD LL ] [ Map ] [ MFD LR ] <- bottom edge
//
// The panes layer OVER the viewscreen like the pod's bezels -
// the MFDs partially occlude the main display by design, just
// as in the original cockpit. The whole canvas scales down
// uniformly on monitors smaller than 1920x1080.
//--------------------------------------------------------------- //---------------------------------------------------------------
int scale_percent = 50; const int canvas_w = 1920;
const char *scale_string = getenv("L4MFDSCALE"); const int canvas_h = 1080;
if (scale_string != NULL)
{
int requested = atoi(scale_string);
if (requested >= 10 && requested <= 200)
{
scale_percent = requested;
}
}
//---------------------------------------------------------------
// Assemble the pod interior in ONE window (the main game window
// becomes the cockpit shell; every display is a chrome-less
// child pane):
// [ MFD UL ] [ MFD UC ] [ MFD UR ]
// [ viewscreen (centered) ]
// [ MFD LL ] [ Map ] [ MFD LR ]
// The 3D scene presents into the viewscreen child via
// gMainPresentWindow. The whole shell AUTO-FITS the monitor work
// area: pane scale steps down and the viewscreen shrinks (the
// present stretches) until everything is on screen.
//---------------------------------------------------------------
int gap = 8;
int main_w = (int) L4Application::GetScreenWidth();
int main_h = (int) L4Application::GetScreenHeight();
HWND cockpit = ApplicationManager::GetCurrentManager()->GetHWnd(); HWND cockpit = ApplicationManager::GetCurrentManager()->GetHWnd();
@@ -3885,75 +3867,28 @@ SVGA16::SVGA16(
} }
RECT work; RECT work;
work.left = 0; work.top = 0; work.right = 1600; work.bottom = 900; work.left = 0; work.top = 0; work.right = 1920; work.bottom = 1080;
SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0); SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0);
int avail_w = (work.right - work.left) - chrome_w; int avail_w = (work.right - work.left) - chrome_w;
int avail_h = (work.bottom - work.top) - chrome_h; int avail_h = (work.bottom - work.top) - chrome_h;
//--------------------------------------------------------------- int scale_num = 100;
// Fit: step the pane scale down until the rows fit the width if (avail_w < canvas_w || avail_h < canvas_h)
// and leave a usable viewscreen height. Pane sizes mirror
// MFDSplitView::LayoutButtons (buttonGap = 4).
//---------------------------------------------------------------
int scale = scale_percent;
int cell_w, cell_h, strip_h, block_h;
int map_col_w, map_block_w, map_block_h;
int row_w, col_span, lower_h;
int view_w, view_h;
for (;;)
{ {
cell_w = (init_width * scale) / 100; int fit_w = (avail_w * 100) / canvas_w;
cell_h = (init_height * scale) / 100; int fit_h = (avail_h * 100) / canvas_h;
scale_num = (fit_w < fit_h) ? fit_w : fit_h;
if (scale_num < 25) scale_num = 25;
strip_h = cell_h / 8; DEBUG_STREAM << "SVGA16: cockpit canvas scaled to " << scale_num
if (strip_h < 18) strip_h = 18; << "% for the " << (work.right - work.left) << "x"
if (strip_h > 40) strip_h = 40;
block_h = cell_h + 2 * (strip_h + 8);
map_col_w = cell_h / 8;
if (map_col_w < 20) map_col_w = 20;
if (map_col_w > 40) map_col_w = 40;
map_block_w = cell_h + 2 * (map_col_w + 8);
map_block_h = cell_w;
col_span = cell_w + gap;
row_w = 3 * cell_w + 2 * gap;
lower_h = (block_h > map_block_h) ? block_h : map_block_h;
// viewscreen gets the leftover height, at most native size,
// keeping the 3D aspect
view_h = avail_h - block_h - lower_h - 2 * gap;
if (view_h > main_h) view_h = main_h;
view_w = (int)((__int64) main_w * view_h / main_h);
if (view_w > avail_w)
{
view_w = avail_w;
view_h = (int)((__int64) main_h * view_w / main_w);
}
if ((row_w <= avail_w && view_h >= 240) || scale <= 20)
{
break;
}
scale -= 5;
}
if (scale != scale_percent)
{
DEBUG_STREAM << "SVGA16: pane scale reduced to " << scale
<< "% to fit the " << (work.right - work.left) << "x"
<< (work.bottom - work.top) << " work area\n" << std::flush; << (work.bottom - work.top) << " work area\n" << std::flush;
} }
int client_w = (row_w > view_w) ? row_w : view_w; #define COCKPIT_CANVAS(v) (((v) * scale_num) / 100)
int client_h = block_h + gap + view_h + gap + lower_h;
int rows_x = (client_w - row_w) / 2; int client_w = COCKPIT_CANVAS(canvas_w);
if (rows_x < 0) rows_x = 0; int client_h = COCKPIT_CANVAS(canvas_h);
int view_x = (client_w - view_w) / 2;
if (view_x < 0) view_x = 0;
int band_y = block_h + gap;
int lower_y = band_y + view_h + gap;
//--------------------------------------------------------------- //---------------------------------------------------------------
// Grow the main window into the cockpit shell // Grow the main window into the cockpit shell
@@ -3969,51 +3904,22 @@ SVGA16::SVGA16(
} }
//--------------------------------------------------------------- //---------------------------------------------------------------
// Button banks per display (addresses per vRIO CockpitLayout, // The viewscreen: created FIRST so every pane sits above it in
// bank-to-display placement per the pod cockpit): // the z-order, and clipped by those siblings so the 3D present
// upper left MFD 0x2F.. / upper center 0x27.. / upper right // never draws over them. STATIC hit-tests transparent, so mouse
// 0x37.. / lower left 0x0F.. / lower right 0x07.. and the map // input over the 3D view reaches the game window as before.
// flanked by Secondary 0x10-0x15 / Screen 0x18-0x1D. // 800x600 (the -res native size), centered on the canvas.
//--------------------------------------------------------------- //---------------------------------------------------------------
splitView[SplitMFDUpperLeft] = new MFDSplitView( int view_w = COCKPIT_CANVAS(800);
"MFD upper left", init_width, init_height, int view_h = COCKPIT_CANVAS(600);
cell_w, cell_h, rows_x, 0,
MFDSplitView::MFDStrips, 0x2F, 0, cockpit);
splitView[SplitMFDUpperCenter] = new MFDSplitView(
"MFD upper center", init_width, init_height,
cell_w, cell_h, rows_x + col_span, 0,
MFDSplitView::MFDStrips, 0x27, 0, cockpit);
splitView[SplitMFDUpperRight] = new MFDSplitView(
"MFD upper right", init_width, init_height,
cell_w, cell_h, rows_x + 2 * col_span, 0,
MFDSplitView::MFDStrips, 0x37, 0, cockpit);
splitView[SplitMFDLowerLeft] = new MFDSplitView(
"MFD lower left", init_width, init_height,
cell_w, cell_h, rows_x, lower_y,
MFDSplitView::MFDStrips, 0x0F, 0, cockpit);
splitView[SplitMFDLowerRight] = new MFDSplitView(
"MFD lower right", init_width, init_height,
cell_w, cell_h, rows_x + 2 * col_span, lower_y,
MFDSplitView::MFDStrips, 0x07, 0, cockpit);
// map is portrait: source rotated 90 degrees clockwise, its pane
// centered under the middle column
splitView[SplitMap] = new MFDSplitView(
"Map", init_height, init_width,
cell_h, cell_w,
rows_x + col_span + (cell_w - map_block_w) / 2, lower_y,
MFDSplitView::SideColumns, 0x10, 0x18, cockpit);
//---------------------------------------------------------------
// The viewscreen: a black STATIC pane the 3D scene presents
// into. STATIC hit-tests transparent, so mouse input over the
// 3D view still reaches the game window as before.
//---------------------------------------------------------------
if (cockpit != NULL) if (cockpit != NULL)
{ {
cockpitViewscreen = CreateWindowExA( cockpitViewscreen = CreateWindowExA(
0, "STATIC", "", 0, "STATIC", "",
WS_CHILD | WS_VISIBLE | SS_BLACKRECT, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SS_BLACKRECT,
view_x, band_y, view_w, view_h, (client_w - view_w) / 2, (client_h - view_h) / 2,
view_w, view_h,
cockpit, NULL, GetModuleHandleA(NULL), NULL); cockpit, NULL, GetModuleHandleA(NULL), NULL);
if (cockpitViewscreen != NULL) if (cockpitViewscreen != NULL)
{ {
@@ -4021,14 +3927,100 @@ SVGA16::SVGA16(
} }
} }
//---------------------------------------------------------------
// Button banks per display (addresses per vRIO CockpitLayout,
// bank-to-display placement per the pod cockpit):
// upper left MFD 0x2F.. / upper center 0x27.. / upper right
// 0x37.. / lower left 0x0F.. / lower right 0x07.. and the map
// flanked by Secondary 0x10-0x15 / Screen 0x18-0x1D.
//---------------------------------------------------------------
int top_glass_w = COCKPIT_CANVAS(640);
int top_glass_h = COCKPIT_CANVAS(480);
int bot_glass_w = COCKPIT_CANVAS(320);
int bot_glass_h = COCKPIT_CANVAS(240);
int map_glass_w = COCKPIT_CANVAS(240);
int map_glass_h = COCKPIT_CANVAS(320);
splitView[SplitMFDUpperLeft] = new MFDSplitView(
"MFD upper left", init_width, init_height,
top_glass_w, top_glass_h, 0, 0,
MFDSplitView::MFDStrips, 0x2F, 0, cockpit);
splitView[SplitMFDUpperCenter] = new MFDSplitView(
"MFD upper center", init_width, init_height,
top_glass_w, top_glass_h, 0, 0,
MFDSplitView::MFDStrips, 0x27, 0, cockpit);
splitView[SplitMFDUpperRight] = new MFDSplitView(
"MFD upper right", init_width, init_height,
top_glass_w, top_glass_h, 0, 0,
MFDSplitView::MFDStrips, 0x37, 0, cockpit);
splitView[SplitMFDLowerLeft] = new MFDSplitView(
"MFD lower left", init_width, init_height,
bot_glass_w, bot_glass_h, 0, 0,
MFDSplitView::MFDStrips, 0x0F, 0, cockpit);
splitView[SplitMFDLowerRight] = new MFDSplitView(
"MFD lower right", init_width, init_height,
bot_glass_w, bot_glass_h, 0, 0,
MFDSplitView::MFDStrips, 0x07, 0, cockpit);
// map is portrait: source rotated 90 degrees clockwise
splitView[SplitMap] = new MFDSplitView(
"Map", init_height, init_width,
map_glass_w, map_glass_h, 0, 0,
MFDSplitView::SideColumns, 0x10, 0x18, cockpit);
//---------------------------------------------------------------
// Place the panes from their measured sizes: top row edge to
// edge across the canvas, bottom cluster on the bottom edge.
//---------------------------------------------------------------
if (splitView[SplitMFDUpperLeft] != NULL)
{
splitView[SplitMFDUpperLeft]->SetPosition(0, 0);
}
if (splitView[SplitMFDUpperCenter] != NULL)
{
splitView[SplitMFDUpperCenter]->SetPosition(
(client_w - splitView[SplitMFDUpperCenter]->ClientWidth()) / 2, 0);
}
if (splitView[SplitMFDUpperRight] != NULL)
{
splitView[SplitMFDUpperRight]->SetPosition(
client_w - splitView[SplitMFDUpperRight]->ClientWidth(), 0);
}
if (splitView[SplitMFDLowerLeft] != NULL)
{
splitView[SplitMFDLowerLeft]->SetPosition(
0, client_h - splitView[SplitMFDLowerLeft]->ClientHeight());
}
if (splitView[SplitMFDLowerRight] != NULL)
{
splitView[SplitMFDLowerRight]->SetPosition(
client_w - splitView[SplitMFDLowerRight]->ClientWidth(),
client_h - splitView[SplitMFDLowerRight]->ClientHeight());
}
if (splitView[SplitMap] != NULL)
{
splitView[SplitMap]->SetPosition(
(client_w - splitView[SplitMap]->ClientWidth()) / 2,
client_h - splitView[SplitMap]->ClientHeight());
}
// The panes go OVER the main display (pod bezel occlusion): pin
// the viewscreen to the bottom of the sibling z-order so the 3D
// present clips around every pane.
if (cockpitViewscreen != NULL)
{
SetWindowPos(cockpitViewscreen, HWND_BOTTOM, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
// the plasma glass sits out for now // the plasma glass sits out for now
PlasmaScreen::Hide(); PlasmaScreen::Hide();
DEBUG_STREAM << "SVGA16: single-window cockpit assembled (" DEBUG_STREAM << "SVGA16: single-window cockpit assembled ("
<< client_w << "x" << client_h << " in " << client_w << "x" << client_h << " canvas at " << scale_num
<< (work.right - work.left) << "x" << (work.bottom - work.top) << "%, viewscreen " << view_w << "x" << view_h
<< " work area, scale " << scale << "%, viewscreen " << " centered)\n" << std::flush;
<< view_w << "x" << view_h << ")\n" << std::flush;
#undef COCKPIT_CANVAS
} }
//STUBBED: VIDEO RB 1/15/07 //STUBBED: VIDEO RB 1/15/07
# if defined(DEBUG) # if defined(DEBUG)