The render target is the size that was asked for

A crosshair off-centre on the second race, and underneath it every race
after the first was a different race.

Windowed, BackBufferWidth/Height were left at zero, so D3D sized the back
buffer to the device window's client area at the moment the device was
created. Everything downstream is built from the size we ASKED for
instead - the projection matrix takes its aspect from it, the reticle is
centred on it - so a window that was not exactly that size rendered at
the wrong shape and got rescaled on the way to the viewscreen pane.

-fit decided which window that was, and it decided differently for the
first mission than for the rest. Its borderless full-monitor placement
lived only in SVGA16's cockpit build, which does not run until a mission
starts - just after that mission has built its device. So race one was
set up against a still-bordered client and every race after it against
the borderless monitor. On a 3440x1440 panel that is a 1.778 image drawn
across a 2.389 target, against 1.816 the first time.

That is not a cosmetic difference. The simulation advances on wall-clock
deltas, so frame cost is physics: two render targets that size and scale
differently are two different races from one lobby and one set of
settings. A racing sim does not get to do that.

So: the back buffer is the requested size windowed as well as
full-screen, and -fit takes its shape at startup rather than four
screens later. SVGA16 still applies the same rect when it builds the
cockpit - that call is now a no-op instead of a change, which is the
point. The first lobby also stops being the only one with a title bar.

The reticle keeps its own share of the blame and is fixed on its own
terms, so it cannot drift again if a target ever does move:

- It is measured against the viewport at draw time and rebuilt when that
  changes, rather than baked once in the constructor from the renderer's
  requested size. One GetViewport a frame, no rewrite until it moves.
- The arms are quads, not lines. D3D9 line rasterisation follows the
  diamond-exit rule and is free to differ between drivers on a segment
  running along a pixel boundary, which is how a crosshair loses one
  pair of arms and keeps the other - and full-screen, where both
  dimensions are usually even and both pairs sit on boundaries, how it
  can lose the lot.
- Arm thickness follows the target rather than being one pixel whatever
  the resolution. One pixel is a width the presentation can throw away
  in a downscale, and it was a hairline at 1440 next to the pod's line
  at 480.

The log names the viewport, the requested size and where the crosshair
landed, and says TARGET DISAGREES with both aspects when the first two
do not match - so the next report of this arrives with its own diagnosis.

Window creation cleaned up while in there: it computed a style and then
handed CreateWindowEx a literal WS_OVERLAPPEDWINDOW regardless, so the
full-screen path never got the WS_POPUP it thought it was asking for.
Borderless modes are now born borderless instead of being restyled a
moment after. The requested size also goes through AdjustWindowRect,
because -res is a render size and was being used as the OUTER rectangle
with the chrome taken out of the middle - which is how -res 640 480 came
to present into a 624x441 client and started all of this.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-07 22:19:39 -05:00
co-authored by Claude Opus 5
parent 769407ca24
commit aa294071f9
6 changed files with 357 additions and 62 deletions
+52 -6
View File
@@ -342,12 +342,41 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
}
DWORD wsStyle = WS_OVERLAPPED | WS_SYSMENU;
if (L4Application::GetFullscreen())
wsStyle = WS_POPUP;
hWnd = CreateWindowEx(0, L"MainWndClass", L"RPL4", WS_OVERLAPPEDWINDOW, 0, 0, L4Application::GetScreenWidth(), L4Application::GetScreenHeight(), (HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL);
if (!hWnd)
return FALSE;
//
// The style the window is BORN with, rather than one worked out and
// then thrown away - the old code computed a style and handed
// CreateWindowEx a literal WS_OVERLAPPEDWINDOW regardless.
//
// Windowed keeps the full overlapped set on purpose: the sizing
// border and the maximise box are how the player arranges the
// cockpit, and mfd_layout.cfg remembers where they left it. The
// borderless modes are born borderless instead of being restyled a
// moment later, so there is no framed window on screen first.
//
DWORD wsStyle = WS_OVERLAPPEDWINDOW;
if (L4Application::GetFullscreen() || L4Application::GetFitDisplay())
{
wsStyle = WS_POPUP;
}
//
// -res is a RENDER size, so it is the client area that has to be it.
// Passed straight to CreateWindowEx it sets the OUTER rectangle and
// the chrome comes out of the middle - which is how -res 640 480
// came to present into a 624x441 client.
//
RECT wanted;
wanted.left = 0;
wanted.top = 0;
wanted.right = (LONG) L4Application::GetScreenWidth();
wanted.bottom = (LONG) L4Application::GetScreenHeight();
AdjustWindowRect(&wanted, wsStyle, FALSE);
hWnd = CreateWindowEx(0, L"MainWndClass", L"RPL4", wsStyle, 0, 0,
wanted.right - wanted.left, wanted.bottom - wanted.top,
(HWND)NULL, (HMENU)NULL, hInstance, (LPVOID)NULL);
if (!hWnd)
return FALSE;
ShowWindow(hWnd, nShowCmd);
@@ -364,6 +393,23 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
RPWindowLayout_Register(hWnd, "RPL4", True);
RPWindowLayout_Load();
}
else if (L4Application::GetFitDisplay())
{
//
// -fit has no saved placement to restore, but it does have a
// shape to take, and it has to take it NOW. SVGA16 applies the
// same borderless full-monitor rect when it assembles the
// cockpit, which is after the first mission has already built
// its D3D device against the window as it stands - so the first
// race of a session used to render to a bordered client and
// every race after it to the borderless monitor. Same lobby,
// same settings, different target and different frame cost.
//
// Settle the window here and every mission of the session,
// first included, is set up against the identical client area.
//
L4Application::FitWindowToMonitor(hWnd);
}
//
//-------------------------------------------------------------------------