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 let device = Arc::new(device);
93 let queue = Arc::new(queue);
94
95 crate::shared_context::register(instance.clone(), device.clone(), queue.clone());
97
98 Ok(Self {
99 instance,
100 adapter,
101 device,
102 queue,
103 dual_source_blending,
104 color_texture_format,
105 device_lost,
106 })
107 }
108
109 #[cfg(target_family = "wasm")]
110 pub async fn new_web() -> anyhow::Result<Self> {
112 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
113 backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
114 flags: wgpu::InstanceFlags::default(),
115 backend_options: wgpu::BackendOptions::default(),
116 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
117 display: None,
118 });
119
120 let adapter = instance
121 .request_adapter(&wgpu::RequestAdapterOptions {
122 power_preference: wgpu::PowerPreference::HighPerformance,
123 compatible_surface: None,
124 force_fallback_adapter: false,
125 })
126 .await
127 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))?;
128
129 log::info!(
130 "Selected GPU adapter: {:?} ({:?})",
131 adapter.get_info().name,
132 adapter.get_info().backend
133 );
134
135 let device_lost = Arc::new(AtomicBool::new(false));
136 let (device, queue, dual_source_blending, color_texture_format) =
137 Self::create_device(&adapter).await?;
138
139 let device = Arc::new(device);
140 let queue = Arc::new(queue);
141
142 crate::shared_context::register(instance.clone(), device.clone(), queue.clone());
144
145 Ok(Self {
146 instance,
147 adapter,
148 device,
149 queue,
150 dual_source_blending,
151 color_texture_format,
152 device_lost,
153 })
154 }
155
156 async fn create_device(
158 adapter: &wgpu::Adapter,
159 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
160 let dual_source_blending = adapter
161 .features()
162 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
163
164 let mut required_features = wgpu::Features::empty();
165 if dual_source_blending {
166 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
167 } else {
168 log::warn!(
169 "Dual-source blending not available on this GPU. \
170 Subpixel text antialiasing will be disabled."
171 );
172 }
173
174 let color_atlas_texture_format = Self::select_color_texture_format(adapter)?;
175
176 let (device, queue) = adapter
177 .request_device(&wgpu::DeviceDescriptor {
178 label: Some("gpui_device"),
179 required_features,
180 required_limits: wgpu::Limits::downlevel_defaults()
181 .using_resolution(adapter.limits())
182 .using_alignment(adapter.limits()),
183 memory_hints: wgpu::MemoryHints::MemoryUsage,
184 trace: wgpu::Trace::Off,
185 experimental_features: wgpu::ExperimentalFeatures::disabled(),
186 })
187 .await
188 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
189
190 Ok((
191 device,
192 queue,
193 dual_source_blending,
194 color_atlas_texture_format,
195 ))
196 }
197
198 #[cfg(not(target_family = "wasm"))]
199 pub fn instance(display: Box<dyn wgpu::wgt::WgpuHasDisplayHandle>) -> wgpu::Instance {
200 wgpu::Instance::new(wgpu::InstanceDescriptor {
201 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
202 flags: wgpu::InstanceFlags::default(),
203 backend_options: wgpu::BackendOptions::default(),
204 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
205 display: Some(display),
206 })
207 }
208
209 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
211 let caps = surface.get_capabilities(&self.adapter);
212 if caps.formats.is_empty() {
213 let info = self.adapter.get_info();
214 anyhow::bail!(
215 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
216 display surface for this window.",
217 info.name,
218 info.backend,
219 info.device,
220 );
221 }
222 Ok(())
223 }
224
225 #[cfg(not(target_family = "wasm"))]
231 async fn select_adapter_and_device(
232 instance: &wgpu::Instance,
233 device_id_filter: Option<u32>,
234 surface: &wgpu::Surface<'_>,
235 compositor_gpu: Option<&CompositorGpuHint>,
236 reject_software: bool,
237 ) -> anyhow::Result<(
238 wgpu::Adapter,
239 wgpu::Device,
240 wgpu::Queue,
241 bool,
242 TextureFormat,
243 )> {
244 let mut adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
245
246 if adapters.is_empty() {
247 anyhow::bail!("No GPU adapters found");
248 }
249
250 if let Some(device_id) = device_id_filter {
251 log::info!("ZED_DEVICE_ID filter: {:#06x}", device_id);
252 }
253
254 adapters.sort_by_key(|adapter| {
262 let info = adapter.get_info();
263
264 let device_known = info.device != 0;
267
268 let user_override: u8 = match device_id_filter {
269 Some(id) if device_known && info.device == id => 0,
270 _ => 1,
271 };
272
273 let compositor_match: u8 = match compositor_gpu {
274 Some(hint)
275 if device_known
276 && info.vendor == hint.vendor_id
277 && info.device == hint.device_id =>
278 {
279 0
280 }
281 _ => 1,
282 };
283
284 let type_priority: u8 = if info.device_type == wgpu::DeviceType::Cpu {
285 4
286 } else {
287 match info.device_type {
288 wgpu::DeviceType::DiscreteGpu => 0,
289 wgpu::DeviceType::IntegratedGpu => 1,
290 wgpu::DeviceType::Other => 2,
291 wgpu::DeviceType::VirtualGpu => 3,
292 wgpu::DeviceType::Cpu => 4,
293 }
294 };
295
296 let backend_priority: u8 = match info.backend {
297 wgpu::Backend::Vulkan | wgpu::Backend::Metal | wgpu::Backend::Dx12 => 0,
298 _ => 1,
299 };
300
301 (
302 user_override,
303 compositor_match,
304 type_priority,
305 backend_priority,
306 )
307 });
308
309 log::info!("Found {} GPU adapter(s):", adapters.len());
311 for adapter in &adapters {
312 let info = adapter.get_info();
313 log::info!(
314 " - {} (vendor={:#06x}, device={:#06x}, backend={:?}, type={:?})",
315 info.name,
316 info.vendor,
317 info.device,
318 info.backend,
319 info.device_type,
320 );
321 }
322
323 for adapter in adapters {
325 let info = adapter.get_info();
326
327 if reject_software && info.device_type == wgpu::DeviceType::Cpu {
328 log::info!(
329 "Skipping software renderer: {} ({:?})",
330 info.name,
331 info.backend
332 );
333 continue;
334 }
335
336 log::info!("Testing adapter: {} ({:?})...", info.name, info.backend);
337
338 match Self::try_adapter_with_surface(&adapter, surface).await {
339 Ok((device, queue, dual_source_blending, color_atlas_texture_format)) => {
340 log::info!(
341 "Selected GPU (passed configuration test): {} ({:?})",
342 info.name,
343 info.backend
344 );
345 return Ok((
346 adapter,
347 device,
348 queue,
349 dual_source_blending,
350 color_atlas_texture_format,
351 ));
352 }
353 Err(e) => {
354 log::info!(
355 " Adapter {} ({:?}) failed: {}, trying next...",
356 info.name,
357 info.backend,
358 e
359 );
360 }
361 }
362 }
363
364 anyhow::bail!("No GPU adapter found that can configure the display surface")
365 }
366
367 #[cfg(not(target_family = "wasm"))]
370 async fn try_adapter_with_surface(
371 adapter: &wgpu::Adapter,
372 surface: &wgpu::Surface<'_>,
373 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool, TextureFormat)> {
374 let caps = surface.get_capabilities(adapter);
375 if caps.formats.is_empty() {
376 anyhow::bail!("no compatible surface formats");
377 }
378 if caps.alpha_modes.is_empty() {
379 anyhow::bail!("no compatible alpha modes");
380 }
381
382 let (device, queue, dual_source_blending, color_atlas_texture_format) =
383 Self::create_device(adapter).await?;
384 let error_scope = device.push_error_scope(wgpu::ErrorFilter::Validation);
385
386 let test_config = wgpu::SurfaceConfiguration {
387 usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
388 format: caps.formats[0],
389 width: 64,
390 height: 64,
391 present_mode: wgpu::PresentMode::Fifo,
392 desired_maximum_frame_latency: 2,
393 alpha_mode: caps.alpha_modes[0],
394 view_formats: vec![],
395 };
396
397 surface.configure(&device, &test_config);
398
399 let error = error_scope.pop().await;
400 if let Some(e) = error {
401 anyhow::bail!("surface configuration failed: {e}");
402 }
403
404 Ok((
405 device,
406 queue,
407 dual_source_blending,
408 color_atlas_texture_format,
409 ))
410 }
411
412 fn select_color_texture_format(adapter: &wgpu::Adapter) -> anyhow::Result<wgpu::TextureFormat> {
414 let required_usages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
415 let bgra_features = adapter.get_texture_format_features(wgpu::TextureFormat::Bgra8Unorm);
416 if bgra_features.allowed_usages.contains(required_usages) {
417 return Ok(wgpu::TextureFormat::Bgra8Unorm);
418 }
419
420 let rgba_features = adapter.get_texture_format_features(wgpu::TextureFormat::Rgba8Unorm);
421 if rgba_features.allowed_usages.contains(required_usages) {
422 let info = adapter.get_info();
423 log::warn!(
424 "Adapter {} ({:?}) does not support Bgra8Unorm atlas textures with usages {:?}; \
425 falling back to Rgba8Unorm atlas textures.",
426 info.name,
427 info.backend,
428 required_usages,
429 );
430 return Ok(wgpu::TextureFormat::Rgba8Unorm);
431 }
432
433 let info = adapter.get_info();
434 Err(anyhow::anyhow!(
435 "Adapter {} ({:?}, device={:#06x}) does not support a usable color atlas texture \
436 format with usages {:?}. Bgra8Unorm allowed usages: {:?}; \
437 Rgba8Unorm allowed usages: {:?}.",
438 info.name,
439 info.backend,
440 info.device,
441 required_usages,
442 bgra_features.allowed_usages,
443 rgba_features.allowed_usages,
444 ))
445 }
446 pub fn supports_dual_source_blending(&self) -> bool {
448 self.dual_source_blending
449 }
450
451 pub fn color_texture_format(&self) -> wgpu::TextureFormat {
453 self.color_texture_format
454 }
455
456 pub fn device_lost(&self) -> bool {
459 self.device_lost.load(Ordering::Relaxed)
460 }
461
462 pub(crate) fn device_lost_flag(&self) -> Arc<AtomicBool> {
464 Arc::clone(&self.device_lost)
465 }
466}
467
468#[cfg(not(target_family = "wasm"))]
469fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
471 let mut id = id.trim();
472
473 if id.starts_with("0x") || id.starts_with("0X") {
474 id = &id[2..];
475 }
476 let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
477 let is_4_chars = id.len() == 4;
478 anyhow::ensure!(
479 is_4_chars && is_hex_string,
480 "Expected a 4 digit PCI ID in hexadecimal format"
481 );
482
483 u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
484}
485
486#[cfg(test)]
487mod tests {
488 use super::parse_pci_id;
489
490 #[test]
491 fn test_parse_device_id() {
492 assert!(parse_pci_id("0xABCD").is_ok());
493 assert!(parse_pci_id("ABCD").is_ok());
494 assert!(parse_pci_id("abcd").is_ok());
495 assert!(parse_pci_id("1234").is_ok());
496 assert!(parse_pci_id("123").is_err());
497 assert_eq!(
498 parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
499 parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
500 );
501
502 assert_eq!(
503 parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
504 parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
505 );
506 }
507}