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}