diff --git a/Cargo.lock b/Cargo.lock index 1e2ae3c4c4..c5732876c7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2081,6 +2081,7 @@ dependencies = [ "serde", "smallvec", "thiserror", + "web-sys", "wgpu-hal", "wgpu-types", ] diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index aa13091282..3f1ed7e04f 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -58,6 +58,7 @@ version = "0.12" [target.'cfg(target_arch = "wasm32")'.dependencies] hal = { path = "../wgpu-hal", package = "wgpu-hal", version = "0.12", features = ["gles"] } +web-sys = { version = "0.3", features = ["HtmlCanvasElement"] } [target.'cfg(all(not(target_arch = "wasm32"), any(target_os = "ios", target_os = "macos")))'.dependencies] hal = { path = "../wgpu-hal", package = "wgpu-hal", version = "0.12", features = ["metal"] } diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index 907c65d08c..638fee9f26 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -493,6 +493,52 @@ impl Global { id.0 } + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn create_surface_webgl_canvas( + &self, + canvas: &web_sys::HtmlCanvasElement, + id_in: Input, + ) -> SurfaceId { + profiling::scope!("create_surface_webgl_canvas", "Instance"); + + 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") + }, + }), + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + + #[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))] + pub fn create_surface_webgl_offscreen_canvas( + &self, + canvas: &web_sys::OffscreenCanvas, + id_in: Input, + ) -> SurfaceId { + profiling::scope!("create_surface_webgl_offscreen_canvas", "Instance"); + + 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") + }, + }), + }; + + let mut token = Token::root(); + let id = self.surfaces.prepare(id_in).assign(surface, &mut token); + id.0 + } + #[cfg(dx12)] /// # Safety /// diff --git a/wgpu-hal/src/gles/web.rs b/wgpu-hal/src/gles/web.rs index 1c0ee6a9db..cdb1942a4b 100644 --- a/wgpu-hal/src/gles/web.rs +++ b/wgpu-hal/src/gles/web.rs @@ -5,7 +5,7 @@ use wasm_bindgen::JsCast; use super::TextureFormatDesc; /// A wrapper around a [`glow::Context`] to provide a fake `lock()` api that makes it compatible -/// with the `AdapterContext` API fromt the EGL implementation. +/// with the `AdapterContext` API from the EGL implementation. pub struct AdapterContext { pub glow_context: glow::Context, } @@ -25,7 +25,62 @@ impl AdapterContext { #[derive(Debug)] pub struct Instance { - canvas: Mutex>, + webgl2_context: Mutex>, +} + +impl Instance { + pub fn create_surface_from_canvas( + &self, + canvas: &web_sys::HtmlCanvasElement, + ) -> Result { + 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::().ok()) + .expect("Cannot convert into WebGL2 context"); + + *self.webgl2_context.lock() = Some(webgl2_context.clone()); + + Ok(Surface { + webgl2_context, + present_program: None, + swapchain: None, + texture: None, + presentable: true, + }) + } + + pub fn create_surface_from_offscreen_canvas( + &self, + canvas: &web_sys::OffscreenCanvas, + ) -> Result { + 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::().ok()) + .expect("Cannot convert into WebGL2 context"); + + *self.webgl2_context.lock() = Some(webgl2_context.clone()); + + Ok(Surface { + webgl2_context, + present_program: None, + swapchain: None, + texture: None, + presentable: true, + }) + } + + fn create_context_options() -> js_sys::Object { + let context_options = js_sys::Object::new(); + js_sys::Reflect::set( + &context_options, + &"antialias".into(), + &wasm_bindgen::JsValue::FALSE, + ) + .expect("Cannot create context options"); + context_options + } } // SAFE: WASM doesn't have threads @@ -35,28 +90,14 @@ unsafe impl Send for Instance {} impl crate::Instance for Instance { unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result { Ok(Instance { - canvas: Mutex::new(None), + webgl2_context: Mutex::new(None), }) } unsafe fn enumerate_adapters(&self) -> Vec> { - let canvas_guard = self.canvas.lock(); - let gl = match *canvas_guard { - Some(ref canvas) => { - let context_options = js_sys::Object::new(); - js_sys::Reflect::set( - &context_options, - &"antialias".into(), - &wasm_bindgen::JsValue::FALSE, - ) - .expect("Cannot create context options"); - let webgl2_context = canvas - .get_context_with_context_options("webgl2", &context_options) - .expect("Cannot create WebGL2 context") - .and_then(|context| context.dyn_into::().ok()) - .expect("Cannot convert into WebGL2 context"); - glow::Context::from_webgl2_context(webgl2_context) - } + let context_guard = self.webgl2_context.lock(); + let gl = match *context_guard { + Some(ref webgl2_context) => glow::Context::from_webgl2_context(webgl2_context.clone()), None => return Vec::new(), }; @@ -79,26 +120,18 @@ impl crate::Instance for Instance { .dyn_into() .expect("Failed to downcast to canvas type"); - *self.canvas.lock() = Some(canvas.clone()); - - Ok(Surface { - canvas, - present_program: None, - swapchain: None, - texture: None, - presentable: true, - }) + self.create_surface_from_canvas(&canvas) } else { unreachable!() } } unsafe fn destroy_surface(&self, surface: Surface) { - let mut canvas_option_ref = self.canvas.lock(); + let mut context_option_ref = self.webgl2_context.lock(); - if let Some(canvas) = canvas_option_ref.as_ref() { - if canvas == &surface.canvas { - *canvas_option_ref = None; + if let Some(context) = context_option_ref.as_ref() { + if context == &surface.webgl2_context { + *context_option_ref = None; } } } @@ -106,7 +139,7 @@ impl crate::Instance for Instance { #[derive(Clone, Debug)] pub struct Surface { - canvas: web_sys::HtmlCanvasElement, + webgl2_context: web_sys::WebGl2RenderingContext, pub(super) swapchain: Option, texture: Option, pub(super) presentable: bool, diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index 834eeefe87..488e735b16 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -280,6 +280,8 @@ web-sys = { version = "0.3.57", features = [ "GpuVertexStepMode", "HtmlCanvasElement", "OffscreenCanvas", + "ImageBitmap", + "ImageBitmapRenderingContext", "Window" ] } js-sys = "0.3.57" diff --git a/wgpu/examples/framework.rs b/wgpu/examples/framework.rs index 0ed135f1cd..676c61c818 100644 --- a/wgpu/examples/framework.rs +++ b/wgpu/examples/framework.rs @@ -1,6 +1,10 @@ use std::future::Future; +#[cfg(target_arch = "wasm32")] +use std::str::FromStr; #[cfg(not(target_arch = "wasm32"))] use std::time::{Duration, Instant}; +#[cfg(target_arch = "wasm32")] +use web_sys::{ImageBitmapRenderingContext, OffscreenCanvas}; use winit::{ event::{self, WindowEvent}, event_loop::{ControlFlow, EventLoop}, @@ -72,6 +76,14 @@ struct Setup { adapter: wgpu::Adapter, device: wgpu::Device, queue: wgpu::Queue, + #[cfg(target_arch = "wasm32")] + offscreen_canvas_setup: Option, +} + +#[cfg(target_arch = "wasm32")] +struct OffscreenCanvasSetup { + offscreen_canvas: OffscreenCanvas, + bitmap_renderer: ImageBitmapRenderingContext, } async fn setup(title: &str) -> Setup { @@ -110,6 +122,39 @@ async fn setup(title: &str) -> Setup { .expect("couldn't append canvas to document body"); } + #[cfg(target_arch = "wasm32")] + let mut offscreen_canvas_setup: Option = None; + #[cfg(target_arch = "wasm32")] + { + use wasm_bindgen::JsCast; + use winit::platform::web::WindowExtWebSys; + + let query_string = web_sys::window().unwrap().location().search().unwrap(); + if let Some(offscreen_canvas_param) = + parse_url_query_string(&query_string, "offscreen_canvas") + { + if FromStr::from_str(offscreen_canvas_param) == Ok(true) { + log::info!("Creating OffscreenCanvasSetup"); + + let offscreen_canvas = + OffscreenCanvas::new(1024, 768).expect("couldn't create OffscreenCanvas"); + + let bitmap_renderer = window + .canvas() + .get_context("bitmaprenderer") + .expect("couldn't create ImageBitmapRenderingContext (Result)") + .expect("couldn't create ImageBitmapRenderingContext (Option)") + .dyn_into::() + .expect("couldn't convert into ImageBitmapRenderingContext"); + + offscreen_canvas_setup = Some(OffscreenCanvasSetup { + offscreen_canvas, + bitmap_renderer, + }) + } + } + }; + log::info!("Initializing the surface..."); let backend = wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all); @@ -117,7 +162,20 @@ async fn setup(title: &str) -> Setup { let instance = wgpu::Instance::new(backend); let (size, surface) = unsafe { let size = window.inner_size(); + + #[cfg(not(target_arch = "wasm32"))] let surface = instance.create_surface(&window); + #[cfg(target_arch = "wasm32")] + let surface = { + if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup { + log::info!("Creating surface from OffscreenCanvas"); + instance + .create_surface_from_offscreen_canvas(&offscreen_canvas_setup.offscreen_canvas) + } else { + instance.create_surface(&window) + } + }; + (size, surface) }; let adapter = @@ -180,11 +238,13 @@ async fn setup(title: &str) -> Setup { adapter, device, queue, + #[cfg(target_arch = "wasm32")] + offscreen_canvas_setup, } } fn start( - Setup { + #[cfg(not(target_arch = "wasm32"))] Setup { window, event_loop, instance, @@ -194,6 +254,17 @@ fn start( device, queue, }: Setup, + #[cfg(target_arch = "wasm32")] Setup { + window, + event_loop, + instance, + size, + surface, + adapter, + device, + queue, + offscreen_canvas_setup, + }: Setup, ) { let spawner = Spawner::new(); let mut config = wgpu::SurfaceConfiguration { @@ -326,6 +397,21 @@ fn start( example.render(&view, &device, &queue, &spawner); frame.present(); + + #[cfg(target_arch = "wasm32")] + { + if let Some(offscreen_canvas_setup) = &offscreen_canvas_setup { + let image_bitmap = offscreen_canvas_setup + .offscreen_canvas + .transfer_to_image_bitmap() + .expect("couldn't transfer offscreen canvas to image bitmap."); + offscreen_canvas_setup + .bitmap_renderer + .transfer_from_image_bitmap(&image_bitmap); + + log::info!("Transferring OffscreenCanvas to ImageBitmapRenderer"); + } + } } _ => {} } diff --git a/wgpu/src/backend/direct.rs b/wgpu/src/backend/direct.rs index fdd599e57b..dae0e1eca6 100644 --- a/wgpu/src/backend/direct.rs +++ b/wgpu/src/backend/direct.rs @@ -181,6 +181,32 @@ impl Context { } } + #[cfg(all(target_arch = "wasm32", feature = "webgl", not(feature = "emscripten")))] + pub fn instance_create_surface_from_canvas( + self: &Arc, + canvas: &web_sys::HtmlCanvasElement, + ) -> Surface { + let id = self.0.create_surface_webgl_canvas(canvas, PhantomData); + 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, + canvas: &web_sys::OffscreenCanvas, + ) -> Surface { + let id = self + .0 + .create_surface_webgl_offscreen_canvas(canvas, PhantomData); + Surface { + id, + configured_device: Mutex::default(), + } + } + #[cfg(target_os = "windows")] pub unsafe fn create_surface_from_visual( self: &Arc, diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 15408d8a7d..1e4e852f7d 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -1739,11 +1739,8 @@ impl Instance { /// # Safety /// /// - canvas must be a valid element to create a surface upon. - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] - pub unsafe fn create_surface_from_canvas( - &self, - canvas: &web_sys::HtmlCanvasElement, - ) -> Surface { + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] + pub fn create_surface_from_canvas(&self, canvas: &web_sys::HtmlCanvasElement) -> Surface { Surface { context: Arc::clone(&self.context), id: self.context.instance_create_surface_from_canvas(canvas), @@ -1755,8 +1752,8 @@ impl Instance { /// # Safety /// /// - canvas must be a valid OffscreenCanvas to create a surface upon. - #[cfg(all(target_arch = "wasm32", not(feature = "webgl")))] - pub unsafe fn create_surface_from_offscreen_canvas( + #[cfg(all(target_arch = "wasm32", not(feature = "emscripten")))] + pub fn create_surface_from_offscreen_canvas( &self, canvas: &web_sys::OffscreenCanvas, ) -> Surface {