alpha2_render/
gpu_errors.rs1use std::sync::Arc;
17use std::sync::atomic::{AtomicU8, AtomicU64, Ordering};
18
19use alpha2_gpu::GpuErrorKind;
20
21use crate::Shared;
22use crate::retire::Retirement;
23
24#[derive(Debug)]
29pub(crate) struct FrameErrorScope {
30 #[cfg(not(target_arch = "wasm32"))]
31 scopes: Option<[wgpu::ErrorScopeGuard; 2]>,
32 retirement: Shared<Retirement>,
35 #[cfg(target_arch = "wasm32")]
36 _thread_bound: std::marker::PhantomData<*mut ()>,
37}
38
39impl FrameErrorScope {
40 pub(crate) fn open(
42 #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] device: &wgpu::Device,
43 retirement: Shared<Retirement>,
44 ) -> Self {
45 Self {
46 #[cfg(not(target_arch = "wasm32"))]
47 scopes: Some([
48 device.push_error_scope(wgpu::ErrorFilter::OutOfMemory),
49 device.push_error_scope(wgpu::ErrorFilter::Validation),
50 ]),
51 retirement,
52 #[cfg(target_arch = "wasm32")]
53 _thread_bound: std::marker::PhantomData,
54 }
55 }
56
57 pub(crate) fn close(mut self) -> Option<GpuErrorKind> {
60 self.pop()
61 }
62
63 #[cfg(not(target_arch = "wasm32"))]
64 fn pop(&mut self) -> Option<GpuErrorKind> {
65 let [out_of_memory, validation] = self.scopes.take()?;
66 let validation = resolved(validation.pop());
67 let out_of_memory = resolved(out_of_memory.pop());
68 out_of_memory
69 .or(validation)
70 .map(|error| GpuErrorKind::of(&error))
71 }
72
73 #[cfg(target_arch = "wasm32")]
74 #[allow(clippy::unused_self)] const fn pop(&mut self) -> Option<GpuErrorKind> {
76 None
77 }
78}
79
80impl Drop for FrameErrorScope {
81 fn drop(&mut self) {
82 if self.pop().is_some() {
83 self.retirement.doubt();
84 }
85 }
86}
87
88pub(crate) fn capture_creation<T>(
94 #[cfg_attr(target_arch = "wasm32", allow(unused_variables))] device: &wgpu::Device,
95 create: impl FnOnce() -> T,
96) -> (T, Option<GpuErrorKind>) {
97 #[cfg(not(target_arch = "wasm32"))]
98 {
99 let out_of_memory = device.push_error_scope(wgpu::ErrorFilter::OutOfMemory);
100 let validation = device.push_error_scope(wgpu::ErrorFilter::Validation);
101 let created = create();
102 let validation = resolved(validation.pop());
103 let out_of_memory = resolved(out_of_memory.pop());
104 (
105 created,
106 out_of_memory
107 .or(validation)
108 .map(|error| GpuErrorKind::of(&error)),
109 )
110 }
111 #[cfg(target_arch = "wasm32")]
112 {
113 (create(), None)
114 }
115}
116
117#[cfg(not(target_arch = "wasm32"))]
119fn resolved<T>(future: impl std::future::Future<Output = Option<T>>) -> Option<T> {
120 let mut future = std::pin::pin!(future);
121 let mut context = std::task::Context::from_waker(std::task::Waker::noop());
122 match future.as_mut().poll(&mut context) {
123 std::task::Poll::Ready(result) => result,
124 std::task::Poll::Pending => None,
125 }
126}
127
128#[derive(Clone, Debug, Default)]
133pub struct GpuErrorReports {
134 inner: Arc<ReportState>,
135}
136
137#[derive(Debug, Default)]
138struct ReportState {
139 reported: AtomicU64,
141 handled: AtomicU64,
143 last: AtomicU8,
145}
146
147impl GpuErrorReports {
148 pub fn report(&self, kind: GpuErrorKind) {
151 self.inner.last.store(kind_code(kind), Ordering::Release);
152 self.inner.reported.fetch_add(1, Ordering::AcqRel);
153 }
154
155 pub(crate) fn count(&self) -> u64 {
157 self.inner.reported.load(Ordering::Acquire)
158 }
159
160 pub(crate) fn last_kind(&self) -> Option<GpuErrorKind> {
161 match self.inner.last.load(Ordering::Acquire) {
162 1 => Some(GpuErrorKind::OutOfMemory),
163 2 => Some(GpuErrorKind::Validation),
164 3 => Some(GpuErrorKind::Internal),
165 _ => None,
166 }
167 }
168
169 pub(crate) fn take_unhandled(&self) -> bool {
171 let reported = self.count();
172 self.inner.handled.swap(reported, Ordering::AcqRel) != reported
173 }
174}
175
176const fn kind_code(kind: GpuErrorKind) -> u8 {
177 match kind {
178 GpuErrorKind::OutOfMemory => 1,
179 GpuErrorKind::Validation => 2,
180 _ => 3,
181 }
182}
183
184#[cfg(test)]
185mod tests {
186 use super::*;
187
188 #[test]
189 fn reports_are_counted_and_handled_once() {
190 let reports = GpuErrorReports::default();
191 assert!(!reports.take_unhandled());
192 assert_eq!(reports.last_kind(), None);
193 let handler = reports.clone();
194 handler.report(GpuErrorKind::OutOfMemory);
195 handler.report(GpuErrorKind::Validation);
196 assert_eq!(reports.count(), 2);
197 assert_eq!(reports.last_kind(), Some(GpuErrorKind::Validation));
198 assert!(reports.take_unhandled());
199 assert!(!reports.take_unhandled());
200 handler.report(GpuErrorKind::Internal);
201 assert_eq!(reports.last_kind(), Some(GpuErrorKind::Internal));
202 assert!(reports.take_unhandled());
203 }
204}