From da96f15dc39c5878b6f0724ea350db503824063f Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 14 Oct 2022 14:27:37 +0200 Subject: [PATCH 1/6] Enable creating "virtual" windows without corresponding OS window Virtual windows will by default not have a surface texture associated to them, but implementors can set the texture in `ExtractedWindow` manually. This is intended to be used when embedding games into other appications like editors or for running games headless. --- crates/bevy_render/src/view/window.rs | 108 +++++++++++++------------- crates/bevy_window/src/window.rs | 59 ++++++++++++-- 2 files changed, 107 insertions(+), 60 deletions(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index a44610d0dc81d..05632cf5b50eb 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -132,6 +132,8 @@ pub struct WindowSurfaces { /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// +/// This will not handle [virtual windows](bevy_window::Window::new_virtual). +/// /// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is /// the performance bottleneck. This can be seen in profiles as multiple prepare-stage systems all /// taking an unusually long time to complete, and all finishing at about the same time as the @@ -161,64 +163,60 @@ pub fn prepare_windows( render_instance: Res, render_adapter: Res, ) { - for window in windows - .windows - .values_mut() - // value of raw_winndow_handle only None if synthetic test - .filter(|x| x.raw_window_handle.is_some()) - { - let window_surfaces = window_surfaces.deref_mut(); - let surface = window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - render_instance - .create_surface(&window.raw_window_handle.as_ref().unwrap().get_handle()) - }); - - let swap_chain_descriptor = wgpu::SurfaceConfiguration { - format: *surface - .get_supported_formats(&render_adapter) - .get(0) - .unwrap_or_else(|| { - panic!( - "No supported formats found for surface {:?} on adapter {:?}", - surface, render_adapter - ) - }), - width: window.physical_width, - height: window.physical_height, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - present_mode: match window.present_mode { - PresentMode::Fifo => wgpu::PresentMode::Fifo, - PresentMode::Mailbox => wgpu::PresentMode::Mailbox, - PresentMode::Immediate => wgpu::PresentMode::Immediate, - PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, - PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { - render_device.configure_surface(surface, &swap_chain_descriptor); - } + let window_surfaces = window_surfaces.deref_mut(); + for window in windows.windows.values_mut() { + if let Some(handle) = &window.handle { + let surface = window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + render_instance.create_surface(&handle.get_handle()) + }); - let frame = match surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { + let swap_chain_descriptor = wgpu::SurfaceConfiguration { + format: *surface + .get_supported_formats(&render_adapter) + .get(0) + .unwrap_or_else(|| { + panic!( + "No supported formats found for surface {:?} on adapter {:?}", + surface, render_adapter + ) + }), + width: window.physical_width, + height: window.physical_height, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + present_mode: match window.present_mode { + PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::Mailbox => wgpu::PresentMode::Mailbox, + PresentMode::Immediate => wgpu::PresentMode::Immediate, + PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, + PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { render_device.configure_surface(surface, &swap_chain_descriptor); - surface - .get_current_texture() - .expect("Error reconfiguring surface") } - err => err.expect("Failed to acquire next swap chain texture!"), - }; - window.swap_chain_texture = Some(TextureView::from(frame)); + let frame = match surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { + render_device.configure_surface(surface, &swap_chain_descriptor); + surface + .get_current_texture() + .expect("Error reconfiguring surface") + } + err => err.expect("Failed to acquire next swap chain texture!"), + }; + + window.swap_chain_texture = Some(TextureView::from(frame)); + } } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index c0eae67f02973..ab5afc7f0185d 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -151,7 +151,7 @@ impl WindowResizeConstraints { } } -/// An operating system window that can present content and receive user input. +/// An operating system or virtual window that can present content and receive user input. /// /// To create a window, use a [`EventWriter`](`crate::CreateWindow`). /// @@ -388,7 +388,7 @@ impl Window { cursor_locked: window_descriptor.cursor_locked, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_window_handle: raw_window_handle.map(RawWindowHandleWrapper::new), + raw_window_handle: Some(RawWindowHandleWrapper::new(raw_window_handle)), focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -396,6 +396,49 @@ impl Window { command_queue: Vec::new(), } } + + /// Creates a new virtual [`Window`]. + /// + /// This window does not have to correspond to an operator system window. + /// + /// It differs from a non-virtual window, in that the caller is responsible + /// for creating and presenting surface textures and inserting them into + /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). + pub fn new_virtual( + id: WindowId, + window_descriptor: &WindowDescriptor, + physical_width: u32, + physical_height: u32, + scale_factor: f64, + position: Option, + ) -> Self { + Window { + id, + requested_width: window_descriptor.width, + requested_height: window_descriptor.height, + position, + physical_width, + physical_height, + 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, + raw_window_handle: None, + 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(), + } + } + /// Get the window's [`WindowId`]. #[inline] pub fn id(&self) -> WindowId { @@ -772,11 +815,17 @@ impl Window { pub fn is_focused(&self) -> bool { self.focused } - /// Get the [`RawWindowHandleWrapper`] corresponding to this window if set. + + /// Get the [`RawWindowHandleWrapper`] corresponding to this window. + /// + /// A return value of `None` signifies that this is a virtual window and does not + /// correspond to an OS window. The creator of the window is responsible + /// for creating and presenting surface textures and inserting them into + /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). /// - /// During normal use, this can be safely unwrapped; the value should only be [`None`] when synthetically constructed for tests. + /// See [`Self::new_virtual`]. pub fn raw_window_handle(&self) -> Option { - self.raw_window_handle.as_ref().cloned() + self.raw_window_handle.clone() } /// The "html canvas" element selector. From fef8f2ad4bf7879d58520b64036bb3f7ce22d626 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 14 Oct 2022 16:59:04 +0200 Subject: [PATCH 2/6] Introduce `AbstractWindowHandle` enum --- crates/bevy_render/src/lib.rs | 21 +++--- crates/bevy_render/src/view/window.rs | 97 +++++++++++++------------- crates/bevy_window/src/window.rs | 44 +++++++----- crates/bevy_winit/src/winit_windows.rs | 2 +- 4 files changed, 87 insertions(+), 77 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 965a0d3e95b21..5913b59bc68dd 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -21,6 +21,7 @@ pub mod view; use bevy_core::FrameCount; use bevy_hierarchy::ValidParentCheckPlugin; +use bevy_window::AbstractWindowHandle; pub use extract_param::Extract; pub mod prelude { @@ -144,17 +145,19 @@ impl Plugin for RenderPlugin { .register_type::(); if let Some(backends) = options.backends { - let windows = app.world.resource_mut::(); let instance = wgpu::Instance::new(backends); - - let surface = windows - .get_primary() - .and_then(|window| window.raw_window_handle()) - .map(|wrapper| unsafe { - let handle = wrapper.get_handle(); - instance.create_surface(&handle) + let surface = { + let windows = app.world.resource_mut::(); + let raw_handle = windows.get_primary().and_then(|window| unsafe { + match window.window_handle() { + AbstractWindowHandle::RawWindowHandle(handle) => { + Some(instance.create_surface(&handle.get_handle())) + } + AbstractWindowHandle::Virtual => None, + } }); - + raw_handle + }; let request_adapter_options = wgpu::RequestAdapterOptions { power_preference: options.power_preference, compatible_surface: surface.as_ref(), diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 05632cf5b50eb..f8f8a3254e8f4 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -6,7 +6,7 @@ use crate::{ use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_utils::{tracing::debug, HashMap, HashSet}; -use bevy_window::{PresentMode, RawWindowHandleWrapper, WindowClosed, WindowId, Windows}; +use bevy_window::{AbstractWindowHandle, PresentMode, WindowClosed, WindowId, Windows}; use std::ops::{Deref, DerefMut}; /// Token to ensure a system runs on the main thread. @@ -38,7 +38,7 @@ impl Plugin for WindowRenderPlugin { pub struct ExtractedWindow { pub id: WindowId, - pub raw_window_handle: Option, + pub handle: AbstractWindowHandle, pub physical_width: u32, pub physical_height: u32, pub present_mode: PresentMode, @@ -83,7 +83,7 @@ fn extract_windows( .entry(window.id()) .or_insert(ExtractedWindow { id: window.id(), - raw_window_handle: window.raw_window_handle(), + handle: window.window_handle(), physical_width: new_width, physical_height: new_height, present_mode: window.present_mode(), @@ -132,7 +132,7 @@ pub struct WindowSurfaces { /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// -/// This will not handle [virtual windows](bevy_window::Window::new_virtual). +/// This will not handle [virtual windows](bevy_window::AbstractWindowHandle::Virtual). /// /// NOTE: `get_current_texture` in `prepare_windows` can take a long time if the GPU workload is /// the performance bottleneck. This can be seen in profiles as multiple prepare-stage systems all @@ -165,58 +165,59 @@ pub fn prepare_windows( ) { let window_surfaces = window_surfaces.deref_mut(); for window in windows.windows.values_mut() { - if let Some(handle) = &window.handle { - let surface = window_surfaces + let surface = match &window.handle { + AbstractWindowHandle::RawWindowHandle(handle) => window_surfaces .surfaces .entry(window.id) .or_insert_with(|| unsafe { // NOTE: On some OSes this MUST be called from the main thread. render_instance.create_surface(&handle.get_handle()) - }); + }), + AbstractWindowHandle::Virtual => continue, + }; + + let swap_chain_descriptor = wgpu::SurfaceConfiguration { + format: *surface + .get_supported_formats(&render_adapter) + .get(0) + .unwrap_or_else(|| { + panic!( + "No supported formats found for surface {:?} on adapter {:?}", + surface, render_adapter + ) + }), + width: window.physical_width, + height: window.physical_height, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + present_mode: match window.present_mode { + PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::Mailbox => wgpu::PresentMode::Mailbox, + PresentMode::Immediate => wgpu::PresentMode::Immediate, + PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, + PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { + render_device.configure_surface(surface, &swap_chain_descriptor); + } - let swap_chain_descriptor = wgpu::SurfaceConfiguration { - format: *surface - .get_supported_formats(&render_adapter) - .get(0) - .unwrap_or_else(|| { - panic!( - "No supported formats found for surface {:?} on adapter {:?}", - surface, render_adapter - ) - }), - width: window.physical_width, - height: window.physical_height, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - present_mode: match window.present_mode { - PresentMode::Fifo => wgpu::PresentMode::Fifo, - PresentMode::Mailbox => wgpu::PresentMode::Mailbox, - PresentMode::Immediate => wgpu::PresentMode::Immediate, - PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, - PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { + let frame = match surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { render_device.configure_surface(surface, &swap_chain_descriptor); + surface + .get_current_texture() + .expect("Error reconfiguring surface") } + err => err.expect("Failed to acquire next swap chain texture!"), + }; - let frame = match surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { - render_device.configure_surface(surface, &swap_chain_descriptor); - surface - .get_current_texture() - .expect("Error reconfiguring surface") - } - err => err.expect("Failed to acquire next swap chain texture!"), - }; - - window.swap_chain_texture = Some(TextureView::from(frame)); - } + window.swap_chain_texture = Some(TextureView::from(frame)); } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index ab5afc7f0185d..b882eb0d73598 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -151,6 +151,21 @@ impl WindowResizeConstraints { } } +/// Handle used for creating surfaces in the render plugin +/// +/// Either a raw handle to an OS window or `Virtual` to signify that there is no corresponding OS window. +#[derive(Clone, Debug)] +pub enum AbstractWindowHandle { + /// The window corresponds to an operator system window. + RawWindowHandle(RawWindowHandleWrapper), + /// The window does not to correspond to an operator system window. + /// + /// It differs from a non-virtual window, in that the caller is responsible + /// for creating and presenting surface textures and inserting them into + /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). + Virtual, +} + /// An operating system or virtual window that can present content and receive user input. /// /// To create a window, use a [`EventWriter`](`crate::CreateWindow`). @@ -259,7 +274,7 @@ pub struct Window { cursor_visible: bool, cursor_locked: bool, physical_cursor_position: Option, - raw_window_handle: Option, + window_handle: AbstractWindowHandle, focused: bool, mode: WindowMode, canvas: Option, @@ -368,7 +383,7 @@ impl Window { physical_height: u32, scale_factor: f64, position: Option, - raw_window_handle: Option, + raw_window_handle: RawWindowHandle, ) -> Self { Window { id, @@ -388,7 +403,9 @@ impl Window { cursor_locked: window_descriptor.cursor_locked, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_window_handle: Some(RawWindowHandleWrapper::new(raw_window_handle)), + window_handle: AbstractWindowHandle::RawWindowHandle(RawWindowHandleWrapper::new( + raw_window_handle, + )), focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -399,11 +416,7 @@ impl Window { /// Creates a new virtual [`Window`]. /// - /// This window does not have to correspond to an operator system window. - /// - /// It differs from a non-virtual window, in that the caller is responsible - /// for creating and presenting surface textures and inserting them into - /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). + /// See [`AbstractWindowHandle::Virtual`]. pub fn new_virtual( id: WindowId, window_descriptor: &WindowDescriptor, @@ -430,7 +443,7 @@ impl Window { cursor_locked: window_descriptor.cursor_locked, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_window_handle: None, + window_handle: AbstractWindowHandle::Virtual, focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -816,16 +829,9 @@ impl Window { self.focused } - /// Get the [`RawWindowHandleWrapper`] corresponding to this window. - /// - /// A return value of `None` signifies that this is a virtual window and does not - /// correspond to an OS window. The creator of the window is responsible - /// for creating and presenting surface textures and inserting them into - /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). - /// - /// See [`Self::new_virtual`]. - pub fn raw_window_handle(&self) -> Option { - self.raw_window_handle.clone() + /// Get the [`AbstractWindowHandle`] corresponding to this window. + pub fn window_handle(&self) -> AbstractWindowHandle { + self.window_handle.clone() } /// The "html canvas" element selector. diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 47423957cfbc5..4467574874c61 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -201,7 +201,7 @@ impl WinitWindows { inner_size.height, scale_factor, position, - Some(raw_window_handle), + raw_window_handle, ) } From d8adc9d3fa4e02e3fbaed05db24f18d5cf8d0a4f Mon Sep 17 00:00:00 2001 From: MDeiml Date: Mon, 17 Oct 2022 18:39:37 +0200 Subject: [PATCH 3/6] Add example --- Cargo.toml | 7 ++ examples/window/virtual.rs | 211 +++++++++++++++++++++++++++++++++++++ 2 files changed, 218 insertions(+) create mode 100644 examples/window/virtual.rs diff --git a/Cargo.toml b/Cargo.toml index 560ae5807f43c..ed933cf7fd24d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1547,6 +1547,13 @@ path = "tests/window/minimising.rs" [package.metadata.example.minimising] hidden = true +[[example]] +name = "virtual" +path = "examples/window/virtual.rs" + +[package.metadata.example.virtual] +hidden = true + [[example]] name = "window_resizing" path = "examples/window/window_resizing.rs" diff --git a/examples/window/virtual.rs b/examples/window/virtual.rs new file mode 100644 index 0000000000000..99ecd3a1ea68f --- /dev/null +++ b/examples/window/virtual.rs @@ -0,0 +1,211 @@ +//! Uses two windows to visualize a 3D model from different angles. + +use std::f32::consts::PI; + +use bevy::{ + core_pipeline::clear_color::ClearColorConfig, + prelude::*, + render::{ + camera::RenderTarget, + extract_resource::{ExtractResource, ExtractResourcePlugin}, + render_asset::{PrepareAssetLabel, RenderAssets}, + render_resource::{ + Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, + }, + view::{ExtractedWindows, RenderLayers, WindowSystem}, + RenderApp, RenderStage, + }, + window::{PresentMode, WindowId}, +}; + +#[derive(Clone, Resource)] +struct WindowTexture { + window_id: WindowId, + render_texture: Handle, +} + +impl ExtractResource for WindowTexture { + type Source = WindowTexture; + + fn extract_resource(source: &WindowTexture) -> Self { + source.clone() + } +} + +fn main() { + let mut app = App::new(); + app.add_plugins(DefaultPlugins) + .add_startup_system(setup) + .add_system(bevy::window::close_on_esc) + .add_plugin(ExtractResourcePlugin::::default()); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_system_to_stage( + RenderStage::Prepare, + prepare_window_texture + .after(PrepareAssetLabel::AssetPrepare) + .before(WindowSystem::Prepare), + ); + } + app.run(); +} + +fn prepare_window_texture( + window_texture: Res, + gpu_images: Res>, + mut extracted_windows: ResMut, +) { + if let Some(window) = extracted_windows.get_mut(&window_texture.window_id) { + window.swap_chain_texture = Some( + gpu_images + .get(&window_texture.render_texture) + .unwrap() + .texture_view + .clone(), + ); + } +} + +fn setup( + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, + mut images: ResMut>, + mut windows: ResMut, +) { + let window_id = WindowId::new(); + windows.add(Window::new_virtual( + window_id, + &WindowDescriptor { + width: 800., + height: 600., + present_mode: PresentMode::AutoNoVsync, + title: "Second window".to_string(), + ..default() + }, + 800, + 600, + 1.0, + None, + )); + + let size = Extent3d { + width: 800, + height: 600, + ..default() + }; + + // This is the texture that will be rendered to. + let mut image = Image { + texture_descriptor: TextureDescriptor { + label: None, + size, + dimension: TextureDimension::D2, + format: TextureFormat::Bgra8UnormSrgb, + mip_level_count: 1, + sample_count: 1, + usage: TextureUsages::TEXTURE_BINDING + | TextureUsages::COPY_DST + | TextureUsages::RENDER_ATTACHMENT, + }, + ..default() + }; + + // fill image.data with zeroes + image.resize(size); + + let image_handle = images.add(image); + commands.insert_resource(WindowTexture { + window_id, + render_texture: image_handle.clone(), + }); + + let cube_handle = meshes.add(Mesh::from(shape::Cube { size: 4.0 })); + let cube_material_handle = materials.add(StandardMaterial { + base_color: Color::rgb(0.8, 0.7, 0.6), + reflectance: 0.02, + unlit: false, + ..default() + }); + + // This specifies the layer used for the first pass, which will be attached to the first pass camera and cube. + let first_pass_layer = RenderLayers::layer(1); + + // The cube that will be rendered to the texture. + commands.spawn(( + PbrBundle { + mesh: cube_handle, + material: cube_material_handle, + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 1.0)), + ..default() + }, + first_pass_layer, + )); + + // Light + // NOTE: Currently lights are shared between passes - see https://github.com/bevyengine/bevy/issues/3462 + commands.spawn(PointLightBundle { + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 10.0)), + ..default() + }); + + commands.spawn(( + Camera3dBundle { + camera_3d: Camera3d { + clear_color: ClearColorConfig::Custom(Color::WHITE), + ..default() + }, + camera: Camera { + // render before the "main pass" camera + priority: -1, + target: RenderTarget::Image(image_handle.clone()), + ..default() + }, + transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) + .looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }, + first_pass_layer, + )); + + let cube_size = 4.0; + let cube_handle = meshes.add(Mesh::from(shape::Box::new(cube_size, cube_size, cube_size))); + + // This material has the texture that has been rendered. + let material_handle = materials.add(StandardMaterial { + base_color_texture: Some(image_handle), + reflectance: 0.02, + unlit: false, + ..default() + }); + + // Main pass cube, with material containing the rendered first pass texture. + commands.spawn(PbrBundle { + mesh: cube_handle, + material: material_handle, + transform: Transform::from_xyz(0.0, 0.0, 1.5) + .with_rotation(Quat::from_rotation_x(-PI / 5.0)), + ..default() + }); + + // The main pass camera. + commands.spawn(Camera3dBundle { + transform: Transform::from_xyz(0.0, 0.0, 15.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }); + + let window_id = WindowId::new(); + windows.add(Window::new_virtual( + window_id, + &WindowDescriptor { + width: 800., + height: 600., + present_mode: PresentMode::AutoNoVsync, + title: "Second window".to_string(), + ..default() + }, + 800, + 600, + 1.0, + None, + )); +} From 54140f96acf449fe28f5a71bbf9647de08f16966 Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Sun, 2 Oct 2022 20:59:21 +0200 Subject: [PATCH 4/6] Implemented support for creating a render surface from canvas or offscreencanvas elements on the wasm target. This only works when not using winit. --- crates/bevy_render/src/lib.rs | 8 ++ crates/bevy_render/src/view/window.rs | 10 ++ crates/bevy_window/Cargo.toml | 1 + crates/bevy_window/src/window.rs | 138 ++++++++++++++++++++----- crates/bevy_winit/src/winit_windows.rs | 34 ++---- 5 files changed, 137 insertions(+), 54 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 5913b59bc68dd..c7f1510d9e467 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -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 diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index f8f8a3254e8f4..90c50502a5181 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -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, }; diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index f1402447072de..f90b282d66bfc 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -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" \ No newline at end of file diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index b882eb0d73598..43d2ec7fb3701 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -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)] @@ -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`](`crate::CreateWindow`). @@ -277,7 +290,6 @@ pub struct Window { window_handle: AbstractWindowHandle, focused: bool, mode: WindowMode, - canvas: Option, fit_canvas_to_parent: bool, command_queue: Vec, } @@ -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(), } @@ -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, 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(), } @@ -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` /// @@ -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 @@ -961,14 +1056,6 @@ pub struct WindowDescriptor { /// macOS X transparent works with winit out of the box, so this issue might be related to: /// Windows 11 is related to 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, /// 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 @@ -996,7 +1083,6 @@ impl Default for WindowDescriptor { cursor_visible: true, mode: WindowMode::Windowed, transparent: false, - canvas: None, fit_canvas_to_parent: false, } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 4467574874c61..b5e31d61c9ce4 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -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::().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 { @@ -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 From a86f7711494b400cba7c9d464a359a4eeaf9f5f3 Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Mon, 17 Oct 2022 00:06:13 +0200 Subject: [PATCH 5/6] Attempt at allowing winit-using code to use a specific HtmlCanvasElement. However, the bevy API doesn't allow for that yet. --- crates/bevy_winit/src/lib.rs | 20 +++++++----- crates/bevy_winit/src/web_resize.rs | 31 +++++++++++++++++++ crates/bevy_winit/src/winit_windows.rs | 43 ++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 8 deletions(-) diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 0107a83e58e67..95e7f69e70953 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -24,9 +24,9 @@ use bevy_utils::{ Instant, }; use bevy_window::{ - CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows, - ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, - WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized, + AbstractWindowHandle, CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, + ModifiesWindows, ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, + WindowCloseRequested, WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged, Windows, }; @@ -709,12 +709,16 @@ fn handle_create_window_events( { let channel = world.resource_mut::(); if create_window_event.descriptor.fit_canvas_to_parent { - let selector = if let Some(selector) = &create_window_event.descriptor.canvas { - selector + if let AbstractWindowHandle::HtmlCanvas(canvas) = window.window_handle { + // PROBLEM: this path is unreachable, because we're always creating the window + // based on the raw window handle above. + channel.listen_to_element(create_window_event.id, canvas.clone()); } 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, + ); + } } } } diff --git a/crates/bevy_winit/src/web_resize.rs b/crates/bevy_winit/src/web_resize.rs index 7e289afdfc675..c666491378adc 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -58,6 +58,15 @@ impl Default for CanvasParentResizeEventChannel { } } +fn get_size_element(element: &HtmlCanvasElement) -> Option> { + 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, + )); +} + impl CanvasParentResizeEventChannel { pub(crate) fn listen_to_selector(&self, window_id: WindowId, selector: &str) { let sender = self.sender.clone(); @@ -81,4 +90,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); + let window = web_sys::window().unwrap(); + + window + .add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref()) + .unwrap(); + closure.forget(); + } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index b5e31d61c9ce4..a447b828019c7 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -24,6 +24,43 @@ impl WinitWindows { event_loop: &winit::event_loop::EventLoopWindowTarget<()>, window_id: WindowId, window_descriptor: &WindowDescriptor, + ) -> Window { + Self::create_window_internal(event_loop, window_id, window_descriptor, None) + } + #[cfg(target_wasm = "wasm32")] + pub fn create_window_with_canvas( + &mut self, + event_loop: &winit::event_loop::EventLoopWindowTarget<()>, + window_id: WindowId, + window_descriptor: &WindowDescriptor, + canvas: web_sys::HtmlCanvasElement, + ) -> Window { + Self::create_window_internal(event_loop, window_id, window_descriptor, Some(canvas)) + } + #[cfg(target_wasm = "wasm32")] + pub fn create_window_with_selector( + &mut self, + event_loop: &winit::event_loop::EventLoopWindowTarget<()>, + window_id: WindowId, + window_descriptor: &WindowDescriptor, + selector: &str, + ) -> Result, wasm_bindgen::JsValue> { + use wasm_bindgen::JsCast; + + let window = web_sys::window().unwrap(); + let document = window.document().unwrap(); + let canvas = document.query_selector(selector)?; + Ok(canvas.map(|canvas| { + let canvas = canvas.dyn_into::().ok(); + Self::create_window_internal(event_loop, window_id, window_descriptor, canvas) + })) + } + fn create_window_internal( + &mut self, + event_loop: &winit::event_loop::EventLoopWindowTarget<()>, + window_id: WindowId, + window_descriptor: &WindowDescriptor, + #[cfg(target_wasm = "wasm32")] canvas: Option, ) -> Window { let mut winit_window_builder = winit::window::WindowBuilder::new(); @@ -93,6 +130,12 @@ impl WinitWindows { #[allow(unused_mut)] let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title); + #[cfg(target_wasm = "wasm32")] + if let Some(canvas) = canvas { + use winit::platform::web::WindowBuilderExtWebSys; + winit_window_builder = winit_window_builder.with_canvas(canvas); + } + let winit_window = winit_window_builder.build(event_loop).unwrap(); if window_descriptor.mode == WindowMode::Windowed { From d9609c4d549c8ba438309c4ed03f51b959623fc9 Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Tue, 18 Oct 2022 23:10:56 +0200 Subject: [PATCH 6/6] Implemented the ability to specify a canvas directly on the wasm target for winit-based applications. --- crates/bevy_window/src/window.rs | 44 +++++++++++++++++++++++++- crates/bevy_winit/src/lib.rs | 26 ++++++++++++--- crates/bevy_winit/src/web_resize.rs | 8 +++-- crates/bevy_winit/src/winit_windows.rs | 23 +++++++++----- 4 files changed, 86 insertions(+), 15 deletions(-) diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 43d2ec7fb3701..8c3c33b001224 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -169,7 +169,7 @@ pub enum AbstractWindowHandle { /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). Virtual, #[cfg(target_arch = "wasm32")] - HtmlCanvas(web_sys::HtmlCanvasElement), + HtmlCanvas(HtmlCanvasElement), #[cfg(target_arch = "wasm32")] OffscreenCanvas(web_sys::OffscreenCanvas), } @@ -290,6 +290,7 @@ pub struct Window { window_handle: AbstractWindowHandle, focused: bool, mode: WindowMode, + #[cfg(target_arch = "wasm32")] fit_canvas_to_parent: bool, command_queue: Vec, } @@ -420,6 +421,7 @@ impl Window { )), focused: true, mode: window_descriptor.mode, + #[cfg(target_arch = "wasm32")] fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, command_queue: Vec::new(), } @@ -457,6 +459,7 @@ impl Window { window_handle: AbstractWindowHandle::Virtual, focused: true, mode: window_descriptor.mode, + #[cfg(target_arch = "wasm32")] fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, command_queue: Vec::new(), } @@ -948,6 +951,7 @@ impl Window { /// 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 @@ -986,6 +990,15 @@ pub enum MonitorSelection { Index(usize), } +#[cfg(target_arch = "wasm32")] +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SendSyncCanvas(pub HtmlCanvasElement); + +#[cfg(target_arch = "wasm32")] +unsafe impl Send for SendSyncCanvas {} +#[cfg(target_arch = "wasm32")] +unsafe impl Sync for SendSyncCanvas {} + /// Describes the information needed for creating a window. /// /// This should be set up before adding the [`WindowPlugin`](crate::WindowPlugin). @@ -1056,6 +1069,8 @@ pub struct WindowDescriptor { /// macOS X transparent works with winit out of the box, so this issue might be related to: /// Windows 11 is related to pub transparent: bool, + #[cfg(target_arch = "wasm32")] + pub canvas: Option, /// 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 @@ -1063,6 +1078,7 @@ pub struct WindowDescriptor { /// 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, } @@ -1083,7 +1099,33 @@ 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 { + pub fn set_canvas_from_selector( + &mut self, + selector: &str, + ) -> Result { + Ok(web_sys::window() + .unwrap() + .document() + .unwrap() + .query_selector(selector)? + .and_then(|element| element.dyn_into().ok()) + .map(|canvas| { + self.canvas = Some(SendSyncCanvas(canvas)); + true + }) + .unwrap_or(false)) + } + pub fn set_canvas(&mut self, canvas: HtmlCanvasElement) { + self.canvas = Some(SendSyncCanvas(canvas)); + } +} diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 95e7f69e70953..a25a8a4ac1b15 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -23,10 +23,12 @@ use bevy_utils::{ tracing::{error, info, trace, warn}, Instant, }; +#[cfg(target_arch = "wasm32")] +use bevy_window::SendSyncCanvas; use bevy_window::{ - AbstractWindowHandle, CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, - ModifiesWindows, ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, - WindowCloseRequested, WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized, + CreateWindow, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, ModifiesWindows, + ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, + WindowClosed, WindowCreated, WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged, Windows, }; @@ -686,6 +688,22 @@ fn handle_create_window_events( #[cfg(not(any(target_os = "windows", target_feature = "x11")))] let mut window_resized_events = world.resource_mut::>(); for create_window_event in create_window_event_reader.iter(&create_window_events) { + #[cfg(target_arch = "wasm32")] + let window = if let Some(SendSyncCanvas(canvas)) = &create_window_event.descriptor.canvas { + winit_windows.create_window_with_canvas( + event_loop, + create_window_event.id, + &create_window_event.descriptor, + canvas.clone(), + ) + } else { + winit_windows.create_window( + event_loop, + create_window_event.id, + &create_window_event.descriptor, + ) + }; + #[cfg(not(target_arch = "wasm32"))] let window = winit_windows.create_window( event_loop, create_window_event.id, @@ -709,7 +727,7 @@ fn handle_create_window_events( { let channel = world.resource_mut::(); if create_window_event.descriptor.fit_canvas_to_parent { - if let AbstractWindowHandle::HtmlCanvas(canvas) = window.window_handle { + if let Some(SendSyncCanvas(canvas)) = &create_window_event.descriptor.canvas { // PROBLEM: this path is unreachable, because we're always creating the window // based on the raw window handle above. channel.listen_to_element(create_window_event.id, canvas.clone()); diff --git a/crates/bevy_winit/src/web_resize.rs b/crates/bevy_winit/src/web_resize.rs index c666491378adc..baf3ca739221e 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -58,7 +58,7 @@ impl Default for CanvasParentResizeEventChannel { } } -fn get_size_element(element: &HtmlCanvasElement) -> Option> { +fn get_size_element(element: &web_sys::HtmlCanvasElement) -> Option> { let parent_element = element.parent_element()?; let rect = parent_element.get_bounding_client_rect(); return Some(winit::dpi::LogicalSize::new( @@ -91,7 +91,11 @@ impl CanvasParentResizeEventChannel { closure.forget(); } - pub(crate) fn listen_to_element(&self, window_id: WindowId, element: HtmlCanvasElement) { + pub(crate) fn listen_to_element( + &self, + window_id: WindowId, + element: web_sys::HtmlCanvasElement, + ) { let sender = self.sender.clone(); let resize = move || { if let Some(size) = get_size_element(&element) { diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index a447b828019c7..9c7b0d0c265f2 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -25,9 +25,16 @@ impl WinitWindows { window_id: WindowId, window_descriptor: &WindowDescriptor, ) -> Window { - Self::create_window_internal(event_loop, window_id, window_descriptor, None) + #[cfg(target_arch = "wasm32")] + { + self.create_window_internal(event_loop, window_id, window_descriptor, None) + } + #[cfg(not(target_arch = "wasm32"))] + { + self.create_window_internal(event_loop, window_id, window_descriptor) + } } - #[cfg(target_wasm = "wasm32")] + #[cfg(target_arch = "wasm32")] pub fn create_window_with_canvas( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>, @@ -35,9 +42,9 @@ impl WinitWindows { window_descriptor: &WindowDescriptor, canvas: web_sys::HtmlCanvasElement, ) -> Window { - Self::create_window_internal(event_loop, window_id, window_descriptor, Some(canvas)) + self.create_window_internal(event_loop, window_id, window_descriptor, Some(canvas)) } - #[cfg(target_wasm = "wasm32")] + #[cfg(target_arch = "wasm32")] pub fn create_window_with_selector( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>, @@ -52,7 +59,7 @@ impl WinitWindows { let canvas = document.query_selector(selector)?; Ok(canvas.map(|canvas| { let canvas = canvas.dyn_into::().ok(); - Self::create_window_internal(event_loop, window_id, window_descriptor, canvas) + self.create_window_internal(event_loop, window_id, window_descriptor, canvas) })) } fn create_window_internal( @@ -60,7 +67,7 @@ impl WinitWindows { event_loop: &winit::event_loop::EventLoopWindowTarget<()>, window_id: WindowId, window_descriptor: &WindowDescriptor, - #[cfg(target_wasm = "wasm32")] canvas: Option, + #[cfg(target_arch = "wasm32")] canvas: Option, ) -> Window { let mut winit_window_builder = winit::window::WindowBuilder::new(); @@ -130,10 +137,10 @@ impl WinitWindows { #[allow(unused_mut)] let mut winit_window_builder = winit_window_builder.with_title(&window_descriptor.title); - #[cfg(target_wasm = "wasm32")] + #[cfg(target_arch = "wasm32")] if let Some(canvas) = canvas { use winit::platform::web::WindowBuilderExtWebSys; - winit_window_builder = winit_window_builder.with_canvas(canvas); + winit_window_builder = winit_window_builder.with_canvas(Some(canvas)); } let winit_window = winit_window_builder.build(event_loop).unwrap();