use ahash::HashMap;
use re_mutex::Mutex;
use super::wgpu_core_error::WgpuCoreWrappedContextError;
#[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(&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,
description,
}
| wgpu::Error::Validation {
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 is_texture_err = ctx_err
.source
.is::<wgpu::wgc::resource::CreateTextureError>();
let ctx_err =
ContextError::WgpuCoreError(WgpuCoreWrappedContextError(ctx_err));
let new_error = self.errors.lock().insert(ctx_err, entry).is_none();
!is_texture_err && new_error
}
#[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}");
}
}
}
}
}