Skip to content

Commit

Permalink
Error scopes API (#2299)
Browse files Browse the repository at this point in the history
  • Loading branch information
kvark authored Dec 17, 2021
1 parent 18f644e commit ec1d022
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 25 deletions.
3 changes: 2 additions & 1 deletion run-wasm-example.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions wgpu/examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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: | | |
Expand Down
25 changes: 23 additions & 2 deletions wgpu/examples/cube/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -83,6 +83,23 @@ fn create_texels(size: usize) -> Vec<u8> {
.collect()
}

// This can be done simpler with `FutureExt`, but we don't want to add
// a dependency just for this small case.
struct ErrorFuture<F> {
inner: F,
}
impl<F: Future<Output = Option<wgpu::Error>>> Future for ErrorFuture<F> {
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,
Expand Down Expand Up @@ -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 });
{
Expand Down Expand Up @@ -372,6 +390,9 @@ impl framework::Example for Example {
}

queue.submit(Some(encoder.finish()));
spawner.spawn_local(ErrorFuture {
inner: device.pop_error_scope(),
});
}
}

Expand Down
50 changes: 45 additions & 5 deletions wgpu/src/backend/direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,21 @@ 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::<wgc::device::DeviceError>()
{
return sink.handle_error(crate::Error::OutOfMemoryError {
return sink.handle_error(crate::Error::OutOfMemory {
source: Box::new(error),
});
}
source_opt = source.source();
}

// 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),
});
Expand Down Expand Up @@ -768,6 +768,7 @@ impl crate::Context for Context {
Ready<Result<(Self::DeviceId, Self::QueueId), crate::RequestDeviceError>>;
type MapAsyncFuture = native_gpu_future::GpuFuture<Result<(), crate::BufferAsyncError>>;
type OnSubmittedWorkDoneFuture = native_gpu_future::GpuFuture<()>;
type PopErrorScopeFuture = Ready<Option<crate::Error>>;

fn init(backends: wgt::Backends) -> Self {
Self(wgc::hub::Global::new(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -2206,19 +2221,44 @@ pub(crate) struct SurfaceOutputDetail {

type ErrorSink = Arc<Mutex<ErrorSinkRaw>>;

struct ErrorScope {
error: Option<crate::Error>,
filter: crate::ErrorFilter,
}

struct ErrorSinkRaw {
scopes: Vec<ErrorScope>,
uncaptured_handler: Box<dyn crate::UncapturedErrorHandler>,
}

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);
}
}
}
}

Expand Down
57 changes: 46 additions & 11 deletions wgpu/src/backend/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ impl fmt::Debug for Context {
}
}

impl crate::Error {
fn from_js(js_error: js_sys::Object) -> Self {
let source = Box::<dyn std::error::Error + Send + Sync>::from("<WebGPU Error>");
if let Some(js_error) = js_error.dyn_ref::<web_sys::GpuValidationError>() {
crate::Error::Validation {
source,
description: js_error.message(),
}
} else if js_error.has_type::<web_sys::GpuOutOfMemoryError>() {
crate::Error::OutOfMemory { source }
} else {
panic!("Unexpected error");
}
}
}

#[derive(Debug)]
pub(crate) struct ComputePass(web_sys::GpuComputePassEncoder);
#[derive(Debug)]
Expand Down Expand Up @@ -901,6 +917,7 @@ fn future_request_adapter(result: JsFutureResult) -> Option<Sendable<web_sys::Gp
Err(_) => None,
}
}

fn future_request_device(
result: JsFutureResult,
) -> Result<(Sendable<web_sys::GpuDevice>, Sendable<web_sys::GpuQueue>), crate::RequestDeviceError>
Expand All @@ -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<crate::Error> {
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,
Expand Down Expand Up @@ -981,6 +1008,8 @@ impl crate::Context for Context {
>;
type OnSubmittedWorkDoneFuture =
MakeSendFuture<wasm_bindgen_futures::JsFuture, fn(JsFutureResult) -> ()>;
type PopErrorScopeFuture =
MakeSendFuture<wasm_bindgen_futures::JsFuture, fn(JsFutureResult) -> Option<crate::Error>>;

fn init(_backends: wgt::Backends) -> Self {
Context(web_sys::window().unwrap().navigator().gpu())
Expand Down Expand Up @@ -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::<dyn std::error::Error + Send + Sync>::from("<WebGPU Error>");
if let Some(js_error) = js_error.dyn_ref::<web_sys::GpuValidationError>() {
handler(crate::Error::ValidationError {
source,
description: js_error.message(),
});
} else if js_error.has_type::<web_sys::GpuOutOfMemoryError>() {
handler(crate::Error::OutOfMemoryError { source });
}
let error = crate::Error::from_js(event.error());
handler(error);
}) as Box<dyn FnMut(_)>);
device
.0
Expand All @@ -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,
Expand Down
34 changes: 28 additions & 6 deletions wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ctx: Context> {
fn set_pipeline(&mut self, pipeline: &Ctx::ComputePipelineId);
fn set_bind_group(
Expand Down Expand Up @@ -183,6 +192,7 @@ trait Context: Debug + Send + Sized + Sync {
+ Send;
type MapAsyncFuture: Future<Output = Result<(), BufferAsyncError>> + Send;
type OnSubmittedWorkDoneFuture: Future<Output = ()> + Send;
type PopErrorScopeFuture: Future<Output = Option<Error>> + Send;

fn init(backends: Backends) -> Self;
fn instance_create_surface(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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<Output = Option<Error>> + 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)
Expand Down Expand Up @@ -3288,12 +3310,12 @@ impl<T> UncapturedErrorHandler for T where T: Fn(Error) + Send + 'static {}
#[derive(Debug)]
pub enum Error {
/// Out of memory error
OutOfMemoryError {
OutOfMemory {
///
source: Box<dyn error::Error + Send + 'static>,
},
/// Validation error, signifying a bug in code or data
ValidationError {
Validation {
///
source: Box<dyn error::Error + Send + 'static>,
///
Expand All @@ -3304,17 +3326,17 @@ 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()),
}
}
}

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),
}
}
}

0 comments on commit ec1d022

Please sign in to comment.