Skip to content

Commit ddfafab

Browse files
Aceericart
andcommitted
Windows as Entities (#5589)
# Objective Fix #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(?) - #5621 - #7099 - #7098 Co-authored-by: Carter Anderson <[email protected]>
1 parent f0c5049 commit ddfafab

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1912
-1864
lines changed

crates/bevy_render/src/camera/camera.rs

Lines changed: 83 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use bevy_ecs::{
1313
component::Component,
1414
entity::Entity,
1515
event::EventReader,
16+
prelude::With,
1617
reflect::ReflectComponent,
1718
system::{Commands, Query, Res},
1819
};
@@ -21,7 +22,10 @@ use bevy_reflect::prelude::*;
2122
use bevy_reflect::FromReflect;
2223
use bevy_transform::components::GlobalTransform;
2324
use bevy_utils::HashSet;
24-
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
25+
use bevy_window::{
26+
NormalizedWindowRef, PrimaryWindow, Window, WindowCreated, WindowRef, WindowResized,
27+
};
28+
2529
use std::{borrow::Cow, ops::Range};
2630
use wgpu::{Extent3d, TextureFormat};
2731

@@ -325,10 +329,21 @@ impl CameraRenderGraph {
325329

326330
/// The "target" that a [`Camera`] will render to. For example, this could be a [`Window`](bevy_window::Window)
327331
/// swapchain or an [`Image`].
328-
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
332+
#[derive(Debug, Clone, Reflect)]
329333
pub enum RenderTarget {
330334
/// Window to which the camera's view is rendered.
331-
Window(WindowId),
335+
Window(WindowRef),
336+
/// Image to which the camera's view is rendered.
337+
Image(Handle<Image>),
338+
}
339+
340+
/// Normalized version of the render target.
341+
///
342+
/// Once we have this we shouldn't need to resolve it down anymore.
343+
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, PartialOrd, Ord)]
344+
pub enum NormalizedRenderTarget {
345+
/// Window to which the camera's view is rendered.
346+
Window(NormalizedWindowRef),
332347
/// Image to which the camera's view is rendered.
333348
Image(Handle<Image>),
334349
}
@@ -340,16 +355,28 @@ impl Default for RenderTarget {
340355
}
341356

