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, Copy, Debug, PartialEq, Eq, Hash)]
71pub struct SurfaceConfig {
72 pub present_mode: wgpu::PresentMode,
74
75 pub desired_maximum_frame_latency: Option<u32>,
83}
84
85impl SurfaceConfig {
86 pub const LOW_LATENCY: Self = Self {
88 present_mode: wgpu::PresentMode::AutoVsync,
89
90 desired_maximum_frame_latency: if cfg!(target_os = "ios") {
91 None } else {
93 Some(1)
94 },
95 };
96
97 pub const HIGH_THROUGHPUT: Self = Self {
100 present_mode: wgpu::PresentMode::AutoVsync,
101 desired_maximum_frame_latency: Some(2), };
103}
104
105#[derive(Clone)]
107pub struct RenderState {
108 pub adapter: wgpu::Adapter,
110
111 #[cfg(not(target_arch = "wasm32"))]
116 pub available_adapters: Vec<wgpu::Adapter>,
117
118 pub instance: wgpu::Instance,
120
121 pub device: wgpu::Device,
123
124 pub queue: wgpu::Queue,
126
127 pub target_format: wgpu::TextureFormat,
129
130 pub renderer: Arc<RwLock<Renderer>>,
132
133 pub surface_config: SurfaceConfig,
137}
138
139async fn request_adapter(
140 instance: &wgpu::Instance,
141 power_preference: wgpu::PowerPreference,
142 compatible_surface: Option<&wgpu::Surface<'_>>,
143 available_adapters: &[wgpu::Adapter],
144) -> Result<wgpu::Adapter, WgpuError> {
145 profiling::function_scope!();
146
147 let adapter = instance
148 .request_adapter(&wgpu::RequestAdapterOptions {
149 power_preference,
150 compatible_surface,
151 force_fallback_adapter: false,
156 apply_limit_buckets: false,
157 })
158 .await
159 .inspect_err(|_err| {
160 if cfg!(target_arch = "wasm32") {
161 } else if available_adapters.is_empty() {
163 if std::env::var("DYLD_LIBRARY_PATH").is_ok() {
164 log::warn!(
169 "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=''"
170 );
171 } else {
172 log::info!("No wgpu adapter found");
173 }
174 } else if available_adapters.len() == 1 {
175 log::info!(
176 "The only available wgpu adapter was not suitable: {}",
177 adapter_info_summary(&available_adapters[0].get_info())
178 );
179 } else {
180 log::info!(
181 "No suitable wgpu adapter found out of the {} available ones: {}",
182 available_adapters.len(),
183 describe_adapters(available_adapters)
184 );
185 }
186 })?;
187
188 if 1 < available_adapters.len() {
189 log::info!(
190 "There are {} available wgpu adapters: {}",
191 available_adapters.len(),
192 describe_adapters(available_adapters)
193 );
194 }
195
196 Ok(adapter)
197}
198
199impl RenderState {
200 pub async fn create(
205 config: &WgpuConfiguration,
206 instance: &wgpu::Instance,
207 compatible_surface: Option<&wgpu::Surface<'static>>,
208 options: RendererOptions,
209 ) -> Result<Self, WgpuError> {
210 profiling::scope!("RenderState::create"); #[cfg(not(target_arch = "wasm32"))]
214 let available_adapters = {
215 let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
216 create_new.instance_descriptor.backends
217 } else {
218 wgpu::Backends::all()
219 };
220
221 instance.enumerate_adapters(backends).await
222 };
223
224 let (instance, adapter, device, queue) = match config.wgpu_setup.clone() {
225 WgpuSetup::CreateNew(WgpuSetupCreateNew {
226 instance_descriptor: _,
227 display_handle: _,
228 power_preference,
229 native_adapter_selector: _native_adapter_selector,
230 device_descriptor,
231 }) => {
232 let adapter = {
233 #[cfg(target_arch = "wasm32")]
234 {
235 request_adapter(instance, power_preference, compatible_surface, &[]).await
236 }
237 #[cfg(not(target_arch = "wasm32"))]
238 if let Some(native_adapter_selector) = _native_adapter_selector {
239 native_adapter_selector(&available_adapters, compatible_surface)
240 .map_err(WgpuError::CustomNativeAdapterSelectionError)
241 } else {
242 request_adapter(
243 instance,
244 power_preference,
245 compatible_surface,
246 &available_adapters,
247 )
248 .await
249 }
250 }?;
251
252 let (device, queue) = {
253 profiling::scope!("request_device");
254 adapter
255 .request_device(&(*device_descriptor)(&adapter))
256 .await?
257 };
258
259 (instance.clone(), adapter, device, queue)
260 }
261 WgpuSetup::Existing(WgpuSetupExisting {
262 instance,
263 adapter,
264 device,
265 queue,
266 }) => (instance, adapter, device, queue),
267 };
268
269 log_adapter_info(&adapter.get_info());
270
271 let surface_formats = {
272 profiling::scope!("get_capabilities");
273 compatible_surface.map_or_else(
274 || vec![wgpu::TextureFormat::Rgba8Unorm],
275 |s| s.get_capabilities(&adapter).formats,
276 )
277 };
278 let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
279
280 let renderer = Renderer::new(&device, target_format, options);
281
282 #[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] Ok(Self {
286 instance,
287 adapter,
288 #[cfg(not(target_arch = "wasm32"))]
289 available_adapters,
290 device,
291 queue,
292 target_format,
293 renderer: Arc::new(RwLock::new(renderer)),
294 surface_config: config.surface,
295 })
296 }
297}
298
299fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
300 if adapters.is_empty() {
301 "(none)".to_owned()
302 } else if adapters.len() == 1 {
303 adapter_info_summary(&adapters[0].get_info())
304 } else {
305 adapters
306 .iter()
307 .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
308 .collect::<Vec<_>>()
309 .join(", ")
310 }
311}
312
313pub enum SurfaceErrorAction {
315 SkipFrame,
317
318 Reconfigure,
323
324 RecreateSurface,
330}
331
332#[derive(Clone)]
334pub struct WgpuConfiguration {
335 pub surface: SurfaceConfig,
340
341 pub wgpu_setup: WgpuSetup,
343
344 pub on_surface_status:
352 Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> SurfaceErrorAction + Send + Sync>,
353}
354
355#[test]
356fn wgpu_config_impl_send_sync() {
357 fn assert_send_sync<T: Send + Sync>() {}
358 assert_send_sync::<WgpuConfiguration>();
359}
360
361impl std::fmt::Debug for WgpuConfiguration {
362 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
363 let Self {
364 surface,
365 wgpu_setup,
366 on_surface_status: _,
367 } = self;
368 f.debug_struct("WgpuConfiguration")
369 .field("surface", &surface)
370 .field("wgpu_setup", &wgpu_setup)
371 .finish_non_exhaustive()
372 }
373}
374
375impl WgpuConfiguration {
376 #[inline]
377 pub fn with_surface_config(mut self, surface_config: SurfaceConfig) -> Self {
378 self.surface = surface_config;
379 self
380 }
381}
382
383impl Default for WgpuConfiguration {
384 fn default() -> Self {
385 Self {
386 surface: SurfaceConfig::HIGH_THROUGHPUT,
387
388 wgpu_setup: WgpuSetup::without_display_handle(),
391 on_surface_status: Arc::new(|status| match status {
392 wgpu::CurrentSurfaceTexture::Outdated => {
393 log::trace!("Dropped frame with error: {status:?}");
397 SurfaceErrorAction::Reconfigure
398 }
399 wgpu::CurrentSurfaceTexture::Lost => {
400 log::debug!("Dropped frame with error: {status:?}");
402 SurfaceErrorAction::RecreateSurface
403 }
404 wgpu::CurrentSurfaceTexture::Occluded => {
405 log::trace!("Skipping frame due to occlusion.");
407 SurfaceErrorAction::SkipFrame
408 }
409 _ => {
410 log::warn!("Dropped frame with error: {status:?}");
411 SurfaceErrorAction::SkipFrame
412 }
413 }),
414 }
415 }
416}
417
418pub fn preferred_framebuffer_format(
423 formats: &[wgpu::TextureFormat],
424) -> Result<wgpu::TextureFormat, WgpuError> {
425 for &format in formats {
426 if matches!(
427 format,
428 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
429 ) {
430 return Ok(format);
431 }
432 }
433
434 formats
435 .first()
436 .copied()
437 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
438}
439
440pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
442 match (depth_buffer, stencil_buffer) {
443 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
444 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
445 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
446 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
447 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
448 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
449 _ => None,
450 }
451}
452
453fn log_adapter_info(info: &wgpu::AdapterInfo) {
456 let summary = adapter_info_summary(info);
457
458 let is_test = cfg!(test); if info.device_type == wgpu::DeviceType::Cpu && !is_test {
461 log::warn!("Software rasterizer detected - loss of performance expected. {summary}");
462 } else {
463 log::debug!("wgpu adapter: {summary}");
464 }
465}
466
467pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
469 let wgpu::AdapterInfo {
470 name,
471 vendor,
472 device,
473 device_type,
474 driver,
475 driver_info,
476 backend,
477 device_pci_bus_id,
478 subgroup_min_size,
479 subgroup_max_size,
480 transient_saves_memory,
481 limit_bucket,
482 } = &info;
483
484 use std::fmt::Write as _;
490
491 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
492
493 if !name.is_empty() {
494 write!(summary, ", name: {name:?}").ok();
495 }
496 if !driver.is_empty() {
497 write!(summary, ", driver: {driver:?}").ok();
498 }
499 if !driver_info.is_empty() {
500 write!(summary, ", driver_info: {driver_info:?}").ok();
501 }
502 if *vendor != 0 {
503 #[cfg(not(target_arch = "wasm32"))]
504 {
505 write!(
506 summary,
507 ", vendor: {} (0x{vendor:04X})",
508 parse_vendor_id(*vendor)
509 )
510 .ok();
511 }
512 #[cfg(target_arch = "wasm32")]
513 {
514 write!(summary, ", vendor: 0x{vendor:04X}").ok();
515 }
516 }
517 if *device != 0 {
518 write!(summary, ", device: 0x{device:02X}").ok();
519 }
520 if !device_pci_bus_id.is_empty() {
521 write!(summary, ", pci_bus_id: {device_pci_bus_id:?}").ok();
522 }
523 if *subgroup_min_size != 0 || *subgroup_max_size != 0 {
524 write!(
525 summary,
526 ", subgroup_size: {subgroup_min_size}..={subgroup_max_size}"
527 )
528 .ok();
529 }
530 write!(
531 summary,
532 ", transient_saves_memory: {transient_saves_memory:?}"
533 )
534 .ok();
535 write!(summary, ", limit_bucket: {limit_bucket:?}").ok();
536
537 summary
538}
539
540#[cfg(not(target_arch = "wasm32"))]
542pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
543 match vendor_id {
544 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
545 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
546 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
547 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
548 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
549 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
550 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
551 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
552 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
553 _ => "Unknown",
554 }
555}