use std::{
fmt,
sync::{Arc, Mutex, atomic::AtomicBool},
};
#[derive(Debug, Clone)]
pub enum GpuAcquireError {
NoAdapter(String),
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 {}
pub const MEGAKERNEL_STORAGE_BUFFERS: u32 = 11;
pub const WAVEFRONT_STORAGE_BUFFERS: u32 = 19;
pub const WAVEFRONT_BOUNCE_TOTAL_BUFFERS: u32 = 32;
const MAX_REQUESTED_STORAGE_BUFFERS_PER_STAGE: u32 = 32;
const MAX_REQUESTED_BUFFERS_AND_ACCEL_PER_STAGE: u32 = 40;
pub struct GpuContext {
pub instance: wgpu::Instance,
pub adapter: wgpu::Adapter,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub validation_error_seen: Arc<AtomicBool>,
pub last_uncaptured_error: Arc<Mutex<Option<String>>>,
}
impl GpuContext {
pub fn acquire() -> Result<Self, GpuAcquireError> {
pollster::block_on(Self::acquire_async())
}
#[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 adapter_limits = adapter.limits();
let required_limits = wgpu::Limits {
max_storage_buffers_per_shader_stage: adapter_limits
.max_storage_buffers_per_shader_stage
.clamp(
wgpu::Limits::default().max_storage_buffers_per_shader_stage,
MAX_REQUESTED_STORAGE_BUFFERS_PER_STAGE,
),
max_buffers_and_acceleration_structures_per_shader_stage: adapter_limits
.max_buffers_and_acceleration_structures_per_shader_stage
.clamp(
wgpu::Limits::default()
.max_buffers_and_acceleration_structures_per_shader_stage,
MAX_REQUESTED_BUFFERS_AND_ACCEL_PER_STAGE,
),
..Default::default()
};
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("indicatrix gpu-feature compute device"),
required_features: adapter.features() & wgpu::Features::PIPELINE_CACHE,
required_limits,
..Default::default()
})
.await
.map_err(|e| GpuAcquireError::RequestDevice(format!("{e:?}")))?;
device.set_device_lost_callback(|reason, message| {
tracing::warn!(?reason, %message, "wgpu device lost");
});
let validation_error_seen = Arc::new(AtomicBool::new(false));
let validation_error_seen_for_handler = Arc::clone(&validation_error_seen);
let last_uncaptured_error = Arc::new(Mutex::new(None));
let last_uncaptured_error_for_handler = Arc::clone(&last_uncaptured_error);
device.on_uncaptured_error(Arc::new(move |error: wgpu::Error| {
tracing::error!(%error, "wgpu uncaptured device error");
validation_error_seen_for_handler.store(true, std::sync::atomic::Ordering::Relaxed);
let mut slot = last_uncaptured_error_for_handler
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
*slot = Some(error.to_string());
}));
Ok(Self {
instance,
adapter,
device,
queue,
validation_error_seen,
last_uncaptured_error,
})
}
}