From b493165bfec341136180ce114d67b1aeac2d425b Mon Sep 17 00:00:00 2001 From: Aevyrie Date: Wed, 9 Mar 2022 21:59:57 +0000 Subject: [PATCH 001/157] Use reactive rendering for ui examples. (#4164) # Objective - Use the low power, reactive rendering settings for UI examples. - Make the feature more discoverable by using it in an applicable context. --- examples/ui/button.rs | 4 +++- examples/ui/ui.rs | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/ui/button.rs b/examples/ui/button.rs index 0a0191a834fc17..9a1bd6f8891fad 100644 --- a/examples/ui/button.rs +++ b/examples/ui/button.rs @@ -1,10 +1,12 @@ -use bevy::prelude::*; +use bevy::{prelude::*, winit::WinitSettings}; /// This example illustrates how to create a button that changes color and text based on its /// interaction state. fn main() { App::new() .add_plugins(DefaultPlugins) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) .add_startup_system(setup) .add_system(button_system) .run(); diff --git a/examples/ui/ui.rs b/examples/ui/ui.rs index 74e1817829ecc4..08d6c09272bd93 100644 --- a/examples/ui/ui.rs +++ b/examples/ui/ui.rs @@ -1,12 +1,15 @@ use bevy::{ input::mouse::{MouseScrollUnit, MouseWheel}, prelude::*, + winit::WinitSettings, }; /// This example illustrates the various features of Bevy UI. fn main() { App::new() .add_plugins(DefaultPlugins) + // Only run the app when there is user input. This will significantly reduce CPU/GPU use. + .insert_resource(WinitSettings::desktop_app()) .add_startup_system(setup) .add_system(mouse_scroll) .run(); From 5af746457efd6ee8ab4180081ee0762cdc339f8c Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Thu, 10 Mar 2022 01:14:21 +0000 Subject: [PATCH 002/157] fix cluster tiling calculations (#4148) # Objective fix cluster tilesize and tilecount calculations. Fixes https://github.com/bevyengine/bevy/issues/4127 & https://github.com/bevyengine/bevy/issues/3596 ## Solution - calculate tilesize as smallest integers such that dimensions.xy() tiles will cover the screen - calculate final dimensions as smallest integers such that final dimensions * tilesize will cover the screen there is more cleanup that could be done in these functions. a future PR will likely remove the tilesize completely, so this is just a minimal change set to fix the current bug at small screen sizes Co-authored-by: Carter Anderson --- crates/bevy_pbr/src/light.rs | 107 +++++++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 11 deletions(-) diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 4842b454d46ec6..ec0988f8d6ef69 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -293,11 +293,29 @@ impl ClusterConfig { total, z_slices, .. } => { let aspect_ratio = screen_size.x as f32 / screen_size.y as f32; - let per_layer = *total as f32 / *z_slices as f32; + let mut z_slices = *z_slices; + if *total < z_slices { + warn!("ClusterConfig has more z-slices than total clusters!"); + z_slices = *total; + } + let per_layer = *total as f32 / z_slices as f32; + let y = f32::sqrt(per_layer / aspect_ratio); - let x = (y * aspect_ratio).floor() as u32; - let y = y.floor() as u32; - UVec3::new(x, y, *z_slices) + + let mut x = (y * aspect_ratio) as u32; + let mut y = y as u32; + + // check extremes + if x == 0 { + x = 1; + y = per_layer as u32; + } + if y == 0 { + x = per_layer as u32; + y = 1; + } + + UVec3::new(x, y, z_slices) } } } @@ -369,8 +387,12 @@ impl Clusters { near: f32, far: f32, ) -> Self { + debug_assert!(screen_size.x > 0 && screen_size.y > 0); + debug_assert!(dimensions.x > 0 && dimensions.y > 0 && dimensions.z > 0); Clusters::new( - (screen_size + UVec2::ONE) / dimensions.xy(), + (screen_size.as_vec2() / dimensions.xy().as_vec2()) + .ceil() + .as_uvec2(), screen_size, dimensions.z, near, @@ -380,13 +402,12 @@ impl Clusters { fn update(&mut self, tile_size: UVec2, screen_size: UVec2, z_slices: u32) { self.tile_size = tile_size; - self.axis_slices = UVec3::new( - (screen_size.x + 1) / tile_size.x, - (screen_size.y + 1) / tile_size.y, - z_slices, - ); + self.axis_slices = (screen_size.as_vec2() / tile_size.as_vec2()) + .ceil() + .as_uvec2() + .extend(z_slices); // NOTE: Maximum 4096 clusters due to uniform buffer size constraints - assert!(self.axis_slices.x * self.axis_slices.y * self.axis_slices.z <= 4096); + debug_assert!(self.axis_slices.x * self.axis_slices.y * self.axis_slices.z <= 4096); } } @@ -1240,3 +1261,67 @@ pub fn check_light_mesh_visibility( } } } + +#[cfg(test)] +mod test { + use super::*; + + fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters { + let dims = config.dimensions_for_screen_size(screen_size); + + // note: near & far do not affect tiling + let clusters = Clusters::from_screen_size_and_dimensions(screen_size, dims, 5.0, 1000.0); + + // check we cover the screen + assert!(clusters.tile_size.x * clusters.axis_slices.x >= screen_size.x); + assert!(clusters.tile_size.y * clusters.axis_slices.y >= screen_size.y); + // check a smaller number of clusters would not cover the screen + assert!(clusters.tile_size.x * (clusters.axis_slices.x - 1) < screen_size.x); + assert!(clusters.tile_size.y * (clusters.axis_slices.y - 1) < screen_size.y); + // check a smaller tilesize would not cover the screen + assert!((clusters.tile_size.x - 1) * clusters.axis_slices.x < screen_size.x); + assert!((clusters.tile_size.y - 1) * clusters.axis_slices.y < screen_size.y); + // check we don't have more clusters than pixels + assert!(clusters.axis_slices.x <= screen_size.x); + assert!(clusters.axis_slices.y <= screen_size.y); + + clusters + } + + #[test] + // check tiling for small screen sizes + fn test_default_cluster_setup_small_screensizes() { + for x in 1..100 { + for y in 1..100 { + let screen_size = UVec2::new(x, y); + let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size); + assert!( + clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z + <= 4096 + ); + } + } + } + + #[test] + // check tiling for long thin screen sizes + fn test_default_cluster_setup_small_x() { + for x in 1..10 { + for y in 1..5000 { + let screen_size = UVec2::new(x, y); + let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size); + assert!( + clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z + <= 4096 + ); + + let screen_size = UVec2::new(y, x); + let clusters = test_cluster_tiling(ClusterConfig::default(), screen_size); + assert!( + clusters.axis_slices.x * clusters.axis_slices.y * clusters.axis_slices.z + <= 4096 + ); + } + } + } +} From 0e821da7045852ff677c2b8ee945dc85a899b181 Mon Sep 17 00:00:00 2001 From: Robert Swain Date: Fri, 11 Mar 2022 23:20:18 +0000 Subject: [PATCH 003/157] bevy_render: Batch insertion for prepare_uniform_components (#4179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Make insertion of uniform components faster ## Solution - Use batch insertion in the prepare_uniform_components system - Improves `many_cubes -- sphere` from ~42fps to ~43fps Co-authored-by: François --- crates/bevy_render/src/render_component.rs | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/bevy_render/src/render_component.rs b/crates/bevy_render/src/render_component.rs index 1c72786f89f160..d6a72e337c4c7e 100644 --- a/crates/bevy_render/src/render_component.rs +++ b/crates/bevy_render/src/render_component.rs @@ -111,14 +111,19 @@ fn prepare_uniform_components( C: AsStd140 + Clone, { component_uniforms.uniforms.clear(); - for (entity, component) in components.iter() { - commands - .get_or_spawn(entity) - .insert(DynamicUniformIndex:: { - index: component_uniforms.uniforms.push(component.clone()), - marker: PhantomData, - }); - } + let entities = components + .iter() + .map(|(entity, component)| { + ( + entity, + (DynamicUniformIndex:: { + index: component_uniforms.uniforms.push(component.clone()), + marker: PhantomData, + },), + ) + }) + .collect::>(); + commands.insert_or_spawn_batch(entities); component_uniforms .uniforms From bf6de8962287050369cd98605490bdd7770c87b4 Mon Sep 17 00:00:00 2001 From: Jakob Hellermann Date: Sat, 12 Mar 2022 00:41:06 +0000 Subject: [PATCH 004/157] use marker components for cameras instead of name strings (#3635) **Problem** - whenever you want more than one of the builtin cameras (for example multiple windows, split screen, portals), you need to add a render graph node that executes the correct sub graph, extract the camera into the render world and add the correct `RenderPhase` components - querying for the 3d camera is annoying because you need to compare the camera's name to e.g. `CameraPlugin::CAMERA_3d` **Solution** - Introduce the marker types `Camera3d`, `Camera2d` and `CameraUi` -> `Query<&mut Transform, With>` works - `PerspectiveCameraBundle::new_3d()` and `PerspectiveCameraBundle::::default()` contain the `Camera3d` marker - `OrthographicCameraBundle::new_3d()` has `Camera3d`, `OrthographicCameraBundle::new_2d()` has `Camera2d` - remove `ActiveCameras`, `ExtractedCameraNames` - run 2d, 3d and ui passes for every camera of their respective marker -> no custom setup for multiple windows example needed **Open questions** - do we need a replacement for `ActiveCameras`? What about a component `ActiveCamera { is_active: bool }` similar to `Visibility`? Co-authored-by: Carter Anderson --- crates/bevy_core_pipeline/src/lib.rs | 31 ++--- .../src/main_pass_driver.rs | 11 +- crates/bevy_gltf/src/loader.rs | 7 +- .../bevy_render/src/camera/active_cameras.rs | 73 ---------- crates/bevy_render/src/camera/bundle.rs | 129 ++++++++---------- crates/bevy_render/src/camera/camera.rs | 124 ++++++++++++++++- crates/bevy_render/src/camera/mod.rs | 81 +---------- crates/bevy_render/src/view/mod.rs | 12 +- crates/bevy_ui/src/entity.rs | 13 +- crates/bevy_ui/src/lib.rs | 6 +- crates/bevy_ui/src/render/camera.rs | 22 +-- crates/bevy_ui/src/render/mod.rs | 11 +- crates/bevy_ui/src/render/render_pass.rs | 15 +- examples/3d/render_to_texture.rs | 64 +++++---- examples/game/alien_cake_addict.rs | 12 +- examples/window/multiple_windows.rs | 70 +--------- 16 files changed, 289 insertions(+), 392 deletions(-) delete mode 100644 crates/bevy_render/src/camera/active_cameras.rs diff --git a/crates/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs index a46bba2789f069..bb915965c73a2e 100644 --- a/crates/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -23,7 +23,7 @@ use bevy_app::{App, Plugin}; use bevy_core::FloatOrd; use bevy_ecs::prelude::*; use bevy_render::{ - camera::{ActiveCameras, CameraPlugin, RenderTarget}, + camera::{ActiveCamera, Camera2d, Camera3d, RenderTarget}, color::Color, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, render_phase::{ @@ -367,23 +367,20 @@ pub fn extract_clear_color( pub fn extract_core_pipeline_camera_phases( mut commands: Commands, - active_cameras: Res, + active_2d: Res>, + active_3d: Res>, ) { - if let Some(camera_2d) = active_cameras.get(CameraPlugin::CAMERA_2D) { - if let Some(entity) = camera_2d.entity { - commands - .get_or_spawn(entity) - .insert(RenderPhase::::default()); - } - } - if let Some(camera_3d) = active_cameras.get(CameraPlugin::CAMERA_3D) { - if let Some(entity) = camera_3d.entity { - commands.get_or_spawn(entity).insert_bundle(( - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), - )); - } + if let Some(entity) = active_2d.get() { + commands + .get_or_spawn(entity) + .insert(RenderPhase::::default()); + } + if let Some(entity) = active_3d.get() { + commands.get_or_spawn(entity).insert_bundle(( + RenderPhase::::default(), + RenderPhase::::default(), + RenderPhase::::default(), + )); } } diff --git a/crates/bevy_core_pipeline/src/main_pass_driver.rs b/crates/bevy_core_pipeline/src/main_pass_driver.rs index bb97ab12bf6394..f317b4d0bd16e3 100644 --- a/crates/bevy_core_pipeline/src/main_pass_driver.rs +++ b/crates/bevy_core_pipeline/src/main_pass_driver.rs @@ -1,6 +1,6 @@ use bevy_ecs::world::World; use bevy_render::{ - camera::{CameraPlugin, ExtractedCameraNames}, + camera::{ActiveCamera, Camera2d, Camera3d}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, renderer::RenderContext, }; @@ -14,18 +14,17 @@ impl Node for MainPassDriverNode { _render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let extracted_cameras = world.resource::(); - if let Some(camera_2d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_2D) { + if let Some(camera_2d) = world.resource::>().get() { graph.run_sub_graph( crate::draw_2d_graph::NAME, - vec![SlotValue::Entity(*camera_2d)], + vec![SlotValue::Entity(camera_2d)], )?; } - if let Some(camera_3d) = extracted_cameras.entities.get(CameraPlugin::CAMERA_3D) { + if let Some(camera_3d) = world.resource::>().get() { graph.run_sub_graph( crate::draw_3d_graph::NAME, - vec![SlotValue::Entity(*camera_3d)], + vec![SlotValue::Entity(camera_3d)], )?; } diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 17dd430e74a284..293e4bece8dd1f 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -12,7 +12,7 @@ use bevy_pbr::{ }; use bevy_render::{ camera::{ - Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection, + Camera, Camera2d, Camera3d, CameraProjection, OrthographicProjection, PerspectiveProjection, }, color::Color, mesh::{Indices, Mesh, VertexAttributeValues}, @@ -494,11 +494,10 @@ fn load_node( }; node.insert(Camera { - name: Some(CameraPlugin::CAMERA_2D.to_owned()), projection_matrix: orthographic_projection.get_projection_matrix(), ..Default::default() }); - node.insert(orthographic_projection); + node.insert(orthographic_projection).insert(Camera2d); } gltf::camera::Projection::Perspective(perspective) => { let mut perspective_projection: PerspectiveProjection = PerspectiveProjection { @@ -513,13 +512,13 @@ fn load_node( perspective_projection.aspect_ratio = aspect_ratio; } node.insert(Camera { - name: Some(CameraPlugin::CAMERA_3D.to_owned()), projection_matrix: perspective_projection.get_projection_matrix(), near: perspective_projection.near, far: perspective_projection.far, ..Default::default() }); node.insert(perspective_projection); + node.insert(Camera3d); } } } diff --git a/crates/bevy_render/src/camera/active_cameras.rs b/crates/bevy_render/src/camera/active_cameras.rs deleted file mode 100644 index 42cebc65a67f2b..00000000000000 --- a/crates/bevy_render/src/camera/active_cameras.rs +++ /dev/null @@ -1,73 +0,0 @@ -use super::Camera; -use bevy_ecs::{ - entity::Entity, - system::{Query, ResMut}, -}; -use bevy_utils::HashMap; - -#[derive(Debug, Default)] -pub struct ActiveCamera { - pub name: String, - pub entity: Option, -} - -#[derive(Debug, Default)] -pub struct ActiveCameras { - cameras: HashMap, -} - -impl ActiveCameras { - pub fn add(&mut self, name: &str) { - self.cameras.insert( - name.to_string(), - ActiveCamera { - name: name.to_string(), - ..Default::default() - }, - ); - } - - pub fn get(&self, name: &str) -> Option<&ActiveCamera> { - self.cameras.get(name) - } - - pub fn get_mut(&mut self, name: &str) -> Option<&mut ActiveCamera> { - self.cameras.get_mut(name) - } - - pub fn remove(&mut self, name: &str) -> Option { - self.cameras.remove(name) - } - - pub fn iter(&self) -> impl Iterator { - self.cameras.values() - } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.cameras.values_mut() - } -} - -pub fn active_cameras_system( - mut active_cameras: ResMut, - query: Query<(Entity, &Camera)>, -) { - for (name, active_camera) in &mut active_cameras.cameras { - if active_camera - .entity - .map_or(false, |entity| query.get(entity).is_err()) - { - active_camera.entity = None; - } - - if active_camera.entity.is_none() { - for (camera_entity, camera) in query.iter() { - if let Some(ref current_name) = camera.name { - if current_name == name { - active_camera.entity = Some(camera_entity); - } - } - } - } - } -} diff --git a/crates/bevy_render/src/camera/bundle.rs b/crates/bevy_render/src/camera/bundle.rs index 920a12bb353359..f32aad826c5b67 100644 --- a/crates/bevy_render/src/camera/bundle.rs +++ b/crates/bevy_render/src/camera/bundle.rs @@ -1,36 +1,48 @@ use crate::{ - camera::{ - Camera, CameraPlugin, DepthCalculation, OrthographicProjection, PerspectiveProjection, - ScalingMode, - }, + camera::{Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection}, primitives::Frustum, view::VisibleEntities, }; -use bevy_ecs::bundle::Bundle; +use bevy_ecs::{bundle::Bundle, prelude::Component}; use bevy_math::Vec3; use bevy_transform::components::{GlobalTransform, Transform}; -use super::CameraProjection; +use super::{CameraProjection, ScalingMode}; + +#[derive(Component, Default)] +pub struct Camera3d; + +#[derive(Component, Default)] +pub struct Camera2d; /// Component bundle for camera entities with perspective projection /// /// Use this for 3D rendering. #[derive(Bundle)] -pub struct PerspectiveCameraBundle { +pub struct PerspectiveCameraBundle { pub camera: Camera, pub perspective_projection: PerspectiveProjection, pub visible_entities: VisibleEntities, pub frustum: Frustum, pub transform: Transform, pub global_transform: GlobalTransform, + pub marker: M, +} + +impl Default for PerspectiveCameraBundle { + fn default() -> Self { + PerspectiveCameraBundle::new_3d() + } } -impl PerspectiveCameraBundle { +impl PerspectiveCameraBundle { pub fn new_3d() -> Self { - Default::default() + PerspectiveCameraBundle::new() } +} - pub fn with_name(name: &str) -> Self { +impl PerspectiveCameraBundle { + pub fn new() -> Self { let perspective_projection = PerspectiveProjection::default(); let view_projection = perspective_projection.get_projection_matrix(); let frustum = Frustum::from_view_projection( @@ -41,7 +53,6 @@ impl PerspectiveCameraBundle { ); PerspectiveCameraBundle { camera: Camera { - name: Some(name.to_string()), near: perspective_projection.near, far: perspective_projection.far, ..Default::default() @@ -51,30 +62,56 @@ impl PerspectiveCameraBundle { frustum, transform: Default::default(), global_transform: Default::default(), + marker: M::default(), } } } -impl Default for PerspectiveCameraBundle { - fn default() -> Self { - PerspectiveCameraBundle::with_name(CameraPlugin::CAMERA_3D) - } -} - /// Component bundle for camera entities with orthographic projection /// /// Use this for 2D games, isometric games, CAD-like 3D views. #[derive(Bundle)] -pub struct OrthographicCameraBundle { +pub struct OrthographicCameraBundle { pub camera: Camera, pub orthographic_projection: OrthographicProjection, pub visible_entities: VisibleEntities, pub frustum: Frustum, pub transform: Transform, pub global_transform: GlobalTransform, + pub marker: M, +} + +impl OrthographicCameraBundle { + pub fn new_3d() -> Self { + let orthographic_projection = OrthographicProjection { + scaling_mode: ScalingMode::FixedVertical, + depth_calculation: DepthCalculation::Distance, + ..Default::default() + }; + let view_projection = orthographic_projection.get_projection_matrix(); + let frustum = Frustum::from_view_projection( + &view_projection, + &Vec3::ZERO, + &Vec3::Z, + orthographic_projection.far(), + ); + OrthographicCameraBundle { + camera: Camera { + near: orthographic_projection.near, + far: orthographic_projection.far, + ..Default::default() + }, + orthographic_projection, + visible_entities: VisibleEntities::default(), + frustum, + transform: Default::default(), + global_transform: Default::default(), + marker: Camera3d, + } + } } -impl OrthographicCameraBundle { +impl OrthographicCameraBundle { /// Create an orthographic projection camera to render 2D content. /// /// The projection creates a camera space where X points to the right of the screen, @@ -112,7 +149,6 @@ impl OrthographicCameraBundle { ); OrthographicCameraBundle { camera: Camera { - name: Some(CameraPlugin::CAMERA_2D.to_string()), near: orthographic_projection.near, far: orthographic_projection.far, ..Default::default() @@ -122,58 +158,7 @@ impl OrthographicCameraBundle { frustum, transform, global_transform: Default::default(), - } - } - - pub fn new_3d() -> Self { - let orthographic_projection = OrthographicProjection { - scaling_mode: ScalingMode::FixedVertical, - depth_calculation: DepthCalculation::Distance, - ..Default::default() - }; - let view_projection = orthographic_projection.get_projection_matrix(); - let frustum = Frustum::from_view_projection( - &view_projection, - &Vec3::ZERO, - &Vec3::Z, - orthographic_projection.far(), - ); - OrthographicCameraBundle { - camera: Camera { - name: Some(CameraPlugin::CAMERA_3D.to_string()), - near: orthographic_projection.near, - far: orthographic_projection.far, - ..Default::default() - }, - orthographic_projection, - visible_entities: VisibleEntities::default(), - frustum, - transform: Default::default(), - global_transform: Default::default(), - } - } - - pub fn with_name(name: &str) -> Self { - let orthographic_projection = OrthographicProjection::default(); - let view_projection = orthographic_projection.get_projection_matrix(); - let frustum = Frustum::from_view_projection( - &view_projection, - &Vec3::ZERO, - &Vec3::Z, - orthographic_projection.far(), - ); - OrthographicCameraBundle { - camera: Camera { - name: Some(name.to_string()), - near: orthographic_projection.near, - far: orthographic_projection.far, - ..Default::default() - }, - orthographic_projection, - visible_entities: VisibleEntities::default(), - frustum, - transform: Default::default(), - global_transform: Default::default(), + marker: Camera2d, } } } diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index 07e748fe8211fe..6da11ccb66473c 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,16 +1,23 @@ +use std::marker::PhantomData; + use crate::{ - camera::CameraProjection, prelude::Image, render_asset::RenderAssets, - render_resource::TextureView, view::ExtractedWindows, + camera::CameraProjection, + prelude::Image, + render_asset::RenderAssets, + render_resource::TextureView, + view::{ExtractedView, ExtractedWindows, VisibleEntities}, + RenderApp, RenderStage, }; +use bevy_app::{App, CoreStage, Plugin, StartupStage}; use bevy_asset::{AssetEvent, Assets, Handle}; use bevy_ecs::{ component::Component, entity::Entity, event::EventReader, - prelude::{DetectChanges, QueryState}, + prelude::{DetectChanges, QueryState, With}, query::Added, reflect::ReflectComponent, - system::{QuerySet, Res}, + system::{Commands, Query, QuerySet, Res, ResMut}, }; use bevy_math::{Mat4, UVec2, Vec2, Vec3}; use bevy_reflect::{Reflect, ReflectDeserialize}; @@ -24,7 +31,6 @@ use wgpu::Extent3d; #[reflect(Component)] pub struct Camera { pub projection_matrix: Mat4, - pub name: Option, #[reflect(ignore)] pub target: RenderTarget, #[reflect(ignore)] @@ -203,3 +209,111 @@ pub fn camera_system( } } } + +pub struct CameraTypePlugin(PhantomData); + +impl Default for CameraTypePlugin { + fn default() -> Self { + Self(Default::default()) + } +} + +impl Plugin for CameraTypePlugin { + fn build(&self, app: &mut App) { + app.init_resource::>() + .add_startup_system_to_stage(StartupStage::PostStartup, set_active_camera::) + .add_system_to_stage(CoreStage::PostUpdate, set_active_camera::); + if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { + render_app.add_system_to_stage(RenderStage::Extract, extract_cameras::); + } + } +} + +/// The canonical source of the "active camera" of the given camera type `T`. +#[derive(Debug)] +pub struct ActiveCamera { + camera: Option, + marker: PhantomData, +} + +impl Default for ActiveCamera { + fn default() -> Self { + Self { + camera: Default::default(), + marker: Default::default(), + } + } +} + +impl Clone for ActiveCamera { + fn clone(&self) -> Self { + Self { + camera: self.camera, + marker: self.marker, + } + } +} + +impl ActiveCamera { + /// Sets the active camera to the given `camera` entity. + pub fn set(&mut self, camera: Entity) { + self.camera = Some(camera); + } + + /// Returns the active camera, if it exists. + pub fn get(&self) -> Option { + self.camera + } +} + +pub fn set_active_camera( + mut active_camera: ResMut>, + cameras: Query>, +) { + if active_camera.get().is_some() { + return; + } + + if let Some(camera) = cameras.iter().next() { + active_camera.camera = Some(camera); + } +} + +#[derive(Component, Debug)] +pub struct ExtractedCamera { + pub target: RenderTarget, + pub physical_size: Option, +} + +pub fn extract_cameras( + mut commands: Commands, + windows: Res, + images: Res>, + active_camera: Res>, + query: Query<(&Camera, &GlobalTransform, &VisibleEntities), With>, +) { + if let Some(entity) = active_camera.get() { + if let Ok((camera, transform, visible_entities)) = query.get(entity) { + if let Some(size) = camera.target.get_physical_size(&windows, &images) { + commands.get_or_spawn(entity).insert_bundle(( + ExtractedCamera { + target: camera.target.clone(), + physical_size: camera.target.get_physical_size(&windows, &images), + }, + ExtractedView { + projection: camera.projection_matrix, + transform: *transform, + width: size.x.max(1), + height: size.y.max(1), + near: camera.near, + far: camera.far, + }, + visible_entities.clone(), + M::default(), + )); + } + } + } + + commands.insert_resource(active_camera.clone()) +} diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 35e3451a4320eb..7ebdf2c20c9412 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -1,41 +1,23 @@ -mod active_cameras; mod bundle; #[allow(clippy::module_inception)] mod camera; mod projection; -pub use active_cameras::*; -use bevy_asset::Assets; -use bevy_math::UVec2; -use bevy_transform::components::GlobalTransform; -use bevy_utils::HashMap; -use bevy_window::Windows; pub use bundle::*; pub use camera::*; pub use projection::*; use crate::{ - prelude::Image, primitives::Aabb, - view::{ComputedVisibility, ExtractedView, Visibility, VisibleEntities}, - RenderApp, RenderStage, + view::{ComputedVisibility, Visibility, VisibleEntities}, }; use bevy_app::{App, CoreStage, Plugin}; -use bevy_ecs::prelude::*; #[derive(Default)] pub struct CameraPlugin; -impl CameraPlugin { - pub const CAMERA_2D: &'static str = "camera_2d"; - pub const CAMERA_3D: &'static str = "camera_3d"; -} - impl Plugin for CameraPlugin { fn build(&self, app: &mut App) { - let mut active_cameras = ActiveCameras::default(); - active_cameras.add(Self::CAMERA_2D); - active_cameras.add(Self::CAMERA_3D); app.register_type::() .register_type::() .register_type::() @@ -46,8 +28,6 @@ impl Plugin for CameraPlugin { .register_type::() .register_type::() .register_type::() - .insert_resource(active_cameras) - .add_system_to_stage(CoreStage::PostUpdate, crate::camera::active_cameras_system) .add_system_to_stage( CoreStage::PostUpdate, crate::camera::camera_system::, @@ -55,61 +35,8 @@ impl Plugin for CameraPlugin { .add_system_to_stage( CoreStage::PostUpdate, crate::camera::camera_system::, - ); - if let Ok(render_app) = app.get_sub_app_mut(RenderApp) { - render_app - .init_resource::() - .add_system_to_stage(RenderStage::Extract, extract_cameras); - } - } -} - -#[derive(Default)] -pub struct ExtractedCameraNames { - pub entities: HashMap, -} - -#[derive(Component, Debug)] -pub struct ExtractedCamera { - pub target: RenderTarget, - pub name: Option, - pub physical_size: Option, -} - -fn extract_cameras( - mut commands: Commands, - active_cameras: Res, - windows: Res, - images: Res>, - query: Query<(Entity, &Camera, &GlobalTransform, &VisibleEntities)>, -) { - let mut entities = HashMap::default(); - for camera in active_cameras.iter() { - let name = &camera.name; - if let Some((entity, camera, transform, visible_entities)) = - camera.entity.and_then(|e| query.get(e).ok()) - { - if let Some(size) = camera.target.get_physical_size(&windows, &images) { - entities.insert(name.clone(), entity); - commands.get_or_spawn(entity).insert_bundle(( - ExtractedCamera { - target: camera.target.clone(), - name: camera.name.clone(), - physical_size: camera.target.get_physical_size(&windows, &images), - }, - ExtractedView { - projection: camera.projection_matrix, - transform: *transform, - width: size.x.max(1), - height: size.y.max(1), - near: camera.near, - far: camera.far, - }, - visible_entities.clone(), - )); - } - } + ) + .add_plugin(CameraTypePlugin::::default()) + .add_plugin(CameraTypePlugin::::default()); } - - commands.insert_resource(ExtractedCameraNames { entities }); } diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 7f31d9458bedb3..ba712a31943cde 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -9,7 +9,7 @@ use wgpu::{ pub use window::*; use crate::{ - camera::{ExtractedCamera, ExtractedCameraNames}, + camera::ExtractedCamera, prelude::Image, render_asset::RenderAssets, render_resource::{std140::AsStd140, DynamicUniformVec, Texture, TextureView}, @@ -174,20 +174,14 @@ fn prepare_view_uniforms( #[allow(clippy::too_many_arguments)] fn prepare_view_targets( mut commands: Commands, - camera_names: Res, windows: Res, images: Res>, msaa: Res, render_device: Res, mut texture_cache: ResMut, - cameras: Query<&ExtractedCamera>, + cameras: Query<(Entity, &ExtractedCamera)>, ) { - for entity in camera_names.entities.values().copied() { - let camera = if let Ok(camera) = cameras.get(entity) { - camera - } else { - continue; - }; + for (entity, camera) in cameras.iter() { if let Some(size) = camera.physical_size { if let Some(texture_view) = camera.target.get_texture_view(&windows, &images) { let sampled_target = if msaa.samples > 1 { diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 02aac0c6bc8c2d..c2a6b218bbc8c6 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -2,9 +2,9 @@ use crate::{ widget::{Button, ImageMode}, - CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, + CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, }; -use bevy_ecs::bundle::Bundle; +use bevy_ecs::{bundle::Bundle, prelude::Component}; use bevy_render::{ camera::{Camera, DepthCalculation, OrthographicProjection, WindowOrigin}, view::{Visibility, VisibleEntities}, @@ -135,10 +135,12 @@ impl Default for ButtonBundle { } } } +#[derive(Component, Default)] +pub struct CameraUi; /// The camera that is needed to see UI elements #[derive(Bundle, Debug)] -pub struct UiCameraBundle { +pub struct UiCameraBundle { /// The camera component pub camera: Camera, /// The orthographic projection settings @@ -150,16 +152,16 @@ pub struct UiCameraBundle { /// Contains visible entities // FIXME there is no frustrum culling for UI pub visible_entities: VisibleEntities, + pub marker: M, } -impl Default for UiCameraBundle { +impl Default for UiCameraBundle { fn default() -> Self { // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset // the camera's translation by far and use a right handed coordinate system let far = 1000.0; UiCameraBundle { camera: Camera { - name: Some(CAMERA_UI.to_string()), ..Default::default() }, orthographic_projection: OrthographicProjection { @@ -171,6 +173,7 @@ impl Default for UiCameraBundle { transform: Transform::from_xyz(0.0, 0.0, far - 0.1), global_transform: Default::default(), visible_entities: Default::default(), + marker: CameraUi, } } } diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index 2f89f6df9ca856..2a44323966a3dd 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -12,6 +12,7 @@ pub mod entity; pub mod update; pub mod widget; +use bevy_render::camera::CameraTypePlugin; pub use flex::*; pub use focus::*; pub use margins::*; @@ -31,6 +32,8 @@ use bevy_math::{Rect, Size}; use bevy_transform::TransformSystem; use update::{ui_z_system, update_clipping_system}; +use crate::prelude::CameraUi; + /// The basic plugin for Bevy UI #[derive(Default)] pub struct UiPlugin; @@ -46,7 +49,8 @@ pub enum UiSystem { impl Plugin for UiPlugin { fn build(&self, app: &mut App) { - app.init_resource::() + app.add_plugin(CameraTypePlugin::::default()) + .init_resource::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_ui/src/render/camera.rs b/crates/bevy_ui/src/render/camera.rs index 2e9ccf29cc5496..d03c972951714a 100644 --- a/crates/bevy_ui/src/render/camera.rs +++ b/crates/bevy_ui/src/render/camera.rs @@ -1,16 +1,18 @@ use bevy_ecs::prelude::*; -use bevy_render::{camera::ActiveCameras, render_phase::RenderPhase}; +use bevy_render::{camera::ActiveCamera, render_phase::RenderPhase}; -/// The name of the UI camera -pub const CAMERA_UI: &str = "camera_ui"; +use crate::prelude::CameraUi; + +use super::TransparentUi; /// Inserts the [`RenderPhase`] into the UI camera -pub fn extract_ui_camera_phases(mut commands: Commands, active_cameras: Res) { - if let Some(camera_ui) = active_cameras.get(CAMERA_UI) { - if let Some(entity) = camera_ui.entity { - commands - .get_or_spawn(entity) - .insert(RenderPhase::::default()); - } +pub fn extract_ui_camera_phases( + mut commands: Commands, + active_camera: Res>, +) { + if let Some(entity) = active_camera.get() { + commands + .get_or_spawn(entity) + .insert(RenderPhase::::default()); } } diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index 579a4acc19f023..dff085aa399936 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -6,8 +6,7 @@ pub use camera::*; pub use pipeline::*; pub use render_pass::*; -use std::ops::Range; - +use crate::{CalculatedClip, Node, UiColor, UiImage}; use bevy_app::prelude::*; use bevy_asset::{load_internal_asset, AssetEvent, Assets, Handle, HandleUntyped}; use bevy_core::FloatOrd; @@ -15,7 +14,6 @@ use bevy_ecs::prelude::*; use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_reflect::TypeUuid; use bevy_render::{ - camera::ActiveCameras, color::Color, render_asset::RenderAssets, render_graph::{RenderGraph, SlotInfo, SlotType}, @@ -31,10 +29,8 @@ use bevy_text::{DefaultTextPipeline, Text}; use bevy_transform::components::GlobalTransform; use bevy_utils::HashMap; use bevy_window::{WindowId, Windows}; - use bytemuck::{Pod, Zeroable}; - -use crate::{CalculatedClip, Node, UiColor, UiImage}; +use std::ops::Range; pub mod node { pub const UI_PASS_DRIVER: &str = "ui_pass_driver"; @@ -61,9 +57,6 @@ pub enum RenderUiSystem { pub fn build_ui_render(app: &mut App) { load_internal_asset!(app, UI_SHADER_HANDLE, "ui.wgsl", Shader::from_wgsl); - let mut active_cameras = app.world.resource_mut::(); - active_cameras.add(CAMERA_UI); - let render_app = match app.get_sub_app_mut(RenderApp) { Ok(render_app) => render_app, Err(_) => return, diff --git a/crates/bevy_ui/src/render/render_pass.rs b/crates/bevy_ui/src/render/render_pass.rs index 005963f04f5d8b..23d1577e6a3bcc 100644 --- a/crates/bevy_ui/src/render/render_pass.rs +++ b/crates/bevy_ui/src/render/render_pass.rs @@ -4,7 +4,7 @@ use bevy_ecs::{ system::{lifetimeless::*, SystemParamItem}, }; use bevy_render::{ - camera::ExtractedCameraNames, + camera::ActiveCamera, render_graph::*, render_phase::*, render_resource::{ @@ -14,20 +14,21 @@ use bevy_render::{ view::*, }; -use super::{draw_ui_graph, UiBatch, UiImageBindGroups, UiMeta, CAMERA_UI}; +use crate::prelude::CameraUi; + +use super::{draw_ui_graph, UiBatch, UiImageBindGroups, UiMeta}; pub struct UiPassDriverNode; -impl bevy_render::render_graph::Node for UiPassDriverNode { +impl Node for UiPassDriverNode { fn run( &self, graph: &mut RenderGraphContext, _render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let extracted_cameras = world.resource::(); - if let Some(camera_ui) = extracted_cameras.entities.get(CAMERA_UI) { - graph.run_sub_graph(draw_ui_graph::NAME, vec![SlotValue::Entity(*camera_ui)])?; + if let Some(camera_ui) = world.resource::>().get() { + graph.run_sub_graph(draw_ui_graph::NAME, vec![SlotValue::Entity(camera_ui)])?; } Ok(()) @@ -49,7 +50,7 @@ impl UiPassNode { } } -impl bevy_render::render_graph::Node for UiPassNode { +impl Node for UiPassNode { fn input(&self) -> Vec { vec![SlotInfo::new(UiPassNode::IN_VIEW, SlotType::Entity)] } diff --git a/examples/3d/render_to_texture.rs b/examples/3d/render_to_texture.rs index abaaf73a3e4e85..64d403b0c4631f 100644 --- a/examples/3d/render_to_texture.rs +++ b/examples/3d/render_to_texture.rs @@ -5,8 +5,8 @@ use bevy::{ prelude::*, reflect::TypeUuid, render::{ - camera::{ActiveCameras, Camera, ExtractedCameraNames, RenderTarget}, - render_graph::{NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, + camera::{ActiveCamera, Camera, CameraTypePlugin, RenderTarget}, + render_graph::{Node, NodeRunError, RenderGraph, RenderGraphContext, SlotValue}, render_phase::RenderPhase, render_resource::{ Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, @@ -17,6 +17,9 @@ use bevy::{ }, }; +#[derive(Component, Default)] +pub struct FirstPassCamera; + // This handle will point at the texture to which we will render in the first pass. pub const RENDER_IMAGE_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Image::TYPE_UUID, 13378939762009864029); @@ -24,26 +27,24 @@ pub const RENDER_IMAGE_HANDLE: HandleUntyped = // The name of the final node of the first pass. pub const FIRST_PASS_DRIVER: &str = "first_pass_driver"; -// The name of the camera that determines the view rendered in the first pass. -pub const FIRST_PASS_CAMERA: &str = "first_pass_camera"; - fn main() { let mut app = App::new(); app.insert_resource(Msaa { samples: 4 }) // Use 4x MSAA .add_plugins(DefaultPlugins) + .add_plugin(CameraTypePlugin::::default()) .add_startup_system(setup) .add_system(cube_rotator_system) .add_system(rotator_system); let render_app = app.sub_app_mut(RenderApp); - + let driver = FirstPassCameraDriver::new(&mut render_app.world); // This will add 3D render phases for the new camera. render_app.add_system_to_stage(RenderStage::Extract, extract_first_pass_camera_phases); let mut graph = render_app.world.resource_mut::(); // Add a node for the first pass. - graph.add_node(FIRST_PASS_DRIVER, FirstPassCameraDriver); + graph.add_node(FIRST_PASS_DRIVER, driver); // The first pass's dependencies include those of the main pass. graph @@ -61,30 +62,44 @@ fn main() { } // Add 3D render phases for FIRST_PASS_CAMERA. -fn extract_first_pass_camera_phases(mut commands: Commands, active_cameras: Res) { - if let Some(camera) = active_cameras.get(FIRST_PASS_CAMERA) { - if let Some(entity) = camera.entity { - commands.get_or_spawn(entity).insert_bundle(( - RenderPhase::::default(), - RenderPhase::::default(), - RenderPhase::::default(), - )); - } +fn extract_first_pass_camera_phases( + mut commands: Commands, + active: Res>, +) { + if let Some(entity) = active.get() { + commands.get_or_spawn(entity).insert_bundle(( + RenderPhase::::default(), + RenderPhase::::default(), + RenderPhase::::default(), + )); } } // A node for the first pass camera that runs draw_3d_graph with this camera. -struct FirstPassCameraDriver; -impl bevy::render::render_graph::Node for FirstPassCameraDriver { +struct FirstPassCameraDriver { + query: QueryState>, +} + +impl FirstPassCameraDriver { + pub fn new(render_world: &mut World) -> Self { + Self { + query: QueryState::new(render_world), + } + } +} +impl Node for FirstPassCameraDriver { + fn update(&mut self, world: &mut World) { + self.query.update_archetypes(world); + } + fn run( &self, graph: &mut RenderGraphContext, _render_context: &mut RenderContext, world: &World, ) -> Result<(), NodeRunError> { - let extracted_cameras = world.resource::(); - if let Some(camera_3d) = extracted_cameras.entities.get(FIRST_PASS_CAMERA) { - graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(*camera_3d)])?; + for camera in self.query.iter_manual(world) { + graph.run_sub_graph(draw_3d_graph::NAME, vec![SlotValue::Entity(camera)])?; } Ok(()) } @@ -102,7 +117,6 @@ fn setup( mut commands: Commands, mut meshes: ResMut>, mut materials: ResMut>, - mut active_cameras: ResMut, mut images: ResMut>, mut clear_colors: ResMut, ) { @@ -165,17 +179,15 @@ fn setup( // First pass camera let render_target = RenderTarget::Image(image_handle); clear_colors.insert(render_target.clone(), Color::WHITE); - active_cameras.add(FIRST_PASS_CAMERA); commands - .spawn_bundle(PerspectiveCameraBundle { + .spawn_bundle(PerspectiveCameraBundle:: { camera: Camera { - name: Some(FIRST_PASS_CAMERA.to_string()), target: render_target, ..default() }, transform: Transform::from_translation(Vec3::new(0.0, 0.0, 15.0)) .looking_at(Vec3::default(), Vec3::Y), - ..default() + ..PerspectiveCameraBundle::new() }) .insert(first_pass_layer); // NOTE: omitting the RenderLayers component for this camera may cause a validation error: diff --git a/examples/game/alien_cake_addict.rs b/examples/game/alien_cake_addict.rs index 43a1acdeb96c05..699c68eeccd71f 100644 --- a/examples/game/alien_cake_addict.rs +++ b/examples/game/alien_cake_addict.rs @@ -1,6 +1,4 @@ -use bevy::{ - core::FixedTimestep, ecs::schedule::SystemSet, prelude::*, render::camera::CameraPlugin, -}; +use bevy::{core::FixedTimestep, ecs::schedule::SystemSet, prelude::*, render::camera::Camera3d}; use rand::Rng; #[derive(Clone, Eq, PartialEq, Debug, Hash)] @@ -257,7 +255,7 @@ fn focus_camera( time: Res