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 device: wgpu::Device,
120
121 pub queue: wgpu::Queue,
123
124 pub target_format: wgpu::TextureFormat,
126
127 pub renderer: Arc<RwLock<Renderer>>,
129
130 pub surface_config: SurfaceConfig,
134}
135
136async fn request_adapter(
137 instance: &wgpu::Instance,
138 power_preference: wgpu::PowerPreference,
139 compatible_surface: Option<&wgpu::Surface<'_>>,
140 available_adapters: &[wgpu::Adapter],
141) -> Result<wgpu::Adapter, WgpuError> {
142 profiling::function_scope!();
143
144 let adapter = instance
145 .request_adapter(&wgpu::RequestAdapterOptions {
146 power_preference,
147 compatible_surface,
148 force_fallback_adapter: false,
153 })
154 .await
155 .inspect_err(|_err| {
156 if cfg!(target_arch = "wasm32") {
157 } else if available_adapters.is_empty() {
159 if std::env::var("DYLD_LIBRARY_PATH").is_ok() {
160 log::warn!(
165 "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=''"
166 );
167 } else {
168 log::info!("No wgpu adapter found");
169 }
170 } else if available_adapters.len() == 1 {
171 log::info!(
172 "The only available wgpu adapter was not suitable: {}",
173 adapter_info_summary(&available_adapters[0].get_info())
174 );
175 } else {
176 log::info!(
177 "No suitable wgpu adapter found out of the {} available ones: {}",
178 available_adapters.len(),
179 describe_adapters(available_adapters)
180 );
181 }
182 })?;
183
184 if 1 < available_adapters.len() {
185 log::info!(
186 "There are {} available wgpu adapters: {}",
187 available_adapters.len(),
188 describe_adapters(available_adapters)
189 );
190 }
191
192 Ok(adapter)
193}
194
195impl RenderState {
196 pub async fn create(
201 config: &WgpuConfiguration,
202 instance: &wgpu::Instance,
203 compatible_surface: Option<&wgpu::Surface<'static>>,
204 options: RendererOptions,
205 ) -> Result<Self, WgpuError> {
206 profiling::scope!("RenderState::create"); #[cfg(not(target_arch = "wasm32"))]
210 let available_adapters = {
211 let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
212 create_new.instance_descriptor.backends
213 } else {
214 wgpu::Backends::all()
215 };
216
217 instance.enumerate_adapters(backends).await
218 };
219
220 let (adapter, device, queue) = match config.wgpu_setup.clone() {
221 WgpuSetup::CreateNew(WgpuSetupCreateNew {
222 instance_descriptor: _,
223 display_handle: _,
224 power_preference,
225 native_adapter_selector: _native_adapter_selector,
226 device_descriptor,
227 }) => {
228 let adapter = {
229 #[cfg(target_arch = "wasm32")]
230 {
231 request_adapter(instance, power_preference, compatible_surface, &[]).await
232 }
233 #[cfg(not(target_arch = "wasm32"))]
234 if let Some(native_adapter_selector) = _native_adapter_selector {
235 native_adapter_selector(&available_adapters, compatible_surface)
236 .map_err(WgpuError::CustomNativeAdapterSelectionError)
237 } else {
238 request_adapter(
239 instance,
240 power_preference,
241 compatible_surface,
242 &available_adapters,
243 )
244 .await
245 }
246 }?;
247
248 let (device, queue) = {
249 profiling::scope!("request_device");
250 adapter
251 .request_device(&(*device_descriptor)(&adapter))
252 .await?
253 };
254
255 (adapter, device, queue)
256 }
257 WgpuSetup::Existing(WgpuSetupExisting {
258 instance: _,
259 adapter,
260 device,
261 queue,
262 }) => (adapter, device, queue),
263 };
264
265 log_adapter_info(&adapter.get_info());
266
267 let surface_formats = {
268 profiling::scope!("get_capabilities");
269 compatible_surface.map_or_else(
270 || vec![wgpu::TextureFormat::Rgba8Unorm],
271 |s| s.get_capabilities(&adapter).formats,
272 )
273 };
274 let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
275
276 let renderer = Renderer::new(&device, target_format, options);
277
278 #[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] Ok(Self {
282 adapter,
283 #[cfg(not(target_arch = "wasm32"))]
284 available_adapters,
285 device,
286 queue,
287 target_format,
288 renderer: Arc::new(RwLock::new(renderer)),
289 surface_config: config.surface,
290 })
291 }
292}
293
294fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
295 if adapters.is_empty() {
296 "(none)".to_owned()
297 } else if adapters.len() == 1 {
298 adapter_info_summary(&adapters[0].get_info())
299 } else {
300 adapters
301 .iter()
302 .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
303 .collect::<Vec<_>>()
304 .join(", ")
305 }
306}
307
308pub enum SurfaceErrorAction {
310 SkipFrame,
312
313 Reconfigure,
318
319 RecreateSurface,
325}
326
327#[derive(Clone)]
329pub struct WgpuConfiguration {
330 pub surface: SurfaceConfig,
335
336 pub wgpu_setup: WgpuSetup,
338
339 pub on_surface_status:
347 Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> SurfaceErrorAction + Send + Sync>,
348}
349
350#[test]
351fn wgpu_config_impl_send_sync() {
352 fn assert_send_sync<T: Send + Sync>() {}
353 assert_send_sync::<WgpuConfiguration>();
354}
355
356impl std::fmt::Debug for WgpuConfiguration {
357 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
358 let Self {
359 surface,
360 wgpu_setup,
361 on_surface_status: _,
362 } = self;
363 f.debug_struct("WgpuConfiguration")
364 .field("surface", &surface)
365 .field("wgpu_setup", &wgpu_setup)
366 .finish_non_exhaustive()
367 }
368}
369
370impl WgpuConfiguration {
371 #[inline]
372 pub fn with_surface_config(mut self, surface_config: SurfaceConfig) -> Self {
373 self.surface = surface_config;
374 self
375 }
376}
377
378impl Default for WgpuConfiguration {
379 fn default() -> Self {
380 Self {
381 surface: SurfaceConfig::HIGH_THROUGHPUT,
382
383 wgpu_setup: WgpuSetup::without_display_handle(),
386 on_surface_status: Arc::new(|status| match status {
387 wgpu::CurrentSurfaceTexture::Outdated => {
388 log::trace!("Dropped frame with error: {status:?}");
392 SurfaceErrorAction::Reconfigure
393 }
394 wgpu::CurrentSurfaceTexture::Lost => {
395 log::debug!("Dropped frame with error: {status:?}");
397 SurfaceErrorAction::RecreateSurface
398 }
399 wgpu::CurrentSurfaceTexture::Occluded => {
400 log::trace!("Skipping frame due to occlusion.");
402 SurfaceErrorAction::SkipFrame
403 }
404 _ => {
405 log::warn!("Dropped frame with error: {status:?}");
406 SurfaceErrorAction::SkipFrame
407 }
408 }),
409 }
410 }
411}
412
413pub fn preferred_framebuffer_format(
418 formats: &[wgpu::TextureFormat],
419) -> Result<wgpu::TextureFormat, WgpuError> {
420 for &format in formats {
421 if matches!(
422 format,
423 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
424 ) {
425 return Ok(format);
426 }
427 }
428
429 formats
430 .first()
431 .copied()
432 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
433}
434
435pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
437 match (depth_buffer, stencil_buffer) {
438 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
439 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
440 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
441 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
442 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
443 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
444 _ => None,
445 }
446}
447
448fn log_adapter_info(info: &wgpu::AdapterInfo) {
451 let summary = adapter_info_summary(info);
452
453 let is_test = cfg!(test); if info.device_type == wgpu::DeviceType::Cpu && !is_test {
456 log::warn!("Software rasterizer detected - loss of performance expected. {summary}");
457 } else {
458 log::debug!("wgpu adapter: {summary}");
459 }
460}
461
462pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
464 let wgpu::AdapterInfo {
465 name,
466 vendor,
467 device,
468 device_type,
469 driver,
470 driver_info,
471 backend,
472 device_pci_bus_id,
473 subgroup_min_size,
474 subgroup_max_size,
475 transient_saves_memory,
476 } = &info;
477
478 use std::fmt::Write as _;
484
485 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
486
487 if !name.is_empty() {
488 write!(summary, ", name: {name:?}").ok();
489 }
490 if !driver.is_empty() {
491 write!(summary, ", driver: {driver:?}").ok();
492 }
493 if !driver_info.is_empty() {
494 write!(summary, ", driver_info: {driver_info:?}").ok();
495 }
496 if *vendor != 0 {
497 #[cfg(not(target_arch = "wasm32"))]
498 {
499 write!(
500 summary,
501 ", vendor: {} (0x{vendor:04X})",
502 parse_vendor_id(*vendor)
503 )
504 .ok();
505 }
506 #[cfg(target_arch = "wasm32")]
507 {
508 write!(summary, ", vendor: 0x{vendor:04X}").ok();
509 }
510 }
511 if *device != 0 {
512 write!(summary, ", device: 0x{device:02X}").ok();
513 }
514 if !device_pci_bus_id.is_empty() {
515 write!(summary, ", pci_bus_id: {device_pci_bus_id:?}").ok();
516 }
517 if *subgroup_min_size != 0 || *subgroup_max_size != 0 {
518 write!(
519 summary,
520 ", subgroup_size: {subgroup_min_size}..={subgroup_max_size}"
521 )
522 .ok();
523 }
524 write!(
525 summary,
526 ", transient_saves_memory: {transient_saves_memory}"
527 )
528 .ok();
529
530 summary
531}
532
533#[cfg(not(target_arch = "wasm32"))]
535pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
536 match vendor_id {
537 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
538 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
539 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
540 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
541 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
542 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
543 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
544 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
545 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
546 _ => "Unknown",
547 }
548}