OffscreenCanvas Support for WebGL Backend (#2603)

* First attempt of exposing create_surface_from_canvas for webgl

* Test Fix Compile For WebGL Offscreen Canvas

* Only specify web-sys feature version in wgpu-core, so that patch version is taken from workspace

* Reuse already existing fn create_surface_from_canvas

* Remove unnecessary unsafe

* Remove unsafe prefix also from top-level create_surface_from_canvas

* Add create_surface_from_offscreen_canvas() for webgl

* Cargo fmt

* Store webgl2_context instead of canvas, which works also for OffscreenCanvas

* Copy skybox example for OffscreenCanvas example

* Use offscreen_canvas instead in newly created example

* Update skypbox_offscreen readme.md

* Allow enabling OffscreenCanvas in examples via http://localhost:8000/?offscreen_canvas=true

* Fix cargo fmt

* [fix warning] Only import FromStr for wasm32

* [fix warning] Exclude offscreen_canvas_setup from non-wasm32

* [fix warning] Add ImageBitmap feature as well so that all related methods can be used

* Fix cargo fmt

* Fix emscripten build

* Remove `webgl` feature from wgpu-core as webgl is the only wasm32 backend

Co-authored-by: Zicklag <zicklag@katharostech.com>
This commit is contained in:
Harald Reingruber 2022-06-07 07:57:16 +02:00 committed by GitHub
parent 717bc40106
commit 25b16d5cc8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 234 additions and 42 deletions

1
Cargo.lock generated
View File

@ -2099,6 +2099,7 @@ dependencies = [
"serde",
"smallvec",
"thiserror",
"web-sys",
"wgpu-hal",
"wgpu-types",
]

View File

@ -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"] }

View File

@ -493,6 +493,52 @@ impl<G: GlobalIdentityHandlerFactory> Global<G> {
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<G, SurfaceId>,
) -> 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<G, SurfaceId>,
) -> 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
///

View File

@ -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<Option<web_sys::HtmlCanvasElement>>,
webgl2_context: Mutex<Option<web_sys::WebGl2RenderingContext>>,
}
impl Instance {
pub fn create_surface_from_canvas(
&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,
present_program: None,
swapchain: None,
texture: None,
presentable: true,
})
}
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.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<super::Api> for Instance {
unsafe fn init(_desc: &crate::InstanceDescriptor) -> Result<Self, crate::InstanceError> {
Ok(Instance {
canvas: Mutex::new(None),
webgl2_context: Mutex::new(None),
})
}
unsafe fn enumerate_adapters(&self) -> Vec<crate::ExposedAdapter<super::Api>> {
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::<web_sys::WebGl2RenderingContext>().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<super::Api> 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<super::Api> for Instance {
#[derive(Clone, Debug)]
pub struct Surface {
canvas: web_sys::HtmlCanvasElement,
webgl2_context: web_sys::WebGl2RenderingContext,
pub(super) swapchain: Option<Swapchain>,
texture: Option<glow::Texture>,
pub(super) presentable: bool,

View File

@ -281,6 +281,8 @@ web-sys = { version = "0.3.57", features = [
"GpuVertexStepMode",
"HtmlCanvasElement",
"OffscreenCanvas",
"ImageBitmap",
"ImageBitmapRenderingContext",
"Window"
] }
js-sys = "0.3.57"

View File

@ -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<OffscreenCanvasSetup>,
}
#[cfg(target_arch = "wasm32")]
struct OffscreenCanvasSetup {
offscreen_canvas: OffscreenCanvas,
bitmap_renderer: ImageBitmapRenderingContext,
}
async fn setup<E: Example>(title: &str) -> Setup {
@ -110,6 +122,39 @@ async fn setup<E: Example>(title: &str) -> Setup {
.expect("couldn't append canvas to document body");
}
#[cfg(target_arch = "wasm32")]
let mut offscreen_canvas_setup: Option<OffscreenCanvasSetup> = 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::<ImageBitmapRenderingContext>()
.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<E: Example>(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<E: Example>(title: &str) -> Setup {
adapter,
device,
queue,
#[cfg(target_arch = "wasm32")]
offscreen_canvas_setup,
}
}
fn start<E: Example>(
Setup {
#[cfg(not(target_arch = "wasm32"))] Setup {
window,
event_loop,
instance,
@ -194,6 +254,17 @@ fn start<E: Example>(
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<E: Example>(
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");
}
}
}
_ => {}
}

View File

@ -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<Self>,
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<Self>,
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<Self>,

View File

@ -1746,11 +1746,8 @@ impl Instance {
/// # Safety
///
/// - canvas must be a valid <canvas> 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),
@ -1762,8 +1759,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 {