integrate Cyd's glass-panel-perf (adopted over the local dirty-gate draft)
# Conflicts: # engine/MUNGA_L4/L4GLASSWIN.cpp
This commit is contained in:
@@ -129,6 +129,12 @@ struct GWin
|
||||
char monitorName[40]; // the PHYSICAL monitor this window landed on
|
||||
// (\.\DISPLAYn), stamped at creation; shown
|
||||
// by BT_POD_IDENT so the cab can be mapped.
|
||||
|
||||
// Dirty-skip (2026-08-09): the last change token the pump blitted for this
|
||||
// window (plane checksum + lamp render). Zero-init (static storage); the pump
|
||||
// re-blits only when the token differs, so unchanged panels cost nothing.
|
||||
unsigned long lastToken;
|
||||
int haveToken;
|
||||
};
|
||||
|
||||
static GWin gWins[8];
|
||||
@@ -1418,7 +1424,16 @@ static void
|
||||
info.bmiHeader.biCompression = BI_RGB;
|
||||
|
||||
const RECT &r = w->surfaceRect;
|
||||
SetStretchBltMode(dc, HALFTONE);
|
||||
// STRETCH MODE (perf, 2026-08-09): default COLORONCOLOR. HALFTONE runs a
|
||||
// per-output-pixel resample filter; done synchronously for all 7 glass windows
|
||||
// every ~16 Hz repaint (BTGlassPanels_Tick), it was the per-display-mode perf
|
||||
// sink -- playtesters saw ~20 fps in the exploded panels vs ~130 fps in the
|
||||
// cockpit surround (same scene, same machine). The MFDs are low-res pixel
|
||||
// content, so nearest-neighbour reads crisp (and closer to the pod CRT).
|
||||
// BT_GLASS_SMOOTH=1 restores HALFTONE for anyone who prefers smoothing to speed.
|
||||
static int sSmooth = -1;
|
||||
if (sSmooth < 0) sSmooth = getenv("BT_GLASS_SMOOTH") ? 1 : 0;
|
||||
SetStretchBltMode(dc, sSmooth ? HALFTONE : COLORONCOLOR);
|
||||
SetBrushOrgEx(dc, 0, 0, NULL);
|
||||
StretchDIBits(dc,
|
||||
r.left, r.top, r.right - r.left, r.bottom - r.top,
|
||||
@@ -2050,6 +2065,62 @@ void
|
||||
// one-shot re-snap); a focused window just repaints from whichever fires first.
|
||||
//###########################################################################
|
||||
|
||||
//
|
||||
// GLASS DIRTY-SKIP (2026-08-09): re-blit only the windows whose content changed.
|
||||
// Each window is a plane of the ONE shared gauge pixelBuffer, so its change token is
|
||||
// the masked plane checksum (SVGA16::PlaneChecksum over every port that can feed the
|
||||
// window) combined with each button's RENDERED lamp brightness + held/latched state.
|
||||
// Folding the flash BRIGHTNESS in (not the raw lamp state) means a flashing lamp
|
||||
// repaints exactly when it toggles, and a static panel -- or a fully idle cockpit --
|
||||
// skips its expand + StretchDIBits entirely. The plane checksum is the only added
|
||||
// cost (once per window per ~16 Hz pump) and is far cheaper than the paint it saves.
|
||||
//
|
||||
static unsigned long
|
||||
GlassWindowToken(GaugeRenderer *gr, GWin *w, unsigned long tick)
|
||||
{
|
||||
unsigned long token = 2166136261UL;
|
||||
|
||||
if (w->portPrimary != NULL && gr != NULL)
|
||||
{
|
||||
// Every plane that could feed the window (primary + Eng twin + RGB group +
|
||||
// their twins) -- OR their masks so a change to ANY is caught; checksum once.
|
||||
const char *ports[8];
|
||||
int np = 0;
|
||||
ports[np++] = w->portPrimary;
|
||||
if (w->portAlt != NULL) ports[np++] = w->portAlt;
|
||||
for (int gi = 0; gi < w->groupCount && np < 7; ++gi)
|
||||
{
|
||||
ports[np++] = w->groupPort[gi];
|
||||
if (w->groupAlt[gi] != NULL && np < 8) ports[np++] = w->groupAlt[gi];
|
||||
}
|
||||
int combined = 0;
|
||||
SVGA16 *svga = NULL;
|
||||
for (int i = 0; i < np; ++i)
|
||||
{
|
||||
L4GraphicsPort *p =
|
||||
static_cast<L4GraphicsPort*>(gr->GetGraphicsPort(ports[i]));
|
||||
if (p == NULL) continue;
|
||||
combined |= p->GetBitMask();
|
||||
if (svga == NULL) svga = static_cast<SVGA16*>(p->graphicsDisplay);
|
||||
}
|
||||
if (svga != NULL && combined != 0)
|
||||
token ^= svga->PlaneChecksum(combined);
|
||||
}
|
||||
|
||||
// Lamps: each button's RENDERED brightness + held/latched, so a flash toggle or a
|
||||
// press repaints exactly the window it lives on.
|
||||
for (int j = 0; j < w->buttonCount; ++j)
|
||||
{
|
||||
int addr = w->buttons[j].address;
|
||||
unsigned long shade =
|
||||
(unsigned long)LampBrightnessOf(PadRIO::GetLampState(addr), tick);
|
||||
int held = (addr == pressedAddress) || latched[addr & 0x7F];
|
||||
token = (token ^ ((unsigned long)(addr & 0xFF) << 4)
|
||||
^ (shade << 1) ^ (unsigned long)held) * 16777619UL;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
|
||||
void
|
||||
BTGlassPanels_Tick()
|
||||
{
|
||||
@@ -2062,16 +2133,47 @@ void
|
||||
return;
|
||||
sLastPaint = now;
|
||||
|
||||
// MERGED: Cyd's dirty-skip (glass-panel-perf) + the #149 [glassperf]
|
||||
// telemetry. BT_GLASS_SWEEP=1 bypasses the skip entirely -- the legacy
|
||||
// always-repaint escape hatch, kept so the field can A/B in one env var.
|
||||
static const int sGlassPerf =
|
||||
!(getenv("BT_PERF_LOG") && *getenv("BT_PERF_LOG") == '0');
|
||||
static const int sLegacySweep =
|
||||
(getenv("BT_GLASS_SWEEP") && *getenv("BT_GLASS_SWEEP") == '1');
|
||||
LARGE_INTEGER gt0, gt1, gfq;
|
||||
QueryPerformanceCounter(>0);
|
||||
GaugeRenderer *gr = BTResolveGaugeRenderer();
|
||||
int repainted = 0;
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
if (gWins[i].hwnd != NULL)
|
||||
GWin &w = gWins[i];
|
||||
if (w.hwnd == NULL)
|
||||
continue;
|
||||
unsigned long token = GlassWindowToken(gr, &w, now);
|
||||
if (!sLegacySweep && w.haveToken && token == w.lastToken)
|
||||
continue; // gauges + lamps unchanged -> skip this window
|
||||
w.lastToken = token;
|
||||
w.haveToken = 1;
|
||||
++repainted;
|
||||
InvalidateRect(w.hwnd, NULL, FALSE);
|
||||
UpdateWindow(w.hwnd); // synchronous paint, not the throttled queue
|
||||
}
|
||||
|
||||
// BT_GLASS_DIRTY: report how many window-repaints the dirty-skip let through vs
|
||||
// the old fixed gWinCount-per-pump, so the saving is visible.
|
||||
static int sDirtyLog = -1;
|
||||
if (sDirtyLog < 0) sDirtyLog = getenv("BT_GLASS_DIRTY") ? 1 : 0;
|
||||
if (sDirtyLog)
|
||||
{
|
||||
static unsigned long sWin = 0;
|
||||
static int sPumps = 0, sPaints = 0;
|
||||
++sPumps; sPaints += repainted;
|
||||
if (now - sWin >= 2000)
|
||||
{
|
||||
InvalidateRect(gWins[i].hwnd, NULL, FALSE);
|
||||
UpdateWindow(gWins[i].hwnd); // synchronous paint, not the throttled queue
|
||||
DEBUG_STREAM << "[glass-dirty] " << sPumps << " pumps -> " << sPaints
|
||||
<< " window-repaints (was " << (sPumps * gWinCount) << " always-on)\n"
|
||||
<< std::flush;
|
||||
sWin = now; sPumps = 0; sPaints = 0;
|
||||
}
|
||||
}
|
||||
QueryPerformanceCounter(>1);
|
||||
@@ -2083,14 +2185,16 @@ void
|
||||
// against D3D present, tickMs IS the per-frame tax and the per-window
|
||||
// split names the guilty panel. Default ON like [segperf]; BT_PERF_LOG=0
|
||||
// opts out.
|
||||
static double sTickMs = 0.0; static int sTicks = 0;
|
||||
static double sTickMs = 0.0; static int sTicks = 0; static int sPaintAcc = 0;
|
||||
sPaintAcc += repainted;
|
||||
static unsigned long sLastReport = 0;
|
||||
sTickMs += 1000.0 * (double)(gt1.QuadPart - gt0.QuadPart) / (double)gfq.QuadPart;
|
||||
++sTicks;
|
||||
if (sGlassPerf && now - sLastReport >= 1000)
|
||||
{
|
||||
sLastReport = now;
|
||||
DEBUG_STREAM << "[glassperf] ticks=" << sTicks << " tickMs=" << sTickMs;
|
||||
DEBUG_STREAM << "[glassperf] ticks=" << sTicks << " tickMs=" << sTickMs
|
||||
<< " paints=" << sPaintAcc;
|
||||
for (int i = 0; i < gWinCount; ++i)
|
||||
{
|
||||
if (gWins[i].perfN > 0)
|
||||
@@ -2099,6 +2203,6 @@ void
|
||||
gWins[i].perfMs = 0.0; gWins[i].perfN = 0;
|
||||
}
|
||||
DEBUG_STREAM << "\n" << std::flush;
|
||||
sTickMs = 0.0; sTicks = 0;
|
||||
sTickMs = 0.0; sTicks = 0; sPaintAcc = 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,6 +692,28 @@ void SVGA16::DrawDevSurface(LPDIRECT3DDEVICE9 device, int slot, int mask, int pa
|
||||
device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, quad, sizeof(InsetVert));
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// GLASS dirty-skip: FNV-1a over the shared pixelBuffer masked to `mask` -- the bits
|
||||
// one glass window can show. The glass repaint pump compares this per window and
|
||||
// re-blits only the ones whose plane changed (L4GLASSWIN BTGlassPanels_Tick). Full
|
||||
// pass (no stride) so a single-word gauge change is never missed; ~640*480 cheap
|
||||
// integer ops, run at most once per window per ~16 Hz pump.
|
||||
//===========================================================================//
|
||||
unsigned long SVGA16::PlaneChecksum(int mask) const
|
||||
{
|
||||
int w = pixelBuffer.Data.Size.x;
|
||||
int h = pixelBuffer.Data.Size.y;
|
||||
const Word *p = pixelBuffer.Data.MapPointer;
|
||||
if (p == NULL || w <= 0 || h <= 0)
|
||||
return 0;
|
||||
unsigned long sum = 2166136261UL; // FNV-1a offset basis
|
||||
Word m = (Word)mask;
|
||||
int n = w * h;
|
||||
for (int i = 0; i < n; ++i)
|
||||
sum = (sum ^ (unsigned long)(p[i] & m)) * 16777619UL;
|
||||
return sum;
|
||||
}
|
||||
|
||||
//===========================================================================//
|
||||
// GLASS per-display windows -- the CPU (no-D3D) analog of DrawDevSurface: expand
|
||||
// one bit-plane of the shared gauge pixelBuffer into a 32-bit BGRA image that the
|
||||
|
||||
@@ -419,6 +419,12 @@ public:
|
||||
// dwords; the image is written TOP-DOWN. *outW/*outH receive the produced size.
|
||||
void ExpandPlaneToBGRA(int mask, int paletteID, int monoTint, int rotateQuadrant,
|
||||
unsigned long *dst, int *outW, int *outH);
|
||||
|
||||
// GLASS dirty-skip (L4GLASSWIN, 2026-08-09): FNV-1a checksum of the shared
|
||||
// gauge pixelBuffer, masked to the bits a given window can show. Lets the
|
||||
// glass repaint pump re-blit ONLY the windows whose plane actually changed
|
||||
// (idle MFDs / static panels skip; the sweeping radar keeps updating).
|
||||
unsigned long PlaneChecksum(int mask) const;
|
||||
};
|
||||
|
||||
//########################################################################
|
||||
|
||||
Reference in New Issue
Block a user