use ahash::HashMap;
use re_mutex::Mutex;
use super::handle_async_error;
use super::wgpu_core_error::WgpuCoreWrappedContextError;
use crate::device_caps::WgpuBackendType;
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum ContextError {
WgpuCoreError(WgpuCoreWrappedContextError),
#[cfg(web)]
WebGpuError(String),
}
pub struct ErrorEntry {
last_occurred_frame_index: u64,
#[expect(dead_code)]
description: String,
}
#[derive(Default)]
pub struct ErrorTracker {
pub errors: Mutex<HashMap<ContextError, ErrorEntry>>,
}
impl ErrorTracker {
pub fn on_device_timeline_frame_finished(&self, device_timeline_frame_index: u64) {
let mut errors = self.errors.lock();
errors.retain(|_error, entry| {
device_timeline_frame_index == entry.last_occurred_frame_index
});
}
pub fn handle_error_future(
self: &std::sync::Arc<Self>,
backend_type: WgpuBackendType,
error_scope_result: impl IntoIterator<
Item = impl std::future::Future<Output = Option<wgpu::Error>> + Send + 'static,
>,
frame_index: u64,
on_last_scope_resolved: impl Fn(&Self, u64) + Send + Sync + 'static,
) {
let mut error_scope_result = error_scope_result.into_iter().peekable();
while let Some(error_future) = error_scope_result.next() {
if error_scope_result.peek().is_none() {
let err_tracker = self.clone();
handle_async_error(
backend_type,
move |error| {
if let Some(error) = error {
err_tracker.handle_error(error, frame_index);
}
on_last_scope_resolved(&err_tracker, frame_index);
},
error_future,
);
break;
}
let err_tracker = self.clone();
handle_async_error(
backend_type,
move |error| {
if let Some(error) = error {
err_tracker.handle_error(error, frame_index);
}
},
error_future,
);
}
}
pub fn handle_error(&self, error: wgpu::Error, frame_index: u64) {
let is_internal_error = matches!(error, wgpu::Error::Internal { .. });
match error {
wgpu::Error::OutOfMemory { source: _ } => {
re_log::error!("A wgpu operation caused out-of-memory: {error}");
}
wgpu::Error::Internal {
source: _source,
description,
}
| wgpu::Error::Validation {
source: _source,
description,
} => {
let entry = ErrorEntry {
last_occurred_frame_index: frame_index,
description: description.clone(),
};
let should_log = match _source.downcast::<wgpu::wgc::error::ContextError>() {
Ok(ctx_err) => {
if ctx_err
.source
.downcast_ref::<wgpu::wgc::command::CommandEncoderError>()
.is_some()
{
return;
}
let ctx_err =
ContextError::WgpuCoreError(WgpuCoreWrappedContextError(ctx_err));
self.errors.lock().insert(ctx_err, entry).is_none()
}
#[cfg(not(web))]
Err(_) => true,
#[cfg(web)]
Err(_) => {
let ctx_err = ContextError::WebGpuError(description.clone());
self.errors.lock().insert(ctx_err, entry).is_none()
}
};
if should_log {
let base_description = if is_internal_error {
"Internal wgpu error"
} else {
"Wgpu validation error"
};
re_log::error!("{base_description} {frame_index}: {description}");
}
}
}
}
}