egui_wgpu/setup.rs
1use std::sync::Arc;
2
3/// A cloneable display handle for use with [`wgpu::InstanceDescriptor`].
4///
5/// [`wgpu::InstanceDescriptor`] stores its display handle as a non-cloneable
6/// `Box<dyn WgpuHasDisplayHandle>`. This trait wraps it so it can be cloned
7/// alongside the rest of the egui wgpu configuration.
8///
9/// Automatically implemented for all types that satisfy the bounds
10/// (including [`winit::event_loop::OwnedDisplayHandle`]).
11pub trait EguiDisplayHandle:
12 wgpu::rwh::HasDisplayHandle + core::fmt::Debug + Send + Sync + 'static
13{
14 /// Clone into a `Box<dyn WgpuHasDisplayHandle>` for [`wgpu::InstanceDescriptor::display`].
15 fn clone_for_wgpu(&self) -> Box<dyn wgpu::wgt::WgpuHasDisplayHandle>;
16
17 /// Clone into a new `Box<dyn EguiDisplayHandle>`.
18 fn clone_display_handle(&self) -> Box<dyn EguiDisplayHandle>;
19}
20
21impl Clone for Box<dyn EguiDisplayHandle> {
22 fn clone(&self) -> Self {
23 // We need to deref here, otherwise this causes infinite recursion stack overflow.
24 (**self).clone_display_handle()
25 }
26}
27
28impl<T> EguiDisplayHandle for T
29where
30 T: wgpu::rwh::HasDisplayHandle + Clone + core::fmt::Debug + Send + Sync + 'static,
31{
32 fn clone_for_wgpu(&self) -> Box<dyn wgpu::wgt::WgpuHasDisplayHandle> {
33 Box::new(self.clone())
34 }
35
36 fn clone_display_handle(&self) -> Box<dyn EguiDisplayHandle> {
37 Box::new(self.clone())
38 }
39}
40
41#[derive(Clone)]
42#[expect(clippy::large_enum_variant)]
43pub enum WgpuSetup {
44 /// Construct a wgpu setup using some predefined settings & heuristics.
45 /// This is the default option. You can customize most behaviours overriding the
46 /// supported backends, power preferences, and device description.
47 ///
48 /// By default can also be configured with various environment variables:
49 /// * `WGPU_BACKEND`: `vulkan`, `dx12`, `metal`, `opengl`, `webgpu`
50 /// * `WGPU_POWER_PREF`: `low`, `high` or `none`
51 /// * `WGPU_TRACE`: Path to a file to output a wgpu trace file.
52 ///
53 /// Each instance flag also comes with an environment variable (for details see [`wgpu::InstanceFlags`]):
54 /// * `WGPU_VALIDATION`: Enables validation (enabled by default in debug builds).
55 /// * `WGPU_DEBUG`: Generate debug information in shaders and objects (enabled by default in debug builds).
56 /// * `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER`: Whether wgpu should expose adapters that run on top of non-compliant adapters.
57 /// * `WGPU_GPU_BASED_VALIDATION`: Enable GPU-based validation.
58 CreateNew(WgpuSetupCreateNew),
59
60 /// Run on an existing wgpu setup.
61 Existing(WgpuSetupExisting),
62}
63
64impl WgpuSetup {
65 /// Creates a new [`WgpuSetup::CreateNew`] with the given display handle.
66 ///
67 /// See [`WgpuSetupCreateNew::from_display_handle`] for details.
68 pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self {
69 Self::CreateNew(WgpuSetupCreateNew::from_display_handle(display_handle))
70 }
71
72 /// Creates a new [`WgpuSetup::CreateNew`] without a display handle.
73 ///
74 /// See [`WgpuSetupCreateNew::without_display_handle`] for details.
75 pub fn without_display_handle() -> Self {
76 Self::CreateNew(WgpuSetupCreateNew::without_display_handle())
77 }
78}
79
80impl core::fmt::Debug for WgpuSetup {
81 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
82 match self {
83 Self::CreateNew(create_new) => f
84 .debug_tuple("WgpuSetup::CreateNew")
85 .field(create_new)
86 .finish(),
87 Self::Existing { .. } => f.debug_tuple("WgpuSetup::Existing").finish(),
88 }
89 }
90}
91
92impl WgpuSetup {
93 /// Creates a new [`wgpu::Instance`] or clones the existing one.
94 ///
95 /// Does *not* store the wgpu instance, so calling this repeatedly may
96 /// create a new instance every time!
97 pub async fn new_instance(&self) -> wgpu::Instance {
98 match self {
99 Self::CreateNew(create_new) => {
100 #[allow(clippy::allow_attributes, unused_mut)]
101 let mut backends = create_new.instance_descriptor.backends;
102
103 // Don't try WebGPU if we're not in a secure context.
104 // Emscripten is excluded: wgpu gates both its `webgpu` backend and
105 // its `web_sys` re-export on `not(Emscripten)` (see the cfg aliases
106 // in `wgpu/build.rs`).
107 #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
108 if backends.contains(wgpu::Backends::BROWSER_WEBGPU) {
109 let is_secure_context =
110 wgpu::web_sys::window().is_some_and(|w| w.is_secure_context());
111 if !is_secure_context {
112 log::info!(
113 "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost."
114 );
115 backends.remove(wgpu::Backends::BROWSER_WEBGPU);
116 }
117 }
118
119 log::debug!("Creating wgpu instance with backends {backends:?}");
120 let desc = &create_new.instance_descriptor;
121 let descriptor = wgpu::InstanceDescriptor {
122 backends: desc.backends,
123 flags: desc.flags,
124 backend_options: desc.backend_options.clone(),
125 memory_budget_thresholds: desc.memory_budget_thresholds,
126 display: create_new
127 .display_handle
128 .as_ref()
129 .map(|handle| handle.clone_for_wgpu()),
130 };
131 wgpu::util::new_instance_with_webgpu_detection(descriptor).await
132 }
133 Self::Existing(existing) => existing.instance.clone(),
134 }
135 }
136}
137
138impl From<WgpuSetupCreateNew> for WgpuSetup {
139 fn from(create_new: WgpuSetupCreateNew) -> Self {
140 Self::CreateNew(create_new)
141 }
142}
143
144impl From<WgpuSetupExisting> for WgpuSetup {
145 fn from(existing: WgpuSetupExisting) -> Self {
146 Self::Existing(existing)
147 }
148}
149
150/// Method for selecting an adapter on native.
151///
152/// This can be used for fully custom adapter selection.
153/// If available, `wgpu::Surface` is passed to allow checking for surface compatibility.
154pub type NativeAdapterSelectorMethod = Arc<
155 dyn Fn(&[wgpu::Adapter], Option<&wgpu::Surface<'_>>) -> Result<wgpu::Adapter, String>
156 + Send
157 + Sync,
158>;
159
160/// Configuration for creating a new wgpu setup.
161///
162/// Used for [`WgpuSetup::CreateNew`].
163///
164/// Prefer [`Self::from_display_handle`] when you have a display handle available.
165/// Most platforms work without one, but some (e.g. Wayland with GLES, or WebGL)
166/// require it, so providing one ensures maximum compatibility.
167/// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle).
168///
169/// Note: The display handle is stored in [`Self::display_handle`] rather than in
170/// [`Self::instance_descriptor`] so the config can be cloned
171/// ([`wgpu::InstanceDescriptor`] is not `Clone`). It is injected at instance creation time.
172pub struct WgpuSetupCreateNew {
173 /// Descriptor for the wgpu instance.
174 ///
175 /// Leave [`wgpu::InstanceDescriptor::display`] as `None` — use [`Self::display_handle`]
176 /// instead (injected at instance creation time).
177 ///
178 /// The most important field is [`wgpu::InstanceDescriptor::backends`], which controls
179 /// which backends are supported (wgpu will pick one of these). For example, set it to
180 /// [`wgpu::Backends::GL`] to use only WebGL. By default on web, WebGPU is preferred
181 /// with WebGL as a fallback (requires the `webgl` feature of crate `wgpu`).
182 pub instance_descriptor: wgpu::InstanceDescriptor,
183
184 /// Display handle passed to wgpu at instance creation time.
185 ///
186 /// Required on some platforms (e.g. Wayland with GLES, WebGL); optional elsewhere.
187 /// With winit, use [`winit::event_loop::OwnedDisplayHandle`].
188 ///
189 /// `eframe` 's winit & web integrations will attempt to fill the display handle automatically if it is left empty.
190 pub display_handle: Option<Box<dyn EguiDisplayHandle>>,
191
192 /// Power preference for the adapter if [`Self::native_adapter_selector`] is not set or targeting web.
193 pub power_preference: wgpu::PowerPreference,
194
195 /// Optional selector for native adapters.
196 ///
197 /// This field has no effect when targeting web!
198 /// Otherwise, if set [`Self::power_preference`] is ignored and the adapter is instead selected by this method.
199 /// Note that [`Self::instance_descriptor`]'s [`wgpu::InstanceDescriptor::backends`]
200 /// are still used to filter the adapter enumeration in the first place.
201 ///
202 /// Defaults to `None`.
203 pub native_adapter_selector: Option<NativeAdapterSelectorMethod>,
204
205 /// Configuration passed on device request, given an adapter
206 pub device_descriptor:
207 Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
208}
209
210impl WgpuSetupCreateNew {
211 /// Creates a new configuration with the given display handle.
212 ///
213 /// This is the recommended constructor. Most platforms (Windows, macOS/iOS, Android, web)
214 /// work fine without a display handle, but some (e.g. Wayland on Linux with GLES) require
215 /// one. Providing it unconditionally ensures your app works everywhere.
216 ///
217 /// If you don't have a display handle available, use [`Self::without_display_handle`]
218 /// instead — it will still work on the majority of platforms.
219 ///
220 /// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle).
221 pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self {
222 Self {
223 display_handle: Some(Box::new(display_handle)),
224 ..Self::without_display_handle()
225 }
226 }
227
228 /// Creates a new configuration without a display handle.
229 ///
230 /// A display handle is not required for headless operation (offscreen rendering, tests,
231 /// compute-only workloads). It also isn't needed on most platforms even when presenting
232 /// to a window — only some configurations (e.g. Wayland on Linux with GLES) require one.
233 ///
234 /// If you do have a display handle available, prefer [`Self::from_display_handle`] for
235 /// maximum compatibility.
236 ///
237 /// With winit you can obtain one via [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle).
238 ///
239 /// `eframe` 's winit & web integrations will attempt to fill the display handle automatically if it is left empty.
240 pub fn without_display_handle() -> Self {
241 Self {
242 instance_descriptor: wgpu::InstanceDescriptor {
243 // Add GL backend, primarily because WebGPU is not stable enough yet.
244 // (note however, that the GL backend needs to be opted-in via the wgpu feature flag "webgl")
245 backends: wgpu::Backends::from_env()
246 .unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL),
247 flags: wgpu::InstanceFlags::from_build_config().with_env(),
248 backend_options: wgpu::BackendOptions::from_env_or_default(),
249 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
250 display: None,
251 },
252
253 display_handle: None,
254
255 power_preference: wgpu::PowerPreference::from_env()
256 .unwrap_or(wgpu::PowerPreference::HighPerformance),
257
258 native_adapter_selector: None,
259
260 device_descriptor: Arc::new(|adapter| {
261 let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
262 wgpu::Limits::downlevel_webgl2_defaults()
263 } else {
264 wgpu::Limits::default()
265 };
266
267 wgpu::DeviceDescriptor {
268 label: Some("egui wgpu device"),
269 required_limits: wgpu::Limits {
270 // When using a depth buffer, we have to be able to create a texture
271 // large enough for the entire surface, and we want to support 4k+ displays.
272 max_texture_dimension_2d: 8192,
273 ..base_limits
274 },
275 ..Default::default()
276 }
277 }),
278 }
279 }
280}
281
282impl Clone for WgpuSetupCreateNew {
283 fn clone(&self) -> Self {
284 let desc = &self.instance_descriptor;
285 Self {
286 instance_descriptor: wgpu::InstanceDescriptor {
287 backends: desc.backends,
288 flags: desc.flags,
289 backend_options: desc.backend_options.clone(),
290 memory_budget_thresholds: desc.memory_budget_thresholds,
291 display: None,
292 },
293 display_handle: self.display_handle.clone(),
294 power_preference: self.power_preference,
295 native_adapter_selector: self.native_adapter_selector.clone(),
296 device_descriptor: Arc::clone(&self.device_descriptor),
297 }
298 }
299}
300
301impl core::fmt::Debug for WgpuSetupCreateNew {
302 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
303 let Self {
304 instance_descriptor,
305 display_handle,
306 power_preference,
307 native_adapter_selector,
308 device_descriptor: _,
309 } = self;
310 f.debug_struct("WgpuSetupCreateNew")
311 .field("instance_descriptor", instance_descriptor)
312 .field("display_handle", display_handle)
313 .field("power_preference", power_preference)
314 .field(
315 "native_adapter_selector",
316 &native_adapter_selector.is_some(),
317 )
318 .finish_non_exhaustive()
319 }
320}
321
322/// Configuration for using an existing wgpu setup.
323///
324/// Used for [`WgpuSetup::Existing`].
325#[derive(Clone)]
326pub struct WgpuSetupExisting {
327 pub instance: wgpu::Instance,
328 pub adapter: wgpu::Adapter,
329 pub device: wgpu::Device,
330 pub queue: wgpu::Queue,
331}