1#[cfg(not(target_family = "wasm"))]
2use gpui_platform::{
3 GpuAdapterInfo, GpuBackend, GpuDeviceType, GpuId, GpuPreference, PermanentGpuId, SessionGpuId,
4};
5use parking_lot::Mutex;
6use std::sync::Arc;
7use std::sync::atomic::{AtomicBool, Ordering};
8use wgpu::TextureFormat;
9
10#[derive(Clone)]
11pub struct WgpuContext {
12 pub instance: wgpu::Instance,
13 pub adapter: wgpu::Adapter,
14 pub device: Arc<wgpu::Device>,
15 pub queue: Arc<wgpu::Queue>,
16 backend: WgpuBackend,
17 dual_source_blending: bool,
18 color_texture_format: wgpu::TextureFormat,
19 errors: Arc<DeviceErrorState>,
20}
21
22#[derive(Default)]
29pub struct DeviceErrorState {
30 lost: AtomicBool,
31 last_error: Mutex<(u64, Option<String>)>,
32}
33
34impl DeviceErrorState {
35 fn install(device: &wgpu::Device) -> Arc<Self> {
36 let errors = Arc::new(Self::default());
37 device.set_device_lost_callback({
38 let errors = Arc::clone(&errors);
39 move |reason, message| {
40 log::error!("wgpu device lost: reason={reason:?}, message={message}");
41 if reason != wgpu::DeviceLostReason::Destroyed {
42 errors.lost.store(true, Ordering::Relaxed);
43 }
44 }
45 });
46 device.on_uncaptured_error(Arc::new({
47 let errors = Arc::clone(&errors);
48 move |error| errors.record(error.to_string())
49 }));
50 errors
51 }
52
53 pub fn device_lost(&self) -> bool {
55 self.lost.load(Ordering::Relaxed)
56 }
57
58 fn record(&self, error: String) {
59 let mut last_error = self.last_error.lock();
60 last_error.0 = last_error.0.wrapping_add(1);
61 last_error.1 = Some(error);
62 }
63
64 pub fn observe_error(&self, generation: &mut u64) -> Option<String> {
69 let last_error = self.last_error.lock();
70 if *generation == last_error.0 {
71 return None;
72 }
73 *generation = last_error.0;
74 last_error.1.clone()
75 }
76
77 pub fn current_generation(&self) -> u64 {
80 self.last_error.lock().0
81 }
82}
83
84#[derive(Clone, Copy, Debug, Eq, PartialEq)]
85pub enum WgpuBackend {
86 BrowserWebGpu,
87 Gl,
88 Native(wgpu::Backend),
89}
90
91#[cfg(target_family = "wasm")]
92#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
93pub enum WebBackendPreference {
94 #[default]
95 Auto,
96 WebGpu,
97 WebGl,
98}
99
100#[cfg(target_family = "wasm")]
101pub struct PreparedWebGraphics {
102 pub context: WgpuContext,
103 pub surface: wgpu::Surface<'static>,
104}
105
106#[cfg(target_family = "wasm")]
111#[derive(Debug)]
112struct WebDisplaySource;
113
114#[cfg(target_family = "wasm")]
115impl raw_window_handle::HasDisplayHandle for WebDisplaySource {
116 fn display_handle(
117 &self,
118 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
119 Ok(raw_window_handle::DisplayHandle::web())
120 }
121}
122
123#[derive(Clone, Copy)]
124pub struct CompositorGpuHint {
125 pub vendor_id: u32,
126 pub device_id: u32,
127}
128
129#[cfg(not(target_family = "wasm"))]
135pub fn adapter_info(info: &wgpu::AdapterInfo) -> GpuAdapterInfo {
136 GpuAdapterInfo::new(
137 GpuId {
138 permanent: PermanentGpuId::Pci {
139 vendor_id: Some(info.vendor),
140 device_id: info.device,
141 subsys_id: None,
142 revision: None,
143 },
144 session: Some(session_for(info)),
145 },
146 info.name.clone(),
147 match info.device_type {
148 wgpu::DeviceType::DiscreteGpu => GpuDeviceType::Discrete,
149 wgpu::DeviceType::IntegratedGpu => GpuDeviceType::Integrated,
150 wgpu::DeviceType::VirtualGpu => GpuDeviceType::Virtual,
151 wgpu::DeviceType::Cpu => GpuDeviceType::Cpu,
152 wgpu::DeviceType::Other => GpuDeviceType::Other,
153 },
154 match info.backend {
155 wgpu::Backend::Vulkan => GpuBackend::Vulkan,
156 wgpu::Backend::Metal => GpuBackend::Metal,
157 wgpu::Backend::Dx12 => GpuBackend::Dx12,
158 wgpu::Backend::Gl => GpuBackend::Gl,
159 wgpu::Backend::BrowserWebGpu => GpuBackend::BrowserWebGpu,
160 wgpu::Backend::Noop => GpuBackend::Other,
161 },
162 info.device_type == wgpu::DeviceType::Cpu,
163 )
164}
165
166#[cfg(target_os = "linux")]
174fn session_for(info: &wgpu::AdapterInfo) -> SessionGpuId {
175 if !info.device_pci_bus_id.is_empty()
176 && let Ok(target) = std::fs::read_link(format!(
177 "/dev/dri/by-path/pci-{}-render",
178 info.device_pci_bus_id
179 ))
180 && let Some(minor) = target
181 .file_name()
182 .and_then(|name| name.to_str())
183 .and_then(|name| name.strip_prefix("renderD"))
184 .and_then(|minor| minor.parse().ok())
185 {
186 return SessionGpuId::Drm {
187 render_minor: minor,
188 };
189 }
190 SessionGpuId::Pci {
191 vendor_id: info.vendor,
192 device_id: info.device,
193 }
194}
195
196#[cfg(not(target_os = "linux"))]
197fn session_for(info: &wgpu::AdapterInfo) -> SessionGpuId {
198 SessionGpuId::Pci {
199 vendor_id: info.vendor,
200 device_id: info.device,
201 }
202}
203
204#[cfg(not(target_family = "wasm"))]
211pub fn enumerate_adapters() -> Vec<GpuAdapterInfo> {
212 enumerate_adapters_from(&WgpuContext::instance(None))
213}
214
215#[cfg(not(target_family = "wasm"))]
217pub fn enumerate_adapters_from(instance: &wgpu::Instance) -> Vec<GpuAdapterInfo> {
218 pollster::block_on(instance.enumerate_adapters(wgpu::Backends::all()))
219 .iter()
220 .map(|adapter| adapter_info(&adapter.get_info()))
221 .collect()
222}
223
224impl WgpuContext {
225 #[cfg(target_os = "linux")]
230 pub fn dmabuf_capabilities(&self) -> Option<gpui_engine::DmaBufCapabilities> {
231 crate::dmabuf::dmabuf_capabilities(&self.device)
232 }
233
234 #[cfg(not(target_family = "wasm"))]
236 pub fn adapter_info(&self) -> GpuAdapterInfo {
237 adapter_info(&self.adapter.get_info())
238 }
239
240 #[cfg(not(target_family = "wasm"))]
241 pub fn new(
242 instance: wgpu::Instance,
243 surface: &wgpu::Surface<'_>,
244 compositor_gpu: Option<CompositorGpuHint>,
245 gpu_preference: GpuPreference,
246 ) -> anyhow::Result<Self> {
247 Self::new_with_options(instance, surface, compositor_gpu, gpu_preference, false)
248 }
249
250 #[cfg(not(target_family = "wasm"))]
251 pub fn new_rejecting_software(
252 instance: wgpu::Instance,
253 surface: &wgpu::Surface<'_>,
254 compositor_gpu: Option<CompositorGpuHint>,
255 gpu_preference: GpuPreference,
256 ) -> anyhow::Result<Self> {
257 Self::new_with_options(instance, surface, compositor_gpu, gpu_preference, true)
258 }
259
260 #[cfg(not(target_family = "wasm"))]
261 fn new_with_options(
262 instance: wgpu::Instance,
263 surface: &wgpu::Surface<'_>,
264 compositor_gpu: Option<CompositorGpuHint>,
265 gpu_preference: GpuPreference,
266 reject_software: bool,
267 ) -> anyhow::Result<Self> {
268 let (adapter, device, queue, dual_source_blending, color_texture_format) =
271 pollster::block_on(Self::select_adapter_and_device(
272 &instance,
273 &gpu_preference,
274 surface,
275 compositor_gpu.as_ref(),
276 reject_software,
277 ))?;
278
279 Ok(Self::from_native_adapter_and_device(
280 instance,
281 adapter,
282 device,
283 queue,
284 dual_source_blending,
285 color_texture_format,
286 ))
287 }
288
289 #[cfg(all(
296 not(target_family = "wasm"),
297 any(
298 test,
299 feature = "bench-support",
300 feature = "test-support",
301 feature = "headless"
302 )
303 ))]
304 pub(crate) fn new_headless_with(
305 gpu_preference: GpuPreference,
306 ) -> anyhow::Result<(Self, wgpu::TextureFormat)> {
307 static HEADLESS: Mutex<Option<(WgpuContext, wgpu::TextureFormat)>> = Mutex::new(None);
313 let mut headless = HEADLESS.lock();
314 if let Some((context, format)) = headless.as_ref()
315 && !context.device_lost()
316 {
317 return Ok((context.clone(), *format));
318 }
319 let created = Self::create_headless(gpu_preference)?;
320 *headless = Some(created.clone());
321 Ok(created)
322 }
323
324 #[cfg(all(
325 not(target_family = "wasm"),
326 any(
327 test,
328 feature = "bench-support",
329 feature = "test-support",
330 feature = "headless"
331 )
332 ))]
333 fn create_headless(
334 gpu_preference: GpuPreference,
335 ) -> anyhow::Result<(Self, wgpu::TextureFormat)> {
336 let instance = Self::instance(None);
337 let (adapter, device, queue, dual_source_blending, color_texture_format, target_format) =
338 pollster::block_on(async {
339 let mut adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
340 Self::order_adapters(&mut adapters, &gpu_preference, None)?;
341
342 for adapter in adapters {
343 let adapter_info = adapter.get_info();
344 let Some(target_format) = Self::headless_target_format(&adapter) else {
345 log::warn!(
346 "Adapter {:?} has no supported headless render target format",
347 adapter_info.name
348 );
349 continue;
350 };
351
352 match Self::create_device(&adapter).await {
353 Ok((device, queue, dual_source_blending, color_texture_format)) => {
354 #[cfg(feature = "bench-support")]
355 if adapter_info.device_type == wgpu::DeviceType::Cpu {
356 log::error!(
357 "Headless renderer selected software adapter {:?}; \
358 benchmark results measure CPU software rendering, not hardware GPU rendering",
359 adapter_info.name
360 );
361 }
362 return Ok((
363 adapter,
364 device,
365 queue,
366 dual_source_blending,
367 color_texture_format,
368 target_format,
369 ));
370 }
371 Err(error) => {
372 log::warn!(
373 "Failed to create a headless device for adapter {:?}: {error:#}",
374 adapter_info.name
375 );
376 }
377 }
378 }
379
380 anyhow::bail!("No usable headless GPU adapter found")
381 })?;
382
383 Ok((
384 Self::from_native_adapter_and_device(
385 instance,
386 adapter,
387 device,
388 queue,
389 dual_source_blending,
390 color_texture_format,
391 ),
392 target_format,
393 ))
394 }
395
396 #[cfg(all(
397 not(target_family = "wasm"),
398 any(
399 test,
400 feature = "bench-support",
401 feature = "test-support",
402 feature = "headless"
403 )
404 ))]
405 fn headless_target_format(adapter: &wgpu::Adapter) -> Option<wgpu::TextureFormat> {
409 let required_usages =
410 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC;
411 [
412 wgpu::TextureFormat::Rgba8Unorm,
413 wgpu::TextureFormat::Bgra8Unorm,
414 ]
415 .into_iter()
416 .find(|format| {
417 adapter
418 .get_texture_format_features(*format)
419 .allowed_usages
420 .contains(required_usages)
421 })
422 }
423
424 #[cfg(not(target_family = "wasm"))]
425 fn from_native_adapter_and_device(
426 instance: wgpu::Instance,
427 adapter: wgpu::Adapter,
428 device: wgpu::Device,
429 queue: wgpu::Queue,
430 dual_source_blending: bool,
431 color_texture_format: TextureFormat,
432 ) -> Self {
433 let errors = DeviceErrorState::install(&device);
434
435 log::info!(
436 "Selected GPU adapter: {:?} ({:?})",
437 adapter.get_info().name,
438 adapter.get_info().backend
439 );
440 let backend = WgpuBackend::Native(adapter.get_info().backend);
441
442 Self {
443 instance,
444 adapter,
445 device: Arc::new(device),
446 queue: Arc::new(queue),
447 backend,
448 dual_source_blending,
449 color_texture_format,
450 errors,
451 }
452 }
453
454 #[cfg(target_family = "wasm")]
455 pub async fn new_web(
456 canvas: &web_sys::HtmlCanvasElement,
457 preference: WebBackendPreference,
458 ) -> anyhow::Result<PreparedWebGraphics> {
459 Self::new_web_with_backend(canvas, preference).await
460 }
461
462 #[cfg(target_family = "wasm")]
463 #[allow(clippy::arc_with_non_send_sync)]
464 async fn new_web_with_backend(
465 canvas: &web_sys::HtmlCanvasElement,
466 preference: WebBackendPreference,
467 ) -> anyhow::Result<PreparedWebGraphics> {
468 let backends = match preference {
469 WebBackendPreference::Auto => wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
470 WebBackendPreference::WebGpu => wgpu::Backends::BROWSER_WEBGPU,
471 WebBackendPreference::WebGl => wgpu::Backends::GL,
472 };
473 let descriptor = wgpu::InstanceDescriptor {
474 backends,
475 flags: wgpu::InstanceFlags::default(),
476 backend_options: wgpu::BackendOptions::default(),
477 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
478 display: Some(Box::new(WebDisplaySource)),
479 };
480 let instance = if preference == WebBackendPreference::Auto {
481 wgpu::util::new_instance_with_webgpu_detection(descriptor).await
482 } else {
483 wgpu::Instance::new(descriptor)
484 };
485 let surface = instance
486 .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
487 .map_err(|error| {
488 anyhow::anyhow!("Failed to create browser graphics surface: {error}")
489 })?;
490
491 let adapter = instance
492 .request_adapter(&wgpu::RequestAdapterOptions {
493 power_preference: wgpu::PowerPreference::HighPerformance,
494 compatible_surface: Some(&surface),
495 force_fallback_adapter: false,
496 })
497 .await
498 .map_err(|error| {
499 anyhow::anyhow!(
500 "Failed to request a {preference:?} adapter compatible with the canvas: {error}"
501 )
502 })?;
503 let adapter_info = adapter.get_info();
504 let backend = match adapter_info.backend {
505 wgpu::Backend::BrowserWebGpu => WgpuBackend::BrowserWebGpu,
506 wgpu::Backend::Gl => WgpuBackend::Gl,
507 backend => {
508 anyhow::bail!(
509 "Browser graphics initialization selected unexpected backend {backend:?}"
510 )
511 }
512 };
513
514 let (device, queue, dual_source_blending, color_texture_format) =
515 Self::create_device(&adapter).await?;
516 let errors = DeviceErrorState::install(&device);
517 log::info!(
518 "Browser graphics initialized: requested={preference:?}, selected={backend:?}, \
519 adapter={:?}, limits={:?}, dual_source_blending={dual_source_blending}",
520 adapter_info.name,
521 device.limits(),
522 );
523
524 let context = Self {
525 instance,
526 adapter,
527 device: Arc::new(device),
528 queue: Arc::new(queue),
529 backend,
530 dual_source_blending,
531 color_texture_format,
532 errors,
533 };
534 Ok(PreparedWebGraphics { context, surface })
535 }
536
537 async fn create_device(
538 adapter: &wgpu::Adapter,
539 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
540 let dual_source_blending = adapter
541 .features()
542 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
543
544 let mut required_features = wgpu::Features::empty();
545 if dual_source_blending {
546 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
547 } else {
548 log::warn!(
549 "Dual-source blending not available on this GPU. \
550 Subpixel text antialiasing will be disabled."
551 );
552 }
553
554 let color_atlas_texture_format = Self::select_color_texture_format(adapter)?;
555
556 if adapter
560 .features()
561 .contains(wgpu::Features::TEXTURE_FORMAT_NV12)
562 {
563 required_features |= wgpu::Features::TEXTURE_FORMAT_NV12;
564 }
565
566 if adapter
571 .features()
572 .contains(wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
573 {
574 required_features |= wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
575 }
576 #[cfg(target_family = "wasm")]
577 let required_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
578 wgpu::Limits::downlevel_webgl2_defaults()
579 .using_resolution(adapter.limits())
580 .using_alignment(adapter.limits())
581 } else {
582 wgpu::Limits::downlevel_defaults()
583 .using_resolution(adapter.limits())
584 .using_alignment(adapter.limits())
585 };
586 #[cfg(not(target_family = "wasm"))]
587 let required_limits = {
588 let supported = adapter.limits();
589 let mut limits = wgpu::Limits::downlevel_defaults()
590 .using_resolution(supported.clone())
591 .using_alignment(supported.clone());
592 if adapter.get_info().backend != wgpu::Backend::Gl {
599 limits.max_storage_buffers_per_shader_stage =
600 supported.max_storage_buffers_per_shader_stage;
601 limits.max_storage_textures_per_shader_stage =
602 supported.max_storage_textures_per_shader_stage;
603 limits.max_uniform_buffer_binding_size = supported.max_uniform_buffer_binding_size;
604 limits.max_inter_stage_shader_variables =
605 supported.max_inter_stage_shader_variables;
606 limits.max_color_attachments = supported.max_color_attachments;
607 }
608 limits
609 };
610
611 let descriptor = wgpu::DeviceDescriptor {
612 label: Some("gpui_device"),
613 required_features,
614 required_limits,
615 memory_hints: wgpu::MemoryHints::MemoryUsage,
616 trace: wgpu::Trace::Off,
617 experimental_features: wgpu::ExperimentalFeatures::disabled(),
618 };
619
620 #[cfg(target_os = "linux")]
626 let with_surface_import = Self::device_with_surface_import(adapter, &descriptor);
627 #[cfg(not(target_os = "linux"))]
628 let with_surface_import: Option<(wgpu::Device, wgpu::Queue)> = None;
629
630 let (device, queue) = match with_surface_import {
631 Some(created) => created,
632 None => adapter
633 .request_device(&descriptor)
634 .await
635 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?,
636 };
637
638 Ok((
639 device,
640 queue,
641 dual_source_blending,
642 color_atlas_texture_format,
643 ))
644 }
645
646 #[cfg(target_os = "linux")]
661 fn device_with_surface_import(
662 adapter: &wgpu::Adapter,
663 descriptor: &wgpu::DeviceDescriptor<'_>,
664 ) -> Option<(wgpu::Device, wgpu::Queue)> {
665 use wgpu::hal::vulkan::Api;
666
667 unsafe {
672 let hal = adapter.as_hal::<Api>()?;
673 if !hal
674 .physical_device_capabilities()
675 .supports_extension(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
676 {
677 return None;
678 }
679 let with_semaphore_fd = hal
685 .physical_device_capabilities()
686 .supports_extension(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME);
687 let open = hal
688 .open_with_callback(
689 descriptor.required_features,
690 &descriptor.required_limits,
691 &descriptor.memory_hints,
692 Some(Box::new(move |args| {
693 args.extensions
694 .push(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME);
695 if with_semaphore_fd {
696 args.extensions
697 .push(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME);
698 }
699 })),
700 )
701 .ok()?;
702 let (device, queue) = adapter
703 .create_device_from_hal::<Api>(open, descriptor)
704 .ok()?;
705 let enabled = device.as_hal::<Api>().is_some_and(|device| {
709 device
710 .enabled_device_extensions()
711 .contains(&ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
712 });
713 enabled.then_some((device, queue))
714 }
715 }
716
717 #[cfg(not(target_family = "wasm"))]
718 pub fn instance(display: Option<Box<dyn wgpu::wgt::WgpuHasDisplayHandle>>) -> wgpu::Instance {
719 wgpu::Instance::new(wgpu::InstanceDescriptor {
720 backends: Self::backends(),
721 flags: wgpu::InstanceFlags::default(),
722 backend_options: wgpu::BackendOptions::default(),
723 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
724 display,
725 })
726 }
727
728 #[cfg(not(target_family = "wasm"))]
736 fn backends() -> wgpu::Backends {
737 let mut backends = wgpu::Backends::VULKAN | wgpu::Backends::GL;
738 if cfg!(target_os = "windows") {
739 backends |= wgpu::Backends::DX12;
740 }
741 if cfg!(target_os = "macos") {
742 backends |= wgpu::Backends::METAL;
743 }
744 backends
745 }
746
747 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
748 let caps = surface.get_capabilities(&self.adapter);
749 if caps.formats.is_empty() {
750 let info = self.adapter.get_info();
751 anyhow::bail!(
752 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
753 display surface for this window.",
754 info.name,
755 info.backend,
756 info.device,
757 );
758 }
759 Ok(())
760 }
761
762 #[cfg(not(target_family = "wasm"))]
768 async fn select_adapter_and_device(
769 instance: &wgpu::Instance,
770 gpu_preference: &GpuPreference,
771 surface: &wgpu::Surface<'_>,
772 compositor_gpu: Option<&CompositorGpuHint>,
773 reject_software: bool,
774 ) -> anyhow::Result<(
775 wgpu::Adapter,
776 wgpu::Device,
777 wgpu::Queue,
778 bool,
779 TextureFormat,
780 )> {
781 let mut adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
782
783 if adapters.is_empty() {
784 anyhow::bail!("No GPU adapters found");
785 }
786
787 Self::order_adapters(&mut adapters, gpu_preference, compositor_gpu)?;
792
793 if let GpuPreference::Device(gpu_id) = gpu_preference {
794 log::info!("GPU preference: {:?}", gpu_id.permanent);
795 }
796
797 log::info!("Found {} GPU adapter(s):", adapters.len());
799 for adapter in &adapters {
800 let info = adapter.get_info();
801 log::info!(
802 " - {} (vendor={:#06x}, device={:#06x}, backend={:?}, type={:?})",
803 info.name,
804 info.vendor,
805 info.device,
806 info.backend,
807 info.device_type,
808 );
809 }
810
811 for adapter in adapters {
813 let info = adapter.get_info();
814
815 if reject_software && info.device_type == wgpu::DeviceType::Cpu {
816 log::info!(
817 "Skipping software renderer: {} ({:?})",
818 info.name,
819 info.backend
820 );
821 continue;
822 }
823
824 log::info!("Testing adapter: {} ({:?})...", info.name, info.backend);
825
826 match Self::try_adapter_with_surface(&adapter, surface).await {
827 Ok((device, queue, dual_source_blending, color_atlas_texture_format)) => {
828 log::info!(
829 "Selected GPU (passed configuration test): {} ({:?})",
830 info.name,
831 info.backend
832 );
833 return Ok((
834 adapter,
835 device,
836 queue,
837 dual_source_blending,
838 color_atlas_texture_format,
839 ));
840 }
841 Err(e) => {
842 log::info!(
843 " Adapter {} ({:?}) failed: {}, trying next...",
844 info.name,
845 info.backend,
846 e
847 );
848 }
849 }
850 }
851
852 anyhow::bail!("No GPU adapter found that can configure the display surface")
853 }
854
855 #[cfg(not(target_family = "wasm"))]
861 fn order_adapters(
862 adapters: &mut Vec<wgpu::Adapter>,
863 preference: &GpuPreference,
864 compositor_gpu: Option<&CompositorGpuHint>,
865 ) -> anyhow::Result<()> {
866 let compositor = compositor_gpu.map(|hint| PermanentGpuId::Pci {
867 vendor_id: Some(hint.vendor_id),
868 device_id: hint.device_id,
869 subsys_id: None,
870 revision: None,
871 });
872 match preference {
873 GpuPreference::Custom(selector) => {
874 let info: Vec<GpuAdapterInfo> = adapters
875 .iter()
876 .map(|adapter| adapter_info(&adapter.get_info()))
877 .collect();
878 let chosen = gpui_platform::custom_choice(&info, selector.as_ref())?;
879 let adapter = adapters.remove(chosen);
880 adapters.sort_by_cached_key(|adapter| {
883 gpui_platform::rank(
884 &adapter_info(&adapter.get_info()),
885 &GpuPreference::Auto,
886 compositor.as_ref(),
887 )
888 });
889 adapters.insert(0, adapter);
890 }
891 _ => adapters.sort_by_cached_key(|adapter| {
892 gpui_platform::rank(
893 &adapter_info(&adapter.get_info()),
894 preference,
895 compositor.as_ref(),
896 )
897 }),
898 }
899 Ok(())
900 }
901
902 #[cfg(not(target_family = "wasm"))]
905 async fn try_adapter_with_surface(
906 adapter: &wgpu::Adapter,
907 surface: &wgpu::Surface<'_>,
908 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
909 let caps = surface.get_capabilities(adapter);
910 if caps.formats.is_empty() {
911 anyhow::bail!("no compatible surface formats");
912 }
913 if caps.alpha_modes.is_empty() {
914 anyhow::bail!("no compatible alpha modes");
915 }
916
917 let (device, queue, dual_source_blending, color_atlas_texture_format) =
918 Self::create_device(adapter).await?;
919 let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
920
921 let test_config = wgpu::SurfaceConfiguration {
922 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
923 format: caps.formats[0],
924 width: 64,
925 height: 64,
926 present_mode: wgpu::PresentMode::Fifo,
927 desired_maximum_frame_latency: 2,
928 alpha_mode: caps.alpha_modes[0],
929 view_formats: vec![],
930 };
931
932 surface.configure(&device, &test_config);
933
934 let error = error_scope.pop().await;
935 if let Some(e) = error {
936 anyhow::bail!("surface configuration failed: {e}");
937 }
938
939 Ok((
940 device,
941 queue,
942 dual_source_blending,
943 color_atlas_texture_format,
944 ))
945 }
946
947 fn select_color_texture_format(adapter: &wgpu::Adapter) -> anyhow::Result<wgpu::TextureFormat> {
948 let required_usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
949 let bgra_features = adapter.get_texture_format_features(wgpu::TextureFormat::Bgra8Unorm);
950 let rgba_features = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba8Unorm);
951 #[cfg(target_family = "wasm")]
952 if adapter.get_info().backend == wgpu::Backend::Gl
953 && rgba_features.allowed_usages.contains(required_usages)
954 {
955 return Ok(wgpu::TextureFormat::Rgba8Unorm);
956 }
957 if bgra_features.allowed_usages.contains(required_usages) {
958 return Ok(wgpu::TextureFormat::Bgra8Unorm);
959 }
960 if rgba_features.allowed_usages.contains(required_usages) {
961 let info = adapter.get_info();
962 log::warn!(
963 "Adapter {} ({:?}) does not support Bgra8Unorm atlas textures with usages {:?}; \
964 falling back to Rgba8Unorm atlas textures.",
965 info.name,
966 info.backend,
967 required_usages,
968 );
969 return Ok(wgpu::TextureFormat::Rgba8Unorm);
970 }
971
972 let info = adapter.get_info();
973 Err(anyhow::anyhow!(
974 "Adapter {} ({:?}, device={:#06x}) does not support a usable color atlas texture \
975 format with usages {:?}. Bgra8Unorm allowed usages: {:?}; \
976 Rgba8Unorm allowed usages: {:?}.",
977 info.name,
978 info.backend,
979 info.device,
980 required_usages,
981 bgra_features.allowed_usages,
982 rgba_features.allowed_usages,
983 ))
984 }
985 pub fn backend(&self) -> WgpuBackend {
986 self.backend
987 }
988
989 pub fn uses_webgl_instance_data(&self) -> bool {
990 matches!(self.backend, WgpuBackend::Gl) && cfg!(target_family = "wasm")
991 }
992
993 pub fn supports_dual_source_blending(&self) -> bool {
994 self.dual_source_blending
995 }
996
997 pub fn color_texture_format(&self) -> wgpu::TextureFormat {
998 self.color_texture_format
999 }
1000
1001 pub fn device_lost(&self) -> bool {
1004 self.errors.device_lost()
1005 }
1006
1007 pub fn errors(&self) -> &Arc<DeviceErrorState> {
1009 &self.errors
1010 }
1011}
1012
1013#[cfg(test)]
1014mod tests {
1015 use super::DeviceErrorState;
1016
1017 #[cfg(target_os = "linux")]
1020 #[test]
1021 fn the_device_enables_the_drm_format_modifier_extension() {
1022 use wgpu::hal::vulkan::Api;
1023
1024 let instance = super::WgpuContext::instance(None);
1025 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1026 power_preference: wgpu::PowerPreference::LowPower,
1027 compatible_surface: None,
1028 force_fallback_adapter: false,
1029 }));
1030 let Ok(adapter) = adapter else {
1031 eprintln!("skipping: no adapter");
1032 return;
1033 };
1034 let supported = unsafe {
1035 adapter.as_hal::<Api>().is_some_and(|hal| {
1036 hal.physical_device_capabilities()
1037 .supports_extension(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
1038 })
1039 };
1040 let semaphore_supported = unsafe {
1041 adapter.as_hal::<Api>().is_some_and(|hal| {
1042 hal.physical_device_capabilities()
1043 .supports_extension(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME)
1044 })
1045 };
1046 if !supported {
1047 eprintln!(
1048 "skipping: {:?} does not support the extension",
1049 adapter.get_info().name
1050 );
1051 return;
1052 }
1053
1054 let descriptor = wgpu::DeviceDescriptor {
1055 label: Some("drm modifier extension test"),
1056 required_features: wgpu::Features::empty(),
1057 required_limits: wgpu::Limits::downlevel_defaults()
1058 .using_resolution(adapter.limits())
1059 .using_alignment(adapter.limits()),
1060 memory_hints: wgpu::MemoryHints::MemoryUsage,
1061 trace: wgpu::Trace::Off,
1062 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1063 };
1064 let (device, _queue) =
1065 super::WgpuContext::device_with_surface_import(&adapter, &descriptor)
1066 .expect("the escape hatch creates the device");
1067 let enabled = unsafe { device.as_hal::<Api>() }.is_some_and(|device| {
1068 let extensions = device.enabled_device_extensions();
1069 extensions.contains(&ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
1070 && (!semaphore_supported
1071 || extensions.contains(&ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME))
1072 });
1073 assert!(
1074 enabled,
1075 "the created device must have the surface extensions enabled"
1076 );
1077 }
1078
1079 #[test]
1080 fn device_errors_are_observed_independently() {
1081 let errors = DeviceErrorState::default();
1082 let mut first = 0;
1083 let mut second = 0;
1084 assert_eq!(errors.observe_error(&mut first), None);
1085 errors.record("first".into());
1086 assert_eq!(errors.observe_error(&mut first).as_deref(), Some("first"));
1087 assert_eq!(errors.observe_error(&mut first), None);
1088 assert_eq!(errors.observe_error(&mut second).as_deref(), Some("first"));
1089 assert_eq!(errors.observe_error(&mut second), None);
1090
1091 errors.record("second".into());
1092 assert_eq!(errors.observe_error(&mut second).as_deref(), Some("second"));
1093 assert_eq!(errors.observe_error(&mut first).as_deref(), Some("second"));
1094 assert_eq!(errors.observe_error(&mut first), None);
1095 assert_eq!(errors.observe_error(&mut second), None);
1096 }
1097
1098 #[test]
1099 fn device_errors_coalesce_and_new_observers_see_latest() {
1100 let errors = DeviceErrorState::default();
1101 errors.record("older".into());
1102 errors.record("latest".into());
1103 let mut generation = 0;
1104 assert_eq!(
1105 errors.observe_error(&mut generation).as_deref(),
1106 Some("latest")
1107 );
1108 assert_eq!(errors.observe_error(&mut generation), None);
1109
1110 let replacement = DeviceErrorState::default();
1111 replacement.record("replacement".into());
1112 generation = 0;
1113 assert_eq!(
1114 replacement.observe_error(&mut generation).as_deref(),
1115 Some("replacement")
1116 );
1117 assert_eq!(replacement.observe_error(&mut generation), None);
1118 }
1119}