1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 14:49:06 -04:00

Update to wgpu 29 (#7990)

* [x] I have followed the instructions in the PR template

This updates wgpu to v29 across the egui crate stack.

There a a few API changes due to the requirement to provide a display
handle up front to properly support GLES on linux. I have done my best
to make the api changes as reasonable as possible, but I don't have all
the greater project context, so lmk if things should be done a bit
differently.

I've also updated glow to 0.17 to make cargo deny happy, there are no
source changes. I'm not sure how you want to land these.

---------

Co-authored-by: lucasmerlin <hi@lucasmerlin.me>
This commit is contained in:
Connor Fitzgerald
2026-03-23 13:21:25 -04:00
committed by GitHub
parent b077cf9102
commit a59e803f25
11 changed files with 374 additions and 166 deletions

View File

@@ -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",
]

View File

@@ -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 }

View File

@@ -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 _,

View File

@@ -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")))
}

View File

@@ -15,13 +15,14 @@ pub(crate) struct WebPainterWgpu {
surface: wgpu::Surface<'static>,
surface_configuration: wgpu::SurfaceConfiguration,
render_state: Option<RenderState>,
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
on_surface_status: Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> SurfaceErrorAction>,
depth_stencil_format: Option<wgpu::TextureFormat>,
depth_texture_view: Option<wgpu::TextureView>,
screen_capture_state: Option<CaptureState>,
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(());
}
};
{

View File

@@ -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<dyn Fn(wgpu::SurfaceError) -> 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<dyn Fn(&wgpu::CurrentSurfaceTexture) -> 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
}),

View File

@@ -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::<u32>();
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::<Vertex>();
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;

View File

@@ -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<dyn WgpuHasDisplayHandle>` 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<dyn WgpuHasDisplayHandle>` suitable for setting on
/// [`wgpu::InstanceDescriptor::display`].
fn clone_for_wgpu(&self) -> Box<dyn wgpu::wgt::WgpuHasDisplayHandle>;
/// Clone this handle into a new `Box<dyn EguiDisplayHandle>`.
fn clone_display_handle(&self) -> Box<dyn EguiDisplayHandle>;
}
impl Clone for Box<dyn EguiDisplayHandle> {
fn clone(&self) -> Self {
// We need to deref here, otherwise this causes infinite recursion stack overflow.
(**self).clone_display_handle()
}
}
impl<T> EguiDisplayHandle for T
where
T: wgpu::rwh::HasDisplayHandle + Clone + std::fmt::Debug + Send + Sync + 'static,
{
fn clone_for_wgpu(&self) -> Box<dyn wgpu::wgt::WgpuHasDisplayHandle> {
Box::new(self.clone())
}
fn clone_display_handle(&self) -> Box<dyn EguiDisplayHandle> {
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<Box<dyn EguiDisplayHandle>>,
/// 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<dyn Fn(&wgpu::Adapter) -> 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`].

View File

@@ -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;

View File

@@ -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,
});

View File

@@ -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(