1#![doc = document_features::document_features!()]
17#![allow(unsafe_code)]
20
21pub use wgpu;
22
23mod renderer;
25
26mod setup;
27
28pub use renderer::*;
29pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting};
30
31pub mod capture;
33
34#[cfg(feature = "winit")]
36pub mod winit;
37
38use std::sync::Arc;
39
40use epaint::mutex::RwLock;
41
42#[derive(thiserror::Error, Debug)]
44pub enum WgpuError {
45 #[error(transparent)]
46 RequestAdapterError(#[from] wgpu::RequestAdapterError),
47
48 #[error("Adapter selection failed: {0}")]
49 CustomNativeAdapterSelectionError(String),
50
51 #[error("There was no valid format for the surface at all.")]
52 NoSurfaceFormatsAvailable,
53
54 #[error(transparent)]
55 RequestDeviceError(#[from] wgpu::RequestDeviceError),
56
57 #[error(transparent)]
58 CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
59
60 #[cfg(feature = "winit")]
61 #[error(transparent)]
62 HandleError(#[from] ::winit::raw_window_handle::HandleError),
63}
64
65#[derive(Clone)]
67pub struct RenderState {
68 pub adapter: wgpu::Adapter,
70
71 #[cfg(not(target_arch = "wasm32"))]
76 pub available_adapters: Vec<wgpu::Adapter>,
77
78 pub device: wgpu::Device,
80
81 pub queue: wgpu::Queue,
83
84 pub target_format: wgpu::TextureFormat,
86
87 pub renderer: Arc<RwLock<Renderer>>,
89}
90
91async fn request_adapter(
92 instance: &wgpu::Instance,
93 power_preference: wgpu::PowerPreference,
94 compatible_surface: Option<&wgpu::Surface<'_>>,
95 available_adapters: &[wgpu::Adapter],
96) -> Result<wgpu::Adapter, WgpuError> {
97 profiling::function_scope!();
98
99 let adapter = instance
100 .request_adapter(&wgpu::RequestAdapterOptions {
101 power_preference,
102 compatible_surface,
103 force_fallback_adapter: false,
108 })
109 .await
110 .inspect_err(|_err| {
111 if cfg!(target_arch = "wasm32") {
112 } else if available_adapters.is_empty() {
114 if std::env::var("DYLD_LIBRARY_PATH").is_ok() {
115 log::warn!(
120 "No wgpu adapter found. This could be because DYLD_LIBRARY_PATH causes dylibs to be loaded that interfere with Metal device creation. Try restarting with DYLD_LIBRARY_PATH=''"
121 );
122 } else {
123 log::info!("No wgpu adapter found");
124 }
125 } else if available_adapters.len() == 1 {
126 log::info!(
127 "The only available wgpu adapter was not suitable: {}",
128 adapter_info_summary(&available_adapters[0].get_info())
129 );
130 } else {
131 log::info!(
132 "No suitable wgpu adapter found out of the {} available ones: {}",
133 available_adapters.len(),
134 describe_adapters(available_adapters)
135 );
136 }
137 })?;
138
139 if cfg!(target_arch = "wasm32") {
140 log::debug!(
141 "Picked wgpu adapter: {}",
142 adapter_info_summary(&adapter.get_info())
143 );
144 } else {
145 if available_adapters.len() == 1 {
147 log::debug!(
148 "Picked the only available wgpu adapter: {}",
149 adapter_info_summary(&adapter.get_info())
150 );
151 } else {
152 log::info!(
153 "There were {} available wgpu adapters: {}",
154 available_adapters.len(),
155 describe_adapters(available_adapters)
156 );
157 log::debug!(
158 "Picked wgpu adapter: {}",
159 adapter_info_summary(&adapter.get_info())
160 );
161 }
162 }
163
164 Ok(adapter)
165}
166
167impl RenderState {
168 pub async fn create(
173 config: &WgpuConfiguration,
174 instance: &wgpu::Instance,
175 compatible_surface: Option<&wgpu::Surface<'static>>,
176 options: RendererOptions,
177 ) -> Result<Self, WgpuError> {
178 profiling::scope!("RenderState::create"); #[cfg(not(target_arch = "wasm32"))]
182 let available_adapters = {
183 let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
184 create_new.instance_descriptor.backends
185 } else {
186 wgpu::Backends::all()
187 };
188
189 instance.enumerate_adapters(backends)
190 };
191
192 let (adapter, device, queue) = match config.wgpu_setup.clone() {
193 WgpuSetup::CreateNew(WgpuSetupCreateNew {
194 instance_descriptor: _,
195 power_preference,
196 native_adapter_selector: _native_adapter_selector,
197 device_descriptor,
198 }) => {
199 let adapter = {
200 #[cfg(target_arch = "wasm32")]
201 {
202 request_adapter(instance, power_preference, compatible_surface, &[]).await
203 }
204 #[cfg(not(target_arch = "wasm32"))]
205 if let Some(native_adapter_selector) = _native_adapter_selector {
206 native_adapter_selector(&available_adapters, compatible_surface)
207 .map_err(WgpuError::CustomNativeAdapterSelectionError)
208 } else {
209 request_adapter(
210 instance,
211 power_preference,
212 compatible_surface,
213 &available_adapters,
214 )
215 .await
216 }
217 }?;
218
219 let (device, queue) = {
220 profiling::scope!("request_device");
221 adapter
222 .request_device(&(*device_descriptor)(&adapter))
223 .await?
224 };
225
226 (adapter, device, queue)
227 }
228 WgpuSetup::Existing(WgpuSetupExisting {
229 instance: _,
230 adapter,
231 device,
232 queue,
233 }) => (adapter, device, queue),
234 };
235
236 let surface_formats = {
237 profiling::scope!("get_capabilities");
238 compatible_surface.map_or_else(
239 || vec![wgpu::TextureFormat::Rgba8Unorm],
240 |s| s.get_capabilities(&adapter).formats,
241 )
242 };
243 let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
244
245 let renderer = Renderer::new(&device, target_format, options);
246
247 #[allow(clippy::arc_with_non_send_sync, clippy::allow_attributes)] Ok(Self {
251 adapter,
252 #[cfg(not(target_arch = "wasm32"))]
253 available_adapters,
254 device,
255 queue,
256 target_format,
257 renderer: Arc::new(RwLock::new(renderer)),
258 })
259 }
260}
261
262fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
263 if adapters.is_empty() {
264 "(none)".to_owned()
265 } else if adapters.len() == 1 {
266 adapter_info_summary(&adapters[0].get_info())
267 } else {
268 adapters
269 .iter()
270 .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
271 .collect::<Vec<_>>()
272 .join(", ")
273 }
274}
275
276pub enum SurfaceErrorAction {
278 SkipFrame,
280
281 RecreateSurface,
283}
284
285#[derive(Clone)]
287pub struct WgpuConfiguration {
288 pub present_mode: wgpu::PresentMode,
290
291 pub desired_maximum_frame_latency: Option<u32>,
299
300 pub wgpu_setup: WgpuSetup,
302
303 pub on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction + Send + Sync>,
305}
306
307#[test]
308fn wgpu_config_impl_send_sync() {
309 fn assert_send_sync<T: Send + Sync>() {}
310 assert_send_sync::<WgpuConfiguration>();
311}
312
313impl std::fmt::Debug for WgpuConfiguration {
314 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315 let Self {
316 present_mode,
317 desired_maximum_frame_latency,
318 wgpu_setup,
319 on_surface_error: _,
320 } = self;
321 f.debug_struct("WgpuConfiguration")
322 .field("present_mode", &present_mode)
323 .field(
324 "desired_maximum_frame_latency",
325 &desired_maximum_frame_latency,
326 )
327 .field("wgpu_setup", &wgpu_setup)
328 .finish_non_exhaustive()
329 }
330}
331
332impl Default for WgpuConfiguration {
333 fn default() -> Self {
334 Self {
335 present_mode: wgpu::PresentMode::AutoVsync,
336 desired_maximum_frame_latency: None,
337 wgpu_setup: Default::default(),
338 on_surface_error: Arc::new(|err| {
339 if err == wgpu::SurfaceError::Outdated {
340 } else {
344 log::warn!("Dropped frame with error: {err}");
345 }
346 SurfaceErrorAction::SkipFrame
347 }),
348 }
349 }
350}
351
352pub fn preferred_framebuffer_format(
357 formats: &[wgpu::TextureFormat],
358) -> Result<wgpu::TextureFormat, WgpuError> {
359 for &format in formats {
360 if matches!(
361 format,
362 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
363 ) {
364 return Ok(format);
365 }
366 }
367
368 formats
369 .first()
370 .copied()
371 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
372}
373
374pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
376 match (depth_buffer, stencil_buffer) {
377 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
378 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
379 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
380 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
381 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
382 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
383 _ => None,
384 }
385}
386
387pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
391 let wgpu::AdapterInfo {
392 name,
393 vendor,
394 device,
395 device_type,
396 driver,
397 driver_info,
398 backend,
399 } = &info;
400
401 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
407
408 if !name.is_empty() {
409 summary += &format!(", name: {name:?}");
410 }
411 if !driver.is_empty() {
412 summary += &format!(", driver: {driver:?}");
413 }
414 if !driver_info.is_empty() {
415 summary += &format!(", driver_info: {driver_info:?}");
416 }
417 if *vendor != 0 {
418 #[cfg(not(target_arch = "wasm32"))]
419 {
420 summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
421 }
422 #[cfg(target_arch = "wasm32")]
423 {
424 summary += &format!(", vendor: 0x{vendor:04X}");
425 }
426 }
427 if *device != 0 {
428 summary += &format!(", device: 0x{device:02X}");
429 }
430
431 summary
432}
433
434#[cfg(not(target_arch = "wasm32"))]
436pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
437 match vendor_id {
438 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
439 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
440 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
441 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
442 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
443 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
444 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
445 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
446 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
447 _ => "Unknown",
448 }
449}