Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 3 additions & 1 deletion crates/egui-wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
48 changes: 25 additions & 23 deletions crates/egui-wgpu/src/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
};

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, smart way to go about that!

let width = surface_state.width;
let height = surface_state.height;

Expand Down Expand Up @@ -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
Expand All @@ -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::<wgpu::hal::api::Metal>() {
hal_surface
.render_layer()
.lock()
.setPresentsWithTransaction(resizing);

Self::configure_surface(
state,
self.render_state.as_ref().unwrap(),
&self.config.surface,
);
}
// SAFETY: `as_hal::<Metal>()` 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::<wgpu::hal::api::Metal>(),
) {
hal_surface
.render_layer()
.lock()
.setPresentsWithTransaction(resizing);

Self::configure_surface(state, render_state, &self.config.surface);
}
}
}

state.resizing = resizing;
}

pub fn on_window_resized(
Expand Down
Loading