Skip to content

Commit a1bd6a8

Browse files
committed
Breaking: Change type of create_surface() functions to expose canvas errors.
Part of fixing #3017.
1 parent 0a4edd3 commit a1bd6a8

File tree

9 files changed

+112
-55
lines changed

9 files changed

+112
-55
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ Bottom level categories:
4040

4141
## Unreleased
4242

43+
### Major changes
44+
45+
- `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/)
46+
4347
### Changes
4448

4549
#### General

wgpu-core/src/instance.rs

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -529,45 +529,53 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
529529
&self,
530530
canvas: &web_sys::HtmlCanvasElement,
531531
id_in: Input<G, SurfaceId>,
532-
) -> SurfaceId {
532+
) -> Result<SurfaceId, hal::InstanceError> {
533533
profiling::scope!("Instance::create_surface_webgl_canvas");
534534

535535
let surface = Surface {
536536
presentation: None,
537-
gl: self.instance.gl.as_ref().map(|inst| HalSurface {
538-
raw: {
539-
inst.create_surface_from_canvas(canvas)
540-
.expect("Create surface from canvas")
541-
},
542-
}),
537+
gl: self
538+
.instance
539+
.gl
540+
.as_ref()
541+
.map(|inst| {
542+
Ok(HalSurface {
543+
raw: inst.create_surface_from_canvas(canvas)?,
544+
})
545+
})
546+
.transpose()?,
543547
};
544548

545549
let mut token = Token::root();
546550
let id = self.surfaces.prepare(id_in).assign(surface, &mut token);
547-
id.0
551+
Ok(id.0)
548552
}
549553

550554
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
551555
pub fn create_surface_webgl_offscreen_canvas(
552556
&self,
553557
canvas: &web_sys::OffscreenCanvas,
554558
id_in: Input<G, SurfaceId>,
555-
) -> SurfaceId {
559+
) -> Result<SurfaceId, hal::InstanceError> {
556560
profiling::scope!("Instance::create_surface_webgl_offscreen_canvas");
557561

558562
let surface = Surface {
559563
presentation: None,
560-
gl: self.instance.gl.as_ref().map(|inst| HalSurface {
561-
raw: {
562-
inst.create_surface_from_offscreen_canvas(canvas)
563-
.expect("Create surface from offscreen canvas")
564-
},
565-
}),
564+
gl: self
565+
.instance
566+
.gl
567+
.as_ref()
568+
.map(|inst| {
569+
Ok(HalSurface {
570+
raw: inst.create_surface_from_offscreen_canvas(canvas)?,
571+
})
572+
})
573+
.transpose()?,
566574
};
567575

568576
let mut token = Token::root();
569577
let id = self.surfaces.prepare(id_in).assign(surface, &mut token);
570-
id.0
578+
Ok(id.0)
571579
}
572580

573581
#[cfg(dx12)]

wgpu/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ optional = true
100100
[dependencies.wgt]
101101
workspace = true
102102

103-
[target.'cfg(any(not(target_arch = "wasm32"), target_os = "emscripten"))'.dependencies.hal]
103+
[dependencies.hal]
104104
workspace = true
105105

106106
[dependencies]

wgpu/examples/framework.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ async fn setup<E: Example>(title: &str) -> Setup {
164164
let size = window.inner_size();
165165

166166
#[cfg(not(target_arch = "wasm32"))]
167-
let surface = instance.create_surface(&window);
167+
let surface = instance.create_surface(&window).unwrap();
168168
#[cfg(target_arch = "wasm32")]
169169
let surface = {
170170
if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup {
@@ -174,7 +174,8 @@ async fn setup<E: Example>(title: &str) -> Setup {
174174
} else {
175175
instance.create_surface(&window)
176176
}
177-
};
177+
}
178+
.unwrap();
178179

179180
(size, surface)
180181
};

wgpu/examples/hello-triangle/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use winit::{
88
async fn run(event_loop: EventLoop<()>, window: Window) {
99
let size = window.inner_size();
1010
let instance = wgpu::Instance::new(wgpu::Backends::all());
11-
let surface = unsafe { instance.create_surface(&window) };
11+
let surface = unsafe { instance.create_surface(&window) }.unwrap();
1212
let adapter = instance
1313
.request_adapter(&wgpu::RequestAdapterOptions {
1414
power_preference: wgpu::PowerPreference::default(),

wgpu/examples/hello-windows/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ struct Viewport {
2020

2121
impl ViewportDesc {
2222
fn new(window: Window, background: wgpu::Color, instance: &wgpu::Instance) -> Self {
23-
let surface = unsafe { instance.create_surface(&window) };
23+
let surface = unsafe { instance.create_surface(&window) }.unwrap();
2424
Self {
2525
window,
2626
background,

wgpu/src/backend/direct.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -220,24 +220,30 @@ impl Context {
220220
pub fn instance_create_surface_from_canvas(
221221
self: &Arc<Self>,
222222
canvas: &web_sys::HtmlCanvasElement,
223-
) -> Surface {
224-
let id = self.0.create_surface_webgl_canvas(canvas, ());
225-
Surface {
223+
) -> Result<Surface, crate::CreateSurfaceError> {
224+
let id = self
225+
.0
226+
.create_surface_webgl_canvas(canvas, ())
227+
.map_err(|hal::InstanceError| crate::CreateSurfaceError {})?;
228+
Ok(Surface {
226229
id,
227230
configured_device: Mutex::default(),
228-
}
231+
})
229232
}
230233

231234
#[cfg(all(target_arch = "wasm32", feature = "webgl", not(feature = "emscripten")))]
232235
pub fn instance_create_surface_from_offscreen_canvas(
233236
self: &Arc<Self>,
234237
canvas: &web_sys::OffscreenCanvas,
235-
) -> Surface {
236-
let id = self.0.create_surface_webgl_offscreen_canvas(canvas, ());
237-
Surface {
238+
) -> Result<Surface, crate::CreateSurfaceError> {
239+
let id = self
240+
.0
241+
.create_surface_webgl_offscreen_canvas(canvas, ())
242+
.map_err(|hal::InstanceError| crate::CreateSurfaceError {})?;
243+
Ok(Surface {
238244
id,
239245
configured_device: Mutex::default(),
240-
}
246+
})
241247
}
242248

243249
#[cfg(target_os = "windows")]
@@ -944,13 +950,13 @@ impl crate::Context for Context {
944950
&self,
945951
display_handle: raw_window_handle::RawDisplayHandle,
946952
window_handle: raw_window_handle::RawWindowHandle,
947-
) -> Self::SurfaceId {
948-
Surface {
953+
) -> Result<Self::SurfaceId, crate::CreateSurfaceError> {
954+
Ok(Surface {
949955
id: self
950956
.0
951957
.instance_create_surface(display_handle, window_handle, ()),
952958
configured_device: Mutex::new(None),
953-
}
959+
})
954960
}
955961

956962
fn instance_request_adapter(

wgpu/src/backend/web.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,17 +1026,15 @@ impl Context {
10261026
pub fn instance_create_surface_from_canvas(
10271027
&self,
10281028
canvas: &web_sys::HtmlCanvasElement,
1029-
) -> <Self as crate::Context>::SurfaceId {
1029+
) -> Result<<Self as crate::Context>::SurfaceId, crate::CreateSurfaceError> {
10301030
self.create_surface_from_context(canvas.get_context("webgpu"))
1031-
.unwrap()
10321031
}
10331032

10341033
pub fn instance_create_surface_from_offscreen_canvas(
10351034
&self,
10361035
canvas: &web_sys::OffscreenCanvas,
1037-
) -> <Self as crate::Context>::SurfaceId {
1036+
) -> Result<<Self as crate::Context>::SurfaceId, crate::CreateSurfaceError> {
10381037
self.create_surface_from_context(canvas.get_context("webgpu"))
1039-
.unwrap()
10401038
}
10411039

10421040
/// Common portion of public `instance_create_surface_from_*` functions.
@@ -1046,7 +1044,7 @@ impl Context {
10461044
fn create_surface_from_context(
10471045
&self,
10481046
context_result: Result<Option<js_sys::Object>, wasm_bindgen::JsValue>,
1049-
) -> Result<<Self as crate::Context>::SurfaceId, ()> {
1047+
) -> Result<<Self as crate::Context>::SurfaceId, crate::CreateSurfaceError> {
10501048
let context: js_sys::Object = match context_result {
10511049
Ok(Some(context)) => context,
10521050
Ok(None) => {
@@ -1056,7 +1054,7 @@ impl Context {
10561054
// “not supported” could include “insufficient GPU resources” or “the GPU process
10571055
// previously crashed”. So, we must return it as an `Err` since it could occur
10581056
// for circumstances outside the application author's control.
1059-
return Err(());
1057+
return Err(crate::CreateSurfaceError {});
10601058
}
10611059
Err(js_error) => {
10621060
// <https://html.spec.whatwg.org/multipage/canvas.html#dom-canvas-getcontext>
@@ -1171,7 +1169,7 @@ impl crate::Context for Context {
11711169
&self,
11721170
_display_handle: raw_window_handle::RawDisplayHandle,
11731171
window_handle: raw_window_handle::RawWindowHandle,
1174-
) -> Self::SurfaceId {
1172+
) -> Result<Self::SurfaceId, crate::CreateSurfaceError> {
11751173
let canvas_attribute = match window_handle {
11761174
raw_window_handle::RawWindowHandle::Web(web_handle) => web_handle.id,
11771175
_ => panic!("expected valid handle for canvas"),

wgpu/src/lib.rs

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ trait Context: Debug + Send + Sized + Sync {
204204
&self,
205205
display_handle: raw_window_handle::RawDisplayHandle,
206206
window_handle: raw_window_handle::RawWindowHandle,
207-
) -> Self::SurfaceId;
207+
) -> Result<Self::SurfaceId, crate::CreateSurfaceError>;
208208
fn instance_request_adapter(
209209
&self,
210210
options: &RequestAdapterOptions<'_>,
@@ -1826,23 +1826,34 @@ impl Instance {
18261826
///
18271827
/// # Safety
18281828
///
1829-
/// - Raw Window Handle must be a valid object to create a surface upon and
1830-
/// must remain valid for the lifetime of the returned surface.
1831-
/// - If not called on the main thread, metal backend will panic.
1829+
/// - `raw_window_handle` must be a valid object to create a surface upon.
1830+
/// - `raw_window_handle` must remain valid until after the returned [`Surface`] is
1831+
/// dropped.
1832+
///
1833+
/// # Errors
1834+
///
1835+
/// - On WebGL 2: Will return an error if the browser does not support WebGL 2,
1836+
/// or declines to provide GPU access (such as due to a resource shortage).
1837+
///
1838+
/// # Panics
1839+
///
1840+
/// - On macOS/Metal: will panic if not called on the main thread.
1841+
/// - On web: will panic if the `raw_window_handle` does not properly refer to a
1842+
/// canvas element.
18321843
pub unsafe fn create_surface<
18331844
W: raw_window_handle::HasRawWindowHandle + raw_window_handle::HasRawDisplayHandle,
18341845
>(
18351846
&self,
18361847
window: &W,
1837-
) -> Surface {
1838-
Surface {
1848+
) -> Result<Surface, CreateSurfaceError> {
1849+
Ok(Surface {
18391850
context: Arc::clone(&self.context),
18401851
id: Context::instance_create_surface(
18411852
&*self.context,
18421853
raw_window_handle::HasRawDisplayHandle::raw_display_handle(window),
18431854
raw_window_handle::HasRawWindowHandle::raw_window_handle(window),
1844-
),
1845-
}
1855+
)?,
1856+
})
18461857
}
18471858

18481859
/// Creates a surface from `CoreAnimationLayer`.
@@ -1872,29 +1883,42 @@ impl Instance {
18721883
///
18731884
/// The `canvas` argument must be a valid `<canvas>` element to
18741885
/// create a surface upon.
1886+
///
1887+
/// # Errors
1888+
///
1889+
/// - On WebGL 2: Will return an error if the browser does not support WebGL 2,
1890+
/// or declines to provide GPU access (such as due to a resource shortage).
18751891
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
1876-
pub fn create_surface_from_canvas(&self, canvas: &web_sys::HtmlCanvasElement) -> Surface {
1877-
Surface {
1892+
pub fn create_surface_from_canvas(
1893+
&self,
1894+
canvas: &web_sys::HtmlCanvasElement,
1895+
) -> Result<Surface, CreateSurfaceError> {
1896+
Ok(Surface {
18781897
context: Arc::clone(&self.context),
1879-
id: self.context.instance_create_surface_from_canvas(canvas),
1880-
}
1898+
id: self.context.instance_create_surface_from_canvas(canvas)?,
1899+
})
18811900
}
18821901

18831902
/// Creates a surface from a `web_sys::OffscreenCanvas`.
18841903
///
18851904
/// The `canvas` argument must be a valid `OffscreenCanvas` object
18861905
/// to create a surface upon.
1906+
///
1907+
/// # Errors
1908+
///
1909+
/// - On WebGL 2: Will return an error if the browser does not support WebGL 2,
1910+
/// or declines to provide GPU access (such as due to a resource shortage).
18871911
#[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))]
18881912
pub fn create_surface_from_offscreen_canvas(
18891913
&self,
18901914
canvas: &web_sys::OffscreenCanvas,
1891-
) -> Surface {
1892-
Surface {
1915+
) -> Result<Surface, CreateSurfaceError> {
1916+
Ok(Surface {
18931917
context: Arc::clone(&self.context),
18941918
id: self
18951919
.context
1896-
.instance_create_surface_from_offscreen_canvas(canvas),
1897-
}
1920+
.instance_create_surface_from_offscreen_canvas(canvas)?,
1921+
})
18981922
}
18991923

19001924
/// Polls all devices.
@@ -2363,6 +2387,22 @@ impl Display for RequestDeviceError {
23632387

23642388
impl error::Error for RequestDeviceError {}
23652389

2390+
/// [`Instance::create_surface()`] or a related function failed.
2391+
#[derive(Clone, PartialEq, Eq, Debug)]
2392+
#[non_exhaustive]
2393+
pub struct CreateSurfaceError {
2394+
// TODO: Report diagnostic clues
2395+
}
2396+
static_assertions::assert_impl_all!(CreateSurfaceError: Send, Sync);
2397+
2398+
impl Display for CreateSurfaceError {
2399+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2400+
write!(f, "Creating a surface failed")
2401+
}
2402+
}
2403+
2404+
impl error::Error for CreateSurfaceError {}
2405+
23662406
/// Error occurred when trying to async map a buffer.
23672407
#[derive(Clone, PartialEq, Eq, Debug)]
23682408
pub struct BufferAsyncError;

0 commit comments

Comments
 (0)