Skip to main content

oxicuda_webgpu/
device.rs

1//! WebGPU device wrapper — owns the wgpu instance, adapter, device, and queue.
2
3use wgpu;
4
5use crate::error::{WebGpuError, WebGpuResult};
6
7/// A fully initialised WebGPU device together with its submit queue.
8///
9/// Created via [`WebGpuDevice::new`] which blocks the calling thread using
10/// [`pollster`] until the async device request completes.
11pub struct WebGpuDevice {
12    /// The wgpu instance used to enumerate adapters.
13    /// Kept alive to ensure the adapter and device remain valid.
14    #[allow(dead_code)]
15    pub(crate) instance: wgpu::Instance,
16    /// The selected GPU adapter.
17    /// Kept alive to ensure the device remains valid.
18    #[allow(dead_code)]
19    pub(crate) adapter: wgpu::Adapter,
20    /// The logical device (command encoder, buffer allocator, …).
21    pub(crate) device: wgpu::Device,
22    /// The queue for submitting command buffers.
23    pub(crate) queue: wgpu::Queue,
24    /// Human-readable adapter name for diagnostics.
25    pub adapter_name: String,
26}
27
28impl WebGpuDevice {
29    /// Create a WebGPU device by selecting the highest-performance adapter.
30    ///
31    /// Blocks the calling thread until the device is ready.
32    pub fn new() -> WebGpuResult<Self> {
33        pollster::block_on(Self::new_async())
34    }
35
36    async fn new_async() -> WebGpuResult<Self> {
37        // wgpu 29: `InstanceDescriptor` does not impl `Default`; use the
38        // provided constructor instead.
39        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
40
41        let adapter = instance
42            .request_adapter(&wgpu::RequestAdapterOptions {
43                power_preference: wgpu::PowerPreference::HighPerformance,
44                compatible_surface: None,
45                force_fallback_adapter: false,
46            })
47            .await
48            .map_err(|e| WebGpuError::DeviceRequest(e.to_string()))?;
49
50        let adapter_name = adapter.get_info().name.clone();
51
52        // `DeviceDescriptor` does implement `Default` in wgpu-types 29 so we
53        // can use struct-update syntax.
54        let (device, queue) = adapter
55            .request_device(&wgpu::DeviceDescriptor {
56                label: Some("oxicuda-webgpu"),
57                required_features: wgpu::Features::empty(),
58                required_limits: wgpu::Limits::default(),
59                memory_hints: wgpu::MemoryHints::default(),
60                ..Default::default()
61            })
62            .await
63            .map_err(|e| WebGpuError::DeviceRequest(e.to_string()))?;
64
65        Ok(Self {
66            instance,
67            adapter,
68            device,
69            queue,
70            adapter_name,
71        })
72    }
73}
74
75impl std::fmt::Debug for WebGpuDevice {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        write!(f, "WebGpuDevice({})", self.adapter_name)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    /// Confirm that WebGpuDevice::new() does not panic — it may return Ok or Err
86    /// depending on whether a GPU is available in the test environment.
87    #[test]
88    fn webgpu_device_new_graceful() {
89        match WebGpuDevice::new() {
90            Ok(dev) => {
91                assert!(!dev.adapter_name.is_empty());
92                // Debug impl should not panic.
93                let _ = format!("{dev:?}");
94            }
95            Err(WebGpuError::NoAdapter) => {
96                // Expected on headless CI without a GPU.
97            }
98            Err(e) => {
99                // Any other error is also acceptable; we just must not panic.
100                let _ = format!("device init error (non-fatal): {e}");
101            }
102        }
103    }
104}