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