Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Merged by Bors] - Frustum Culling (for Sprites) #1492

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions crates/bevy_render/src/camera/active_cameras.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use bevy_utils::HashMap;

#[derive(Debug, Default)]
pub struct ActiveCamera {
pub name: String,
pub entity: Option<Entity>,
pub bindings: RenderResourceBindings,
}
Expand All @@ -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> {
Expand All @@ -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<Item = &ActiveCamera> {
self.cameras.values()
}

pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ActiveCamera> {
self.cameras.values_mut()
}
}

pub fn active_cameras_system(
Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_render/src/camera/visible_entities.rs
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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>>,
visible_query: Query<(Entity, &Visible, Option<&RenderLayers>), Without<OutsideFrustum>>,
visible_transform_query: Query<&GlobalTransform, Without<OutsideFrustum>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double negatives 🤯

(nothing we can do here, but always causes me to pause for a moment when reading logic like this)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I like to avoid double negatives in apis whenever possible. But yeah the tradeoff here for the inverse is too much :)

) {
for (camera, camera_global_transform, mut visible_entities, maybe_camera_mask) in
camera_query.iter_mut()
Expand Down
11 changes: 11 additions & 0 deletions crates/bevy_render/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -137,6 +138,7 @@ impl Plugin for RenderPlugin {
.register_type::<DepthCalculation>()
.register_type::<Draw>()
.register_type::<Visible>()
.register_type::<OutsideFrustum>()
.register_type::<RenderPipelines>()
.register_type::<OrthographicProjection>()
.register_type::<PerspectiveProjection>()
Expand Down
8 changes: 6 additions & 2 deletions crates/bevy_render/src/pipeline/render_pipelines.rs
Original file line number Diff line number Diff line change
@@ -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},
};
Expand Down Expand Up @@ -86,7 +87,10 @@ pub fn draw_render_pipelines_system(
mut render_resource_bindings: ResMut<RenderResourceBindings>,
msaa: Res<Msaa>,
meshes: Res<Assets<Mesh>>,
mut query: Query<(&mut Draw, &mut RenderPipelines, &Handle<Mesh>, &Visible)>,
mut query: Query<
(&mut Draw, &mut RenderPipelines, &Handle<Mesh>, &Visible),
Without<OutsideFrustum>,
>,
) {
for (mut draw, mut render_pipelines, mesh_handle, visible) in query.iter_mut() {
if !visible.is_visible {
Expand Down
11 changes: 7 additions & 4 deletions crates/bevy_render/src/shader/shader_defs.rs
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -61,7 +64,7 @@ impl ShaderDef for Option<Handle<Texture>> {
}

/// Updates [RenderPipelines] with the latest [ShaderDefs]
pub fn shader_defs_system<T>(mut query: Query<(&T, &mut RenderPipelines)>)
pub fn shader_defs_system<T>(mut query: Query<(&T, &mut RenderPipelines), Without<OutsideFrustum>>)
where
T: ShaderDefs + Send + Sync + 'static,
{
Expand Down Expand Up @@ -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<T: Asset>(
assets: Res<Assets<T>>,
mut query: Query<(&Handle<T>, &mut RenderPipelines)>,
mut query: Query<(&Handle<T>, &mut RenderPipelines), Without<OutsideFrustum>>,
) where
T: ShaderDefs + Send + Sync + 'static,
{
Expand Down
1 change: 1 addition & 0 deletions crates/bevy_sprite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
120 changes: 120 additions & 0 deletions crates/bevy_sprite/src/frustum_culling.rs
Original file line number Diff line number Diff line change
@@ -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<Windows>,
active_cameras: Res<ActiveCameras>,
camera_transforms: Query<&Transform, With<Camera>>,
culled_sprites: Query<&OutsideFrustum, With<Sprite>>,
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::<OutsideFrustum>();
}
} else if culled_sprites.get(entity).is_err() {
commands.entity(entity).insert(OutsideFrustum);
}
}
}
}
}

pub fn atlas_frustum_culling_system(
mut commands: Commands,
windows: Res<Windows>,
active_cameras: Res<ActiveCameras>,
textures: Res<Assets<TextureAtlas>>,
camera_transforms: Query<&Transform, With<Camera>>,
culled_sprites: Query<&OutsideFrustum, With<TextureAtlasSprite>>,
sprites: Query<(
Entity,
&Transform,
&TextureAtlasSprite,
&Handle<TextureAtlas>,
)>,
) {
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::<OutsideFrustum>();
}
} else if culled_sprites.get(entity).is_err() {
commands.entity(entity).insert(OutsideFrustum);
}
}
}
}
}
}
}
55 changes: 48 additions & 7 deletions crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod entity;

mod color_material;
mod dynamic_texture_atlas_builder;
mod frustum_culling;
mod rect;
mod render;
mod sprite;
Expand All @@ -26,17 +27,34 @@ 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,
shader::{asset_shader_defs_system, Shader},
};
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;

Expand All @@ -59,16 +77,39 @@ impl Plugin for SpritePlugin {
asset_shader_defs_system::<ColorMaterial>.system(),
);

let world = app.world_mut().cell();
let mut render_graph = world.get_resource_mut::<RenderGraph>().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::<OutsideFrustum>(
StorageType::SparseSet,
))
.unwrap();

let world_cell = world.cell();
let mut render_graph = world_cell.get_resource_mut::<RenderGraph>().unwrap();
let mut pipelines = world_cell
.get_resource_mut::<Assets<PipelineDescriptor>>()
.unwrap();
let mut shaders = world.get_resource_mut::<Assets<Shader>>().unwrap();
let mut shaders = world_cell.get_resource_mut::<Assets<Shader>>().unwrap();
crate::render::add_sprite_graph(&mut render_graph, &mut pipelines, &mut shaders);

let mut meshes = world.get_resource_mut::<Assets<Mesh>>().unwrap();
let mut color_materials = world.get_resource_mut::<Assets<ColorMaterial>>().unwrap();
let mut meshes = world_cell.get_resource_mut::<Assets<Mesh>>().unwrap();
let mut color_materials = world_cell
.get_resource_mut::<Assets<ColorMaterial>>()
.unwrap();
color_materials.set_untracked(Handle::<ColorMaterial>::default(), ColorMaterial::default());
meshes.set_untracked(
QUAD_HANDLE,
Expand Down
8 changes: 6 additions & 2 deletions crates/bevy_sprite/src/sprite.rs
Original file line number Diff line number Diff line change
@@ -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,
};
Expand Down Expand Up @@ -76,7 +80,7 @@ impl Sprite {
pub fn sprite_system(
materials: Res<Assets<ColorMaterial>>,
textures: Res<Assets<Texture>>,
mut query: Query<(&mut Sprite, &Handle<ColorMaterial>)>,
mut query: Query<(&mut Sprite, &Handle<ColorMaterial>), Without<OutsideFrustum>>,
) {
for (mut sprite, handle) in query.iter_mut() {
match sprite.resize_mode {
Expand Down
Loading