diff --git a/crates/bevy_render/src/camera/visible_entities.rs b/crates/bevy_render/src/camera/visible_entities.rs index f116c17f33d89..a32e9138a4bc4 100644 --- a/crates/bevy_render/src/camera/visible_entities.rs +++ b/crates/bevy_render/src/camera/visible_entities.rs @@ -1,5 +1,5 @@ use super::{Camera, DepthCalculation}; -use crate::prelude::Visible; +use crate::{draw::WithinFrustum, prelude::Visible}; use bevy_core::FloatOrd; use bevy_ecs::{entity::Entity, query::With, reflect::ReflectComponent, system::Query}; use bevy_reflect::Reflect; @@ -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>), With>, + visible_transform_query: Query<&GlobalTransform, With>, ) { 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..64b88454df5c9 100644 --- a/crates/bevy_render/src/draw.rs +++ b/crates/bevy_render/src/draw.rs @@ -66,6 +66,12 @@ impl Default for Visible { } } +/// Viewable is used for frustum culling. +/// Any Sprite or AtlasTextureSprite will have this removed if they are outside the camera frustum and thus not be rendered. +#[derive(Debug, Default, Clone, Reflect)] +#[reflect(Component)] +pub struct WithinFrustum; + /// 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..8faa91aaa4531 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::{Visible, WithinFrustum}; + 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..0f874ed4a7d50 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, WithinFrustum}, mesh::{Indices, Mesh}, prelude::{Msaa, Visible}, renderer::RenderResourceBindings, }; use bevy_asset::{Assets, Handle}; use bevy_ecs::{ + query::With, 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), + With, + >, ) { 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..9e8ae1c1fdf2a 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::WithinFrustum, pipeline::RenderPipelines, Texture}; pub use bevy_derive::ShaderDefs; -use bevy_ecs::system::{Query, Res}; +use bevy_ecs::{ + query::With, + 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), With>) 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), With>, ) 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..d11e59ee180b0 --- /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, Without}; +use bevy_math::Vec2; +use bevy_render::{camera::Camera, draw::WithinFrustum, render_graph::base::MainPass}; +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 sprites( + mut commands: Commands, + windows: Res, + cameras: Query<&Transform, With>, + visible: Query<&WithinFrustum, 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 camera_transform in cameras.iter() { + 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 visible.get(entity).is_err() { + commands.entity(entity).insert(WithinFrustum); + } + } else if visible.get(entity).is_ok() { + commands.entity(entity).remove::(); + } + } + } +} + +pub fn atlases( + mut commands: Commands, + windows: Res, + textures: Res>, + cameras: Query<&Transform, With>, + visible: Query<&WithinFrustum, With>, + sprites: Query<( + Entity, + &Transform, + &TextureAtlasSprite, + &Handle, + )>, +) { + let window = windows.get_primary().unwrap(); + let window_size = Vec2::new(window.width(), window.height()); + + for camera_transform in cameras.iter() { + 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 visible.get(entity).is_err() { + commands.entity(entity).insert(WithinFrustum); + } + } else if visible.get(entity).is_ok() { + commands.entity(entity).remove::(); + } + } + } + } + } +} + +pub fn other( + mut commands: Commands, + query: Query, Without, Without)>, +) { + for entity in query.iter() { + commands.entity(entity).insert(WithinFrustum); + } +} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 98034b62605ef..f8e2108b3b822 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::WithinFrustum, mesh::{shape, Mesh}, pipeline::PipelineDescriptor, render_graph::RenderGraph, @@ -50,6 +55,9 @@ impl Plugin for SpritePlugin { .register_type::() .register_type::() .add_system_to_stage(CoreStage::PostUpdate, sprite_system.system()) + .add_system_to_stage(CoreStage::PostUpdate, frustum_culling::sprites.system()) + .add_system_to_stage(CoreStage::PostUpdate, frustum_culling::atlases.system()) + .add_system_to_stage(CoreStage::PostUpdate, frustum_culling::other.system()) .add_system_to_stage( CoreStage::PostUpdate, material_texture_detection_system.system(), @@ -59,16 +67,25 @@ 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 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..be69c6292131d 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::With, + system::{Query, Res}, +}; use bevy_math::Vec2; use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid}; use bevy_render::{ + draw::WithinFrustum, 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), With>, ) { 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..1c0099990545f 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -7,7 +7,7 @@ use bevy_ecs::{ }; use bevy_math::{Size, Vec3}; use bevy_render::{ - draw::{DrawContext, Drawable}, + draw::{DrawContext, Drawable, WithinFrustum}, mesh::Mesh, prelude::{Draw, Msaa, Texture, Visible}, render_graph::base::MainPass, @@ -72,7 +72,7 @@ pub fn draw_text2d_system( &GlobalTransform, &Text2dSize, ), - With, + (With, With), >, ) { 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..5afac8a2a31b4 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, With}, system::{Local, Query, QuerySet, Res, ResMut}, }; use bevy_math::Size; use bevy_render::{ - draw::{Draw, DrawContext, Drawable}, + draw::{Draw, DrawContext, Drawable, WithinFrustum}, 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), + With, + >, ) { let scale_factor = if let Some(window) = windows.get_primary() { window.scale_factor()