Skip to content

Commit

Permalink
Windows as Entities (bevyengine#5589)
Browse files Browse the repository at this point in the history
# Objective

Fix bevyengine#4530

- Make it easier to open/close/modify windows by setting them up as `Entity`s with a `Window` component.
- Make multiple windows very simple to set up. (just add a `Window` component to an entity and it should open)

## Solution

- Move all properties of window descriptor to ~components~ a component.
- Replace `WindowId` with `Entity`.
- ~Use change detection for components to update backend rather than events/commands. (The `CursorMoved`/`WindowResized`/... events are kept for user convenience.~
  Check each field individually to see what we need to update, events are still kept for user convenience.

---

## Changelog

- `WindowDescriptor` renamed to `Window`.
    - Width/height consolidated into a `WindowResolution` component.
    - Requesting maximization/minimization is done on the [`Window::state`] field.
- `WindowId` is now `Entity`.

## Migration Guide

- Replace `WindowDescriptor` with `Window`.
    - Change `width` and `height` fields in a `WindowResolution`, either by doing
      ```rust
      WindowResolution::new(width, height) // Explicitly
      // or using From<_> for tuples for convenience
      (1920., 1080.).into()
      ```
- Replace any `WindowCommand` code to just modify the `Window`'s fields directly  and creating/closing windows is now by spawning/despawning an entity with a `Window` component like so:
  ```rust
  let window = commands.spawn(Window { ... }).id(); // open window
  commands.entity(window).despawn(); // close window
  ```

## Unresolved
- ~How do we tell when a window is minimized by a user?~
  ~Currently using the `Resize(0, 0)` as an indicator of minimization.~
  No longer attempting to tell given how finnicky this was across platforms, now the user can only request that a window be maximized/minimized.
  
 ## Future work
 - Move `exit_on_close` functionality out from windowing and into app(?)
 - bevyengine#5621
 - bevyengine#7099
 - bevyengine#7098


Co-authored-by: Carter Anderson <mcanders1@gmail.com>
  • Loading branch information
2 people authored and ItsDoot committed Feb 1, 2023
1 parent ffde302 commit 7a59ed4
Show file tree
Hide file tree
Showing 47 changed files with 1,912 additions and 1,864 deletions.
142 changes: 83 additions & 59 deletions crates/bevy_render/src/camera/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use bevy_ecs::{
component::Component,
entity::Entity,
event::EventReader,
prelude::With,
reflect::ReflectComponent,
system::{Commands, Query, Res},
};
Expand All @@ -21,7 +22,10 @@ use bevy_reflect::prelude::*;
use bevy_reflect::FromReflect;
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashSet;
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
use bevy_window::{
NormalizedWindowRef, PrimaryWindow, Window, WindowCreated, WindowRef, WindowResized,
};

use std::{borrow::Cow, ops::Range};
use wgpu::{Extent3d, TextureFormat};

Expand Down Expand Up @@ -325,10 +329,21 @@ impl CameraRenderGraph {

/// The "target" that a [`Camera`] will render to. For example, this could be a [`Window`](bevy_window::Window)
/// swapchain or an [`Image`].
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Debug, Clone, Reflect)]
pub enum RenderTarget {
/// Window to which the camera's view is rendered.
Window(WindowId),
Window(WindowRef),
/// Image to which the camera's view is rendered.
Image(Handle<Image>),
}

/// Normalized version of the render target.
///
/// Once we have this we shouldn't need to resolve it down anymore.
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NormalizedRenderTarget {
/// Window to which the camera's view is rendered.
Window(NormalizedWindowRef),
/// Image to which the camera's view is rendered.
Image(Handle<Image>),
}
Expand All @@ -340,16 +355,28 @@ impl Default for RenderTarget {
}

impl RenderTarget {
/// Normalize the render target down to a more concrete value, mostly used for equality comparisons.
pub fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget> {
match self {
RenderTarget::Window(window_ref) => window_ref
.normalize(primary_window)
.map(NormalizedRenderTarget::Window),
RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())),
}
}
}

