547: Implement on_uncaptured_error  r=kvark,cwfitzgerald a=scoopr

This is very crude at the moment, I'm mostly just checking if the directions is at all viable.

So I set out to implement `on_caught_error`, focusing solely on the direct backend. It just accepts a `Fn(GPUError)` (and I named it GPUError because I didn't yet want to rename the import of `std::error::Error`).

I store the handler in a `ErrorSinkRaw` struct, which is then wrapped in Arc+Mutex, with the idea that other resources can point to the same Arc, so when `Device::on_uncaught_error` is called mid application, all resources will point to the right place. Otherwise I think something like `Buffer::unmap` would have to find the `Device` it was created with, to find the uncaught handler.

Now I store the `ErrorSink` in the `Device` struct, because it is a convenient place to hold it. But I guess it is a bit nonsensical with the web backend, so either it would need to be `cfg(not(wasm32))`, or move the whole thing in the backend side, but in the direct backend, I don't see a convenient place to store the closure. But the unwrapping is done in direct backend, so it can't really be moved up in to wgpu-core either, unless the error handling overall is moved there.

Also related, I'm now passing the `error_sink` argument to the few methods where I implemented the error handling, which I think is a bit ugly, but it works.

Co-authored-by: Mikko Lehtonen <scoopr@iki.fi>
This commit is contained in:
bors[bot]
2020-09-05 16:02:20 +00:00
committed by GitHub
3 changed files with 197 additions and 45 deletions

View File

@@ -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<wgc::hub::IdentityManagerFactory>);
@@ -497,9 +501,15 @@ fn map_pass_channel<V: Copy + Default>(
}
}
#[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::<Vec<_>>();
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::<ArrayVec<[_; wgc::MAX_BIND_GROUPS]>>();
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<T> {
fn unwrap_pretty(self) -> T;
fn unwrap_error_sink(self, error_sink: &ErrorSink, fallback: impl FnOnce() -> T) -> T;
}
impl<T, E> PrettyResult<T> for Result<T, E>
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<Mutex<ErrorSinkRaw>>;
struct ErrorSinkRaw {
uncaptured_handler: Box<dyn crate::UncapturedErrorHandler>,
}
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");
}

View File

@@ -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,

View File

@@ -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<T> 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<dyn error::Error + Send + Sync + 'static>,
},
}
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"),
}
}
}