use crate::fault::{Fault, catching};
use crate::gpu::GpuContext;
#[cfg(not(target_arch = "wasm32"))]
const FILTERS: [wgpu::ErrorFilter; 3] = [
wgpu::ErrorFilter::OutOfMemory,
wgpu::ErrorFilter::Validation,
wgpu::ErrorFilter::Internal,
];
#[cfg(not(target_arch = "wasm32"))]
pub fn catching_on<T>(ctx: &GpuContext, during: &'static str, f: impl FnOnce() -> T) -> Result<T, Fault> {
let guards: Vec<wgpu::ErrorScopeGuard> = FILTERS
.iter()
.map(|&filter| ctx.device.push_error_scope(filter))
.collect();
let caught = catching(during, f);
let mut reported = None;
for guard in guards.into_iter().rev() {
reported = reported.or(pollster::block_on(guard.pop()));
}
match (caught, reported) {
(Err(fault), _) => Err(fault),
(Ok(_), Some(error)) => Err(Fault::device(during, error)),
(Ok(value), None) => Ok(value),
}
}
#[cfg(target_arch = "wasm32")]
pub fn catching_on<T>(_ctx: &GpuContext, during: &'static str, f: impl FnOnce() -> T) -> Result<T, Fault> {
catching(during, f)
}
#[cfg(test)]
mod tests {
use super::catching_on;
use crate::fault::FaultKind;
use crate::gpu::headless_context;
fn oversized_buffer(device: &wgpu::Device) {
drop(device.create_buffer(&wgpu::BufferDescriptor {
label: Some("henad_fault_test_oversized"),
size: device.limits().max_buffer_size + 4096,
usage: wgpu::BufferUsages::STORAGE,
mapped_at_creation: false,
}));
}
#[test]
fn a_device_error_comes_back_as_a_fault() {
let Some(ctx) = headless_context("henad_fault_test", wgpu::Features::empty()) else {
log::warn!("skipping a_device_error_comes_back_as_a_fault: no adapter");
return;
};
let fault = catching_on(&ctx, "testing", || oversized_buffer(&ctx.device))
.expect_err("an over-sized buffer should have been reported");
assert!(
matches!(fault.kind, FaultKind::Device(_)),
"expected a device fault, got {fault:?}"
);
}
#[test]
fn the_device_still_works_after_a_captured_error() {
let Some(ctx) = headless_context("henad_fault_reuse_test", wgpu::Features::empty()) else {
log::warn!("skipping the_device_still_works_after_a_captured_error: no adapter");
return;
};
drop(catching_on(&ctx, "testing", || oversized_buffer(&ctx.device)));
let outcome = catching_on(&ctx, "testing", || {
let buffer = ctx.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("henad_fault_test_fine"),
size: 1024,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let mut encoder = ctx
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
encoder.clear_buffer(&buffer, 0, None);
ctx.queue.submit(Some(encoder.finish()));
});
assert!(outcome.is_ok(), "the device was left unusable: {outcome:?}");
}
#[test]
fn a_panic_inside_a_scope_leaves_the_scopes_balanced() {
let Some(ctx) = headless_context("henad_fault_unwind_test", wgpu::Features::empty()) else {
log::warn!("skipping a_panic_inside_a_scope_leaves_the_scopes_balanced: no adapter");
return;
};
let fault = catching_on(&ctx, "testing", || panic!("from inside a scope"))
.expect_err("the panic should have been caught");
assert!(matches!(fault.kind, FaultKind::Panic { .. }), "{fault:?}");
assert!(catching_on(&ctx, "testing", || ()).is_ok());
}
}