mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
Error scopes API (#2299)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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: | | |
|
||||
|
||||
@@ -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<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,
|
||||
@@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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::<wgc::device::DeviceError>()
|
||||
{
|
||||
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<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(
|
||||
@@ -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<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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)]
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
@@ -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())
|
||||
@@ -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
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
@@ -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(
|
||||
@@ -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<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)
|
||||
@@ -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>,
|
||||
///
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user