From 4fd4a7142e9d77113fb98a2b69312bc759ab713f Mon Sep 17 00:00:00 2001 From: Andreas Reich Date: Fri, 12 Jan 2024 04:24:01 +0100 Subject: [PATCH] Unify surface creation by introducing new `SurfaceTarget` enum (#4984) --- CHANGELOG.md | 8 + player/src/bin/play.rs | 3 +- tests/src/init.rs | 2 +- tests/tests/create_surface_error.rs | 4 +- wgpu-core/src/instance.rs | 143 ++------- wgpu/src/backend/direct.rs | 116 +++---- wgpu/src/backend/web.rs | 102 +++---- wgpu/src/context.rs | 16 +- wgpu/src/lib.rs | 454 +++++++++++++--------------- 9 files changed, 344 insertions(+), 504 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3bd13e24ce..d7b360b5dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -59,6 +59,14 @@ This feature allowed you to call `global_id` on any wgpu opaque handle to get a Wgpu now exposes backend feature for the Direct3D 12 (`dx12`) and Metal (`metal`) backend. These are enabled by default, but don't do anything when not targetting the corresponding OS. By @daxpedda in [#4815](https://github.com/gfx-rs/wgpu/pull/4815) +### Unified surface creation + +Previously, there were various specialized surface creation functions for various platform specific handles. +Now, `wgpu::Instance::create_surface` & `wgpu::Instance::create_surface_unsafe` instead each take a value that can be converted to the unified `wgpu::SurfaceTarget`/`wgpu::SurfaceTargetUnsafe` enums. +Conversion to `wgpu::SurfaceTarget` is automatic for anything implementing `raw-window-handle`'s `HasWindowHandle` & `HasDisplayHandle` traits, +meaning that you can continue to e.g. pass references to winit windows as before. +By @wumpf in [#4984](https://github.com/gfx-rs/wgpu/pull/4984) + ### New Features #### General diff --git a/player/src/bin/play.rs b/player/src/bin/play.rs index 3a05511b80..cc62010fbb 100644 --- a/player/src/bin/play.rs +++ b/player/src/bin/play.rs @@ -63,7 +63,8 @@ fn main() { window.window_handle().unwrap().into(), wgc::id::TypedId::zip(0, 1, wgt::Backend::Empty), ) - }; + } + .unwrap(); let device = match actions.pop() { Some(trace::Action::Init { desc, backend }) => { diff --git a/tests/src/init.rs b/tests/src/init.rs index 4615516b50..6a24f5faec 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -51,7 +51,7 @@ pub async fn initialize_adapter(adapter_index: usize) -> (Instance, Adapter, Opt let canvas = initialize_html_canvas(); _surface = instance - .create_surface_from_canvas(canvas.clone()) + .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .expect("could not create surface from canvas"); surface_guard = Some(SurfaceGuard { canvas }); diff --git a/tests/tests/create_surface_error.rs b/tests/tests/create_surface_error.rs index 9749642f2f..87aeb15726 100644 --- a/tests/tests/create_surface_error.rs +++ b/tests/tests/create_surface_error.rs @@ -1,6 +1,6 @@ //! Test that `create_surface_*()` accurately reports those errors we can provoke. -/// This test applies to those cfgs that have a `create_surface_from_canvas` method, which +/// This test applies to those cfgs that can create a surface from a canvas, which /// include WebGL and WebGPU, but *not* Emscripten GLES. #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] #[wasm_bindgen_test::wasm_bindgen_test] @@ -15,7 +15,7 @@ fn canvas_get_context_returned_null() { #[allow(clippy::redundant_clone)] // false positive — can't and shouldn't move out. let error = instance - .create_surface_from_canvas(canvas.clone()) + .create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone())) .unwrap_err(); assert!( diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index d1c25a0246..2101fb8edc 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -479,68 +479,55 @@ impl Global { display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, id_in: Input, - ) -> SurfaceId { + ) -> Result { profiling::scope!("Instance::create_surface"); fn init( - any_surface: &mut Option, inst: &Option, display_handle: raw_window_handle::RawDisplayHandle, window_handle: raw_window_handle::RawWindowHandle, - ) { - if any_surface.is_none() { - if let Some(surface) = inst.as_ref().and_then(|inst| unsafe { - match inst.create_surface(display_handle, window_handle) { - Ok(raw) => Some(HalSurface:: { raw: Arc::new(raw) }), - Err(e) => { - log::warn!("Error: {:?}", e); - None - } - } - }) { - *any_surface = Some(AnySurface::new(surface)); + ) -> Option> { + inst.as_ref().map(|inst| unsafe { + match inst.create_surface(display_handle, window_handle) { + Ok(raw) => Ok(AnySurface::new(HalSurface:: { raw: Arc::new(raw) })), + Err(e) => Err(e), } - } + }) } - let mut hal_surface = None; + let mut hal_surface: Option> = None; + #[cfg(all(feature = "vulkan", not(target_arch = "wasm32")))] - init::( - &mut hal_surface, - &self.instance.vulkan, - display_handle, - window_handle, - ); + if hal_surface.is_none() { + hal_surface = + init::(&self.instance.vulkan, display_handle, window_handle); + } #[cfg(all(feature = "metal", any(target_os = "macos", target_os = "ios")))] - init::( - &mut hal_surface, - &self.instance.metal, - display_handle, - window_handle, - ); + if hal_surface.is_none() { + hal_surface = + init::(&self.instance.metal, display_handle, window_handle); + } #[cfg(all(feature = "dx12", windows))] - init::( - &mut hal_surface, - &self.instance.dx12, - display_handle, - window_handle, - ); + if hal_surface.is_none() { + hal_surface = + init::(&self.instance.dx12, display_handle, window_handle); + } #[cfg(feature = "gles")] - init::( - &mut hal_surface, - &self.instance.gl, - display_handle, - window_handle, - ); + if hal_surface.is_none() { + hal_surface = init::(&self.instance.gl, display_handle, window_handle); + } + + // This is only None if there's no instance at all. + let hal_surface = hal_surface.unwrap()?; let surface = Surface { presentation: Mutex::new(None), info: ResourceInfo::new(""), - raw: hal_surface.unwrap(), + raw: hal_surface, }; let (id, _) = self.surfaces.prepare::(id_in).assign(surface); - id + Ok(id) } /// # Safety @@ -578,78 +565,6 @@ impl Global { id } - #[cfg(all( - target_arch = "wasm32", - not(target_os = "emscripten"), - feature = "gles" - ))] - pub fn create_surface_webgl_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - id_in: Input, - ) -> Result { - profiling::scope!("Instance::create_surface_webgl_canvas"); - - let surface = Surface { - presentation: Mutex::new(None), - info: ResourceInfo::new(""), - raw: { - let hal_surface: HalSurface = self - .instance - .gl - .as_ref() - .map(|inst| { - let raw_surface = inst.create_surface_from_canvas(canvas)?; - Ok(HalSurface { - raw: Arc::new(raw_surface), - }) - }) - .transpose()? - .unwrap(); - AnySurface::new(hal_surface) - }, - }; - - let (id, _) = self.surfaces.prepare::(id_in).assign(surface); - Ok(id) - } - - #[cfg(all( - target_arch = "wasm32", - not(target_os = "emscripten"), - feature = "gles" - ))] - pub fn create_surface_webgl_offscreen_canvas( - &self, - canvas: web_sys::OffscreenCanvas, - id_in: Input, - ) -> Result { - profiling::scope!("Instance::create_surface_webgl_offscreen_canvas"); - - let surface = Surface { - presentation: Mutex::new(None), - info: ResourceInfo::new(""), - raw: { - let hal_surface: HalSurface = self - .instance - .gl - .as_ref() - .map(|inst| { - let raw_surface = inst.create_surface_from_offscreen_canvas(canvas)?; - Ok(HalSurface { - raw: Arc::new(raw_surface), - }) - }) - .transpose()? - .unwrap(); - AnySurface::new(hal_surface) - }, - }; - - let (id, _) = self.surfaces.prepare::(id_in).assign(surface); - Ok(id) - } - #[cfg(all(feature = "dx12", windows))] /// # Safety /// diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index 057c4f1453..05a0db4263 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -7,7 +7,8 @@ use crate::{ DownlevelCapabilities, Features, Label, Limits, LoadOp, MapMode, Operations, PipelineLayoutDescriptor, RenderBundleEncoderDescriptor, RenderPipelineDescriptor, SamplerDescriptor, ShaderModuleDescriptor, ShaderModuleDescriptorSpirV, ShaderSource, StoreOp, - SurfaceStatus, TextureDescriptor, TextureViewDescriptor, UncapturedErrorHandler, + SurfaceStatus, SurfaceTargetUnsafe, TextureDescriptor, TextureViewDescriptor, + UncapturedErrorHandler, }; use arrayvec::ArrayVec; @@ -231,81 +232,6 @@ impl Context { self.0.generate_report() } - #[cfg(metal)] - pub unsafe fn create_surface_from_core_animation_layer( - &self, - layer: *mut std::ffi::c_void, - ) -> Surface { - let id = unsafe { self.0.instance_create_surface_metal(layer, ()) }; - Surface { - id, - configured_device: Mutex::default(), - } - } - - #[cfg(any(webgpu, webgl))] - pub fn instance_create_surface_from_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - ) -> Result { - let id = self.0.create_surface_webgl_canvas(canvas, ())?; - Ok(Surface { - id, - configured_device: Mutex::default(), - }) - } - - #[cfg(any(webgpu, webgl))] - pub fn instance_create_surface_from_offscreen_canvas( - &self, - canvas: web_sys::OffscreenCanvas, - ) -> Result { - let id = self.0.create_surface_webgl_offscreen_canvas(canvas, ())?; - Ok(Surface { - id, - configured_device: Mutex::default(), - }) - } - - #[cfg(dx12)] - pub unsafe fn create_surface_from_visual(&self, visual: *mut std::ffi::c_void) -> Surface { - let id = unsafe { self.0.instance_create_surface_from_visual(visual, ()) }; - Surface { - id, - configured_device: Mutex::default(), - } - } - - #[cfg(dx12)] - pub unsafe fn create_surface_from_surface_handle( - &self, - surface_handle: *mut std::ffi::c_void, - ) -> Surface { - let id = unsafe { - self.0 - .instance_create_surface_from_surface_handle(surface_handle, ()) - }; - Surface { - id, - configured_device: Mutex::default(), - } - } - - #[cfg(dx12)] - pub unsafe fn create_surface_from_swap_chain_panel( - &self, - swap_chain_panel: *mut std::ffi::c_void, - ) -> Surface { - let id = unsafe { - self.0 - .instance_create_surface_from_swap_chain_panel(swap_chain_panel, ()) - }; - Surface { - id, - configured_device: Mutex::default(), - } - } - fn handle_error( &self, sink_mutex: &Mutex, @@ -594,19 +520,45 @@ impl crate::Context for Context { unsafe fn instance_create_surface( &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(Self::SurfaceId, Self::SurfaceData), crate::CreateSurfaceError> { - let id = unsafe { - self.0 - .instance_create_surface(display_handle, window_handle, ()) + let id = match target { + SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + } => unsafe { + self.0 + .instance_create_surface(raw_display_handle, raw_window_handle, ())? + }, + + #[cfg(metal)] + SurfaceTargetUnsafe::CoreAnimationLayer(layer) => unsafe { + self.0.instance_create_surface_metal(layer, ()) + }, + + #[cfg(dx12)] + SurfaceTargetUnsafe::CompositionVisual(visual) => unsafe { + self.0.instance_create_surface_from_visual(visual, ()) + }, + + #[cfg(dx12)] + SurfaceTargetUnsafe::SurfaceHandle(surface_handle) => unsafe { + self.0 + .instance_create_surface_from_surface_handle(surface_handle, ()) + }, + + #[cfg(dx12)] + SurfaceTargetUnsafe::SwapChainPanel(swap_chain_panel) => unsafe { + self.0 + .instance_create_surface_from_swap_chain_panel(swap_chain_panel, ()) + }, }; Ok(( id, Surface { id, - configured_device: Mutex::new(None), + configured_device: Mutex::default(), }, )) } diff --git a/wgpu/src/backend/web.rs b/wgpu/src/backend/web.rs index ee606e6d9c..705d33dcca 100644 --- a/wgpu/src/backend/web.rs +++ b/wgpu/src/backend/web.rs @@ -18,7 +18,7 @@ use wasm_bindgen::{prelude::*, JsCast}; use crate::{ context::{downcast_ref, ObjectId, QueueWriteBuffer, Unused}, - UncapturedErrorHandler, + SurfaceTargetUnsafe, UncapturedErrorHandler, }; fn create_identified(value: T) -> (Identified, Sendable) { @@ -878,35 +878,7 @@ where } impl Context { - pub fn instance_create_surface_from_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - ) -> Result< - ( - ::SurfaceId, - ::SurfaceData, - ), - crate::CreateSurfaceError, - > { - let result = canvas.get_context("webgpu"); - self.create_surface_from_context(Canvas::Canvas(canvas), result) - } - - pub fn instance_create_surface_from_offscreen_canvas( - &self, - canvas: web_sys::OffscreenCanvas, - ) -> Result< - ( - ::SurfaceId, - ::SurfaceData, - ), - crate::CreateSurfaceError, - > { - let result = canvas.get_context("webgpu"); - self.create_surface_from_context(Canvas::Offscreen(canvas), result) - } - - /// Common portion of public `instance_create_surface_from_*` functions. + /// Common portion of the internal branches of the public `instance_create_surface` function. /// /// Note: Analogous code also exists in the WebGL2 backend at /// `wgpu_hal::gles::web::Instance`. @@ -1068,36 +1040,50 @@ impl crate::context::Context for Context { unsafe fn instance_create_surface( &self, - _display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(Self::SurfaceId, Self::SurfaceData), crate::CreateSurfaceError> { - let canvas_element: web_sys::HtmlCanvasElement = match window_handle { - raw_window_handle::RawWindowHandle::Web(handle) => { - let canvas_node: wasm_bindgen::JsValue = web_sys::window() - .and_then(|win| win.document()) - .and_then(|doc| { - doc.query_selector_all(&format!("[data-raw-handle=\"{}\"]", handle.id)) - .ok() - }) - .and_then(|nodes| nodes.get(0)) - .expect("expected to find single canvas") - .into(); - canvas_node.into() - } - raw_window_handle::RawWindowHandle::WebCanvas(handle) => { - let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; - value.clone().unchecked_into() - } - raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => { - let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; - let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into(); + match target { + SurfaceTargetUnsafe::RawHandle { + raw_display_handle: _, + raw_window_handle, + } => { + let canvas_element: web_sys::HtmlCanvasElement = match raw_window_handle { + raw_window_handle::RawWindowHandle::Web(handle) => { + let canvas_node: wasm_bindgen::JsValue = web_sys::window() + .and_then(|win| win.document()) + .and_then(|doc| { + doc.query_selector_all(&format!( + "[data-raw-handle=\"{}\"]", + handle.id + )) + .ok() + }) + .and_then(|nodes| nodes.get(0)) + .expect("expected to find single canvas") + .into(); + canvas_node.into() + } + raw_window_handle::RawWindowHandle::WebCanvas(handle) => { + let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; + value.clone().unchecked_into() + } + raw_window_handle::RawWindowHandle::WebOffscreenCanvas(handle) => { + let value: &JsValue = unsafe { handle.obj.cast().as_ref() }; + let canvas: web_sys::OffscreenCanvas = value.clone().unchecked_into(); + let context_result = canvas.get_context("webgpu"); + + return self.create_surface_from_context( + Canvas::Offscreen(canvas), + context_result, + ); + } + _ => panic!("expected valid handle for canvas"), + }; - return self.instance_create_surface_from_offscreen_canvas(canvas); + let context_result = canvas_element.get_context("webgpu"); + self.create_surface_from_context(Canvas::Canvas(canvas_element), context_result) } - _ => panic!("expected valid handle for canvas"), - }; - - self.instance_create_surface_from_canvas(canvas_element) + } } fn instance_request_adapter( @@ -2146,7 +2132,7 @@ impl crate::context::Context for Context { _pipeline_layout: &Self::PipelineLayoutId, _pipeline_layout_data: &Self::PipelineLayoutData, ) { - // Dropped automatically + // Dropped automaticaly } fn shader_module_drop( diff --git a/wgpu/src/context.rs b/wgpu/src/context.rs index 785bba6114..2221921623 100644 --- a/wgpu/src/context.rs +++ b/wgpu/src/context.rs @@ -14,8 +14,8 @@ use crate::{ PipelineLayoutDescriptor, QuerySetDescriptor, RenderBundleDescriptor, RenderBundleEncoderDescriptor, RenderPassDescriptor, RenderPipelineDescriptor, RequestAdapterOptions, RequestDeviceError, SamplerDescriptor, ShaderModuleDescriptor, - ShaderModuleDescriptorSpirV, Texture, TextureDescriptor, TextureViewDescriptor, - UncapturedErrorHandler, + ShaderModuleDescriptorSpirV, SurfaceTargetUnsafe, Texture, TextureDescriptor, + TextureViewDescriptor, UncapturedErrorHandler, }; /// Meta trait for an id tracked by a context. @@ -98,8 +98,7 @@ pub trait Context: Debug + WasmNotSendSync + Sized { fn init(instance_desc: wgt::InstanceDescriptor) -> Self; unsafe fn instance_create_surface( &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(Self::SurfaceId, Self::SurfaceData), crate::CreateSurfaceError>; fn instance_request_adapter( &self, @@ -1143,8 +1142,7 @@ pub(crate) trait DynContext: Debug + WasmNotSendSync { unsafe fn instance_create_surface( &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(ObjectId, Box), crate::CreateSurfaceError>; #[allow(clippy::type_complexity)] fn instance_request_adapter( @@ -2001,11 +1999,9 @@ where unsafe fn instance_create_surface( &self, - display_handle: raw_window_handle::RawDisplayHandle, - window_handle: raw_window_handle::RawWindowHandle, + target: SurfaceTargetUnsafe, ) -> Result<(ObjectId, Box), crate::CreateSurfaceError> { - let (surface, data) = - unsafe { Context::instance_create_surface(self, display_handle, window_handle) }?; + let (surface, data) = unsafe { Context::instance_create_surface(self, target) }?; Ok((surface.into(), Box::new(data) as _)) } diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 9c5173e6e0..0c65a43100 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -365,7 +365,7 @@ static_assertions::assert_impl_all!(SurfaceConfiguration: Send, Sync); /// serves a similar role. pub struct Surface<'window> { context: Arc, - _surface: Option>, + _surface: Option>, id: ObjectId, data: Box, // Stores the latest `SurfaceConfiguration` that was set using `Surface::configure`. @@ -409,6 +409,149 @@ impl Drop for Surface<'_> { } } +/// Super trait for window handles as used in [`SurfaceTarget`]. +pub trait WindowHandle: HasWindowHandle + HasDisplayHandle + WasmNotSendSync {} + +impl WindowHandle for T where T: HasWindowHandle + HasDisplayHandle + WasmNotSendSync {} + +/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with safe surface creation. +/// +/// This is either a window or an actual web canvas depending on the platform and +/// enabled features. +/// Refer to the individual variants for more information. +/// +/// See also [`SurfaceTargetUnsafe`] for unsafe variants. +#[non_exhaustive] +pub enum SurfaceTarget<'window> { + /// Window handle producer. + /// + /// If the specified display and window handle are not supported by any of the backends, then the surface + /// will not be supported by any adapters. + /// + /// # Errors + /// + /// - On WebGL2: surface creation returns 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. + Window(Box), + + /// Surface from a `web_sys::HtmlCanvasElement`. + /// + /// The `canvas` argument must be a valid `` element to + /// create a surface upon. + /// + /// # Errors + /// + /// - On WebGL2: surface creation 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(any(webgpu, webgl))] + Canvas(web_sys::HtmlCanvasElement), + + /// Surface from a `web_sys::OffscreenCanvas`. + /// + /// The `canvas` argument must be a valid `OffscreenCanvas` object + /// to create a surface upon. + /// + /// # Errors + /// + /// - On WebGL2: surface creation 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(any(webgpu, webgl))] + OffscreenCanvas(web_sys::OffscreenCanvas), +} + +impl<'a, T> From for SurfaceTarget<'a> +where + T: WindowHandle + 'a, +{ + fn from(window: T) -> Self { + Self::Window(Box::new(window)) + } +} + +/// The window/canvas/surface/swap-chain/etc. a surface is attached to, for use with unsafe surface creation. +/// +/// This is either a window or an actual web canvas depending on the platform and +/// enabled features. +/// Refer to the individual variants for more information. +/// +/// See also [`SurfaceTarget`] for safe variants. +#[non_exhaustive] +pub enum SurfaceTargetUnsafe { + /// Raw window & display handle. + /// + /// If the specified display and window handle are not supported by any of the backends, then the surface + /// will not be supported by any adapters. + /// + /// # Safety + /// + /// - `raw_window_handle` & `raw_display_handle` must be valid objects to create a surface upon. + /// - `raw_window_handle` & `raw_display_handle` must remain valid until after the returned + /// [`Surface`] is dropped. + RawHandle { + /// Raw display handle, underlying display must outlive the surface created from this. + raw_display_handle: raw_window_handle::RawDisplayHandle, + + /// Raw display handle, underlying window must outlive the surface created from this. + raw_window_handle: raw_window_handle::RawWindowHandle, + }, + + /// Surface from `CoreAnimationLayer`. + /// + /// # Safety + /// + /// - layer must be a valid object to create a surface upon. + #[cfg(metal)] + CoreAnimationLayer(*mut std::ffi::c_void), + + /// Surface from `IDCompositionVisual`. + /// + /// # Safety + /// + /// - visual must be a valid IDCompositionVisual to create a surface upon. + #[cfg(dx12)] + CompositionVisual(*mut std::ffi::c_void), + + /// Surface from DX12 `SurfaceHandle`. + /// + /// # Safety + /// + /// - surface_handle must be a valid SurfaceHandle to create a surface upon. + #[cfg(dx12)] + SurfaceHandle(*mut std::ffi::c_void), + + /// Surface from DX12 `SwapChainPanel`. + /// + /// # Safety + /// + /// - visual must be a valid SwapChainPanel to create a surface upon. + #[cfg(dx12)] + SwapChainPanel(*mut std::ffi::c_void), +} + +impl SurfaceTargetUnsafe { + /// Creates a [`SurfaceTargetUnsafe::RawHandle`] from a window. + /// + /// # Safety + /// + /// - `window` must outlive the resulting surface target + /// (and subsequently the surface created for this target). + pub unsafe fn from_window(window: &T) -> Result + where + T: HasDisplayHandle + HasWindowHandle, + { + Ok(Self::RawHandle { + raw_display_handle: window.display_handle()?.as_raw(), + raw_window_handle: window.window_handle()?.as_raw(), + }) + } +} + /// Handle to a binding group layout. /// /// A `BindGroupLayout` is a handle to the GPU-side layout of a binding group. It can be used to @@ -1740,258 +1883,97 @@ impl Instance { } } - /// Creates a surface from a raw window handle. - /// - /// If the specified display and window handle are not supported by any of the backends, then the surface - /// will not be supported by any adapters. - /// - /// If a reference is passed in `window`, the returned [`Surface`] will - /// hold a lifetime to it. Owned values will return a [`Surface<'static>`] - /// instead. - /// - /// # 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 fn create_surface<'window, W>( - &self, - window: W, - ) -> Result, CreateSurfaceError> - where - W: HasWindowHandle + HasDisplayHandle + WasmNotSendSync + 'window, - { - let mut surface = unsafe { self.create_surface_from_raw(&window) }?; - surface._surface = Some(Box::new(window)); - Ok(surface) - } - - /// An alternative version to [`create_surface()`](Self::create_surface) - /// which has no lifetime requirements to `window` and doesn't require - /// [`Send`] or [`Sync`] (on non-Wasm targets). This makes it `unsafe` - /// instead and always returns a [`Surface<'static>`]. + /// Creates a new surface targeting a given window/canvas/surface/etc.. /// - /// See [`create_surface()`](Self::create_surface) for more details. - /// - /// # Safety + /// See [`SurfaceTarget`] for what targets are supported. + /// See [`Instance::create_surface`] for surface creation with unsafe target variants. /// - /// - `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. - pub unsafe fn create_surface_from_raw( + /// Most commonly used are window handles (or provider of windows handles) + /// which can be passed directly as they're automatically converted to [`SurfaceTarget`]. + pub fn create_surface<'window>( &self, - window: &W, - ) -> Result, CreateSurfaceError> - where - W: HasWindowHandle + HasDisplayHandle, - { - let raw_display_handle = window - .display_handle() - .map_err(|e| CreateSurfaceError { - inner: CreateSurfaceErrorKind::RawHandle(e), - })? - .as_raw(); - let raw_window_handle = window - .window_handle() - .map_err(|e| CreateSurfaceError { - inner: CreateSurfaceErrorKind::RawHandle(e), - })? - .as_raw(); - let (id, data) = unsafe { - DynContext::instance_create_surface( - &*self.context, - raw_display_handle, - raw_window_handle, - ) - }?; - Ok(Surface { - context: Arc::clone(&self.context), - _surface: None, - id, - data, - config: Mutex::new(None), - }) - } - - /// Creates a surface from `CoreAnimationLayer`. - /// - /// # Safety - /// - /// - layer must be a valid object to create a surface upon. - #[cfg(metal)] - pub unsafe fn create_surface_from_core_animation_layer( - &self, - layer: *mut std::ffi::c_void, - ) -> Surface<'static> { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_core_animation_layer(layer) - }; - Surface { - context: Arc::clone(&self.context), - _surface: None, - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } - } - - /// Creates a surface from `IDCompositionVisual`. - /// - /// # Safety - /// - /// - visual must be a valid IDCompositionVisual to create a surface upon. - #[cfg(dx12)] - pub unsafe fn create_surface_from_visual( - &self, - visual: *mut std::ffi::c_void, - ) -> Surface<'static> { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_visual(visual) - }; - Surface { - context: Arc::clone(&self.context), - _surface: None, - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } - } - - /// Creates a surface from `SurfaceHandle`. - /// - /// # Safety - /// - /// - surface_handle must be a valid SurfaceHandle to create a surface upon. - #[cfg(dx12)] - pub unsafe fn create_surface_from_surface_handle( - &self, - surface_handle: *mut std::ffi::c_void, - ) -> Surface<'static> { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_surface_handle(surface_handle) - }; - Surface { - context: Arc::clone(&self.context), - _surface: None, - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } - } + target: impl Into>, + ) -> Result, CreateSurfaceError> { + // Handle origin (i.e. window) to optionally take ownership of to make the surface outlast the window. + let handle_origin; + + let target = target.into(); + let mut surface = match target { + SurfaceTarget::Window(window) => unsafe { + let surface = self.create_surface_unsafe( + SurfaceTargetUnsafe::from_window(&window).map_err(|e| CreateSurfaceError { + inner: CreateSurfaceErrorKind::RawHandle(e), + })?, + ); + handle_origin = Some(window); + + surface + }?, + + #[cfg(any(webgpu, webgl))] + SurfaceTarget::Canvas(canvas) => { + handle_origin = None; + + let value: &wasm_bindgen::JsValue = &canvas; + let obj = std::ptr::NonNull::from(value).cast(); + let raw_window_handle = raw_window_handle::WebCanvasWindowHandle::new(obj).into(); + let raw_display_handle = raw_window_handle::WebDisplayHandle::new().into(); + + // Note that we need to call this while we still have `value` around. + // This is safe without storing canvas to `handle_origin` since the surface will create a copy internally. + unsafe { + self.create_surface_unsafe(SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + }) + }? + } - /// Creates a surface from `SwapChainPanel`. - /// - /// # Safety - /// - /// - visual must be a valid SwapChainPanel to create a surface upon. - #[cfg(dx12)] - pub unsafe fn create_surface_from_swap_chain_panel( - &self, - swap_chain_panel: *mut std::ffi::c_void, - ) -> Surface<'static> { - let surface = unsafe { - self.context - .as_any() - .downcast_ref::() - .unwrap() - .create_surface_from_swap_chain_panel(swap_chain_panel) + #[cfg(any(webgpu, webgl))] + SurfaceTarget::OffscreenCanvas(canvas) => { + handle_origin = None; + + let value: &wasm_bindgen::JsValue = &canvas; + let obj = std::ptr::NonNull::from(value).cast(); + let raw_window_handle = + raw_window_handle::WebOffscreenCanvasWindowHandle::new(obj).into(); + let raw_display_handle = raw_window_handle::WebDisplayHandle::new().into(); + + // Note that we need to call this while we still have `value` around. + // This is safe without storing canvas to `handle_origin` since the surface will create a copy internally. + unsafe { + self.create_surface_unsafe(SurfaceTargetUnsafe::RawHandle { + raw_display_handle, + raw_window_handle, + }) + }? + } }; - Surface { - context: Arc::clone(&self.context), - _surface: None, - id: ObjectId::from(surface.id()), - data: Box::new(surface), - config: Mutex::new(None), - } - } - /// Creates a surface from a `web_sys::HtmlCanvasElement`. - /// - /// The `canvas` argument must be a valid `` 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(any(webgpu, webgl))] - pub fn create_surface_from_canvas( - &self, - canvas: web_sys::HtmlCanvasElement, - ) -> Result, CreateSurfaceError> { - let surface = self - .context - .as_any() - .downcast_ref::() - .unwrap() - .instance_create_surface_from_canvas(canvas)?; + surface._surface = handle_origin; - // TODO: This is ugly, a way to create things from a native context needs to be made nicer. - Ok(Surface { - context: Arc::clone(&self.context), - _surface: None, - #[cfg(webgl)] - id: ObjectId::from(surface.id()), - #[cfg(webgl)] - data: Box::new(surface), - #[cfg(webgpu)] - id: ObjectId::UNUSED, - #[cfg(webgpu)] - data: Box::new(surface.1), - config: Mutex::new(None), - }) + Ok(surface) } - /// Creates a surface from a `web_sys::OffscreenCanvas`. + /// Creates a new surface targeting a given window/canvas/surface/etc. using an unsafe target. /// - /// The `canvas` argument must be a valid `OffscreenCanvas` object - /// to create a surface upon. + /// See [`SurfaceTargetUnsafe`] for what targets are supported. + /// See [`Instance::create_surface`] for surface creation with safe target variants. /// - /// # Errors + /// # Safety /// - /// - 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(any(webgpu, webgl))] - pub fn create_surface_from_offscreen_canvas( + /// - See respective [`SurfaceTargetUnsafe`] variants for safety requirements. + pub unsafe fn create_surface_unsafe<'window>( &self, - canvas: web_sys::OffscreenCanvas, - ) -> Result, CreateSurfaceError> { - let surface = self - .context - .as_any() - .downcast_ref::() - .unwrap() - .instance_create_surface_from_offscreen_canvas(canvas)?; + target: SurfaceTargetUnsafe, + ) -> Result, CreateSurfaceError> { + let (id, data) = unsafe { self.context.instance_create_surface(target) }?; - // TODO: This is ugly, a way to create things from a native context needs to be made nicer. Ok(Surface { context: Arc::clone(&self.context), _surface: None, - #[cfg(webgl)] - id: ObjectId::from(surface.id()), - #[cfg(webgl)] - data: Box::new(surface), - #[cfg(webgpu)] - id: ObjectId::UNUSED, - #[cfg(webgpu)] - data: Box::new(surface.1), + id, + data, config: Mutex::new(None), }) }