win32: present what the system invalidated along with the damage - #375
Open
bigtree108-dmytro wants to merge 1 commit into
Open
bigtree108-dmytro wants to merge 1 commit into
bigtree108-dmytro wants to merge 1 commit into
Conversation
When a window is restored from minimized, Windows invalidates its whole client area. The window keeps showing its old contents until it is painted again, and from then on only what was painted since the restore is visible, the rest is black. present_with_damage copied only the damage rectangles and then validated the whole window, so Windows was told the area had been repainted and never asked again. The buffer still holds the last frame there, so read the window's update rectangle and copy it along with the damage before validating. That only works while the request is pending: an application that skips presenting because nothing changed loses the request once the paint message is handled, so the docs now say to present with empty damage in that case. The new damage example presents only a moving square each frame. Restored from minimized four times, its window came back 2.2 to 2.6 % intact before this change and 100 % after it.
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Tested on:
Hi, and thanks for softbuffer!
When a window is restored from minimized, Windows invalidates its whole client area. The window keeps showing its old contents until it is painted again, and from then on only what was painted since the restore is visible; the rest is black. On Windows,
present_with_damagecopies the damage rectangles and then callsValidateRect(hwnd, NULL), which tells Windows that the whole window has been repainted. So an application that presents only what changed ends up with a black window apart from its damage, and Windows never asks again.The buffer still holds the last frame in those areas, which is what an age of 1 promises, so this change reads the window's update rectangle with
GetUpdateRectand copies it along with the damage before validating. When Windows has nothing pending,GetUpdateRectreturns false and presenting works exactly as before. A redraw requested through winit'srequest_redrawdoesn't create an update region either (winit usesRDW_INTERNALPAINT), so the extra copy only happens when the system actually asked for a repaint.This only works while the request is still pending. winit delivers
RedrawRequestedbefore it callsDefWindowProcW, so presenting from that handler, or from anywhere before it, picks the area up. An application that skips presenting because nothing changed loses it once the paint message has been handled, so I added a short Win32 note to thepresent_with_damagedocs: present with empty damage while handling the request.The new
damageexample animates a small square and presents only the two rectangles it touches each frame. I minimized and restored its window four times from a script and counted how much of the window on screen matched the expected image afterwards: 2.2 to 2.6 % on master, 100 % with this change.To check the other cases, I ran a separate test app through the same script with different ways of presenting, on master and on this branch. Restore from minimized, three times each:
RedrawRequestedabout_to_wait(outsideWM_PAINT)The last row is the limit described above. For that case the application needs to know which area was lost, and winit doesn't report that today; I've proposed it in rust-windowing/winit#4719.
In the same runs, maximize and restore, growing and shrinking the window, covering it with another window, and moving it half off screen and back all stayed at 100 % with this change, with no panics. On master they did too, except in the small-buffer run, which never recovered from the first restore because its buffer never changes size and so never gets a full present. The window had no pending update region at any point during three seconds of steady animation, so nothing extra was copied, and none while it was covered. A
GetUpdateRectcall took about 3 µs in a release build, while a present took several milliseconds.The rectangle is clipped to the buffer in a small function with unit tests for the edge cases: inside, exactly the buffer, one pixel in each corner, larger than the buffer, crossing each edge, fully outside on every side, empty, inverted, and extreme coordinates.
Tested on Windows 11. Locally,
cargo fmt,cargo clippy --all-targets -- -Dwarningsforx86_64-pc-windows-msvcandi686-pc-windows-msvc,cargo teston both,cargo doc, and the MSRV check with 1.71.1 and minimal versions forx86_64-pc-windows-msvcandx86_64-pc-windows-gnuall pass. Not tested: moving the window to a screen with another scale factor, since only one screen was connected, and Windows 10.One more note: we ran into this through Slint (slint-ui/slint#13537), which is on softbuffer 0.4. The change applies to 0.4.8 as it is, so if a 0.4.x patch release is something you'd consider, I'm happy to open a backport PR against a release branch.
Related: