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 is the only
4//! part of `GpuRenderer` that needs a host window handle. Tests that
5//! exercise the
6//! decompressed-chunk upload + read-back path want neither a window
7//! nor a swapchain; this module exposes that bare device.
8//!
9//! Same `GpuInitError` surface as `GpuRenderer::new` so callers can
10//! share the fall-back-to-CPU pattern.
11
12use crate::{GpuInitError, GpuRendererSettings, PowerPreference};
13
14/// Bare wgpu device + queue with no surface. Suitable for
15/// `wgpu::ComputePass` work that doesn't present.
16pub struct HeadlessGpu {
17    pub device: wgpu::Device,
18    pub queue: wgpu::Queue,
19    pub adapter_info: String,
20}
21
22impl HeadlessGpu {
23    /// # Errors
24    /// Returns [`GpuInitError::NoAdapter`] if no compatible adapter
25    /// is present (no Vulkan/Metal/DX12 driver), or
26    /// [`GpuInitError::RequestDevice`] if device creation fails.
27    pub async fn new(settings: GpuRendererSettings) -> Result<Self, GpuInitError> {
28        let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
29        let power_preference = match settings.power_preference {
30            PowerPreference::Low => wgpu::PowerPreference::LowPower,
31            PowerPreference::High => wgpu::PowerPreference::HighPerformance,
32        };
33        let adapter = instance
34            .request_adapter(&wgpu::RequestAdapterOptions {
35                power_preference,
36                compatible_surface: None,
37                force_fallback_adapter: false,
38            })
39            .await
40            .map_err(|_| GpuInitError::NoAdapter)?;
41        let info = adapter.get_info();
42        let adapter_info = format!(
43            "{name} ({backend:?}, {device_type:?})",
44            name = info.name,
45            backend = info.backend,
46            device_type = info.device_type,
47        );
48        let (device, queue) = adapter
49            .request_device(&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                experimental_features: wgpu::ExperimentalFeatures::disabled(),
54                memory_hints: wgpu::MemoryHints::default(),
55                trace: wgpu::Trace::Off,
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}