diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 8cd5252b2c..7d742fd891 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -7,9 +7,13 @@ use crate::{ }; use arrayvec::ArrayVec; +use fmt::Debug; use futures::future::{ready, Ready}; +use parking_lot::Mutex; use smallvec::SmallVec; -use std::{borrow::Cow::Borrowed, error::Error, fmt, marker::PhantomData, ops::Range, slice}; +use std::{ + borrow::Cow::Borrowed, error::Error, fmt, marker::PhantomData, ops::Range, slice, sync::Arc, +}; use typed_arena::Arena; pub struct Context(wgc::hub::Global); @@ -497,9 +501,15 @@ fn map_pass_channel( } } +#[derive(Debug)] +pub(crate) struct Device { + id: wgc::id::DeviceId, + error_sink: ErrorSink, +} + impl crate::Context for Context { type AdapterId = wgc::id::AdapterId; - type DeviceId = wgc::id::DeviceId; + type DeviceId = Device; type QueueId = wgc::id::QueueId; type ShaderModuleId = wgc::id::ShaderModuleId; type BindGroupLayoutId = wgc::id::BindGroupLayoutId; @@ -567,7 +577,11 @@ impl crate::Context for Context { *adapter => global.adapter_request_device(*adapter, desc, trace_dir, PhantomData) ) .unwrap_pretty(); - ready(Ok((device_id, device_id))) + let device = Device { + id: device_id, + error_sink: Arc::new(Mutex::new(ErrorSinkRaw::new())), + }; + ready(Ok((device, device_id))) } fn adapter_features(&self, adapter: &Self::AdapterId) -> Features { @@ -582,12 +596,12 @@ impl crate::Context for Context { fn device_features(&self, device: &Self::DeviceId) -> Features { let global = &self.0; - wgc::gfx_select!(*device => global.device_features(*device)).unwrap_pretty() + wgc::gfx_select!(device.id => global.device_features(device.id)).unwrap_pretty() } fn device_limits(&self, device: &Self::DeviceId) -> Limits { let global = &self.0; - wgc::gfx_select!(*device => global.device_limits(*device)).unwrap_pretty() + wgc::gfx_select!(device.id => global.device_limits(device.id)).unwrap_pretty() } fn device_create_swap_chain( @@ -598,7 +612,7 @@ impl crate::Context for Context { ) -> Self::SwapChainId { let global = &self.0; wgc::gfx_select!( - *device => global.device_create_swap_chain(*device, *surface, desc) + device.id => global.device_create_swap_chain(device.id, *surface, desc) ) .unwrap_pretty() } @@ -614,9 +628,12 @@ impl crate::Context for Context { }; let global = &self.0; wgc::gfx_select!( - *device => global.device_create_shader_module(*device, desc, PhantomData) + device.id => global.device_create_shader_module(device.id, desc, PhantomData) + ) + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.shader_module_error(PhantomData)), ) - .unwrap_pretty() } fn device_create_bind_group_layout( @@ -626,12 +643,12 @@ impl crate::Context for Context { ) -> Self::BindGroupLayoutId { let global = &self.0; wgc::gfx_select!( - *device => global.device_create_bind_group_layout(*device, &wgc::binding_model::BindGroupLayoutDescriptor { + device.id => global.device_create_bind_group_layout(device.id, &wgc::binding_model::BindGroupLayoutDescriptor { label: desc.label.map(Borrowed), entries: Borrowed(desc.entries), }, PhantomData) ) - .unwrap_pretty() + .unwrap_error_sink(&device.error_sink, || wgc::gfx_select!( device.id => global.bind_group_layout_error(PhantomData))) } fn device_create_bind_group( @@ -673,8 +690,8 @@ impl crate::Context for Context { .collect::>(); let global = &self.0; - wgc::gfx_select!(*device => global.device_create_bind_group( - *device, + wgc::gfx_select!(device.id => global.device_create_bind_group( + device.id, &bm::BindGroupDescriptor { label: desc.label.as_ref().map(|label| Borrowed(&label[..])), layout: desc.layout.id, @@ -682,7 +699,10 @@ impl crate::Context for Context { }, PhantomData )) - .unwrap_pretty() + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.bind_group_error(PhantomData)), + ) } fn device_create_pipeline_layout( @@ -708,8 +728,8 @@ impl crate::Context for Context { .collect::>(); let global = &self.0; - wgc::gfx_select!(*device => global.device_create_pipeline_layout( - *device, + wgc::gfx_select!(device.id => global.device_create_pipeline_layout( + device.id, &wgc::binding_model::PipelineLayoutDescriptor { label: desc.label.map(Borrowed), bind_group_layouts: Borrowed(&temp_layouts), @@ -717,7 +737,10 @@ impl crate::Context for Context { }, PhantomData )) - .unwrap_pretty() + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.pipeline_layout_error(PhantomData)), + ) } fn device_create_render_pipeline( @@ -755,8 +778,8 @@ impl crate::Context for Context { }; let global = &self.0; - wgc::gfx_select!(*device => global.device_create_render_pipeline( - *device, + wgc::gfx_select!(device.id => global.device_create_render_pipeline( + device.id, &pipe::RenderPipelineDescriptor { label: desc.label.map(Borrowed), layout: desc.layout.map(|l| l.id), @@ -774,7 +797,10 @@ impl crate::Context for Context { PhantomData, None )) - .unwrap_pretty() + .unwrap_error_sink(&device.error_sink, || { + let err = wgc::gfx_select!( device.id => global.render_pipeline_error(PhantomData)); + (err, 0u8) + }) .0 } @@ -786,8 +812,8 @@ impl crate::Context for Context { use wgc::pipeline as pipe; let global = &self.0; - wgc::gfx_select!(*device => global.device_create_compute_pipeline( - *device, + wgc::gfx_select!(device.id => global.device_create_compute_pipeline( + device.id, &pipe::ComputePipelineDescriptor { label: desc.label.map(Borrowed), layout: desc.layout.map(|l| l.id), @@ -799,7 +825,10 @@ impl crate::Context for Context { PhantomData, None )) - .unwrap_pretty() + .unwrap_error_sink(&device.error_sink, || { + let err = wgc::gfx_select!( device.id => global.compute_pipeline_error(PhantomData)); + (err, 0u8) + }) .0 } @@ -809,8 +838,8 @@ impl crate::Context for Context { desc: &crate::BufferDescriptor<'_>, ) -> Self::BufferId { let global = &self.0; - wgc::gfx_select!(*device => global.device_create_buffer( - *device, + wgc::gfx_select!(device.id => global.device_create_buffer( + device.id, &wgt::BufferDescriptor { label: desc.label.map(Borrowed), mapped_at_creation: desc.mapped_at_creation, @@ -819,7 +848,10 @@ impl crate::Context for Context { }, PhantomData )) - .unwrap_pretty() + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.buffer_error(PhantomData)), + ) } fn device_create_texture( @@ -828,8 +860,8 @@ impl crate::Context for Context { desc: &TextureDescriptor, ) -> Self::TextureId { let global = &self.0; - wgc::gfx_select!(*device => global.device_create_texture( - *device, + wgc::gfx_select!(device.id => global.device_create_texture( + device.id, &wgt::TextureDescriptor { label: desc.label.map(Borrowed), size: desc.size, @@ -841,7 +873,10 @@ impl crate::Context for Context { }, PhantomData )) - .unwrap_pretty() + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.texture_error(PhantomData)), + ) } fn device_create_sampler( @@ -850,8 +885,8 @@ impl crate::Context for Context { desc: &SamplerDescriptor, ) -> Self::SamplerId { let global = &self.0; - wgc::gfx_select!(*device => global.device_create_sampler( - *device, + wgc::gfx_select!(device.id => global.device_create_sampler( + device.id, &wgc::resource::SamplerDescriptor { label: desc.label.map(Borrowed), address_modes: [desc.address_mode_u, desc.address_mode_v, desc.address_mode_w], @@ -866,7 +901,10 @@ impl crate::Context for Context { }, PhantomData )) - .unwrap_pretty() + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.sampler_error(PhantomData)), + ) } fn device_create_command_encoder( @@ -875,14 +913,17 @@ impl crate::Context for Context { desc: &CommandEncoderDescriptor, ) -> Self::CommandEncoderId { let global = &self.0; - wgc::gfx_select!(*device => global.device_create_command_encoder( - *device, + wgc::gfx_select!(device.id => global.device_create_command_encoder( + device.id, &wgt::CommandEncoderDescriptor { label: desc.label.map(Borrowed), }, PhantomData )) - .unwrap_pretty() + .unwrap_error_sink( + &device.error_sink, + || wgc::gfx_select!( device.id => global.command_encoder_error(PhantomData)), + ) } fn device_create_render_bundle_encoder( @@ -897,31 +938,31 @@ impl crate::Context for Context { depth_stencil_format: desc.depth_stencil_format, sample_count: desc.sample_count, }, - *device, + device.id, None, ) - .unwrap_pretty() + .unwrap_pretty() // TODO: errorsink, but missing render_bundle_error } fn device_drop(&self, device: &Self::DeviceId) { #[cfg(not(target_arch = "wasm32"))] { let global = &self.0; - wgc::gfx_select!(*device => global.device_poll(*device, true)).unwrap_pretty() + wgc::gfx_select!(device.id => global.device_poll(device.id, true)).unwrap_pretty() } //TODO: make this work in general #[cfg(not(target_arch = "wasm32"))] #[cfg(feature = "metal-auto-capture")] { let global = &self.0; - wgc::gfx_select!(*device => global.device_drop(*device)); + wgc::gfx_select!(device.id => global.device_drop(device.id)); } } fn device_poll(&self, device: &Self::DeviceId, maintain: crate::Maintain) { let global = &self.0; - wgc::gfx_select!(*device => global.device_poll( - *device, + wgc::gfx_select!(device.id => global.device_poll( + device.id, match maintain { crate::Maintain::Poll => false, crate::Maintain::Wait => true, @@ -930,6 +971,15 @@ impl crate::Context for Context { .unwrap_pretty() } + fn device_on_uncaptured_error( + &self, + device: &Self::DeviceId, + handler: impl crate::UncapturedErrorHandler, + ) { + let mut error_sink = device.error_sink.lock(); + error_sink.uncaptured_handler = Box::new(handler); + } + fn buffer_map_async( &self, buffer: &Self::BufferId, @@ -1346,13 +1396,63 @@ pub(crate) struct SwapChainOutputDetail { trait PrettyResult { fn unwrap_pretty(self) -> T; + fn unwrap_error_sink(self, error_sink: &ErrorSink, fallback: impl FnOnce() -> T) -> T; } impl PrettyResult for Result where - E: Error, + E: Error + Send + Sync + 'static, { fn unwrap_pretty(self) -> T { self.unwrap_or_else(|err| panic!("{}", err)) } + + fn unwrap_error_sink(self, error_sink: &ErrorSink, fallback: impl FnOnce() -> T) -> T { + self.unwrap_or_else(|err| { + let error_sink = error_sink.lock(); + error_sink.handle_error(crate::Error::ValidationError { + source: Box::new(err), + }); + fallback() + }) + } +} + +type ErrorSink = Arc>; + +struct ErrorSinkRaw { + uncaptured_handler: Box, +} + +impl ErrorSinkRaw { + fn new() -> ErrorSinkRaw { + ErrorSinkRaw { + uncaptured_handler: Box::from(default_error_handler), + } + } + fn handle_error(&self, err: crate::Error) { + (self.uncaptured_handler)(err); + } +} + +impl Debug for ErrorSinkRaw { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ErrorSink") + } +} + +fn default_error_handler(err: crate::Error) { + eprintln!("wgpu error: {}\n", err); + + if err.source().is_some() { + eprintln!("Caused by:"); + let mut source_opt = err.source(); + while let Some(source) = source_opt { + eprintln!(" {}", source); + source_opt = source.source(); + } + eprintln!(); + } + + panic!("Handling wgpu errors as fatal by default"); } diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index 804abe000c..680b0c81dd 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -1148,6 +1148,14 @@ impl crate::Context for Context { // Device is polled automatically } + fn device_on_uncaptured_error( + &self, + _device: &Self::DeviceId, + _handler: impl crate::UncapturedErrorHandler, + ) { + // TODO: + } + fn buffer_map_async( &self, _buffer: &Self::BufferId, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6444687ab1..e745a0bca3 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -12,7 +12,7 @@ mod macros; use std::{ borrow::Cow, - error::Error, + error, fmt::{Debug, Display}, future::Future, marker::PhantomData, @@ -262,6 +262,11 @@ trait Context: Debug + Send + Sized + Sync { ) -> Self::RenderBundleEncoderId; fn device_drop(&self, device: &Self::DeviceId); fn device_poll(&self, device: &Self::DeviceId, maintain: Maintain); + fn device_on_uncaptured_error( + &self, + device: &Self::DeviceId, + handler: impl UncapturedErrorHandler, + ); fn buffer_map_async( &self, @@ -1266,7 +1271,7 @@ impl Display for SwapChainError { } } -impl Error for SwapChainError {} +impl error::Error for SwapChainError {} impl Instance { /// Create an new instance of wgpu. @@ -1542,6 +1547,11 @@ impl Device { id: Context::device_create_swap_chain(&*self.context, &self.id, &surface.id, desc), } } + + /// Set a callback for errors that are not handled in error scopes. + pub fn on_uncaptured_error(&self, handler: impl UncapturedErrorHandler) { + self.context.device_on_uncaptured_error(&self.id, handler); + } } impl Drop for Device { @@ -1562,7 +1572,7 @@ impl Display for RequestDeviceError { } } -impl Error for RequestDeviceError {} +impl error::Error for RequestDeviceError {} /// Error occurred when trying to async map a buffer. #[derive(Clone, PartialEq, Eq, Debug)] @@ -1574,7 +1584,7 @@ impl Display for BufferAsyncError { } } -impl Error for BufferAsyncError {} +impl error::Error for BufferAsyncError {} /// Type of buffer mapping. #[derive(Debug, Clone, Copy, PartialEq)] @@ -2610,3 +2620,37 @@ impl SwapChain { } } } + +/// Type for the callback of uncaptured error handler +pub trait UncapturedErrorHandler: Fn(Error) + Send + Sync + 'static {} +impl UncapturedErrorHandler for T where T: Fn(Error) + Send + Sync + 'static {} + +/// Error type +#[derive(Debug)] +pub enum Error { + /// Out of memory error + OutOfMemoryError, + /// Validation error, signifying a bug in code or data + ValidationError { + /// + source: Box, + }, +} + +impl error::Error for Error { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Error::OutOfMemoryError => None, + Error::ValidationError { source } => Some(source.as_ref()), + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::OutOfMemoryError => f.write_str("Out of Memory"), + Error::ValidationError { .. } => f.write_str("Validation error"), + } + } +}