From 7c31f20fa42fd5313d58411c093cdcb7f425a832 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 14 Oct 2022 16:59:04 +0200 Subject: [PATCH 1/9] Introduce `AbstractWindowHandle` enum --- crates/bevy_render/src/lib.rs | 1 + crates/bevy_render/src/view/window.rs | 13 ++++++++++--- crates/bevy_window/src/window.rs | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index dd47d53212b6b..bbf375cf79924 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -23,6 +23,7 @@ pub mod texture; pub mod view; use bevy_hierarchy::ValidParentCheckPlugin; +use bevy_window::AbstractWindowHandle; pub use extract_param::Extract; pub mod prelude { diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 8118e4fd06d8f..cd2812e6652de 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -8,6 +8,7 @@ use bevy_ecs::prelude::*; use bevy_utils::{tracing::debug, HashMap, HashSet}; use bevy_window::{ CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosed, + AbstractWindowHandle }; use std::ops::{Deref, DerefMut}; use wgpu::TextureFormat; @@ -42,7 +43,7 @@ impl Plugin for WindowRenderPlugin { pub struct ExtractedWindow { /// An entity that contains the components in [`Window`]. pub entity: Entity, - pub handle: RawHandleWrapper, + pub handle: AbstractWindowHandle, pub physical_width: u32, pub physical_height: u32, pub present_mode: PresentMode, @@ -90,7 +91,7 @@ fn extract_windows( let mut extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow { entity, - handle: handle.clone(), + handle: AbstractWindowHandle::RawWindowHandle(handle.clone()), physical_width: new_width, physical_height: new_height, present_mode: window.present_mode, @@ -148,6 +149,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::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-set systems all /// taking an unusually long time to complete, and all finishing at about the same time as the @@ -179,6 +182,10 @@ pub fn prepare_windows( mut msaa: ResMut, ) { for window in windows.windows.values_mut() { + let AbstractWindowHandle::RawWindowHandle(handle) = &window.handle else { + continue + }; + let window_surfaces = window_surfaces.deref_mut(); let surface_data = window_surfaces .surfaces @@ -187,7 +194,7 @@ pub fn prepare_windows( // NOTE: On some OSes this MUST be called from the main thread. // As of wgpu 0.15, only failable if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. let surface = render_instance - .create_surface(&window.handle.get_handle()) + .create_surface(&handle.get_handle()) .expect("Failed to create wgpu surface"); let caps = surface.get_capabilities(&render_adapter); let formats = caps.formats; diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 5b079d5137f71..ce1c3db19d128 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -450,6 +450,25 @@ impl WindowPosition { } } +/// 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(crate::RawHandleWrapper), + /// 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`). +/// /// ## Window Sizes /// /// There are three sizes associated with a window. The physical size which is From ed98f893aa153bc5896dff34016b4986078a5a02 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Mon, 17 Oct 2022 18:39:37 +0200 Subject: [PATCH 2/9] 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 7c5687121e804..5509eeaa6e680 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1900,6 +1900,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 958b1d97686b417dfcbf7aba1e8d66a8125ca10b Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Sun, 2 Oct 2022 20:59:21 +0200 Subject: [PATCH 3/9] Implemented support for creating a render surface from canvas or offscreencanvas elements on the wasm target. --- crates/bevy_render/src/lib.rs | 24 ++++++++++++----- crates/bevy_render/src/view/window.rs | 36 +++++++++++++++++--------- crates/bevy_window/Cargo.toml | 4 +++ crates/bevy_window/src/raw_handle.rs | 37 +++++++++++++++++++++++++++ crates/bevy_window/src/window.rs | 37 ++++++++++++++++----------- crates/bevy_winit/src/system.rs | 34 +++++++++++++++++------- 6 files changed, 129 insertions(+), 43 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index bbf375cf79924..85a3f8090625c 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -23,7 +23,6 @@ pub mod texture; pub mod view; use bevy_hierarchy::ValidParentCheckPlugin; -use bevy_window::AbstractWindowHandle; pub use extract_param::Extract; pub mod prelude { @@ -40,7 +39,7 @@ pub mod prelude { }; } -use bevy_window::{PrimaryWindow, RawHandleWrapper}; +use bevy_window::PrimaryWindow; use globals::GlobalsPlugin; pub use once_cell; @@ -190,12 +189,14 @@ pub struct RenderApp; impl Plugin for RenderPlugin { /// Initializes the renderer, sets up the [`RenderSet`](RenderSet) and creates the rendering sub-app. fn build(&self, app: &mut App) { + use bevy_window::AbstractHandleWrapper; + app.add_asset::() .add_debug_asset::() .init_asset_loader::() .init_debug_asset_loader::(); - let mut system_state: SystemState>> = + let mut system_state: SystemState>> = SystemState::new(&mut app.world); let primary_window = system_state.get(&app.world); @@ -206,10 +207,19 @@ impl Plugin for RenderPlugin { }); let surface = primary_window.get_single().ok().map(|wrapper| unsafe { // SAFETY: Plugins should be set up on the main thread. - let handle = wrapper.get_handle(); - instance - .create_surface(&handle) - .expect("Failed to create wgpu surface") + match wrapper { + AbstractHandleWrapper::RawHandle(handle) => instance + .create_surface(&handle.get_handle()) + .expect("Failed to create wgpu surface"), + #[cfg(target_arch = "wasm32")] + AbstractHandleWrapper::HtmlCanvas(canvas) => { + instance.create_surface_from_canvas(canvas).unwrap() + } + #[cfg(target_arch = "wasm32")] + AbstractHandleWrapper::OffscreenCanvas(offscreen_canvas) => instance + .create_surface_from_offscreen_canvas(offscreen_canvas) + .unwrap(), + } }); let request_adapter_options = wgpu::RequestAdapterOptions { diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index cd2812e6652de..c4d20ce1b1bab 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -7,8 +7,7 @@ use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_utils::{tracing::debug, HashMap, HashSet}; use bevy_window::{ - CompositeAlphaMode, PresentMode, PrimaryWindow, RawHandleWrapper, Window, WindowClosed, - AbstractWindowHandle + AbstractHandleWrapper, CompositeAlphaMode, PresentMode, PrimaryWindow, Window, WindowClosed, }; use std::ops::{Deref, DerefMut}; use wgpu::TextureFormat; @@ -43,7 +42,7 @@ impl Plugin for WindowRenderPlugin { pub struct ExtractedWindow { /// An entity that contains the components in [`Window`]. pub entity: Entity, - pub handle: AbstractWindowHandle, + pub handle: AbstractHandleWrapper, pub physical_width: u32, pub physical_height: u32, pub present_mode: PresentMode, @@ -77,7 +76,14 @@ impl DerefMut for ExtractedWindows { fn extract_windows( mut extracted_windows: ResMut, mut closed: Extract>, - windows: Extract)>>, + windows: Extract< + Query<( + Entity, + &Window, + &AbstractHandleWrapper, + Option<&PrimaryWindow>, + )>, + >, ) { for (entity, window, handle, primary) in windows.iter() { if primary.is_some() { @@ -91,7 +97,7 @@ fn extract_windows( let mut extracted_window = extracted_windows.entry(entity).or_insert(ExtractedWindow { entity, - handle: AbstractWindowHandle::RawWindowHandle(handle.clone()), + handle: handle.clone(), physical_width: new_width, physical_height: new_height, present_mode: window.present_mode, @@ -182,10 +188,6 @@ pub fn prepare_windows( mut msaa: ResMut, ) { for window in windows.windows.values_mut() { - let AbstractWindowHandle::RawWindowHandle(handle) = &window.handle else { - continue - }; - let window_surfaces = window_surfaces.deref_mut(); let surface_data = window_surfaces .surfaces @@ -193,9 +195,19 @@ pub fn prepare_windows( .or_insert_with(|| unsafe { // NOTE: On some OSes this MUST be called from the main thread. // As of wgpu 0.15, only failable if the given window is a HTML canvas and obtaining a WebGPU or WebGL2 context fails. - let surface = render_instance - .create_surface(&handle.get_handle()) - .expect("Failed to create wgpu surface"); + let surface = match &window.handle { + AbstractHandleWrapper::RawHandle(handle) => render_instance + .create_surface(&handle.get_handle()) + .expect("Failed to create wgpu surface"), + #[cfg(target_arch = "wasm32")] + AbstractHandleWrapper::HtmlCanvas(canvas) => render_instance + .create_surface_from_canvas(canvas) + .expect("Failed to create wgpu surface"), + #[cfg(target_arch = "wasm32")] + AbstractHandleWrapper::OffscreenCanvas(canvas) => render_instance + .create_surface_from_offscreen_canvas(canvas) + .expect("Failed to create wgpu surface"), + }; let caps = surface.get_capabilities(&render_adapter); let formats = caps.formats; // For future HDR output support, we'll need to request a format that supports HDR, diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index c65bcc6e3b97d..78acb62eaf711 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -27,3 +27,7 @@ raw-window-handle = "0.5" # other serde = { version = "1.0", features = ["derive"], optional = true } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +web-sys = "0.3" +wasm-bindgen = "0.2" diff --git a/crates/bevy_window/src/raw_handle.rs b/crates/bevy_window/src/raw_handle.rs index 6c535605991f0..95fd422103b5b 100644 --- a/crates/bevy_window/src/raw_handle.rs +++ b/crates/bevy_window/src/raw_handle.rs @@ -2,6 +2,8 @@ use bevy_ecs::prelude::Component; use raw_window_handle::{ HasRawDisplayHandle, HasRawWindowHandle, RawDisplayHandle, RawWindowHandle, }; +#[cfg(target_arch = "wasm32")] +use web_sys::{HtmlCanvasElement, OffscreenCanvas}; /// A wrapper over [`RawWindowHandle`] and [`RawDisplayHandle`] that allows us to safely pass it across threads. /// @@ -74,3 +76,38 @@ unsafe impl HasRawDisplayHandle for ThreadLockedRawWindowHandleWrapper { self.0.get_display_handle() } } + +/// Handle used for creating surfaces in the render plugin +/// +/// Either a raw handle to an OS window or some canvas flavor when compiling on wasm. +#[derive(Clone, Debug, Component)] +pub enum AbstractHandle { + /// The window corresponds to an operator system window. + RawHandle(T), + #[cfg(target_arch = "wasm32")] + HtmlCanvas(HtmlCanvasElement), + #[cfg(target_arch = "wasm32")] + OffscreenCanvas(OffscreenCanvas), +} + +impl Default for AbstractHandle +where + T: Default, +{ + fn default() -> Self { + Self::RawHandle(Default::default()) + } +} + +pub type AbstractHandlePlaceholder = AbstractHandle<()>; +pub type AbstractHandleWrapper = AbstractHandle; + +#[cfg(target_arch = "wasm32")] +unsafe impl Send for AbstractHandleWrapper {} +#[cfg(target_arch = "wasm32")] +unsafe impl Sync for AbstractHandleWrapper {} + +#[cfg(target_arch = "wasm32")] +unsafe impl Send for AbstractHandlePlaceholder {} +#[cfg(target_arch = "wasm32")] +unsafe impl Sync for AbstractHandlePlaceholder {} diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index ce1c3db19d128..1e500160ac517 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -10,6 +10,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_utils::tracing::warn; +use crate::raw_handle::AbstractHandlePlaceholder; use crate::CursorIcon; /// Marker component for the window considered the primary window. @@ -115,6 +116,26 @@ pub struct Window { /// Note: This does not stop the program from fullscreening/setting /// the size programmatically. pub resizable: bool, + /// Determines with which rendering context window should be associated. + /// + /// ## Platform-specific + /// + /// For non-web platforms there exists only one enum variant that indicates + /// that `winit` should use `RawWindowHandle`. + /// Note that this field doesn't hold handle value itself, + /// i.e. you don't need to create the window yourself. + /// Instead it is generated when winit creates a new window for you. + /// You can safely pass `Default::default()`. + /// + /// For web the enum offers two more enum variants: one for html canvas and offscreen canvas. + /// Those are later piped through to `wgpu`. + /// + /// ## Reflection + /// + /// On `wasm32` this field contains `js-sys` objects which are neither `Send` nor `Sync` + /// nor implement `Reflect`. + #[reflect(ignore)] + pub handle: AbstractHandlePlaceholder, /// Should the window have decorations enabled? /// /// (Decorations are the minimize, maximize, and close buttons on desktop apps) @@ -199,6 +220,7 @@ impl Default for Window { internal: Default::default(), composite_alpha_mode: Default::default(), resize_constraints: Default::default(), + handle: Default::default(), ime_enabled: Default::default(), ime_position: Default::default(), resizable: true, @@ -450,21 +472,6 @@ impl WindowPosition { } } -/// 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(crate::RawHandleWrapper), - /// 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`). diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 592251cbcf1e4..6a375ccb3a09c 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -65,15 +65,31 @@ pub(crate) fn create_window<'a>( window .resolution .set_scale_factor(winit_window.scale_factor()); - commands - .entity(entity) - .insert(RawHandleWrapper { - window_handle: winit_window.raw_window_handle(), - display_handle: winit_window.raw_display_handle(), - }) - .insert(CachedWindow { - window: window.clone(), - }); + + let handle = { + use bevy_window::{AbstractHandlePlaceholder, AbstractHandleWrapper}; + + match &window.handle { + AbstractHandlePlaceholder::RawHandle(()) => { + AbstractHandleWrapper::RawHandle(RawHandleWrapper { + window_handle: winit_window.raw_window_handle(), + display_handle: winit_window.raw_display_handle(), + }) + } + #[cfg(target_arch = "wasm32")] + AbstractHandlePlaceholder::HtmlCanvas(canvas) => { + AbstractHandleWrapper::HtmlCanvas(canvas.clone()) + } + #[cfg(target_arch = "wasm32")] + AbstractHandlePlaceholder::OffscreenCanvas(canvas) => { + AbstractHandleWrapper::OffscreenCanvas(canvas.clone()) + } + } + }; + + commands.entity(entity).insert(handle).insert(CachedWindow { + window: window.clone(), + }); #[cfg(target_arch = "wasm32")] { From 0bf959a46c76ae562dfebf1cfc0a47f6b3fe0eaa Mon Sep 17 00:00:00 2001 From: Andreas Monitzer Date: Mon, 17 Oct 2022 00:06:13 +0200 Subject: [PATCH 4/9] Attempt at allowing winit-using code to use a specific HtmlCanvasElement. Doesn't handle OffscreenCanvas properly yet. --- crates/bevy_winit/src/system.rs | 20 +++++++++----- crates/bevy_winit/src/web_resize.rs | 38 ++++++++++++++------------ crates/bevy_winit/src/winit_windows.rs | 18 ++++-------- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 6a375ccb3a09c..3538689916759 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -20,7 +20,7 @@ use winit::{ }; #[cfg(target_arch = "wasm32")] -use crate::web_resize::{CanvasParentResizeEventChannel, WINIT_CANVAS_SELECTOR}; +use crate::web_resize::CanvasParentResizeEventChannel; use crate::{ accessibility::{AccessKitAdapters, WinitActionHandlers}, converters::{self, convert_window_level}, @@ -93,13 +93,19 @@ pub(crate) fn create_window<'a>( #[cfg(target_arch = "wasm32")] { + use bevy_window::AbstractHandlePlaceholder; + if window.fit_canvas_to_parent { - let selector = if let Some(selector) = &window.canvas { - selector - } else { - WINIT_CANVAS_SELECTOR - }; - event_channel.listen_to_selector(entity, selector); + // We ignore other two variants. + match &window.handle { + AbstractHandlePlaceholder::RawHandle(_) => (), + AbstractHandlePlaceholder::HtmlCanvas(canvas) => { + event_channel.listen_to_element(entity, canvas.clone()); + } + AbstractHandlePlaceholder::OffscreenCanvas(_) => { + todo!() + } + } } } diff --git a/crates/bevy_winit/src/web_resize.rs b/crates/bevy_winit/src/web_resize.rs index a53075dea3883..6b08beffff8ca 100644 --- a/crates/bevy_winit/src/web_resize.rs +++ b/crates/bevy_winit/src/web_resize.rs @@ -3,6 +3,7 @@ use bevy_app::{App, Plugin, Update}; use bevy_ecs::prelude::*; use crossbeam_channel::{Receiver, Sender}; use wasm_bindgen::JsCast; +use web_sys::HtmlCanvasElement; use winit::dpi::LogicalSize; pub(crate) struct CanvasParentResizePlugin; @@ -36,33 +37,34 @@ fn canvas_parent_resize_event_handler( } } -fn get_size(selector: &str) -> Option> { - let win = web_sys::window().unwrap(); - let doc = win.document().unwrap(); - let element = doc.query_selector(selector).ok()??; - 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 { fn default() -> Self { let (sender, receiver) = crossbeam_channel::unbounded(); - return Self { sender, receiver }; + Self { sender, receiver } } } +fn get_size_element(element: &HtmlCanvasElement) -> Option> { + let parent_element = element.parent_element()?; + let rect = parent_element.get_bounding_client_rect(); + Some(winit::dpi::LogicalSize::new( + rect.width() as f32, + rect.height() as f32, + )) +} + impl CanvasParentResizeEventChannel { - pub(crate) fn listen_to_selector(&self, window: Entity, selector: &str) { + /// Listen to resize events on the element + /// + /// ## Panic + /// + /// Do not call from a web-worker! + /// This method uses global `window` object to attach events to, + /// which doesn't exist inside worker context. + pub(crate) fn listen_to_element(&self, window: Entity, element: HtmlCanvasElement) { let sender = self.sender.clone(); - let owned_selector = selector.to_string(); let resize = move || { - if let Some(size) = get_size(&owned_selector) { + if let Some(size) = get_size_element(&element) { sender.send(ResizeEvent { size, window }).unwrap(); } }; diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 6bc5995490714..f75699c7a46f9 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -120,21 +120,15 @@ impl WinitWindows { #[cfg(target_arch = "wasm32")] { - use wasm_bindgen::JsCast; + use bevy_window::AbstractHandlePlaceholder; use winit::platform::web::WindowBuilderExtWebSys; - if let Some(selector) = &window.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); + match &window.handle { + AbstractHandlePlaceholder::RawHandle(_) => todo!(), + AbstractHandlePlaceholder::HtmlCanvas(canvas) => { + winit_window_builder = winit_window_builder.with_canvas(Some(canvas.clone())); } + AbstractHandlePlaceholder::OffscreenCanvas(_) => todo!(), } winit_window_builder = From 7a7de7c365667e1ca3a5792858a1845d68b09d97 Mon Sep 17 00:00:00 2001 From: haibane_tenshi Date: Thu, 30 Mar 2023 15:53:52 +0300 Subject: [PATCH 5/9] Groom canvas-related APIs, OffscreenCanvas still not working. --- crates/bevy_render/src/lib.rs | 17 +++-- crates/bevy_render/src/view/window.rs | 19 ++++-- crates/bevy_window/src/raw_handle.rs | 74 ++++++++++++++------- crates/bevy_window/src/window.rs | 91 ++++++++++++++++++-------- crates/bevy_winit/src/system.rs | 81 ++++++++++++----------- crates/bevy_winit/src/winit_windows.rs | 35 +++++++--- 6 files changed, 205 insertions(+), 112 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 85a3f8090625c..e6e56f9b89e95 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -212,13 +212,18 @@ impl Plugin for RenderPlugin { .create_surface(&handle.get_handle()) .expect("Failed to create wgpu surface"), #[cfg(target_arch = "wasm32")] - AbstractHandleWrapper::HtmlCanvas(canvas) => { - instance.create_surface_from_canvas(canvas).unwrap() + AbstractHandleWrapper::WebHandle(web_handle) => { + use bevy_window::WebHandle; + + match web_handle { + WebHandle::HtmlCanvas(canvas) => { + instance.create_surface_from_canvas(canvas).unwrap() + } + WebHandle::OffscreenCanvas(offscreen_canvas) => instance + .create_surface_from_offscreen_canvas(offscreen_canvas) + .unwrap(), + } } - #[cfg(target_arch = "wasm32")] - AbstractHandleWrapper::OffscreenCanvas(offscreen_canvas) => instance - .create_surface_from_offscreen_canvas(offscreen_canvas) - .unwrap(), } }); diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index c4d20ce1b1bab..8324bae29ab29 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -200,13 +200,18 @@ pub fn prepare_windows( .create_surface(&handle.get_handle()) .expect("Failed to create wgpu surface"), #[cfg(target_arch = "wasm32")] - AbstractHandleWrapper::HtmlCanvas(canvas) => render_instance - .create_surface_from_canvas(canvas) - .expect("Failed to create wgpu surface"), - #[cfg(target_arch = "wasm32")] - AbstractHandleWrapper::OffscreenCanvas(canvas) => render_instance - .create_surface_from_offscreen_canvas(canvas) - .expect("Failed to create wgpu surface"), + AbstractHandleWrapper::WebHandle(web_handle) => { + use bevy_window::WebHandle; + + match web_handle { + WebHandle::HtmlCanvas(canvas) => render_instance + .create_surface_from_canvas(canvas) + .expect("Failed to create wgpu surface"), + WebHandle::OffscreenCanvas(canvas) => render_instance + .create_surface_from_offscreen_canvas(canvas) + .expect("Failed to create wgpu surface"), + } + } }; let caps = surface.get_capabilities(&render_adapter); let formats = caps.formats; diff --git a/crates/bevy_window/src/raw_handle.rs b/crates/bevy_window/src/raw_handle.rs index 95fd422103b5b..dfaf31bf1d798 100644 --- a/crates/bevy_window/src/raw_handle.rs +++ b/crates/bevy_window/src/raw_handle.rs @@ -79,35 +79,61 @@ unsafe impl HasRawDisplayHandle for ThreadLockedRawWindowHandleWrapper { /// Handle used for creating surfaces in the render plugin /// -/// Either a raw handle to an OS window or some canvas flavor when compiling on wasm. -#[derive(Clone, Debug, Component)] -pub enum AbstractHandle { +/// Either a raw handle to an OS window or some canvas flavor on wasm. +/// For non-web platforms it essentially compiles down to newtype wrapper around `RawHandleWrapper`. +/// +/// # Details +/// +/// `RawHandleWrapper` is not particularly useful on wasm. +/// +/// * `RawDisplayHandle` is entirely ignored as Bevy has no control over +/// where the element is going to be displayed. +/// * `RawWindowHandle::Web` contains a single `u32` as payload. +/// `wgpu` uses that in a css selector to discover canvas element. +/// +/// This system is overly rigid and fragile. +/// Regardless of how we specify the target element `wgpu` have to land on `WebGl2RenderingContext` +/// in order to render anything. +/// However that prevents us from directly specifying which element it should use. +/// This is especially bad when Bevy is run from web-worker context: +/// workers don't have access to DOM, so it inevitably leads to panic! +/// +/// It is understandable why `RawWindowHandle` doesn't include JS objects, +/// so instead we use `AbstractHandleWrapper` to provide a workaround. +/// +/// # Note +/// +/// While workable it might be possible to remove this abstraction. +/// At the end of the day interpretation of `RawWindowHandle::Web` payload is up to us. +/// We can intercept it before it makes to `wgpu::Instance` and use it to look up +/// `HtmlCanvasElement` or `OffscreenCanvas` from global memory +/// (which will be different on whether Bevy runs as main or worker) +/// and pass that to `wgpu`. +/// This will require a bunch of extra machinery and will be confusing to users +/// which don't rely on `bevy_winit` but can be an option in case this abstraction is undesirable. +#[derive(Debug, Clone, Component)] +pub enum AbstractHandleWrapper { /// The window corresponds to an operator system window. - RawHandle(T), - #[cfg(target_arch = "wasm32")] - HtmlCanvas(HtmlCanvasElement), - #[cfg(target_arch = "wasm32")] - OffscreenCanvas(OffscreenCanvas), -} + RawHandle(RawHandleWrapper), -impl Default for AbstractHandle -where - T: Default, -{ - fn default() -> Self { - Self::RawHandle(Default::default()) - } + /// A handle to JS object containing rendering surface. + #[cfg(target_arch = "wasm32")] + WebHandle(WebHandle), } -pub type AbstractHandlePlaceholder = AbstractHandle<()>; -pub type AbstractHandleWrapper = AbstractHandle; - -#[cfg(target_arch = "wasm32")] -unsafe impl Send for AbstractHandleWrapper {} +/// A `Send + Sync` wrapper around `HtmlCanvasElement` or `OffscreenCanvas`. +/// +/// # Safety +/// +/// Only safe to use from the main thread. #[cfg(target_arch = "wasm32")] -unsafe impl Sync for AbstractHandleWrapper {} +#[derive(Debug, Clone, Component)] +pub enum WebHandle { + HtmlCanvas(HtmlCanvasElement), + OffscreenCanvas(OffscreenCanvas), +} #[cfg(target_arch = "wasm32")] -unsafe impl Send for AbstractHandlePlaceholder {} +unsafe impl Send for WebHandle {} #[cfg(target_arch = "wasm32")] -unsafe impl Sync for AbstractHandlePlaceholder {} +unsafe impl Sync for WebHandle {} diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 1e500160ac517..9844a61af6662 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -10,7 +10,6 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; use bevy_utils::tracing::warn; -use crate::raw_handle::AbstractHandlePlaceholder; use crate::CursorIcon; /// Marker component for the window considered the primary window. @@ -116,26 +115,6 @@ pub struct Window { /// Note: This does not stop the program from fullscreening/setting /// the size programmatically. pub resizable: bool, - /// Determines with which rendering context window should be associated. - /// - /// ## Platform-specific - /// - /// For non-web platforms there exists only one enum variant that indicates - /// that `winit` should use `RawWindowHandle`. - /// Note that this field doesn't hold handle value itself, - /// i.e. you don't need to create the window yourself. - /// Instead it is generated when winit creates a new window for you. - /// You can safely pass `Default::default()`. - /// - /// For web the enum offers two more enum variants: one for html canvas and offscreen canvas. - /// Those are later piped through to `wgpu`. - /// - /// ## Reflection - /// - /// On `wasm32` this field contains `js-sys` objects which are neither `Send` nor `Sync` - /// nor implement `Reflect`. - #[reflect(ignore)] - pub handle: AbstractHandlePlaceholder, /// Should the window have decorations enabled? /// /// (Decorations are the minimize, maximize, and close buttons on desktop apps) @@ -163,14 +142,37 @@ pub struct Window { /// /// - iOS / Android / Web / Wayland: Unsupported. pub window_level: WindowLevel, - /// The "html canvas" element selector. + /// Instructs which web element window should be associated with. /// - /// 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). + /// ## Platform-specific /// - /// This value has no effect on non-web platforms. - pub canvas: Option, + /// This field is ignored for non-web platforms. + /// You can safely initialize it to `Default::default()`. + /// + /// For web platform the enum determines how `WinitPlugin` is going to discover + /// which web element the window should be associated with. + /// + /// ## Panic safety + /// + /// On `wasm32` it is important to know *how* Bevy is going to be run. + /// Wasm can be run either as **main** (e.g. on main JS event loop) or as web **worker**. + /// + /// * When run as **main**, all web APIs are available so all variants for `WebElement` will work. + /// * When run as **worker** only `WebElement::OffscreenCanvas` is safe, other variants will panic. + /// + /// This happens because: + /// * `WebElement::Generate` and `WebElement::CssSelector` require access to DOM which worker doesn't have. + /// * Worker cannot directly interact with WebGL context of `HtmlCanvasElement`. + /// + /// For more details on web-worker APIs see [MDN docs](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API). + /// + /// Note that by default the field is initialized to `Generate` and it will panic for web workers! + /// + /// ## Reflection + /// + /// On `wasm32` this field contains `js-sys` objects which don't implement `Reflect`. + #[reflect(ignore)] + pub web_element: WebElement, /// 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 @@ -220,7 +222,6 @@ impl Default for Window { internal: Default::default(), composite_alpha_mode: Default::default(), resize_constraints: Default::default(), - handle: Default::default(), ime_enabled: Default::default(), ime_position: Default::default(), resizable: true, @@ -228,9 +229,9 @@ impl Default for Window { transparent: false, focused: true, window_level: Default::default(), + web_element: Default::default(), fit_canvas_to_parent: false, prevent_default_event_handling: true, - canvas: None, } } } @@ -852,3 +853,35 @@ pub enum WindowLevel { /// The window will always be on top of normal windows. AlwaysOnTop, } + +/// Instructs which web element window should be associated with. +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub enum WebElement { + /// Generate a new `HtmlCanvasElement` and attach it to body. + /// + /// This option is good for quick testing/setup, + /// but consider choosing more controllable behavior. + #[default] + Generate, + + /// Discover `HtmlCanvasElement` via a css selector. + /// + /// # Panic + /// + /// This option will panic if the discovered element is not a canvas. + #[cfg(target_arch = "wasm32")] + CssSelector(String), + + /// Use specified `HtmlCanvasElement`. + #[cfg(target_arch = "wasm32")] + HtmlCanvas(web_sys::HtmlCanvasElement), + + /// Use specified `OffscreenCanvas`. + #[cfg(target_arch = "wasm32")] + OffscreenCanvas(web_sys::OffscreenCanvas), +} + +#[cfg(target_arch = "wasm32")] +unsafe impl Send for WebElement {} +#[cfg(target_arch = "wasm32")] +unsafe impl Sync for WebElement {} diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index 3538689916759..ea76d071ff0fe 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -11,8 +11,7 @@ use bevy_utils::{ tracing::{error, info, warn}, HashMap, }; -use bevy_window::{RawHandleWrapper, Window, WindowClosed, WindowCreated}; -use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; +use bevy_window::{Window, WindowClosed, WindowCreated}; use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, @@ -44,6 +43,8 @@ pub(crate) fn create_window<'a>( #[cfg(target_arch = "wasm32")] event_channel: ResMut, ) { for (entity, mut window) in created_windows { + use bevy_window::AbstractHandleWrapper; + if winit_windows.get_window(entity).is_some() { continue; } @@ -66,49 +67,53 @@ pub(crate) fn create_window<'a>( .resolution .set_scale_factor(winit_window.scale_factor()); - let handle = { - use bevy_window::{AbstractHandlePlaceholder, AbstractHandleWrapper}; + let handle: AbstractHandleWrapper = { + // Cannot apply attributes to expressions yet :( + #[allow(unused_mut)] + let mut handle; - match &window.handle { - AbstractHandlePlaceholder::RawHandle(()) => { - AbstractHandleWrapper::RawHandle(RawHandleWrapper { - window_handle: winit_window.raw_window_handle(), - display_handle: winit_window.raw_display_handle(), - }) - } - #[cfg(target_arch = "wasm32")] - AbstractHandlePlaceholder::HtmlCanvas(canvas) => { - AbstractHandleWrapper::HtmlCanvas(canvas.clone()) - } - #[cfg(target_arch = "wasm32")] - AbstractHandlePlaceholder::OffscreenCanvas(canvas) => { - AbstractHandleWrapper::OffscreenCanvas(canvas.clone()) + #[cfg(not(target_arch = "wasm32"))] + { + handle = AbstractHandleWrapper::RawHandle(RawHandleWrapper { + window_handle: winit_window.raw_window_handle(), + display_handle: winit_window.raw_display_handle(), + }); + } + + #[cfg(target_arch = "wasm32")] + { + use bevy_window::{WebElement, WebHandle}; + + let web_handle = match &window.web_element { + WebElement::Generate => todo!(), + WebElement::CssSelector(_) => todo!(), + WebElement::HtmlCanvas(canvas) => WebHandle::HtmlCanvas(canvas.clone()), + WebElement::OffscreenCanvas(canvas) => { + WebHandle::OffscreenCanvas(canvas.clone()) + } + }; + + if window.fit_canvas_to_parent { + match &web_handle { + WebHandle::HtmlCanvas(canvas) => { + event_channel.listen_to_element(entity, canvas.clone()); + } + WebHandle::OffscreenCanvas(_) => { + todo!() + } + } } + + handle = AbstractHandleWrapper::WebHandle(web_handle); } + + handle }; commands.entity(entity).insert(handle).insert(CachedWindow { window: window.clone(), }); - #[cfg(target_arch = "wasm32")] - { - use bevy_window::AbstractHandlePlaceholder; - - if window.fit_canvas_to_parent { - // We ignore other two variants. - match &window.handle { - AbstractHandlePlaceholder::RawHandle(_) => (), - AbstractHandlePlaceholder::HtmlCanvas(canvas) => { - event_channel.listen_to_element(entity, canvas.clone()); - } - AbstractHandlePlaceholder::OffscreenCanvas(_) => { - todo!() - } - } - } - } - event_writer.send(WindowCreated { window: entity }); } } @@ -300,8 +305,8 @@ pub(crate) fn changed_window( } #[cfg(target_arch = "wasm32")] - if window.canvas != cache.window.canvas { - window.canvas = cache.window.canvas.clone(); + if window.web_element != cache.window.web_element { + window.web_element = cache.window.web_element.clone(); warn!( "Bevy currently doesn't support modifying the window canvas after initialization." ); diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index f75699c7a46f9..ca39697dda29f 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -120,16 +120,34 @@ impl WinitWindows { #[cfg(target_arch = "wasm32")] { - use bevy_window::AbstractHandlePlaceholder; + use bevy_window::WebElement; use winit::platform::web::WindowBuilderExtWebSys; - match &window.handle { - AbstractHandlePlaceholder::RawHandle(_) => todo!(), - AbstractHandlePlaceholder::HtmlCanvas(canvas) => { - winit_window_builder = winit_window_builder.with_canvas(Some(canvas.clone())); + let canvas = match &window.web_element { + // We let winit create canvas element for us. + // We will attach it to DOM later. + WebElement::Generate => None, + WebElement::CssSelector(selector) => { + use wasm_bindgen::JsCast; + + let win = web_sys::window() + .expect("bevy must run from main loop to use css selector"); + let doc = win.document().unwrap(); + let element = doc + .query_selector(selector) + .unwrap() + .expect("no element is fitting the selector query"); + let canvas = element + .dyn_into::() + .expect("selector must point to a canvas element"); + + Some(canvas) } - AbstractHandlePlaceholder::OffscreenCanvas(_) => todo!(), - } + WebElement::HtmlCanvas(canvas) => Some(canvas.clone()), + WebElement::OffscreenCanvas(_) => todo!(), + }; + + winit_window_builder = winit_window_builder.with_canvas(canvas); winit_window_builder = winit_window_builder.with_prevent_default(window.prevent_default_event_handling) @@ -184,9 +202,10 @@ impl WinitWindows { #[cfg(target_arch = "wasm32")] { + use bevy_window::WebElement; use winit::platform::web::WindowExtWebSys; - if window.canvas.is_none() { + if let &WebElement::Generate = &window.web_element { let canvas = winit_window.canvas(); let window = web_sys::window().unwrap(); From bda7a3fc33077ce3d966681405d4338c375242c0 Mon Sep 17 00:00:00 2001 From: haibane_tenshi Date: Thu, 30 Mar 2023 19:00:06 +0300 Subject: [PATCH 6/9] Make WinitWindows::crate_window panic when trying to create window backed up by OffscreenCanvas. --- crates/bevy_winit/src/winit_windows.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index ca39697dda29f..b1bb3d18fc455 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -144,7 +144,9 @@ impl WinitWindows { Some(canvas) } WebElement::HtmlCanvas(canvas) => Some(canvas.clone()), - WebElement::OffscreenCanvas(_) => todo!(), + WebElement::OffscreenCanvas(_) => { + panic!("winit cannot manage windows backed by OffscreenCanvas") + } }; winit_window_builder = winit_window_builder.with_canvas(canvas); From 7dc71c07794cc2f74340ceee7b2454bd28dd765d Mon Sep 17 00:00:00 2001 From: haibane_tenshi Date: Thu, 30 Mar 2023 19:16:33 +0300 Subject: [PATCH 7/9] Make bevy_winit::system::create_window respect Window.web_element options. --- crates/bevy_winit/src/system.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index ea76d071ff0fe..fa82c60015297 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -68,9 +68,7 @@ pub(crate) fn create_window<'a>( .set_scale_factor(winit_window.scale_factor()); let handle: AbstractHandleWrapper = { - // Cannot apply attributes to expressions yet :( - #[allow(unused_mut)] - let mut handle; + let handle; #[cfg(not(target_arch = "wasm32"))] { @@ -85,8 +83,12 @@ pub(crate) fn create_window<'a>( use bevy_window::{WebElement, WebHandle}; let web_handle = match &window.web_element { - WebElement::Generate => todo!(), - WebElement::CssSelector(_) => todo!(), + // Canvas is already created/discovered by winit. + WebElement::Generate | WebElement::CssSelector(_) => { + use winit::platform::web::WindowExtWebSys; + + WebHandle::HtmlCanvas(winit_window.canvas()) + } WebElement::HtmlCanvas(canvas) => WebHandle::HtmlCanvas(canvas.clone()), WebElement::OffscreenCanvas(canvas) => { WebHandle::OffscreenCanvas(canvas.clone()) @@ -98,9 +100,8 @@ pub(crate) fn create_window<'a>( WebHandle::HtmlCanvas(canvas) => { event_channel.listen_to_element(entity, canvas.clone()); } - WebHandle::OffscreenCanvas(_) => { - todo!() - } + // OffscreenCanvas exists outside DOM tree. + WebHandle::OffscreenCanvas(_) => (), } } From 837f870206ac9aa4debe9dc75229e951e18a8aa3 Mon Sep 17 00:00:00 2001 From: haibane_tenshi Date: Thu, 30 Mar 2023 19:29:07 +0300 Subject: [PATCH 8/9] Re-add non-wasm related imports for bevy_winit::system::create_window that somehow got lost in refactors --- crates/bevy_winit/src/system.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/bevy_winit/src/system.rs b/crates/bevy_winit/src/system.rs index fa82c60015297..6abd3ad3c14bb 100644 --- a/crates/bevy_winit/src/system.rs +++ b/crates/bevy_winit/src/system.rs @@ -72,6 +72,9 @@ pub(crate) fn create_window<'a>( #[cfg(not(target_arch = "wasm32"))] { + use bevy_window::RawHandleWrapper; + use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle}; + handle = AbstractHandleWrapper::RawHandle(RawHandleWrapper { window_handle: winit_window.raw_window_handle(), display_handle: winit_window.raw_display_handle(), From 181bd4a5dcae214e7e6751b68c0f471452472803 Mon Sep 17 00:00:00 2001 From: haibane_tenshi Date: Fri, 31 Mar 2023 21:08:44 +0300 Subject: [PATCH 9/9] Remove vestigial bits from old PR. --- Cargo.toml | 7 - crates/bevy_render/src/view/window.rs | 2 - crates/bevy_window/Cargo.toml | 1 - crates/bevy_window/src/window.rs | 4 - examples/window/virtual.rs | 211 -------------------------- 5 files changed, 225 deletions(-) delete mode 100644 examples/window/virtual.rs diff --git a/Cargo.toml b/Cargo.toml index 5509eeaa6e680..7c5687121e804 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1900,13 +1900,6 @@ 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/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 8324bae29ab29..a029290221765 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -155,8 +155,6 @@ pub struct WindowSurfaces { /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// -/// 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-set systems all /// taking an unusually long time to complete, and all finishing at about the same time as the diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 78acb62eaf711..3b0780e19fd7e 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -30,4 +30,3 @@ serde = { version = "1.0", features = ["derive"], optional = true } [target.'cfg(target_arch = "wasm32")'.dependencies] web-sys = "0.3" -wasm-bindgen = "0.2" diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 9844a61af6662..5337afc6e42c3 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -473,10 +473,6 @@ impl WindowPosition { } } -/// An operating system or virtual window that can present content and receive user input. -/// -/// To create a window, use a [`EventWriter`](`crate::CreateWindow`). -/// /// ## Window Sizes /// /// There are three sizes associated with a window. The physical size which is diff --git a/examples/window/virtual.rs b/examples/window/virtual.rs deleted file mode 100644 index 99ecd3a1ea68f..0000000000000 --- a/examples/window/virtual.rs +++ /dev/null @@ -1,211 +0,0 @@ -//! 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, - )); -}