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,
271}
272
273#[derive(Clone)]
275pub struct WgpuConfiguration {
276 pub present_mode: wgpu::PresentMode,
278
279 pub desired_maximum_frame_latency: Option<u32>,
287
288 pub wgpu_setup: WgpuSetup,
290
291 pub on_surface_status:
299 Arc<dyn Fn(&wgpu::CurrentSurfaceTexture) -> SurfaceErrorAction + Send + Sync>,
300}
301
302#[test]
303fn wgpu_config_impl_send_sync() {
304 fn assert_send_sync<T: Send + Sync>() {}
305 assert_send_sync::<WgpuConfiguration>();
306}
307
308impl std::fmt::Debug for WgpuConfiguration {
309 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
310 let Self {
311 present_mode,
312 desired_maximum_frame_latency,
313 wgpu_setup,
314 on_surface_status: _,
315 } = self;
316 f.debug_struct("WgpuConfiguration")
317 .field("present_mode", &present_mode)
318 .field(
319 "desired_maximum_frame_latency",
320 &desired_maximum_frame_latency,
321 )
322 .field("wgpu_setup", &wgpu_setup)
323 .finish_non_exhaustive()
324 }
325}
326
327impl Default for WgpuConfiguration {
328 fn default() -> Self {
329 Self {
330 present_mode: wgpu::PresentMode::AutoVsync,
331 desired_maximum_frame_latency: None,
332 wgpu_setup: WgpuSetup::without_display_handle(),
335 on_surface_status: Arc::new(|status| {
336 match status {
337 wgpu::CurrentSurfaceTexture::Outdated => {
338 }
342 wgpu::CurrentSurfaceTexture::Occluded => {
343 log::debug!("Dropped frame with error: {status:?}");
345 }
346 _ => {
347 log::warn!("Dropped frame with error: {status:?}");
348 }
349 }
350
351 SurfaceErrorAction::SkipFrame
352 }),
353 }
354 }
355}
356
357pub fn preferred_framebuffer_format(
362 formats: &[wgpu::TextureFormat],
363) -> Result<wgpu::TextureFormat, WgpuError> {
364 for &format in formats {
365 if matches!(
366 format,
367 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
368 ) {
369 return Ok(format);
370 }
371 }
372
373 formats
374 .first()
375 .copied()
376 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
377}
378
379pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
381 match (depth_buffer, stencil_buffer) {
382 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
383 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
384 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
385 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
386 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
387 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
388 _ => None,
389 }
390}
391
392fn log_adapter_info(info: &wgpu::AdapterInfo) {
395 let summary = adapter_info_summary(info);
396
397 let is_test = cfg!(test); if info.device_type == wgpu::DeviceType::Cpu && !is_test {
400 log::warn!("Software rasterizer detected - loss of performance expected. {summary}");
401 } else {
402 log::debug!("wgpu adapter: {summary}");
403 }
404}
405
406pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
408 let wgpu::AdapterInfo {
409 name,
410 vendor,
411 device,
412 device_type,
413 driver,
414 driver_info,
415 backend,
416 device_pci_bus_id,
417 subgroup_min_size,
418 subgroup_max_size,
419 transient_saves_memory,
420 } = &info;
421
422 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
428
429 if !name.is_empty() {
430 summary += &format!(", name: {name:?}");
431 }
432 if !driver.is_empty() {
433 summary += &format!(", driver: {driver:?}");
434 }
435 if !driver_info.is_empty() {
436 summary += &format!(", driver_info: {driver_info:?}");
437 }
438 if *vendor != 0 {
439 #[cfg(not(target_arch = "wasm32"))]
440 {
441 summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
442 }
443 #[cfg(target_arch = "wasm32")]
444 {
445 summary += &format!(", vendor: 0x{vendor:04X}");
446 }
447 }
448 if *device != 0 {
449 summary += &format!(", device: 0x{device:02X}");
450 }
451 if !device_pci_bus_id.is_empty() {
452 summary += &format!(", pci_bus_id: {device_pci_bus_id:?}");
453 }
454 if *subgroup_min_size != 0 || *subgroup_max_size != 0 {
455 summary += &format!(", subgroup_size: {subgroup_min_size}..={subgroup_max_size}");
456 }
457 summary += &format!(", transient_saves_memory: {transient_saves_memory}");
458
459 summary
460}
461
462#[cfg(not(target_arch = "wasm32"))]
464pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
465 match vendor_id {
466 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
467 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
468 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
469 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
470 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
471 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
472 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
473 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
474 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
475 _ => "Unknown",
476 }
477}