diff --git a/run-wasm-example.sh b/run-wasm-example.sh index 2265e74eb5..7aa5085c49 100755 --- a/run-wasm-example.sh +++ b/run-wasm-example.sh @@ -3,7 +3,8 @@ set -e echo "Compiling..." -cargo build --example $1 --target wasm32-unknown-unknown --features webgl +RUSTFLAGS=--cfg=web_sys_unstable_apis +cargo build --example $1 --target wasm32-unknown-unknown --features "$2" echo "Generating bindings..." mkdir -p target/wasm-examples/$1 diff --git a/wgpu/examples/README.md b/wgpu/examples/README.md index ad7b342fa2..4a42e8f4e6 100644 --- a/wgpu/examples/README.md +++ b/wgpu/examples/README.md @@ -34,6 +34,7 @@ All framework-based examples render to the window and are reftested against the | blending | | :star: | :star: | | | | | | :star: | | | render bundles | | | | | :star: | | | | :star: | | | compute passes | :star: | | | | | | | | | | +| error scopes | | | :star: | | | | | | | | | *optional extensions* | | | | | | | | :star: | | | | - SPIR-V shaders | | | | | | | | :star: | | | | - binding indexing | | | | | | | | :star: | | | diff --git a/wgpu/examples/cube/main.rs b/wgpu/examples/cube/main.rs index f06c5643f5..60a4954270 100644 --- a/wgpu/examples/cube/main.rs +++ b/wgpu/examples/cube/main.rs @@ -2,7 +2,7 @@ mod framework; use bytemuck::{Pod, Zeroable}; -use std::{borrow::Cow, mem}; +use std::{borrow::Cow, future::Future, mem, pin::Pin, task}; use wgpu::util::DeviceExt; #[repr(C)] @@ -83,6 +83,23 @@ fn create_texels(size: usize) -> Vec { .collect() } +// This can be done simpler with `FutureExt`, but we don't want to add +// a dependency just for this small case. +struct ErrorFuture { + inner: F, +} +impl>> Future for ErrorFuture { + type Output = (); + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll<()> { + let inner = unsafe { self.map_unchecked_mut(|me| &mut me.inner) }; + inner.poll(cx).map(|error| { + if let Some(e) = error { + panic!("Rendering {}", e); + } + }) + } +} + struct Example { vertex_buf: wgpu::Buffer, index_buf: wgpu::Buffer, @@ -335,8 +352,9 @@ impl framework::Example for Example { view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue, - _spawner: &framework::Spawner, + spawner: &framework::Spawner, ) { + device.push_error_scope(wgpu::ErrorFilter::Validation); let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); { @@ -372,6 +390,9 @@ impl framework::Example for Example { } queue.submit(Some(encoder.finish())); + spawner.spawn_local(ErrorFuture { + inner: device.pop_error_scope(), + }); } } diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 7519ad3d29..aa1c990d1d 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -177,13 +177,13 @@ impl Context { label: label.unwrap_or_default().to_string(), label_key, }; - let sink = sink_mutex.lock(); + let mut sink = sink_mutex.lock(); let mut source_opt: Option<&(dyn Error + 'static)> = Some(&error); while let Some(source) = source_opt { if let Some(wgc::device::DeviceError::OutOfMemory) = source.downcast_ref::() { - return sink.handle_error(crate::Error::OutOfMemoryError { + return sink.handle_error(crate::Error::OutOfMemory { source: Box::new(error), }); } @@ -191,7 +191,7 @@ impl Context { } // Otherwise, it is a validation error - sink.handle_error(crate::Error::ValidationError { + sink.handle_error(crate::Error::Validation { description: self.format_error(&error), source: Box::new(error), }); @@ -768,6 +768,7 @@ impl crate::Context for Context { Ready>; type MapAsyncFuture = native_gpu_future::GpuFuture>; type OnSubmittedWorkDoneFuture = native_gpu_future::GpuFuture<()>; + type PopErrorScopeFuture = Ready>; fn init(backends: wgt::Backends) -> Self { Self(wgc::hub::Global::new( @@ -1567,6 +1568,20 @@ impl crate::Context for Context { error_sink.uncaptured_handler = Box::new(handler); } + fn device_push_error_scope(&self, device: &Self::DeviceId, filter: crate::ErrorFilter) { + let mut error_sink = device.error_sink.lock(); + error_sink.scopes.push(ErrorScope { + error: None, + filter, + }); + } + + fn device_pop_error_scope(&self, device: &Self::DeviceId) -> Self::PopErrorScopeFuture { + let mut error_sink = device.error_sink.lock(); + let scope = error_sink.scopes.pop().unwrap(); + ready(scope.error) + } + fn buffer_map_async( &self, buffer: &Self::BufferId, @@ -2206,19 +2221,44 @@ pub(crate) struct SurfaceOutputDetail { type ErrorSink = Arc>; +struct ErrorScope { + error: Option, + filter: crate::ErrorFilter, +} + struct ErrorSinkRaw { + scopes: Vec, uncaptured_handler: Box, } impl ErrorSinkRaw { fn new() -> ErrorSinkRaw { ErrorSinkRaw { + scopes: Vec::new(), uncaptured_handler: Box::from(default_error_handler), } } - fn handle_error(&self, err: crate::Error) { - (self.uncaptured_handler)(err); + fn handle_error(&mut self, err: crate::Error) { + let filter = match err { + crate::Error::OutOfMemory { .. } => crate::ErrorFilter::OutOfMemory, + crate::Error::Validation { .. } => crate::ErrorFilter::Validation, + }; + match self + .scopes + .iter_mut() + .rev() + .find(|scope| scope.filter == filter) + { + Some(scope) => { + if scope.error.is_none() { + scope.error = Some(err); + } + } + None => { + (self.uncaptured_handler)(err); + } + } } } diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index 19f9517c0a..e783db9eeb 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -32,6 +32,22 @@ impl fmt::Debug for Context { } } +impl crate::Error { + fn from_js(js_error: js_sys::Object) -> Self { + let source = Box::::from(""); + if let Some(js_error) = js_error.dyn_ref::() { + crate::Error::Validation { + source, + description: js_error.message(), + } + } else if js_error.has_type::() { + crate::Error::OutOfMemory { source } + } else { + panic!("Unexpected error"); + } + } +} + #[derive(Debug)] pub(crate) struct ComputePass(web_sys::GpuComputePassEncoder); #[derive(Debug)] @@ -901,6 +917,7 @@ fn future_request_adapter(result: JsFutureResult) -> Option None, } } + fn future_request_device( result: JsFutureResult, ) -> Result<(Sendable, Sendable), crate::RequestDeviceError> @@ -918,6 +935,16 @@ fn future_map_async(result: JsFutureResult) -> Result<(), crate::BufferAsyncErro result.map(|_| ()).map_err(|_| crate::BufferAsyncError) } +fn future_pop_error_scope(result: JsFutureResult) -> Option { + match result { + Ok(js_value) if js_value.is_object() => { + let js_error = wasm_bindgen::JsCast::dyn_into(js_value).unwrap(); + Some(crate::Error::from_js(js_error)) + } + _ => None, + } +} + impl Context { pub fn instance_create_surface_from_canvas( &self, @@ -981,6 +1008,8 @@ impl crate::Context for Context { >; type OnSubmittedWorkDoneFuture = MakeSendFuture ()>; + type PopErrorScopeFuture = + MakeSendFuture Option>; fn init(_backends: wgt::Backends) -> Self { Context(web_sys::window().unwrap().navigator().gpu()) @@ -1665,17 +1694,8 @@ impl crate::Context for Context { handler: impl crate::UncapturedErrorHandler, ) { let f = Closure::wrap(Box::new(move |event: web_sys::GpuUncapturedErrorEvent| { - // Convert the JS error into a wgpu error. - let js_error = event.error(); - let source = Box::::from(""); - if let Some(js_error) = js_error.dyn_ref::() { - handler(crate::Error::ValidationError { - source, - description: js_error.message(), - }); - } else if js_error.has_type::() { - handler(crate::Error::OutOfMemoryError { source }); - } + let error = crate::Error::from_js(event.error()); + handler(error); }) as Box); device .0 @@ -1684,6 +1704,21 @@ impl crate::Context for Context { f.forget(); } + fn device_push_error_scope(&self, device: &Self::DeviceId, filter: crate::ErrorFilter) { + device.0.push_error_scope(match filter { + crate::ErrorFilter::OutOfMemory => web_sys::GpuErrorFilter::OutOfMemory, + crate::ErrorFilter::Validation => web_sys::GpuErrorFilter::Validation, + }); + } + + fn device_pop_error_scope(&self, device: &Self::DeviceId) -> Self::PopErrorScopeFuture { + let error_promise = device.0.pop_error_scope(); + MakeSendFuture::new( + wasm_bindgen_futures::JsFuture::from(error_promise), + future_pop_error_scope, + ) + } + fn buffer_map_async( &self, buffer: &Self::BufferId, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 1722d78a55..bffc6f5072 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -44,6 +44,15 @@ pub use wgt::{ use backend::{BufferMappedRange, Context as C}; +/// Filter for error scopes. +#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)] +pub enum ErrorFilter { + /// Catch only out-of-memory errors. + OutOfMemory, + /// Catch only validation errors. + Validation, +} + trait ComputePassInner { fn set_pipeline(&mut self, pipeline: &Ctx::ComputePipelineId); fn set_bind_group( @@ -183,6 +192,7 @@ trait Context: Debug + Send + Sized + Sync { + Send; type MapAsyncFuture: Future> + Send; type OnSubmittedWorkDoneFuture: Future + Send; + type PopErrorScopeFuture: Future> + Send; fn init(backends: Backends) -> Self; fn instance_create_surface( @@ -317,6 +327,8 @@ trait Context: Debug + Send + Sized + Sync { device: &Self::DeviceId, handler: impl UncapturedErrorHandler, ); + fn device_push_error_scope(&self, device: &Self::DeviceId, filter: ErrorFilter); + fn device_pop_error_scope(&self, device: &Self::DeviceId) -> Self::PopErrorScopeFuture; fn buffer_map_async( &self, @@ -1877,6 +1889,16 @@ impl Device { self.context.device_on_uncaptured_error(&self.id, handler); } + /// Push an error scope. + pub fn push_error_scope(&self, filter: ErrorFilter) { + self.context.device_push_error_scope(&self.id, filter); + } + + /// Pop an error scope. + pub fn pop_error_scope(&self) -> impl Future> + Send { + self.context.device_pop_error_scope(&self.id) + } + /// Starts frame capture. pub fn start_capture(&self) { Context::device_start_capture(&*self.context, &self.id) @@ -3288,12 +3310,12 @@ impl UncapturedErrorHandler for T where T: Fn(Error) + Send + 'static {} #[derive(Debug)] pub enum Error { /// Out of memory error - OutOfMemoryError { + OutOfMemory { /// source: Box, }, /// Validation error, signifying a bug in code or data - ValidationError { + Validation { /// source: Box, /// @@ -3304,8 +3326,8 @@ pub enum Error { impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { match self { - Error::OutOfMemoryError { source } => Some(source.as_ref()), - Error::ValidationError { source, .. } => Some(source.as_ref()), + Error::OutOfMemory { source } => Some(source.as_ref()), + Error::Validation { source, .. } => Some(source.as_ref()), } } } @@ -3313,8 +3335,8 @@ impl error::Error for Error { 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 { description, .. } => f.write_str(description), + Error::OutOfMemory { .. } => f.write_str("Out of Memory"), + Error::Validation { description, .. } => f.write_str(description), } } }