Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-renderer/src/rendering/wgpu/resources.rs
1pub mod quad;
2pub mod uniforms;
3 
4use std::cell::RefCell;
5use std::collections::HashSet;
6use std::sync::{
7 atomic::{AtomicBool, Ordering},
8 Arc,
9};
10 
11use crate::rendering::OnGPUDeviceSelected;
12use crate::windowing;
13use crate::{r#async::block_on, rendering::GPUPowerPreference};
14use anyhow::{anyhow, Result};
15use itertools::Itertools;
16use lazy_static::lazy_static;
17use pathfinder_geometry::vector::Vector2F;
18use strato_ui_core::rendering::{GPUBackend, GPUDeviceInfo, GPUDeviceType};
19use thiserror::Error;
20use version_compare::Version;
21use wgpu::{
22 Adapter, Backend, CompositeAlphaMode, CurrentSurfaceTexture, Device, DeviceType, PresentMode,
23 Queue, Surface, SurfaceConfiguration,
24};
25 
26/// A mostly-arbitrary value to use as the height/width of a surface when
27/// creating a default surface configuration.
28///
29/// 4 was chosen here because sometimes drivers care that things are a
30/// multiple of 2 or 4, so this seemed like a safe choice, while being
31/// small enough that any buffers that get allocated are tiny and quick to
32/// create and destroy.
33const SURFACE_SIZE_FOR_TESTING: u32 = 4;
34 
35lazy_static! {
36 /// The minimum supported driver version for lavapipe, the Vulkan version
37 /// of Mesa's llvmpipe software renderer.
38 ///
39 /// While lavapipe is theoretically Vulkan 1.3 compatible starting in version
40 /// 22.1.2, in practice, Warp windows don't render properly until 24.0.2.
41 static ref MIN_SUPPORTED_LAVAPIPE_VERSION: Version<'static> = Version::from("24.0.2")
42 .expect("should not fail to parse version");
43 
44 /// The minimum supported driver version for Vulkan-backed Intel UHD integrated graphics.
45 ///
46 /// Some issues we've seen: PLAT-744 and PLAT-599.
47 /// Mesa changelog mentions a fix for flickering on Intel UHD:
48 /// https://docs.mesa3d.org/relnotes/21.3.6.html#:~:text=Flickering%20Intel%20Uhd%20620%20Graphics
49 static ref MIN_SUPPORTED_INTEL_UHD_VERSION: Version<'static> = Version::from("21.3.6")
50 .expect("should not fail to parse version");
51 
52 /// Nvidia drivers version 535 have problems with Wayland window managers, e.g. PLAT-667 and
53 /// PLAT-674.
54 static ref MIN_SUPPORTED_NVIDIA_VERSION: Version<'static> = Version::from("545")
55 .expect("should not fail to parse version");
56 
57 static ref MAX_SUPPORTED_NVIDIA_VERSION_ON_WINDOWS: Version<'static> = Version::from("572")
58 .expect("should not fail to parse version");
59}
60 
61/// Set of resources needed to render using wgpu.
62pub struct Resources {
63 pub device: wgpu::Device,
64 pub device_lost: Arc<AtomicBool>,
65 pub queue: Queue,
66 pub adapter: Adapter,
67 pub surface: Surface<'static>,
68 pub surface_config: RefCell<SurfaceConfiguration>,
69 pub supported_backends: Vec<wgpu::Backend>,
70 uniforms: uniforms::Uniforms,
71 quad: quad::Resources,
72}
73 
74impl Resources {
75 /// Attempts to construct a new instance of [`Resources`] via the provided `window_handle`.
76 pub fn new(
77 window_handle: impl Into<wgpu::SurfaceTarget<'static>> + wgpu::rwh::HasDisplayHandle,
78 gpu_power_preference: GPUPowerPreference,
79 backend_preference: Option<wgpu::Backend>,
80 on_gpu_device_selected: &OnGPUDeviceSelected,
81 initial_surface_size: Vector2F,
82 downrank_non_nvidia_vulkan_adapters: bool,
83 ) -> Result<Self> {
84 let windowing_system = window_handle.display_handle()?.as_raw().try_into().ok();
85 
86 let instance = super::get_wgpu_instance();
87 let surface = instance.create_surface(window_handle)?;
88 
89 let backends = super::wgpu_backend_options();
90 // All of the WGPU initialization functions are asynchronous. For simplicity while
91 // prototyping, we just use `block_on` to force them to be synchronous.
92 block_on(async {
93 let (adapter, device, queue, surface_config, supported_backends) = select_adapter(
94 &instance,
95 &surface,
96 backends,
97 backend_preference,
98 gpu_power_preference,
99 initial_surface_size,
100 windowing_system,
101 downrank_non_nvidia_vulkan_adapters,
102 )
103 .await
104 .ok_or_else(|| anyhow!("No usable wgpu adapter was found"))?;
105 let adapter_info = adapter.get_info();
106 
107 log::info!(
108 "Using {:?} {:?} ({}) for rendering new window.",
109 adapter_info.backend,
110 adapter_info.device_type,
111 adapter_info.name,
112 );
113 
114 on_gpu_device_selected(device_info_from_adapter_info(adapter_info));
115 
116 let uniforms = uniforms::Uniforms::new(&device);
117 let quad = quad::Resources::new(&device);
118 
119 let device_lost = Arc::new(AtomicBool::new(false));
120 
121 let device_lost_clone = device_lost.clone();
122 device.set_device_lost_callback(move |device_lost_reason, message| {
123 device_lost_clone.store(true, Ordering::SeqCst);
124 log::warn!("The current device is lost. Reason: {device_lost_reason:?}. Message: {message}")
125 });
126 
127 Ok(Self {
128 device,
129 device_lost,
130 queue,
131 adapter,
132 surface,
133 surface_config: surface_config.into(),
134 supported_backends: supported_backends.into_iter().collect(),
135 uniforms,
136 quad,
137 })
138 })
139 }
140 
141 pub fn uniform_bind_group_layout(&self) -> &wgpu::BindGroupLayout {
142 self.uniforms.bind_group_layout()
143 }
144 
145 pub fn configure_render_pass<'a>(
146 &'a self,
147 render_pass: &mut wgpu::RenderPass<'a>,
148 drawable_size: Vector2F,
149 ) {
150 self.uniforms
151 .configure_render_pass(render_pass, drawable_size, self);
152 self.quad.configure_render_pass(render_pass);
153 }
154 
155 /// Updates the size of the underlying surface.
156 pub fn update_surface_size(&self, size: Vector2F) -> Result<(), SurfaceConfigureError> {
157 if size.x() > 0. && size.y() > 0. {
158 let mut surface_config = self.surface_config.borrow_mut();
159 surface_config.width = size.x() as u32;
160 surface_config.height = size.y() as u32;
161 block_on(configure_surface(
162 &self.surface,
163 &self.device,
164 &surface_config,
165 ))
166 } else {
167 Ok(())
168 }
169 }
170 
171 /// Gets the next surface texture to render to.
172 pub fn get_surface_texture(&self) -> Result<wgpu::SurfaceTexture, GetSurfaceTextureError> {
173 let Resources {
174 surface,
175 device,
176 surface_config,
177 ..
178 } = self;
179 
180 let error = match get_surface_texture(surface) {
181 Ok(texture) => return Ok(texture),
182 Err(error) => error,
183 };
184 
185 log::warn!("Encountered error while getting the next swap chain texture: {error:#}");
186 match error {
187 GetSurfaceTextureError::Timeout
188 | GetSurfaceTextureError::Validation
189 | GetSurfaceTextureError::Occluded
190 | GetSurfaceTextureError::ConfigurationError(_) => {
191 // Skip this frame and hope it resolves itself by the next one.
192 log::info!("Skipping rendering the current frame...");
193 Err(error)
194 }
195 GetSurfaceTextureError::Lost | GetSurfaceTextureError::Outdated => {
196 block_on(configure_surface(surface, device, &surface_config.borrow()))
197 .map_err(GetSurfaceTextureError::ConfigurationError)?;
198 
199 match get_surface_texture(surface) {
200 Ok(texture) => {
201 log::info!("Successfully recreated the swap chain");
202 Ok(texture)
203 }
204 Err(e) => {
205 log::warn!("Failed to recreate the swap chain: {e:#}");
206 Err(e)
207 }
208 }
209 }
210 }
211 }
212}
213 
214fn device_info_from_adapter_info(adapter_info: wgpu::AdapterInfo) -> GPUDeviceInfo {
215 let device_type = match adapter_info.device_type {
216 DeviceType::Other => GPUDeviceType::Other,
217 DeviceType::IntegratedGpu => GPUDeviceType::IntegratedGpu,
218 DeviceType::DiscreteGpu => GPUDeviceType::DiscreteGpu,
219 DeviceType::VirtualGpu => GPUDeviceType::VirtualGpu,
220 DeviceType::Cpu => GPUDeviceType::Cpu,
221 };
222 let backend = match adapter_info.backend {
223 Backend::Noop => GPUBackend::Empty,
224 Backend::Vulkan => GPUBackend::Vulkan,
225 Backend::Metal => GPUBackend::Metal,
226 Backend::Dx12 => GPUBackend::Dx12,
227 Backend::Gl => GPUBackend::Gl,
228 Backend::BrowserWebGpu => GPUBackend::BrowserWebGpu,
229 };
230 GPUDeviceInfo {
231 device_type,
232 device_name: adapter_info.name,
233 driver_name: adapter_info.driver,
234 driver_info: adapter_info.driver_info,
235 backend,
236 }
237}
238 
239/// Selects the adapter to use to render to the given surface.
240///
241/// The adapter is selected from the set of adapters that support the given
242/// backends, and priority is determined by the power preference.
243///
244/// This is inspired by the implementation of `request_adapter` in `wgpu_core`:
245/// https://github.com/gfx-rs/wgpu/blob/badb3c88ea29acb159d333e2f60b1cc305bbd512/wgpu-core/src/instance.rs#L857
246#[allow(clippy::too_many_arguments)]
247#[cfg_attr(target_family = "wasm", allow(unused_variables))]
248async fn select_adapter(
249 instance: &wgpu::Instance,
250 surface: &wgpu::Surface<'static>,
251 backends: wgpu::Backends,
252 backend_preference: Option<wgpu::Backend>,
253 gpu_power_preference: GPUPowerPreference,
254 initial_surface_size: Vector2F,
255 windowing_system: Option<windowing::System>,
256 downrank_non_nvidia_vulkan_adapters: bool,
257) -> Option<(
258 Adapter,
259 Device,
260 Queue,
261 SurfaceConfiguration,
262 HashSet<wgpu::Backend>,
263)> {
264 cfg_if::cfg_if! {
265 if #[cfg(target_family = "wasm")] {
266 let power_preference = match gpu_power_preference {
267 GPUPowerPreference::LowPower => wgpu::PowerPreference::LowPower,
268 GPUPowerPreference::HighPerformance => wgpu::PowerPreference::HighPerformance,
269 };
270 let request_adapter_options = wgpu::RequestAdapterOptions {
271 power_preference,
272 force_fallback_adapter: false,
273 compatible_surface: Some(surface),
274 };
275 
276 let adapter = instance.request_adapter(&request_adapter_options).await.ok()?;
277 let adapters = [adapter].into_iter();
278 } else {
279 let adapters = instance
280 .enumerate_adapters(backends)
281 .await
282 .into_iter();
283 }
284 }
285 
286 log::info!("Enabled wgpu backends: {backends:?}");
287 
288 log::info!("Available wgpu adapters (in priority order):");
289 
290 let sorted_adapters = sort_adapters(
291 adapters.collect(),
292 backend_preference,
293 &gpu_power_preference,
294 windowing_system,
295 downrank_non_nvidia_vulkan_adapters,
296 );
297 
298 let adapters = sorted_adapters
299 // Filter out any unsupported adapters and log information about each one.
300 .filter(|adapter| is_supported_adapter(adapter, surface))
301 // While we don't strictly need to collect the iterator into a vector,
302 // this ensures we log adapter information for all adapters. (Omitting
303 // this means the iterator is lazily evaluated, and we'll only print
304 // adapter information up until the point where we find a working one.)
305 .collect_vec();
306 
307 let supported_backends = adapters
308 .iter()
309 .map(|adapter| adapter.get_info().backend)
310 .collect::<HashSet<_>>();
311 
312 for adapter in adapters {
313 if let Some((device, queue, surface_config)) =
314 initialize_device(&adapter, surface, initial_surface_size).await
315 {
316 return Some((adapter, device, queue, surface_config, supported_backends));
317 }
318 }
319 
320 None
321}
322 
323/// Sorts adapters according to user preference, stability, and performance.
324///
325/// All sorts performed here should be stable, ensuring that the relative ordering of previous
326/// sorting steps is preserved.
327pub(super) fn sort_adapters(
328 adapters: Vec<wgpu::Adapter>,
329 backend_preference: Option<wgpu::Backend>,
330 gpu_power_preference: &GPUPowerPreference,
331 windowing_system: Option<windowing::System>,
332 downrank_non_nvidia_vulkan_adapters: bool,
333) -> impl Iterator<Item = wgpu::Adapter> {
334 adapters
335 .into_iter()
336 // Sort adapters by backend priority.
337 .sorted_by_cached_key(|adapter| adapter_backend_sort_func(adapter, backend_preference))
338 .sorted_by_cached_key(adapter_supported_features)
339 // Sort adapters based on low/high power preferences.
340 .sorted_by_cached_key(power_preference_adapter_sort_func(gpu_power_preference))
341 // Sort adapters that we know have some issues towards the end of the list.
342 .sorted_by_cached_key(|adapter| {
343 adapter_stability_sort_func(
344 adapter,
345 windowing_system,
346 downrank_non_nvidia_vulkan_adapters,
347 )
348 })
349}
350 
351/// Returns whether or not a particular adapter is supported and can be used
352/// for rendering.
353fn is_supported_adapter(adapter: &wgpu::Adapter, surface: &wgpu::Surface) -> bool {
354 let can_present = adapter.is_surface_supported(surface);
355 
356 let supported_texture_format = surface
357 .get_default_config(adapter, SURFACE_SIZE_FOR_TESTING, SURFACE_SIZE_FOR_TESTING)
358 .map(|config| config.format);
359 let supported_alpha_modes = surface.get_capabilities(adapter).alpha_modes;
360 
361 // Log information about the adapter (to assist with debugging).
362 let info = adapter.get_info();
363 let device_type = &info.device_type;
364 let device_name = &info.name;
365 let backend = &info.backend;
366 let driver = if info.driver.is_empty() {
367 "Unknown"
368 } else {
369 &info.driver
370 };
371 let driver_info = if info.driver_info.is_empty() {
372 String::new()
373 } else {
374 format!(" ({})", info.driver_info)
375 };
376 log::info!("{device_type:?}: {device_name}\n\tBackend: {backend:?}\n\tDriver: {driver}{driver_info}\n\tCan present: {can_present}\n\tSupported texture format: {supported_texture_format:?}\n\tSupported alpha mode: {supported_alpha_modes:?}");
377 
378 can_present && supported_texture_format.is_some()
379}
380 
381/// Encode levels of preference for graphics adapters based on features they enable. This takes
382/// precedence under the "GPU power preference".
383#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
384enum AdapterFeatureSet {
385 /// No features are hindered by what this adapter supports.
386 Full = 0,
387 /// Some non-critical features not supported by the adapter.
388 MissingMinorFeatures = 1,
389}
390 
391fn adapter_supported_features(adapter: &Adapter) -> AdapterFeatureSet {
392 if adapter_has_rendering_offset_bug(&adapter.get_info()) {
393 log::warn!("Deprioritizing OpenGL-backed Intel UHD adapter");
394 AdapterFeatureSet::MissingMinorFeatures
395 } else {
396 AdapterFeatureSet::Full
397 }
398}
399 
400fn is_nvidia_adapter(adapter_info: &wgpu::AdapterInfo) -> bool {
401 adapter_info.driver == "NVIDIA"
402}
403 
404fn is_vulkan_nvidia_adapter(adapter_info: &wgpu::AdapterInfo) -> bool {
405 // Only consider Vulkan adapters using the Nvidia driver.
406 adapter_info.backend == wgpu::Backend::Vulkan && is_nvidia_adapter(adapter_info)
407}
408 
409/// Returns whether or not the provided adapter is an unsupported Nvidia driver version for strato_ui
410/// to render properly.
411fn is_older_nvidia_adapter(adapter_info: &wgpu::AdapterInfo) -> bool {
412 if !is_vulkan_nvidia_adapter(adapter_info) {
413 return false;
414 }
415 
416 let Some(version) = Version::from(&adapter_info.driver_info) else {
417 // Log an error so we know this occurred and can improve the logic as-needed.
418 log::error!(
419 "Unable to parse Vulkan-backed Nvidia adapter version {:?}; de-prioritizing out of an \
420 abundance of caution.",
421 adapter_info.driver_info
422 );
423 return true;
424 };
425 
426 version < *MIN_SUPPORTED_NVIDIA_VERSION
427}
428 
429/// Returns whether this adapter is a newer Windows NVIDIA adapter using a non-DX12 backend.
430/// On NVIDIA drivers 572 and later, the default value of "auto" for the "Vulkan / OpenGL Present
431/// Method" can cause crashes when creating multiple windows, so we downrank it.
432fn is_newer_nondx12_nvidia_adapter_on_windows(adapter_info: &wgpu::AdapterInfo) -> bool {
433 if !cfg!(windows) {
434 return false;
435 }
436 
437 if !is_nvidia_adapter(adapter_info) {
438 return false;
439 }
440 
441 if adapter_info.backend == Backend::Dx12 {
442 return false;
443 }
444 
445 let Some(version) = Version::from(&adapter_info.driver_info) else {
446 // Log an error so we know this occurred and can improve the logic as-needed.
447 log::error!(
448 "Unable to parse Nvidia adapter version {:?} adapter_info.driver_info",
449 adapter_info.driver_info
450 );
451 return false;
452 };
453 
454 version >= *MAX_SUPPORTED_NVIDIA_VERSION_ON_WINDOWS
455}
456 
457/// Returns whether this adapter is the integrated OpenGL driver for Windows running in Parallels.
458/// It caused problems with theme background images.
459/// https://linear.app/warpdotdev/issue/CORE-3692/background-images-broken-in-parallels
460fn is_gl_to_metal_adapter_on_windows_in_parallels(adapter_info: &wgpu::AdapterInfo) -> bool {
461 cfg!(windows)
462 && adapter_info.backend == Backend::Gl
463 && adapter_info.device_type == DeviceType::IntegratedGpu
464 && adapter_info.driver_info.to_lowercase().contains("metal")
465 && adapter_info.name.to_lowercase().starts_with("parallels")
466}
467 
468/// Returns whether or not the provided adapter is an unsupported Intel UHD Mesa driver version for
469/// strato_ui to render properly. Currently, we limit this to "Intel UHD Graphics 620", but we do have
470/// some suspicion that more Intel UHD devices are affected, e.g. PLAT-599 has a "Intel(R) UHD
471/// Graphics (TGL GT1)" user seeing the exact same issue.
472fn is_older_vulkan_intel_uhd_adapter(adapter_info: &wgpu::AdapterInfo) -> bool {
473 if adapter_info.backend != wgpu::Backend::Vulkan
474 || adapter_info.device_type != wgpu::DeviceType::IntegratedGpu
475 || !adapter_info.name.contains("Intel(R) HD Graphics 620")
476 {
477 return false;
478 }
479 
480 mesa_driver_version_is_below_minimum(
481 &adapter_info.driver_info,
482 &MIN_SUPPORTED_INTEL_UHD_VERSION,
483 )
484}
485 
486/// Returns true if this is:
487/// 1) An Intel UHD 620 Graphics device
488/// 2) Using the Vulkan backend
489/// 3) On Windows
490///
491/// We have indication that this specific device is unstable on Windows so we ignore it in the
492/// hopes that there is a DX12 or GL version of this adapter that is more stable.
493fn is_intel_uhd_620_adapter_on_windows_with_vulkan_backend(
494 adapter_info: &wgpu::AdapterInfo,
495) -> bool {
496 cfg!(windows)
497 && adapter_info.backend == Backend::Vulkan
498 && adapter_info.device_type == DeviceType::IntegratedGpu
499 && (adapter_info.name.contains("Intel(R) UHD Graphics 620")
500 || adapter_info.name.contains("Intel(R) HD Graphics 620"))
501}
502 
503/// Returns whether the given adapter is known to have a rendering offset bug on Windows.
504///
505/// Certain Intel integrated GPU drivers using the GL backend render the scene at an offset from
506/// the window bounds when window decorations are disabled. The offset matches the size of the
507/// window decorations (e.g. title bar height). Enabling native window decorations fixes the
508/// alignment.
509///
510/// See: https://github.com/warpdotdev/Warp/issues/6120
511pub fn adapter_has_rendering_offset_bug(adapter_info: &wgpu::AdapterInfo) -> bool {
512 if !cfg!(windows) {
513 return false;
514 }
515 
516 if adapter_info.backend != Backend::Gl || adapter_info.device_type != DeviceType::IntegratedGpu
517 {
518 return false;
519 }
520 
521 // Known affected Intel integrated GPU models. This list is based on user reports from
522 // https://github.com/warpdotdev/Warp/issues/6120.
523 let affected_models = [
524 "Intel(R) HD Graphics 4000",
525 "Intel(R) HD Graphics 4400",
526 "Intel(R) HD Graphics 4600",
527 "Intel(R) HD Graphics 5500",
528 "Intel(R) HD Graphics P4600",
529 "Intel(R) Iris(TM) Pro Graphics 5200",
530 "Intel(R) Iris(TM) Graphics 6100",
531 ];
532 
533 affected_models
534 .iter()
535 .any(|model| adapter_info.name.contains(model))
536}
537 
538/// Checks whether the provided adapter info describes a lavapipe
539/// (Vulkan llvmpipe) adapter that may not work properly with strato_ui.
540fn is_older_lavapipe_adapter(adapter_info: &wgpu::AdapterInfo) -> bool {
541 // Only consider Vulkan adapters using the llvmpipe driver.
542 if adapter_info.backend != wgpu::Backend::Vulkan || adapter_info.driver != "llvmpipe" {
543 return false;
544 }
545 
546 mesa_driver_version_is_below_minimum(&adapter_info.driver_info, &MIN_SUPPORTED_LAVAPIPE_VERSION)
547}
548 
549fn mesa_driver_version_is_below_minimum(info_str: &str, min_version: &Version) -> bool {
550 let &[name, version, ..] = info_str.splitn(3, ' ').collect_vec().as_slice() else {
551 // Log an error so we know this occurred and can improve the logic as-needed.
552 log::error!(
553 "Encountered Mesa driver info {info_str:?} with an unexpected format! (too few parts)"
554 );
555 return false;
556 };
557 
558 // Perform an extra check that we parsed the driver info string properly.
559 if name.trim() != "Mesa" {
560 // Log an error so we know this occurred and can improve the logic as-needed.
561 log::error!(
562 "Encountered Mesa driver info {info_str:?} with an unexpected format! (name != Mesa)"
563 );
564 return false;
565 }
566 
567 let manifest = version_compare::Manifest {
568 // We only care about major, minor, and patch versions.
569 max_depth: Some(3),
570 ..Default::default()
571 };
572 let Some(version) = Version::from_manifest(version, &manifest) else {
573 // Log an error so we know this occurred and can improve the logic as-needed.
574 log::error!(
575 "Unable to parse Mesa version {version:?}; de-prioritizing out of an abundance of caution."
576 );
577 return true;
578 };
579 
580 version < *min_version
581}
582 
583/// Creates a device and command queue for the given adapter that is guaranteed
584/// to be able to create a swapchain for the surface.
585async fn initialize_device(
586 adapter: &Adapter,
587 surface: &Surface<'static>,
588 initial_surface_size: Vector2F,
589) -> Option<(Device, Queue, SurfaceConfiguration)> {
590 log::info!(
591 "Verifying adapter \"{}\" is valid...",
592 adapter.get_info().name
593 );
594 
595 // `Limits::downlevel_webgl2_defaults` gives very conservative defaults. We want to keep these
596 // limits low in order to make sure we remain compatible with lower-end devices. One exception
597 // to this is sizes of textures. `using_resolution` increases the size limits on textures. We
598 // need this because users' displays often exceed the downleveled default limits of 2048px.
599 // Here, we increase that to the ceiling of what this adapter is capable of.
600 let mut limits = wgpu::Limits::downlevel_webgl2_defaults().using_resolution(adapter.limits());
601 // Set a higher minimum number of variables that can be passed between shader stages.
602 limits.max_inter_stage_shader_variables = 15;
603 
604 limits.max_mesh_output_layers = 0;
605 
606 let (device, queue) = match adapter
607 .request_device(&wgpu::DeviceDescriptor {
608 // Use the broadest/most permissive device requirements
609 // so that we can run on as many machines as possible.
610 // If we use any WGSL features that aren't included in
611 // these defaults, we can add specific overrides as needed.
612 required_limits: limits,
613 ..Default::default()
614 })
615 .await
616 {
617 Ok(device_and_queue) => device_and_queue,
618 Err(err) => {
619 log::warn!("Failed to create a logical device: {err:#}");
620 return None;
621 }
622 };
623 
624 // Ensure that we're able to create a swapchain before we treat the device
625 // as valid.
626 let Some(surface_config) = create_surface_config(adapter, surface, initial_surface_size) else {
627 log::warn!("Failed to get default surface configuration");
628 return None;
629 };
630 
631 match configure_surface(surface, &device, &surface_config).await {
632 Ok(_) => Some((device, queue, surface_config)),
633 Err(err) => {
634 log::warn!("Failed to create swapchain: {err:#}");
635 None
636 }
637 }
638}
639 
640/// Returns a priority for an adapter based on backend type, to be used as a
641/// sort function.
642///
643/// This matches the order used by wgpu; see:
644/// https://github.com/gfx-rs/wgpu/blob/v0.18/wgpu-core/src/instance.rs#L869-L913
645#[cfg(not(windows))]
646fn adapter_backend_sort_func(
647 adapter: &wgpu::Adapter,
648 backend_preference: Option<wgpu::Backend>,
649) -> usize {
650 let backend = adapter.get_info().backend;
651 if backend_preference.is_some_and(|pref| pref == backend) {
652 return 0;
653 }
654 match backend {
655 wgpu::Backend::Vulkan => 1,
656 wgpu::Backend::Metal => 2,
657 wgpu::Backend::Dx12 => 3,
658 wgpu::Backend::BrowserWebGpu => 4,
659 wgpu::Backend::Gl => 5,
660 wgpu::Backend::Noop => 6,
661 }
662}
663 
664/// Returns a priority for an adapter based on backend type, to be used as a
665/// sort function.
666///
667/// This prioritizes DX12 on Windows which is more reliable. See this issue:
668/// https://github.com/gfx-rs/wgpu/issues/2719
669#[cfg(windows)]
670fn adapter_backend_sort_func(
671 adapter: &wgpu::Adapter,
672 backend_preference: Option<wgpu::Backend>,
673) -> usize {
674 let backend = adapter.get_info().backend;
675 if backend_preference.is_some_and(|pref| pref == backend) {
676 return 0;
677 }
678 match backend {
679 // On Windows, we prefer DirectX 12 over Vulkan. Given that no other
680 // platform supports DX12 at all, there's no need to condition this
681 // ranking on OS.
682 wgpu::Backend::Dx12 => 1,
683 wgpu::Backend::Vulkan => 2,
684 wgpu::Backend::Gl => 3,
685 wgpu::Backend::Metal => 4,
686 wgpu::Backend::BrowserWebGpu => 5,
687 wgpu::Backend::Noop => 6,
688 }
689}
690 
691/// Returns a priority for an adapter based on our expectations of its
692/// stability.
693///
694/// This should be used to deprioritize adapters where they _may not_
695/// work, but we're not so confident that they are broken that we fully filter
696/// them out. Ultimately, if the user only has one adapter, it's better for
697/// us to attempt to use it than for us to give up without trying.
698fn adapter_stability_sort_func(
699 adapter: &wgpu::Adapter,
700 windowing_system: Option<windowing::System>,
701 downrank_non_nvidia_vulkan_adapters: bool,
702) -> AdapterSupport {
703 let adapter_info = adapter.get_info();
704 
705 let window_server_is_wayland = matches!(
706 windowing_system,
707 Some(windowing::System::Wayland) | Some(windowing::System::X11 { is_x_wayland: true })
708 );
709 
710 if downrank_non_nvidia_vulkan_adapters
711 && adapter_info.backend == Backend::Vulkan
712 && !is_vulkan_nvidia_adapter(&adapter_info)
713 {
714 log::info!("Deprioritizing non-NVIDIA Vulkan adapter (the PRIME performance profile is likely enabled)");
715 return AdapterSupport::Unsupported;
716 }
717 
718 if is_intel_uhd_620_adapter_on_windows_with_vulkan_backend(&adapter_info) {
719 log::warn!("Deprioritizing Vulkan-backed Intel UHD 620 adapter");
720 return AdapterSupport::SupportedWithIssues;
721 }
722 
723 if is_older_vulkan_intel_uhd_adapter(&adapter_info) {
724 log::warn!(
725 "Deprioritizing Vulkan-backed Intel UHD adapter due to Mesa < {} (unsupported)",
726 *MIN_SUPPORTED_INTEL_UHD_VERSION
727 );
728 AdapterSupport::SupportedWithIssues
729 }
730 // Deprioritize older lavapipe adapters where we have evidence that they are less stable.
731 else if is_older_lavapipe_adapter(&adapter_info) {
732 log::warn!(
733 "Deprioritizing Vulkan-backed llvmpipe adapter due to Mesa < {} (unsupported)",
734 *MIN_SUPPORTED_LAVAPIPE_VERSION
735 );
736 AdapterSupport::Unsupported
737 // Same with Nvidia drivers, though this is only an issue with a Wayland window server.
738 } else if window_server_is_wayland && is_older_nvidia_adapter(&adapter_info) {
739 log::warn!(
740 "Deprioritizing Vulkan-backed Nvidia adapter due to version < {} (unsupported).\nSee \
741 the \"Graphics\" secion of our docs here: \
742 https://docs.warp.dev/help/known-issues#linux-1",
743 *MIN_SUPPORTED_NVIDIA_VERSION
744 );
745 AdapterSupport::Unsupported
746 } else if is_newer_nondx12_nvidia_adapter_on_windows(&adapter_info) {
747 log::warn!(
748 "Deprioritizing non DX12 Nvidia adapter due to version > {} (unsupported). Newer NVIDIA \
749 drivers can crash if multiple windows are created if the `Vulkan / OpenGL Present Method\
750 NVIDIA setting is set to `Auto` or `Prefer layered on DXGI Swapchain`.",
751 *MAX_SUPPORTED_NVIDIA_VERSION_ON_WINDOWS
752 );
753 AdapterSupport::SupportedWithIssues
754 } else if is_gl_to_metal_adapter_on_windows_in_parallels(&adapter_info) {
755 log::warn!("Deprioritizing integrated OpenGL Windows Parallels adapter.");
756 AdapterSupport::SupportedWithIssues
757 } else {
758 AdapterSupport::Supported
759 }
760}
761 
762/// Encode levels of preference for graphics adapters based on application stability. This takes
763/// precedence over the "GPU power preference". We've seen varying severities of graphics issues on
764/// Linux and Windows.
765#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
766enum AdapterSupport {
767 /// The adapter has no known issues.
768 Supported = 0,
769 /// The adapter is somewhat usable, but there have been some problems.
770 SupportedWithIssues = 1,
771 /// The adapter is basically not viable. Warpui will either crash or not render.
772 Unsupported = 2,
773}
774 
775/// Returns a function that computes the priority for an adapter based on
776/// device type, to be used as a sort function.
777///
778/// This matches the order used by wgpu; see:
779/// https://github.com/gfx-rs/wgpu/blob/v0.18/wgpu-core/src/instance.rs#L953-L954
780fn power_preference_adapter_sort_func(
781 pref: &GPUPowerPreference,
782) -> impl FnMut(&wgpu::Adapter) -> usize {
783 match pref {
784 GPUPowerPreference::LowPower => {
785 |adapter: &wgpu::Adapter| match adapter.get_info().device_type {
786 wgpu::DeviceType::IntegratedGpu => 0,
787 wgpu::DeviceType::DiscreteGpu => 1,
788 wgpu::DeviceType::Other => 2,
789 wgpu::DeviceType::VirtualGpu => 3,
790 wgpu::DeviceType::Cpu => 4,
791 }
792 }
793 GPUPowerPreference::HighPerformance => {
794 |adapter: &wgpu::Adapter| match adapter.get_info().device_type {
795 wgpu::DeviceType::DiscreteGpu => 0,
796 wgpu::DeviceType::IntegratedGpu => 1,
797 wgpu::DeviceType::Other => 2,
798 wgpu::DeviceType::VirtualGpu => 3,
799 wgpu::DeviceType::Cpu => 4,
800 }
801 }
802 }
803}
804 
805fn create_surface_config(
806 adapter: &Adapter,
807 surface: &Surface,
808 initial_surface_size: Vector2F,
809) -> Option<SurfaceConfiguration> {
810 let mut config = surface.get_default_config(
811 adapter,
812 initial_surface_size.x() as u32,
813 initial_surface_size.y() as u32,
814 )?;
815 // Make sure we're not using an sRGB format.
816 config.format = config.format.remove_srgb_suffix();
817 
818 let caps = surface.get_capabilities(adapter);
819 // COPY_SRC is only needed to support integration test frame capture via
820 // request_frame_capture. It is not required for normal rendering.
821 #[cfg(feature = "integration_tests")]
822 if caps.usages.contains(wgpu::TextureUsages::COPY_SRC) {
823 config.usage |= wgpu::TextureUsages::COPY_SRC;
824 }
825 
826 // Use a non-vsync presentation mode for reduced input delay. This could
827 // cause visual tearing on present, but we're ok with paying that cost to
828 // improve responsiveness.
829 config.present_mode = PresentMode::AutoNoVsync;
830 
831 // Explicitly request a non-opaque alpha compositing mode, if available.
832 // Without this, transparent surfaces don't work on native Wayland.
833 if caps
834 .alpha_modes
835 .contains(&CompositeAlphaMode::PostMultiplied)
836 && adapter.get_info().backend != wgpu::Backend::Dx12
837 {
838 config.alpha_mode = CompositeAlphaMode::PostMultiplied;
839 } else if caps
840 .alpha_modes
841 .contains(&CompositeAlphaMode::PreMultiplied)
842 {
843 config.alpha_mode = CompositeAlphaMode::PreMultiplied;
844 } else if caps.alpha_modes.contains(&CompositeAlphaMode::Inherit) {
845 config.alpha_mode = CompositeAlphaMode::Inherit;
846 } else {
847 config.alpha_mode = CompositeAlphaMode::Auto;
848 }
849 
850 Some(config)
851}
852 
853#[derive(Error, Debug)]
854pub enum GetSurfaceTextureError {
855 #[error("Timeout while getting next surface texture")]
856 Timeout,
857 #[error("Window is occluded and cannot be presented to")]
858 Occluded,
859 #[error("Surface configuration outdated")]
860 Outdated,
861 #[error("Device lost")]
862 Lost,
863 #[error("Validation error")]
864 Validation,
865 #[error("Failed to configure surface")]
866 ConfigurationError(SurfaceConfigureError),
867}
868 
869fn get_surface_texture(
870 surface: &Surface<'_>,
871) -> Result<wgpu::SurfaceTexture, GetSurfaceTextureError> {
872 let error = match surface.get_current_texture() {
873 CurrentSurfaceTexture::Success(texture) | CurrentSurfaceTexture::Suboptimal(texture) => {
874 return Ok(texture)
875 }
876 CurrentSurfaceTexture::Timeout => GetSurfaceTextureError::Timeout,
877 CurrentSurfaceTexture::Occluded => GetSurfaceTextureError::Occluded,
878 CurrentSurfaceTexture::Outdated => GetSurfaceTextureError::Outdated,
879 CurrentSurfaceTexture::Lost => GetSurfaceTextureError::Lost,
880 CurrentSurfaceTexture::Validation => GetSurfaceTextureError::Validation,
881 };
882 Err(error)
883}
884 
885/// Represents an error that occurred when configuring a surface.
886#[derive(Error, Debug)]
887pub enum SurfaceConfigureError {
888 #[error("Failed to configure surface: {source:#}\n\nDesired configuration: {config:#?}")]
889 Error {
890 /// The underlying error.
891 #[source]
892 source: wgpu::Error,
893 /// The desired configuration.
894 config: SurfaceConfiguration,
895 },
896}
897 
898/// Configures the provided surface.
899async fn configure_surface(
900 surface: &Surface<'_>,
901 device: &Device,
902 surface_config: &SurfaceConfiguration,
903) -> Result<(), SurfaceConfigureError> {
904 let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
905 surface.configure(device, surface_config);
906 match error_scope.pop().await {
907 Some(err) => Err(SurfaceConfigureError::Error {
908 source: err,
909 config: surface_config.clone(),
910 }),
911 None => Ok(()),
912 }
913}
914 
915#[cfg(test)]
916#[path = "resources_tests.rs"]
917mod tests;
918