Skip to content

Commit

Permalink
Divide the single VisibleEntities list into separate lists for 2D m…
Browse files Browse the repository at this point in the history
…eshes, 3D meshes, lights, and UI elements, for performance. (bevyengine#12582)

This commit splits `VisibleEntities::entities` into four separate lists:
one for lights, one for 2D meshes, one for 3D meshes, and one for UI
elements. This allows `queue_material_meshes` and similar methods to
avoid examining entities that are obviously irrelevant. In particular,
this separation helps scenes with many skinned meshes, as the individual
bones are considered visible entities but have no rendered appearance.

Internally, `VisibleEntities::entities` is a `HashMap` from the `TypeId`
representing a `QueryFilter` to the appropriate `Entity` list. I had to
do this because `VisibleEntities` is located within an upstream crate
from the crates that provide lights (`bevy_pbr`) and 2D meshes
(`bevy_sprite`). As an added benefit, this setup allows apps to provide
their own types of renderable components, by simply adding a specialized
`check_visibility` to the schedule.

This provides a 16.23% end-to-end speedup on `many_foxes` with 10,000
foxes (24.06 ms/frame to 20.70 ms/frame).

## Migration guide

* `check_visibility` and `VisibleEntities` now store the four types of
renderable entities--2D meshes, 3D meshes, lights, and UI
elements--separately. If your custom rendering code examines
`VisibleEntities`, it will now need to specify which type of entity it's
interested in using the `WithMesh2d`, `WithMesh`, `WithLight`, and
`WithNode` types respectively. If your app introduces a new type of
renderable entity, you'll need to add an explicit call to
`check_visibility` to the schedule to accommodate your new component or
components.

## Analysis

`many_foxes`, 10,000 foxes: `main`:
![Screenshot 2024-03-31
114444](https://github.com/bevyengine/bevy/assets/157897/16ecb2ff-6e04-46c0-a4b0-b2fde2084bad)

`many_foxes`, 10,000 foxes, this branch:
![Screenshot 2024-03-31
114256](https://github.com/bevyengine/bevy/assets/157897/94dedae4-bd00-45b2-9aaf-dfc237004ddb)

`queue_material_meshes` (yellow = this branch, red = `main`):
![Screenshot 2024-03-31
114637](https://github.com/bevyengine/bevy/assets/157897/f90912bd-45bd-42c4-bd74-57d98a0f036e)

`queue_shadows` (yellow = this branch, red = `main`):
![Screenshot 2024-03-31
114607](https://github.com/bevyengine/bevy/assets/157897/6ce693e3-20c0-4234-8ec9-a6f191299e2d)
  • Loading branch information
pcwalton committed Apr 11, 2024
1 parent 5c3ae32 commit 5caf085
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 54 deletions.
10 changes: 9 additions & 1 deletion crates/bevy_pbr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ use bevy_render::{
render_graph::RenderGraph,
render_resource::Shader,
texture::{GpuImage, Image},
view::VisibilitySystems,
view::{check_visibility, VisibilitySystems},
ExtractSchedule, Render, RenderApp, RenderSet,
};
use bevy_transform::TransformSystem;
Expand Down Expand Up @@ -349,6 +349,14 @@ impl Plugin for PbrPlugin {
.in_set(SimulationLightSystems::UpdateLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
check_visibility::<WithLight>
.in_set(VisibilitySystems::CheckVisibility)
.after(VisibilitySystems::CalculateBounds)
.after(VisibilitySystems::UpdateOrthographicFrusta)
.after(VisibilitySystems::UpdatePerspectiveFrusta)
.after(VisibilitySystems::UpdateProjectionFrusta)
.after(VisibilitySystems::VisibilityPropagate)
.after(TransformSystem::TransformPropagate),
check_light_mesh_visibility
.in_set(SimulationLightSystems::CheckLightVisibility)
.after(VisibilitySystems::CalculateBounds)
Expand Down
31 changes: 20 additions & 11 deletions crates/bevy_pbr/src/light/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ use bevy_render::{
camera::{Camera, CameraProjection},
extract_component::ExtractComponent,
extract_resource::ExtractResource,
mesh::Mesh,
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
render_resource::BufferBindingType,
renderer::RenderDevice,
view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities},
view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities, WithMesh},
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::tracing::warn;
Expand Down Expand Up @@ -98,6 +99,10 @@ impl Default for PointLightShadowMap {
}
}

/// A convenient alias for `Or<(With<PointLight>, With<SpotLight>,
/// With<DirectionalLight>)>`, for use with [`VisibleEntities`].
pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;

/// Controls the resolution of [`DirectionalLight`] shadow maps.
#[derive(Resource, Clone, Debug, Reflect)]
#[reflect(Resource)]
Expand Down Expand Up @@ -432,19 +437,19 @@ fn calculate_cascade(
texel_size: cascade_texel_size,
}
}
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not cast shadows.
/// Add this component to make a [`Mesh`] not cast shadows.
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default)]
pub struct NotShadowCaster;
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not receive shadows.
/// Add this component to make a [`Mesh`] not receive shadows.
///
/// **Note:** If you're using diffuse transmission, setting [`NotShadowReceiver`] will
/// cause both “regular” shadows as well as diffusely transmitted shadows to be disabled,
/// even when [`TransmittedShadowReceiver`] is being used.
#[derive(Component, Reflect, Default)]
#[reflect(Component, Default)]
pub struct NotShadowReceiver;
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0`
/// Add this component to make a [`Mesh`] using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0`
/// receive shadows on its diffuse transmission lobe. (i.e. its “backside”)
///
/// Not enabled by default, as it requires carefully setting up [`thickness`](crate::pbr_material::StandardMaterial::thickness)
Expand Down Expand Up @@ -1859,7 +1864,11 @@ pub fn check_light_mesh_visibility(
Option<&Aabb>,
Option<&GlobalTransform>,
),
(Without<NotShadowCaster>, Without<DirectionalLight>),
(
Without<NotShadowCaster>,
Without<DirectionalLight>,
With<Handle<Mesh>>,
),
>,
) {
fn shrink_entities(visible_entities: &mut VisibleEntities) {
Expand Down Expand Up @@ -1947,7 +1956,7 @@ pub fn check_light_mesh_visibility(
}

view_visibility.set();
frustum_visible_entities.entities.push(entity);
frustum_visible_entities.get_mut::<WithMesh>().push(entity);
}
}
} else {
Expand All @@ -1959,7 +1968,7 @@ pub fn check_light_mesh_visibility(
.expect("Per-view visible entities should have been inserted already");

for frustum_visible_entities in view_visible_entities {
frustum_visible_entities.entities.push(entity);
frustum_visible_entities.get_mut::<WithMesh>().push(entity);
}
}
}
Expand Down Expand Up @@ -2028,13 +2037,13 @@ pub fn check_light_mesh_visibility(
{
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
view_visibility.set();
visible_entities.entities.push(entity);
visible_entities.push::<WithMesh>(entity);
}
}
} else {
view_visibility.set();
for visible_entities in cubemap_visible_entities.iter_mut() {
visible_entities.entities.push(entity);
visible_entities.push::<WithMesh>(entity);
}
}
}
Expand Down Expand Up @@ -2089,11 +2098,11 @@ pub fn check_light_mesh_visibility(

if frustum.intersects_obb(aabb, &model_to_world, true, true) {
view_visibility.set();
visible_entities.entities.push(entity);
visible_entities.push::<WithMesh>(entity);
}
} else {
view_visibility.set();
visible_entities.entities.push(entity);
visible_entities.push::<WithMesh>(entity);
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use bevy_render::{
render_resource::*,
renderer::RenderDevice,
texture::FallbackImage,
view::{ExtractedView, Msaa, VisibleEntities},
view::{ExtractedView, Msaa, VisibleEntities, WithMesh},
};
use bevy_utils::tracing::error;
use std::marker::PhantomData;
Expand Down Expand Up @@ -645,7 +645,7 @@ pub fn queue_material_meshes<M: Material>(
}

let rangefinder = view.rangefinder3d();
for visible_entity in &visible_entities.entities {
for visible_entity in visible_entities.iter::<WithMesh>() {
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
continue;
};
Expand Down
3 changes: 2 additions & 1 deletion crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod prepass_bindings;

use bevy_render::mesh::{GpuMesh, MeshVertexBufferLayoutRef};
use bevy_render::render_resource::binding_types::uniform_buffer;
use bevy_render::view::WithMesh;
pub use prepass_bindings::*;

use bevy_asset::{load_internal_asset, AssetServer};
Expand Down Expand Up @@ -774,7 +775,7 @@ pub fn queue_prepass_material_meshes<M: Material>(
view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS;
}

for visible_entity in &visible_entities.entities {
for visible_entity in visible_entities.iter::<WithMesh>() {
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
continue;
};
Expand Down
10 changes: 5 additions & 5 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bevy_render::{
render_resource::*,
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::*,
view::{ExtractedView, RenderLayers, ViewVisibility, VisibleEntities},
view::{ExtractedView, RenderLayers, ViewVisibility, VisibleEntities, WithMesh},
Extract,
};
use bevy_transform::{components::GlobalTransform, prelude::Transform};
Expand Down Expand Up @@ -1647,13 +1647,13 @@ pub fn queue_shadows<M: Material>(
.get(*light_entity)
.expect("Failed to get spot light visible entities"),
};
// NOTE: Lights with shadow mapping disabled will have no visible entities
// so no meshes will be queued

let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
light_key.set(MeshPipelineKey::DEPTH_CLAMP_ORTHO, is_directional_light);

for entity in visible_entities.iter().copied() {
// NOTE: Lights with shadow mapping disabled will have no visible entities
// so no meshes will be queued

for entity in visible_entities.iter::<WithMesh>().copied() {
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(entity)
else {
continue;
Expand Down
108 changes: 82 additions & 26 deletions crates/bevy_render/src/view/visibility/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
mod render_layers;

use std::any::TypeId;

use bevy_derive::Deref;
use bevy_ecs::query::QueryFilter;
pub use render_layers::*;

use bevy_app::{Plugin, PostUpdate};
Expand All @@ -9,7 +12,7 @@ use bevy_ecs::prelude::*;
use bevy_hierarchy::{Children, Parent};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_transform::{components::GlobalTransform, TransformSystem};
use bevy_utils::Parallel;
use bevy_utils::{Parallel, TypeIdMap};

use crate::{
camera::{
Expand Down Expand Up @@ -170,23 +173,67 @@ pub struct NoFrustumCulling;
#[reflect(Component, Default)]
pub struct VisibleEntities {
#[reflect(ignore)]
pub entities: Vec<Entity>,
pub entities: TypeIdMap<Vec<Entity>>,
}

impl VisibleEntities {
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
self.entities.iter()
pub fn get<QF>(&self) -> &[Entity]
where
QF: 'static,
{
match self.entities.get(&TypeId::of::<QF>()) {
Some(entities) => &entities[..],
None => &[],
}
}

pub fn get_mut<QF>(&mut self) -> &mut Vec<Entity>
where
QF: 'static,
{
self.entities.entry(TypeId::of::<QF>()).or_default()
}

pub fn iter<QF>(&self) -> impl DoubleEndedIterator<Item = &Entity>
where
QF: 'static,
{
self.get::<QF>().iter()
}

pub fn len<QF>(&self) -> usize
where
QF: 'static,
{
self.get::<QF>().len()
}

pub fn len(&self) -> usize {
self.entities.len()
pub fn is_empty<QF>(&self) -> bool
where
QF: 'static,
{
self.get::<QF>().is_empty()
}

pub fn is_empty(&self) -> bool {
self.entities.is_empty()
pub fn clear<QF>(&mut self)
where
QF: 'static,
{
self.get_mut::<QF>().clear();
}

pub fn push<QF>(&mut self, entity: Entity)
where
QF: 'static,
{
self.get_mut::<QF>().push(entity);
}
}

/// A convenient alias for `With<Handle<Mesh>>`, for use with
/// [`VisibleEntities`].
pub type WithMesh = With<Handle<Mesh>>;

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
pub enum VisibilitySystems {
/// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,
Expand Down Expand Up @@ -238,7 +285,7 @@ impl Plugin for VisibilityPlugin {
.after(camera_system::<Projection>)
.after(TransformSystem::TransformPropagate),
(visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate),
check_visibility
check_visibility::<WithMesh>
.in_set(CheckVisibility)
.after(CalculateBounds)
.after(UpdateOrthographicFrusta)
Expand Down Expand Up @@ -366,35 +413,44 @@ fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) {

/// System updating the visibility of entities each frame.
///
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each frame, it updates the
/// [`ViewVisibility`] of all entities, and for each view also compute the [`VisibleEntities`]
/// for that view.
pub fn check_visibility(
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each
/// frame, it updates the [`ViewVisibility`] of all entities, and for each view
/// also compute the [`VisibleEntities`] for that view.
///
/// This system needs to be run for each type of renderable entity. If you add a
/// new type of renderable entity, you'll need to add an instantiation of this
/// system to the [`VisibilitySystems::CheckVisibility`] set so that Bevy will
/// detect visibility properly for those entities.
pub fn check_visibility<QF>(
mut thread_queues: Local<Parallel<Vec<Entity>>>,
mut view_query: Query<(
&mut VisibleEntities,
&Frustum,
Option<&RenderLayers>,
&Camera,
)>,
mut visible_aabb_query: Query<(
Entity,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
&GlobalTransform,
Has<NoFrustumCulling>,
)>,
) {
mut visible_aabb_query: Query<
(
Entity,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
&GlobalTransform,
Has<NoFrustumCulling>,
),
QF,
>,
) where
QF: QueryFilter + 'static,
{
for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query {
if !camera.is_active {
continue;
}

let view_mask = maybe_view_mask.copied().unwrap_or_default();

visible_entities.entities.clear();
visible_aabb_query.par_iter_mut().for_each(|query_item| {
let (
entity,
Expand Down Expand Up @@ -442,8 +498,8 @@ pub fn check_visibility(
});
});

visible_entities.entities.clear();
thread_queues.drain_into(&mut visible_entities.entities);
visible_entities.clear::<QF>();
thread_queues.drain_into(visible_entities.get_mut::<QF>());
}
}

Expand Down
11 changes: 10 additions & 1 deletion crates/bevy_sprite/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub mod prelude {
};
}

use bevy_transform::TransformSystem;
pub use bundle::*;
pub use dynamic_texture_atlas_builder::*;
pub use mesh2d::*;
Expand All @@ -51,7 +52,7 @@ use bevy_render::{
render_phase::AddRenderCommand,
render_resource::{Shader, SpecializedRenderPipelines},
texture::Image,
view::{NoFrustumCulling, VisibilitySystems},
view::{check_visibility, NoFrustumCulling, VisibilitySystems},
ExtractSchedule, Render, RenderApp, RenderSet,
};

Expand Down Expand Up @@ -94,6 +95,14 @@ impl Plugin for SpritePlugin {
compute_slices_on_sprite_change,
)
.in_set(SpriteSystem::ComputeSlices),
check_visibility::<WithMesh2d>
.in_set(VisibilitySystems::CheckVisibility)
.after(VisibilitySystems::CalculateBounds)
.after(VisibilitySystems::UpdateOrthographicFrusta)
.after(VisibilitySystems::UpdatePerspectiveFrusta)
.after(VisibilitySystems::UpdateProjectionFrusta)
.after(VisibilitySystems::VisibilityPropagate)
.after(TransformSystem::TransformPropagate),
),
);

Expand Down
Loading

0 comments on commit 5caf085

Please sign in to comment.