mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-11-25 16:24:24 +00:00
Return an error instead of panicking when canvas context is unavailable (#3052)
* Low-level error handling in canvas context creation (for WebGPU and WebGL 2). Part of fixing #3017. This commit changes the handling of `web_sys` errors and nulls from `expect()` to returning `Err`, but it doesn't actually affect the public API — that will be done in the next commit. * Breaking: Change type of `create_surface()` functions to expose canvas errors. Part of fixing #3017.
This commit is contained in:
parent
7960c5e154
commit
2209463a54
@ -63,6 +63,10 @@ Additionally `Surface::get_default_config` now returns an Option and returns Non
|
||||
+ let config = surface.get_default_config(&adapter).expect("Surface unsupported by adapter");
|
||||
```
|
||||
|
||||
#### Fallible surface creation
|
||||
|
||||
`Instance::create_surface()` now returns `Result<Surface, CreateSurfaceError>` instead of `Surface`. This allows an error to be returned instead of panicking if the given window is a HTML canvas and obtaining a WebGPU or WebGL 2 context fails. (No other platforms currently report any errors through this path.) By @kpreid in [#3052](https://github.com/gfx-rs/wgpu/pull/3052/)
|
||||
|
||||
### Changes
|
||||
|
||||
#### General
|
||||
|
@ -502,22 +502,26 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
&self,
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
id_in: Input<G, SurfaceId>,
|
||||
) -> SurfaceId {
|
||||
) -> Result<SurfaceId, hal::InstanceError> {
|
||||
profiling::scope!("Instance::create_surface_webgl_canvas");
|
||||
|
||||
let surface = Surface {
|
||||
presentation: None,
|
||||
gl: self.instance.gl.as_ref().map(|inst| HalSurface {
|
||||
raw: {
|
||||
inst.create_surface_from_canvas(canvas)
|
||||
.expect("Create surface from canvas")
|
||||
},
|
||||
}),
|
||||
gl: self
|
||||
.instance
|
||||
.gl
|
||||
.as_ref()
|
||||
.map(|inst| {
|
||||
Ok(HalSurface {
|
||||
raw: inst.create_surface_from_canvas(canvas)?,
|
||||
})
|
||||
})
|
||||
.transpose()?,
|
||||
};
|
||||
|
||||
let mut token = Token::root();
|
||||
let id = self.surfaces.prepare(id_in).assign(surface, &mut token);
|
||||
id.0
|
||||
Ok(id.0)
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
|
||||
@ -525,22 +529,26 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
|
||||
&self,
|
||||
canvas: &web_sys::OffscreenCanvas,
|
||||
id_in: Input<G, SurfaceId>,
|
||||
) -> SurfaceId {
|
||||
) -> Result<SurfaceId, hal::InstanceError> {
|
||||
profiling::scope!("Instance::create_surface_webgl_offscreen_canvas");
|
||||
|
||||
let surface = Surface {
|
||||
presentation: None,
|
||||
gl: self.instance.gl.as_ref().map(|inst| HalSurface {
|
||||
raw: {
|
||||
inst.create_surface_from_offscreen_canvas(canvas)
|
||||
.expect("Create surface from offscreen canvas")
|
||||
},
|
||||
}),
|
||||
gl: self
|
||||
.instance
|
||||
.gl
|
||||
.as_ref()
|
||||
.map(|inst| {
|
||||
Ok(HalSurface {
|
||||
raw: inst.create_surface_from_offscreen_canvas(canvas)?,
|
||||
})
|
||||
})
|
||||
.transpose()?,
|
||||
};
|
||||
|
||||
let mut token = Token::root();
|
||||
let id = self.surfaces.prepare(id_in).assign(surface, &mut token);
|
||||
id.0
|
||||
Ok(id.0)
|
||||
}
|
||||
|
||||
#[cfg(dx12)]
|
||||
|
@ -33,32 +33,53 @@ impl Instance {
|
||||
&self,
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
) -> Result<Surface, crate::InstanceError> {
|
||||
let webgl2_context = canvas
|
||||
.get_context_with_context_options("webgl2", &Self::create_context_options())
|
||||
.expect("Cannot create WebGL2 context")
|
||||
.and_then(|context| context.dyn_into::<web_sys::WebGl2RenderingContext>().ok())
|
||||
.expect("Cannot convert into WebGL2 context");
|
||||
|
||||
*self.webgl2_context.lock() = Some(webgl2_context.clone());
|
||||
|
||||
Ok(Surface {
|
||||
webgl2_context,
|
||||
srgb_present_program: None,
|
||||
swapchain: None,
|
||||
texture: None,
|
||||
presentable: true,
|
||||
})
|
||||
self.create_surface_from_context(
|
||||
canvas.get_context_with_context_options("webgl2", &Self::create_context_options()),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn create_surface_from_offscreen_canvas(
|
||||
&self,
|
||||
canvas: &web_sys::OffscreenCanvas,
|
||||
) -> Result<Surface, crate::InstanceError> {
|
||||
let webgl2_context = canvas
|
||||
.get_context_with_context_options("webgl2", &Self::create_context_options())
|
||||
.expect("Cannot create WebGL2 context")
|
||||
.and_then(|context| context.dyn_into::<web_sys::WebGl2RenderingContext>().ok())
|
||||
.expect("Cannot convert into WebGL2 context");
|
||||
self.create_surface_from_context(
|
||||
canvas.get_context_with_context_options("webgl2", &Self::create_context_options()),
|
||||
)
|
||||
}
|
||||
|
||||
/// Common portion of public `create_surface_from_*` functions.
|
||||
///
|
||||
/// Note: Analogous code also exists in the WebGPU backend at
|
||||
/// `wgpu::backend::web::Context`.
|
||||
fn create_surface_from_context(
|
||||
&self,
|
||||
context_result: Result<Option<js_sys::Object>, wasm_bindgen::JsValue>,
|
||||
) -> Result<Surface, crate::InstanceError> {
|
||||
let context_object: js_sys::Object = match context_result {
|
||||
Ok(Some(context)) => context,
|
||||
Ok(None) => {
|
||||
// <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev>
|
||||
// A getContext() call “returns null if contextId is not supported, or if the
|
||||
// canvas has already been initialized with another context type”. Additionally,
|
||||
// “not supported” could include “insufficient GPU resources” or “the GPU process
|
||||
// previously crashed”. So, we must return it as an `Err` since it could occur
|
||||
// for circumstances outside the application author's control.
|
||||
return Err(crate::InstanceError);
|
||||
}
|
||||
Err(js_error) => {
|
||||
// <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext>
|
||||
// A thrown exception indicates misuse of the canvas state. Ideally we wouldn't
|
||||
// panic in this case, but for now, `InstanceError` conveys no detail, so it
|
||||
// is more informative to panic with a specific message.
|
||||
panic!("canvas.getContext() threw {js_error:?}")
|
||||
}
|
||||
};
|
||||
|
||||
// Not returning this error because it is a type error that shouldn't happen unless
|
||||
// the browser, JS builtin objects, or wasm bindings are misbehaving somehow.
|
||||
let webgl2_context: web_sys::WebGl2RenderingContext = context_object
|
||||
.dyn_into()
|
||||
.expect("canvas context is not a WebGl2RenderingContext");
|
||||
|
||||
*self.webgl2_context.lock() = Some(webgl2_context.clone());
|
||||
|
||||
|
@ -83,7 +83,7 @@ wgsl = ["wgc?/wgsl"]
|
||||
trace = ["serde", "wgc/trace"]
|
||||
replay = ["serde", "wgc/replay"]
|
||||
angle = ["wgc/angle"]
|
||||
webgl = ["wgc"]
|
||||
webgl = ["hal", "wgc"]
|
||||
emscripten = ["webgl"]
|
||||
vulkan-portability = ["wgc/vulkan-portability"]
|
||||
expose-ids = []
|
||||
@ -100,9 +100,13 @@ optional = true
|
||||
[dependencies.wgt]
|
||||
workspace = true
|
||||
|
||||
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))'.dependencies.hal]
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.hal]
|
||||
workspace = true
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies.hal]
|
||||
workspace = true
|
||||
optional = true
|
||||
|
||||
[dependencies]
|
||||
arrayvec.workspace = true
|
||||
log.workspace = true
|
||||
|
@ -164,7 +164,7 @@ async fn setup<E: Example>(title: &str) -> Setup {
|
||||
let size = window.inner_size();
|
||||
|
||||
#[cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))]
|
||||
let surface = instance.create_surface(&window);
|
||||
let surface = instance.create_surface(&window).unwrap();
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
|
||||
let surface = {
|
||||
if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup {
|
||||
@ -174,7 +174,8 @@ async fn setup<E: Example>(title: &str) -> Setup {
|
||||
} else {
|
||||
instance.create_surface(&window)
|
||||
}
|
||||
};
|
||||
}
|
||||
.unwrap();
|
||||
|
||||
(size, surface)
|
||||
};
|
||||
|
@ -8,7 +8,7 @@ use winit::{
|
||||
async fn run(event_loop: EventLoop<()>, window: Window) {
|
||||
let size = window.inner_size();
|
||||
let instance = wgpu::Instance::new(wgpu::Backends::all());
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
let surface = unsafe { instance.create_surface(&window) }.unwrap();
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference: wgpu::PowerPreference::default(),
|
||||
|
@ -20,7 +20,7 @@ struct Viewport {
|
||||
|
||||
impl ViewportDesc {
|
||||
fn new(window: Window, background: wgpu::Color, instance: &wgpu::Instance) -> Self {
|
||||
let surface = unsafe { instance.create_surface(&window) };
|
||||
let surface = unsafe { instance.create_surface(&window) }.unwrap();
|
||||
Self {
|
||||
window,
|
||||
background,
|
||||
|
@ -219,24 +219,30 @@ impl Context {
|
||||
pub fn instance_create_surface_from_canvas(
|
||||
self: &Arc<Self>,
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
) -> Surface {
|
||||
let id = self.0.create_surface_webgl_canvas(canvas, ());
|
||||
Surface {
|
||||
) -> Result<Surface, crate::CreateSurfaceError> {
|
||||
let id = self
|
||||
.0
|
||||
.create_surface_webgl_canvas(canvas, ())
|
||||
.map_err(|hal::InstanceError| crate::CreateSurfaceError {})?;
|
||||
Ok(Surface {
|
||||
id,
|
||||
configured_device: Mutex::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "webgl", not(feature = "emscripten")))]
|
||||
pub fn instance_create_surface_from_offscreen_canvas(
|
||||
self: &Arc<Self>,
|
||||
canvas: &web_sys::OffscreenCanvas,
|
||||
) -> Surface {
|
||||
let id = self.0.create_surface_webgl_offscreen_canvas(canvas, ());
|
||||
Surface {
|
||||
) -> Result<Surface, crate::CreateSurfaceError> {
|
||||
let id = self
|
||||
.0
|
||||
.create_surface_webgl_offscreen_canvas(canvas, ())
|
||||
.map_err(|hal::InstanceError| crate::CreateSurfaceError {})?;
|
||||
Ok(Surface {
|
||||
id,
|
||||
configured_device: Mutex::default(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
@ -943,13 +949,13 @@ impl crate::Context for Context {
|
||||
&self,
|
||||
display_handle: raw_window_handle::RawDisplayHandle,
|
||||
window_handle: raw_window_handle::RawWindowHandle,
|
||||
) -> Self::SurfaceId {
|
||||
Surface {
|
||||
) -> Result<Self::SurfaceId, crate::CreateSurfaceError> {
|
||||
Ok(Surface {
|
||||
id: self
|
||||
.0
|
||||
.instance_create_surface(display_handle, window_handle, ()),
|
||||
configured_device: Mutex::new(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn instance_request_adapter(
|
||||
|
@ -1026,23 +1026,51 @@ impl Context {
|
||||
pub fn instance_create_surface_from_canvas(
|
||||
&self,
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
) -> <Self as crate::Context>::SurfaceId {
|
||||
let context: wasm_bindgen::JsValue = match canvas.get_context("webgpu") {
|
||||
Ok(Some(ctx)) => ctx.into(),
|
||||
_ => panic!("expected to get context from canvas"),
|
||||
};
|
||||
create_identified(context.into())
|
||||
) -> Result<<Self as crate::Context>::SurfaceId, crate::CreateSurfaceError> {
|
||||
self.create_surface_from_context(canvas.get_context("webgpu"))
|
||||
}
|
||||
|
||||
pub fn instance_create_surface_from_offscreen_canvas(
|
||||
&self,
|
||||
canvas: &web_sys::OffscreenCanvas,
|
||||
) -> <Self as crate::Context>::SurfaceId {
|
||||
let context: wasm_bindgen::JsValue = match canvas.get_context("webgpu") {
|
||||
Ok(Some(ctx)) => ctx.into(),
|
||||
_ => panic!("expected to get context from canvas"),
|
||||
) -> Result<<Self as crate::Context>::SurfaceId, crate::CreateSurfaceError> {
|
||||
self.create_surface_from_context(canvas.get_context("webgpu"))
|
||||
}
|
||||
|
||||
/// Common portion of public `instance_create_surface_from_*` functions.
|
||||
///
|
||||
/// Note: Analogous code also exists in the WebGL2 backend at
|
||||
/// `wgpu_hal::gles::web::Instance`.
|
||||
fn create_surface_from_context(
|
||||
&self,
|
||||
context_result: Result<Option<js_sys::Object>, wasm_bindgen::JsValue>,
|
||||
) -> Result<<Self as crate::Context>::SurfaceId, crate::CreateSurfaceError> {
|
||||
let context: js_sys::Object = match context_result {
|
||||
Ok(Some(context)) => context,
|
||||
Ok(None) => {
|
||||
// <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext-dev>
|
||||
// A getContext() call “returns null if contextId is not supported, or if the
|
||||
// canvas has already been initialized with another context type”. Additionally,
|
||||
// “not supported” could include “insufficient GPU resources” or “the GPU process
|
||||
// previously crashed”. So, we must return it as an `Err` since it could occur
|
||||
// for circumstances outside the application author's control.
|
||||
return Err(crate::CreateSurfaceError {});
|
||||
}
|
||||
Err(js_error) => {
|
||||
// <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext>
|
||||
// A thrown exception indicates misuse of the canvas state. Ideally we wouldn't
|
||||
// panic in this case ... TODO
|
||||
panic!("canvas.getContext() threw {js_error:?}")
|
||||
}
|
||||
};
|
||||
create_identified(context.into())
|
||||
|
||||
// Not returning this error because it is a type error that shouldn't happen unless
|
||||
// the browser, JS builtin objects, or wasm bindings are misbehaving somehow.
|
||||
let context: web_sys::GpuCanvasContext = context
|
||||
.dyn_into()
|
||||
.expect("canvas context is not a GPUCanvasContext");
|
||||
|
||||
Ok(create_identified(context))
|
||||
}
|
||||
|
||||
pub fn queue_copy_external_image_to_texture(
|
||||
@ -1141,7 +1169,7 @@ impl crate::Context for Context {
|
||||
&self,
|
||||
_display_handle: raw_window_handle::RawDisplayHandle,
|
||||
window_handle: raw_window_handle::RawWindowHandle,
|
||||
) -> Self::SurfaceId {
|
||||
) -> Result<Self::SurfaceId, crate::CreateSurfaceError> {
|
||||
let canvas_attribute = match window_handle {
|
||||
raw_window_handle::RawWindowHandle::Web(web_handle) => web_handle.id,
|
||||
_ => panic!("expected valid handle for canvas"),
|
||||
|
@ -204,7 +204,7 @@ trait Context: Debug + Send + Sized + Sync {
|
||||
&self,
|
||||
display_handle: raw_window_handle::RawDisplayHandle,
|
||||
window_handle: raw_window_handle::RawWindowHandle,
|
||||
) -> Self::SurfaceId;
|
||||
) -> Result<Self::SurfaceId, crate::CreateSurfaceError>;
|
||||
fn instance_request_adapter(
|
||||
&self,
|
||||
options: &RequestAdapterOptions<'_>,
|
||||
@ -1840,23 +1840,34 @@ impl Instance {
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - Raw Window Handle must be a valid object to create a surface upon and
|
||||
/// must remain valid for the lifetime of the returned surface.
|
||||
/// - If not called on the main thread, metal backend will panic.
|
||||
/// - `raw_window_handle` must be a valid object to create a surface upon.
|
||||
/// - `raw_window_handle` must remain valid until after the returned [`Surface`] is
|
||||
/// dropped.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - On WebGL2: Will return an error if the browser does not support WebGL2,
|
||||
/// or declines to provide GPU access (such as due to a resource shortage).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - On macOS/Metal: will panic if not called on the main thread.
|
||||
/// - On web: will panic if the `raw_window_handle` does not properly refer to a
|
||||
/// canvas element.
|
||||
pub unsafe fn create_surface<
|
||||
W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle,
|
||||
>(
|
||||
&self,
|
||||
window: &W,
|
||||
) -> Surface {
|
||||
Surface {
|
||||
) -> Result<Surface, CreateSurfaceError> {
|
||||
Ok(Surface {
|
||||
context: Arc::clone(&self.context),
|
||||
id: Context::instance_create_surface(
|
||||
&*self.context,
|
||||
raw_window_handle::HasRawDisplayHandle::raw_display_handle(window),
|
||||
raw_window_handle::HasRawWindowHandle::raw_window_handle(window),
|
||||
),
|
||||
}
|
||||
)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a surface from `CoreAnimationLayer`.
|
||||
@ -1886,29 +1897,42 @@ impl Instance {
|
||||
///
|
||||
/// The `canvas` argument must be a valid `<canvas>` element to
|
||||
/// create a surface upon.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - On WebGL2: Will return an error if the browser does not support WebGL2,
|
||||
/// or declines to provide GPU access (such as due to a resource shortage).
|
||||
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
|
||||
pub fn create_surface_from_canvas(&self, canvas: &web_sys::HtmlCanvasElement) -> Surface {
|
||||
Surface {
|
||||
pub fn create_surface_from_canvas(
|
||||
&self,
|
||||
canvas: &web_sys::HtmlCanvasElement,
|
||||
) -> Result<Surface, CreateSurfaceError> {
|
||||
Ok(Surface {
|
||||
context: Arc::clone(&self.context),
|
||||
id: self.context.instance_create_surface_from_canvas(canvas),
|
||||
}
|
||||
id: self.context.instance_create_surface_from_canvas(canvas)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a surface from a `web_sys::OffscreenCanvas`.
|
||||
///
|
||||
/// The `canvas` argument must be a valid `OffscreenCanvas` object
|
||||
/// to create a surface upon.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - On WebGL2: Will return an error if the browser does not support WebGL2,
|
||||
/// or declines to provide GPU access (such as due to a resource shortage).
|
||||
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
|
||||
pub fn create_surface_from_offscreen_canvas(
|
||||
&self,
|
||||
canvas: &web_sys::OffscreenCanvas,
|
||||
) -> Surface {
|
||||
Surface {
|
||||
) -> Result<Surface, CreateSurfaceError> {
|
||||
Ok(Surface {
|
||||
context: Arc::clone(&self.context),
|
||||
id: self
|
||||
.context
|
||||
.instance_create_surface_from_offscreen_canvas(canvas),
|
||||
}
|
||||
.instance_create_surface_from_offscreen_canvas(canvas)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Polls all devices.
|
||||
@ -2377,6 +2401,22 @@ impl Display for RequestDeviceError {
|
||||
|
||||
impl error::Error for RequestDeviceError {}
|
||||
|
||||
/// [`Instance::create_surface()`] or a related function failed.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
#[non_exhaustive]
|
||||
pub struct CreateSurfaceError {
|
||||
// TODO: Report diagnostic clues
|
||||
}
|
||||
static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync);
|
||||
|
||||
impl Display for CreateSurfaceError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Creating a surface failed")
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for CreateSurfaceError {}
|
||||
|
||||
/// Error occurred when trying to async map a buffer.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct BufferAsyncError;
|
||||
|
Loading…
Reference in New Issue
Block a user