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 apply_limit_buckets: false,
497 })
498 .await
499 .map_err(|error| {
500 anyhow::anyhow!(
501 "Failed to request a {preference:?} adapter compatible with the canvas: {error}"
502 )
503 })?;
504 let adapter_info = adapter.get_info();
505 let backend = match adapter_info.backend {
506 wgpu::Backend::BrowserWebGpu => WgpuBackend::BrowserWebGpu,
507 wgpu::Backend::Gl => WgpuBackend::Gl,
508 backend => {
509 anyhow::bail!(
510 "Browser graphics initialization selected unexpected backend {backend:?}"
511 )
512 }
513 };
514
515 let (device, queue, dual_source_blending, color_texture_format) =
516 Self::create_device(&adapter).await?;
517 let errors = DeviceErrorState::install(&device);
518 log::info!(
519 "Browser graphics initialized: requested={preference:?}, selected={backend:?}, \
520 adapter={:?}, limits={:?}, dual_source_blending={dual_source_blending}",
521 adapter_info.name,
522 device.limits(),
523 );
524
525 let context = Self {
526 instance,
527 adapter,
528 device: Arc::new(device),
529 queue: Arc::new(queue),
530 backend,
531 dual_source_blending,
532 color_texture_format,
533 errors,
534 };
535 Ok(PreparedWebGraphics { context, surface })
536 }
537
538 async fn create_device(
539 adapter: &wgpu::Adapter,
540 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
541 let dual_source_blending = adapter
542 .features()
543 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
544
545 let mut required_features = wgpu::Features::empty();
546 if dual_source_blending {
547 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
548 } else {
549 log::warn!(
550 "Dual-source blending not available on this GPU. \
551 Subpixel text antialiasing will be disabled."
552 );
553 }
554
555 let color_atlas_texture_format = Self::select_color_texture_format(adapter)?;
556
557 if adapter
561 .features()
562 .contains(wgpu::Features::TEXTURE_FORMAT_NV12)
563 {
564 required_features |= wgpu::Features::TEXTURE_FORMAT_NV12;
565 }
566
567 if adapter
572 .features()
573 .contains(wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
574 {
575 required_features |= wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
576 }
577 #[cfg(target_family = "wasm")]
578 let required_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
579 wgpu::Limits::downlevel_webgl2_defaults()
580 .using_resolution(adapter.limits())
581 .using_alignment(adapter.limits())
582 } else {
583 wgpu::Limits::downlevel_defaults()
584 .using_resolution(adapter.limits())
585 .using_alignment(adapter.limits())
586 };
587 #[cfg(not(target_family = "wasm"))]
588 let required_limits = {
589 let supported = adapter.limits();
590 let mut limits = wgpu::Limits::downlevel_defaults()
591 .using_resolution(supported.clone())
592 .using_alignment(supported.clone());
593 if adapter.get_info().backend != wgpu::Backend::Gl {
600 limits.max_storage_buffers_per_shader_stage =
601 supported.max_storage_buffers_per_shader_stage;
602 limits.max_storage_textures_per_shader_stage =
603 supported.max_storage_textures_per_shader_stage;
604 limits.max_uniform_buffer_binding_size = supported.max_uniform_buffer_binding_size;
605 limits.max_inter_stage_shader_variables =
606 supported.max_inter_stage_shader_variables;
607 limits.max_color_attachments = supported.max_color_attachments;
608 }
609 limits
610 };
611
612 let descriptor = wgpu::DeviceDescriptor {
613 label: Some("gpui_device"),
614 required_features,
615 required_limits,
616 memory_hints: wgpu::MemoryHints::MemoryUsage,
617 trace: wgpu::Trace::Off,
618 experimental_features: wgpu::ExperimentalFeatures::disabled(),
619 };
620
621 #[cfg(target_os = "linux")]
627 let with_surface_import = Self::device_with_surface_import(adapter, &descriptor);
628 #[cfg(not(target_os = "linux"))]
629 let with_surface_import: Option<(wgpu::Device, wgpu::Queue)> = None;
630
631 let (device, queue) = match with_surface_import {
632 Some(created) => created,
633 None => adapter
634 .request_device(&descriptor)
635 .await
636 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?,
637 };
638
639 Ok((
640 device,
641 queue,
642 dual_source_blending,
643 color_atlas_texture_format,
644 ))
645 }
646
647 #[cfg(target_os = "linux")]
662 fn device_with_surface_import(
663 adapter: &wgpu::Adapter,
664 descriptor: &wgpu::DeviceDescriptor<'_>,
665 ) -> Option<(wgpu::Device, wgpu::Queue)> {
666 use wgpu::hal::vulkan::Api;
667
668 unsafe {
673 let hal = adapter.as_hal::<Api>()?;
674 if !hal
675 .physical_device_capabilities()
676 .supports_extension(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
677 {
678 return None;
679 }
680 let with_semaphore_fd = hal
686 .physical_device_capabilities()
687 .supports_extension(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME);
688 let open = hal
689 .open_with_callback(
690 descriptor.required_features,
691 &descriptor.required_limits,
692 &descriptor.memory_hints,
693 Some(Box::new(move |args| {
694 args.extensions
695 .push(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME);
696 if with_semaphore_fd {
697 args.extensions
698 .push(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME);
699 }
700 })),
701 )
702 .ok()?;
703 let (device, queue) = adapter
704 .create_device_from_hal::<Api>(open, descriptor)
705 .ok()?;
706 let enabled = device.as_hal::<Api>().is_some_and(|device| {
710 device
711 .enabled_device_extensions()
712 .contains(&ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
713 });
714 enabled.then_some((device, queue))
715 }
716 }
717
718 #[cfg(not(target_family = "wasm"))]
719 pub fn instance(display: Option<Box<dyn wgpu::wgt::WgpuHasDisplayHandle>>) -> wgpu::Instance {
720 wgpu::Instance::new(wgpu::InstanceDescriptor {
721 backends: Self::backends(),
722 flags: wgpu::InstanceFlags::default(),
723 backend_options: wgpu::BackendOptions::default(),
724 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
725 display,
726 })
727 }
728
729 #[cfg(not(target_family = "wasm"))]
737 fn backends() -> wgpu::Backends {
738 let mut backends = wgpu::Backends::VULKAN | wgpu::Backends::GL;
739 if cfg!(target_os = "windows") {
740 backends |= wgpu::Backends::DX12;
741 }
742 if cfg!(target_os = "macos") {
743 backends |= wgpu::Backends::METAL;
744 }
745 backends
746 }
747
748 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
749 let caps = surface.get_capabilities(&self.adapter);
750 if caps.formats.is_empty() {
751 let info = self.adapter.get_info();
752 anyhow::bail!(
753 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
754 display surface for this window.",
755 info.name,
756 info.backend,
757 info.device,
758 );
759 }
760 Ok(())
761 }
762
763 #[cfg(not(target_family = "wasm"))]
769 async fn select_adapter_and_device(
770 instance: &wgpu::Instance,
771 gpu_preference: &GpuPreference,
772 surface: &wgpu::Surface<'_>,
773 compositor_gpu: Option<&CompositorGpuHint>,
774 reject_software: bool,
775 ) -> anyhow::Result<(
776 wgpu::Adapter,
777 wgpu::Device,
778 wgpu::Queue,
779 bool,
780 TextureFormat,
781 )> {
782 let mut adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
783
784 if adapters.is_empty() {
785 anyhow::bail!("No GPU adapters found");
786 }
787
788 Self::order_adapters(&mut adapters, gpu_preference, compositor_gpu)?;
793
794 if let GpuPreference::Device(gpu_id) = gpu_preference {
795 log::info!("GPU preference: {:?}", gpu_id.permanent);
796 }
797
798 log::info!("Found {} GPU adapter(s):", adapters.len());
800 for adapter in &adapters {
801 let info = adapter.get_info();
802 log::info!(
803 " - {} (vendor={:#06x}, device={:#06x}, backend={:?}, type={:?})",
804 info.name,
805 info.vendor,
806 info.device,
807 info.backend,
808 info.device_type,
809 );
810 }
811
812 for adapter in adapters {
814 let info = adapter.get_info();
815
816 if reject_software && info.device_type == wgpu::DeviceType::Cpu {
817 log::info!(
818 "Skipping software renderer: {} ({:?})",
819 info.name,
820 info.backend
821 );
822 continue;
823 }
824
825 log::info!("Testing adapter: {} ({:?})...", info.name, info.backend);
826
827 match Self::try_adapter_with_surface(&adapter, surface).await {
828 Ok((device, queue, dual_source_blending, color_atlas_texture_format)) => {
829 log::info!(
830 "Selected GPU (passed configuration test): {} ({:?})",
831 info.name,
832 info.backend
833 );
834 return Ok((
835 adapter,
836 device,
837 queue,
838 dual_source_blending,
839 color_atlas_texture_format,
840 ));
841 }
842 Err(e) => {
843 log::info!(
844 " Adapter {} ({:?}) failed: {}, trying next...",
845 info.name,
846 info.backend,
847 e
848 );
849 }
850 }
851 }
852
853 anyhow::bail!("No GPU adapter found that can configure the display surface")
854 }
855
856 #[cfg(not(target_family = "wasm"))]
862 fn order_adapters(
863 adapters: &mut Vec<wgpu::Adapter>,
864 preference: &GpuPreference,
865 compositor_gpu: Option<&CompositorGpuHint>,
866 ) -> anyhow::Result<()> {
867 let compositor = compositor_gpu.map(|hint| PermanentGpuId::Pci {
868 vendor_id: Some(hint.vendor_id),
869 device_id: hint.device_id,
870 subsys_id: None,
871 revision: None,
872 });
873 match preference {
874 GpuPreference::Custom(selector) => {
875 let info: Vec<GpuAdapterInfo> = adapters
876 .iter()
877 .map(|adapter| adapter_info(&adapter.get_info()))
878 .collect();
879 let chosen = gpui_platform::custom_choice(&info, selector.as_ref())?;
880 let adapter = adapters.remove(chosen);
881 adapters.sort_by_cached_key(|adapter| {
884 gpui_platform::rank(
885 &adapter_info(&adapter.get_info()),
886 &GpuPreference::Auto,
887 compositor.as_ref(),
888 )
889 });
890 adapters.insert(0, adapter);
891 }
892 _ => adapters.sort_by_cached_key(|adapter| {
893 gpui_platform::rank(
894 &adapter_info(&adapter.get_info()),
895 preference,
896 compositor.as_ref(),
897 )
898 }),
899 }
900 Ok(())
901 }
902
903 #[cfg(not(target_family = "wasm"))]
906 async fn try_adapter_with_surface(
907 adapter: &wgpu::Adapter,
908 surface: &wgpu::Surface<'_>,
909 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
910 let caps = surface.get_capabilities(adapter);
911 if caps.formats.is_empty() {
912 anyhow::bail!("no compatible surface formats");
913 }
914 if caps.alpha_modes.is_empty() {
915 anyhow::bail!("no compatible alpha modes");
916 }
917
918 let (device, queue, dual_source_blending, color_atlas_texture_format) =
919 Self::create_device(adapter).await?;
920 let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
921
922 let test_config = wgpu::SurfaceConfiguration {
923 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
924 format: caps.formats[0],
925 color_space: wgpu::SurfaceColorSpace::Auto,
926 width: 64,
927 height: 64,
928 present_mode: wgpu::PresentMode::Fifo,
929 desired_maximum_frame_latency: 2,
930 alpha_mode: caps.alpha_modes[0],
931 view_formats: vec![],
932 };
933
934 surface.configure(&device, &test_config);
935
936 let error = error_scope.pop().await;
937 if let Some(e) = error {
938 anyhow::bail!("surface configuration failed: {e}");
939 }
940
941 Ok((
942 device,
943 queue,
944 dual_source_blending,
945 color_atlas_texture_format,
946 ))
947 }
948
949 fn select_color_texture_format(adapter: &wgpu::Adapter) -> anyhow::Result<wgpu::TextureFormat> {
950 let required_usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
951 let bgra_features = adapter.get_texture_format_features(wgpu::TextureFormat::Bgra8Unorm);
952 let rgba_features = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba8Unorm);
953 #[cfg(target_family = "wasm")]
954 if adapter.get_info().backend == wgpu::Backend::Gl
955 && rgba_features.allowed_usages.contains(required_usages)
956 {
957 return Ok(wgpu::TextureFormat::Rgba8Unorm);
958 }
959 if bgra_features.allowed_usages.contains(required_usages) {
960 return Ok(wgpu::TextureFormat::Bgra8Unorm);
961 }
962 if rgba_features.allowed_usages.contains(required_usages) {
963 let info = adapter.get_info();
964 log::warn!(
965 "Adapter {} ({:?}) does not support Bgra8Unorm atlas textures with usages {:?}; \
966 falling back to Rgba8Unorm atlas textures.",
967 info.name,
968 info.backend,
969 required_usages,
970 );
971 return Ok(wgpu::TextureFormat::Rgba8Unorm);
972 }
973
974 let info = adapter.get_info();
975 Err(anyhow::anyhow!(
976 "Adapter {} ({:?}, device={:#06x}) does not support a usable color atlas texture \
977 format with usages {:?}. Bgra8Unorm allowed usages: {:?}; \
978 Rgba8Unorm allowed usages: {:?}.",
979 info.name,
980 info.backend,
981 info.device,
982 required_usages,
983 bgra_features.allowed_usages,
984 rgba_features.allowed_usages,
985 ))
986 }
987 pub fn backend(&self) -> WgpuBackend {
988 self.backend
989 }
990
991 pub fn uses_webgl_instance_data(&self) -> bool {
992 matches!(self.backend, WgpuBackend::Gl) && cfg!(target_family = "wasm")
993 }
994
995 pub fn supports_dual_source_blending(&self) -> bool {
996 self.dual_source_blending
997 }
998
999 pub fn color_texture_format(&self) -> wgpu::TextureFormat {
1000 self.color_texture_format
1001 }
1002
1003 pub fn device_lost(&self) -> bool {
1006 self.errors.device_lost()
1007 }
1008
1009 pub fn errors(&self) -> &Arc<DeviceErrorState> {
1011 &self.errors
1012 }
1013}
1014
1015#[cfg(test)]
1016mod tests {
1017 use super::DeviceErrorState;
1018
1019 #[cfg(target_os = "linux")]
1022 #[test]
1023 fn the_device_enables_the_drm_format_modifier_extension() {
1024 use wgpu::hal::vulkan::Api;
1025
1026 let instance = super::WgpuContext::instance(None);
1027 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1028 power_preference: wgpu::PowerPreference::LowPower,
1029 compatible_surface: None,
1030 force_fallback_adapter: false,
1031 apply_limit_buckets: false,
1032 }));
1033 let Ok(adapter) = adapter else {
1034 eprintln!("skipping: no adapter");
1035 return;
1036 };
1037 let supported = unsafe {
1038 adapter.as_hal::<Api>().is_some_and(|hal| {
1039 hal.physical_device_capabilities()
1040 .supports_extension(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
1041 })
1042 };
1043 let semaphore_supported = unsafe {
1044 adapter.as_hal::<Api>().is_some_and(|hal| {
1045 hal.physical_device_capabilities()
1046 .supports_extension(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME)
1047 })
1048 };
1049 if !supported {
1050 eprintln!(
1051 "skipping: {:?} does not support the extension",
1052 adapter.get_info().name
1053 );
1054 return;
1055 }
1056
1057 let descriptor = wgpu::DeviceDescriptor {
1058 label: Some("drm modifier extension test"),
1059 required_features: wgpu::Features::empty(),
1060 required_limits: wgpu::Limits::downlevel_defaults()
1061 .using_resolution(adapter.limits())
1062 .using_alignment(adapter.limits()),
1063 memory_hints: wgpu::MemoryHints::MemoryUsage,
1064 trace: wgpu::Trace::Off,
1065 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1066 };
1067 let (device, _queue) =
1068 super::WgpuContext::device_with_surface_import(&adapter, &descriptor)
1069 .expect("the escape hatch creates the device");
1070 let enabled = unsafe { device.as_hal::<Api>() }.is_some_and(|device| {
1071 let extensions = device.enabled_device_extensions();
1072 extensions.contains(&ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
1073 && (!semaphore_supported
1074 || extensions.contains(&ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME))
1075 });
1076 assert!(
1077 enabled,
1078 "the created device must have the surface extensions enabled"
1079 );
1080 }
1081
1082 #[test]
1083 fn device_errors_are_observed_independently() {
1084 let errors = DeviceErrorState::default();
1085 let mut first = 0;
1086 let mut second = 0;
1087 assert_eq!(errors.observe_error(&mut first), None);
1088 errors.record("first".into());
1089 assert_eq!(errors.observe_error(&mut first).as_deref(), Some("first"));
1090 assert_eq!(errors.observe_error(&mut first), None);
1091 assert_eq!(errors.observe_error(&mut second).as_deref(), Some("first"));
1092 assert_eq!(errors.observe_error(&mut second), None);
1093
1094 errors.record("second".into());
1095 assert_eq!(errors.observe_error(&mut second).as_deref(), Some("second"));
1096 assert_eq!(errors.observe_error(&mut first).as_deref(), Some("second"));
1097 assert_eq!(errors.observe_error(&mut first), None);
1098 assert_eq!(errors.observe_error(&mut second), None);
1099 }
1100
1101 #[test]
1102 fn device_errors_coalesce_and_new_observers_see_latest() {
1103 let errors = DeviceErrorState::default();
1104 errors.record("older".into());
1105 errors.record("latest".into());
1106 let mut generation = 0;
1107 assert_eq!(
1108 errors.observe_error(&mut generation).as_deref(),
1109 Some("latest")
1110 );
1111 assert_eq!(errors.observe_error(&mut generation), None);
1112
1113 let replacement = DeviceErrorState::default();
1114 replacement.record("replacement".into());
1115 generation = 0;
1116 assert_eq!(
1117 replacement.observe_error(&mut generation).as_deref(),
1118 Some("replacement")
1119 );
1120 assert_eq!(replacement.observe_error(&mut generation), None);
1121 }
1122}