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) = Self::create_device(&adapter)?;
56
57 Ok(Self {
58 instance,
59 adapter,
60 device: Arc::new(device),
61 queue: Arc::new(queue),
62 dual_source_blending,
63 })
64 }
65
66 #[cfg(target_family = "wasm")]
67 pub async fn new_web() -> anyhow::Result<Self> {
68 let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor {
69 backends: wgpu::Backends::BROWSER_WEBGPU | wgpu::Backends::GL,
70 flags: wgpu::InstanceFlags::default(),
71 backend_options: wgpu::BackendOptions::default(),
72 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
73 });
74
75 let adapter = instance
76 .request_adapter(&wgpu::RequestAdapterOptions {
77 power_preference: wgpu::PowerPreference::None,
78 compatible_surface: None,
79 force_fallback_adapter: false,
80 })
81 .await
82 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))?;
83 Self::create_context(instance, adapter).await
84 }
85
86 #[cfg(target_family = "wasm")]
87 async fn create_context(
88 instance: wgpu::Instance,
89 adapter: wgpu::Adapter,
90 ) -> anyhow::Result<Self> {
91 log::info!(
92 "Selected GPU adapter: {:?} ({:?})",
93 adapter.get_info().name,
94 adapter.get_info().backend
95 );
96
97 let dual_source_blending_available = adapter
98 .features()
99 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
100
101 let mut required_features = wgpu::Features::empty();
102 if dual_source_blending_available {
103 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
104 } else {
105 log::info!(
106 "Dual-source blending not available on this GPU. \
107 Subpixel text antialiasing will be disabled."
108 );
109 }
110
111 let (device, queue) = adapter
112 .request_device(&wgpu::DeviceDescriptor {
113 label: Some("gpui_device"),
114 required_features,
115 required_limits: wgpu::Limits::default(),
116 memory_hints: wgpu::MemoryHints::MemoryUsage,
117 trace: wgpu::Trace::Off,
118 experimental_features: wgpu::ExperimentalFeatures::disabled(),
119 })
120 .await
121 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
122
123 Ok(Self {
124 instance,
125 adapter,
126 device: Arc::new(device),
127 queue: Arc::new(queue),
128 dual_source_blending: dual_source_blending_available,
129 })
130 }
131
132 #[cfg(not(target_family = "wasm"))]
133 pub fn instance() -> wgpu::Instance {
134 wgpu::Instance::new(&wgpu::InstanceDescriptor {
135 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
136 flags: wgpu::InstanceFlags::default(),
137 backend_options: wgpu::BackendOptions::default(),
138 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
139 })
140 }
141
142 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
143 let caps = surface.get_capabilities(&self.adapter);
144 if caps.formats.is_empty() {
145 let info = self.adapter.get_info();
146 anyhow::bail!(
147 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
148 display surface for this window.",
149 info.name,
150 info.backend,
151 info.device,
152 );
153 }
154 Ok(())
155 }
156
157 #[cfg(not(target_family = "wasm"))]
158 fn create_device(adapter: &wgpu::Adapter) -> anyhow::Result<(wgpu::Device, wgpu::Queue, bool)> {
159 let dual_source_blending_available = adapter
160 .features()
161 .contains(wgpu::Features::DUAL_SOURCE_BLENDING);
162
163 let mut required_features = wgpu::Features::empty();
164 if dual_source_blending_available {
165 required_features |= wgpu::Features::DUAL_SOURCE_BLENDING;
166 } else {
167 log::warn!(
168 "Dual-source blending not available on this GPU. \
169 Subpixel text antialiasing will be disabled."
170 );
171 }
172
173 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
174 label: Some("gpui_device"),
175 required_features,
176 required_limits: wgpu::Limits::default(),
177 memory_hints: wgpu::MemoryHints::MemoryUsage,
178 trace: wgpu::Trace::Off,
179 experimental_features: wgpu::ExperimentalFeatures::disabled(),
180 }))
181 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
182
183 Ok((device, queue, dual_source_blending_available))
184 }
185
186 #[cfg(not(target_family = "wasm"))]
187 async fn select_adapter(
188 instance: &wgpu::Instance,
189 device_id_filter: Option<u32>,
190 compatible_surface: Option<&wgpu::Surface<'_>>,
191 ) -> anyhow::Result<wgpu::Adapter> {
192 if let Some(device_id) = device_id_filter {
193 let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
194
195 if adapters.is_empty() {
196 anyhow::bail!("No GPU adapters found");
197 }
198
199 let mut non_matching_adapter_infos: Vec<wgpu::AdapterInfo> = Vec::new();
200
201 for adapter in adapters.into_iter() {
202 let info = adapter.get_info();
203 if info.device == device_id {
204 if let Some(surface) = compatible_surface {
205 let caps = surface.get_capabilities(&adapter);
206 if caps.formats.is_empty() {
207 log::warn!(
208 "GPU matching ZED_DEVICE_ID={:#06x} ({}) is not compatible \
209 with the display surface. Falling back to auto-selection.",
210 device_id,
211 info.name,
212 );
213 break;
214 }
215 }
216 log::info!(
217 "Found GPU matching ZED_DEVICE_ID={:#06x}: {}",
218 device_id,
219 info.name
220 );
221 return Ok(adapter);
222 } else {
223 non_matching_adapter_infos.push(info);
224 }
225 }
226
227 log::warn!(
228 "No compatible GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
229 device_id
230 );
231
232 for info in &non_matching_adapter_infos {
233 log::warn!(
234 " - {} (device_id={:#06x}, backend={})",
235 info.name,
236 info.device,
237 info.backend
238 );
239 }
240 }
241
242 instance
243 .request_adapter(&wgpu::RequestAdapterOptions {
244 power_preference: wgpu::PowerPreference::None,
245 compatible_surface,
246 force_fallback_adapter: false,
247 })
248 .await
249 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))
250 }
251
252 pub fn supports_dual_source_blending(&self) -> bool {
253 self.dual_source_blending
254 }
255}
256
257#[cfg(not(target_family = "wasm"))]
258fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
259 let mut id = id.trim();
260
261 if id.starts_with("0x") || id.starts_with("0X") {
262 id = &id[2..];
263 }
264 let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
265 let is_4_chars = id.len() == 4;
266 anyhow::ensure!(
267 is_4_chars && is_hex_string,
268 "Expected a 4 digit PCI ID in hexadecimal format"
269 );
270
271 u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
272}
273
274#[cfg(test)]
275mod tests {
276 use super::parse_pci_id;
277
278 #[test]
279 fn test_parse_device_id() {
280 assert!(parse_pci_id("0xABCD").is_ok());
281 assert!(parse_pci_id("ABCD").is_ok());
282 assert!(parse_pci_id("abcd").is_ok());
283 assert!(parse_pci_id("1234").is_ok());
284 assert!(parse_pci_id("123").is_err());
285 assert_eq!(
286 parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
287 parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
288 );
289
290 assert_eq!(
291 parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
292 parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
293 );
294 }
295}