From 4d1a736016747a4016e358be6fd08a6c7c962823 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Wed, 24 Jan 2024 09:36:17 +0100 Subject: [PATCH] Add `WgpuConfiguration::desired_maximum_frame_latency` (#3874) Setting `desired_maximum_frame_latency` to a low value should theoretically lead to lower latency in winit apps using `egui-wgpu` (e.g. in `eframe` with `wgpu` backend). * Replaces https://github.com/emilk/egui/pull/3714 * See also https://github.com/gfx-rs/wgpu/pull/4899 ---- It seems like `desired_maximum_frame_latency` has no effect on my Mac. I lowered my monitor refresh-rate to 30Hz to test, and can see no difference between `desired_maximum_frame_latency` of `0` or `3`. Before when experimenting with changing the global `DESIRED_NUM_FRAMES` in `wgpu` I saw a huge difference, so I wonder what has changed. I verified that `set_maximum_drawable_count` is being called with either `1` or `2`, but I perceive no difference between the two. --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- crates/egui-wgpu/src/lib.rs | 29 ++++++++++++++++++++--- crates/egui-wgpu/src/winit.rs | 44 +++++++++++++++++------------------ 4 files changed, 51 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be93056fc..0c85eaa68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4219,9 +4219,9 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc" [[package]] name = "wgpu" -version = "0.19.0" +version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0b71d2ded29e2161db50ab731d6cb42c037bd7ab94864a98fa66ff36b4721a8" +checksum = "0bfe9a310dcf2e6b85f00c46059aaeaf4184caa8e29a1ecd4b7a704c3482332d" dependencies = [ "arrayvec", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 9fa472ba6..67f0806ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,7 +53,7 @@ glow = "0.13" puffin = "0.18" raw-window-handle = "0.6.0" thiserror = "1.0.37" -wgpu = { version = "0.19", features = [ +wgpu = { version = "0.19.1", features = [ # Make the renderer `Sync` even on wasm32, because it makes the code simpler: "fragile-send-sync-non-atomic-wasm", ] } diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 6a43598da..66caf2616 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -228,6 +228,15 @@ pub struct WgpuConfiguration { /// Present mode used for the primary surface. pub present_mode: wgpu::PresentMode, + /// Desired maximum number of frames that the presentation engine should queue in advance. + /// + /// Use `1` for low-latency, and `2` for high-throughput. + /// + /// See [`wgpu::SurfaceConfiguration::desired_maximum_frame_latency`] for details. + /// + /// `None` = `wgpu` default. + pub desired_maximum_frame_latency: Option, + /// Power preference for the adapter. pub power_preference: wgpu::PowerPreference, @@ -237,10 +246,22 @@ pub struct WgpuConfiguration { impl std::fmt::Debug for WgpuConfiguration { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let Self { + supported_backends, + device_descriptor: _, + present_mode, + desired_maximum_frame_latency, + power_preference, + on_surface_error: _, + } = self; f.debug_struct("WgpuConfiguration") - .field("supported_backends", &self.supported_backends) - .field("present_mode", &self.present_mode) - .field("power_preference", &self.power_preference) + .field("supported_backends", &supported_backends) + .field("present_mode", &present_mode) + .field( + "desired_maximum_frame_latency", + &desired_maximum_frame_latency, + ) + .field("power_preference", &power_preference) .finish_non_exhaustive() } } @@ -274,6 +295,8 @@ impl Default for WgpuConfiguration { present_mode: wgpu::PresentMode::AutoVsync, + desired_maximum_frame_latency: None, + power_preference: wgpu::util::power_preference_from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 154606862..ca292212a 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -142,7 +142,7 @@ impl Painter { fn configure_surface( surface_state: &SurfaceState, render_state: &RenderState, - present_mode: wgpu::PresentMode, + config: &WgpuConfiguration, ) { crate::profile_function!(); @@ -155,21 +155,25 @@ impl Painter { let width = surface_state.width; let height = surface_state.height; - surface_state.surface.configure( - &render_state.device, - &wgpu::SurfaceConfiguration { - // TODO(emilk): expose `desired_maximum_frame_latency` to eframe users - usage, - format: render_state.target_format, - present_mode, - alpha_mode: surface_state.alpha_mode, - view_formats: vec![render_state.target_format], - ..surface_state - .surface - .get_default_config(&render_state.adapter, width, height) - .expect("The surface isn't supported by this adapter") - }, - ); + let mut surf_config = wgpu::SurfaceConfiguration { + usage, + format: render_state.target_format, + present_mode: config.present_mode, + alpha_mode: surface_state.alpha_mode, + view_formats: vec![render_state.target_format], + ..surface_state + .surface + .get_default_config(&render_state.adapter, width, height) + .expect("The surface isn't supported by this adapter") + }; + + if let Some(desired_maximum_frame_latency) = config.desired_maximum_frame_latency { + surf_config.desired_maximum_frame_latency = desired_maximum_frame_latency; + } + + surface_state + .surface + .configure(&render_state.device, &surf_config); } /// Updates (or clears) the [`winit::window::Window`] associated with the [`Painter`] @@ -328,7 +332,7 @@ impl Painter { surface_state.width = width; surface_state.height = height; - Self::configure_surface(surface_state, render_state, self.configuration.present_mode); + Self::configure_surface(surface_state, render_state, &self.configuration); if let Some(depth_format) = self.depth_format { self.depth_texture_view.insert( @@ -525,11 +529,7 @@ impl Painter { Ok(frame) => frame, Err(err) => match (*self.configuration.on_surface_error)(err) { SurfaceErrorAction::RecreateSurface => { - Self::configure_surface( - surface_state, - render_state, - self.configuration.present_mode, - ); + Self::configure_surface(surface_state, render_state, &self.configuration); return None; } SurfaceErrorAction::SkipFrame => {