1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
//! Adapter/device acquisition for `indicatrix`'s `gpu`-feature compute infrastructure.
use std::fmt;
/// Why [`GpuContext::acquire`] could not obtain a usable GPU.
///
/// Both variants carry the underlying `wgpu` error's `Debug` output as a `String`
/// (avoids exposing the raw error type in this crate's public API), so callers can
/// report a diagnostic and exit nonzero without panicking.
#[derive(Debug, Clone)]
pub enum GpuAcquireError {
/// No backend/driver on this system produced an adapter at all.
NoAdapter(String),
/// An adapter was found but creating a logical device from it failed.
RequestDevice(String),
}
impl fmt::Display for GpuAcquireError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoAdapter(msg) => write!(
f,
"no wgpu adapter is available on this system (no compatible GPU/driver \
found via any backend): {msg}"
),
Self::RequestDevice(msg) => write!(f, "failed to acquire a wgpu device: {msg}"),
}
}
}
impl std::error::Error for GpuAcquireError {}
/// A live `wgpu` adapter/device/queue, acquired once and reused by every self-test.
pub struct GpuContext {
pub instance: wgpu::Instance,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
}
impl GpuContext {
/// Synchronously acquires a `wgpu` adapter/device/queue via [`pollster::block_on`].
///
/// Prefers a high-performance adapter but accepts whatever the system offers (no
/// discrete-GPU requirement).
///
/// # Errors
///
/// [`GpuAcquireError::NoAdapter`] if no backend/driver can produce an adapter, or
/// [`GpuAcquireError::RequestDevice`] if device creation failed. Callers MUST treat
/// either as a clean, diagnosable condition to report and exit on -- never panic or
/// `unwrap`, since "no GPU here" is expected on some machines, not a bug.
pub fn acquire() -> Result<Self, GpuAcquireError> {
pollster::block_on(Self::acquire_async())
}
// `pub(crate)`: wasm32-only `GpuFrameRenderer::new_async` awaits this directly
// instead of `pollster::block_on` (which parks the OS thread, unavailable on wasm32).
//
// `clippy::future_not_send` fires only for wasm32: `wgpu`'s web backend future holds
// browser-side `Rc` handles, never `Send` -- and there's no second thread on
// wasm32-unknown-unknown to send it to anyway. `cfg_attr` scopes the allow to that
// target so a native regression would still be caught by clippy.
#[cfg_attr(
target_arch = "wasm32",
allow(
clippy::future_not_send,
reason = "wgpu's web backend futures are inherently !Send (browser-side Rc \
handles); wasm32-unknown-unknown has no second thread regardless"
)
)]
pub(crate) async fn acquire_async() -> Result<Self, GpuAcquireError> {
let instance =
wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
apply_limit_buckets: false,
})
.await
.map_err(|e| GpuAcquireError::NoAdapter(format!("{e:?}")))?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("indicatrix gpu-feature compute device"),
..Default::default()
})
.await
.map_err(|e| GpuAcquireError::RequestDevice(format!("{e:?}")))?;
// Logging-only: fires on a driver-owned thread whenever `wgpu` detects device
// loss, independent of `GpuFrameError::DeviceLost` from an in-flight dispatch.
// The actual "stop retrying" mechanism is `GpuBackend`'s `lost` flag, driven by
// that error return -- never synchronize crate-wide state from this callback.
device.set_device_lost_callback(|reason, message| {
tracing::warn!(?reason, %message, "wgpu device lost");
});
Ok(Self {
instance,
adapter,
device,
queue,
})
}
}