diff --git a/crates/bevy_render/src/camera/active_cameras.rs b/crates/bevy_render/src/camera/active_cameras.rs index 030c296c98f9f..3fe61f77217b7 100644 --- a/crates/bevy_render/src/camera/active_cameras.rs +++ b/crates/bevy_render/src/camera/active_cameras.rs @@ -9,6 +9,7 @@ use bevy_utils::HashMap; #[derive(Debug, Default)] pub struct ActiveCamera { + pub name: String, pub entity: Option, pub bindings: RenderResourceBindings, } @@ -20,8 +21,13 @@ pub struct ActiveCameras { impl ActiveCameras { pub fn add(&mut self, name: &str) { - self.cameras - .insert(name.to_string(), ActiveCamera::default()); + self.cameras.insert( + name.to_string(), + ActiveCamera { + name: name.to_string(), + ..Default::default() + }, + ); } pub fn get(&self, name: &str) -> Option<&ActiveCamera> { @@ -31,6 +37,14 @@ impl ActiveCameras { pub fn get_mut(&mut self, name: &str) -> Option<&mut ActiveCamera> { self.cameras.get_mut(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( diff --git a/crates/bevy_render/src/camera/visible_entities.rs b/crates/bevy_render/src/camera/visible_entities.rs index f116c17f33d89..1fd0a9da460bc 100644 --- a/crates/bevy_render/src/camera/visible_entities.rs +++ b/crates/bevy_render/src/camera/visible_entities.rs @@ -1,7 +1,7 @@ use super::{Camera, DepthCalculation}; -use crate::prelude::Visible; +use crate::{draw::OutsideFrustum, prelude::Visible}; use bevy_core::FloatOrd; -use bevy_ecs::{entity::Entity, query::With, reflect::ReflectComponent, system::Query}; +use bevy_ecs::{entity::Entity, query::Without, reflect::ReflectComponent, system::Query}; use bevy_reflect::Reflect; use bevy_transform::prelude::GlobalTransform; @@ -204,8 +204,8 @@ pub fn visible_entities_system( &mut VisibleEntities, Option<&RenderLayers>, )>, - visible_query: Query<(Entity, &Visible, Option<&RenderLayers>)>, - visible_transform_query: Query<&GlobalTransform, With>, + visible_query: Query<(Entity, &Visible, Option<&RenderLayers>), Without>, + visible_transform_query: Query<&GlobalTransform, Without>, ) { for (camera, camera_global_transform, mut visible_entities, maybe_camera_mask) in camera_query.iter_mut() diff --git a/crates/bevy_render/src/draw.rs b/crates/bevy_render/src/draw.rs index 1daf2de282df4..6faac65c32865 100644 --- a/crates/bevy_render/src/draw.rs +++ b/crates/bevy_render/src/draw.rs @@ -66,6 +66,17 @@ impl Default for Visible { } } +/// A component that indicates that an entity is outside the view frustum. +/// Any entity with this component will be ignored during rendering. +/// +/// # Note +/// This does not handle multiple "views" properly as it is a "global" filter. +/// This will be resolved in the future. For now, disable frustum culling if you +/// need to support multiple views (ex: set the `SpriteSettings::frustum_culling_enabled` resource). +#[derive(Debug, Default, Clone, Reflect)] +#[reflect(Component)] +pub struct OutsideFrustum; + /// A component that indicates how to draw an entity. #[derive(Debug, Clone, Reflect)] #[reflect(Component)] diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 2d93873708be1..3f0ffe41efcbf 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -17,7 +17,8 @@ use bevy_ecs::{ system::{IntoExclusiveSystem, IntoSystem}, }; use bevy_transform::TransformSystem; -use draw::Visible; +use draw::{OutsideFrustum, Visible}; + pub use once_cell; pub mod prelude { @@ -137,6 +138,7 @@ impl Plugin for RenderPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_render/src/pipeline/render_pipelines.rs b/crates/bevy_render/src/pipeline/render_pipelines.rs index 24a43e92a27e2..ff3a041118501 100644 --- a/crates/bevy_render/src/pipeline/render_pipelines.rs +++ b/crates/bevy_render/src/pipeline/render_pipelines.rs @@ -1,12 +1,13 @@ use super::{PipelineDescriptor, PipelineSpecialization}; use crate::{ - draw::{Draw, DrawContext}, + draw::{Draw, DrawContext, OutsideFrustum}, mesh::{Indices, Mesh}, prelude::{Msaa, Visible}, renderer::RenderResourceBindings, }; use bevy_asset::{Assets, Handle}; use bevy_ecs::{ + query::Without, reflect::ReflectComponent, system::{Query, Res, ResMut}, }; @@ -86,7 +87,10 @@ pub fn draw_render_pipelines_system( mut render_resource_bindings: ResMut, msaa: Res, meshes: Res>, - mut query: Query<(&mut Draw, &mut RenderPipelines, &Handle, &Visible)>, + mut query: Query< + (&mut Draw, &mut RenderPipelines, &Handle, &Visible), + Without, + >, ) { for (mut draw, mut render_pipelines, mesh_handle, visible) in query.iter_mut() { if !visible.is_visible { diff --git a/crates/bevy_render/src/shader/shader_defs.rs b/crates/bevy_render/src/shader/shader_defs.rs index db862dc1d599b..12ad56e29cab2 100644 --- a/crates/bevy_render/src/shader/shader_defs.rs +++ b/crates/bevy_render/src/shader/shader_defs.rs @@ -1,8 +1,11 @@ use bevy_asset::{Asset, Assets, Handle}; -use crate::{pipeline::RenderPipelines, Texture}; +use crate::{draw::OutsideFrustum, pipeline::RenderPipelines, Texture}; pub use bevy_derive::ShaderDefs; -use bevy_ecs::system::{Query, Res}; +use bevy_ecs::{ + query::Without, + system::{Query, Res}, +}; /// Something that can either be "defined" or "not defined". This is used to determine if a "shader /// def" should be considered "defined" @@ -61,7 +64,7 @@ impl ShaderDef for Option> { } /// Updates [RenderPipelines] with the latest [ShaderDefs] -pub fn shader_defs_system(mut query: Query<(&T, &mut RenderPipelines)>) +pub fn shader_defs_system(mut query: Query<(&T, &mut RenderPipelines), Without>) where T: ShaderDefs + Send + Sync + 'static, { @@ -94,7 +97,7 @@ pub fn clear_shader_defs_system(mut query: Query<&mut RenderPipelines>) { /// Updates [RenderPipelines] with the latest [ShaderDefs] from a given asset type pub fn asset_shader_defs_system( assets: Res>, - mut query: Query<(&Handle, &mut RenderPipelines)>, + mut query: Query<(&Handle, &mut RenderPipelines), Without>, ) where T: ShaderDefs + Send + Sync + 'static, { diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 2ae632f368ac7..516621f8a454c 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -24,6 +24,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.4.0", features = ["bevy" bevy_render = { path = "../bevy_render", version = "0.4.0" } bevy_transform = { path = "../bevy_transform", version = "0.4.0" } bevy_utils = { path = "../bevy_utils", version = "0.4.0" } +bevy_window = { path = "../bevy_window", version = "0.4.0" } # other rectangle-pack = "0.2" diff --git a/crates/bevy_sprite/src/frustum_culling.rs b/crates/bevy_sprite/src/frustum_culling.rs new file mode 100644 index 0000000000000..0c75722c6fefb --- /dev/null +++ b/crates/bevy_sprite/src/frustum_culling.rs @@ -0,0 +1,120 @@ +use bevy_asset::{Assets, Handle}; +use bevy_ecs::prelude::{Commands, Entity, Query, Res, With}; +use bevy_math::Vec2; +use bevy_render::{ + camera::{ActiveCameras, Camera}, + draw::OutsideFrustum, +}; +use bevy_transform::components::Transform; +use bevy_window::Windows; + +use crate::{Sprite, TextureAtlas, TextureAtlasSprite}; + +struct Rect { + position: Vec2, + size: Vec2, +} + +impl Rect { + #[inline] + pub fn is_intersecting(&self, other: Rect) -> bool { + self.position.distance(other.position) < (self.get_radius() + other.get_radius()) + } + + #[inline] + pub fn get_radius(&self) -> f32 { + let half_size = self.size / Vec2::splat(2.0); + (half_size.x.powf(2.0) + half_size.y.powf(2.0)).sqrt() + } +} + +pub fn sprite_frustum_culling_system( + mut commands: Commands, + windows: Res, + active_cameras: Res, + camera_transforms: Query<&Transform, With>, + culled_sprites: Query<&OutsideFrustum, With>, + sprites: Query<(Entity, &Transform, &Sprite)>, +) { + let window_size = if let Some(window) = windows.get_primary() { + Vec2::new(window.width(), window.height()) + } else { + return; + }; + + for active_camera_entity in active_cameras.iter().filter_map(|a| a.entity) { + if let Ok(camera_transform) = camera_transforms.get(active_camera_entity) { + let camera_size = window_size * camera_transform.scale.truncate(); + + let rect = Rect { + position: camera_transform.translation.truncate(), + size: camera_size, + }; + + for (entity, drawable_transform, sprite) in sprites.iter() { + let sprite_rect = Rect { + position: drawable_transform.translation.truncate(), + size: sprite.size, + }; + + if rect.is_intersecting(sprite_rect) { + if culled_sprites.get(entity).is_ok() { + commands.entity(entity).remove::(); + } + } else if culled_sprites.get(entity).is_err() { + commands.entity(entity).insert(OutsideFrustum); + } + } + } + } +} + +pub fn atlas_frustum_culling_system( + mut commands: Commands, + windows: Res, + active_cameras: Res, + textures: Res>, + camera_transforms: Query<&Transform, With>, + culled_sprites: Query<&OutsideFrustum, With>, + sprites: Query<( + Entity, + &Transform, + &TextureAtlasSprite, + &Handle, + )>, +) { + let window = windows.get_primary().unwrap(); + let window_size = Vec2::new(window.width(), window.height()); + + for active_camera_entity in active_cameras.iter().filter_map(|a| a.entity) { + if let Ok(camera_transform) = camera_transforms.get(active_camera_entity) { + let camera_size = window_size * camera_transform.scale.truncate(); + + let rect = Rect { + position: camera_transform.translation.truncate(), + size: camera_size, + }; + + for (entity, drawable_transform, sprite, atlas_handle) in sprites.iter() { + if let Some(atlas) = textures.get(atlas_handle) { + if let Some(sprite) = atlas.textures.get(sprite.index as usize) { + let size = Vec2::new(sprite.width(), sprite.height()); + + let sprite_rect = Rect { + position: drawable_transform.translation.truncate(), + size, + }; + + if rect.is_intersecting(sprite_rect) { + if culled_sprites.get(entity).is_ok() { + commands.entity(entity).remove::(); + } + } else if culled_sprites.get(entity).is_err() { + commands.entity(entity).insert(OutsideFrustum); + } + } + } + } + } + } +} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 98034b62605ef..89bc737152e79 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -3,6 +3,7 @@ pub mod entity; mod color_material; mod dynamic_texture_atlas_builder; +mod frustum_culling; mod rect; mod render; mod sprite; @@ -26,10 +27,14 @@ pub use texture_atlas_builder::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Assets, Handle, HandleUntyped}; -use bevy_ecs::system::IntoSystem; +use bevy_ecs::{ + component::{ComponentDescriptor, StorageType}, + system::IntoSystem, +}; use bevy_math::Vec2; use bevy_reflect::TypeUuid; use bevy_render::{ + draw::OutsideFrustum, mesh::{shape, Mesh}, pipeline::PipelineDescriptor, render_graph::RenderGraph, @@ -37,6 +42,19 @@ use bevy_render::{ }; use sprite::sprite_system; +#[derive(Debug, Clone)] +pub struct SpriteSettings { + pub frustum_culling_enabled: bool, +} + +impl Default for SpriteSettings { + fn default() -> Self { + Self { + frustum_culling_enabled: true, + } + } +} + #[derive(Default)] pub struct SpritePlugin; @@ -59,16 +77,39 @@ impl Plugin for SpritePlugin { asset_shader_defs_system::.system(), ); - let world = app.world_mut().cell(); - let mut render_graph = world.get_resource_mut::().unwrap(); - let mut pipelines = world + let sprite_settings = app + .world_mut() + .get_resource_or_insert_with(SpriteSettings::default) + .clone(); + if sprite_settings.frustum_culling_enabled { + app.add_system_to_stage( + CoreStage::PostUpdate, + frustum_culling::sprite_frustum_culling_system.system(), + ) + .add_system_to_stage( + CoreStage::PostUpdate, + frustum_culling::atlas_frustum_culling_system.system(), + ); + } + let world = app.world_mut(); + world + .register_component(ComponentDescriptor::new::( + StorageType::SparseSet, + )) + .unwrap(); + + let world_cell = world.cell(); + let mut render_graph = world_cell.get_resource_mut::().unwrap(); + let mut pipelines = world_cell .get_resource_mut::>() .unwrap(); - let mut shaders = world.get_resource_mut::>().unwrap(); + let mut shaders = world_cell.get_resource_mut::>().unwrap(); crate::render::add_sprite_graph(&mut render_graph, &mut pipelines, &mut shaders); - let mut meshes = world.get_resource_mut::>().unwrap(); - let mut color_materials = world.get_resource_mut::>().unwrap(); + let mut meshes = world_cell.get_resource_mut::>().unwrap(); + let mut color_materials = world_cell + .get_resource_mut::>() + .unwrap(); color_materials.set_untracked(Handle::::default(), ColorMaterial::default()); meshes.set_untracked( QUAD_HANDLE, diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index 555048970cde8..3413160d76fd6 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -1,10 +1,14 @@ use crate::ColorMaterial; use bevy_asset::{Assets, Handle}; use bevy_core::Bytes; -use bevy_ecs::system::{Query, Res}; +use bevy_ecs::{ + query::Without, + system::{Query, Res}, +}; use bevy_math::Vec2; use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid}; use bevy_render::{ + draw::OutsideFrustum, renderer::{RenderResource, RenderResourceType, RenderResources}, texture::Texture, }; @@ -76,7 +80,7 @@ impl Sprite { pub fn sprite_system( materials: Res>, textures: Res>, - mut query: Query<(&mut Sprite, &Handle)>, + mut query: Query<(&mut Sprite, &Handle), Without>, ) { for (mut sprite, handle) in query.iter_mut() { match sprite.resize_mode { diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 4cb2714720000..112ce1cac0c11 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -2,12 +2,12 @@ use bevy_asset::Assets; use bevy_ecs::{ bundle::Bundle, entity::Entity, - query::{Changed, With}, + query::{Changed, With, Without}, system::{Local, Query, QuerySet, Res, ResMut}, }; use bevy_math::{Size, Vec3}; use bevy_render::{ - draw::{DrawContext, Drawable}, + draw::{DrawContext, Drawable, OutsideFrustum}, mesh::Mesh, prelude::{Draw, Msaa, Texture, Visible}, render_graph::base::MainPass, @@ -72,7 +72,7 @@ pub fn draw_text2d_system( &GlobalTransform, &Text2dSize, ), - With, + (With, Without), >, ) { let font_quad = meshes.get(&QUAD_HANDLE).unwrap(); diff --git a/crates/bevy_ui/src/widget/text.rs b/crates/bevy_ui/src/widget/text.rs index f92e54ebe7989..f9e7dc8bc429d 100644 --- a/crates/bevy_ui/src/widget/text.rs +++ b/crates/bevy_ui/src/widget/text.rs @@ -2,12 +2,12 @@ use crate::{CalculatedSize, Node, Style, Val}; use bevy_asset::Assets; use bevy_ecs::{ entity::Entity, - query::{Changed, Or}, + query::{Changed, Or, Without}, system::{Local, Query, QuerySet, Res, ResMut}, }; use bevy_math::Size; use bevy_render::{ - draw::{Draw, DrawContext, Drawable}, + draw::{Draw, DrawContext, Drawable, OutsideFrustum}, mesh::Mesh, prelude::{Msaa, Visible}, renderer::RenderResourceBindings, @@ -136,7 +136,10 @@ pub fn draw_text_system( meshes: Res>, mut render_resource_bindings: ResMut, text_pipeline: Res, - mut query: Query<(Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform)>, + mut query: Query< + (Entity, &mut Draw, &Visible, &Text, &Node, &GlobalTransform), + Without, + >, ) { let scale_factor = if let Some(window) = windows.get_primary() { window.scale_factor()