Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable creating "virtual" windows without corresponding OS window #6256

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,17 @@ path = "tests/window/minimising.rs"
[package.metadata.example.minimising]
hidden = true

[[example]]
name = "virtual"
path = "examples/window/virtual.rs"
MDeiml marked this conversation as resolved.
Show resolved Hide resolved

[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"
Expand Down
23 changes: 14 additions & 9 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -142,17 +143,21 @@ impl Plugin for RenderPlugin {
.register_type::<Color>();

if let Some(backends) = options.backends {
let windows = app.world.resource_mut::<bevy_window::Windows>();
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::<bevy_window::Windows>();
let raw_handle = windows.get_primary().and_then(|window| {
match window.window_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,
}
});

raw_handle
};
let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: options.power_preference,
compatible_surface: surface.as_ref(),
Expand Down
53 changes: 26 additions & 27 deletions crates/bevy_render/src/view/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ 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, WindowClosed, WindowId, Windows,
};
use std::ops::{Deref, DerefMut};
use wgpu::TextureFormat;
Expand Down Expand Up @@ -41,7 +41,7 @@ impl Plugin for WindowRenderPlugin {

pub struct ExtractedWindow {
pub id: WindowId,
pub raw_handle: Option<RawHandleWrapper>,
pub handle: AbstractWindowHandle,
pub physical_width: u32,
pub physical_height: u32,
pub present_mode: PresentMode,
Expand Down Expand Up @@ -88,7 +88,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(),
Expand Down Expand Up @@ -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::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
/// taking an unusually long time to complete, and all finishing at about the same time as the
Expand Down Expand Up @@ -173,31 +175,28 @@ pub fn prepare_windows(
render_instance: Res<RenderInstance>,
render_adapter: Res<RenderAdapter>,
) {
for window in windows
.windows
.values_mut()
// value of raw_handle is only None in synthetic tests
.filter(|x| x.raw_handle.is_some())
{
for window in windows.windows.values_mut() {
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 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(&handle.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_configuration = wgpu::SurfaceConfiguration {
format: surface_data.format,
Expand Down
29 changes: 17 additions & 12 deletions crates/bevy_window/src/raw_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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
}
}

Expand All @@ -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
}
}
76 changes: 65 additions & 11 deletions crates/bevy_window/src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,22 @@ impl WindowResizeConstraints {
}
}

/// An operating system window that can present content and receive user input.
/// 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(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<CreateWindow>`](`crate::CreateWindow`).
///
Expand Down Expand Up @@ -241,13 +256,13 @@ impl WindowResizeConstraints {
/// 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);
Expand Down Expand Up @@ -287,7 +302,7 @@ pub struct Window {
cursor_visible: bool,
cursor_grab_mode: CursorGrabMode,
physical_cursor_position: Option<DVec2>,
raw_handle: Option<RawHandleWrapper>,
window_handle: AbstractWindowHandle,
focused: bool,
mode: WindowMode,
canvas: Option<String>,
Expand Down Expand Up @@ -411,7 +426,46 @@ impl Window {
physical_height: u32,
scale_factor: f64,
position: Option<IVec2>,
raw_handle: Option<RawHandleWrapper>,
raw_handle: RawHandleWrapper,
) -> 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_grab_mode: window_descriptor.cursor_grab_mode,
cursor_icon: CursorIcon::Default,
physical_cursor_position: None,
window_handle: AbstractWindowHandle::RawWindowHandle(raw_handle),
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(),
alpha_mode: window_descriptor.alpha_mode,
}
}

/// Creates a new virtual [`Window`].
///
/// See [`AbstractWindowHandle::Virtual`].
pub fn new_virtual(
id: WindowId,
window_descriptor: &WindowDescriptor,
physical_width: u32,
physical_height: u32,
scale_factor: f64,
position: Option<IVec2>,
) -> Self {
Window {
id,
Expand All @@ -431,7 +485,7 @@ impl Window {
cursor_grab_mode: window_descriptor.cursor_grab_mode,
cursor_icon: CursorIcon::Default,
physical_cursor_position: None,
raw_handle,
window_handle: AbstractWindowHandle::Virtual,
focused: true,
mode: window_descriptor.mode,
canvas: window_descriptor.canvas.clone(),
Expand All @@ -440,6 +494,7 @@ impl Window {
alpha_mode: window_descriptor.alpha_mode,
}
}

/// Get the window's [`WindowId`].
#[inline]
pub fn id(&self) -> WindowId {
Expand Down Expand Up @@ -822,11 +877,10 @@ impl Window {
pub fn is_focused(&self) -> bool {
self.focused
}
/// Get the [`RawHandleWrapper`] corresponding to this window if set.
///
/// 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<RawHandleWrapper> {
self.raw_handle.as_ref().cloned()

/// Get the [`AbstractWindowHandle`] corresponding to this window.
pub fn window_handle(&self) -> AbstractWindowHandle {
self.window_handle.clone()
}

/// The "html canvas" element selector.
Expand Down
13 changes: 8 additions & 5 deletions crates/bevy_winit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<Events<WindowResized>>();
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.
Expand Down
16 changes: 11 additions & 5 deletions crates/bevy_winit/src/winit_windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand All @@ -211,7 +217,7 @@ impl WinitWindows {
inner_size.height,
scale_factor,
position,
Some(raw_handle),
raw_handle,
)
}

Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading