Skip to content

Fix Windows resize handling under DPI scaling - #684

Open
Dkijas wants to merge 1 commit into
jamespetts:masterfrom
Dkijas:fix/gdi-dpi-resize-23805-extended
Open

Fix Windows resize handling under DPI scaling#684
Dkijas wants to merge 1 commit into
jamespetts:masterfrom
Dkijas:fix/gdi-dpi-resize-23805-extended

Conversation

@Dkijas

@Dkijas Dkijas commented Sep 6, 2026

Copy link
Copy Markdown

Summary

This fixes two independent Windows resize problems on the GDI path:

  1. a resize event could be overwritten before the new size was applied;
  2. under Windows display scaling, WindowSize was produced in logical pixels but consumed as
    physical ones, so the window was repainted at the wrong size after a resize.

They are independent: the second one is invisible at 100 % scaling, the first one is not.

Related to the behaviour reported in forum topic
23805.

Resize event loss

sys_event holds a single pending event, but one message retrieval dispatches every sent message
before it returns the posted one, so WM_SIZE writes the slot and a later message of the same
retrieval overwrites it. Maximizing triggers it because the window grows under the mouse pointer
and Windows follows WM_SIZE with a WM_MOUSEMOVE. Separately, the loading screen stores the
resizes it consumes and flushes them back, so an obsolete size could be applied after the real one.

WM_SIZE now keeps only the newest client size, GetEvents() hands it to the slot as soon as the
slot is free, and queue_event() drops already queued SYSTEM_RESIZE events.

DPI unit mismatch

dr_os_open fills WindowSize from MaxSize, which is scaled up, and WM_PAINT consumes it as
physical client pixels — both as the StretchDIBits destination and to recompute
AllDib->bmiHeader.biHeight. But dr_textur_resize stored the logical size it is passed.

At 100 % scaling the two are the same number and nothing shows. Above it, every repaint after a
resize painted into a rectangle smaller than the client area: with a 3440x1369 client at 150 %,
WindowSize held 2304x912.

Extended-specific implementation

This is not a copy of the Simutrans Standard patch. sys_event.new_window_size is a scr_size
here, so the fields are .w/.h, and since scr_coord_val is sint32 the pending state needs no
cast — Standard's uint16 fields do. The change was derived and measured against this code base.

Validation

Measured on this branch's base, at 150 % and 100 % screen scaling, with a lab-only trace of the
producer/consumer contract and negative controls that remove each half separately:

  • without the DPI correction: 2/2 runs fail, WindowSize holding the logical size
  • without the event-delivery correction: the maximize resize is lost in 1 of 4 runs (it is a race)
  • with the full change at 150 %: 2/2 pass, WindowSize = 3456x1368 for a 3440x1369 client
  • with the full change at 100 %: pass, no regression
  • GDI and SDL2 both build with 0 errors, 385 objects each, and the warning set is unchanged from
    the unpatched baseline

Scope

Only simevent.cc and sys/simsys_w.cc. sys/simsys_w.cc is Windows/GDI only; simevent.cc is
shared by every backend, which is why SDL2 is included above. No savegame format change, no pak
format change, no new dependencies, nothing unrelated.

Two independent problems on the Windows GDI path.

First, a resize can be lost before it is applied. sys_event holds a single
pending event, but one message retrieval dispatches every sent message before
it returns the posted one, so WM_SIZE writes the slot and a later message of
the same retrieval overwrites it. Maximizing triggers it because the window
grows under the mouse pointer and Windows follows WM_SIZE with a WM_MOUSEMOVE.
The loading screen can also replay a resize it stored earlier, applying an
obsolete size after the real one. WM_SIZE now keeps only the newest client
size, GetEvents() hands it to the slot as soon as the slot is free, and
queue_event() drops already queued resizes.

Second, WindowSize mixes units. dr_os_open fills it from MaxSize, which is
scaled up, and WM_PAINT consumes it as physical client pixels both as the
StretchDIBits destination and to recompute the framebuffer height, but
dr_textur_resize stored the logical size it is passed. At 100% scaling the two
are the same number; above it every repaint after a resize painted into a
rectangle smaller than the client area and rewrote biHeight too small. With a
3440x1369 client at 150%, WindowSize held 2304x912.

Reported for Simutrans in forum topic 23805; the same symptom appears here.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant