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.

Simplified API by removing the redundant selector-based canvas resolver in the web target.

Added a reference to `set_canvas_from_selector` in the canvas field documentation.

Fixed compilation of non-wasm targets.
  • Loading branch information
anlumo committed Oct 14, 2022
1 parent da84b1f commit c2afbb7
Show file tree
Hide file tree
Showing 7 changed files with 133 additions and 38 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::Canvas(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
16 changes: 16 additions & 0 deletions crates/bevy_render/src/view/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ pub struct ExtractedWindow {
pub swap_chain_texture: Option<TextureView>,
pub size_changed: bool,
pub present_mode_changed: bool,
#[cfg(target_arch = "wasm32")]
pub canvas: Option<bevy_window::Canvas>,
}

#[derive(Default, Resource)]
Expand Down Expand Up @@ -90,6 +92,8 @@ fn extract_windows(
swap_chain_texture: None,
size_changed: false,
present_mode_changed: false,
#[cfg(target_arch = "wasm32")]
canvas: window.canvas().cloned(),
});

// NOTE: Drop the swap chain frame here
Expand Down Expand Up @@ -173,6 +177,18 @@ pub fn prepare_windows(
// NOTE: On some OSes this MUST be called from the main thread.
render_instance.create_surface(&handle.get_handle())
}),
AbstractWindowHandle::Canvas(canvas) => {
window_surfaces
.surfaces
.entry(window.id)
.or_insert_with(|| render_instance.create_surface_from_canvas(canvas));
}
AbstractWindowHandle::OffscreenCanvas(canvas) => {
window_surfaces
.surfaces
.entry(window.id)
.or_insert_with(|| render_instance.create_surface_from_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"
77 changes: 54 additions & 23 deletions crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ 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;

#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Reflect, FromReflect)]
#[reflect_value(PartialEq, Hash)]
Expand Down Expand Up @@ -164,8 +166,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 Canvas {}
#[cfg(target_arch = "wasm32")]
unsafe impl Sync for Canvas {}

/// 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 +235,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 @@ -355,7 +365,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 +402,6 @@ 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(),
}
Expand Down Expand Up @@ -750,12 +758,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 +788,21 @@ impl Window {
self.window_handle.clone()
}

/// The "html canvas" element selector.
/// The canvas element.
///
/// 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.
/// If set, this canvas element will be used, rather than creating a new one.
#[cfg(target_arch = "wasm32")]
#[inline]
pub fn canvas(&self) -> Option<&str> {
self.canvas.as_deref()
pub fn canvas(&self) -> Option<&Canvas> {
self.canvas.as_ref()
}

/// 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 +911,19 @@ 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.
/// The canvas element.
///
/// 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).
/// If set, this canvas element will be used, rather than creating a new one.
///
/// This value has no effect on non-web platforms.
pub canvas: Option<String>,
/// See [WindowDescriptor::set_canvas_from_selector] to set it via a CSS selector.
#[cfg(target_arch = "wasm32")]
pub canvas: Option<Canvas>,
/// 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")]
pub fit_canvas_to_parent: bool,
}

Expand All @@ -942,8 +944,37 @@ impl Default for WindowDescriptor {
cursor_visible: true,
mode: WindowMode::Windowed,
transparent: false,
#[cfg(target_arch = "wasm32")]
canvas: None,
#[cfg(target_arch = "wasm32")]
fit_canvas_to_parent: false,
}
}
}

#[cfg(target_arch = "wasm32")]
impl WindowDescriptor {
/// Convenience function for setting the canvas to be used by this WindowDescriptor.
///
/// 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(true)` if the canvas was found and `Ok(false)` otherwise. Note that matching a non-canvas element
/// also returns `Ok(false)`.
pub fn set_canvas_from_selector(
&mut self,
selector: &str,
) -> Result<bool, wasm_bindgen::JsValue> {
let canvas = web_sys::window()
.unwrap()
.document()
.unwrap()
.query_selector(selector)?;
self.canvas = canvas
.and_then(|canvas| canvas.dyn_into().ok())
.map(Canvas::HtmlCanvas);
Ok(self.canvas.is_some())
}
}
17 changes: 13 additions & 4 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,12 +709,21 @@ fn handle_create_window_events(
{
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
if let Some(canvas) = &create_window_event.descriptor.canvas {
match canvas {
bevy_window::Canvas::HtmlCanvas(canvas) => {
channel.listen_to_element(create_window_event.id, canvas.clone());
}
bevy_window::Canvas::OffscreenCanvas(_) => {
panic!("OffscreenCanvas not supported by winit!");
}
}
} else {
web_resize::WINIT_CANVAS_SELECTOR
channel.listen_to_selector(
create_window_event.id,
web_resize::WINIT_CANVAS_SELECTOR,
);
};
channel.listen_to_selector(create_window_event.id, selector);
}
}
}
Expand Down
32 changes: 32 additions & 0 deletions crates/bevy_winit/src/web_resize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use bevy_ecs::prelude::*;
use bevy_window::WindowId;
use crossbeam_channel::{Receiver, Sender};
use wasm_bindgen::JsCast;
use web_sys::HtmlCanvasElement;
use winit::dpi::LogicalSize;

pub(crate) struct CanvasParentResizePlugin;
Expand Down Expand Up @@ -49,6 +50,15 @@ fn get_size(selector: &str) -> Option<LogicalSize<f32>> {
));
}

fn get_size_element(element: &HtmlCanvasElement) -> Option<LogicalSize<f32>> {
let parent_element = element.parent_element()?;
let rect = parent_element.get_bounding_client_rect();
return Some(winit::dpi::LogicalSize::new(
rect.width() as f32,
rect.height() as f32,
));
}

pub(crate) const WINIT_CANVAS_SELECTOR: &str = "canvas[data-raw-handle]";

impl Default for CanvasParentResizeEventChannel {
Expand Down Expand Up @@ -81,4 +91,26 @@ impl CanvasParentResizeEventChannel {
.unwrap();
closure.forget();
}

pub(crate) fn listen_to_element(&self, window_id: WindowId, element: HtmlCanvasElement) {
let sender = self.sender.clone();
let resize = move || {
if let Some(size) = get_size_element(&element) {
sender.send(ResizeEvent { size, window_id }).unwrap();
}
};

// ensure resize happens on startup
resize();

let closure = wasm_bindgen::closure::Closure::wrap(Box::new(move |_: web_sys::Event| {
resize();
}) as Box<dyn FnMut(_)>);
let window = web_sys::window().unwrap();

window
.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())
.unwrap();
closure.forget();
}
}
20 changes: 9 additions & 11 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,15 @@ impl WinitWindows {
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);
if let Some(canvas) = &window_descriptor.canvas {
match canvas {
bevy_window::Canvas::HtmlCanvas(canvas) => {
let canvas = canvas.dyn_ref::<web_sys::HtmlCanvasElement>().cloned();
winit_window_builder = winit_window_builder.with_canvas(canvas);
}
bevy_window::Canvas::OffscreenCanvas(_) => {
panic!("winit does not support OffscreenCanvas.")
}
}
}
}
Expand Down

0 comments on commit c2afbb7

Please sign in to comment.