Skip to content

Commit

Permalink
Implemented support for creating a render surface from canvas or offs…
Browse files Browse the repository at this point in the history
…creencanvas elements on the wasm target. This only works when not using winit.
  • Loading branch information
anlumo committed Oct 16, 2022
1 parent da84b1f commit 990b9ea
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 66 deletions.
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"
144 changes: 114 additions & 30 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 @@ -224,7 +237,7 @@ pub struct Window {
window_handle: AbstractWindowHandle,
focused: bool,
mode: WindowMode,
canvas: Option<String>,
#[allow(dead_code)]
fit_canvas_to_parent: bool,
command_queue: Vec<WindowCommand>,
}
Expand Down Expand Up @@ -355,7 +368,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 @@ -393,7 +405,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 @@ -750,12 +858,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 @@ -780,25 +888,12 @@ 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
/// children. This creates a "feedback loop" that will result in the canvas growing on each resize. When using this
/// feature, ensure the parent's size is not affected by its children.
///
/// This value has no effect on non-web platforms.
#[cfg(target_arch = "wasm32")]
#[inline]
pub fn fit_canvas_to_parent(&self) -> bool {
self.fit_canvas_to_parent
Expand Down Expand Up @@ -907,21 +1002,11 @@ 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
/// children. This creates a "feedback loop" that will result in the canvas growing on each resize. When using this
/// feature, ensure the parent's size is not affected by its children.
///
/// This value has no effect on non-web platforms.
pub fit_canvas_to_parent: bool,
}

Expand All @@ -942,7 +1027,6 @@ impl Default for WindowDescriptor {
cursor_visible: true,
mode: WindowMode::Windowed,
transparent: false,
canvas: None,
fit_canvas_to_parent: false,
}
}
Expand Down
9 changes: 1 addition & 8 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -708,14 +708,7 @@ fn handle_create_window_events(
#[cfg(target_arch = "wasm32")]
{
let channel = world.resource_mut::<web_resize::CanvasParentResizeEventChannel>();
if create_window_event.descriptor.fit_canvas_to_parent {
let selector = if let Some(selector) = &create_window_event.descriptor.canvas {
selector
} else {
web_resize::WINIT_CANVAS_SELECTOR
};
channel.listen_to_selector(create_window_event.id, selector);
}
channel.listen_to_selector(create_window_event.id, web_resize::WINIT_CANVAS_SELECTOR);
}
}
}
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

0 comments on commit 990b9ea

Please sign in to comment.