mirror of
https://github.com/emilk/egui.git
synced 2026-06-26 22:53:14 -04:00
Fix macOS wgpu live resize with low-latency surfaces (#8229)
## 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](https://github.com/rust-windowing/winit/pull/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`](71c4ff3c33/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 commit is contained in:
committed by
GitHub
parent
2e26b70ae9
commit
a8d09eb60d
@@ -844,13 +844,18 @@ impl WgpuWinitRunning<'_> {
|
||||
//
|
||||
// Thus, Painter, responsible for wgpu surfaces and their resize, has to be notified of the
|
||||
// resize lifecycle, yet winit does not provide any events for that. To work around,
|
||||
// the last resized viewport is tracked until any next non-resize event is received.
|
||||
// the last resized viewport is tracked until a later event outside the live resize stream
|
||||
// is received.
|
||||
//
|
||||
// Accidental state change during the resize process due to an unexpected event fire
|
||||
// is ok, state will switch back upon next resize event.
|
||||
// AppKit can emit `Moved` events during top/left live resize because the window origin
|
||||
// changes along with the content size. Treat those as part of live resize on macOS.
|
||||
//
|
||||
// See: https://github.com/emilk/egui/issues/903
|
||||
if let Some(id) = viewport_id
|
||||
let event_keeps_resize_active = matches!(event, winit::event::WindowEvent::Resized(_))
|
||||
|| (cfg!(target_os = "macos") && matches!(event, winit::event::WindowEvent::Moved(_)));
|
||||
|
||||
if !event_keeps_resize_active
|
||||
&& let Some(id) = viewport_id
|
||||
&& shared.resized_viewport == viewport_id
|
||||
{
|
||||
shared.painter.on_window_resize_state_change(id, false);
|
||||
|
||||
Reference in New Issue
Block a user