1use alloc::string::ToString as _;
2use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
3use core::fmt;
4
5use thiserror::Error;
6
7use alloc::format;
8use core::error;
9
10use hashbrown::HashMap;
11use wgpu_sync::Mutex;
12use wgt::error::{Error, ErrorFilter, ErrorSource, ErrorType, UncapturedErrorHandler, WebGpuError};
13use wgt::WasmNotSendSync;
14
15use crate::device::Device;
16
17mod thread_id {
23 #[cfg(feature = "std")]
24 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25 pub struct ThreadId(std::thread::ThreadId);
26
27 #[cfg(feature = "std")]
28 impl ThreadId {
29 pub fn current() -> Self {
30 ThreadId(std::thread::current().id())
31 }
32 }
33
34 #[cfg(not(feature = "std"))]
35 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36 pub struct ThreadId(());
37
38 #[cfg(not(feature = "std"))]
39 impl ThreadId {
40 pub fn current() -> Self {
41 ThreadId(())
45 }
46 }
47}
48
49struct ErrorScope {
50 pub error: Option<Error>,
51 pub filter: ErrorFilter,
52}
53
54struct InternalErrorSink {
55 scopes: HashMap<thread_id::ThreadId, Vec<ErrorScope>>,
56 uncaptured_handler: Option<Arc<dyn UncapturedErrorHandler>>,
57}
58
59pub struct ErrorSink(Mutex<InternalErrorSink>);
60
61impl ErrorSink {
62 pub fn new() -> ErrorSink {
63 ErrorSink(Mutex::new(InternalErrorSink::new()))
65 }
66
67 #[cold]
68 #[track_caller]
69 #[inline(never)]
70 fn handle_error_inner(
71 &self,
72 error_type: ErrorType,
73 source: ErrorSource,
74 label: Option<&str>,
75 fn_ident: &'static str,
76 ) {
77 let source: ErrorSource = Box::new(ContextError {
78 fn_ident,
79 source,
80 label: label.unwrap_or_default().to_string(),
81 });
82 let final_error_handling = {
83 let mut sink = self.0.lock();
84 let error = match error_type {
85 ErrorType::Internal => {
86 let description = format_error(&*source);
87 Error::Internal {
88 source,
89 description,
90 }
91 }
92 ErrorType::OutOfMemory => Error::OutOfMemory { source },
93 ErrorType::Validation => {
94 let description = format_error(&*source);
95 Error::Validation {
96 source,
97 description,
98 }
99 }
100 ErrorType::DeviceLost => return, };
102 sink.handle_error_or_return_handler(error)
103 };
104
105 if let Some(f) = final_error_handling {
106 f();
110 }
111 }
112
113 #[inline]
114 #[track_caller]
115 pub fn handle_error(
116 &self,
117 source: impl WebGpuError + WasmNotSendSync + 'static,
118 label: Option<&str>,
119 fn_ident: &'static str,
120 ) {
121 let error_type = source.webgpu_error_type();
122 self.handle_error_inner(error_type, Box::new(source), label, fn_ident)
123 }
124
125 #[inline]
126 #[track_caller]
127 pub fn handle_error_nolabel(
128 &self,
129 source: impl WebGpuError + WasmNotSendSync + 'static,
130 fn_ident: &'static str,
131 ) {
132 let error_type = source.webgpu_error_type();
133 self.handle_error_inner(error_type, Box::new(source), None, fn_ident)
134 }
135}
136
137impl InternalErrorSink {
138 fn new() -> InternalErrorSink {
139 InternalErrorSink {
140 scopes: HashMap::new(),
141 uncaptured_handler: None,
142 }
143 }
144
145 #[track_caller]
155 #[must_use]
156 fn handle_error_or_return_handler(&mut self, err: Error) -> Option<impl FnOnce()> {
157 let filter = match err {
158 Error::OutOfMemory { .. } => ErrorFilter::OutOfMemory,
159 Error::Validation { .. } => ErrorFilter::Validation,
160 Error::Internal { .. } => ErrorFilter::Internal,
161 };
162 let thread_id = thread_id::ThreadId::current();
163 let scopes = self.scopes.entry(thread_id).or_default();
164 match scopes.iter_mut().rev().find(|scope| scope.filter == filter) {
165 Some(scope) => {
166 if scope.error.is_none() {
167 scope.error = Some(err);
168 }
169 None
170 }
171 None => {
172 if let Some(custom_handler) = &self.uncaptured_handler {
173 let custom_handler = Arc::clone(custom_handler);
174 Some(move || (custom_handler)(err))
175 } else {
176 default_error_handler(err)
178 }
179 }
180 }
181 }
182}
183
184impl fmt::Debug for InternalErrorSink {
185 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186 write!(f, "ErrorSink")
187 }
188}
189
190#[track_caller]
191fn default_error_handler(err: Error) -> ! {
192 log::error!("Handling wgpu errors as fatal by default");
193 panic!("wgpu error: {err}\n");
194}
195
196#[derive(Debug, Error)]
197#[error("Error scope stack is empty")]
198pub struct EmptyErrorScopeStack;
199
200impl Device {
201 pub fn on_uncaptured_error(&self, handler: Arc<dyn UncapturedErrorHandler>) {
202 let mut error_sink = self.error_sink.0.lock();
203 error_sink.uncaptured_handler = Some(handler);
204 }
205
206 pub fn push_error_scope(&self, filter: ErrorFilter) {
208 let mut error_sink = self.error_sink.0.lock();
209 let thread_id = thread_id::ThreadId::current();
210 let scopes = error_sink.scopes.entry(thread_id).or_default();
211 scopes.push(ErrorScope {
212 error: None,
213 filter,
214 });
215 }
216
217 pub fn pop_error_scope(&self) -> Result<Option<Error>, EmptyErrorScopeStack> {
219 if !self.is_valid() {
221 return Ok(None);
223 }
224 let mut error_sink = self.error_sink.0.lock();
225
226 let thread_id = thread_id::ThreadId::current();
227 let scopes = error_sink.scopes.entry(thread_id).or_default();
228 match scopes.pop() {
230 Some(scope) => Ok(scope.error),
233 None => Err(EmptyErrorScopeStack),
235 }
236 }
237}
238
239impl Device {
240 pub fn error_sink(&self) -> &ErrorSink {
243 &self.error_sink
244 }
245}
246
247#[inline(never)]
248pub fn format_error(err: &(dyn error::Error + 'static)) -> String {
249 let mut output = String::new();
250 let mut level = 1;
251
252 fn print_tree(output: &mut String, level: &mut usize, e: &(dyn error::Error + 'static)) {
253 let mut print = |e: &(dyn error::Error + 'static)| {
254 use core::fmt::Write;
255 writeln!(output, "{}{}", " ".repeat(*level * 2), e).unwrap();
256
257 if let Some(e) = e.source() {
258 *level += 1;
259 print_tree(output, level, e);
260 *level -= 1;
261 }
262 };
263 if let Some(multi) = e.downcast_ref::<MultiError>() {
264 for e in multi.errors() {
265 print(e);
266 }
267 } else {
268 print(e);
269 }
270 }
271
272 print_tree(&mut output, &mut level, err);
273
274 format!("Validation Error\n\nCaused by:\n{output}")
275}
276
277impl Device {
278 #[inline]
279 #[track_caller]
280 pub fn handle_error(
281 &self,
282 source: impl WebGpuError + WasmNotSendSync + 'static,
283 label: Option<&str>,
284 fn_ident: &'static str,
285 ) {
286 self.error_sink.handle_error(source, label, fn_ident);
287 }
288
289 #[inline]
290 #[track_caller]
291 pub fn handle_error_nolabel(
292 &self,
293 source: impl WebGpuError + WasmNotSendSync + 'static,
294 fn_ident: &'static str,
295 ) {
296 self.error_sink.handle_error_nolabel(source, fn_ident);
297 }
298}
299
300#[derive(Debug, Error)]
301#[error(
302 "In {fn_ident}{}{}{}",
303 if self.label.is_empty() { "" } else { ", label = '" },
304 self.label,
305 if self.label.is_empty() { "" } else { "'" }
306)]
307pub struct ContextError {
308 pub fn_ident: &'static str,
309 #[source]
310 pub source: ErrorSource,
311 pub label: String,
312}
313
314#[derive(Clone)]
316pub struct MultiError {
317 inner: Vec<Arc<dyn error::Error + Send + Sync + 'static>>,
318}
319
320impl MultiError {
321 pub fn new<T: error::Error + Send + Sync + 'static>(
322 iter: impl ExactSizeIterator<Item = T>,
323 ) -> Option<Self> {
324 if iter.len() == 0 {
325 return None;
326 }
327 Some(Self {
328 inner: iter.map(Box::from).map(Arc::from).collect(),
329 })
330 }
331
332 pub fn errors(
333 &self,
334 ) -> Box<dyn Iterator<Item = &(dyn error::Error + Send + Sync + 'static)> + '_> {
335 Box::new(self.inner.iter().map(|e| e.as_ref()))
336 }
337}
338
339impl fmt::Debug for MultiError {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
341 fmt::Debug::fmt(&self.inner[0], f)
342 }
343}
344
345impl fmt::Display for MultiError {
346 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
347 fmt::Display::fmt(&self.inner[0], f)
348 }
349}
350
351impl error::Error for MultiError {
352 fn source(&self) -> Option<&(dyn error::Error + 'static)> {
353 self.inner[0].source()
354 }
355}
356
357impl Device {
359 pub fn push_error_scope_with_index(&self, filter: ErrorFilter) -> u32 {
360 let index = {
361 let mut error_sink = self.error_sink.0.lock();
362 let thread_id = thread_id::ThreadId::current();
363 let scopes = error_sink.scopes.entry(thread_id).or_default();
364 scopes
365 .len()
366 .try_into()
367 .expect("Greater than 2^32 nested error scopes")
368 };
369 self.push_error_scope(filter);
370 index
371 }
372
373 pub fn pop_error_scope_checked(&self, index: u32) -> Option<Error> {
374 #[cfg(feature = "std")]
375 fn is_panicking() -> bool {
376 std::thread::panicking()
377 }
378
379 #[cfg(not(feature = "std"))]
380 fn is_panicking() -> bool {
381 false
382 }
383
384 let mut error_sink = self.error_sink.0.lock();
385
386 let is_panicking = is_panicking();
389 let thread_id = thread_id::ThreadId::current();
390 let err = "Mismatched pop_error_scope call: no error scope for this thread. Error scopes are thread-local.";
391 let scopes = match error_sink.scopes.get_mut(&thread_id) {
392 Some(s) => s,
393 None => {
394 if !is_panicking {
395 panic!("{err}");
396 } else {
397 return None;
398 }
399 }
400 };
401 if scopes.is_empty() && !is_panicking {
402 panic!("{err}");
403 }
404 if index as usize != scopes.len() - 1 && !is_panicking {
405 panic!(
406 "Mismatched pop_error_scope call: error scopes must be popped in reverse order."
407 );
408 }
409
410 let scope = match scopes.pop() {
415 Some(s) => s,
416 None if !is_panicking => unreachable!(),
417 None => return None,
418 };
419
420 scope.error
421 }
422}