From 7a28eb9b91d446f51f2faf6d4a17b64e11c8f198 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 14 Oct 2022 14:27:37 +0200 Subject: [PATCH 1/5] 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 | 143 +++++++++++++------------- crates/bevy_window/src/window.rs | 63 ++++++++++-- 2 files changed, 128 insertions(+), 78 deletions(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 627c96e8c9de8..dd8d25cdb2489 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -144,6 +144,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 @@ -173,79 +175,78 @@ pub fn prepare_windows( render_instance: Res, render_adapter: Res, ) { - for window in windows - .windows - .values_mut() - // value of raw_handle is only None in synthetic tests - .filter(|x| x.raw_handle.is_some()) - { - let window_surfaces = window_surfaces.deref_mut(); - let surface_data = window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - let surface = render_instance - .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); - let format = *surface - .get_supported_formats(&render_adapter) - .get(0) - .unwrap_or_else(|| { - panic!( - "No supported formats found for surface {:?} on adapter {:?}", - surface, render_adapter - ) + for window in windows.windows.values_mut() { + if let Some(handle) = &window.handle { + let window_surfaces = window_surfaces.deref_mut(); + let surface_data = + window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + let surface = render_instance + .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); + let format = *surface + .get_supported_formats(&render_adapter) + .get(0) + .unwrap_or_else(|| { + panic!( + "No supported formats found for surface {:?} on adapter {:?}", + surface, render_adapter + ) + }); + SurfaceData { surface, format } }); - SurfaceData { surface, format } - }); - - let surface_configuration = wgpu::SurfaceConfiguration { - format: surface_data.format, - 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, - }, - alpha_mode: match window.alpha_mode { - CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto, - CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque, - CompositeAlphaMode::PreMultiplied => wgpu::CompositeAlphaMode::PreMultiplied, - CompositeAlphaMode::PostMultiplied => wgpu::CompositeAlphaMode::PostMultiplied, - CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - let frame = if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { - render_device.configure_surface(&surface_data.surface, &surface_configuration); - surface_data - .surface - .get_current_texture() - .expect("Error configuring surface") - } else { - match surface_data.surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { - render_device.configure_surface(&surface_data.surface, &surface_configuration); - surface_data - .surface - .get_current_texture() - .expect("Error reconfiguring surface") + + let surface_configuration = wgpu::SurfaceConfiguration { + format: surface_data.format, + 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, + }, + alpha_mode: match window.alpha_mode { + CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto, + CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque, + CompositeAlphaMode::PreMultiplied => wgpu::CompositeAlphaMode::PreMultiplied, + CompositeAlphaMode::PostMultiplied => wgpu::CompositeAlphaMode::PostMultiplied, + CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + let frame = if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { + render_device.configure_surface(&surface_data.surface, &surface_configuration); + surface_data + .surface + .get_current_texture() + .expect("Error configuring surface") + } else { + match surface_data.surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { + render_device + .configure_surface(&surface_data.surface, &surface_configuration); + surface_data + .surface + .get_current_texture() + .expect("Error reconfiguring surface") + } + err => err.expect("Failed to acquire next swap chain texture!"), } - err => err.expect("Failed to acquire next swap chain texture!"), - } - }; + }; - window.swap_chain_texture = Some(TextureView::from(frame)); - window.swap_chain_texture_format = Some(surface_data.format); + window.swap_chain_texture = Some(TextureView::from(frame)); + window.swap_chain_texture_format = Some(surface_data.format); + } } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 1c7416b2c96a2..f4d8d1417eee6 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -179,7 +179,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`). /// @@ -411,7 +411,7 @@ impl Window { physical_height: u32, scale_factor: f64, position: Option, - raw_handle: Option, + raw_handle: RawHandleWrapper, ) -> Self { Window { id, @@ -431,7 +431,7 @@ impl Window { cursor_grab_mode: window_descriptor.cursor_grab_mode, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_handle, + raw_handle: Some(raw_handle), focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -440,6 +440,49 @@ impl Window { alpha_mode: window_descriptor.alpha_mode, } } + + /// 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 { @@ -822,11 +865,17 @@ impl Window { pub fn is_focused(&self) -> bool { self.focused } - /// Get the [`RawHandleWrapper`] 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. - pub fn raw_handle(&self) -> Option { - self.raw_handle.as_ref().cloned() + /// See [`Self::new_virtual`]. + pub fn raw_window_handle(&self) -> Option { + self.raw_handle.clone() } /// The "html canvas" element selector. From 006dd273c5a56782a788b45e8fc199b3d15fe01e Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 14 Oct 2022 16:59:04 +0200 Subject: [PATCH 2/5] Introduce `AbstractWindowHandle` enum --- crates/bevy_render/src/lib.rs | 21 +-- crates/bevy_render/src/view/window.rs | 175 +++++++++++++++---------- crates/bevy_window/src/window.rs | 40 +++--- crates/bevy_winit/src/winit_windows.rs | 2 +- 4 files changed, 138 insertions(+), 100 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 59b5cc5b462b8..f8cea30fddabc 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 { @@ -142,17 +143,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_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 dd8d25cdb2489..6e83120f785ed 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -7,7 +7,8 @@ use bevy_app::{App, Plugin}; use bevy_ecs::prelude::*; use bevy_utils::{tracing::debug, HashMap, HashSet}; use bevy_window::{ - CompositeAlphaMode, PresentMode, RawHandleWrapper, WindowClosed, WindowId, Windows, + AbstractWindowHandle, CompositeAlphaMode, PresentMode, RawHandleWrapper, WindowClosed, + WindowId, Windows, }; use std::ops::{Deref, DerefMut}; use wgpu::TextureFormat; @@ -41,7 +42,7 @@ impl Plugin for WindowRenderPlugin { pub struct ExtractedWindow { pub id: WindowId, - pub raw_handle: Option, + pub handle: AbstractWindowHandle, pub physical_width: u32, pub physical_height: u32, pub present_mode: PresentMode, @@ -88,7 +89,7 @@ fn extract_windows( .entry(window.id()) .or_insert(ExtractedWindow { id: window.id(), - raw_handle: window.raw_handle(), + handle: window.window_handle(), physical_width: new_width, physical_height: new_height, present_mode: window.present_mode(), @@ -144,7 +145,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 @@ -176,77 +177,107 @@ pub fn prepare_windows( render_adapter: Res, ) { for window in windows.windows.values_mut() { - if let Some(handle) = &window.handle { - let window_surfaces = window_surfaces.deref_mut(); - let surface_data = - window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - let surface = render_instance - .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); - let format = *surface - .get_supported_formats(&render_adapter) - .get(0) - .unwrap_or_else(|| { - panic!( - "No supported formats found for surface {:?} on adapter {:?}", - surface, render_adapter - ) - }); - SurfaceData { surface, format } + let window_surfaces = window_surfaces.deref_mut(); + let surface_data = 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. + let surface = render_instance + .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); + let format = *surface + .get_supported_formats(&render_adapter) + .get(0) + .unwrap_or_else(|| { + panic!( + "No supported formats found for surface {:?} on adapter {:?}", + surface, render_adapter + ) + }); + SurfaceData { surface, format } + }), + AbstractWindowHandle::Virtual => continue, + }; + let surface_data = window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + let surface = render_instance + .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); + let format = *surface + .get_supported_formats(&render_adapter) + .get(0) + .unwrap_or_else(|| { + panic!( + "No supported formats found for surface {:?} on adapter {:?}", + surface, render_adapter + ) }); + SurfaceData { surface, format } + }); - let surface_configuration = wgpu::SurfaceConfiguration { - format: surface_data.format, - 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, - }, - alpha_mode: match window.alpha_mode { - CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto, - CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque, - CompositeAlphaMode::PreMultiplied => wgpu::CompositeAlphaMode::PreMultiplied, - CompositeAlphaMode::PostMultiplied => wgpu::CompositeAlphaMode::PostMultiplied, - CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - let frame = if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { - render_device.configure_surface(&surface_data.surface, &surface_configuration); - surface_data - .surface - .get_current_texture() - .expect("Error configuring surface") - } else { - match surface_data.surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { - render_device - .configure_surface(&surface_data.surface, &surface_configuration); - surface_data - .surface - .get_current_texture() - .expect("Error reconfiguring surface") - } - err => err.expect("Failed to acquire next swap chain texture!"), + let surface_configuration = wgpu::SurfaceConfiguration { + format: surface_data.format, + 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, + }, + alpha_mode: match window.alpha_mode { + CompositeAlphaMode::Auto => wgpu::CompositeAlphaMode::Auto, + CompositeAlphaMode::Opaque => wgpu::CompositeAlphaMode::Opaque, + CompositeAlphaMode::PreMultiplied => wgpu::CompositeAlphaMode::PreMultiplied, + CompositeAlphaMode::PostMultiplied => wgpu::CompositeAlphaMode::PostMultiplied, + CompositeAlphaMode::Inherit => wgpu::CompositeAlphaMode::Inherit, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + let frame = if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { + render_device.configure_surface(&surface_data.surface, &surface_configuration); + surface_data + .surface + .get_current_texture() + .expect("Error configuring surface") + } else { + match surface_data.surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { + render_device.configure_surface(&surface_data.surface, &surface_configuration); + surface_data + .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_format = Some(surface_data.format); - } + window.swap_chain_texture = Some(TextureView::from(frame)); + window.swap_chain_texture_format = Some(surface_data.format); } + + 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 f4d8d1417eee6..88585f42a8ac5 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -179,6 +179,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`). @@ -287,7 +302,7 @@ pub struct Window { cursor_visible: bool, cursor_grab_mode: CursorGrabMode, physical_cursor_position: Option, - raw_handle: Option, + window_handle: AbstractWindowHandle, focused: bool, mode: WindowMode, canvas: Option, @@ -431,7 +446,7 @@ impl Window { cursor_grab_mode: window_descriptor.cursor_grab_mode, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_handle: Some(raw_handle), + window_handle: AbstractWindowHandle::RawWindowHandle(raw_handle), focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -443,11 +458,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, @@ -474,7 +485,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(), @@ -866,16 +877,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_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 5d803c944b2bc..71b06f554a3ce 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -211,7 +211,7 @@ impl WinitWindows { inner_size.height, scale_factor, position, - Some(raw_handle), + raw_window_handle, ) } From 5c03cb91f4f0e617c4eae4e752fd9c7baec50761 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Mon, 17 Oct 2022 18:39:37 +0200 Subject: [PATCH 3/5] Add example --- Cargo.toml | 11 ++ examples/README.md | 1 + examples/window/virtual.rs | 211 +++++++++++++++++++++++++++++++++++++ 3 files changed, 223 insertions(+) create mode 100644 examples/window/virtual.rs diff --git a/Cargo.toml b/Cargo.toml index f8f26109910da..ad564188424e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1577,6 +1577,17 @@ path = "tests/window/minimising.rs" [package.metadata.example.minimising] hidden = true +[[example]] +name = "virtual" +path = "examples/window/virtual.rs" + +[package.metadata.example.virtual] +name = "Virtual Window" +description = "Demonstrates creating virtual windows and manually setting their render target" +category = "Window" +wasm = true + + [[example]] name = "window_resizing" path = "examples/window/window_resizing.rs" diff --git a/examples/README.md b/examples/README.md index 31b986abdbe05..3fead4b5a183f 100644 --- a/examples/README.md +++ b/examples/README.md @@ -328,6 +328,7 @@ Example | Description [Multiple Windows](../examples/window/multiple_windows.rs) | Demonstrates creating multiple windows, and rendering to them [Scale Factor Override](../examples/window/scale_factor_override.rs) | Illustrates how to customize the default window settings [Transparent Window](../examples/window/transparent_window.rs) | Illustrates making the window transparent and hiding the window decoration +[Virtual Window](../examples/window/virtual.rs) | Demonstrates creating virtual windows and manually setting their render target [Window Resizing](../examples/window/window_resizing.rs) | Demonstrates resizing and responding to resizing a window [Window Settings](../examples/window/window_settings.rs) | Demonstrates customizing default window settings 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 ac6fa7d37444ce6a70dc4b821fbaa6059aee8dd8 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Wed, 19 Oct 2022 10:44:34 +0200 Subject: [PATCH 4/5] Fix doctest --- crates/bevy_render/src/view/window.rs | 37 ++------------------------ crates/bevy_window/src/window.rs | 9 ++++--- crates/bevy_winit/src/winit_windows.rs | 2 +- 3 files changed, 8 insertions(+), 40 deletions(-) diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index 6e83120f785ed..a012c6adb2f7a 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::{ - AbstractWindowHandle, CompositeAlphaMode, PresentMode, RawHandleWrapper, WindowClosed, - WindowId, Windows, + AbstractWindowHandle, CompositeAlphaMode, PresentMode, WindowClosed, WindowId, Windows, }; use std::ops::{Deref, DerefMut}; use wgpu::TextureFormat; @@ -184,8 +183,7 @@ pub fn prepare_windows( .entry(window.id) .or_insert_with(|| unsafe { // NOTE: On some OSes this MUST be called from the main thread. - let surface = render_instance - .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); + let surface = render_instance.create_surface(&handle.get_handle()); let format = *surface .get_supported_formats(&render_adapter) .get(0) @@ -199,24 +197,6 @@ pub fn prepare_windows( }), AbstractWindowHandle::Virtual => continue, }; - let surface_data = window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - let surface = render_instance - .create_surface(&window.raw_handle.as_ref().unwrap().get_handle()); - let format = *surface - .get_supported_formats(&render_adapter) - .get(0) - .unwrap_or_else(|| { - panic!( - "No supported formats found for surface {:?} on adapter {:?}", - surface, render_adapter - ) - }); - SurfaceData { surface, format } - }); let surface_configuration = wgpu::SurfaceConfiguration { format: surface_data.format, @@ -267,17 +247,4 @@ pub fn prepare_windows( window.swap_chain_texture = Some(TextureView::from(frame)); window.swap_chain_texture_format = Some(surface_data.format); } - - 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 88585f42a8ac5..6d2e6831d295b 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -185,7 +185,7 @@ impl WindowResizeConstraints { #[derive(Clone, Debug)] pub enum AbstractWindowHandle { /// The window corresponds to an operator system window. - RawWindowHandle(RawWindowHandleWrapper), + RawWindowHandle(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 @@ -256,13 +256,13 @@ pub enum AbstractWindowHandle { /// resize_constraints, /// ..default() /// }; -/// let mut window = Window::new( +/// let mut window = Window::new_virtual( /// WindowId::new(), /// &window_descriptor, /// 100, // physical_width /// 100, // physical_height /// 1.0, // scale_factor -/// None, None); +/// None); /// /// let area = compute_window_area(&window); /// assert_eq!(area, 100.0 * 100.0); @@ -482,7 +482,7 @@ impl Window { resizable: window_descriptor.resizable, decorations: window_descriptor.decorations, cursor_visible: window_descriptor.cursor_visible, - cursor_locked: window_descriptor.cursor_locked, + cursor_grab_mode: window_descriptor.cursor_grab_mode, cursor_icon: CursorIcon::Default, physical_cursor_position: None, window_handle: AbstractWindowHandle::Virtual, @@ -491,6 +491,7 @@ impl Window { canvas: window_descriptor.canvas.clone(), fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, command_queue: Vec::new(), + alpha_mode: window_descriptor.alpha_mode, } } diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 71b06f554a3ce..436fea00385fa 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -211,7 +211,7 @@ impl WinitWindows { inner_size.height, scale_factor, position, - raw_window_handle, + raw_handle, ) } From 4290e3b553ea1d5e40055fa74eaa2ab05fd031a5 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Mon, 7 Nov 2022 16:39:25 +0100 Subject: [PATCH 5/5] Ensure safety of creating windows --- crates/bevy_render/src/lib.rs | 8 ++++--- crates/bevy_window/src/raw_handle.rs | 29 +++++++++++++++----------- crates/bevy_winit/src/lib.rs | 13 +++++++----- crates/bevy_winit/src/winit_windows.rs | 14 +++++++++---- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index f8cea30fddabc..6b1e837609c8e 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -146,11 +146,13 @@ impl Plugin for RenderPlugin { let instance = wgpu::Instance::new(backends); let surface = { let windows = app.world.resource_mut::(); - let raw_handle = windows.get_primary().and_then(|window| unsafe { + let raw_handle = windows.get_primary().and_then(|window| { match window.window_handle() { - AbstractWindowHandle::RawWindowHandle(handle) => { + AbstractWindowHandle::RawWindowHandle(handle) => unsafe { + // SAFETY: This is only run on the main thread. Also the caller of `RawHandleWrapper::new` has + // ensured that the window was created on the main thread. Some(instance.create_surface(&handle.get_handle())) - } + }, AbstractWindowHandle::Virtual => None, } }); diff --git a/crates/bevy_window/src/raw_handle.rs b/crates/bevy_window/src/raw_handle.rs index 0e495d2c138b5..bed16c30ddcb5 100644 --- a/crates/bevy_window/src/raw_handle.rs +++ b/crates/bevy_window/src/raw_handle.rs @@ -9,11 +9,24 @@ use raw_window_handle::{ /// thread-safe. #[derive(Debug, Clone)] pub struct RawHandleWrapper { - pub window_handle: RawWindowHandle, - pub display_handle: RawDisplayHandle, + window_handle: RawWindowHandle, + display_handle: RawDisplayHandle, } impl RawHandleWrapper { + /// Create a new `RawHandleWrapper` from its components + /// + /// # Safety + /// + /// If the particular underlying handle can only be used on the same thread, the caller should ensure that the window + /// was created on the main thread. + pub unsafe fn new(window_handle: RawWindowHandle, display_handle: RawDisplayHandle) -> Self { + RawHandleWrapper { + window_handle, + display_handle, + } + } + /// Returns a [`HasRawWindowHandle`] + [`HasRawDisplayHandle`] impl, which exposes [`RawWindowHandle`] and [`RawDisplayHandle`]. /// /// # Safety @@ -23,14 +36,6 @@ impl RawHandleWrapper { pub unsafe fn get_handle(&self) -> ThreadLockedRawWindowHandleWrapper { ThreadLockedRawWindowHandleWrapper(self.clone()) } - - pub fn get_display_handle(&self) -> RawDisplayHandle { - self.display_handle - } - - pub fn get_window_handle(&self) -> RawWindowHandle { - self.window_handle - } } // SAFETY: [`RawHandleWrapper`] is just a normal "raw pointer", which doesn't impl Send/Sync. However the pointer is only @@ -58,7 +63,7 @@ pub struct ThreadLockedRawWindowHandleWrapper(RawHandleWrapper); // and so exposing a safe method to get a [`RawWindowHandle`] directly would be UB. unsafe impl HasRawWindowHandle for ThreadLockedRawWindowHandleWrapper { fn raw_window_handle(&self) -> RawWindowHandle { - self.0.get_window_handle() + self.0.window_handle } } @@ -70,6 +75,6 @@ unsafe impl HasRawWindowHandle for ThreadLockedRawWindowHandleWrapper { // and so exposing a safe method to get a [`RawDisplayHandle`] directly would be UB. unsafe impl HasRawDisplayHandle for ThreadLockedRawWindowHandleWrapper { fn raw_display_handle(&self) -> RawDisplayHandle { - self.0.get_display_handle() + self.0.display_handle } } diff --git a/crates/bevy_winit/src/lib.rs b/crates/bevy_winit/src/lib.rs index 831020323a45d..b4362a202b5d2 100644 --- a/crates/bevy_winit/src/lib.rs +++ b/crates/bevy_winit/src/lib.rs @@ -685,11 +685,14 @@ 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) { - let window = winit_windows.create_window( - event_loop, - create_window_event.id, - &create_window_event.descriptor, - ); + let window = unsafe { + // SAFETY: This is only called from the main thread + winit_windows.create_window( + event_loop, + create_window_event.id, + &create_window_event.descriptor, + ) + }; // This event is already sent on windows, x11, and xwayland. // TODO: we aren't yet sure about native wayland, so we might be able to exclude it, // but sending a duplicate event isn't problematic, as windows already does this. diff --git a/crates/bevy_winit/src/winit_windows.rs b/crates/bevy_winit/src/winit_windows.rs index 436fea00385fa..69a51a482c3c3 100644 --- a/crates/bevy_winit/src/winit_windows.rs +++ b/crates/bevy_winit/src/winit_windows.rs @@ -23,7 +23,10 @@ pub struct WinitWindows { } impl WinitWindows { - pub fn create_window( + /// # SAFETY + /// + /// This should only be called from the main thread, as it might create windows that cannot be used across threads + pub unsafe fn create_window( &mut self, event_loop: &winit::event_loop::EventLoopWindowTarget<()>, window_id: WindowId, @@ -199,9 +202,12 @@ impl WinitWindows { .map(|position| IVec2::new(position.x, position.y)); let inner_size = winit_window.inner_size(); let scale_factor = winit_window.scale_factor(); - let raw_handle = RawHandleWrapper { - window_handle: winit_window.raw_window_handle(), - display_handle: winit_window.raw_display_handle(), + let raw_handle = unsafe { + // SAFETY: Caller ensured that this is run on the main thread + RawHandleWrapper::new( + winit_window.raw_window_handle(), + winit_window.raw_display_handle(), + ) }; self.windows.insert(winit_window.id(), winit_window); Window::new(