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(test, feature = "bench-support", feature = "test-support", feature = "headless")
298 ))]
299 pub(crate) fn new_headless_with(
300 gpu_preference: GpuPreference,
301 ) -> anyhow::Result<(Self, wgpu::TextureFormat)> {
302 static HEADLESS: Mutex<Option<(WgpuContext, wgpu::TextureFormat)>> = Mutex::new(None);
308 let mut headless = HEADLESS.lock();
309 if let Some((context, format)) = headless.as_ref()
310 && !context.device_lost()
311 {
312 return Ok((context.clone(), *format));
313 }
314 let created = Self::create_headless(gpu_preference)?;
315 *headless = Some(created.clone());
316 Ok(created)
317 }
318
319 #[cfg(all(
320 not(target_family = "wasm"),
321 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
322 ))]
323 fn create_headless(
324 gpu_preference: GpuPreference,
325 ) -> anyhow::Result<(Self, wgpu::TextureFormat)> {
326 let instance = Self::instance(None);
327 let (adapter, device, queue, dual_source_blending, color_texture_format, target_format) =
328 pollster::block_on(async {
329 let mut adapters = instance.enumerate_adapters(wgpu::Backends::all()).await;
330 Self::order_adapters(&mut adapters, &gpu_preference, None)?;
331
332 for adapter in adapters {
333 let adapter_info = adapter.get_info();
334 let Some(target_format) = Self::headless_target_format(&adapter) else {
335 log::warn!(
336 "Adapter {:?} has no supported headless render target format",
337 adapter_info.name
338 );
339 continue;
340 };
341
342 match Self::create_device(&adapter).await {
343 Ok((device, queue, dual_source_blending, color_texture_format)) => {
344 #[cfg(feature = "bench-support")]
345 if adapter_info.device_type == wgpu::DeviceType::Cpu {
346 log::error!(
347 "Headless renderer selected software adapter {:?}; \
348 benchmark results measure CPU software rendering, not hardware GPU rendering",
349 adapter_info.name
350 );
351 }
352 return Ok((
353 adapter,
354 device,
355 queue,
356 dual_source_blending,
357 color_texture_format,
358 target_format,
359 ));
360 }
361 Err(error) => {
362 log::warn!(
363 "Failed to create a headless device for adapter {:?}: {error:#}",
364 adapter_info.name
365 );
366 }
367 }
368 }
369
370 anyhow::bail!("No usable headless GPU adapter found")
371 })?;
372
373 Ok((
374 Self::from_native_adapter_and_device(
375 instance,
376 adapter,
377 device,
378 queue,
379 dual_source_blending,
380 color_texture_format,
381 ),
382 target_format,
383 ))
384 }
385
386 #[cfg(all(
387 not(target_family = "wasm"),
388 any(test, feature = "bench-support", feature = "test-support", feature = "headless")
389 ))]
390 fn headless_target_format(adapter: &wgpu::Adapter) -> Option<wgpu::TextureFormat> {
394 let required_usages =
395 wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC;
396 [
397 wgpu::TextureFormat::Rgba8Unorm,
398 wgpu::TextureFormat::Bgra8Unorm,
399 ]
400 .into_iter()
401 .find(|format| {
402 adapter
403 .get_texture_format_features(*format)
404 .allowed_usages
405 .contains(required_usages)
406 })
407 }
408
409 #[cfg(not(target_family = "wasm"))]
410 fn from_native_adapter_and_device(
411 instance: wgpu::Instance,
412 adapter: wgpu::Adapter,
413 device: wgpu::Device,
414 queue: wgpu::Queue,
415 dual_source_blending: bool,
416 color_texture_format: TextureFormat,
417 ) -> Self {
418 let errors = DeviceErrorState::install(&device);
419
420 log::info!(
421 "Selected GPU adapter: {:?} ({:?})",
422 adapter.get_info().name,
423 adapter.get_info().backend
424 );
425 let backend = WgpuBackend::Native(adapter.get_info().backend);
426
427 Self {
428 instance,
429 adapter,
430 device: Arc::new(device),
431 queue: Arc::new(queue),
432 backend,
433 dual_source_blending,
434 color_texture_format,
435 errors,
436 }
437 }
438
439 #[cfg(target_family = "wasm")]
440 pub async fn new_web(
441 canvas: &web_sys::HtmlCanvasElement,
442 preference: WebBackendPreference,
443 ) -> anyhow::Result<PreparedWebGraphics> {
444 Self::new_web_with_backend(canvas, preference).await
445 }
446
447 #[cfg(target_family = "wasm")]
448 #[allow(clippy::arc_with_non_send_sync)]
449 async fn new_web_with_backend(
450 canvas: &web_sys::HtmlCanvasElement,
451 preference: WebBackendPreference,
452 ) -> anyhow::Result<PreparedWebGraphics> {
453 let backends = match preference {
454 WebBackendPreference::Auto => wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
455 WebBackendPreference::WebGpu => wgpu::Backends::BROWSER_WEBGPU,
456 WebBackendPreference::WebGl => wgpu::Backends::GL,
457 };
458 let descriptor = wgpu::InstanceDescriptor {
459 backends,
460 flags: wgpu::InstanceFlags::default(),
461 backend_options: wgpu::BackendOptions::default(),
462 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
463 display: Some(Box::new(WebDisplaySource)),
464 };
465 let instance = if preference == WebBackendPreference::Auto {
466 wgpu::util::new_instance_with_webgpu_detection(descriptor).await
467 } else {
468 wgpu::Instance::new(descriptor)
469 };
470 let surface = instance
471 .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
472 .map_err(|error| {
473 anyhow::anyhow!("Failed to create browser graphics surface: {error}")
474 })?;
475
476 let adapter = instance
477 .request_adapter(&wgpu::RequestAdapterOptions {
478 power_preference: wgpu::PowerPreference::HighPerformance,
479 compatible_surface: Some(&surface),
480 force_fallback_adapter: false,
481 })
482 .await
483 .map_err(|error| {
484 anyhow::anyhow!(
485 "Failed to request a {preference:?} adapter compatible with the canvas: {error}"
486 )
487 })?;
488 let adapter_info = adapter.get_info();
489 let backend = match adapter_info.backend {
490 wgpu::Backend::BrowserWebGpu => WgpuBackend::BrowserWebGpu,
491 wgpu::Backend::Gl => WgpuBackend::Gl,
492 backend => {
493 anyhow::bail!(
494 "Browser graphics initialization selected unexpected backend {backend:?}"
495 )
496 }
497 };
498
499 let (device, queue, dual_source_blending, color_texture_format) =
500 Self::create_device(&adapter).await?;
501 let errors = DeviceErrorState::install(&device);
502 log::info!(
503 "Browser graphics initialized: requested={preference:?}, selected={backend:?}, \
504 adapter={:?}, limits={:?}, dual_source_blending={dual_source_blending}",
505 adapter_info.name,
506 device.limits(),
507 );
508
509 let context = Self {
510 instance,
511 adapter,
512 device: Arc::new(device),
513 queue: Arc::new(queue),
514 backend,
515 dual_source_blending,
516 color_texture_format,
517 errors,
518 };
519 Ok(PreparedWebGraphics { context, surface })
520 }
521
522 async fn create_device(
523 adapter: &wgpu::Adapter,
524 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
525 let dual_source_blending = adapter
526 .features()
527 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
528
529 let mut required_features = wgpu::Features::empty();
530 if dual_source_blending {
531 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
532 } else {
533 log::warn!(
534 "Dual-source blending not available on this GPU. \
535 Subpixel text antialiasing will be disabled."
536 );
537 }
538
539 let color_atlas_texture_format = Self::select_color_texture_format(adapter)?;
540
541 if adapter
545 .features()
546 .contains(wgpu::Features::TEXTURE_FORMAT_NV12)
547 {
548 required_features |= wgpu::Features::TEXTURE_FORMAT_NV12;
549 }
550
551 if adapter
556 .features()
557 .contains(wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES)
558 {
559 required_features |= wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES;
560 }
561 #[cfg(target_family = "wasm")]
562 let required_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
563 wgpu::Limits::downlevel_webgl2_defaults()
564 .using_resolution(adapter.limits())
565 .using_alignment(adapter.limits())
566 } else {
567 wgpu::Limits::downlevel_defaults()
568 .using_resolution(adapter.limits())
569 .using_alignment(adapter.limits())
570 };
571 #[cfg(not(target_family = "wasm"))]
572 let required_limits = {
573 let supported = adapter.limits();
574 let mut limits = wgpu::Limits::downlevel_defaults()
575 .using_resolution(supported.clone())
576 .using_alignment(supported.clone());
577 if adapter.get_info().backend != wgpu::Backend::Gl {
584 limits.max_storage_buffers_per_shader_stage =
585 supported.max_storage_buffers_per_shader_stage;
586 limits.max_storage_textures_per_shader_stage =
587 supported.max_storage_textures_per_shader_stage;
588 limits.max_uniform_buffer_binding_size = supported.max_uniform_buffer_binding_size;
589 limits.max_inter_stage_shader_variables =
590 supported.max_inter_stage_shader_variables;
591 limits.max_color_attachments = supported.max_color_attachments;
592 }
593 limits
594 };
595
596 let descriptor = wgpu::DeviceDescriptor {
597 label: Some("gpui_device"),
598 required_features,
599 required_limits,
600 memory_hints: wgpu::MemoryHints::MemoryUsage,
601 trace: wgpu::Trace::Off,
602 experimental_features: wgpu::ExperimentalFeatures::disabled(),
603 };
604
605 #[cfg(target_os = "linux")]
611 let with_surface_import = Self::device_with_surface_import(adapter, &descriptor);
612 #[cfg(not(target_os = "linux"))]
613 let with_surface_import: Option<(wgpu::Device, wgpu::Queue)> = None;
614
615 let (device, queue) = match with_surface_import {
616 Some(created) => created,
617 None => adapter
618 .request_device(&descriptor)
619 .await
620 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?,
621 };
622
623 Ok((
624 device,
625 queue,
626 dual_source_blending,
627 color_atlas_texture_format,
628 ))
629 }
630
631 #[cfg(target_os = "linux")]
646 fn device_with_surface_import(
647 adapter: &wgpu::Adapter,
648 descriptor: &wgpu::DeviceDescriptor<'_>,
649 ) -> Option<(wgpu::Device, wgpu::Queue)> {
650 use wgpu::hal::vulkan::Api;
651
652 unsafe {
657 let hal = adapter.as_hal::<Api>()?;
658 if !hal
659 .physical_device_capabilities()
660 .supports_extension(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
661 {
662 return None;
663 }
664 let with_semaphore_fd = hal
670 .physical_device_capabilities()
671 .supports_extension(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME);
672 let open = hal
673 .open_with_callback(
674 descriptor.required_features,
675 &descriptor.required_limits,
676 &descriptor.memory_hints,
677 Some(Box::new(move |args| {
678 args.extensions
679 .push(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME);
680 if with_semaphore_fd {
681 args.extensions
682 .push(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME);
683 }
684 })),
685 )
686 .ok()?;
687 let (device, queue) = adapter
688 .create_device_from_hal::<Api>(open, descriptor)
689 .ok()?;
690 let enabled = device.as_hal::<Api>().is_some_and(|device| {
694 device
695 .enabled_device_extensions()
696 .contains(&ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
697 });
698 enabled.then_some((device, queue))
699 }
700 }
701
702 #[cfg(not(target_family = "wasm"))]
703 pub fn instance(display: Option<Box<dyn wgpu::wgt::WgpuHasDisplayHandle>>) -> wgpu::Instance {
704 wgpu::Instance::new(wgpu::InstanceDescriptor {
705 backends: Self::backends(),
706 flags: wgpu::InstanceFlags::default(),
707 backend_options: wgpu::BackendOptions::default(),
708 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
709 display,
710 })
711 }
712
713 #[cfg(not(target_family = "wasm"))]
721 fn backends() -> wgpu::Backends {
722 let mut backends = wgpu::Backends::VULKAN | wgpu::Backends::GL;
723 if cfg!(target_os = "windows") {
724 backends |= wgpu::Backends::DX12;
725 }
726 if cfg!(target_os = "macos") {
727 backends |= wgpu::Backends::METAL;
728 }
729 backends
730 }
731
732 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
733 let caps = surface.get_capabilities(&self.adapter);
734 if caps.formats.is_empty() {
735 let info = self.adapter.get_info();
736 anyhow::bail!(
737 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
738 display surface for this window.",
739 info.name,
740 info.backend,
741 info.device,
742 );
743 }
744 Ok(())
745 }
746
747 #[cfg(not(target_family = "wasm"))]
753 async fn select_adapter_and_device(
754 instance: &wgpu::Instance,
755 gpu_preference: &GpuPreference,
756 surface: &wgpu::Surface<'_>,
757 compositor_gpu: Option<&CompositorGpuHint>,
758 reject_software: bool,
759 ) -> anyhow::Result<(
760 wgpu::Adapter,
761 wgpu::Device,
762 wgpu::Queue,
763 bool,
764 TextureFormat,
765 )> {
766 let mut adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
767
768 if adapters.is_empty() {
769 anyhow::bail!("No GPU adapters found");
770 }
771
772 Self::order_adapters(&mut adapters, gpu_preference, compositor_gpu)?;
777
778 if let GpuPreference::Device(gpu_id) = gpu_preference {
779 log::info!("GPU preference: {:?}", gpu_id.permanent);
780 }
781
782 log::info!("Found {} GPU adapter(s):", adapters.len());
784 for adapter in &adapters {
785 let info = adapter.get_info();
786 log::info!(
787 " - {} (vendor={:#06x}, device={:#06x}, backend={:?}, type={:?})",
788 info.name,
789 info.vendor,
790 info.device,
791 info.backend,
792 info.device_type,
793 );
794 }
795
796 for adapter in adapters {
798 let info = adapter.get_info();
799
800 if reject_software && info.device_type == wgpu::DeviceType::Cpu {
801 log::info!(
802 "Skipping software renderer: {} ({:?})",
803 info.name,
804 info.backend
805 );
806 continue;
807 }
808
809 log::info!("Testing adapter: {} ({:?})...", info.name, info.backend);
810
811 match Self::try_adapter_with_surface(&adapter, surface).await {
812 Ok((device, queue, dual_source_blending, color_atlas_texture_format)) => {
813 log::info!(
814 "Selected GPU (passed configuration test): {} ({:?})",
815 info.name,
816 info.backend
817 );
818 return Ok((
819 adapter,
820 device,
821 queue,
822 dual_source_blending,
823 color_atlas_texture_format,
824 ));
825 }
826 Err(e) => {
827 log::info!(
828 " Adapter {} ({:?}) failed: {}, trying next...",
829 info.name,
830 info.backend,
831 e
832 );
833 }
834 }
835 }
836
837 anyhow::bail!("No GPU adapter found that can configure the display surface")
838 }
839
840 #[cfg(not(target_family = "wasm"))]
846 fn order_adapters(
847 adapters: &mut Vec<wgpu::Adapter>,
848 preference: &GpuPreference,
849 compositor_gpu: Option<&CompositorGpuHint>,
850 ) -> anyhow::Result<()> {
851 let compositor = compositor_gpu.map(|hint| PermanentGpuId::Pci {
852 vendor_id: Some(hint.vendor_id),
853 device_id: hint.device_id,
854 subsys_id: None,
855 revision: None,
856 });
857 match preference {
858 GpuPreference::Custom(selector) => {
859 let info: Vec<GpuAdapterInfo> = adapters
860 .iter()
861 .map(|adapter| adapter_info(&adapter.get_info()))
862 .collect();
863 let chosen = gpui_platform::custom_choice(&info, selector.as_ref())?;
864 let adapter = adapters.remove(chosen);
865 adapters.sort_by_cached_key(|adapter| {
868 gpui_platform::rank(
869 &adapter_info(&adapter.get_info()),
870 &GpuPreference::Auto,
871 compositor.as_ref(),
872 )
873 });
874 adapters.insert(0, adapter);
875 }
876 _ => adapters.sort_by_cached_key(|adapter| {
877 gpui_platform::rank(
878 &adapter_info(&adapter.get_info()),
879 preference,
880 compositor.as_ref(),
881 )
882 }),
883 }
884 Ok(())
885 }
886
887 #[cfg(not(target_family = "wasm"))]
890 async fn try_adapter_with_surface(
891 adapter: &wgpu::Adapter,
892 surface: &wgpu::Surface<'_>,
893 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
894 let caps = surface.get_capabilities(adapter);
895 if caps.formats.is_empty() {
896 anyhow::bail!("no compatible surface formats");
897 }
898 if caps.alpha_modes.is_empty() {
899 anyhow::bail!("no compatible alpha modes");
900 }
901
902 let (device, queue, dual_source_blending, color_atlas_texture_format) =
903 Self::create_device(adapter).await?;
904 let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
905
906 let test_config = wgpu::SurfaceConfiguration {
907 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
908 format: caps.formats[0],
909 width: 64,
910 height: 64,
911 present_mode: wgpu::PresentMode::Fifo,
912 desired_maximum_frame_latency: 2,
913 alpha_mode: caps.alpha_modes[0],
914 view_formats: vec![],
915 };
916
917 surface.configure(&device, &test_config);
918
919 let error = error_scope.pop().await;
920 if let Some(e) = error {
921 anyhow::bail!("surface configuration failed: {e}");
922 }
923
924 Ok((
925 device,
926 queue,
927 dual_source_blending,
928 color_atlas_texture_format,
929 ))
930 }
931
932 fn select_color_texture_format(adapter: &wgpu::Adapter) -> anyhow::Result<wgpu::TextureFormat> {
933 let required_usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
934 let bgra_features = adapter.get_texture_format_features(wgpu::TextureFormat::Bgra8Unorm);
935 let rgba_features = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba8Unorm);
936 #[cfg(target_family = "wasm")]
937 if adapter.get_info().backend == wgpu::Backend::Gl
938 && rgba_features.allowed_usages.contains(required_usages)
939 {
940 return Ok(wgpu::TextureFormat::Rgba8Unorm);
941 }
942 if bgra_features.allowed_usages.contains(required_usages) {
943 return Ok(wgpu::TextureFormat::Bgra8Unorm);
944 }
945 if rgba_features.allowed_usages.contains(required_usages) {
946 let info = adapter.get_info();
947 log::warn!(
948 "Adapter {} ({:?}) does not support Bgra8Unorm atlas textures with usages {:?}; \
949 falling back to Rgba8Unorm atlas textures.",
950 info.name,
951 info.backend,
952 required_usages,
953 );
954 return Ok(wgpu::TextureFormat::Rgba8Unorm);
955 }
956
957 let info = adapter.get_info();
958 Err(anyhow::anyhow!(
959 "Adapter {} ({:?}, device={:#06x}) does not support a usable color atlas texture \
960 format with usages {:?}. Bgra8Unorm allowed usages: {:?}; \
961 Rgba8Unorm allowed usages: {:?}.",
962 info.name,
963 info.backend,
964 info.device,
965 required_usages,
966 bgra_features.allowed_usages,
967 rgba_features.allowed_usages,
968 ))
969 }
970 pub fn backend(&self) -> WgpuBackend {
971 self.backend
972 }
973
974 pub fn uses_webgl_instance_data(&self) -> bool {
975 matches!(self.backend, WgpuBackend::Gl) && cfg!(target_family = "wasm")
976 }
977
978 pub fn supports_dual_source_blending(&self) -> bool {
979 self.dual_source_blending
980 }
981
982 pub fn color_texture_format(&self) -> wgpu::TextureFormat {
983 self.color_texture_format
984 }
985
986 pub fn device_lost(&self) -> bool {
989 self.errors.device_lost()
990 }
991
992 pub fn errors(&self) -> &Arc<DeviceErrorState> {
994 &self.errors
995 }
996}
997
998#[cfg(test)]
999mod tests {
1000 use super::DeviceErrorState;
1001
1002 #[cfg(target_os = "linux")]
1005 #[test]
1006 fn the_device_enables_the_drm_format_modifier_extension() {
1007 use wgpu::hal::vulkan::Api;
1008
1009 let instance = super::WgpuContext::instance(None);
1010 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
1011 power_preference: wgpu::PowerPreference::LowPower,
1012 compatible_surface: None,
1013 force_fallback_adapter: false,
1014 }));
1015 let Ok(adapter) = adapter else {
1016 eprintln!("skipping: no adapter");
1017 return;
1018 };
1019 let supported = unsafe {
1020 adapter.as_hal::<Api>().is_some_and(|hal| {
1021 hal.physical_device_capabilities()
1022 .supports_extension(ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
1023 })
1024 };
1025 let semaphore_supported = unsafe {
1026 adapter.as_hal::<Api>().is_some_and(|hal| {
1027 hal.physical_device_capabilities()
1028 .supports_extension(ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME)
1029 })
1030 };
1031 if !supported {
1032 eprintln!(
1033 "skipping: {:?} does not support the extension",
1034 adapter.get_info().name
1035 );
1036 return;
1037 }
1038
1039 let descriptor = wgpu::DeviceDescriptor {
1040 label: Some("drm modifier extension test"),
1041 required_features: wgpu::Features::empty(),
1042 required_limits: wgpu::Limits::downlevel_defaults()
1043 .using_resolution(adapter.limits())
1044 .using_alignment(adapter.limits()),
1045 memory_hints: wgpu::MemoryHints::MemoryUsage,
1046 trace: wgpu::Trace::Off,
1047 experimental_features: wgpu::ExperimentalFeatures::disabled(),
1048 };
1049 let (device, _queue) =
1050 super::WgpuContext::device_with_surface_import(&adapter, &descriptor)
1051 .expect("the escape hatch creates the device");
1052 let enabled = unsafe { device.as_hal::<Api>() }.is_some_and(|device| {
1053 let extensions = device.enabled_device_extensions();
1054 extensions.contains(&ash::vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME)
1055 && (!semaphore_supported
1056 || extensions.contains(&ash::vk::KHR_EXTERNAL_SEMAPHORE_FD_NAME))
1057 });
1058 assert!(
1059 enabled,
1060 "the created device must have the surface extensions enabled"
1061 );
1062 }
1063
1064 #[test]
1065 fn device_errors_are_observed_independently() {
1066 let errors = DeviceErrorState::default();
1067 let mut first = 0;
1068 let mut second = 0;
1069 assert_eq!(errors.observe_error(&mut first), None);
1070 errors.record("first".into());
1071 assert_eq!(errors.observe_error(&mut first).as_deref(), Some("first"));
1072 assert_eq!(errors.observe_error(&mut first), None);
1073 assert_eq!(errors.observe_error(&mut second).as_deref(), Some("first"));
1074 assert_eq!(errors.observe_error(&mut second), None);
1075
1076 errors.record("second".into());
1077 assert_eq!(errors.observe_error(&mut second).as_deref(), Some("second"));
1078 assert_eq!(errors.observe_error(&mut first).as_deref(), Some("second"));
1079 assert_eq!(errors.observe_error(&mut first), None);
1080 assert_eq!(errors.observe_error(&mut second), None);
1081 }
1082
1083 #[test]
1084 fn device_errors_coalesce_and_new_observers_see_latest() {
1085 let errors = DeviceErrorState::default();
1086 errors.record("older".into());
1087 errors.record("latest".into());
1088 let mut generation = 0;
1089 assert_eq!(
1090 errors.observe_error(&mut generation).as_deref(),
1091 Some("latest")
1092 );
1093 assert_eq!(errors.observe_error(&mut generation), None);
1094
1095 let replacement = DeviceErrorState::default();
1096 replacement.record("replacement".into());
1097 generation = 0;
1098 assert_eq!(
1099 replacement.observe_error(&mut generation).as_deref(),
1100 Some("replacement")
1101 );
1102 assert_eq!(replacement.observe_error(&mut generation), None);
1103 }
1104}