1#[cfg(not(target_family = "wasm"))]
2use anyhow::Context as _;
3#[cfg(not(target_family = "wasm"))]
4use rgpui::ResultExt;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use wgpu::TextureFormat;
8
9pub struct WgpuContext {
11 pub instance: wgpu::Instance,
12 pub adapter: wgpu::Adapter,
13 pub device: Arc<wgpu::Device>,
14 pub queue: Arc<wgpu::Queue>,
15 dual_source_blending: bool,
16 color_texture_format: wgpu::TextureFormat,
17 device_lost: Arc<AtomicBool>,
18}
19
20#[derive(Clone, Copy)]
22pub struct CompositorGpuHint {
23 pub vendor_id: u32,
24 pub device_id: u32,
25}
26
27impl WgpuContext {
28 #[cfg(not(target_family = "wasm"))]
29 pub fn new(
30 instance: wgpu::Instance,
31 surface: &wgpu::Surface<'_>,
32 compositor_gpu: Option<CompositorGpuHint>,
33 ) -> anyhow::Result<Self> {
34 Self::new_with_options(instance, surface, compositor_gpu, false)
35 }
36
37 #[cfg(not(target_family = "wasm"))]
38 pub fn new_rejecting_software(
39 instance: wgpu::Instance,
40 surface: &wgpu::Surface<'_>,
41 compositor_gpu: Option<CompositorGpuHint>,
42 ) -> anyhow::Result<Self> {
43 Self::new_with_options(instance, surface, compositor_gpu, true)
44 }
45
46 #[cfg(not(target_family = "wasm"))]
47 fn new_with_options(
48 instance: wgpu::Instance,
49 surface: &wgpu::Surface<'_>,
50 compositor_gpu: Option<CompositorGpuHint>,
51 reject_software: bool,
52 ) -> anyhow::Result<Self> {
53 let device_id_filter = match std::env::var("ZED_DEVICE_ID") {
54 Ok(val) => parse_pci_id(&val)
55 .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable")
56 .log_err(),
57 Err(std::env::VarError::NotPresent) => None,
58 err => {
59 err.context("读取 `ZED_DEVICE_ID` 环境变量失败").log_err();
60 None
61 }
62 };
63
64 let (adapter, device, queue, dual_source_blending, color_texture_format) =
67 rgpui::block_on(Self::select_adapter_and_device(
68 &instance,
69 device_id_filter,
70 surface,
71 compositor_gpu.as_ref(),
72 reject_software,
73 ))?;
74
75 let device_lost = Arc::new(AtomicBool::new(false));
76 device.set_device_lost_callback({
77 let device_lost = Arc::clone(&device_lost);
78 move |reason, message| {
79 log::error!("wgpu device lost: reason={reason:?}, message={message}");
80 if reason != wgpu::DeviceLostReason::Destroyed {
81 device_lost.store(true, Ordering::Relaxed);
82 }
83 }
84 });
85
86 log::info!(
87 "Selected GPU adapter: {:?} ({:?})",
88 adapter.get_info().name,
89 adapter.get_info().backend
90 );
91
92 Ok(Self {
93 instance,
94 adapter,
95 device: Arc::new(device),
96 queue: Arc::new(queue),
97 dual_source_blending,
98 color_texture_format,
99 device_lost,
100 })
101 }
102
103 #[cfg(target_family = "wasm")]
104 pub async fn new_web() -> anyhow::Result<Self> {
106 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
107 backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
108 flags: wgpu::InstanceFlags::default(),
109 backend_options: wgpu::BackendOptions::default(),
110 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
111 display: None,
112 });
113
114 let adapter = instance
115 .request_adapter(&wgpu::RequestAdapterOptions {
116 power_preference: wgpu::PowerPreference::HighPerformance,
117 compatible_surface: None,
118 force_fallback_adapter: false,
119 })
120 .await
121 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))?;
122
123 log::info!(
124 "Selected GPU adapter: {:?} ({:?})",
125 adapter.get_info().name,
126 adapter.get_info().backend
127 );
128
129 let device_lost = Arc::new(AtomicBool::new(false));
130 let (device, queue, dual_source_blending, color_texture_format) =
131 Self::create_device(&adapter).await?;
132
133 Ok(Self {
134 instance,
135 adapter,
136 device: Arc::new(device),
137 queue: Arc::new(queue),
138 dual_source_blending,
139 color_texture_format,
140 device_lost,
141 })
142 }
143
144 async fn create_device(
146 adapter: &wgpu::Adapter,
147 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
148 let dual_source_blending = adapter
149 .features()
150 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
151
152 let mut required_features = wgpu::Features::empty();
153 if dual_source_blending {
154 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
155 } else {
156 log::warn!(
157 "Dual-source blending not available on this GPU. \
158 Subpixel text antialiasing will be disabled."
159 );
160 }
161
162 let color_atlas_texture_format = Self::select_color_texture_format(adapter)?;
163
164 let (device, queue) = adapter
165 .request_device(&wgpu::DeviceDescriptor {
166 label: Some("gpui_device"),
167 required_features,
168 required_limits: wgpu::Limits::downlevel_defaults()
169 .using_resolution(adapter.limits())
170 .using_alignment(adapter.limits()),
171 memory_hints: wgpu::MemoryHints::MemoryUsage,
172 trace: wgpu::Trace::Off,
173 experimental_features: wgpu::ExperimentalFeatures::disabled(),
174 })
175 .await
176 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
177
178 Ok((
179 device,
180 queue,
181 dual_source_blending,
182 color_atlas_texture_format,
183 ))
184 }
185
186 #[cfg(not(target_family = "wasm"))]
187 pub fn instance(display: Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) -> wgpu::Instance {
188 wgpu::Instance::new(wgpu::InstanceDescriptor {
189 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
190 flags: wgpu::InstanceFlags::default(),
191 backend_options: wgpu::BackendOptions::default(),
192 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
193 display: Some(display),
194 })
195 }
196
197 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
199 let caps = surface.get_capabilities(&self.adapter);
200 if caps.formats.is_empty() {
201 let info = self.adapter.get_info();
202 anyhow::bail!(
203 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
204 display surface for this window.",
205 info.name,
206 info.backend,
207 info.device,
208 );
209 }
210 Ok(())
211 }
212
213 #[cfg(not(target_family = "wasm"))]
219 async fn select_adapter_and_device(
220 instance: &wgpu::Instance,
221 device_id_filter: Option<u32>,
222 surface: &wgpu::Surface<'_>,
223 compositor_gpu: Option<&CompositorGpuHint>,
224 reject_software: bool,
225 ) -> anyhow::Result<(
226 wgpu::Adapter,
227 wgpu::Device,
228 wgpu::Queue,
229 bool,
230 TextureFormat,
231 )> {
232 let mut adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
233
234 if adapters.is_empty() {
235 anyhow::bail!("No GPU adapters found");
236 }
237
238 if let Some(device_id) = device_id_filter {
239 log::info!("ZED_DEVICE_ID filter: {:#06x}", device_id);
240 }
241
242 adapters.sort_by_key(|adapter| {
250 let info = adapter.get_info();
251
252 let device_known = info.device != 0;
255
256 let user_override: u8 = match device_id_filter {
257 Some(id) if device_known && info.device == id => 0,
258 _ => 1,
259 };
260
261 let compositor_match: u8 = match compositor_gpu {
262 Some(hint)
263 if device_known
264 && info.vendor == hint.vendor_id
265 && info.device == hint.device_id =>
266 {
267 0
268 }
269 _ => 1,
270 };
271
272 let type_priority: u8 = if info.device_type == wgpu::DeviceType::Cpu {
273 4
274 } else {
275 match info.device_type {
276 wgpu::DeviceType::DiscreteGpu => 0,
277 wgpu::DeviceType::IntegratedGpu => 1,
278 wgpu::DeviceType::Other => 2,
279 wgpu::DeviceType::VirtualGpu => 3,
280 wgpu::DeviceType::Cpu => 4,
281 }
282 };
283
284 let backend_priority: u8 = match info.backend {
285 wgpu::Backend::Vulkan | wgpu::Backend::Metal | wgpu::Backend::Dx12 => 0,
286 _ => 1,
287 };
288
289 (
290 user_override,
291 compositor_match,
292 type_priority,
293 backend_priority,
294 )
295 });
296
297 log::info!("Found {} GPU adapter(s):", adapters.len());
299 for adapter in &adapters {
300 let info = adapter.get_info();
301 log::info!(
302 " - {} (vendor={:#06x}, device={:#06x}, backend={:?}, type={:?})",
303 info.name,
304 info.vendor,
305 info.device,
306 info.backend,
307 info.device_type,
308 );
309 }
310
311 for adapter in adapters {
313 let info = adapter.get_info();
314
315 if reject_software && info.device_type == wgpu::DeviceType::Cpu {
316 log::info!(
317 "Skipping software renderer: {} ({:?})",
318 info.name,
319 info.backend
320 );
321 continue;
322 }
323
324 log::info!("Testing adapter: {} ({:?})...", info.name, info.backend);
325
326 match Self::try_adapter_with_surface(&adapter, surface).await {
327 Ok((device, queue, dual_source_blending, color_atlas_texture_format)) => {
328 log::info!(
329 "Selected GPU (passed configuration test): {} ({:?})",
330 info.name,
331 info.backend
332 );
333 return Ok((
334 adapter,
335 device,
336 queue,
337 dual_source_blending,
338 color_atlas_texture_format,
339 ));
340 }
341 Err(e) => {
342 log::info!(
343 " Adapter {} ({:?}) failed: {}, trying next...",
344 info.name,
345 info.backend,
346 e
347 );
348 }
349 }
350 }
351
352 anyhow::bail!("No GPU adapter found that can configure the display surface")
353 }
354
355 #[cfg(not(target_family = "wasm"))]
358 async fn try_adapter_with_surface(
359 adapter: &wgpu::Adapter,
360 surface: &wgpu::Surface<'_>,
361 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
362 let caps = surface.get_capabilities(adapter);
363 if caps.formats.is_empty() {
364 anyhow::bail!("no compatible surface formats");
365 }
366 if caps.alpha_modes.is_empty() {
367 anyhow::bail!("no compatible alpha modes");
368 }
369
370 let (device, queue, dual_source_blending, color_atlas_texture_format) =
371 Self::create_device(adapter).await?;
372 let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
373
374 let test_config = wgpu::SurfaceConfiguration {
375 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
376 format: caps.formats[0],
377 width: 64,
378 height: 64,
379 present_mode: wgpu::PresentMode::Fifo,
380 desired_maximum_frame_latency: 2,
381 alpha_mode: caps.alpha_modes[0],
382 view_formats: vec![],
383 };
384
385 surface.configure(&device, &test_config);
386
387 let error = error_scope.pop().await;
388 if let Some(e) = error {
389 anyhow::bail!("surface configuration failed: {e}");
390 }
391
392 Ok((
393 device,
394 queue,
395 dual_source_blending,
396 color_atlas_texture_format,
397 ))
398 }
399
400 fn select_color_texture_format(adapter: &wgpu::Adapter) -> anyhow::Result<wgpu::TextureFormat> {
402 let required_usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
403 let bgra_features = adapter.get_texture_format_features(wgpu::TextureFormat::Bgra8Unorm);
404 if bgra_features.allowed_usages.contains(required_usages) {
405 return Ok(wgpu::TextureFormat::Bgra8Unorm);
406 }
407
408 let rgba_features = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba8Unorm);
409 if rgba_features.allowed_usages.contains(required_usages) {
410 let info = adapter.get_info();
411 log::warn!(
412 "Adapter {} ({:?}) does not support Bgra8Unorm atlas textures with usages {:?}; \
413 falling back to Rgba8Unorm atlas textures.",
414 info.name,
415 info.backend,
416 required_usages,
417 );
418 return Ok(wgpu::TextureFormat::Rgba8Unorm);
419 }
420
421 let info = adapter.get_info();
422 Err(anyhow::anyhow!(
423 "Adapter {} ({:?}, device={:#06x}) does not support a usable color atlas texture \
424 format with usages {:?}. Bgra8Unorm allowed usages: {:?}; \
425 Rgba8Unorm allowed usages: {:?}.",
426 info.name,
427 info.backend,
428 info.device,
429 required_usages,
430 bgra_features.allowed_usages,
431 rgba_features.allowed_usages,
432 ))
433 }
434 pub fn supports_dual_source_blending(&self) -> bool {
436 self.dual_source_blending
437 }
438
439 pub fn color_texture_format(&self) -> wgpu::TextureFormat {
441 self.color_texture_format
442 }
443
444 pub fn device_lost(&self) -> bool {
447 self.device_lost.load(Ordering::Relaxed)
448 }
449
450 pub(crate) fn device_lost_flag(&self) -> Arc<AtomicBool> {
452 Arc::clone(&self.device_lost)
453 }
454}
455
456#[cfg(not(target_family = "wasm"))]
457fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
459 let mut id = id.trim();
460
461 if id.starts_with("0x") || id.starts_with("0X") {
462 id = &id[2..];
463 }
464 let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
465 let is_4_chars = id.len() == 4;
466 anyhow::ensure!(
467 is_4_chars && is_hex_string,
468 "Expected a 4 digit PCI ID in hexadecimal format"
469 );
470
471 u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
472}
473
474#[cfg(test)]
475mod tests {
476 use super::parse_pci_id;
477
478 #[test]
479 fn test_parse_device_id() {
480 assert!(parse_pci_id("0xABCD").is_ok());
481 assert!(parse_pci_id("ABCD").is_ok());
482 assert!(parse_pci_id("abcd").is_ok());
483 assert!(parse_pci_id("1234").is_ok());
484 assert!(parse_pci_id("123").is_err());
485 assert_eq!(
486 parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
487 parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
488 );
489
490 assert_eq!(
491 parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
492 parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
493 );
494 }
495}