use std::fmt;
use henad_compute::fault::FaultSink;
use henad_compute::gpu::{GpuContext, GpuNeeds};
use henad_compute::runtime_info::RuntimeInfo;
#[derive(Debug)]
pub enum DeviceError {
NoAdapter(wgpu::RequestAdapterError),
BelowBaseline {
adapter: String,
limit: &'static str,
},
NoDevice(wgpu::RequestDeviceError),
}
impl fmt::Display for DeviceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::NoAdapter(_) => f.write_str("no suitable GPU adapter found"),
Self::BelowBaseline { adapter, limit } => {
write!(f, "adapter '{adapter}' offers less than the WebGPU baseline in {limit}")
}
Self::NoDevice(_) => f.write_str("failed to create GPU device"),
}
}
}
impl std::error::Error for DeviceError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NoAdapter(error) => Some(error),
Self::BelowBaseline { .. } => None,
Self::NoDevice(error) => Some(error),
}
}
}
pub fn acquire_headless(needs: GpuNeeds) -> Result<GpuContext, DeviceError> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle());
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
..Default::default()
}))
.map_err(DeviceError::NoAdapter)?;
let required_limits = device_limits(&adapter.limits(), |baseline| {
henad_compute::gpu::limits::raise(&adapter, baseline, needs)
})
.map_err(|limit| DeviceError::BelowBaseline {
adapter: adapter.get_info().name,
limit,
})?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("henad-explore"),
required_features: wgpu::Features::empty(),
required_limits,
memory_hints: wgpu::MemoryHints::Performance,
experimental_features: wgpu::ExperimentalFeatures::disabled(),
trace: wgpu::Trace::Off,
}))
.map_err(DeviceError::NoDevice)?;
let runtime = RuntimeInfo::collect(&adapter, &device);
Ok(GpuContext::new(device, queue, wgpu::TextureFormat::Rgba8Unorm, FaultSink::new()).with_runtime_info(runtime))
}
pub(crate) fn device_limits(
available: &wgpu::Limits,
raise: impl FnOnce(&wgpu::Limits) -> wgpu::Limits,
) -> Result<wgpu::Limits, &'static str> {
let baseline = wgpu::Limits::default();
match short_limit(&baseline, available) {
Some(limit) => Err(limit),
None => Ok(raise(&baseline)),
}
}
fn short_limit(required: &wgpu::Limits, available: &wgpu::Limits) -> Option<&'static str> {
let mut short = None;
required.check_limits_with_fail_fn(available, true, |name, _, _| short = Some(name));
short
}
#[cfg(test)]
mod tests {
use super::{device_limits, short_limit};
#[test]
fn a_webgl2_adapter_falls_short_of_the_baseline() {
let baseline = wgpu::Limits::default();
assert_eq!(short_limit(&baseline, &baseline), None);
assert!(short_limit(&baseline, &wgpu::Limits::downlevel_webgl2_defaults()).is_some());
}
#[test]
fn an_adapter_short_of_storage_buffers_falls_short_of_the_baseline() {
let available = wgpu::Limits {
max_storage_buffers_per_shader_stage: 7,
..wgpu::Limits::default()
};
let clamp = |base: &wgpu::Limits| wgpu::Limits {
max_storage_buffers_per_shader_stage: base
.max_storage_buffers_per_shader_stage
.min(available.max_storage_buffers_per_shader_stage),
..base.clone()
};
assert_eq!(
device_limits(&available, clamp).err(),
Some("max_storage_buffers_per_shader_stage")
);
assert!(device_limits(&wgpu::Limits::default(), clamp).is_ok());
}
}