1#[cfg(not(target_family = "wasm"))]
2use anyhow::Context as _;
3#[cfg(not(target_family = "wasm"))]
4use gpui_util::ResultExt;
5use std::sync::Arc;
6
7pub struct WgpuContext {
8 pub instance: wgpu::Instance,
9 pub adapter: wgpu::Adapter,
10 pub device: Arc<wgpu::Device>,
11 pub queue: Arc<wgpu::Queue>,
12 dual_source_blending: bool,
13}
14
15impl WgpuContext {
16 #[cfg(not(target_family = "wasm"))]
17 pub fn new(instance: wgpu::Instance, surface: &wgpu::Surface<'_>) -> anyhow::Result<Self> {
18 let device_id_filter = match std::env::var("ZED_DEVICE_ID") {
19 Ok(val) => parse_pci_id(&val)
20 .context("Failed to parse device ID from `ZED_DEVICE_ID` environment variable")
21 .log_err(),
22 Err(std::env::VarError::NotPresent) => None,
23 err => {
24 err.context("Failed to read value of `ZED_DEVICE_ID` environment variable")
25 .log_err();
26 None
27 }
28 };
29
30 let adapter = pollster::block_on(Self::select_adapter(
31 &instance,
32 device_id_filter,
33 Some(surface),
34 ))?;
35
36 let caps = surface.get_capabilities(&adapter);
37 if caps.formats.is_empty() {
38 let info = adapter.get_info();
39 anyhow::bail!(
40 "No adapter compatible with the display surface could be found. \
41 Best candidate {:?} (backend={:?}, device={:#06x}) reports no \
42 supported surface formats.",
43 info.name,
44 info.backend,
45 info.device,
46 );
47 }
48
49 log::info!(
50 "Selected GPU adapter: {:?} ({:?})",
51 adapter.get_info().name,
52 adapter.get_info().backend
53 );
54
55 let (device, queue, dual_source_blending) =
56 pollster::block_on(Self::create_device(&adapter))?;
57
58 Ok(Self {
59 instance,
60 adapter,
61 device: Arc::new(device),
62 queue: Arc::new(queue),
63 dual_source_blending,
64 })
65 }
66
67 #[cfg(target_family = "wasm")]
68 pub async fn new_web() -> anyhow::Result<Self> {
69 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
70 backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
71 flags: wgpu::InstanceFlags::default(),
72 backend_options: wgpu::BackendOptions::default(),
73 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
74 });
75
76 let adapter = instance
77 .request_adapter(&wgpu::RequestAdapterOptions {
78 power_preference: wgpu::PowerPreference::HighPerformance,
79 compatible_surface: None,
80 force_fallback_adapter: false,
81 })
82 .await
83 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))?;
84
85 log::info!(
86 "Selected GPU adapter: {:?} ({:?})",
87 adapter.get_info().name,
88 adapter.get_info().backend
89 );
90
91 let (device, queue, dual_source_blending) = Self::create_device(&adapter).await?;
92
93 Ok(Self {
94 instance,
95 adapter,
96 device: Arc::new(device),
97 queue: Arc::new(queue),
98 dual_source_blending,
99 })
100 }
101
102 async fn create_device(
103 adapter: &wgpu::Adapter,
104 ) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool)> {
105 let dual_source_blending = adapter
106 .features()
107 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
108
109 let mut required_features = wgpu::Features::empty();
110 if dual_source_blending {
111 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
112 } else {
113 log::warn!(
114 "Dual-source blending not available on this GPU. \
115 Subpixel text antialiasing will be disabled."
116 );
117 }
118
119 let (device, queue) = adapter
120 .request_device(&wgpu::DeviceDescriptor {
121 label: Some("gpui_device"),
122 required_features,
123 required_limits: wgpu::Limits::downlevel_defaults()
124 .using_resolution(adapter.limits())
125 .using_alignment(adapter.limits()),
126 memory_hints: wgpu::MemoryHints::MemoryUsage,
127 trace: wgpu::Trace::Off,
128 experimental_features: wgpu::ExperimentalFeatures::disabled(),
129 })
130 .await
131 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
132
133 Ok((device, queue, dual_source_blending))
134 }
135
136 #[cfg(not(target_family = "wasm"))]
137 pub fn instance() -> wgpu::Instance {
138 wgpu::Instance::new(&wgpu::InstanceDescriptor {
139 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
140 flags: wgpu::InstanceFlags::default(),
141 backend_options: wgpu::BackendOptions::default(),
142 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
143 })
144 }
145
146 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
147 let caps = surface.get_capabilities(&self.adapter);
148 if caps.formats.is_empty() {
149 let info = self.adapter.get_info();
150 anyhow::bail!(
151 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
152 display surface for this window.",
153 info.name,
154 info.backend,
155 info.device,
156 );
157 }
158 Ok(())
159 }
160
161 #[cfg(not(target_family = "wasm"))]
162 async fn select_adapter(
163 instance: &wgpu::Instance,
164 device_id_filter: Option<u32>,
165 compatible_surface: Option<&wgpu::Surface<'_>>,
166 ) -> anyhow::Result<wgpu::Adapter> {
167 if let Some(device_id) = device_id_filter {
168 let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
169
170 if adapters.is_empty() {
171 anyhow::bail!("No GPU adapters found");
172 }
173
174 let mut non_matching_adapter_infos: Vec<wgpu::AdapterInfo> = Vec::new();
175
176 for adapter in adapters.into_iter() {
177 let info = adapter.get_info();
178 if info.device == device_id {
179 if let Some(surface) = compatible_surface {
180 let caps = surface.get_capabilities(&adapter);
181 if caps.formats.is_empty() {
182 log::warn!(
183 "GPU matching ZED_DEVICE_ID={:#06x} ({}) is not compatible \
184 with the display surface. Falling back to auto-selection.",
185 device_id,
186 info.name,
187 );
188 break;
189 }
190 }
191 log::info!(
192 "Found GPU matching ZED_DEVICE_ID={:#06x}: {}",
193 device_id,
194 info.name
195 );
196 return Ok(adapter);
197 } else {
198 non_matching_adapter_infos.push(info);
199 }
200 }
201
202 log::warn!(
203 "No compatible GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
204 device_id
205 );
206
207 for info in &non_matching_adapter_infos {
208 log::warn!(
209 " - {} (device_id={:#06x}, backend={})",
210 info.name,
211 info.device,
212 info.backend
213 );
214 }
215 }
216
217 instance
218 .request_adapter(&wgpu::RequestAdapterOptions {
219 power_preference: wgpu::PowerPreference::HighPerformance,
220 compatible_surface,
221 force_fallback_adapter: false,
222 })
223 .await
224 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))
225 }
226
227 pub fn supports_dual_source_blending(&self) -> bool {
228 self.dual_source_blending
229 }
230}
231
232#[cfg(not(target_family = "wasm"))]
233fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
234 let mut id = id.trim();
235
236 if id.starts_with("0x") || id.starts_with("0X") {
237 id = &id[2..];
238 }
239 let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
240 let is_4_chars = id.len() == 4;
241 anyhow::ensure!(
242 is_4_chars && is_hex_string,
243 "Expected a 4 digit PCI ID in hexadecimal format"
244 );
245
246 u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
247}
248
249#[cfg(test)]
250mod tests {
251 use super::parse_pci_id;
252
253 #[test]
254 fn test_parse_device_id() {
255 assert!(parse_pci_id("0xABCD").is_ok());
256 assert!(parse_pci_id("ABCD").is_ok());
257 assert!(parse_pci_id("abcd").is_ok());
258 assert!(parse_pci_id("1234").is_ok());
259 assert!(parse_pci_id("123").is_err());
260 assert_eq!(
261 parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
262 parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
263 );
264
265 assert_eq!(
266 parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
267 parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
268 );
269 }
270}