wgpu_context.rs

  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}