Mission loads 72s -> 4s: the wait screen was GPU-syncing every idle frame
The operator challenged the "inevitable ~minute" load (Task Manager showed idle CPU) and was right. Measured chain: - A/B same box/egg: dev 7s, +glass 30s, +BT_DEV_GAUGES 72s+. - Drain census: identical ~560-event make streams; 190 events/s in dev vs a metronomic 7/s in the console shape -- the loop, not the events. - Stack sampling: 4/6 samples inside ExecuteIdle's wait-screen paint -- GDI GetDC/FillRect on the D3DPOOL_DEFAULT offscreen surface forces a full GPU pipeline sync (~140ms/frame at glass window size). The 2026-07-22 flicker fix traded a cosmetic bug for a 25x load throttle, worst on the operator box (bigger window + gauge-window GPU work), with idle CPU because a GPU-sync stall isn't compute. Fix (ExecuteIdle): the staging surface is D3DPOOL_SYSTEMMEM (GetDC is pure CPU; survives Reset for free) uploaded via UpdateSurface, and a change gate skips the whole idle frame unless the spinner phase (12.5Hz) or state text changed -- between paints the loop runs free. Result: console-shape load 72s+ -> 4.1s (BEGIN -> READY), drain at 345 events/s; every pod benefits (same code), and the all-ready launch gate now waits on a seconds-scale slowest loader. Permanent pre-run diagnostics kept: the drain census + >50ms slow-event lines (EVENT.cpp, stand down at scene-live; slow-event identity captured BEFORE Process -- Receiver::Receive(Event*) deletes the event). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
87ef6a85f9
commit
56ca2f5c13
@@ -8936,13 +8936,20 @@ void DPLRenderer::ExecuteIdle()
|
||||
hr = mDevice->EndScene();
|
||||
|
||||
// The waiting screen (join/join_lan UX fix 2026-07-22; FLICKER fix
|
||||
// 2026-07-22): paint the text + spinner INTO the backbuffer via the
|
||||
// surface's GDI handle BEFORE Present, so the presented frame already
|
||||
// carries the overlay -- the old paint-AFTER-Present alternated a black
|
||||
// D3D frame with the GDI redraw and visibly flickered during loads.
|
||||
// GetDC-on-backbuffer failure (unusual formats) falls back to the old
|
||||
// window-DC paint.
|
||||
// 2026-07-22; LOAD-SPEED fix 2026-07-23): paint the text + spinner INTO
|
||||
// the presented frame -- but NEVER via GDI on a DEFAULT-pool surface
|
||||
// per frame: GetDC/FillRect on a GPU surface forces a full pipeline
|
||||
// sync (~140ms/frame on the operator GPU at glass size), which throttled
|
||||
// the pre-run loop to ~7 iterations/s and multiplied every mission load
|
||||
// (stack-sampled: 4/6 samples inside the overlay FillRect/GetDC; drain
|
||||
// census: same 560 events at 190/s dev vs 7/s glass+gauges). Now: the
|
||||
// GDI paint goes to a SYSTEMMEM staging surface (GetDC there is pure
|
||||
// CPU), uploaded with UpdateSurface, and the whole frame is SKIPPED
|
||||
// unless the spinner phase or text changed (12.5 Hz) -- between paints
|
||||
// the loop runs free and the event queue drains at full speed.
|
||||
{
|
||||
const char *wait_line1_probe = ""; // (filled below; declared for the gate)
|
||||
(void)wait_line1_probe;
|
||||
const char *wait_line1 = "WAITING FOR MISSION ASSIGNMENT";
|
||||
const char *wait_line2 = "the operator starts the session from the console";
|
||||
switch (application ? application->GetApplicationState() : -1)
|
||||
@@ -8963,6 +8970,20 @@ void DPLRenderer::ExecuteIdle()
|
||||
// OFFSCREEN PLAIN surface (GetDC always works there) and StretchRect
|
||||
// it onto the backbuffer pre-Present. The cache is D3DPOOL_DEFAULT,
|
||||
// released on device loss below.
|
||||
// CHANGE GATE: repaint/present only when the spinner phase or the
|
||||
// text changes -- otherwise skip the whole idle frame (the last
|
||||
// presented image stays on screen; nothing else draws pre-run).
|
||||
{
|
||||
static unsigned long s_lastPhase = 0xFFFFFFFF;
|
||||
static const char *s_lastLine1 = NULL;
|
||||
unsigned long phase = (GetTickCount() / 80UL) % 12UL;
|
||||
if (phase == s_lastPhase && wait_line1 == s_lastLine1)
|
||||
{
|
||||
return; // nothing changed: free the loop
|
||||
}
|
||||
s_lastPhase = phase;
|
||||
s_lastLine1 = wait_line1;
|
||||
}
|
||||
Logical painted_in_backbuffer = False;
|
||||
IDirect3DSurface9 *backbuffer = NULL;
|
||||
if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO,
|
||||
@@ -8984,8 +9005,10 @@ void DPLRenderer::ExecuteIdle()
|
||||
}
|
||||
if (mWaitOverlaySurface == NULL)
|
||||
{
|
||||
// SYSTEMMEM: GetDC is pure CPU (no GPU sync) and the
|
||||
// surface survives device Reset for free.
|
||||
mDevice->CreateOffscreenPlainSurface(desc.Width,
|
||||
desc.Height, D3DFMT_X8R8G8B8, D3DPOOL_DEFAULT,
|
||||
desc.Height, D3DFMT_X8R8G8B8, D3DPOOL_SYSTEMMEM,
|
||||
&mWaitOverlaySurface, NULL);
|
||||
}
|
||||
HDC surface_dc = NULL;
|
||||
@@ -8996,8 +9019,10 @@ void DPLRenderer::ExecuteIdle()
|
||||
BTWaitScreenPaintDC(surface_dc, (int)desc.Width,
|
||||
(int)desc.Height, wait_line1, wait_line2);
|
||||
mWaitOverlaySurface->ReleaseDC(surface_dc);
|
||||
if (SUCCEEDED(mDevice->StretchRect(mWaitOverlaySurface,
|
||||
NULL, backbuffer, NULL, D3DTEXF_NONE)))
|
||||
// SYSTEMMEM -> DEFAULT is UpdateSurface's job
|
||||
// (StretchRect cannot take a sysmem source).
|
||||
if (SUCCEEDED(mDevice->UpdateSurface(mWaitOverlaySurface,
|
||||
NULL, backbuffer, NULL)))
|
||||
{
|
||||
painted_in_backbuffer = True;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user