1#![doc = document_features::document_features!()]
17pub use wgpu;
20
21mod renderer;
23
24mod setup;
25
26pub use renderer::*;
27pub use setup::{
28 EguiDisplayHandle, NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew,
29 WgpuSetupExisting,
30};
31
32#[cfg(feature = "capture")]
34pub mod capture;
35
36#[cfg(feature = "winit")]
38pub mod winit;
39
40use std::sync::Arc;
41
42use epaint::mutex::RwLock;
43
44#[derive(thiserror::Error, Debug)]
46pub enum WgpuError {
47 #[error(transparent)]
48 RequestAdapterError(#[from] wgpu::RequestAdapterError),
49
50 #[error("Adapter selection failed: {0}")]
51 CustomNativeAdapterSelectionError(String),
52
53 #[error("There was no valid format for the surface at all.")]
54 NoSurfaceFormatsAvailable,
55
56 #[error(transparent)]
57 RequestDeviceError(#[from] wgpu::RequestDeviceError),
58
59 #[error(transparent)]
60 CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
61
62 #[cfg(feature = "winit")]
63 #[error(transparent)]
64 HandleError(#[from] ::winit::raw_window_handle::HandleError),
65}
66
67#[derive(Clone)]
69pub struct RenderState {
70 pub adapter: wgpu::Adapter,
72
73 #[cfg(not(target_arch = "wasm32"))]
78 pub available_adapters: Vec<wgpu::Adapter>,
79
80 pub device: wgpu::Device,
82
83 pub queue: wgpu::Queue,
85
86 pub target_format: wgpu::TextureFormat,
88
89 pub renderer: Arc<RwLock<Renderer>>,
91}
92
93async fn request_adapter(
94 instance: &wgpu::Instance,
95 power_preference: wgpu::PowerPreference,
96 compatible_surface: Option<&wgpu::Surface<'_>>,
97 available_adapters: &[wgpu::Adapter],
98) -> Result<wgpu::Adapter, WgpuError> {
99 profiling::function_scope!();
100
101 let adapter = instance
102 .request_adapter(&wgpu::RequestAdapterOptions {
103 power_preference,
104 compatible_surface,
105 force_fallback_adapter: false,
110 })
111 .await
112 .inspect_err(|_err| {
113 if cfg!(target_arch = "wasm32") {
114 } else if available_adapters.is_empty() {
116 if std::env::var("DYLD_LIBRARY_PATH").is_ok() {
117 log::warn!(
122 "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=''"
123 );
124 } else {
125 log::info!("No wgpu adapter found");
126 }
127 } else if available_adapters.len() == 1 {
128 log::info!(
129 "The only available wgpu adapter was not suitable: {}",
130 adapter_info_summary(&available_adapters[0].get_info())
131 );
132 } else {
133 log::info!(
134 "No suitable wgpu adapter found out of the {} available ones: {}",
135 available_adapters.len(),
136 describe_adapters(available_adapters)
137 );
138 }
139 })?;
140
141 if cfg!(target_arch = "wasm32") {
142 log::debug!(
143 "Picked wgpu adapter: {}",
144 adapter_info_summary(&adapter.get_info())
145 );
146 } else {
147 if available_adapters.len() == 1 {
149 log::debug!(
150 "Picked the only available wgpu adapter: {}",
151 adapter_info_summary(&adapter.get_info())
152 );
153 } else {
154 log::info!(
155 "There were {} available wgpu adapters: {}",
156 available_adapters.len(),
157 describe_adapters(available_adapters)
158 );
159 log::debug!(
160 "Picked wgpu adapter: {}",
161 adapter_info_summary(&adapter.get_info())
162 );
163 }
164 }
165
166 Ok(adapter)
167}
168
169impl RenderState {
170 pub async fn create(
175 config: &WgpuConfiguration,
176 instance: &wgpu::Instance,
177 compatible_surface: Option<&wgpu::Surface<'static>>,
178 options: RendererOptions,
179 ) -> Result<Self, WgpuError> {
180 profiling::scope!("RenderState::create"); #[cfg(not(target_arch = "wasm32"))]
184 let available_adapters = {
185 let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
186 create_new.instance_descriptor.backends
187 } else {
188 wgpu::Backends::all()
189 };
190
191 instance.enumerate_adapters(backends).await
192 };
193
194 let (adapter, device, queue) = match config.wgpu_setup.clone() {
195 WgpuSetup::CreateNew(WgpuSetupCreateNew {
196 instance_descriptor: _,
197 display_handle: _,
198 power_preference,
199 native_adapter_selector: _native_adapter_selector,
200 device_descriptor,
201 }) => {
202 let adapter = {
203 #[cfg(target_arch = "wasm32")]
204 {
205 request_adapter(instance, power_preference, compatible_surface, &[]).await
206 }
207 #[cfg(not(target_arch = "wasm32"))]
208 if let Some(native_adapter_selector) = _native_adapter_selector {
209 native_adapter_selector(&available_adapters, compatible_surface)
210 .map_err(WgpuError::CustomNativeAdapterSelectionError)
211 } else {
212 request_adapter(
213 instance,
214 power_preference,
215 compatible_surface,
216 &available_adapters,
217 )
218 .await
219 }
220 }?;
221
222 let (device, queue) = {
223 profiling::scope!("request_device");
224 adapter
225 .request_device(&(*device_descriptor)(&adapter))
226 .await?
227 };
228
229 (adapter, device, queue)
230 }
231 WgpuSetup::Existing(WgpuSetupExisting {
232 instance: _,
233 adapter,
234 device,
235 queue,
236 }) => (adapter, device, queue),
237 };
238
239 let surface_formats = {
240 profiling::scope!("get_capabilities");
241 compatible_surface.map_or_else(
242 || vec![wgpu::TextureFormat::Rgba8Unorm],
243 |s| s.get_capabilities(&adapter).formats,
244 )
245 };
246 let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
247
248 let renderer = Renderer::new(&device, target_format, options);
249
250 #[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] Ok(Self {
254 adapter,
255 #[cfg(not(target_arch = "wasm32"))]
256 available_adapters,
257 device,
258 queue,
259 target_format,
260 renderer: Arc::new(RwLock::new(renderer)),
261 })
262 }
263}
264
265fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
266 if adapters.is_empty() {
267 "(none)".to_owned()
268 } else if adapters.len() == 1 {
269 adapter_info_summary(&adapters[0].get_info())
270 } else {
271 adapters
272 .iter()
273 .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
274 .collect::<Vec<_>>()
275 .join(", ")
276 }
277}
278
279pub enum SurfaceErrorAction {
281 SkipFrame,
283
284 RecreateSurface,
286}
287
288#[derive(Clone)]
290pub struct WgpuConfiguration {
291 pub present_mode: wgpu::PresentMode,
293
294 pub desired_maximum_frame_latency: Option<u32>,
302
303 pub wgpu_setup: WgpuSetup,
305
306 pub on_surface_status:
314 Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> SurfaceErrorAction + Send + Sync>,
315}
316
317#[test]
318fn wgpu_config_impl_send_sync() {
319 fn assert_send_sync<T: Send + Sync>() {}
320 assert_send_sync::<WgpuConfiguration>();
321}
322
323impl std::fmt::Debug for WgpuConfiguration {
324 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
325 let Self {
326 present_mode,
327 desired_maximum_frame_latency,
328 wgpu_setup,
329 on_surface_status: _,
330 } = self;
331 f.debug_struct("WgpuConfiguration")
332 .field("present_mode", &present_mode)
333 .field(
334 "desired_maximum_frame_latency",
335 &desired_maximum_frame_latency,
336 )
337 .field("wgpu_setup", &wgpu_setup)
338 .finish_non_exhaustive()
339 }
340}
341
342impl Default for WgpuConfiguration {
343 fn default() -> Self {
344 Self {
345 present_mode: wgpu::PresentMode::AutoVsync,
346 desired_maximum_frame_latency: None,
347 wgpu_setup: WgpuSetup::without_display_handle(),
350 on_surface_status: Arc::new(|status| {
351 match status {
352 wgpu::CurrentSurfaceTexture::Outdated => {
353 }
357 wgpu::CurrentSurfaceTexture::Occluded => {
358 log::debug!("Dropped frame with error: {status:?}");
360 }
361 _ => {
362 log::warn!("Dropped frame with error: {status:?}");
363 }
364 }
365
366 SurfaceErrorAction::SkipFrame
367 }),
368 }
369 }
370}
371
372pub fn preferred_framebuffer_format(
377 formats: &[wgpu::TextureFormat],
378) -> Result<wgpu::TextureFormat, WgpuError> {
379 for &format in formats {
380 if matches!(
381 format,
382 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
383 ) {
384 return Ok(format);
385 }
386 }
387
388 formats
389 .first()
390 .copied()
391 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
392}
393
394pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
396 match (depth_buffer, stencil_buffer) {
397 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
398 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
399 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
400 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
401 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
402 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
403 _ => None,
404 }
405}
406
407pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
411 let wgpu::AdapterInfo {
412 name,
413 vendor,
414 device,
415 device_type,
416 driver,
417 driver_info,
418 backend,
419 device_pci_bus_id,
420 subgroup_min_size,
421 subgroup_max_size,
422 transient_saves_memory,
423 } = &info;
424
425 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
431
432 if !name.is_empty() {
433 summary += &format!(", name: {name:?}");
434 }
435 if !driver.is_empty() {
436 summary += &format!(", driver: {driver:?}");
437 }
438 if !driver_info.is_empty() {
439 summary += &format!(", driver_info: {driver_info:?}");
440 }
441 if *vendor != 0 {
442 #[cfg(not(target_arch = "wasm32"))]
443 {
444 summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
445 }
446 #[cfg(target_arch = "wasm32")]
447 {
448 summary += &format!(", vendor: 0x{vendor:04X}");
449 }
450 }
451 if *device != 0 {
452 summary += &format!(", device: 0x{device:02X}");
453 }
454 if !device_pci_bus_id.is_empty() {
455 summary += &format!(", pci_bus_id: {device_pci_bus_id:?}");
456 }
457 if *subgroup_min_size != 0 || *subgroup_max_size != 0 {
458 summary += &format!(", subgroup_size: {subgroup_min_size}..={subgroup_max_size}");
459 }
460 summary += &format!(", transient_saves_memory: {transient_saves_memory}");
461
462 summary
463}
464
465#[cfg(not(target_arch = "wasm32"))]
467pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
468 match vendor_id {
469 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
470 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
471 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
472 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
473 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
474 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
475 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
476 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
477 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
478 _ => "Unknown",
479 }
480}