342357
impl RenderTarget {
358+
/// Normalize the render target down to a more concrete value, mostly used for equality comparisons.
359+
pub fn normalize(&self, primary_window: Option<Entity>) -> Option<NormalizedRenderTarget> {
360+
match self {
361+
RenderTarget::Window(window_ref) => window_ref
362+
.normalize(primary_window)
363+
.map(NormalizedRenderTarget::Window),
364+
RenderTarget::Image(handle) => Some(NormalizedRenderTarget::Image(handle.clone())),
365+
}
366+
}
367+
}
368+
369+
impl NormalizedRenderTarget {
343370
pub fn get_texture_view<'a>(
344371
&self,
345372
windows: &'a ExtractedWindows,
346373
images: &'a RenderAssets<Image>,
347374
) -> Option<&'a TextureView> {
348375
match self {
349-
RenderTarget::Window(window_id) => windows
350-
.get(window_id)
376+
NormalizedRenderTarget::Window(window_ref) => windows
377+
.get(&window_ref.entity())
351378
.and_then(|window| window.swap_chain_texture.as_ref()),
352-
RenderTarget::Image(image_handle) => {
379+
NormalizedRenderTarget::Image(image_handle) => {
353380
images.get(image_handle).map(|image| &image.texture_view)
354381
}
355382
}
@@ -362,47 +389,55 @@ impl RenderTarget {
362389
images: &'a RenderAssets<Image>,
363390
) -> Option<TextureFormat> {
364391
match self {
365-
RenderTarget::Window(window_id) => windows
366-
.get(window_id)
392+
NormalizedRenderTarget::Window(window_ref) => windows
393+
.get(&window_ref.entity())
367394
.and_then(|window| window.swap_chain_texture_format),
368-
RenderTarget::Image(image_handle) => {
395+
NormalizedRenderTarget::Image(image_handle) => {
369396
images.get(image_handle).map(|image| image.texture_format)
370397
}
371398
}
372399
}
373400

374-
pub fn get_render_target_info(
401+
pub fn get_render_target_info<'a>(
375402
&self,
376-
windows: &Windows,
403+
resolutions: impl IntoIterator<Item = (Entity, &'a Window)>,
377404
images: &Assets<Image>,
378405
) -> Option<RenderTargetInfo> {
379-
Some(match self {
380-
RenderTarget::Window(window_id) => {
381-
let window = windows.get(*window_id)?;
382-
RenderTargetInfo {
383-
physical_size: UVec2::new(window.physical_width(), window.physical_height()),
384-
scale_factor: window.scale_factor(),
385-
}
386-
}
387-
RenderTarget::Image(image_handle) => {
406+
match self {
407+
NormalizedRenderTarget::Window(window_ref) => resolutions
408+
.into_iter()
409+
.find(|(entity, _)| *entity == window_ref.entity())
410+
.map(|(_, window)| RenderTargetInfo {
411+
physical_size: UVec2::new(
412+
window.resolution.physical_width(),
413+
window.resolution.physical_height(),
414+
),
415+
scale_factor: window.resolution.scale_factor(),
416+
}),
417+
NormalizedRenderTarget::Image(image_handle) => {
388418
let image = images.get(image_handle)?;
389419
let Extent3d { width, height, .. } = image.texture_descriptor.size;
390-
RenderTargetInfo {
420+
Some(RenderTargetInfo {
391421
physical_size: UVec2::new(width, height),
392422
scale_factor: 1.0,
393-
}
423+
})
394424
}
395-
})
425+
}
396426
}
427+
397428
// Check if this render target is contained in the given changed windows or images.
398429
fn is_changed(
399430
&self,
400-
changed_window_ids: &[WindowId],
431+
changed_window_ids: &HashSet<Entity>,
401432
changed_image_handles: &HashSet<&Handle<Image>>,
402433
) -> bool {
403434
match self {
404-
RenderTarget::Window(window_id) => changed_window_ids.contains(window_id),
405-
RenderTarget::Image(image_handle) => changed_image_handles.contains(&image_handle),
435+
NormalizedRenderTarget::Window(window_ref) => {
436+
changed_window_ids.contains(&window_ref.entity())
437+
}
438+
NormalizedRenderTarget::Image(image_handle) => {
439+
changed_image_handles.contains(&image_handle)
440+
}
406441
}
407442
}
408443
}
@@ -431,29 +466,16 @@ pub fn camera_system<T: CameraProjection + Component>(
431466
mut window_resized_events: EventReader<WindowResized>,
432467
mut window_created_events: EventReader<WindowCreated>,
433468
mut image_asset_events: EventReader<AssetEvent<Image>>,
434-
windows: Res<Windows>,
469+
primary_window: Query<Entity, With<PrimaryWindow>>,
470+
windows: Query<(Entity, &Window)>,
435471
images: Res<Assets<Image>>,
436472
mut cameras: Query<(&mut Camera, &mut T)>,
437473
) {
438-
let mut changed_window_ids = Vec::new();
439-
440-
// Collect all unique window IDs of changed windows by inspecting created windows
441-
for event in window_created_events.iter() {
442-
if changed_window_ids.contains(&event.id) {
443-
continue;
444-
}
445-
446-
changed_window_ids.push(event.id);
447-
}
474+
let primary_window = primary_window.iter().next();
448475

449-
// Collect all unique window IDs of changed windows by inspecting resized windows
450-
for event in window_resized_events.iter() {
451-
if changed_window_ids.contains(&event.id) {
452-
continue;
453-
}
454-
455-
changed_window_ids.push(event.id);
456-
}
476+
let mut changed_window_ids = HashSet::new();
477+
changed_window_ids.extend(window_created_events.iter().map(|event| event.window));
478+
changed_window_ids.extend(window_resized_events.iter().map(|event| event.window));
457479

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

475-
if camera
476-
.target
477-
.is_changed(&changed_window_ids, &changed_image_handles)
478-
|| camera.is_added()
479-
|| camera_projection.is_changed()
480-
|| camera.computed.old_viewport_size != viewport_size
481-
{
482-
camera.computed.target_info = camera.target.get_render_target_info(&windows, &images);
483-
camera.computed.old_viewport_size = viewport_size;
484-
if let Some(size) = camera.logical_viewport_size() {
485-
camera_projection.update(size.x, size.y);
486-
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
497+
if let Some(normalized_target) = camera.target.normalize(primary_window) {
498+
if normalized_target.is_changed(&changed_window_ids, &changed_image_handles)
499+
|| camera.is_added()
500+
|| camera_projection.is_changed()
501+
|| camera.computed.old_viewport_size != viewport_size
502+
{
503+
camera.computed.target_info =
504+
normalized_target.get_render_target_info(&windows, &images);
505+
if let Some(size) = camera.logical_viewport_size() {
506+
camera_projection.update(size.x, size.y);
507+
camera.computed.projection_matrix = camera_projection.get_projection_matrix();
508+
}
487509
}
488510
}
489511
}
490512
}
491513

492514
#[derive(Component, Debug)]
493515
pub struct ExtractedCamera {
494-
pub target: RenderTarget,
516+
pub target: Option<NormalizedRenderTarget>,
495517
pub physical_viewport_size: Option<UVec2>,
496518
pub physical_target_size: Option<UVec2>,
497519
pub viewport: Option<Viewport>,
@@ -510,7 +532,9 @@ pub fn extract_cameras(
510532
&VisibleEntities,
511533
)>,
512534
>,
535+
primary_window: Extract<Query<Entity, With<PrimaryWindow>>>,
513536
) {
537+
let primary_window = primary_window.iter().next();
514538
for (entity, camera, camera_render_graph, transform, visible_entities) in query.iter() {
515539
if !camera.is_active {
516540
continue;
@@ -525,7 +549,7 @@ pub fn extract_cameras(
525549
}
526550
commands.get_or_spawn(entity).insert((
527551
ExtractedCamera {
528-
target: camera.target.clone(),
552+
target: camera.target.normalize(primary_window),
529553
viewport: camera.viewport.clone(),
530554
physical_viewport_size: Some(viewport_size),
531555
physical_target_size: Some(target_size),

crates/bevy_render/src/camera/camera_driver_node.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::{
2-
camera::{ExtractedCamera, RenderTarget},
2+
camera::{ExtractedCamera, NormalizedRenderTarget},
33
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
44
renderer::RenderContext,
55
view::ExtractedWindows,
@@ -52,8 +52,8 @@ impl Node for CameraDriverNode {
5252
}
5353
previous_order_target = Some(new_order_target);
5454
if let Ok((_, camera)) = self.cameras.get_manual(world, entity) {
55-
if let RenderTarget::Window(id) = camera.target {
56-
camera_windows.insert(id);
55+
if let Some(NormalizedRenderTarget::Window(window_ref)) = camera.target {
56+
camera_windows.insert(window_ref.entity());
5757
}
5858
graph
5959
.run_sub_graph(camera.render_graph.clone(), vec![SlotValue::Entity(entity)])?;

crates/bevy_render/src/lib.rs

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub mod prelude {
3737
};
3838
}
3939

40+
use bevy_window::{PrimaryWindow, RawHandleWrapper};
4041
use globals::GlobalsPlugin;
4142
pub use once_cell;
4243

@@ -50,7 +51,7 @@ use crate::{
5051
};
5152
use bevy_app::{App, AppLabel, Plugin};
5253
use bevy_asset::{AddAsset, AssetServer};
53-
use bevy_ecs::prelude::*;
54+
use bevy_ecs::{prelude::*, system::SystemState};
5455
use bevy_utils::tracing::debug;
5556
use std::{
5657
any::TypeId,
@@ -138,17 +139,17 @@ impl Plugin for RenderPlugin {
138139
.init_asset_loader::<ShaderLoader>()
139140
.init_debug_asset_loader::<ShaderLoader>();
140141

142+
let mut system_state: SystemState<Query<&RawHandleWrapper, With<PrimaryWindow>>> =
143+
SystemState::new(&mut app.world);
144+
let primary_window = system_state.get(&app.world);
145+
141146
if let Some(backends) = self.wgpu_settings.backends {
142-
let windows = app.world.resource_mut::<bevy_window::Windows>();
143147
let instance = wgpu::Instance::new(backends);
144-
145-
let surface = windows
146-
.get_primary()
147-
.and_then(|window| window.raw_handle())
148-
.map(|wrapper| unsafe {
149-
let handle = wrapper.get_handle();
150-
instance.create_surface(&handle)
151-
});
148+
let surface = primary_window.get_single().ok().map(|wrapper| unsafe {
149+
// SAFETY: Plugins should be set up on the main thread.
150+
let handle = wrapper.get_handle();
151+
instance.create_surface(&handle)
152+
});
152153

153154
let request_adapter_options = wgpu::RequestAdapterOptions {
154155
power_preference: self.wgpu_settings.power_preference,

crates/bevy_render/src/view/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -285,10 +285,10 @@ fn prepare_view_targets(
285285
) {
286286
let mut textures = HashMap::default();
287287
for (entity, camera, view) in cameras.iter() {
288-
if let Some(target_size) = camera.physical_target_size {
288+
if let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target) {
289289
if let (Some(out_texture_view), Some(out_texture_format)) = (
290-
camera.target.get_texture_view(&windows, &images),
291-
camera.target.get_texture_format(&windows, &images),
290+
target.get_texture_view(&windows, &images),
291+
target.get_texture_format(&windows, &images),
292292
) {
293293
let size = Extent3d {
294294
width: target_size.x,

0 commit comments

Comments
 (0)