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