use std::sync::Arc;
use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
use alpha2_gpu::GpuErrorKind;
use crate::Shared;
use crate::retire::Retirement;
#[derive(Debug)]
pub(crate) struct FrameErrorScope {
#[cfg(not(target_arch = "wasm32"))]
scopes: Option<[wgpu::ErrorScopeGuard; 2]>,
retirement: Shared<Retirement>,
#[cfg(target_arch = "wasm32")]
_thread_bound: std::marker::PhantomData<*mut ()>,
}
impl FrameErrorScope {
pub(crate) fn open(
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))] device: &wgpu::Device,
retirement: Shared<Retirement>,
) -> Self {
Self {
#[cfg(not(target_arch = "wasm32"))]
scopes: Some([
device.push_error_scope(wgpu::ErrorFilter::OutOfMemory),
device.push_error_scope(wgpu::ErrorFilter::Validation),
]),
retirement,
#[cfg(target_arch = "wasm32")]
_thread_bound: std::marker::PhantomData,
}
}
pub(crate) fn close(mut self) -> Option<GpuErrorKind> {
self.pop()
}
#[cfg(not(target_arch = "wasm32"))]
fn pop(&mut self) -> Option<GpuErrorKind> {
let [out_of_memory, validation] = self.scopes.take()?;
let validation = resolved(validation.pop());
let out_of_memory = resolved(out_of_memory.pop());
out_of_memory
.or(validation)
.map(|error| GpuErrorKind::of(&error))
}
#[cfg(target_arch = "wasm32")]
#[allow(clippy::unused_self)] const fn pop(&mut self) -> Option<GpuErrorKind> {
None
}
}
impl Drop for FrameErrorScope {
fn drop(&mut self) {
if self.pop().is_some() {
self.retirement.doubt();
}
}
}
pub(crate) fn capture_creation<T>(
#[cfg_attr(target_arch = "wasm32", allow(unused_variables))] device: &wgpu::Device,
create: impl FnOnce() -> T,
) -> (T, Option<GpuErrorKind>) {
#[cfg(not(target_arch = "wasm32"))]
{
let out_of_memory = device.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
let validation = device.push_error_scope(wgpu::ErrorFilter::Validation);
let created = create();
let validation = resolved(validation.pop());
let out_of_memory = resolved(out_of_memory.pop());
(
created,
out_of_memory
.or(validation)
.map(|error| GpuErrorKind::of(&error)),
)
}
#[cfg(target_arch = "wasm32")]
{
(create(), None)
}
}
#[cfg(not(target_arch = "wasm32"))]
fn resolved<T>(future: impl std::future::Future<Output = Option<T>>) -> Option<T> {
let mut future = std::pin::pin!(future);
let mut context = std::task::Context::from_waker(std::task::Waker::noop());
match future.as_mut().poll(&mut context) {
std::task::Poll::Ready(result) => result,
std::task::Poll::Pending => None,
}
}
#[derive(Clone, Debug, Default)]
pub struct GpuErrorReports {
inner: Arc<ReportState>,
}
#[derive(Debug, Default)]
struct ReportState {
reported: AtomicU64,
handled: AtomicU64,
last: AtomicU8,
}
impl GpuErrorReports {
pub fn report(&self, kind: GpuErrorKind) {
self.inner.last.store(kind_code(kind), Ordering::Release);
self.inner.reported.fetch_add(1, Ordering::AcqRel);
}
pub(crate) fn count(&self) -> u64 {
self.inner.reported.load(Ordering::Acquire)
}
pub(crate) fn last_kind(&self) -> Option<GpuErrorKind> {
match self.inner.last.load(Ordering::Acquire) {
1 => Some(GpuErrorKind::OutOfMemory),
2 => Some(GpuErrorKind::Validation),
3 => Some(GpuErrorKind::Internal),
_ => None,
}
}
pub(crate) fn take_unhandled(&self) -> bool {
let reported = self.count();
self.inner.handled.swap(reported, Ordering::AcqRel) != reported
}
}
const fn kind_code(kind: GpuErrorKind) -> u8 {
match kind {
GpuErrorKind::OutOfMemory => 1,
GpuErrorKind::Validation => 2,
_ => 3,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reports_are_counted_and_handled_once() {
let reports = GpuErrorReports::default();
assert!(!reports.take_unhandled());
assert_eq!(reports.last_kind(), None);
let handler = reports.clone();
handler.report(GpuErrorKind::OutOfMemory);
handler.report(GpuErrorKind::Validation);
assert_eq!(reports.count(), 2);
assert_eq!(reports.last_kind(), Some(GpuErrorKind::Validation));
assert!(reports.take_unhandled());
assert!(!reports.take_unhandled());
handler.report(GpuErrorKind::Internal);
assert_eq!(reports.last_kind(), Some(GpuErrorKind::Internal));
assert!(reports.take_unhandled());
}
}