Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OffscreenCanvas Support for WebGL Backend #2603

Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
416b38b
First attempt of exposing create_surface_from_canvas for webgl
haraldreingruber-dedalus Apr 15, 2022
7696677
Test Fix Compile For WebGL Offscreen Canvas
zicklag Apr 16, 2022
3ed5422
Only specify web-sys feature version in wgpu-core, so that patch vers…
haraldreingruber-dedalus Apr 17, 2022
9527f08
Reuse already existing fn create_surface_from_canvas
haraldreingruber-dedalus Apr 17, 2022
fa8c64a
Remove unnecessary unsafe
haraldreingruber-dedalus Apr 17, 2022
d273a67
Remove unsafe prefix also from top-level create_surface_from_canvas
haraldreingruber-dedalus Apr 17, 2022
807d45f
Add create_surface_from_offscreen_canvas() for webgl
haraldreingruber-dedalus Apr 17, 2022
7d70ed1
Cargo fmt
haraldreingruber-dedalus Apr 18, 2022
48a3c54
Store webgl2_context instead of canvas, which works also for Offscree…
haraldreingruber-dedalus Apr 18, 2022
f083cd1
Copy skybox example for OffscreenCanvas example
haraldreingruber-dedalus Apr 21, 2022
b089cd2
Use offscreen_canvas instead in newly created example
haraldreingruber-dedalus Apr 22, 2022
1846bec
Update skypbox_offscreen readme.md
haraldreingruber-dedalus Apr 23, 2022
d7083da
Allow enabling OffscreenCanvas in examples via http://localhost:8000/…
haraldreingruber-dedalus May 3, 2022
eb8bbf9
Merge branch 'master' into feature/webgl_offscreen_canvas
haraldreingruber-dedalus May 31, 2022
0426f86
Fix cargo fmt
haraldreingruber-dedalus Jun 4, 2022
2464909
[fix warning] Only import FromStr for wasm32
haraldreingruber-dedalus Jun 4, 2022
2553e1a
[fix warning] Exclude offscreen_canvas_setup from non-wasm32
haraldreingruber-dedalus Jun 4, 2022
fd2cbdc
[fix warning] Add ImageBitmap feature as well so that all related met…
haraldreingruber-dedalus Jun 4, 2022
c2cba40
Fix cargo fmt
haraldreingruber-dedalus Jun 4, 2022
158c416
Fix emscripten build
haraldreingruber-dedalus Jun 6, 2022
95ca719
Remove `webgl` feature from wgpu-core as webgl is the only wasm32 bac…
haraldreingruber-dedalus Jun 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

2 changes: 2 additions & 0 deletions wgpu-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ replay = ["serde", "wgt/replay", "arrayvec/serde", "naga/deserialize"]
serial-pass = ["serde", "wgt/serde", "arrayvec/serde"]
id32 = []
vulkan-portability = ["hal/vulkan"]
webgl = ["web-sys"]
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved

[dependencies]
arrayvec = "0.7"
Expand Down Expand Up @@ -58,6 +59,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"], optional = true }
cwfitzgerald marked this conversation as resolved.
Show resolved Hide resolved

[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", feature = "webgl"))]
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", feature = "webgl"))]
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
4 changes: 3 additions & 1 deletion wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ glsl = ["naga/glsl-in"]
trace = ["serde", "wgc/trace"]
replay = ["serde", "wgc/replay"]
angle = ["wgc/angle"]
webgl = ["wgc"]
webgl = ["wgc", "wgc/webgl"]
emscripten = ["webgl"]
vulkan-portability = ["wgc/vulkan-portability"]

Expand Down Expand Up @@ -280,6 +280,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
Loading