Skip to content

Commit

Permalink
OffscreenCanvas Support for WebGL Backend (#2603)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
haraldreingruber-dedalus and zicklag authored Jun 7, 2022
1 parent 717bc40 commit 25b16d5
Show file tree
Hide file tree
Showing 8 changed files with 234 additions and 42 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions wgpu-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down
46 changes: 46 additions & 0 deletions wgpu-core/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
Expand Down
101 changes: 67 additions & 34 deletions wgpu-hal/src/gles/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand All @@ -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
Expand All @@ -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(),
};

Expand All @@ -79,34 +120,26 @@ 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;
}
}
}
}

#[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,
Expand Down
2 changes: 2 additions & 0 deletions wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,8 @@ web-sys = { version = "0.3.57", features = [
"GpuVertexStepMode",
"HtmlCanvasElement",
"OffscreenCanvas",
"ImageBitmap",
"ImageBitmapRenderingContext",
"Window"
] }
js-sys = "0.3.57"
Expand Down
88 changes: 87 additions & 1 deletion wgpu/examples/framework.rs
Original file line number Diff line number Diff line change
@@ -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},
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -110,14 +122,60 @@ 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);

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 =
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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");
}
}
}
_ => {}
}
Expand Down
26 changes: 26 additions & 0 deletions wgpu/src/backend/direct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
Loading

0 comments on commit 25b16d5

Please sign in to comment.