impl NormalizedRenderTarget {
pub fn get_texture_view<'a>(
&self,
windows: &'a ExtractedWindows,
images: &'a RenderAssets<Image>,
) -> Option<&'a TextureView> {
match self {
RenderTarget::Window(window_id) => windows
.get(window_id)
NormalizedRenderTarget::Window(window_ref) => windows
.get(&window_ref.entity())
.and_then(|window| window.swap_chain_texture.as_ref()),
RenderTarget::Image(image_handle) => {
NormalizedRenderTarget::Image(image_handle) => {
images.get(image_handle).map(|image| &image.texture_view)
}
}
Expand All @@ -362,47 +389,55 @@ impl RenderTarget {
images: &'a RenderAssets<Image>,
) -> Option<TextureFormat> {
match self {
RenderTarget::Window(window_id) => windows
.get(window_id)
NormalizedRenderTarget::Window(window_ref) => windows
.get(&window_ref.entity())
.and_then(|window| window.swap_chain_texture_format),
RenderTarget::Image(image_handle) => {
NormalizedRenderTarget::Image(image_handle) => {
images.get(image_handle).map(|image| image.texture_format)
}
}
}

pub fn get_render_target_info(
pub fn get_render_target_info<'a>(
&self,
windows: &Windows,
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
images: &Assets<Image>,
) -> Option<RenderTargetInfo> {
Some(match self {
RenderTarget::Window(window_id) => {
let window = windows.get(*window_id)?;
RenderTargetInfo {
physical_size: UVec2::new(window.physical_width(), window.physical_height()),
scale_factor: window.scale_factor(),
}
}
RenderTarget::Image(image_handle) => {
match self {
NormalizedRenderTarget::Window(window_ref) => resolutions
.into_iter()
.find(|(entity, _)| *entity == window_ref.entity())
.map(|(_, window)| RenderTargetInfo {
physical_size: UVec2::new(
window.resolution.physical_width(),
window.resolution.physical_height(),
),
scale_factor: window.resolution.scale_factor(),
}),
NormalizedRenderTarget::Image(image_handle) => {
let image = images.get(image_handle)?;
let Extent3d { width, height, .. } = image.texture_descriptor.size;
RenderTargetInfo {
Some(RenderTargetInfo {
physical_size: UVec2::new(width, height),
scale_factor: 1.0,
}
})
}
})
}
}

// Check if this render target is contained in the given changed windows or images.
fn is_changed(
&self,
changed_window_ids: &[WindowId],
changed_window_ids: &HashSet<Entity>,
changed_image_handles: &HashSet<&Handle<Image>>,
) -> bool {
match self {
RenderTarget::Window(window_id) => changed_window_ids.contains(window_id),
RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle),
NormalizedRenderTarget::Window(window_ref) => {
changed_window_ids.contains(&window_ref.entity())
}
NormalizedRenderTarget::Image(image_handle) => {
changed_image_handles.contains(&image_handle)
}
}
}
}
Expand Down Expand Up @@ -431,29 +466,16 @@ pub fn camera_system<T: CameraProjection + Component>(
mut window_resized_events: EventReader<WindowResized>,
mut window_created_events: EventReader<WindowCreated>,
mut image_asset_events: EventReader<AssetEvent<Image>>,
windows: Res<Windows>,
primary_window: Query<Entity, With<PrimaryWindow>>,
windows: Query<(Entity, &Window)>,
images: Res<Assets<Image>>,
mut cameras: Query<(&mut Camera, &mut T)>,
) {
let mut changed_window_ids = Vec::new();

// Collect all unique window IDs of changed windows by inspecting created windows
for event in window_created_events.iter() {
if changed_window_ids.contains(&event.id) {
continue;
}

changed_window_ids.push(event.id);
}
let primary_window = primary_window.iter().next();

// Collect all unique window IDs of changed windows by inspecting resized windows
for event in window_resized_events.iter() {
if changed_window_ids.contains(&event.id) {
continue;
}

changed_window_ids.push(event.id);
}
let mut changed_window_ids = HashSet::new();
changed_window_ids.extend(window_created_events.iter().map(|event| event.window));
changed_window_ids.extend(window_resized_events.iter().map(|event| event.window));

let changed_image_handles: HashSet<&Handle<Image>> = image_asset_events
.iter()
Expand All @@ -472,26 +494,26 @@ pub fn camera_system<T: CameraProjection + Component>(
.as_ref()
.map(|viewport| viewport.physical_size);

if camera
.target
.is_changed(&changed_window_ids, &changed_image_handles)
|| camera.is_added()
|| camera_projection.is_changed()
|| camera.computed.old_viewport_size != viewport_size
{
camera.computed.target_info = camera.target.get_render_target_info(&windows, &images);
camera.computed.old_viewport_size = viewport_size;
if let Some(size) = camera.logical_viewport_size() {
camera_projection.update(size.x, size.y);
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
if let Some(normalized_target) = camera.target.normalize(primary_window) {
if normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
|| camera.is_added()
|| camera_projection.is_changed()
|| camera.computed.old_viewport_size != viewport_size
{
camera.computed.target_info =
normalized_target.get_render_target_info(&windows, &images);
if let Some(size) = camera.logical_viewport_size() {
camera_projection.update(size.x, size.y);
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
}
}
}
}
}

#[derive(Component, Debug)]
pub struct ExtractedCamera {
pub target: RenderTarget,
pub target: Option<NormalizedRenderTarget>,
pub physical_viewport_size: Option<UVec2>,
pub physical_target_size: Option<UVec2>,
pub viewport: Option<Viewport>,
Expand All @@ -510,7 +532,9 @@ pub fn extract_cameras(
&VisibleEntities,
)>,
>,
primary_window: Extract<Query<Entity, With<PrimaryWindow>>>,
) {
let primary_window = primary_window.iter().next();
for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
if !camera.is_active {
continue;
Expand All @@ -525,7 +549,7 @@ pub fn extract_cameras(
}
commands.get_or_spawn(entity).insert((
ExtractedCamera {
target: camera.target.clone(),
target: camera.target.normalize(primary_window),
viewport: camera.viewport.clone(),
physical_viewport_size: Some(viewport_size),
physical_target_size: Some(target_size),
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_render/src/camera/camera_driver_node.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
camera::{ExtractedCamera, RenderTarget},
camera::{ExtractedCamera, NormalizedRenderTarget},
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
renderer::RenderContext,
view::ExtractedWindows,
Expand Down Expand Up @@ -52,8 +52,8 @@ impl Node for CameraDriverNode {
}
previous_order_target = Some(new_order_target);
if let Ok((_, camera)) = self.cameras.get_manual(world, entity) {
if let RenderTarget::Window(id) = camera.target {
camera_windows.insert(id);
if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target {
camera_windows.insert(window_ref.entity());
}
graph
.run_sub_graph(camera.render_graph.clone(), vec![SlotValue::Entity(entity)])?;
Expand Down
21 changes: 11 additions & 10 deletions crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ pub mod prelude {
};
}

use bevy_window::{PrimaryWindow, RawHandleWrapper};
use globals::GlobalsPlugin;
pub use once_cell;

Expand All @@ -50,7 +51,7 @@ use crate::{
};
use bevy_app::{App, AppLabel, Plugin};
use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::prelude::*;
use bevy_ecs::{prelude::*, system::SystemState};
use bevy_utils::tracing::debug;
use std::{
any::TypeId,
Expand Down Expand Up @@ -138,17 +139,17 @@ impl Plugin for RenderPlugin {
.init_asset_loader::<ShaderLoader>()
.init_debug_asset_loader::<ShaderLoader>();

let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
SystemState::new(&mut app.world);
let primary_window = system_state.get(&app.world);

if let Some(backends) = self.wgpu_settings.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 = 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)
});

let request_adapter_options = wgpu::RequestAdapterOptions {
power_preference: self.wgpu_settings.power_preference,
Expand Down
6 changes: 3 additions & 3 deletions crates/bevy_render/src/view/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,10 @@ fn prepare_view_targets(
) {
let mut textures = HashMap::default();
for (entity, camera, view) in cameras.iter() {
if let Some(target_size) = camera.physical_target_size {
if let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target) {
if let (Some(out_texture_view), Some(out_texture_format)) = (
camera.target.get_texture_view(&windows, &images),
camera.target.get_texture_format(&windows, &images),
target.get_texture_view(&windows, &images),
target.get_texture_format(&windows, &images),
) {
let size = Extent3d {
width: target_size.x,
Expand Down
Loading

0 comments on commit 7a59ed4

Please sign in to comment.