Gitea #56 (+#50): pin the windowed backbuffer so a device reset cannot silently re-size it

ROOT CAUSE.  mPresentParams.BackBufferWidth/Height were assigned ONLY in the
fullscreen branch (L4VIDEO.cpp:3430-3434); windowed they stayed 0, which tells
D3D9 to derive the backbuffer from the device window's client area at
CreateDevice.  Fine at startup -- but the device-lost recovery path saves and
restores mPresentParams around Reset(), so it faithfully preserved those ZEROS
and Reset re-derived the backbuffer from whatever the client area was at THAT
moment, while gBTCockpitCanvasW/H still described the startup canvas.

The cockpit layout is computed in backbuffer space and clicks are mapped
client -> canvas (L4VB16.cpp BTCockpitMouseDown), so after that divergence panels
are drawn for one geometry and clicks are mapped in another: buttons drift off
their artwork, and at a large enough mismatch nothing is clickable at all -- which
is #50 (SAURON: 'none of my MFDs clickable' while other players' worked).

It needs BOTH a resize/maximise AND a device loss (alt-tab, Steam overlay,
monitor sleep, UAC, RDP, driver hiccup), in that order.  That is why it hits some
players and never the operator, who does not resize the cockpit window -- and it
matches the report exactly: the buttons 'become' misaligned rather than starting
that way.

FIX
 * windowed: record the client rect into BackBufferWidth/Height at CreateDevice.
   That is exactly what D3D would have derived, so startup behaviour is
   unchanged, but the save/restore now preserves a REAL size and Reset can never
   pick a different one.  Deliberately NOT screenWidth/screenHeight -- those are
   the app's -res values (default 800x600) while the cockpit window is sized
   independently, so pinning to them would shrink the backbuffer.
 * BTVerifyCockpitCanvasAfterReset(): called at both device-lost recovery sites;
   compares the real backbuffer against the canvas globals, and if they ever
   disagree it says so and re-syncs instead of drifting silently.

VERIFIED: solo cockpit run unchanged -- '[cockpit] view 900x440 canvas 1452x999',
BT_SHOT still 1452x999, Heat panel renders correctly, no mismatch warning.

NOTE: reverted an earlier scripted edit of this file that had corrupted an
unrelated line -- L4VIDEO.cpp is CRLF and a Python join('\n') introduced a bare
LF inside a string literal.  Use the edit tool on this file, not line surgery.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
This commit is contained in:
arcattack
2026-07-25 09:52:28 -05:00
co-authored by Claude Opus 5
parent 08977ff128
commit 5bbe070e9d
+81
View File
@@ -3432,6 +3432,44 @@ DPLRenderer::DPLRenderer(
mPresentParams.BackBufferWidth = screenWidth;
mPresentParams.BackBufferHeight = screenHeight;
}
else
{
//
// PIN THE WINDOWED BACKBUFFER (Gitea #56, and #50 -- the same bug).
//
// These were left at 0 for the windowed case, which tells D3D9 to derive the
// backbuffer from the device window's client area at CreateDevice time. Fine
// at startup -- but the device-lost recovery path saves and restores
// mPresentParams around Reset(), so it faithfully preserves the ZEROS and
// Reset re-derives the backbuffer from whatever the client area is at THAT
// moment.
//
// Field consequence: a player resizes or maximises the window (harmless by
// itself -- the cockpit layout lives in backbuffer space and the hit-test maps
// client -> canvas), then the device is lost (alt-tab, Steam overlay, monitor
// sleep, UAC prompt, RDP, driver hiccup). On recovery the backbuffer silently
// becomes the NEW client size while gBTCockpitCanvasW/H still describe the
// startup canvas -- so panels are drawn for one geometry and clicks are mapped
// in another. Buttons drift off their artwork, and at a big enough mismatch
// nothing is clickable at all (#50, "none of my MFDs clickable" while other
// players' worked). It needs BOTH steps in that order, which is why the
// operator -- who never resizes the cockpit window -- has never seen it.
//
// Recording the client rect here yields exactly what D3D would have derived,
// so startup behaviour is unchanged, but the save/restore now preserves a REAL
// size and Reset can never silently pick a different one. (Do NOT use
// screenWidth/screenHeight: those are the app's -res values, default 800x600,
// while the cockpit-surround window is sized independently -- pinning to them
// would shrink the backbuffer and break the composite.)
//
RECT rc;
if (hWnd != NULL && GetClientRect(hWnd, &rc)
&& rc.right > rc.left && rc.bottom > rc.top)
{
mPresentParams.BackBufferWidth = (UINT)(rc.right - rc.left);
mPresentParams.BackBufferHeight = (UINT)(rc.bottom - rc.top);
}
}
HRESULT hr;
@@ -8067,6 +8105,45 @@ void
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// BTVerifyCockpitCanvasAfterReset (Gitea #56)
//
// The cockpit-surround layout is computed in BACKBUFFER space and clicks are
// mapped client -> backbuffer through gBTCockpitCanvasW/H (L4VB16.cpp
// BTCockpitMouseDown). With the windowed backbuffer now pinned at CreateDevice
// this should always agree -- so a mismatch here means the surface was re-created
// at a different size behind our back, which is exactly the state where panels
// draw in one geometry and clicks land in another. Say so, and re-sync, rather
// than drift silently for the rest of the session.
//
static void BTVerifyCockpitCanvasAfterReset(LPDIRECT3DDEVICE9 device)
{
extern int gBTGaugeCockpit, gBTCockpitCanvasW, gBTCockpitCanvasH;
if (device == NULL || !gBTGaugeCockpit || gBTCockpitCanvasW <= 0)
{
return;
}
IDirect3DSurface9 *back = NULL;
if (FAILED(device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &back))
|| back == NULL)
{
return;
}
D3DSURFACE_DESC desc;
if (SUCCEEDED(back->GetDesc(&desc))
&& ((int)desc.Width != gBTCockpitCanvasW
|| (int)desc.Height != gBTCockpitCanvasH))
{
DEBUG_STREAM << "[resize] Gitea #56: backbuffer is " << desc.Width << "x"
<< desc.Height << " after Reset but the cockpit canvas is "
<< gBTCockpitCanvasW << "x" << gBTCockpitCanvasH
<< " -- clicks would be mis-mapped; re-syncing" << std::endl;
gBTCockpitCanvasW = (int)desc.Width;
gBTCockpitCanvasH = (int)desc.Height;
}
back->Release();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Execute Method, performs the rendering of one frame
//
@@ -8801,6 +8878,8 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
mPresentParams.BackBufferCount = bbCount;
mPresentParams.BackBufferWidth = bbWidth;
mPresentParams.BackBufferHeight = bbHeight;
BTVerifyCockpitCanvasAfterReset(mDevice); // Gitea #56 guard
}
ticks = HiResNowTicks();
@@ -9079,6 +9158,8 @@ void DPLRenderer::ExecuteIdle()
mPresentParams.BackBufferCount = bbCount;
mPresentParams.BackBufferWidth = bbWidth;
mPresentParams.BackBufferHeight = bbHeight;
BTVerifyCockpitCanvasAfterReset(mDevice); // Gitea #56 guard
}
}
}