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 memory_hints: wgpu::MemoryHints::MemoryUsage,
125 trace: wgpu::Trace::Off,
126 experimental_features: wgpu::ExperimentalFeatures::disabled(),
127 })
128 .await
129 .map_err(|e| anyhow::anyhow!("Failed to create wgpu device: {e}"))?;
130
131 Ok((device, queue, dual_source_blending))
132 }
133
134 #[cfg(not(target_family = "wasm"))]
135 pub fn instance() -> wgpu::Instance {
136 wgpu::Instance::new(&wgpu::InstanceDescriptor {
137 backends: wgpu::Backends::VULKAN | wgpu::Backends::GL,
138 flags: wgpu::InstanceFlags::default(),
139 backend_options: wgpu::BackendOptions::default(),
140 memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
141 })
142 }
143
144 pub fn check_compatible_with_surface(&self, surface: &wgpu::Surface<'_>) -> anyhow::Result<()> {
145 let caps = surface.get_capabilities(&self.adapter);
146 if caps.formats.is_empty() {
147 let info = self.adapter.get_info();
148 anyhow::bail!(
149 "Adapter {:?} (backend={:?}, device={:#06x}) is not compatible with the \
150 display surface for this window.",
151 info.name,
152 info.backend,
153 info.device,
154 );
155 }
156 Ok(())
157 }
158
159 #[cfg(not(target_family = "wasm"))]
160 async fn select_adapter(
161 instance: &wgpu::Instance,
162 device_id_filter: Option<u32>,
163 compatible_surface: Option<&wgpu::Surface<'_>>,
164 ) -> anyhow::Result<wgpu::Adapter> {
165 if let Some(device_id) = device_id_filter {
166 let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
167
168 if adapters.is_empty() {
169 anyhow::bail!("No GPU adapters found");
170 }
171
172 let mut non_matching_adapter_infos: Vec<wgpu::AdapterInfo> = Vec::new();
173
174 for adapter in adapters.into_iter() {
175 let info = adapter.get_info();
176 if info.device == device_id {
177 if let Some(surface) = compatible_surface {
178 let caps = surface.get_capabilities(&adapter);
179 if caps.formats.is_empty() {
180 log::warn!(
181 "GPU matching ZED_DEVICE_ID={:#06x} ({}) is not compatible \
182 with the display surface. Falling back to auto-selection.",
183 device_id,
184 info.name,
185 );
186 break;
187 }
188 }
189 log::info!(
190 "Found GPU matching ZED_DEVICE_ID={:#06x}: {}",
191 device_id,
192 info.name
193 );
194 return Ok(adapter);
195 } else {
196 non_matching_adapter_infos.push(info);
197 }
198 }
199
200 log::warn!(
201 "No compatible GPU found matching ZED_DEVICE_ID={:#06x}. Available devices:",
202 device_id
203 );
204
205 for info in &non_matching_adapter_infos {
206 log::warn!(
207 " - {} (device_id={:#06x}, backend={})",
208 info.name,
209 info.device,
210 info.backend
211 );
212 }
213 }
214
215 instance
216 .request_adapter(&wgpu::RequestAdapterOptions {
217 power_preference: wgpu::PowerPreference::HighPerformance,
218 compatible_surface,
219 force_fallback_adapter: false,
220 })
221 .await
222 .map_err(|e| anyhow::anyhow!("Failed to request GPU adapter: {e}"))
223 }
224
225 pub fn supports_dual_source_blending(&self) -> bool {
226 self.dual_source_blending
227 }
228}
229
230#[cfg(not(target_family = "wasm"))]
231fn parse_pci_id(id: &str) -> anyhow::Result<u32> {
232 let mut id = id.trim();
233
234 if id.starts_with("0x") || id.starts_with("0X") {
235 id = &id[2..];
236 }
237 let is_hex_string = id.chars().all(|c| c.is_ascii_hexdigit());
238 let is_4_chars = id.len() == 4;
239 anyhow::ensure!(
240 is_4_chars && is_hex_string,
241 "Expected a 4 digit PCI ID in hexadecimal format"
242 );
243
244 u32::from_str_radix(id, 16).context("parsing PCI ID as hex")
245}
246
247#[cfg(test)]
248mod tests {
249 use super::parse_pci_id;
250
251 #[test]
252 fn test_parse_device_id() {
253 assert!(parse_pci_id("0xABCD").is_ok());
254 assert!(parse_pci_id("ABCD").is_ok());
255 assert!(parse_pci_id("abcd").is_ok());
256 assert!(parse_pci_id("1234").is_ok());
257 assert!(parse_pci_id("123").is_err());
258 assert_eq!(
259 parse_pci_id(&format!("{:x}", 0x1234)).unwrap(),
260 parse_pci_id(&format!("{:X}", 0x1234)).unwrap(),
261 );
262
263 assert_eq!(
264 parse_pci_id(&format!("{:#x}", 0x1234)).unwrap(),
265 parse_pci_id(&format!("{:#X}", 0x1234)).unwrap(),
266 );
267 }
268}