Un-pack the 7-display cockpit in-engine (L4MFDSPLIT=1)
The pod drove five monochrome MFDs from the color channels of two video outputs - SVGA16 packs bit-slices of the shared gauge canvas into R/G/B of gauge window 3 (upper MFDs) and R/G of window 4 (lower MFDs), with the map palettized on the secondary and physically mounted portrait. The desktop reconstruction previously required an external BitBlt-mirror wrapper. With L4MFDSPLIT=1, SVGA16 renders each display into its own window (MFDSplitView, plain GDI) straight from the canvas + port bit-masks: five green-screen MFD windows and the 90CW-rotated Map, tiled in the pod grid to the right of the main view (L4MFDSCALE percent, default 50). The packed D3D windows stay hidden but keep presenting off-screen, leaving the original path untouched. Handles spanning mode (2-window setups). Also: the plasma glass now opens directly below the main view (clamped to the work area; L4PLASMAPOS=x,y overrides) per playtest feedback. Verified: window grid comes up as main + 5 MFDs + Map + plasma with the packed windows hidden; screenshots confirm a green MFD score readout and the portrait tactical map rendering correctly. dist packer and BUILD.md updated; the launcher wrapper is obsolete for split-mode use. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+171
-1
@@ -2,6 +2,7 @@
|
||||
#pragma hdrstop
|
||||
|
||||
#include "l4vb16.h"
|
||||
#include "l4mfdview.h"
|
||||
#include "../munga/gaugrend.h"
|
||||
#include "L4VIDEO.h"
|
||||
#include "DXUtils.h"
|
||||
@@ -176,7 +177,12 @@ void SVGA16::BuildWindows(unsigned int width, unsigned int height, bool windowed
|
||||
gaugeWindows[j] = CreateWindowEx(0, L"MainWndClass", L"RPL4", WS_BORDER, surfaceWindowRect.left, surfaceWindowRect.top, surfaceWindowRect.right-surfaceWindowRect.left, surfaceWindowRect.bottom-surfaceWindowRect.top, (HWND)NULL, (HMENU)NULL, L4Application::GetAppInstance(), (LPVOID)NULL);
|
||||
if (gaugeWindows[j])
|
||||
{
|
||||
ShowWindow(gaugeWindows[j], SW_SHOW);
|
||||
// In split-view mode the packed windows are only kept as D3D
|
||||
// device targets; the split windows are what the player sees.
|
||||
if (!splitViews)
|
||||
{
|
||||
ShowWindow(gaugeWindows[j], SW_SHOW);
|
||||
}
|
||||
|
||||
memset(&mPresentParams[j], 0, sizeof(D3DPRESENT_PARAMETERS));
|
||||
|
||||
@@ -3809,7 +3815,76 @@ SVGA16::SVGA16(
|
||||
int *aux2Index
|
||||
):Video16BitBuffered(init_width, init_height)
|
||||
{
|
||||
//------------------------------------------------------------------
|
||||
// Split-view mode: decide before BuildWindows so the packed gauge
|
||||
// windows can stay hidden.
|
||||
//------------------------------------------------------------------
|
||||
splitViews = False;
|
||||
{
|
||||
const char *split_string = getenv("L4MFDSPLIT");
|
||||
if (split_string != NULL && atoi(split_string) != 0)
|
||||
{
|
||||
splitViews = True;
|
||||
}
|
||||
}
|
||||
for (int view = 0; view < SplitViewCount; ++view)
|
||||
{
|
||||
splitView[view] = NULL;
|
||||
}
|
||||
|
||||
BuildWindows(init_width,init_height,windowed, secondaryIndex, aux1Index, aux2Index);
|
||||
|
||||
if (splitViews)
|
||||
{
|
||||
//---------------------------------------------------------------
|
||||
// One window per cockpit display. L4MFDSCALE is the view size in
|
||||
// percent of the gauge canvas (default 50). Default layout is the
|
||||
// pod grid to the right of the main view:
|
||||
// [ main ] [ UL ] [ C ] [ UR ]
|
||||
// [ LL ] [ map ] [ LR ]
|
||||
//---------------------------------------------------------------
|
||||
int scale_percent = 50;
|
||||
const char *scale_string = getenv("L4MFDSCALE");
|
||||
if (scale_string != NULL)
|
||||
{
|
||||
int requested = atoi(scale_string);
|
||||
if (requested >= 10 && requested <= 200)
|
||||
{
|
||||
scale_percent = requested;
|
||||
}
|
||||
}
|
||||
|
||||
int cell_w = (init_width * scale_percent) / 100;
|
||||
int cell_h = (init_height * scale_percent) / 100;
|
||||
int frame_h = GetSystemMetrics(SM_CYCAPTION) + 2 * GetSystemMetrics(SM_CYFIXEDFRAME);
|
||||
int frame_w = 2 * GetSystemMetrics(SM_CXFIXEDFRAME);
|
||||
int grid_x = init_width + frame_w;
|
||||
int row2_y = cell_h + frame_h;
|
||||
|
||||
splitView[SplitMFDUpperLeft] = new MFDSplitView(
|
||||
"MFD upper left", init_width, init_height,
|
||||
cell_w, cell_h, grid_x, 0);
|
||||
splitView[SplitMFDUpperCenter] = new MFDSplitView(
|
||||
"MFD upper center", init_width, init_height,
|
||||
cell_w, cell_h, grid_x + (cell_w + frame_w), 0);
|
||||
splitView[SplitMFDUpperRight] = new MFDSplitView(
|
||||
"MFD upper right", init_width, init_height,
|
||||
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), 0);
|
||||
splitView[SplitMFDLowerLeft] = new MFDSplitView(
|
||||
"MFD lower left", init_width, init_height,
|
||||
cell_w, cell_h, grid_x, row2_y);
|
||||
splitView[SplitMFDLowerRight] = new MFDSplitView(
|
||||
"MFD lower right", init_width, init_height,
|
||||
cell_w, cell_h, grid_x + 2 * (cell_w + frame_w), row2_y);
|
||||
// map is portrait: source rotated 90 degrees clockwise
|
||||
splitView[SplitMap] = new MFDSplitView(
|
||||
"Map", init_height, init_width,
|
||||
cell_h, cell_w,
|
||||
grid_x + (cell_w + frame_w) + (cell_w - cell_h) / 2, row2_y);
|
||||
|
||||
DEBUG_STREAM << "SVGA16: split views active - 5 MFD windows + rotated map (scale "
|
||||
<< scale_percent << "%)\n" << std::flush;
|
||||
}
|
||||
//STUBBED: VIDEO RB 1/15/07
|
||||
# if defined(DEBUG)
|
||||
Tell("SVGA16::SVGA16()\n");
|
||||
@@ -3993,6 +4068,13 @@ SVGA16::~SVGA16()
|
||||
// Set VWE video splitter clock divider
|
||||
//---------------------------------------------------------
|
||||
//SVGASetSplitterClock(False);
|
||||
|
||||
for (int view = 0; view < SplitViewCount; ++view)
|
||||
{
|
||||
delete splitView[view];
|
||||
splitView[view] = NULL;
|
||||
}
|
||||
|
||||
Check_Fpu();
|
||||
}
|
||||
|
||||
@@ -4029,6 +4111,43 @@ int GetShiftAmount(DWORD mask)
|
||||
return amount;
|
||||
}
|
||||
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// Render one MFD's bit-slice of the shared canvas into its split-view
|
||||
// window as a green screen. The mask arrives in the duplicated
|
||||
// (mask | mask<<16) form the packed path uses.
|
||||
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
void SVGA16::FillSplitMFD(
|
||||
SplitViewID view,
|
||||
DWORD mask,
|
||||
int shift
|
||||
)
|
||||
{
|
||||
if (splitView[view] == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Word mask16 = (Word)(mask & 0xFFFF);
|
||||
Word max_value = (Word)(mask16 >> shift);
|
||||
if (max_value == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Word *source = pixelBuffer.Data.MapPointer;
|
||||
unsigned long *dest = splitView[view]->Pixels();
|
||||
int count = pixelBuffer.Data.Size.x * pixelBuffer.Data.Size.y;
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
unsigned long value = (unsigned long)((*source++ & mask16) >> shift);
|
||||
unsigned long green = (value * 255ul) / max_value;
|
||||
*dest++ = green << 8;
|
||||
}
|
||||
|
||||
splitView[view]->Repaint();
|
||||
}
|
||||
|
||||
Logical SVGA16::Update(Logical forceAll)
|
||||
{
|
||||
HRESULT hr;
|
||||
@@ -4101,6 +4220,57 @@ Logical SVGA16::Update(Logical forceAll)
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------
|
||||
// Split views: render this window's displays into their own desktop
|
||||
// windows, straight from the shared canvas (CPU-side, no D3D).
|
||||
//------------------------------------------------------------------
|
||||
if (splitViews)
|
||||
{
|
||||
if (mDisplayToUpdate == 0)
|
||||
{
|
||||
if (splitView[SplitMap] != NULL)
|
||||
{
|
||||
// Map is portrait-mounted: rotate 90 degrees clockwise.
|
||||
// dest(x,y) = source(row = srcH-1-x, col = y)
|
||||
Word *source_base = pixelBuffer.Data.MapPointer;
|
||||
unsigned long *dest = splitView[SplitMap]->Pixels();
|
||||
int src_w = pixelBuffer.Data.Size.x;
|
||||
int src_h = pixelBuffer.Data.Size.y;
|
||||
|
||||
for (int dy = 0; dy < src_w; ++dy)
|
||||
{
|
||||
for (int dx = 0; dx < src_h; ++dx)
|
||||
{
|
||||
Word pixel = source_base[(src_h - 1 - dx) * src_w + dy];
|
||||
PaletteTriplet *entry =
|
||||
&secPalette->paletteData.Color[pixel & secMask];
|
||||
*dest++ = ((unsigned long) entry->Red << 16) |
|
||||
((unsigned long) entry->Green << 8) |
|
||||
((unsigned long) entry->Blue);
|
||||
}
|
||||
}
|
||||
splitView[SplitMap]->Repaint();
|
||||
}
|
||||
}
|
||||
else if (mDisplayToUpdate == 1)
|
||||
{
|
||||
FillSplitMFD(SplitMFDUpperLeft, ulMask, ulMask_sh);
|
||||
FillSplitMFD(SplitMFDUpperCenter, ucMask, ucMask_sh);
|
||||
FillSplitMFD(SplitMFDUpperRight, urMask, urMask_sh);
|
||||
if (NUMGAUGEWINDOWS < 3)
|
||||
{
|
||||
// spanning mode: the lower MFDs ride this window too
|
||||
FillSplitMFD(SplitMFDLowerLeft, llMask, llMask_sh);
|
||||
FillSplitMFD(SplitMFDLowerRight, lrMask, lrMask_sh);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
FillSplitMFD(SplitMFDLowerLeft, llMask, llMask_sh);
|
||||
FillSplitMFD(SplitMFDLowerRight, lrMask, lrMask_sh);
|
||||
}
|
||||
}
|
||||
|
||||
Word *data = pixelBuffer.Data.MapPointer;
|
||||
|
||||
D3DLOCKED_RECT rect;
|
||||
|
||||
Reference in New Issue
Block a user