Files
RP412/MUNGA_L4/L4PLASMASCREEN.cpp
T
CydandClaude Fable 5 214a8e079c 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>
2026-07-12 14:10:52 -05:00

262 lines
6.3 KiB
C++

#include "mungal4.h"
#pragma hdrstop
#include "l4plasmascreen.h"
#include "l4app.h"
namespace
{
const char plasmaWindowClass[] = "RPPlasmaScreen";
LRESULT CALLBACK
PlasmaWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
PlasmaScreen *screen =
(PlasmaScreen *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);
switch (message)
{
case WM_PAINT:
if (screen != NULL)
{
// handled below through the friend-free public surface:
// the window procedure only asks the screen to repaint.
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// PlasmaScreen stores what StretchDIBits needs in
// window properties to keep the class decoupled.
const BITMAPINFO *bmi =
(const BITMAPINFO *) GetPropA(hwnd, "PlasmaBitmapInfo");
const void *pixels = GetPropA(hwnd, "PlasmaPixels");
RECT client;
GetClientRect(hwnd, &client);
if (bmi != NULL && pixels != NULL)
{
SetStretchBltMode(hdc, COLORONCOLOR);
StretchDIBits(
hdc,
0, 0, client.right, client.bottom,
0, 0, PlasmaScreen::plasmaWidth, PlasmaScreen::plasmaHeight,
pixels, bmi, DIB_RGB_COLORS, SRCCOPY
);
}
EndPaint(hwnd, &ps);
return 0;
}
break;
case WM_CLOSE:
// The glass is part of the cockpit; just hide it.
ShowWindow(hwnd, SW_HIDE);
return 0;
case WM_ERASEBKGND:
return 1;
}
return DefWindowProcA(hwnd, message, wParam, lParam);
}
}
//########################################################################
//############################ PlasmaScreen ##############################
//########################################################################
PlasmaScreen::PlasmaScreen():
Video8BitBuffered(plasmaWidth, plasmaHeight)
{
Check_Pointer(this);
window = NULL;
bitmapInfo = NULL;
scale = 4;
const char *scale_string = getenv("L4PLASMASCALE");
if (scale_string != NULL)
{
int requested = atoi(scale_string);
if (requested >= 1 && requested <= 16)
{
scale = requested;
}
}
//---------------------------------------------------------------
// Build the 8bpp DIB header with the plasma-orange palette:
// index 0 is the dark glass, everything else lights up.
//---------------------------------------------------------------
BITMAPINFOHEADER *header = (BITMAPINFOHEADER *) new char[
sizeof(BITMAPINFOHEADER) + 256 * sizeof(RGBQUAD)];
memset(header, 0, sizeof(BITMAPINFOHEADER));
header->biSize = sizeof(BITMAPINFOHEADER);
header->biWidth = plasmaWidth;
header->biHeight = -plasmaHeight; // top-down, matching PixelMap8
header->biPlanes = 1;
header->biBitCount = 8;
header->biCompression = BI_RGB;
header->biClrUsed = 256;
RGBQUAD *palette = (RGBQUAD *)((char *) header + sizeof(BITMAPINFOHEADER));
for (int i = 0; i < 256; ++i)
{
if (i == 0)
{
palette[i].rgbRed = 24; palette[i].rgbGreen = 10; palette[i].rgbBlue = 4;
}
else
{
palette[i].rgbRed = 255; palette[i].rgbGreen = 144; palette[i].rgbBlue = 32;
}
palette[i].rgbReserved = 0;
}
bitmapInfo = header;
CreateGlassWindow();
Tell("PlasmaScreen: on-screen plasma display, scale x" << scale << "\n");
}
PlasmaScreen::~PlasmaScreen()
{
Check_Pointer(this);
if (window != NULL)
{
RemovePropA((HWND) window, "PlasmaBitmapInfo");
RemovePropA((HWND) window, "PlasmaPixels");
DestroyWindow((HWND) window);
window = NULL;
}
delete [] (char *) bitmapInfo;
bitmapInfo = NULL;
}
Logical
PlasmaScreen::TestInstance() const
{
return valid;
}
void
PlasmaScreen::CreateGlassWindow()
{
HINSTANCE instance = GetModuleHandleA(NULL);
static Logical class_registered = False;
if (!class_registered)
{
class_registered = True;
WNDCLASSA window_class;
memset(&window_class, 0, sizeof(window_class));
window_class.style = CS_HREDRAW | CS_VREDRAW;
window_class.lpfnWndProc = PlasmaWndProc;
window_class.hInstance = instance;
window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
window_class.lpszClassName = plasmaWindowClass;
RegisterClassA(&window_class);
}
RECT bounds;
bounds.left = 0;
bounds.top = 0;
bounds.right = plasmaWidth * scale;
bounds.bottom = plasmaHeight * scale;
AdjustWindowRect(&bounds, WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, FALSE);
int window_w = bounds.right - bounds.left;
int window_h = bounds.bottom - bounds.top;
//---------------------------------------------------------------
// Default position: directly below the main view (the pod mounts
// the glass adjacent to the displays). The main window sits at
// (0,0) with the -res size; clamp to the work area in case the
// main view fills the screen. L4PLASMAPOS=x,y overrides.
//---------------------------------------------------------------
int x = 0;
int y = (int) L4Application::GetScreenHeight();
const char *pos_string = getenv("L4PLASMAPOS");
if (pos_string != NULL)
{
int px = 0, py = 0;
if (sscanf(pos_string, "%d,%d", &px, &py) == 2)
{
x = px;
y = py;
}
}
else
{
RECT work;
if (SystemParametersInfoA(SPI_GETWORKAREA, 0, &work, 0))
{
if (y + window_h > work.bottom)
{
y = work.bottom - window_h;
}
if (y < 0)
{
y = 0;
}
}
}
window = CreateWindowExA(
0,
plasmaWindowClass,
"Plasma Display",
WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
x, y,
window_w,
window_h,
NULL, NULL, instance, NULL
);
if (window != NULL)
{
SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
SetPropA((HWND) window, "PlasmaBitmapInfo", bitmapInfo);
SetPropA((HWND) window, "PlasmaPixels", pixelBuffer->Data.MapPointer);
ShowWindow((HWND) window, SW_SHOWNOACTIVATE);
}
else
{
DEBUG_STREAM << "PlasmaScreen: window creation failed\n" << std::flush;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Called from the gauge renderer's background task, same slot the serial
// plasma used for streaming. Repaint when any line changed.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Logical
PlasmaScreen::Update(Logical forceall)
{
Check(this);
Logical dirty = forceall;
for (int y = 0; y < plasmaHeight; ++y)
{
if (changedLine[y])
{
changedLine[y] = 0;
dirty = True;
}
}
if (dirty && window != NULL)
{
InvalidateRect((HWND) window, NULL, FALSE);
}
return False; // 'done' - nothing left to stream
}
void
PlasmaScreen::WaitForUpdate()
{
Check(this);
}