mirror of
https://github.com/gfx-rs/wgpu.git
synced 2026-04-22 03:02:01 -04:00
[rs] Merge #547
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:
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user