Fix AppKit live resize redraw timing#4588
Conversation
|
@madsmtm Gentle bump - does this approach look acceptable? |
0f06896 to
e142221
Compare
e142221 to
3508b03
Compare
| unsafe { | ||
| // 2 == NSViewLayerContentsRedrawDuringViewResize. Ask AppKit to redisplay the | ||
| // layer while the view is being resized so layer-backed surfaces keep painting. | ||
| let _: () = msg_send![&*this, setLayerContentsRedrawPolicy: 2isize]; |
There was a problem hiding this comment.
Wouldn't it be possible here to use https://docs.rs/objc2-app-kit/latest/objc2_app_kit/struct.NSView.html#method.setLayerContentsRedrawPolicy ?
| } | ||
| // AppKit updates the content view size here during live resize. Queue a surface | ||
| // resize and redraw before the event-tracking run loop waits again. | ||
| self.surface_resized(); |
There was a problem hiding this comment.
I'm curious as to why this is needed, when setPostsFrameChangedNotifications(true) is active on the view.
| self.redraw_during_live_resize(); | ||
| } | ||
|
|
||
| #[unsafe(method(displayLayer:))] |
There was a problem hiding this comment.
Is there a risk that drawRect isn't called anymore when displayLayer? I have this vague memory, but perhaps this applies only to UIKit.
| unsafe { | ||
| // 2 == NSViewLayerContentsRedrawDuringViewResize. Ask AppKit to redisplay the | ||
| // layer while the view is being resized so layer-backed surfaces keep painting. | ||
| let _: () = msg_send![&*this, setLayerContentsRedrawPolicy: 2isize]; | ||
| } |
There was a problem hiding this comment.
I think you can do this even without unsafe:
| unsafe { | |
| // 2 == NSViewLayerContentsRedrawDuringViewResize. Ask AppKit to redisplay the | |
| // layer while the view is being resized so layer-backed surfaces keep painting. | |
| let _: () = msg_send![&*this, setLayerContentsRedrawPolicy: 2isize]; | |
| } | |
| this.setLayerContentsRedrawPolicy(NSViewLayerContentsRedrawPolicy::DuringViewResize); |
tronical
left a comment
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 :)
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.