// Copyright 2018-2025 the Deno authors. MIT license. use std::fmt::Display; use std::fmt::Formatter; use std::sync::Mutex; use std::sync::OnceLock; use deno_core::cppgc::make_cppgc_object; use deno_core::v8; use deno_core::JsRuntime; use deno_core::V8TaskSpawner; use wgpu_core::binding_model::CreateBindGroupError; use wgpu_core::binding_model::CreateBindGroupLayoutError; use wgpu_core::binding_model::CreatePipelineLayoutError; use wgpu_core::binding_model::GetBindGroupLayoutError; use wgpu_core::command::ClearError; use wgpu_core::command::CommandEncoderError; use wgpu_core::command::ComputePassError; use wgpu_core::command::CreateRenderBundleError; use wgpu_core::command::EncoderStateError; use wgpu_core::command::PassStateError; use wgpu_core::command::QueryError; use wgpu_core::command::RenderBundleError; use wgpu_core::command::RenderPassError; use wgpu_core::device::queue::QueueSubmitError; use wgpu_core::device::queue::QueueWriteError; use wgpu_core::device::DeviceError; use wgpu_core::pipeline::CreateComputePipelineError; use wgpu_core::pipeline::CreateRenderPipelineError; use wgpu_core::pipeline::CreateShaderModuleError; use wgpu_core::present::ConfigureSurfaceError; use wgpu_core::resource::BufferAccessError; use wgpu_core::resource::CreateBufferError; use wgpu_core::resource::CreateQuerySetError; use wgpu_core::resource::CreateSamplerError; use wgpu_core::resource::CreateTextureError; use wgpu_core::resource::CreateTextureViewError; use wgpu_types::error::{ErrorType, WebGpuError}; use crate::device::GPUDeviceLostInfo; use crate::device::GPUDeviceLostReason; pub type ErrorHandler = std::rc::Rc; pub struct DeviceErrorHandler { pub is_lost: OnceLock<()>, pub scopes: Mutex)>>, lost_resolver: Mutex>>, spawner: V8TaskSpawner, // The error handler is constructed before the device. A weak // reference to the device is placed here with `set_device` // after the device is constructed. device: OnceLock>, } impl DeviceErrorHandler { pub fn new( lost_resolver: v8::Global, spawner: V8TaskSpawner, ) -> Self { Self { is_lost: Default::default(), scopes: Mutex::new(vec![]), lost_resolver: Mutex::new(Some(lost_resolver)), device: OnceLock::new(), spawner, } } pub fn set_device(&self, device: v8::Weak) { self.device.set(device).unwrap() } pub fn push_error>(&self, err: Option) { let Some(err) = err else { return; }; if self.is_lost.get().is_some() { return; } let err = err.into(); if let GPUError::Lost(reason) = err { let _ = self.is_lost.set(()); if let Some(resolver) = self.lost_resolver.lock().unwrap().take() { self.spawner.spawn(move |scope| { let resolver = v8::Local::new(scope, resolver); let info = make_cppgc_object(scope, GPUDeviceLostInfo { reason }); let info = v8::Local::new(scope, info); resolver.resolve(scope, info.into()); }); } return; } let error_filter = match err { GPUError::Lost(_) => unreachable!(), GPUError::Validation(_) => GPUErrorFilter::Validation, GPUError::OutOfMemory => GPUErrorFilter::OutOfMemory, GPUError::Internal => GPUErrorFilter::Internal, }; let mut scopes = self.scopes.lock().unwrap(); let scope = scopes .iter_mut() .rfind(|(filter, _)| filter == &error_filter); if let Some(scope) = scope { scope.1.push(err); } else { let device = self .device .get() .expect("set_device was not called") .clone(); self.spawner.spawn(move |scope| { let state = JsRuntime::op_state_from(&*scope); let Some(device) = device.to_local(scope) else { // The device has already gone away, so we don't have // anywhere to report the error. return; }; let key = v8::String::new(scope, "dispatchEvent").unwrap(); let val = device.get(scope, key.into()).unwrap(); let func = v8::Global::new(scope, val.try_cast::().unwrap()); let device = v8::Global::new(scope, device.cast::()); let error_event_class = state.borrow().borrow::().0.clone(); let error = deno_core::error::to_v8_error(scope, &err); let error_event_class = v8::Local::new(scope, error_event_class.clone()); let constructor = v8::Local::::try_from(error_event_class).unwrap(); let kind = v8::String::new(scope, "uncapturederror").unwrap(); let obj = v8::Object::new(scope); let key = v8::String::new(scope, "error").unwrap(); obj.set(scope, key.into(), error); let event = constructor .new_instance(scope, &[kind.into(), obj.into()]) .unwrap(); let recv = v8::Local::new(scope, device); func.open(scope).call(scope, recv, &[event.into()]); }); } } } #[derive(deno_core::WebIDL, Eq, PartialEq)] #[webidl(enum)] pub enum GPUErrorFilter { Validation, OutOfMemory, Internal, } #[derive(Debug, deno_error::JsError)] pub enum GPUError { // TODO(@crowlKats): consider adding an unreachable value that uses unreachable!() #[class("UNREACHABLE")] Lost(GPUDeviceLostReason), #[class("GPUValidationError")] Validation(String), #[class("GPUOutOfMemoryError")] OutOfMemory, #[class("GPUInternalError")] Internal, } impl Display for GPUError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { GPUError::Lost(_) => Ok(()), GPUError::Validation(s) => f.write_str(s), GPUError::OutOfMemory => f.write_str("not enough memory left"), GPUError::Internal => Ok(()), } } } impl std::error::Error for GPUError {} impl GPUError { fn from_webgpu(e: impl WebGpuError) -> Self { match e.webgpu_error_type() { ErrorType::Internal => GPUError::Internal, ErrorType::DeviceLost => GPUError::Lost(GPUDeviceLostReason::Unknown), // TODO: this variant should be ignored, register the lost callback instead. ErrorType::OutOfMemory => GPUError::OutOfMemory, ErrorType::Validation => GPUError::Validation(fmt_err(&e)), } } } fn fmt_err(err: &(dyn std::error::Error + 'static)) -> String { let mut output = err.to_string(); let mut e = err.source(); while let Some(source) = e { output.push_str(&format!(": {source}")); e = source.source(); } if output.is_empty() { output.push_str("validation error"); } output } impl From for GPUError { fn from(err: EncoderStateError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: PassStateError) -> Self { GPUError::Validation(fmt_err(&err)) } } impl From for GPUError { fn from(err: CreateBufferError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: DeviceError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: BufferAccessError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateBindGroupLayoutError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreatePipelineLayoutError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateBindGroupError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: RenderBundleError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateRenderBundleError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CommandEncoderError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: QueryError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: ComputePassError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateComputePipelineError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: GetBindGroupLayoutError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateRenderPipelineError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: RenderPassError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateSamplerError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateShaderModuleError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateTextureError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateTextureViewError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: CreateQuerySetError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: QueueSubmitError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: QueueWriteError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: ClearError) -> Self { GPUError::from_webgpu(err) } } impl From for GPUError { fn from(err: ConfigureSurfaceError) -> Self { GPUError::from_webgpu(err) } } #[derive(Debug, thiserror::Error, deno_error::JsError)] pub enum GPUGenericError { #[class(type)] #[error("Illegal constructor")] InvalidConstructor, }