diff --git a/Cargo.lock b/Cargo.lock index ec1abe31c..e9694b07f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -536,7 +536,16 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ - "bit-vec", + "bit-vec 0.8.0", +] + +[[package]] +name = "bit-set" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ddef2995421ab6a5c779542c81ee77c115206f4ad9d5a8e05f4ff49716a3dd" +dependencies = [ + "bit-vec 0.9.1", ] [[package]] @@ -545,6 +554,12 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +[[package]] +name = "bit-vec" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71798fca2c1fe1086445a7258a4bc81e6e49dcd24c8d0dd9a1e57395b603f51" + [[package]] name = "bitflags" version = "1.3.2" @@ -560,12 +575,6 @@ dependencies = [ "serde", ] -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - [[package]] name = "block2" version = "0.5.1" @@ -805,9 +814,9 @@ dependencies = [ [[package]] name = "codespan-reporting" -version = "0.12.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe6d2e5af09e8c8ad56c969f2157a3d4238cebc7c55f0a517728c38f7b200f81" +checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681" dependencies = [ "serde", "termcolor", @@ -924,7 +933,7 @@ checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "core-graphics-types 0.1.3", + "core-graphics-types", "foreign-types", "libc", ] @@ -940,17 +949,6 @@ dependencies = [ "libc", ] -[[package]] -name = "core-graphics-types" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" -dependencies = [ - "bitflags 2.9.4", - "core-foundation 0.10.1", - "libc", -] - [[package]] name = "core_maths" version = "0.1.1" @@ -1659,7 +1657,7 @@ version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f" dependencies = [ - "bit-set", + "bit-set 0.8.0", "regex-automata", "regex-syntax", ] @@ -1931,9 +1929,9 @@ dependencies = [ [[package]] name = "glow" -version = "0.16.0" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5e5ea60d70410161c8bf5da3fdfeaa1c72ed2c15f8bbb9d19fe3a4fad085f08" +checksum = "29038e1c483364cc6bb3cf78feee1816002e127c331a1eec55a4d202b9e1adb5" dependencies = [ "js-sys", "slotmap", @@ -2619,15 +2617,6 @@ version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - [[package]] name = "memchr" version = "2.7.4" @@ -2652,21 +2641,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7047791b5bc903b8cd963014b355f71dc9864a9a0b727057676c1dcae5cbc15" -dependencies = [ - "bitflags 2.9.4", - "block", - "core-graphics-types 0.2.0", - "foreign-types", - "log", - "objc", - "paste", -] - [[package]] name = "mimalloc" version = "0.1.48" @@ -2731,12 +2705,12 @@ dependencies = [ [[package]] name = "naga" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "618f667225063219ddfc61251087db8a9aec3c3f0950c916b614e403486f1135" +checksum = "85b4372fed0bd362d646d01b6926df0e837859ccc522fed720c395e0460f29c8" dependencies = [ "arrayvec", - "bit-set", + "bit-set 0.9.1", "bitflags 2.9.4", "cfg-if", "cfg_aliases", @@ -2841,15 +2815,6 @@ dependencies = [ "syn", ] -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - [[package]] name = "objc-sys" version = "0.3.5" @@ -2888,7 +2853,7 @@ dependencies = [ "objc2-core-data", "objc2-core-image", "objc2-foundation 0.2.2", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", ] [[package]] @@ -2974,7 +2939,7 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", ] [[package]] @@ -3054,6 +3019,18 @@ dependencies = [ "objc2-foundation 0.2.2", ] +[[package]] +name = "objc2-metal" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794" +dependencies = [ + "bitflags 2.9.4", + "block2 0.6.2", + "objc2 0.6.3", + "objc2-foundation 0.3.2", +] + [[package]] name = "objc2-quartz-core" version = "0.2.2" @@ -3064,7 +3041,20 @@ dependencies = [ "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", - "objc2-metal", + "objc2-metal 0.2.2", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.9.4", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", ] [[package]] @@ -3092,7 +3082,7 @@ dependencies = [ "objc2-core-location", "objc2-foundation 0.2.2", "objc2-link-presentation", - "objc2-quartz-core", + "objc2-quartz-core 0.2.2", "objc2-symbols", "objc2-uniform-type-identifiers", "objc2-user-notifications", @@ -3232,12 +3222,6 @@ dependencies = [ "windows-link 0.2.1", ] -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - [[package]] name = "pathdiff" version = "0.2.3" @@ -3695,6 +3679,18 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "raw-window-metal" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40d213455a5f1dc59214213c7330e074ddf8114c9a42411eb890c767357ce135" +dependencies = [ + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-quartz-core 0.3.2", +] + [[package]] name = "rayon" version = "1.11.0" @@ -4243,9 +4239,9 @@ dependencies = [ [[package]] name = "spirv" -version = "0.3.0+sdk-1.3.268.0" +version = "0.4.0+sdk-1.4.341.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda41003dc44290527a59b13432d4a0379379fa074b70174882adfbdfd917844" +checksum = "d9571ea910ebd84c86af4b3ed27f9dbdc6ad06f17c5f96146b2b671e2976744f" dependencies = [ "bitflags 2.9.4", ] @@ -5137,9 +5133,9 @@ checksum = "a751b3277700db47d3e574514de2eced5e54dc8a5436a3bf7a0b248b2cee16f3" [[package]] name = "wgpu" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9cb534d5ffd109c7d1135f34cdae29e60eab94855a625dcfe1705f8bc7ad79f" +checksum = "78f9f386699b1fb8b8a05bfe82169b24d151f05702d2905a0bf93bc454fcc825" dependencies = [ "arrayvec", "bitflags 2.9.4", @@ -5167,13 +5163,13 @@ dependencies = [ [[package]] name = "wgpu-core" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bb4c8b5db5f00e56f1f08869d870a0dff7c8bc7ebc01091fec140b0cf0211a9" +checksum = "c7c34181b0acb8f98168f78f8e57ec66f57df5522b39143dbe5f2f45d7ca927c" dependencies = [ "arrayvec", - "bit-set", - "bit-vec", + "bit-set 0.9.1", + "bit-vec 0.9.1", "bitflags 2.9.4", "bytemuck", "cfg_aliases", @@ -5195,61 +5191,61 @@ dependencies = [ "wgpu-core-deps-wasm", "wgpu-core-deps-windows-linux-android", "wgpu-hal", + "wgpu-naga-bridge", "wgpu-types", ] [[package]] name = "wgpu-core-deps-apple" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b7b696b918f337c486bf93142454080a32a37832ba8a31e4f48221890047da" +checksum = "43acd053312501689cd92a01a9638d37f3e41a5fd9534875efa8917ee2d11ac0" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-emscripten" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34b251c331f84feac147de3c4aa3aa45112622a95dd7ee1b74384fa0458dbd79" +checksum = "ef043bf135cc68b6f667c55ff4e345ce2b5924d75bad36a47921b0287ca4b24a" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-wasm" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12a2cf578ce8d7d50d0e63ddc2345c7dcb599f6eb90b888813406ea78b9b7010" +checksum = "2f7b75e72f49035f000dd5262e4126242e92a090a4fd75931ecfe7e60784e6fa" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-core-deps-windows-linux-android" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68ca976e72b2c9964eb243e281f6ce7f14a514e409920920dcda12ae40febaae" +checksum = "725d5c006a8c02967b6d93ef04f6537ec4593313e330cfe86d9d3f946eb90f28" dependencies = [ "wgpu-hal", ] [[package]] name = "wgpu-hal" -version = "28.0.0" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "293080d77fdd14d6b08a67c5487dfddbf874534bb7921526db56a7b75d7e3bef" +checksum = "058b6047337cf323a4f092486443a9337f3d81325347e5d77deed7e563aeaedc" dependencies = [ "android_system_properties", "arrayvec", "ash", - "bit-set", + "bit-set 0.9.1", "bitflags 2.9.4", - "block", + "block2 0.6.2", "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", "glow", "glutin_wgl_sys", "gpu-allocator", @@ -5260,10 +5256,13 @@ dependencies = [ "libc", "libloading", "log", - "metal", "naga", "ndk-sys", - "objc", + "objc2 0.6.3", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "objc2-metal 0.3.2", + "objc2-quartz-core 0.3.2", "once_cell", "ordered-float", "parking_lot", @@ -5272,26 +5271,40 @@ dependencies = [ "profiling", "range-alloc", "raw-window-handle", + "raw-window-metal", "renderdoc-sys", "smallvec", "thiserror 2.0.17", "wasm-bindgen", + "wayland-sys", "web-sys", + "wgpu-naga-bridge", "wgpu-types", "windows", "windows-core 0.62.2", ] [[package]] -name = "wgpu-types" -version = "28.0.0" +name = "wgpu-naga-bridge" +version = "29.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e18308757e594ed2cd27dddbb16a139c42a683819d32a2e0b1b0167552f5840c" +checksum = "d0b8e1e505095f24cb4a578f04b1421d456257dca7fac114d9d9dd3d978c34b8" +dependencies = [ + "naga", + "wgpu-types", +] + +[[package]] +name = "wgpu-types" +version = "29.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15ece45db77dd5451f11c0ce898334317ce8502d304a20454b531fdc0652fae" dependencies = [ "bitflags 2.9.4", "bytemuck", "js-sys", "log", + "raw-window-handle", "web-sys", ] diff --git a/Cargo.toml b/Cargo.toml index a617e84a5..b084399a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ ehttp = { version = "0.7.1", default-features = false } enum-map = "2.7.3" env_logger = { version = "0.11.8", default-features = false } font-types = { version = "0.11.0", default-features = false, features = ["std"] } -glow = "0.16.0" +glow = "0.17.0" glutin = { version = "0.32.3", default-features = false } glutin-winit = { version = "0.5.0", default-features = false } home = "0.5.9" @@ -144,7 +144,7 @@ wayland-cursor = { version = "0.31.11", default-features = false } web-sys = "0.3.77" web-time = "1.1.0" # Timekeeping for native and web webbrowser = "1.0.5" -wgpu = { version = "28.0.0", default-features = false, features = ["std"] } +wgpu = { version = "29.0.0", default-features = false, features = ["std"] } windows-sys = "0.61.2" winit = { version = "0.30.12", default-features = false } diff --git a/crates/eframe/src/native/wgpu_integration.rs b/crates/eframe/src/native/wgpu_integration.rs index 9d6283808..ea96a1845 100644 --- a/crates/eframe/src/native/wgpu_integration.rs +++ b/crates/eframe/src/native/wgpu_integration.rs @@ -184,9 +184,17 @@ impl<'app> WgpuWinitApp<'app> { builder: ViewportBuilder, ) -> crate::Result<&mut WgpuWinitRunning<'app>> { profiling::function_scope!(); + // Inject the display handle into the wgpu setup so that wgpu can create + // surfaces on platforms that require it (e.g. GLES on Wayland). + let mut wgpu_options = self.native_options.wgpu_options.clone(); + if let egui_wgpu::WgpuSetup::CreateNew(ref mut create_new) = wgpu_options.wgpu_setup + && create_new.display_handle.is_none() + { + create_new.display_handle = Some(Box::new(event_loop.owned_display_handle())); + } let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new( egui_ctx.clone(), - self.native_options.wgpu_options.clone(), + wgpu_options, self.native_options.viewport.transparent.unwrap_or(false), egui_wgpu::RendererOptions { msaa_samples: self.native_options.multisampling as _, diff --git a/crates/eframe/src/web/mod.rs b/crates/eframe/src/web/mod.rs index 1e54d7a84..87771f722 100644 --- a/crates/eframe/src/web/mod.rs +++ b/crates/eframe/src/web/mod.rs @@ -38,6 +38,7 @@ mod web_painter_wgpu; pub use backend::*; use egui::Theme; +use js_sys::Object; use wasm_bindgen::prelude::*; use web_sys::{Document, MediaQueryList, Node}; @@ -370,5 +371,5 @@ pub fn percent_decode(s: &str) -> String { /// Are we running inside the Safari browser? pub fn is_safari_browser() -> bool { - web_sys::window().is_some_and(|window| window.has_own_property(&JsValue::from("safari"))) + web_sys::window().is_some_and(|window| Object::has_own(&window, &JsValue::from("safari"))) } diff --git a/crates/eframe/src/web/web_painter_wgpu.rs b/crates/eframe/src/web/web_painter_wgpu.rs index f7adb8fbb..ebce9d981 100644 --- a/crates/eframe/src/web/web_painter_wgpu.rs +++ b/crates/eframe/src/web/web_painter_wgpu.rs @@ -15,13 +15,14 @@ pub(crate) struct WebPainterWgpu { surface: wgpu::Surface<'static>, surface_configuration: wgpu::SurfaceConfiguration, render_state: Option, - on_surface_error: Arc SurfaceErrorAction>, + on_surface_status: Arc SurfaceErrorAction>, depth_stencil_format: Option, depth_texture_view: Option, screen_capture_state: Option, capture_tx: CaptureSender, capture_rx: CaptureReceiver, ctx: egui::Context, + needs_reconfigure: bool, } impl WebPainterWgpu { @@ -105,11 +106,12 @@ impl WebPainterWgpu { surface_configuration, depth_stencil_format, depth_texture_view: None, - on_surface_error: Arc::clone(&options.wgpu_options.on_surface_error) as _, + on_surface_status: Arc::clone(&options.wgpu_options.on_surface_status) as _, screen_capture_state: None, capture_tx, capture_rx, ctx, + needs_reconfigure: false, }) } } @@ -195,18 +197,28 @@ impl WebPainter for WebPainterWgpu { ); } + if self.needs_reconfigure { + self.surface + .configure(&render_state.device, &self.surface_configuration); + self.needs_reconfigure = false; + } + let output_frame = match self.surface.get_current_texture() { - Ok(frame) => frame, - Err(err) => match (*self.on_surface_error)(err) { - SurfaceErrorAction::RecreateSurface => { - self.surface - .configure(&render_state.device, &self.surface_configuration); - return Ok(()); + wgpu::CurrentSurfaceTexture::Success(frame) => frame, + wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { + self.needs_reconfigure = true; + frame + } + other => { + match (*self.on_surface_status)(&other) { + SurfaceErrorAction::RecreateSurface => { + self.surface + .configure(&render_state.device, &self.surface_configuration); + } + SurfaceErrorAction::SkipFrame => {} } - SurfaceErrorAction::SkipFrame => { - return Ok(()); - } - }, + return Ok(()); + } }; { diff --git a/crates/egui-wgpu/src/lib.rs b/crates/egui-wgpu/src/lib.rs index 46becf8f7..05936d6cd 100644 --- a/crates/egui-wgpu/src/lib.rs +++ b/crates/egui-wgpu/src/lib.rs @@ -24,7 +24,10 @@ mod renderer; mod setup; pub use renderer::*; -pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting}; +pub use setup::{ + EguiDisplayHandle, NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, + WgpuSetupExisting, +}; /// Helpers for capturing screenshots of the UI. #[cfg(feature = "capture")] @@ -191,6 +194,7 @@ impl RenderState { let (adapter, device, queue) = match config.wgpu_setup.clone() { WgpuSetup::CreateNew(WgpuSetupCreateNew { instance_descriptor: _, + display_handle: _, power_preference, native_adapter_selector: _native_adapter_selector, device_descriptor, @@ -272,7 +276,7 @@ fn describe_adapters(adapters: &[wgpu::Adapter]) -> String { } } -/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`] +/// Specifies which action should be taken as consequence of a surface error. pub enum SurfaceErrorAction { /// Do nothing and skip the current frame. SkipFrame, @@ -299,8 +303,15 @@ pub struct WgpuConfiguration { /// How to create the wgpu adapter & device pub wgpu_setup: WgpuSetup, - /// Callback for surface errors. - pub on_surface_error: Arc SurfaceErrorAction + Send + Sync>, + /// Callback for surface status changes. + /// + /// Called with the [`wgpu::CurrentSurfaceTexture`] result whenever acquiring a frame + /// does not return [`wgpu::CurrentSurfaceTexture::Success`]. For + /// [`wgpu::CurrentSurfaceTexture::Suboptimal`], egui uses the frame as-is and + /// defers surface reconfiguration to the next frame — the callback is not invoked + /// in that case either. + pub on_surface_status: + Arc SurfaceErrorAction + Send + Sync>, } #[test] @@ -315,7 +326,7 @@ impl std::fmt::Debug for WgpuConfiguration { present_mode, desired_maximum_frame_latency, wgpu_setup, - on_surface_error: _, + on_surface_status: _, } = self; f.debug_struct("WgpuConfiguration") .field("present_mode", &present_mode) @@ -333,14 +344,16 @@ impl Default for WgpuConfiguration { Self { present_mode: wgpu::PresentMode::AutoVsync, desired_maximum_frame_latency: None, - wgpu_setup: Default::default(), - on_surface_error: Arc::new(|err| { - if err == wgpu::SurfaceError::Outdated { + // No display handle available at this point — callers should replace this with + // `WgpuSetup::from_display_handle(...)` before creating the instance if one is available. + wgpu_setup: WgpuSetup::without_display_handle(), + on_surface_status: Arc::new(|status| { + if matches!(status, wgpu::CurrentSurfaceTexture::Outdated) { // This error occurs when the app is minimized on Windows. // Silently return here to prevent spamming the console with: // "The underlying surface has changed, and therefore the swap chain must be updated" } else { - log::warn!("Dropped frame with error: {err}"); + log::warn!("Dropped frame with error: {status:?}"); } SurfaceErrorAction::SkipFrame }), diff --git a/crates/egui-wgpu/src/renderer.rs b/crates/egui-wgpu/src/renderer.rs index c37802448..3222a5521 100644 --- a/crates/egui-wgpu/src/renderer.rs +++ b/crates/egui-wgpu/src/renderer.rs @@ -352,7 +352,10 @@ impl Renderer { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("egui_pipeline_layout"), - bind_group_layouts: &[&uniform_bind_group_layout, &texture_bind_group_layout], + bind_group_layouts: &[ + Some(&uniform_bind_group_layout), + Some(&texture_bind_group_layout), + ], immediate_size: 0, }); @@ -360,8 +363,8 @@ impl Renderer { .depth_stencil_format .map(|format| wgpu::DepthStencilState { format, - depth_write_enabled: false, - depth_compare: wgpu::CompareFunction::Always, + depth_write_enabled: Some(false), + depth_compare: Some(wgpu::CompareFunction::Always), stencil: wgpu::StencilState::default(), bias: wgpu::DepthBiasState::default(), }); @@ -968,7 +971,8 @@ impl Renderer { Primitive::Mesh(mesh) => { let size = mesh.indices.len() * std::mem::size_of::(); let slice = index_offset..(size + index_offset); - index_buffer_staging[slice.clone()] + index_buffer_staging + .slice(slice.clone()) .copy_from_slice(bytemuck::cast_slice(&mesh.indices)); self.index_buffer.slices.push(slice); index_offset += size; @@ -1011,7 +1015,8 @@ impl Renderer { Primitive::Mesh(mesh) => { let size = mesh.vertices.len() * std::mem::size_of::(); let slice = vertex_offset..(size + vertex_offset); - vertex_buffer_staging[slice.clone()] + vertex_buffer_staging + .slice(slice.clone()) .copy_from_slice(bytemuck::cast_slice(&mesh.vertices)); self.vertex_buffer.slices.push(slice); vertex_offset += size; diff --git a/crates/egui-wgpu/src/setup.rs b/crates/egui-wgpu/src/setup.rs index 0c3cb8c39..9d83d4380 100644 --- a/crates/egui-wgpu/src/setup.rs +++ b/crates/egui-wgpu/src/setup.rs @@ -1,5 +1,48 @@ use std::sync::Arc; +/// A cloneable display handle for use with [`wgpu::InstanceDescriptor`]. +/// +/// This trait exists so that a [`winit::event_loop::OwnedDisplayHandle`] (or similar platform +/// display handle) can be stored, cloned, and later passed to wgpu. +/// +/// wgpu requires an explicit display handle for GLES on some platforms (notably Wayland). +/// Because [`wgpu::InstanceDescriptor`] contains a `Box` which is +/// not cloneable, we wrap the handle in this trait so it can be cloned alongside the rest of +/// the egui wgpu configuration. +/// +/// This is automatically implemented for all types that satisfy the bounds (including +/// [`winit::event_loop::OwnedDisplayHandle`]). +pub trait EguiDisplayHandle: + wgpu::rwh::HasDisplayHandle + std::fmt::Debug + Send + Sync + 'static +{ + /// Clone this handle into a `Box` suitable for setting on + /// [`wgpu::InstanceDescriptor::display`]. + fn clone_for_wgpu(&self) -> Box; + + /// Clone this handle into a new `Box`. + fn clone_display_handle(&self) -> Box; +} + +impl Clone for Box { + fn clone(&self) -> Self { + // We need to deref here, otherwise this causes infinite recursion stack overflow. + (**self).clone_display_handle() + } +} + +impl EguiDisplayHandle for T +where + T: wgpu::rwh::HasDisplayHandle + Clone + std::fmt::Debug + Send + Sync + 'static, +{ + fn clone_for_wgpu(&self) -> Box { + Box::new(self.clone()) + } + + fn clone_display_handle(&self) -> Box { + Box::new(self.clone()) + } +} + #[derive(Clone)] pub enum WgpuSetup { /// Construct a wgpu setup using some predefined settings & heuristics. @@ -22,9 +65,32 @@ pub enum WgpuSetup { Existing(WgpuSetupExisting), } -impl Default for WgpuSetup { - fn default() -> Self { - Self::CreateNew(WgpuSetupCreateNew::default()) +impl WgpuSetup { + /// Creates a new [`WgpuSetup::CreateNew`] with the given display handle. + /// + /// This is the recommended constructor. Most platforms (Windows, macOS/iOS, Android, web) + /// work fine without a display handle, but some (e.g. Wayland on Linux with GLES) require + /// one. Providing it unconditionally ensures your app works everywhere. + /// + /// If you don't have a display handle available, use [`Self::without_display_handle`] + /// instead — it will still work on the majority of platforms. + /// + /// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). + pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self { + Self::CreateNew(WgpuSetupCreateNew::from_display_handle(display_handle)) + } + + /// Creates a new [`WgpuSetup::CreateNew`] without a display handle. + /// + /// A display handle is not required for headless operation (offscreen rendering, tests, + /// compute-only workloads). It also isn't needed on most platforms even when presenting + /// to a window — only some configurations (e.g. Wayland on Linux with GLES) require one. + /// + /// If you do have a display handle available, prefer [`Self::from_display_handle`] for + /// maximum compatibility. With winit you can obtain one via + /// [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). + pub fn without_display_handle() -> Self { + Self::CreateNew(WgpuSetupCreateNew::without_display_handle()) } } @@ -65,8 +131,18 @@ impl WgpuSetup { } log::debug!("Creating wgpu instance with backends {backends:?}"); - wgpu::util::new_instance_with_webgpu_detection(&create_new.instance_descriptor) - .await + let desc = &create_new.instance_descriptor; + let descriptor = wgpu::InstanceDescriptor { + backends: desc.backends, + flags: desc.flags, + backend_options: desc.backend_options.clone(), + memory_budget_thresholds: desc.memory_budget_thresholds, + display: create_new + .display_handle + .as_ref() + .map(|handle| handle.clone_for_wgpu()), + }; + wgpu::util::new_instance_with_webgpu_detection(descriptor).await } Self::Existing(existing) => existing.instance.clone(), } @@ -98,9 +174,28 @@ pub type NativeAdapterSelectorMethod = Arc< /// Configuration for creating a new wgpu setup. /// /// Used for [`WgpuSetup::CreateNew`]. +/// +/// Use [`Self::from_display_handle`] when you have a display handle available — this is the +/// recommended constructor. With winit you can obtain one via +/// [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). +/// Most platforms (Windows, macOS/iOS, Android, web) work fine without one, but some +/// (e.g. Wayland on Linux with GLES) require it. Providing it unconditionally ensures your +/// app works everywhere. +/// +/// If you don't have a display handle, use [`Self::without_display_handle`] — it will still +/// work on the majority of platforms, and is appropriate for headless rendering, tests, or +/// web targets. +/// +/// Note: The [`wgpu::InstanceDescriptor::display`] field is always stored as `None` in +/// [`Self::instance_descriptor`]. The display handle is stored separately so it can be cloned +/// (since [`wgpu::InstanceDescriptor`] itself does not implement `Clone`), and is injected +/// into the descriptor at instance creation time. pub struct WgpuSetupCreateNew { /// Instance descriptor for creating a wgpu instance. /// + /// The [`wgpu::InstanceDescriptor::display`] field should be left as `None`; use the + /// [`Self::display_handle`] field instead (it will be injected when the instance is created). + /// /// The most important field is [`wgpu::InstanceDescriptor::backends`], which /// controls which backends are supported (wgpu will pick one of these). /// If you only want to support WebGL (and not WebGPU), @@ -110,6 +205,16 @@ pub struct WgpuSetupCreateNew { /// and only if you have enabled the `webgl` feature of crate `wgpu`. pub instance_descriptor: wgpu::InstanceDescriptor, + /// The display handle to pass to wgpu when creating the instance. + /// + /// Most platforms (Windows, macOS/iOS, Android, web) work without this, but some + /// (e.g. Wayland on Linux with GLES) require it. If you have a display handle + /// available, providing it ensures maximum compatibility. + /// + /// When using winit, this is typically the + /// [`winit::event_loop::OwnedDisplayHandle`] obtained from the event loop. + pub display_handle: Option>, + /// Power preference for the adapter if [`Self::native_adapter_selector`] is not set or targeting web. pub power_preference: wgpu::PowerPreference, @@ -128,32 +233,34 @@ pub struct WgpuSetupCreateNew { Arc wgpu::DeviceDescriptor<'static> + Send + Sync>, } -impl Clone for WgpuSetupCreateNew { - fn clone(&self) -> Self { +impl WgpuSetupCreateNew { + /// Creates a new configuration with the given display handle. + /// + /// This is the recommended constructor. Most platforms (Windows, macOS/iOS, Android, web) + /// work fine without a display handle, but some (e.g. Wayland on Linux with GLES) require + /// one. Providing it unconditionally ensures your app works everywhere. + /// + /// If you don't have a display handle available, use [`Self::without_display_handle`] + /// instead — it will still work on the majority of platforms. + /// + /// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). + pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self { Self { - instance_descriptor: self.instance_descriptor.clone(), - power_preference: self.power_preference, - native_adapter_selector: self.native_adapter_selector.clone(), - device_descriptor: Arc::clone(&self.device_descriptor), + display_handle: Some(Box::new(display_handle)), + ..Self::without_display_handle() } } -} -impl std::fmt::Debug for WgpuSetupCreateNew { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("WgpuSetupCreateNew") - .field("instance_descriptor", &self.instance_descriptor) - .field("power_preference", &self.power_preference) - .field( - "native_adapter_selector", - &self.native_adapter_selector.is_some(), - ) - .finish() - } -} - -impl Default for WgpuSetupCreateNew { - fn default() -> Self { + /// Creates a new configuration without a display handle. + /// + /// A display handle is not required for headless operation (offscreen rendering, tests, + /// compute-only workloads). It also isn't needed on most platforms even when presenting + /// to a window — only some configurations (e.g. Wayland on Linux with GLES) require one. + /// + /// If you do have a display handle available, prefer [`Self::from_display_handle`] for + /// maximum compatibility. With winit you can obtain one via + /// [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle). + pub fn without_display_handle() -> Self { Self { instance_descriptor: wgpu::InstanceDescriptor { // Add GL backend, primarily because WebGPU is not stable enough yet. @@ -163,8 +270,11 @@ impl Default for WgpuSetupCreateNew { flags: wgpu::InstanceFlags::from_build_config().with_env(), backend_options: wgpu::BackendOptions::from_env_or_default(), memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(), + display: None, }, + display_handle: None, + power_preference: wgpu::PowerPreference::from_env() .unwrap_or(wgpu::PowerPreference::HighPerformance), @@ -192,6 +302,39 @@ impl Default for WgpuSetupCreateNew { } } +impl Clone for WgpuSetupCreateNew { + fn clone(&self) -> Self { + let desc = &self.instance_descriptor; + Self { + instance_descriptor: wgpu::InstanceDescriptor { + backends: desc.backends, + flags: desc.flags, + backend_options: desc.backend_options.clone(), + memory_budget_thresholds: desc.memory_budget_thresholds, + display: None, + }, + display_handle: self.display_handle.clone(), + power_preference: self.power_preference, + native_adapter_selector: self.native_adapter_selector.clone(), + device_descriptor: Arc::clone(&self.device_descriptor), + } + } +} + +impl std::fmt::Debug for WgpuSetupCreateNew { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WgpuSetupCreateNew") + .field("instance_descriptor", &self.instance_descriptor) + .field("display_handle", &self.display_handle) + .field("power_preference", &self.power_preference) + .field( + "native_adapter_selector", + &self.native_adapter_selector.is_some(), + ) + .finish() + } +} + /// Configuration for using an existing wgpu setup. /// /// Used for [`WgpuSetup::Existing`]. diff --git a/crates/egui-wgpu/src/winit.rs b/crates/egui-wgpu/src/winit.rs index 5fb8d123a..3f6adfc27 100644 --- a/crates/egui-wgpu/src/winit.rs +++ b/crates/egui-wgpu/src/winit.rs @@ -17,6 +17,7 @@ struct SurfaceState { width: u32, height: u32, resizing: bool, + needs_reconfigure: bool, } /// Everything you need to paint egui with [`wgpu`] on [`winit`]. @@ -234,6 +235,7 @@ impl Painter { height: size.height, alpha_mode, resizing: false, + needs_reconfigure: false, }, ); let Some(width) = NonZeroU32::new(size.width) else { @@ -368,7 +370,7 @@ impl Painter { hal_surface .render_layer() .lock() - .set_presents_with_transaction(resizing); + .setPresentsWithTransaction(resizing); Self::configure_surface( state, @@ -454,7 +456,7 @@ impl Painter { commands_submitted: false, }; - let Some(surface_state) = self.surfaces.get(&viewport_id) else { + let Some(surface_state) = self.surfaces.get_mut(&viewport_id) else { return vsync_sec; }; @@ -491,6 +493,11 @@ impl Painter { ) }; + if surface_state.needs_reconfigure { + Self::configure_surface(surface_state, render_state, &self.configuration); + surface_state.needs_reconfigure = false; + } + let output_frame = { profiling::scope!("get_current_texture"); // This is what vsync-waiting happens on my Mac. @@ -501,16 +508,20 @@ impl Painter { }; let output_frame = match output_frame { - Ok(frame) => frame, - Err(err) => match (*self.configuration.on_surface_error)(err) { - SurfaceErrorAction::RecreateSurface => { - Self::configure_surface(surface_state, render_state, &self.configuration); - return vsync_sec; + wgpu::CurrentSurfaceTexture::Success(frame) => frame, + wgpu::CurrentSurfaceTexture::Suboptimal(frame) => { + surface_state.needs_reconfigure = true; + frame + } + other => { + match (*self.configuration.on_surface_status)(&other) { + SurfaceErrorAction::RecreateSurface => { + Self::configure_surface(surface_state, render_state, &self.configuration); + } + SurfaceErrorAction::SkipFrame => {} } - SurfaceErrorAction::SkipFrame => { - return vsync_sec; - } - }, + return vsync_sec; + } }; let mut capture_buffer = None; diff --git a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs index fd1d9ae73..d83f000a4 100644 --- a/crates/egui_demo_app/src/apps/custom3d_wgpu.rs +++ b/crates/egui_demo_app/src/apps/custom3d_wgpu.rs @@ -40,7 +40,7 @@ impl Custom3d { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("custom3d"), - bind_group_layouts: &[&bind_group_layout], + bind_group_layouts: &[Some(&bind_group_layout)], immediate_size: 0, }); diff --git a/crates/egui_kittest/src/wgpu.rs b/crates/egui_kittest/src/wgpu.rs index 3f97e0036..a9f0de9ad 100644 --- a/crates/egui_kittest/src/wgpu.rs +++ b/crates/egui_kittest/src/wgpu.rs @@ -17,7 +17,8 @@ pub(crate) const WAIT_TIMEOUT: Duration = Duration::from_secs(10); /// Default wgpu setup used for the wgpu renderer. pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup { - let mut setup = egui_wgpu::WgpuSetupCreateNew::default(); + // No display handle needed for headless testing — we don't present to a window. + let mut setup = egui_wgpu::WgpuSetupCreateNew::without_display_handle(); // WebGPU not supported yet since we rely on blocking screenshots. setup @@ -58,6 +59,7 @@ pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup { } pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState { + // No display handle needed for headless testing — we don't present to a window. let instance = pollster::block_on(setup.new_instance()); pollster::block_on(egui_wgpu::RenderState::create(