diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index ef4e7e9ca..918b8419f 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -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); diff --git a/crates/egui-wgpu/Cargo.toml b/crates/egui-wgpu/Cargo.toml index 0b081e134..590905182 100644 --- a/crates/egui-wgpu/Cargo.toml +++ b/crates/egui-wgpu/Cargo.toml @@ -51,7 +51,9 @@ x11 = ["winit?/x11"] ## Thus that usage is guarded against with compiler errors in wgpu. fragile-send-sync-non-atomic-wasm = ["wgpu/fragile-send-sync-non-atomic-wasm"] -## Enables `present_with_transaction` surface flag temporary during window resize on MacOS. +## Enables the macOS live-resize jitter fix, which uses `present_with_transaction`. +## This requires the `wgpu/metal` backend. Disable this feature if you want to use egui-wgpu +## with a different backend (e.g. Vulkan or GL) on macOS without pulling in Metal. macos-window-resize-jitter-fix = ["wgpu/metal"] [dependencies] diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 2da393e49..0730f203b 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -104,6 +104,15 @@ impl Painter { desired_maximum_frame_latency, } = *config; + // Transaction presentation can hold a drawable during AppKit live resize. Keep the + // configured low-latency path normally, but use three Metal drawables while resizing. + #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))] + let desired_maximum_frame_latency = if surface_state.resizing { + Some(desired_maximum_frame_latency.unwrap_or(2).max(2)) + } else { + desired_maximum_frame_latency + }; + let width = surface_state.width; let height = surface_state.height; @@ -406,6 +415,9 @@ impl Painter { return; } + // Set before reconfiguring so macOS live resize uses the temporary latency bump above. + state.resizing = resizing; + // Resizing is a bit tricky on macOS. // It requires enabling ["present_with_transaction"](https://developer.apple.com/documentation/quartzcore/cametallayer/presentswithtransaction) // flag to avoid jittering during the resize. Even though resize jittering on macOS @@ -414,32 +426,22 @@ impl Painter { // See https://github.com/emilk/egui/issues/903 #[cfg(all(target_os = "macos", feature = "macos-window-resize-jitter-fix"))] { - // setPresentsWithTransaction causes hangs when desired_maximum_frame_latency == 1 - let is_low_latency = self - .render_state - .as_ref() - .is_some_and(|rs| rs.surface_config.desired_maximum_frame_latency == Some(1)); - if !is_low_latency { - // SAFETY: The cast is checked with if condition. If the used backend is not metal - // it gracefully fails. - unsafe { - if let Some(hal_surface) = state.surface.as_hal::() { - hal_surface - .render_layer() - .lock() - .setPresentsWithTransaction(resizing); + // SAFETY: `as_hal::()` returns `None` unless this surface is backed by wgpu's + // Metal backend. + unsafe { + if let (Some(render_state), Some(hal_surface)) = ( + self.render_state.as_ref(), + state.surface.as_hal::(), + ) { + hal_surface + .render_layer() + .lock() + .setPresentsWithTransaction(resizing); - Self::configure_surface( - state, - self.render_state.as_ref().unwrap(), - &self.config.surface, - ); - } + Self::configure_surface(state, render_state, &self.config.surface); } } } - - state.resizing = resizing; } pub fn on_window_resized(