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