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 1 < available_adapters.len() {
142 log::info!(
143 "There are {} available wgpu adapters: {}",
144 available_adapters.len(),
145 describe_adapters(available_adapters)
146 );
147 }
148
149 Ok(adapter)
150}
151
152impl RenderState {
153 pub async fn create(
158 config: &WgpuConfiguration,
159 instance: &wgpu::Instance,
160 compatible_surface: Option<&wgpu::Surface<'static>>,
161 options: RendererOptions,
162 ) -> Result<Self, WgpuError> {
163 profiling::scope!("RenderState::create"); #[cfg(not(target_arch = "wasm32"))]
167 let available_adapters = {
168 let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
169 create_new.instance_descriptor.backends
170 } else {
171 wgpu::Backends::all()
172 };
173
174 instance.enumerate_adapters(backends).await
175 };
176
177 let (adapter, device, queue) = match config.wgpu_setup.clone() {
178 WgpuSetup::CreateNew(WgpuSetupCreateNew {
179 instance_descriptor: _,
180 display_handle: _,
181 power_preference,
182 native_adapter_selector: _native_adapter_selector,
183 device_descriptor,
184 }) => {
185 let adapter = {
186 #[cfg(target_arch = "wasm32")]
187 {
188 request_adapter(instance, power_preference, compatible_surface, &[]).await
189 }
190 #[cfg(not(target_arch = "wasm32"))]
191 if let Some(native_adapter_selector) = _native_adapter_selector {
192 native_adapter_selector(&available_adapters, compatible_surface)
193 .map_err(WgpuError::CustomNativeAdapterSelectionError)
194 } else {
195 request_adapter(
196 instance,
197 power_preference,
198 compatible_surface,
199 &available_adapters,
200 )
201 .await
202 }
203 }?;
204
205 let (device, queue) = {
206 profiling::scope!("request_device");
207 adapter
208 .request_device(&(*device_descriptor)(&adapter))
209 .await?
210 };
211
212 (adapter, device, queue)
213 }
214 WgpuSetup::Existing(WgpuSetupExisting {
215 instance: _,
216 adapter,
217 device,
218 queue,
219 }) => (adapter, device, queue),
220 };
221
222 log_adapter_info(&adapter.get_info());
223
224 let surface_formats = {
225 profiling::scope!("get_capabilities");
226 compatible_surface.map_or_else(
227 || vec![wgpu::TextureFormat::Rgba8Unorm],
228 |s| s.get_capabilities(&adapter).formats,
229 )
230 };
231 let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
232
233 let renderer = Renderer::new(&device, target_format, options);
234
235 #[allow(clippy::allow_attributes, clippy::arc_with_non_send_sync)] Ok(Self {
239 adapter,
240 #[cfg(not(target_arch = "wasm32"))]
241 available_adapters,
242 device,
243 queue,
244 target_format,
245 renderer: Arc::new(RwLock::new(renderer)),
246 })
247 }
248}
249
250fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
251 if adapters.is_empty() {
252 "(none)".to_owned()
253 } else if adapters.len() == 1 {
254 adapter_info_summary(&adapters[0].get_info())
255 } else {
256 adapters
257 .iter()
258 .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
259 .collect::<Vec<_>>()
260 .join(", ")
261 }
262}
263
264pub enum SurfaceErrorAction {
266 SkipFrame,
268
269 RecreateSurface,
276}
277
278#[derive(Clone)]
280pub struct WgpuConfiguration {
281 pub present_mode: wgpu::PresentMode,
283
284 pub desired_maximum_frame_latency: Option<u32>,
292
293 pub wgpu_setup: WgpuSetup,
295
296 pub on_surface_status:
304 Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> 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_status: _,
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: WgpuSetup::without_display_handle(),
340 on_surface_status: Arc::new(|status| match status {
341 wgpu::CurrentSurfaceTexture::Outdated => {
342 log::trace!("Dropped frame with error: {status:?}");
346 SurfaceErrorAction::RecreateSurface
347 }
348 wgpu::CurrentSurfaceTexture::Lost => {
349 log::debug!("Dropped frame with error: {status:?}");
351 SurfaceErrorAction::RecreateSurface
352 }
353 wgpu::CurrentSurfaceTexture::Occluded => {
354 log::trace!("Skipping frame due to occlusion.");
356 SurfaceErrorAction::SkipFrame
357 }
358 _ => {
359 log::warn!("Dropped frame with error: {status:?}");
360 SurfaceErrorAction::SkipFrame
361 }
362 }),
363 }
364 }
365}
366
367pub fn preferred_framebuffer_format(
372 formats: &[wgpu::TextureFormat],
373) -> Result<wgpu::TextureFormat, WgpuError> {
374 for &format in formats {
375 if matches!(
376 format,
377 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
378 ) {
379 return Ok(format);
380 }
381 }
382
383 formats
384 .first()
385 .copied()
386 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
387}
388
389pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
391 match (depth_buffer, stencil_buffer) {
392 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
393 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
394 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
395 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
396 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
397 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
398 _ => None,
399 }
400}
401
402fn log_adapter_info(info: &wgpu::AdapterInfo) {
405 let summary = adapter_info_summary(info);
406
407 let is_test = cfg!(test); if info.device_type == wgpu::DeviceType::Cpu && !is_test {
410 log::warn!("Software rasterizer detected - loss of performance expected. {summary}");
411 } else {
412 log::debug!("wgpu adapter: {summary}");
413 }
414}
415
416pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
418 let wgpu::AdapterInfo {
419 name,
420 vendor,
421 device,
422 device_type,
423 driver,
424 driver_info,
425 backend,
426 device_pci_bus_id,
427 subgroup_min_size,
428 subgroup_max_size,
429 transient_saves_memory,
430 } = &info;
431
432 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
438
439 if !name.is_empty() {
440 summary += &format!(", name: {name:?}");
441 }
442 if !driver.is_empty() {
443 summary += &format!(", driver: {driver:?}");
444 }
445 if !driver_info.is_empty() {
446 summary += &format!(", driver_info: {driver_info:?}");
447 }
448 if *vendor != 0 {
449 #[cfg(not(target_arch = "wasm32"))]
450 {
451 summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
452 }
453 #[cfg(target_arch = "wasm32")]
454 {
455 summary += &format!(", vendor: 0x{vendor:04X}");
456 }
457 }
458 if *device != 0 {
459 summary += &format!(", device: 0x{device:02X}");
460 }
461 if !device_pci_bus_id.is_empty() {
462 summary += &format!(", pci_bus_id: {device_pci_bus_id:?}");
463 }
464 if *subgroup_min_size != 0 || *subgroup_max_size != 0 {
465 summary += &format!(", subgroup_size: {subgroup_min_size}..={subgroup_max_size}");
466 }
467 summary += &format!(", transient_saves_memory: {transient_saves_memory}");
468
469 summary
470}
471
472#[cfg(not(target_arch = "wasm32"))]
474pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
475 match vendor_id {
476 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
477 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
478 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
479 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
480 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
481 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
482 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
483 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
484 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
485 _ => "Unknown",
486 }
487}