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

Implemented support for creating a render surface from a canvas or offscreencanvas #6147

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
Prev Previous commit
Next Next commit
Implemented support for creating a render surface from canvas or offs…
…creencanvas elements on the wasm target. This only works when not using winit.
  • Loading branch information
anlumo committed Oct 18, 2022
commit 54140f96acf449fe28f5a71bbf9647de08f16966
8 changes: 8 additions & 0 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,14 @@ impl Plugin for RenderPlugin {
Some(instance.create_surface(&handle.get_handle()))
}
AbstractWindowHandle::Virtual => None,
#[cfg(target_arch = "wasm32")]
AbstractWindowHandle::HtmlCanvas(canvas) => {
Some(instance.create_surface_from_canvas(&canvas))
}
#[cfg(target_arch = "wasm32")]
AbstractWindowHandle::OffscreenCanvas(canvas) => {
Some(instance.create_surface_from_offscreen_canvas(&canvas))
}
}
});
raw_handle
Expand Down
10 changes: 10 additions & 0 deletions crates/bevy_render/src/view/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ pub fn prepare_windows(
// NOTE: On some OSes this MUST be called from the main thread.
render_instance.create_surface(&handle.get_handle())
}),
#[cfg(target_arch = "wasm32")]
AbstractWindowHandle::HtmlCanvas(canvas) => window_surfaces
.surfaces
.entry(window.id)
.or_insert_with(|| render_instance.create_surface_from_canvas(canvas)),
#[cfg(target_arch = "wasm32")]
AbstractWindowHandle::OffscreenCanvas(canvas) => window_surfaces
.surfaces
.entry(window.id)
.or_insert_with(|| render_instance.create_surface_from_offscreen_canvas(canvas)),
AbstractWindowHandle::Virtual => continue,
};

Expand Down
1 change: 1 addition & 0 deletions crates/bevy_window/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ serde = { version = "1.0", features = ["derive"], optional = true }

[target.'cfg(target_arch = "wasm32")'.dependencies]
web-sys = "0.3"
wasm-bindgen = "0.2"
138 changes: 112 additions & 26 deletions crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ use bevy_math::{DVec2, IVec2, UVec2, Vec2};
use bevy_reflect::{FromReflect, Reflect};
use bevy_utils::{tracing::warn, Uuid};
use raw_window_handle::RawWindowHandle;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use web_sys::{HtmlCanvasElement, OffscreenCanvas};

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Reflect, FromReflect)]
#[reflect_value(PartialEq, Hash)]
Expand Down Expand Up @@ -164,8 +168,17 @@ pub enum AbstractWindowHandle {
/// for creating and presenting surface textures and inserting them into
/// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html).
Virtual,
#[cfg(target_arch = "wasm32")]
HtmlCanvas(web_sys::HtmlCanvasElement),
#[cfg(target_arch = "wasm32")]
OffscreenCanvas(web_sys::OffscreenCanvas),
}

#[cfg(target_arch = "wasm32")]
unsafe impl Send for AbstractWindowHandle {}
#[cfg(target_arch = "wasm32")]
unsafe impl Sync for AbstractWindowHandle {}

/// An operating system or virtual window that can present content and receive user input.
///
/// To create a window, use a [`EventWriter<CreateWindow>`](`crate::CreateWindow`).
Expand Down Expand Up @@ -277,7 +290,6 @@ pub struct Window {
window_handle: AbstractWindowHandle,
focused: bool,
mode: WindowMode,
canvas: Option<String>,
fit_canvas_to_parent: bool,
command_queue: Vec<WindowCommand>,
}
Expand Down Expand Up @@ -408,7 +420,6 @@ impl Window {
)),
focused: true,
mode: window_descriptor.mode,
canvas: window_descriptor.canvas.clone(),
fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent,
command_queue: Vec::new(),
}
Expand Down Expand Up @@ -446,7 +457,103 @@ impl Window {
window_handle: AbstractWindowHandle::Virtual,
focused: true,
mode: window_descriptor.mode,
canvas: window_descriptor.canvas.clone(),
fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent,
command_queue: Vec::new(),
}
}

/// Creates a new [`Window`] from a canvas.
///
/// See [`AbstractWindowHandle::HtmlCanvas`].
#[cfg(target_arch = "wasm32")]
pub fn new_canvas(
id: WindowId,
window_descriptor: &WindowDescriptor,
canvas: HtmlCanvasElement,
) -> Self {
let size = canvas.get_bounding_client_rect();
Window {
id,
requested_width: window_descriptor.width,
requested_height: window_descriptor.height,
position: None,
physical_width: size.width() as _,
physical_height: size.height() as _,
resize_constraints: window_descriptor.resize_constraints,
scale_factor_override: window_descriptor.scale_factor_override,
backend_scale_factor: web_sys::window().unwrap().device_pixel_ratio(),
title: window_descriptor.title.clone(),
present_mode: window_descriptor.present_mode,
resizable: window_descriptor.resizable,
decorations: window_descriptor.decorations,
cursor_visible: window_descriptor.cursor_visible,
cursor_locked: window_descriptor.cursor_locked,
cursor_icon: CursorIcon::Default,
physical_cursor_position: None,
window_handle: AbstractWindowHandle::HtmlCanvas(canvas),
focused: true,
mode: window_descriptor.mode,
fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent,
command_queue: Vec::new(),
}
}

