From eee987bd0a862d6bdbc6e60a8cf436209b0ac7a2 Mon Sep 17 00:00:00 2001 From: MDeiml Date: Fri, 14 Oct 2022 14:27:37 +0200 Subject: [PATCH] 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/lib.rs | 6 +- crates/bevy_render/src/view/window.rs | 100 +++++++++++++------------- crates/bevy_window/src/window.rs | 60 ++++++++++++++-- 3 files changed, 110 insertions(+), 56 deletions(-) diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 62d0bdb7badd6b..886809de363635 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -147,9 +147,9 @@ impl Plugin for RenderPlugin { let instance = wgpu::Instance::new(backends); let surface = { let windows = app.world.resource_mut::(); - let raw_handle = windows.get_primary().map(|window| unsafe { - let handle = window.raw_window_handle().get_handle(); - instance.create_surface(&handle) + let raw_handle = windows.get_primary().and_then(|window| unsafe { + let handle = window.raw_window_handle()?.get_handle(); + Some(instance.create_surface(&handle)) }); raw_handle }; diff --git a/crates/bevy_render/src/view/window.rs b/crates/bevy_render/src/view/window.rs index fbe9ba4f667759..83f5c49737eb99 100644 --- a/crates/bevy_render/src/view/window.rs +++ b/crates/bevy_render/src/view/window.rs @@ -38,7 +38,7 @@ impl Plugin for WindowRenderPlugin { pub struct ExtractedWindow { pub id: WindowId, - pub handle: RawWindowHandleWrapper, + pub handle: Option, pub physical_width: u32, pub physical_height: u32, pub present_mode: PresentMode, @@ -132,6 +132,8 @@ pub struct WindowSurfaces { /// Creates and (re)configures window surfaces, and obtains a swapchain texture for rendering. /// +/// This will not handle [virtual windows](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 @@ -163,56 +165,58 @@ pub fn prepare_windows( ) { let window_surfaces = window_surfaces.deref_mut(); for window in windows.windows.values_mut() { - let surface = window_surfaces - .surfaces - .entry(window.id) - .or_insert_with(|| unsafe { - // NOTE: On some OSes this MUST be called from the main thread. - render_instance.create_surface(&window.handle.get_handle()) - }); - - let swap_chain_descriptor = wgpu::SurfaceConfiguration { - format: *surface - .get_supported_formats(&render_adapter) - .get(0) - .unwrap_or_else(|| { - panic!( - "No supported formats found for surface {:?} on adapter {:?}", - surface, render_adapter - ) - }), - width: window.physical_width, - height: window.physical_height, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - present_mode: match window.present_mode { - PresentMode::Fifo => wgpu::PresentMode::Fifo, - PresentMode::Mailbox => wgpu::PresentMode::Mailbox, - PresentMode::Immediate => wgpu::PresentMode::Immediate, - PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, - PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, - }, - }; - - // Do the initial surface configuration if it hasn't been configured yet. Or if size or - // present mode changed. - if window_surfaces.configured_windows.insert(window.id) - || window.size_changed - || window.present_mode_changed - { - render_device.configure_surface(surface, &swap_chain_descriptor); - } + if let Some(handle) = &window.handle { + let surface = window_surfaces + .surfaces + .entry(window.id) + .or_insert_with(|| unsafe { + // NOTE: On some OSes this MUST be called from the main thread. + render_instance.create_surface(&handle.get_handle()) + }); - let frame = match surface.get_current_texture() { - Ok(swap_chain_frame) => swap_chain_frame, - Err(wgpu::SurfaceError::Outdated) => { + let swap_chain_descriptor = wgpu::SurfaceConfiguration { + format: *surface + .get_supported_formats(&render_adapter) + .get(0) + .unwrap_or_else(|| { + panic!( + "No supported formats found for surface {:?} on adapter {:?}", + surface, render_adapter + ) + }), + width: window.physical_width, + height: window.physical_height, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + present_mode: match window.present_mode { + PresentMode::Fifo => wgpu::PresentMode::Fifo, + PresentMode::Mailbox => wgpu::PresentMode::Mailbox, + PresentMode::Immediate => wgpu::PresentMode::Immediate, + PresentMode::AutoVsync => wgpu::PresentMode::AutoVsync, + PresentMode::AutoNoVsync => wgpu::PresentMode::AutoNoVsync, + }, + }; + + // Do the initial surface configuration if it hasn't been configured yet. Or if size or + // present mode changed. + if window_surfaces.configured_windows.insert(window.id) + || window.size_changed + || window.present_mode_changed + { render_device.configure_surface(surface, &swap_chain_descriptor); - surface - .get_current_texture() - .expect("Error reconfiguring surface") } - err => err.expect("Failed to acquire next swap chain texture!"), - }; - window.swap_chain_texture = Some(TextureView::from(frame)); + let frame = match surface.get_current_texture() { + Ok(swap_chain_frame) => swap_chain_frame, + Err(wgpu::SurfaceError::Outdated) => { + render_device.configure_surface(surface, &swap_chain_descriptor); + surface + .get_current_texture() + .expect("Error reconfiguring surface") + } + err => err.expect("Failed to acquire next swap chain texture!"), + }; + + window.swap_chain_texture = Some(TextureView::from(frame)); + } } } diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index 8ebf978efcfd6c..10c1b4c0d6bf32 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -151,7 +151,7 @@ impl WindowResizeConstraints { } } -/// An operating system window that can present content and receive user input. +/// An operating system or virtual window that can present content and receive user input. /// /// To create a window, use a [`EventWriter`](`crate::CreateWindow`). /// @@ -206,7 +206,7 @@ pub struct Window { cursor_visible: bool, cursor_locked: bool, physical_cursor_position: Option, - raw_window_handle: RawWindowHandleWrapper, + raw_window_handle: Option, focused: bool, mode: WindowMode, canvas: Option, @@ -335,7 +335,7 @@ impl Window { cursor_locked: window_descriptor.cursor_locked, cursor_icon: CursorIcon::Default, physical_cursor_position: None, - raw_window_handle: RawWindowHandleWrapper::new(raw_window_handle), + raw_window_handle: Some(RawWindowHandleWrapper::new(raw_window_handle)), focused: true, mode: window_descriptor.mode, canvas: window_descriptor.canvas.clone(), @@ -343,6 +343,49 @@ impl Window { command_queue: Vec::new(), } } + + /// Creates a new virtual [`Window`]. + /// + /// This window does not have to correspond to an operator system window. + /// + /// It differs from a non-virtual window, in that the caller is responsible + /// for creating and presenting surface textures and inserting them into + /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). + pub fn new_virtual( + id: WindowId, + window_descriptor: &WindowDescriptor, + physical_width: u32, + physical_height: u32, + scale_factor: f64, + position: Option, + ) -> Self { + Window { + id, + requested_width: window_descriptor.width, + requested_height: window_descriptor.height, + position, + physical_width, + physical_height, + resize_constraints: window_descriptor.resize_constraints, + scale_factor_override: window_descriptor.scale_factor_override, + backend_scale_factor: scale_factor, + title: window_descriptor.title.clone(), + present_mode: window_descriptor.present_mode, + resizable: window_descriptor.resizable, + decorations: window_descriptor.decorations, + cursor_visible: window_descriptor.cursor_visible, + cursor_locked: window_descriptor.cursor_locked, + cursor_icon: CursorIcon::Default, + physical_cursor_position: None, + raw_window_handle: None, + focused: true, + mode: window_descriptor.mode, + canvas: window_descriptor.canvas.clone(), + fit_canvas_to_parent: window_descriptor.fit_canvas_to_parent, + command_queue: Vec::new(), + } + } + /// Get the window's [`WindowId`]. #[inline] pub fn id(&self) -> WindowId { @@ -719,8 +762,15 @@ impl Window { pub fn is_focused(&self) -> bool { self.focused } - /// Get the [`RawWindowHandleWrapper`] corresponding to this window - pub fn raw_window_handle(&self) -> RawWindowHandleWrapper { + /// Get the [`RawWindowHandleWrapper`] corresponding to this window. + /// + /// A return value of `None` signifies that this is a virtual window and does not + /// correspond to an OS window. The creator of the window is responsible + /// for creating and presenting surface textures and inserting them into + /// [`ExtractedWindow`](https://docs.rs/bevy/*/bevy/render/view/struct.ExtractedWindow.html). + /// + /// See [`Self::new_virtual`]. + pub fn raw_window_handle(&self) -> Option { self.raw_window_handle.clone() }