Skip to main content

roxlap_gpu/
headless.rs

1//! GPU.2 — headless device + queue for tests and offline tools.
2//!
3//! Stands up a wgpu device with no surface — the surface dependency
4//! is what couples `GpuRenderer` to winit. Tests that exercise the
5//! decompressed-chunk upload + read-back path want neither a window
6//! nor a swapchain; this module exposes that bare device.
7//!
8//! Same `GpuInitError` surface as `GpuRenderer::new` so callers can
9//! share the fall-back-to-CPU pattern.
10
11use crate::{GpuInitError, GpuRendererSettings, PowerPreference};
12
13/// Bare wgpu device + queue with no surface. Suitable for
14/// `wgpu::ComputePass` work that doesn't present.
15pub struct HeadlessGpu {
16    pub device: wgpu::Device,
17    pub queue: wgpu::Queue,
18    pub adapter_info: String,
19}
20
21impl HeadlessGpu {
22    /// # Errors
23    /// Returns [`GpuInitError::NoAdapter`] if no compatible adapter
24    /// is present (no Vulkan/Metal/DX12 driver), or
25    /// [`GpuInitError::RequestDevice`] if device creation fails.
26    pub async fn new(settings: GpuRendererSettings) -> Result<Self, GpuInitError> {
27        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::default());
28        let power_preference = match settings.power_preference {
29            PowerPreference::Low => wgpu::PowerPreference::LowPower,
30            PowerPreference::High => wgpu::PowerPreference::HighPerformance,
31        };
32        let adapter = instance
33            .request_adapter(&wgpu::RequestAdapterOptions {
34                power_preference,
35                compatible_surface: None,
36                force_fallback_adapter: false,
37            })
38            .await
39            .ok_or(GpuInitError::NoAdapter)?;
40        let info = adapter.get_info();
41        let adapter_info = format!(
42            "{name} ({backend:?}, {device_type:?})",
43            name = info.name,
44            backend = info.backend,
45            device_type = info.device_type,
46        );
47        let (device, queue) = adapter
48            .request_device(
49                &wgpu::DeviceDescriptor {
50                    label: Some("roxlap-gpu headless device"),
51                    required_features: wgpu::Features::empty(),
52                    required_limits: crate::pick_required_limits(&adapter.limits()),
53                    memory_hints: wgpu::MemoryHints::default(),
54                },
55                None,
56            )
57            .await?;
58        Ok(Self {
59            device,
60            queue,
61            adapter_info,
62        })
63    }
64
65    /// Synchronous wrapper for tests / hosts without an async runtime.
66    ///
67    /// # Errors
68    /// See [`Self::new`].
69    pub fn new_blocking(settings: GpuRendererSettings) -> Result<Self, GpuInitError> {
70        pollster::block_on(Self::new(settings))
71    }
72}