/// Creates a new [`Window`] from a selector to a canvas.
///
/// The selector format used is a [CSS selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors).
/// It uses the first element matching the selector.
///
/// Returns an `Err` if the selector format is invalid. Panics if it is run from a web worker.
///
/// Returns Ok(None) when the element could not be found with the selector.
///
/// See [`AbstractWindowHandle::HtmlCanvas`].
#[cfg(target_arch = "wasm32")]
pub fn new_canvas_selector(
id: WindowId,
window_descriptor: &WindowDescriptor,
selector: &str,
) -> Result<Option<Self>, wasm_bindgen::JsValue> {
Ok(web_sys::window()
.unwrap()
.document()
.unwrap()
.query_selector(selector)?
.and_then(|element| element.dyn_into().ok())
.map(|canvas| Self::new_canvas(id, window_descriptor, canvas)))
}

/// Creates a new [`Window`] from an offscreen canvas.
///
/// See [`AbstractWindowHandle::OffscreenCanvas`].
#[cfg(target_arch = "wasm32")]
pub fn new_offscreen_canvas(
id: WindowId,
window_descriptor: &WindowDescriptor,
canvas: OffscreenCanvas,
scale_factor: f64,
) -> Self {
Window {
id,
requested_width: window_descriptor.width,
requested_height: window_descriptor.height,
position: None,
physical_width: canvas.width() as _,
physical_height: canvas.height() as _,
resize_constraints: window_descriptor.resize_constraints,
scale_factor_override: window_descriptor.scale_factor_override,
backend_scale_factor: scale_factor,
title: window_descriptor.title.clone(),
present_mode: window_descriptor.present_mode,
resizable: window_descriptor.resizable,
decorations: window_descriptor.decorations,
cursor_visible: window_descriptor.cursor_visible,
cursor_locked: window_descriptor.cursor_locked,
cursor_icon: CursorIcon::Default,
physical_cursor_position: None,
window_handle: AbstractWindowHandle::OffscreenCanvas(canvas),
focused: true,
mode: window_descriptor.mode,
fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent,
command_queue: Vec::new(),
}
Expand Down Expand Up @@ -803,12 +910,12 @@ impl Window {
});
}
/// Close the operating system window corresponding to this [`Window`].
///
///
/// This will also lead to this [`Window`] being removed from the
/// [`Windows`] resource.
///
/// If the default [`WindowPlugin`] is used, when no windows are
/// open, the [app will exit](bevy_app::AppExit).
/// open, the [app will exit](bevy_app::AppExit).
/// To disable this behaviour, set `exit_on_all_closed` on the [`WindowPlugin`]
/// to `false`
///
Expand All @@ -834,18 +941,6 @@ impl Window {
self.window_handle.clone()
}

/// The "html canvas" element selector.
///
/// If set, this selector will be used to find a matching html canvas element,
/// rather than creating a new one.
/// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
///
/// This value has no effect on non-web platforms.
#[inline]
pub fn canvas(&self) -> Option<&str> {
self.canvas.as_deref()
}

/// Whether or not to fit the canvas element's size to its parent element's size.
///
/// **Warning**: this will not behave as expected for parents that set their size according to the size of their
Expand Down Expand Up @@ -961,14 +1056,6 @@ pub struct WindowDescriptor {
/// macOS X transparent works with winit out of the box, so this issue might be related to: <https://github.com/gfx-rs/wgpu/issues/687>
/// Windows 11 is related to <https://github.com/rust-windowing/winit/issues/2082>
pub transparent: bool,
/// The "html canvas" element selector.
///
/// If set, this selector will be used to find a matching html canvas element,
/// rather than creating a new one.
/// Uses the [CSS selector format](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector).
///
/// This value has no effect on non-web platforms.
pub canvas: Option<String>,
/// Whether or not to fit the canvas element's size to its parent element's size.
///
/// **Warning**: this will not behave as expected for parents that set their size according to the size of their
Expand Down Expand Up @@ -996,7 +1083,6 @@ impl Default for WindowDescriptor {
cursor_visible: true,
mode: WindowMode::Windowed,
transparent: false,
canvas: None,
fit_canvas_to_parent: false,
}
}
Expand Down
34 changes: 6 additions & 28 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,26 +93,6 @@ impl WinitWindows {
#[allow(unused_mut)]
let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title);

#[cfg(target_arch = "wasm32")]
{
use wasm_bindgen::JsCast;
use winit::platform::web::WindowBuilderExtWebSys;

if let Some(selector) = &window_descriptor.canvas {
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let canvas = document
.query_selector(&selector)
.expect("Cannot query for canvas element.");
if let Some(canvas) = canvas {
let canvas = canvas.dyn_into::<web_sys::HtmlCanvasElement>().ok();
winit_window_builder = winit_window_builder.with_canvas(canvas);
} else {
panic!("Cannot find element: {}.", selector);
}
}
}

let winit_window = winit_window_builder.build(event_loop).unwrap();

if window_descriptor.mode == WindowMode::Windowed {
Expand Down Expand Up @@ -174,16 +154,14 @@ impl WinitWindows {
{
use winit::platform::web::WindowExtWebSys;

if window_descriptor.canvas.is_none() {
let canvas = winit_window.canvas();
let canvas = winit_window.canvas();

let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let body = document.body().unwrap();

body.append_child(&canvas)
.expect("Append canvas to HTML body.");
}
body.append_child(&canvas)
.expect("Append canvas to HTML body.");
}

let position = winit_window
Expand Down