Repository navigation
window: drop a worker frame the window has resized past - #786
Merged
Merged
Conversation
Resizing the main window on Windows could hang Copperline and slow the whole host until the process was killed (reported on Win11 with an AMD GPU). The present worker plans each frame on the main thread and presents it later. On Windows the main thread pumps the modal size-move loop while the user drags an edge, so the window changes size under frames still queued for the worker. wgpu picks Vulkan first there, and the driver answers VK_ERROR_OUT_OF_DATE_KHR for a swapchain whose extent no longer matches the window. pixels 0.17.2 retries that acquire in a loop with no bound, reconfiguring from its cached surface size, which only the main thread can update, and only after reclaiming the GPU side. The worker rebuilt the swapchain at the stale size forever while the main thread waited in PresentWorker::reclaim to apply the Resized event: the window stopped responding, and the swapchain churn starved the GPU driver. This is issue #362's loop again, now on a thread the corrective event cannot reach. Single-threaded present never hit it on Windows, since the window can only change size while that same thread pumps messages. Each PresentJob now carries the surface size it was planned for, and the worker compares it with the window's live size before acquiring. A job whose window has moved on is dropped: it was drawn for the old geometry anyway, and the Resized behind the change reconfigures the surface on the main thread for the next frame. On macOS the worker must not call winit, and the Metal layer never reports a resized surface out of date, so it presents as before. The race between that check and the acquire is closed by patching in upstream's bounded retry (parasyte/pixels#462, unreleased): pixels gives up after one reconfigure and returns GetSurface(Outdated). That error is logged at debug as a dropped frame on both present paths; other present failures stay errors. The patch is a revision-pinned [patch.crates-io] in the root, player and fuzz workspaces, allowed in deny.toml, to be removed once a pixels release includes #462. COPPERLINE_THREADED_PRESENT=0 or WGPU_BACKEND=dx12 avoid the hang on builds without this fix.
Contributor
There was a problem hiding this comment.
🔵 Needs a closer look
The GPU-threading fix still needs resize validation on the affected Windows/Vulkan platform.
No open issues 🎉
What changed in this PR
Addresses resize-related presentation hangs by dropping stale worker frames and bounding surface-acquisition retries.
Changes:
- Check live window size before worker presentation.
- Pin the upstream pixels retry fix and log outdated surfaces as dropped frames.
- Add regression tests and update presentation documentation.
| File | Description |
|---|---|
| src/video/window/tests.rs | Tests stale-frame detection and error classification. |
| src/video/window/present.rs | Adds size checks and present-error logging helpers. |
| src/video/window.rs | Records planned size and skips stale worker jobs. |
| fuzz/Cargo.toml | Applies the pixels patch to fuzzing. |
| docs/internals/video.md | Documents resize handling and bounded retries. |
| deny.toml | Allows the upstream pixels Git source. |
| crates/copperline-player/Cargo.toml | Applies the pixels patch to the player. |
| crates/copperline-player/Cargo.lock | Pins the player's patched pixels source. |
| Cargo.toml | Pins the upstream bounded-retry patch. |
| Cargo.lock | Locks the patched pixels source. |
Review effort: Balanced
LinuxJedi
added a commit
that referenced
this pull request
Oct 17, 2026
Forward-merge #786 (drop a present-worker frame the window has resized past), #787 (copperhf refuses OpenDevice on an empty unit with TDERR_BadUnitNum), #788 (--mouse-to-after steers in screenshot pixels, a field at a time), #789 (copperhf marks queued requests NT_MESSAGE so WaitIO waits for them) and #790 (copperhf NUL-terminates the mounter's BSTRs).
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.
Problem
A bug report: on Windows 11 with an AMD GPU, resizing the main window hangs Copperline, and the whole machine is very slow until it recovers. It reproduces reliably.
Since 0.21.0 the present worker plans each frame on the main thread and presents it later. On Windows the main thread pumps the modal size-move loop while the user drags an edge, so the window changes size while frames are still queued for the worker. On Windows wgpu picks Vulkan first, and the driver returns
VK_ERROR_OUT_OF_DATE_KHRwhen the swapchain extent no longer matches the window. pixels 0.17.2 retries that acquire in a loop with no bound, reconfiguring from its cached surface size. Only the main thread can update that size, and only after reclaiming the GPU side. So the worker rebuilt the swapchain at the stale size forever, while the main thread waited inPresentWorker::reclaimto apply theResizedevent. The window stopped responding, and the swapchain churn starved the GPU driver.This is the #362 loop again (fullscreen toggles on X11), now on a thread the corrective event cannot reach. The redraw-time resync from #362 does not cover it, because the size check runs on the main thread and the acquire runs on the worker. Single-threaded present never hit this on Windows: there the window can only change size while that same thread pumps messages.
Fix
PresentJobrecords the surface size it was planned for. Before acquiring, the worker compares that size with the window's live size (worker_surface_current). If the window has changed size, the worker drops the job: it was drawn for the old geometry anyway. TheResizedevent behind the change reconfigures the surface on the main thread, and the next frame is planned for the new size. On macOS the worker must not call winit, and the Metal layer never reports a resized surface as out of date, so it presents as before.GetSurface(Outdated). Both present paths log that at debug level as a dropped frame; other present failures are still errors.[patch.crates-io]in the root,crates/copperline-playerandfuzzworkspaces (the ones that build pixels), with the source allowed indeny.toml. Remove all of them once a pixels release includes Add built-in General MIDI device #462. The lockfile changes only swap pixels' source line.docs/internals/video.mddescribes the worker-side check and the bounded retry.Testing
present_worker_drops_a_frame_the_window_has_resized_pastandan_out_of_date_surface_is_a_dropped_frame_not_an_error.cargo test --release,cargo clippy --all-targetsandcargo fmt --checkare clean on macOS.COPPERLINE_THREADED_PRESENT=0orWGPU_BACKEND=dx12.