Wait screen: hard cutoff at first real scene present (handoff flicker)

The launch handoff (LoadingMission -> RunningMission) could interleave
the idle wait-overlay presents with the first real game frames (user
report: "the wait screen is still there when game graphics come up").
gBTSceneHasPresented latches at the render path's Present; ExecuteIdle
stands down permanently once set -- the overlay can never paint over
(or alternate with) live graphics.  Permanent one-shot/1Hz [waitscreen]
diagnostics record the active paint path, the scene-live moment, and
idle paints.

Verified: 70-frame 0.5s capture across the handoff -- 54 wait frames,
then game frames with at most ONE ambiguous frame at the cut (was
repeated interleaving); no black frames.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-22 16:49:40 -05:00
co-authored by Claude Opus 4.8
parent c421ec4af8
commit 66f22fb923
+28
View File
@@ -8697,6 +8697,14 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
// DIAG (turn-hitch hunt): draw CPU is _rt0..here; Present blocks on the GPU.
LARGE_INTEGER _rt1; QueryPerformanceCounter(&_rt1);
hr = mDevice->Present(NULL, NULL, NULL, NULL);
// The wait-screen overlay stands down PERMANENTLY the moment the first
// REAL scene frame presents (user report 2026-07-22: the launch handoff
// briefly interleaved idle-overlay presents with game frames = flicker).
extern Logical gBTSceneHasPresented;
if (!gBTSceneHasPresented)
DEBUG_STREAM << "[waitscreen] scene LIVE (first real present) t="
<< GetTickCount() << std::endl << std::flush;
gBTSceneHasPresented = True;
{
LARGE_INTEGER _rt2, _rf; QueryPerformanceCounter(&_rt2); QueryPerformanceFrequency(&_rf);
const double drawMs = (double)(_rt1.QuadPart - _rt0.QuadPart) * 1000.0 / (double)_rf.QuadPart;
@@ -8844,10 +8852,30 @@ void BTWaitScreenPaint(const char *line1, const char *line2)
ReleaseDC(wnd, dc);
}
// One-way per-process latch: the real render path has presented a frame.
// ExecuteIdle (the wait-screen overlay) stands down for good once set -- the
// launch handoff must never interleave an idle black-clear/overlay present
// with live game frames.
Logical gBTSceneHasPresented = False;
void DPLRenderer::ExecuteIdle()
{
HRESULT hr;
if (gBTSceneHasPresented)
return; // game graphics own the screen now
{
static unsigned long s_lastIdleLog = 0;
unsigned long now = GetTickCount();
if (now - s_lastIdleLog > 1000UL)
{
s_lastIdleLog = now;
DEBUG_STREAM << "[waitscreen] idle paint t=" << now << " state="
<< (application ? (int)application->GetApplicationState() : -1)
<< std::endl << std::flush;
}
}
hr = mDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0xFF000000, 1.0f, 0);
hr = mDevice->BeginScene();