egui_wgpu/setup.rs
1use std::sync::Arc;
2
3#[derive(Clone)]
4pub enum WgpuSetup {
5 /// Construct a wgpu setup using some predefined settings & heuristics.
6 /// This is the default option. You can customize most behaviours overriding the
7 /// supported backends, power preferences, and device description.
8 ///
9 /// By default can also be configured with various environment variables:
10 /// * `WGPU_BACKEND`: `vulkan`, `dx12`, `metal`, `opengl`, `webgpu`
11 /// * `WGPU_POWER_PREF`: `low`, `high` or `none`
12 /// * `WGPU_TRACE`: Path to a file to output a wgpu trace file.
13 ///
14 /// Each instance flag also comes with an environment variable (for details see [`wgpu::InstanceFlags`]):
15 /// * `WGPU_VALIDATION`: Enables validation (enabled by default in debug builds).
16 /// * `WGPU_DEBUG`: Generate debug information in shaders and objects (enabled by default in debug builds).
17 /// * `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER`: Whether wgpu should expose adapters that run on top of non-compliant adapters.
18 /// * `WGPU_GPU_BASED_VALIDATION`: Enable GPU-based validation.
19 CreateNew(WgpuSetupCreateNew),
20
21 /// Run on an existing wgpu setup.
22 Existing(WgpuSetupExisting),
23}
24
25impl Default for WgpuSetup {
26 fn default() -> Self {
27 Self::CreateNew(WgpuSetupCreateNew::default())
28 }
29}
30
31impl std::fmt::Debug for WgpuSetup {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 Self::CreateNew(create_new) => f
35 .debug_tuple("WgpuSetup::CreateNew")
36 .field(create_new)
37 .finish(),
38 Self::Existing { .. } => f.debug_tuple("WgpuSetup::Existing").finish(),
39 }
40 }
41}
42
43impl WgpuSetup {
44 /// Creates a new [`wgpu::Instance`] or clones the existing one.
45 ///
46 /// Does *not* store the wgpu instance, so calling this repeatedly may
47 /// create a new instance every time!
48 pub async fn new_instance(&self) -> wgpu::Instance {
49 match self {
50 Self::CreateNew(create_new) => {
51 #[allow(unused_mut, clippy::allow_attributes)]
52 let mut backends = create_new.instance_descriptor.backends;
53
54 // Don't try WebGPU if we're not in a secure context.
55 #[cfg(target_arch = "wasm32")]
56 if backends.contains(wgpu::Backends::BROWSER_WEBGPU) {
57 let is_secure_context =
58 wgpu::web_sys::window().is_some_and(|w| w.is_secure_context());
59 if !is_secure_context {
60 log::info!(
61 "WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost."
62 );
63 backends.remove(wgpu::Backends::BROWSER_WEBGPU);
64 }
65 }
66
67 log::debug!("Creating wgpu instance with backends {backends:?}");
68 wgpu::util::new_instance_with_webgpu_detection(&create_new.instance_descriptor)
69 .await
70 }
71 Self::Existing(existing) => existing.instance.clone(),
72 }
73 }
74}
75
76impl From<WgpuSetupCreateNew> for WgpuSetup {
77 fn from(create_new: WgpuSetupCreateNew) -> Self {
78 Self::CreateNew(create_new)
79 }
80}
81
82impl From<WgpuSetupExisting> for WgpuSetup {
83 fn from(existing: WgpuSetupExisting) -> Self {
84 Self::Existing(existing)
85 }
86}
87
88/// Method for selecting an adapter on native.
89///
90/// This can be used for fully custom adapter selection.
91/// If available, `wgpu::Surface` is passed to allow checking for surface compatibility.
92pub type NativeAdapterSelectorMethod = Arc<
93 dyn Fn(&[wgpu::Adapter], Option<&wgpu::Surface<'_>>) -> Result<wgpu::Adapter, String>
94 + Send
95 + Sync,
96>;
97
98/// Configuration for creating a new wgpu setup.
99///
100/// Used for [`WgpuSetup::CreateNew`].
101pub struct WgpuSetupCreateNew {
102 /// Instance descriptor for creating a wgpu instance.
103 ///
104 /// The most important field is [`wgpu::InstanceDescriptor::backends`], which
105 /// controls which backends are supported (wgpu will pick one of these).
106 /// If you only want to support WebGL (and not WebGPU),
107 /// you can set this to [`wgpu::Backends::GL`].
108 /// By default on web, WebGPU will be used if available.
109 /// WebGL will only be used as a fallback,
110 /// and only if you have enabled the `webgl` feature of crate `wgpu`.
111 pub instance_descriptor: wgpu::InstanceDescriptor,
112
113 /// Power preference for the adapter if [`Self::native_adapter_selector`] is not set or targeting web.
114 pub power_preference: wgpu::PowerPreference,
115
116 /// Optional selector for native adapters.
117 ///
118 /// This field has no effect when targeting web!
119 /// Otherwise, if set [`Self::power_preference`] is ignored and the adapter is instead selected by this method.
120 /// Note that [`Self::instance_descriptor`]'s [`wgpu::InstanceDescriptor::backends`]
121 /// are still used to filter the adapter enumeration in the first place.
122 ///
123 /// Defaults to `None`.
124 pub native_adapter_selector: Option<NativeAdapterSelectorMethod>,
125
126 /// Configuration passed on device request, given an adapter
127 pub device_descriptor:
128 Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
129}
130
131impl Clone for WgpuSetupCreateNew {
132 fn clone(&self) -> Self {
133 Self {
134 instance_descriptor: self.instance_descriptor.clone(),
135 power_preference: self.power_preference,
136 native_adapter_selector: self.native_adapter_selector.clone(),
137 device_descriptor: self.device_descriptor.clone(),
138 }
139 }
140}
141
142impl std::fmt::Debug for WgpuSetupCreateNew {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 f.debug_struct("WgpuSetupCreateNew")
145 .field("instance_descriptor", &self.instance_descriptor)
146 .field("power_preference", &self.power_preference)
147 .field(
148 "native_adapter_selector",
149 &self.native_adapter_selector.is_some(),
150 )
151 .finish()
152 }
153}
154
155impl Default for WgpuSetupCreateNew {
156 fn default() -> Self {
157 Self {
158 instance_descriptor: wgpu::InstanceDescriptor {
159 // Add GL backend, primarily because WebGPU is not stable enough yet.
160 // (note however, that the GL backend needs to be opted-in via the wgpu feature flag "webgl")
161 backends: wgpu::Backends::from_env()
162 .unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL),
163 flags: wgpu::InstanceFlags::from_build_config().with_env(),
164 backend_options: wgpu::BackendOptions::from_env_or_default(),
165 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
166 },
167
168 power_preference: wgpu::PowerPreference::from_env()
169 .unwrap_or(wgpu::PowerPreference::HighPerformance),
170
171 native_adapter_selector: None,
172
173 device_descriptor: Arc::new(|adapter| {
174 let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
175 wgpu::Limits::downlevel_webgl2_defaults()
176 } else {
177 wgpu::Limits::default()
178 };
179
180 wgpu::DeviceDescriptor {
181 label: Some("egui wgpu device"),
182 required_limits: wgpu::Limits {
183 // When using a depth buffer, we have to be able to create a texture
184 // large enough for the entire surface, and we want to support 4k+ displays.
185 max_texture_dimension_2d: 8192,
186 ..base_limits
187 },
188 ..Default::default()
189 }
190 }),
191 }
192 }
193}
194
195/// Configuration for using an existing wgpu setup.
196///
197/// Used for [`WgpuSetup::Existing`].
198#[derive(Clone)]
199pub struct WgpuSetupExisting {
200 pub instance: wgpu::Instance,
201 pub adapter: wgpu::Adapter,
202 pub device: wgpu::Device,
203 pub queue: wgpu::Queue,
204}