1use std::fmt;
6
7use henad_compute::fault::FaultSink;
8use henad_compute::gpu::{GpuContext, GpuNeeds};
9use henad_compute::runtime_info::RuntimeInfo;
10
11#[derive(Debug)]
13pub enum DeviceError {
14 NoAdapter(wgpu::RequestAdapterError),
16 BelowBaseline {
18 adapter: String,
20 limit: &'static str,
22 },
23 NoDevice(wgpu::RequestDeviceError),
25}
26
27impl fmt::Display for DeviceError {
28 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29 match self {
30 Self::NoAdapter(_) => f.write_str("no suitable GPU adapter found"),
31 Self::BelowBaseline { adapter, limit } => {
32 write!(f, "adapter '{adapter}' offers less than the WebGPU baseline in {limit}")
33 }
34 Self::NoDevice(_) => f.write_str("failed to create GPU device"),
35 }
36 }
37}
38
39impl std::error::Error for DeviceError {
40 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
41 match self {
42 Self::NoAdapter(error) => Some(error),
43 Self::BelowBaseline { .. } => None,
44 Self::NoDevice(error) => Some(error),
45 }
46 }
47}
48
49pub fn acquire_headless(needs: GpuNeeds) -> Result<GpuContext, DeviceError> {
64 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
65 let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
66 power_preference: wgpu::PowerPreference::HighPerformance,
67 force_fallback_adapter: false,
68 compatible_surface: None,
69 ..Default::default()
70 }))
71 .map_err(DeviceError::NoAdapter)?;
72 let required_limits = device_limits(&adapter.limits(), |baseline| {
73 henad_compute::gpu::limits::raise(&adapter, baseline, needs)
74 })
75 .map_err(|limit| DeviceError::BelowBaseline {
76 adapter: adapter.get_info().name,
77 limit,
78 })?;
79 let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
80 label: Some("henad-explore"),
81 required_features: wgpu::Features::empty(),
82 required_limits,
83 memory_hints: wgpu::MemoryHints::Performance,
84 experimental_features: wgpu::ExperimentalFeatures::disabled(),
85 trace: wgpu::Trace::Off,
86 }))
87 .map_err(DeviceError::NoDevice)?;
88 let runtime = RuntimeInfo::collect(&adapter, &device);
89 Ok(GpuContext::new(device, queue, wgpu::TextureFormat::Rgba8Unorm, FaultSink::new()).with_runtime_info(runtime))
92}
93
94pub(crate) fn device_limits(
103 available: &wgpu::Limits,
104 raise: impl FnOnce(&wgpu::Limits) -> wgpu::Limits,
105) -> Result<wgpu::Limits, &'static str> {
106 let baseline = wgpu::Limits::default();
107 match short_limit(&baseline, available) {
108 Some(limit) => Err(limit),
109 None => Ok(raise(&baseline)),
110 }
111}
112
113fn short_limit(required: &wgpu::Limits, available: &wgpu::Limits) -> Option<&'static str> {
116 let mut short = None;
117 required.check_limits_with_fail_fn(available, true, |name, _, _| short = Some(name));
118 short
119}
120
121#[cfg(test)]
122mod tests {
123 use super::{device_limits, short_limit};
124
125 #[test]
126 fn a_webgl2_adapter_falls_short_of_the_baseline() {
127 let baseline = wgpu::Limits::default();
128 assert_eq!(short_limit(&baseline, &baseline), None);
129 assert!(short_limit(&baseline, &wgpu::Limits::downlevel_webgl2_defaults()).is_some());
130 }
131
132 #[test]
135 fn an_adapter_short_of_storage_buffers_falls_short_of_the_baseline() {
136 let available = wgpu::Limits {
137 max_storage_buffers_per_shader_stage: 7,
138 ..wgpu::Limits::default()
139 };
140 let clamp = |base: &wgpu::Limits| wgpu::Limits {
141 max_storage_buffers_per_shader_stage: base
142 .max_storage_buffers_per_shader_stage
143 .min(available.max_storage_buffers_per_shader_stage),
144 ..base.clone()
145 };
146 assert_eq!(
147 device_limits(&available, clamp).err(),
148 Some("max_storage_buffers_per_shader_stage")
149 );
150 assert!(device_limits(&wgpu::Limits::default(), clamp).is_ok());
151 }
152}