mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-10-30 05:51:42 +00:00
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:
parent
717bc40106
commit
25b16d5cc8
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2099,6 +2099,7 @@ dependencies = [
|
||||
"serde",
|
||||
"smallvec",
|
||||
"thiserror",
|
||||
"web-sys",
|
||||
"wgpu-hal",
|
||||
"wgpu-types",
|
||||
]
|
||||
|
@ -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"] }
|
||||
|
@ -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
|
||||
///
|
||||
|
@ -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,
|
||||
|
@ -281,6 +281,8 @@ web-sys = { version = "0.3.57", features = [
|
||||
"GpuVertexStepMode",
|
||||
"HtmlCanvasElement",
|
||||
"OffscreenCanvas",
|
||||
"ImageBitmap",
|
||||
"ImageBitmapRenderingContext",
|
||||
"Window"
|
||||
] }
|
||||
js-sys = "0.3.57"
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user