Skip to main content

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 + std::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 + std::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 std::fmt::Debug for WgpuSetup {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::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                #[cfg(target_arch = "wasm32")]
105                if backends.contains(wgpu::Backends::BROWSER_WEBGPU) {
106                    let is_secure_context =
107                        wgpu::web_sys::window().is_some_and(|w| w.is_secure_context());
108                    if !is_secure_context {
109                        log::info!(
110                            "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost."
111                        );
112                        backends.remove(wgpu::Backends::BROWSER_WEBGPU);
113                    }
114                }
115
116                log::debug!("Creating wgpu instance with backends {backends:?}");
117                let desc = &create_new.instance_descriptor;
118                let descriptor = wgpu::InstanceDescriptor {
119                    backends: desc.backends,
120                    flags: desc.flags,
121                    backend_options: desc.backend_options.clone(),
122                    memory_budget_thresholds: desc.memory_budget_thresholds,
123                    display: create_new
124                        .display_handle
125                        .as_ref()
126                        .map(|handle| handle.clone_for_wgpu()),
127                };
128                wgpu::util::new_instance_with_webgpu_detection(descriptor).await
129            }
130            Self::Existing(existing) => existing.instance.clone(),
131        }
132    }
133}
134
135impl From<WgpuSetupCreateNew> for WgpuSetup {
136    fn from(create_new: WgpuSetupCreateNew) -> Self {
137        Self::CreateNew(create_new)
138    }
139}
140
141impl From<WgpuSetupExisting> for WgpuSetup {
142    fn from(existing: WgpuSetupExisting) -> Self {
143        Self::Existing(existing)
144    }
145}
146
147/// Method for selecting an adapter on native.
148///
149/// This can be used for fully custom adapter selection.
150/// If available, `wgpu::Surface` is passed to allow checking for surface compatibility.
151pub type NativeAdapterSelectorMethod = Arc<
152    dyn Fn(&[wgpu::Adapter], Option<&wgpu::Surface<'_>>) -> Result<wgpu::Adapter, String>
153        + Send
154        + Sync,
155>;
156
157/// Configuration for creating a new wgpu setup.
158///
159/// Used for [`WgpuSetup::CreateNew`].
160///
161/// Prefer [`Self::from_display_handle`] when you have a display handle available.
162/// Most platforms work without one, but some (e.g. Wayland with GLES, or WebGL)
163/// require it, so providing one ensures maximum compatibility.
164/// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle).
165///
166/// Note: The display handle is stored in [`Self::display_handle`] rather than in
167/// [`Self::instance_descriptor`] so the config can be cloned
168/// ([`wgpu::InstanceDescriptor`] is not `Clone`). It is injected at instance creation time.
169pub struct WgpuSetupCreateNew {
170    /// Descriptor for the wgpu instance.
171    ///
172    /// Leave [`wgpu::InstanceDescriptor::display`] as `None` — use [`Self::display_handle`]
173    /// instead (injected at instance creation time).
174    ///
175    /// The most important field is [`wgpu::InstanceDescriptor::backends`], which controls
176    /// which backends are supported (wgpu will pick one of these). For example, set it to
177    /// [`wgpu::Backends::GL`] to use only WebGL. By default on web, WebGPU is preferred
178    /// with WebGL as a fallback (requires the `webgl` feature of crate `wgpu`).
179    pub instance_descriptor: wgpu::InstanceDescriptor,
180
181    /// Display handle passed to wgpu at instance creation time.
182    ///
183    /// Required on some platforms (e.g. Wayland with GLES, WebGL); optional elsewhere.
184    /// With winit, use [`winit::event_loop::OwnedDisplayHandle`].
185    ///
186    /// `eframe` 's winit & web integrations will attempt to fill the display handle automatically if it is left empty.
187    pub display_handle: Option<Box<dyn EguiDisplayHandle>>,
188
189    /// Power preference for the adapter if [`Self::native_adapter_selector`] is not set or targeting web.
190    pub power_preference: wgpu::PowerPreference,
191
192    /// Optional selector for native adapters.
193    ///
194    /// This field has no effect when targeting web!
195    /// Otherwise, if set [`Self::power_preference`] is ignored and the adapter is instead selected by this method.
196    /// Note that [`Self::instance_descriptor`]'s [`wgpu::InstanceDescriptor::backends`]
197    /// are still used to filter the adapter enumeration in the first place.
198    ///
199    /// Defaults to `None`.
200    pub native_adapter_selector: Option<NativeAdapterSelectorMethod>,
201
202    /// Configuration passed on device request, given an adapter
203    pub device_descriptor:
204        Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
205}
206
207impl WgpuSetupCreateNew {
208    /// Creates a new configuration with the given display handle.
209    ///
210    /// This is the recommended constructor. Most platforms (Windows, macOS/iOS, Android, web)
211    /// work fine without a display handle, but some (e.g. Wayland on Linux with GLES) require
212    /// one. Providing it unconditionally ensures your app works everywhere.
213    ///
214    /// If you don't have a display handle available, use [`Self::without_display_handle`]
215    /// instead — it will still work on the majority of platforms.
216    ///
217    /// With winit, pass [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle).
218    pub fn from_display_handle(display_handle: impl EguiDisplayHandle) -> Self {
219        Self {
220            display_handle: Some(Box::new(display_handle)),
221            ..Self::without_display_handle()
222        }
223    }
224
225    /// Creates a new configuration without a display handle.
226    ///
227    /// A display handle is not required for headless operation (offscreen rendering, tests,
228    /// compute-only workloads). It also isn't needed on most platforms even when presenting
229    /// to a window — only some configurations (e.g. Wayland on Linux with GLES) require one.
230    ///
231    /// If you do have a display handle available, prefer [`Self::from_display_handle`] for
232    /// maximum compatibility.
233    ///
234    /// With winit you can obtain one via [`EventLoop::owned_display_handle`](winit::event_loop::EventLoop::owned_display_handle).
235    ///
236    /// `eframe` 's winit & web integrations will attempt to fill the display handle automatically if it is left empty.
237    pub fn without_display_handle() -> Self {
238        Self {
239            instance_descriptor: wgpu::InstanceDescriptor {
240                // Add GL backend, primarily because WebGPU is not stable enough yet.
241                // (note however, that the GL backend needs to be opted-in via the wgpu feature flag "webgl")
242                backends: wgpu::Backends::from_env()
243                    .unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL),
244                flags: wgpu::InstanceFlags::from_build_config().with_env(),
245                backend_options: wgpu::BackendOptions::from_env_or_default(),
246                memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
247                display: None,
248            },
249
250            display_handle: None,
251
252            power_preference: wgpu::PowerPreference::from_env()
253                .unwrap_or(wgpu::PowerPreference::HighPerformance),
254
255            native_adapter_selector: None,
256
257            device_descriptor: Arc::new(|adapter| {
258                let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
259                    wgpu::Limits::downlevel_webgl2_defaults()
260                } else {
261                    wgpu::Limits::default()
262                };
263
264                wgpu::DeviceDescriptor {
265                    label: Some("egui wgpu device"),
266                    required_limits: wgpu::Limits {
267                        // When using a depth buffer, we have to be able to create a texture
268                        // large enough for the entire surface, and we want to support 4k+ displays.
269                        max_texture_dimension_2d: 8192,
270                        ..base_limits
271                    },
272                    ..Default::default()
273                }
274            }),
275        }
276    }
277}
278
279impl Clone for WgpuSetupCreateNew {
280    fn clone(&self) -> Self {
281        let desc = &self.instance_descriptor;
282        Self {
283            instance_descriptor: wgpu::InstanceDescriptor {
284                backends: desc.backends,
285                flags: desc.flags,
286                backend_options: desc.backend_options.clone(),
287                memory_budget_thresholds: desc.memory_budget_thresholds,
288                display: None,
289            },
290            display_handle: self.display_handle.clone(),
291            power_preference: self.power_preference,
292            native_adapter_selector: self.native_adapter_selector.clone(),
293            device_descriptor: Arc::clone(&self.device_descriptor),
294        }
295    }
296}
297
298impl std::fmt::Debug for WgpuSetupCreateNew {
299    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
300        let Self {
301            instance_descriptor,
302            display_handle,
303            power_preference,
304            native_adapter_selector,
305            device_descriptor: _,
306        } = self;
307        f.debug_struct("WgpuSetupCreateNew")
308            .field("instance_descriptor", instance_descriptor)
309            .field("display_handle", display_handle)
310            .field("power_preference", power_preference)
311            .field(
312                "native_adapter_selector",
313                &native_adapter_selector.is_some(),
314            )
315            .finish_non_exhaustive()
316    }
317}
318
319/// Configuration for using an existing wgpu setup.
320///
321/// Used for [`WgpuSetup::Existing`].
322#[derive(Clone)]
323pub struct WgpuSetupExisting {
324    pub instance: wgpu::Instance,
325    pub adapter: wgpu::Adapter,
326    pub device: wgpu::Device,
327    pub queue: wgpu::Queue,
328}