1#![doc = document_features::document_features!()]
17#![allow(unsafe_code)]
20
21pub use wgpu;
22
23mod renderer;
25
26mod setup;
27
28pub use renderer::*;
29pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting};
30
31pub mod capture;
33
34#[cfg(feature = "winit")]
36pub mod winit;
37
38use std::sync::Arc;
39
40use epaint::mutex::RwLock;
41
42#[derive(thiserror::Error, Debug)]
44pub enum WgpuError {
45 #[error("Failed to create wgpu adapter, no suitable adapter found: {0}")]
46 NoSuitableAdapterFound(String),
47
48 #[error("There was no valid format for the surface at all.")]
49 NoSurfaceFormatsAvailable,
50
51 #[error(transparent)]
52 RequestDeviceError(#[from] wgpu::RequestDeviceError),
53
54 #[error(transparent)]
55 CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
56
57 #[cfg(feature = "winit")]
58 #[error(transparent)]
59 HandleError(#[from] ::winit::raw_window_handle::HandleError),
60}
61
62#[derive(Clone)]
64pub struct RenderState {
65 pub adapter: wgpu::Adapter,
67
68 #[cfg(not(target_arch = "wasm32"))]
73 pub available_adapters: Vec<wgpu::Adapter>,
74
75 pub device: wgpu::Device,
77
78 pub queue: wgpu::Queue,
80
81 pub target_format: wgpu::TextureFormat,
83
84 pub renderer: Arc<RwLock<Renderer>>,
86}
87
88async fn request_adapter(
89 instance: &wgpu::Instance,
90 power_preference: wgpu::PowerPreference,
91 compatible_surface: Option<&wgpu::Surface<'_>>,
92 _available_adapters: &[wgpu::Adapter],
93) -> Result<wgpu::Adapter, WgpuError> {
94 profiling::function_scope!();
95
96 let adapter = instance
97 .request_adapter(&wgpu::RequestAdapterOptions {
98 power_preference,
99 compatible_surface,
100 force_fallback_adapter: false,
105 })
106 .await
107 .ok_or_else(|| {
108 #[cfg(not(target_arch = "wasm32"))]
109 if _available_adapters.is_empty() {
110 log::info!("No wgpu adapters found");
111 } else if _available_adapters.len() == 1 {
112 log::info!(
113 "The only available wgpu adapter was not suitable: {}",
114 adapter_info_summary(&_available_adapters[0].get_info())
115 );
116 } else {
117 log::info!(
118 "No suitable wgpu adapter found out of the {} available ones: {}",
119 _available_adapters.len(),
120 describe_adapters(_available_adapters)
121 );
122 }
123
124 WgpuError::NoSuitableAdapterFound("`request_adapters` returned `None`".to_owned())
125 })?;
126
127 #[cfg(target_arch = "wasm32")]
128 log::debug!(
129 "Picked wgpu adapter: {}",
130 adapter_info_summary(&adapter.get_info())
131 );
132
133 #[cfg(not(target_arch = "wasm32"))]
134 if _available_adapters.len() == 1 {
135 log::debug!(
136 "Picked the only available wgpu adapter: {}",
137 adapter_info_summary(&adapter.get_info())
138 );
139 } else {
140 log::info!(
141 "There were {} available wgpu adapters: {}",
142 _available_adapters.len(),
143 describe_adapters(_available_adapters)
144 );
145 log::debug!(
146 "Picked wgpu adapter: {}",
147 adapter_info_summary(&adapter.get_info())
148 );
149 }
150
151 Ok(adapter)
152}
153
154impl RenderState {
155 pub async fn create(
160 config: &WgpuConfiguration,
161 instance: &wgpu::Instance,
162 compatible_surface: Option<&wgpu::Surface<'static>>,
163 depth_format: Option<wgpu::TextureFormat>,
164 msaa_samples: u32,
165 dithering: bool,
166 ) -> Result<Self, WgpuError> {
167 profiling::scope!("RenderState::create"); #[cfg(not(target_arch = "wasm32"))]
171 let available_adapters = {
172 let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
173 create_new.instance_descriptor.backends
174 } else {
175 wgpu::Backends::all()
176 };
177
178 instance.enumerate_adapters(backends)
179 };
180
181 let (adapter, device, queue) = match config.wgpu_setup.clone() {
182 WgpuSetup::CreateNew(WgpuSetupCreateNew {
183 instance_descriptor: _,
184 power_preference,
185 native_adapter_selector: _native_adapter_selector,
186 device_descriptor,
187 trace_path,
188 }) => {
189 let adapter = {
190 #[cfg(target_arch = "wasm32")]
191 {
192 request_adapter(instance, power_preference, compatible_surface, &[]).await
193 }
194 #[cfg(not(target_arch = "wasm32"))]
195 if let Some(native_adapter_selector) = _native_adapter_selector {
196 native_adapter_selector(&available_adapters, compatible_surface)
197 .map_err(WgpuError::NoSuitableAdapterFound)
198 } else {
199 request_adapter(
200 instance,
201 power_preference,
202 compatible_surface,
203 &available_adapters,
204 )
205 .await
206 }
207 }?;
208
209 let (device, queue) = {
210 profiling::scope!("request_device");
211 adapter
212 .request_device(&(*device_descriptor)(&adapter), trace_path.as_deref())
213 .await?
214 };
215
216 (adapter, device, queue)
217 }
218 WgpuSetup::Existing(WgpuSetupExisting {
219 instance: _,
220 adapter,
221 device,
222 queue,
223 }) => (adapter, device, queue),
224 };
225
226 let surface_formats = {
227 profiling::scope!("get_capabilities");
228 compatible_surface.map_or_else(
229 || vec![wgpu::TextureFormat::Rgba8Unorm],
230 |s| s.get_capabilities(&adapter).formats,
231 )
232 };
233 let target_format = crate::preferred_framebuffer_format(&surface_formats)?;
234
235 let renderer = Renderer::new(
236 &device,
237 target_format,
238 depth_format,
239 msaa_samples,
240 dithering,
241 );
242
243 #[allow(clippy::arc_with_non_send_sync)]
246 Ok(Self {
247 adapter,
248 #[cfg(not(target_arch = "wasm32"))]
249 available_adapters,
250 device,
251 queue,
252 target_format,
253 renderer: Arc::new(RwLock::new(renderer)),
254 })
255 }
256}
257
258#[cfg(not(target_arch = "wasm32"))]
259fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
260 if adapters.is_empty() {
261 "(none)".to_owned()
262 } else if adapters.len() == 1 {
263 adapter_info_summary(&adapters[0].get_info())
264 } else {
265 adapters
266 .iter()
267 .map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
268 .collect::<Vec<_>>()
269 .join(", ")
270 }
271}
272
273pub enum SurfaceErrorAction {
275 SkipFrame,
277
278 RecreateSurface,
280}
281
282#[derive(Clone)]
284pub struct WgpuConfiguration {
285 pub present_mode: wgpu::PresentMode,
287
288 pub desired_maximum_frame_latency: Option<u32>,
296
297 pub wgpu_setup: WgpuSetup,
299
300 pub on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction + Send + Sync>,
302}
303
304#[test]
305fn wgpu_config_impl_send_sync() {
306 fn assert_send_sync<T: Send + Sync>() {}
307 assert_send_sync::<WgpuConfiguration>();
308}
309
310impl std::fmt::Debug for WgpuConfiguration {
311 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
312 let Self {
313 present_mode,
314 desired_maximum_frame_latency,
315 wgpu_setup,
316 on_surface_error: _,
317 } = self;
318 f.debug_struct("WgpuConfiguration")
319 .field("present_mode", &present_mode)
320 .field(
321 "desired_maximum_frame_latency",
322 &desired_maximum_frame_latency,
323 )
324 .field("wgpu_setup", &wgpu_setup)
325 .finish_non_exhaustive()
326 }
327}
328
329impl Default for WgpuConfiguration {
330 fn default() -> Self {
331 Self {
332 present_mode: wgpu::PresentMode::AutoVsync,
333 desired_maximum_frame_latency: None,
334 wgpu_setup: Default::default(),
335 on_surface_error: Arc::new(|err| {
336 if err == wgpu::SurfaceError::Outdated {
337 } else {
341 log::warn!("Dropped frame with error: {err}");
342 }
343 SurfaceErrorAction::SkipFrame
344 }),
345 }
346 }
347}
348
349pub fn preferred_framebuffer_format(
354 formats: &[wgpu::TextureFormat],
355) -> Result<wgpu::TextureFormat, WgpuError> {
356 for &format in formats {
357 if matches!(
358 format,
359 wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
360 ) {
361 return Ok(format);
362 }
363 }
364
365 formats
366 .first()
367 .copied()
368 .ok_or(WgpuError::NoSurfaceFormatsAvailable)
369}
370
371pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
373 match (depth_buffer, stencil_buffer) {
374 (0, 8) => Some(wgpu::TextureFormat::Stencil8),
375 (16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
376 (24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
377 (24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
378 (32, 0) => Some(wgpu::TextureFormat::Depth32Float),
379 (32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
380 _ => None,
381 }
382}
383
384pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
388 let wgpu::AdapterInfo {
389 name,
390 vendor,
391 device,
392 device_type,
393 driver,
394 driver_info,
395 backend,
396 } = &info;
397
398 let mut summary = format!("backend: {backend:?}, device_type: {device_type:?}");
404
405 if !name.is_empty() {
406 summary += &format!(", name: {name:?}");
407 }
408 if !driver.is_empty() {
409 summary += &format!(", driver: {driver:?}");
410 }
411 if !driver_info.is_empty() {
412 summary += &format!(", driver_info: {driver_info:?}");
413 }
414 if *vendor != 0 {
415 #[cfg(not(target_arch = "wasm32"))]
416 {
417 summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
418 }
419 #[cfg(target_arch = "wasm32")]
420 {
421 summary += &format!(", vendor: 0x{vendor:04X}");
422 }
423 }
424 if *device != 0 {
425 summary += &format!(", device: 0x{device:02X}");
426 }
427
428 summary
429}
430
431#[cfg(not(target_arch = "wasm32"))]
433pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
434 match vendor_id {
435 wgpu::hal::auxil::db::amd::VENDOR => "AMD",
436 wgpu::hal::auxil::db::apple::VENDOR => "Apple",
437 wgpu::hal::auxil::db::arm::VENDOR => "ARM",
438 wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
439 wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
440 wgpu::hal::auxil::db::intel::VENDOR => "Intel",
441 wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
442 wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
443 wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
444 _ => "Unknown",
445 }
446}