Fix AppKit live resize redraw timing - #4588
Merged
Merged
Conversation
Contributor
Author
|
@madsmtm Gentle bump - does this approach look acceptable? |
yay
force-pushed
the
steady/macos-live-resize
branch
2 times, most recently
from
June 13, 2026 18:03
e142221 to
3508b03
Compare
Member
tronical
reviewed
Jun 15, 2026
tronical
reviewed
Jun 15, 2026
tronical
reviewed
Jun 15, 2026
tronical
reviewed
Jun 15, 2026
tronical
left a comment
Contributor
There was a problem hiding this comment.
Looks overall good to me. A few questions inside, and I think that unsafe call can be replaced with a clean safe call :)
yay
force-pushed
the
steady/macos-live-resize
branch
2 times, most recently
from
June 24, 2026 12:23
aeb21f1 to
2bafd96
Compare
yay
force-pushed
the
steady/macos-live-resize
branch
from
June 24, 2026 13:23
2bafd96 to
021fb7c
Compare
Contributor
Author
|
Thanks for reviewing! Just addressed all comments. Sorry it took me a while to get back to it, even though I was the one who bumped it. |
tronical
approved these changes
Jun 24, 2026
Contributor
|
This only touches appkit files and that part is green in the CI. the linux failures are unrelated and already fixed on master. So merging this one :) |
lucasmerlin
added a commit
to emilk/egui
that referenced
this pull request
Jun 25, 2026
## Summary This fixes macOS live-resize behavior for the `eframe`/`egui-wgpu` path when using the low-latency wgpu surface configuration. The problem I was seeing is that native window resize can look visibly below the baseline expected from a desktop GUI: stale or stretched frames (manifesting as wobble/jitter), or severe lag while dragging a window edge. The fix has three parts: - use `CAMetalLayer.presentsWithTransaction` during live resize to avoid stale/stretched frames - temporarily use at least `desired_maximum_frame_latency = 2` while live resize is active, so transaction presentation does not stall when the app normally uses `SurfaceConfig::LOW_LATENCY` - treat macOS `WindowEvent::Moved` as part of the live-resize event stream, since resizing from the top or left edge changes the window origin This PR depends on the winit-side AppKit live-resize timing fix in [rust-windowing/winit#4588](rust-windowing/winit#4588) A renderer-only frame-latency change is not enough by itself. The temporary latency bump only solves the drawable starvation caused by combining `presentsWithTransaction` with `SurfaceConfig::LOW_LATENCY`. It does not change when winit emits resize/redraw events, whether redraws are delivered during AppKit's live-resize event-tracking loop, or whether the surface size is derived from the current backing rect. That is why the winit fix is needed first: it makes the windowing layer report the current AppKit backing size and request redraws from the live-resize/display callbacks. egui-wgpu still needs this PR on top because winit does not own the wgpu `Surface` or the underlying `CAMetalLayer` presentation policy. In other words: winit fixes when the windowing layer reports resize/redraw work, while this PR fixes how egui-wgpu presents Metal-backed wgpu frames during that resize. ## Why change the existing feature? The existing `macos-window-resize-jitter-fix` feature addresses one symptom by enabling transaction presentation during resize, but it is not enough for the low-latency wgpu path. In particular, `presentsWithTransaction` and `SurfaceConfig::LOW_LATENCY` interact poorly during AppKit live resize. The old code avoids that by [skipping transaction presentation when latency is `1`](https://github.com/emilk/egui/blob/71c4ff3c337a08bee934f249463d6701bf76b420/crates/egui-wgpu/src/winit.rs#L417), but that means low-latency users get the resize jitter/wobble back. This PR keeps the low-latency path normally, but temporarily bumps frame latency only while live resize is active. That gives the resize path enough drawable slack without changing normal interaction latency. I removed the `macos-window-resize-jitter-fix` feature because this seems like the behavior the macOS wgpu path should have by default, not a separate opt-in. If keeping the feature as a no-op compatibility alias is preferred, I can adjust the PR. ## Validation I created a small demo app that somewhat resembles the layout of my actual app and highlights both horizontal and vertical resize jitter: - a borderless macOS window - a simple toolbar - a scrolling side list - `SurfaceConfig::LOW_LATENCY` The toolbar and list make stale or stretched frames easy to see during native resize. The jitter is visible even on the traffic light buttons. Recordings: ### Before 1: no transaction presentation, low latency Shows jitter/wobble and stale/stretched frames during live resize. https://github.com/user-attachments/assets/2cf4467b-e14c-4f41-8021-0b8c23f41004 ### Before 2: transaction presentation with low latency Shows the other failure mode: live resize can become severely laggy when transaction presentation is used while keeping `SurfaceConfig::LOW_LATENCY`. https://github.com/user-attachments/assets/2f866790-f472-4ede-a3c0-480e8f0f041a ### After: patched egui-wgpu + patched winit, low latency No visible wobble/jitter and no severe live-resize lag. https://github.com/user-attachments/assets/59e46e9f-7906-4b5c-a6c7-1d09eae644cd --------- Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
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.
This fixes macOS live-resize behavior for layer-backed winit views.
While investigating resize artifacts in a downstream app and looking at other AppKit/Metal applications with smooth live resize behavior such as Zed editor, I found that AppKit can update and ask a layer-backed content view to display during live resize through paths that winit was not fully using for surface resize/redraw timing.
The main issue is that during live resize, the view backing size and redraw timing need to stay synchronized with AppKit's resize/display callbacks. Otherwise surface users can render with stale size information or miss redraw opportunities during the event-tracking run loop. In practice this shows up as visible wobble/jitter and stale or stretched frames while the window edge or corner is being dragged.
This PR changes the AppKit view path to:
NSViewLayerContentsRedrawDuringViewResizefor the content viewsetFrameSize:by updating the surface size and requesting redraw during live resizeviewDidChangeBackingPropertiesfor backing-scale/backing-size changesdisplayLayer:as a redraw signal during live resizeconvertRectToBacking(bounds)instead of deriving it from the window/frame sizeValidation
I used a small winit +
CAMetalLayerdemo that:WindowEvent::RedrawRequestedCAMetalLayer.presentsWithTransactionI recorded the same demo before and after the fix:
Before:
before-the-fix.mp4
After:
after-the-fix.mp4
In the before video, live resize shows stale/stretched frames. In the after video, resizing stays synchronized and smooth.
I also validated the same change against winit
0.30.13, since that is the version used by the downstream app where I first reproduced the issue.Downstream validation/follow-up in egui: emilk/egui#8229.
That PR uses this winit change as the windowing-layer part of the fix, then handles the renderer-specific
egui-wgpu/Metal presentation policy separately.