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