Skip to content

Commit 5caf085

Browse files
authored
Divide the single VisibleEntities list into separate lists for 2D meshes, 3D meshes, lights, and UI elements, for performance. (#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)
1 parent 5c3ae32 commit 5caf085

File tree

12 files changed

+159
-54
lines changed

12 files changed

+159
-54
lines changed

crates/bevy_pbr/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ use bevy_render::{
9898
render_graph::RenderGraph,
9999
render_resource::Shader,
100100
texture::{GpuImage, Image},
101-
view::VisibilitySystems,
101+
view::{check_visibility, VisibilitySystems},
102102
ExtractSchedule, Render, RenderApp, RenderSet,
103103
};
104104
use bevy_transform::TransformSystem;
@@ -349,6 +349,14 @@ impl Plugin for PbrPlugin {
349349
.in_set(SimulationLightSystems::UpdateLightFrusta)
350350
.after(TransformSystem::TransformPropagate)
351351
.after(SimulationLightSystems::AssignLightsToClusters),
352+
check_visibility::<WithLight>
353+
.in_set(VisibilitySystems::CheckVisibility)
354+
.after(VisibilitySystems::CalculateBounds)
355+
.after(VisibilitySystems::UpdateOrthographicFrusta)
356+
.after(VisibilitySystems::UpdatePerspectiveFrusta)
357+
.after(VisibilitySystems::UpdateProjectionFrusta)
358+
.after(VisibilitySystems::VisibilityPropagate)
359+
.after(TransformSystem::TransformPropagate),
352360
check_light_mesh_visibility
353361
.in_set(SimulationLightSystems::CheckLightVisibility)
354362
.after(VisibilitySystems::CalculateBounds)

crates/bevy_pbr/src/light/mod.rs

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ use bevy_render::{
1010
camera::{Camera, CameraProjection},
1111
extract_component::ExtractComponent,
1212
extract_resource::ExtractResource,
13+
mesh::Mesh,
1314
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
1415
render_resource::BufferBindingType,
1516
renderer::RenderDevice,
16-
view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities},
17+
view::{InheritedVisibility, RenderLayers, ViewVisibility, VisibleEntities, WithMesh},
1718
};
1819
use bevy_transform::components::{GlobalTransform, Transform};
1920
use bevy_utils::tracing::warn;
@@ -98,6 +99,10 @@ impl Default for PointLightShadowMap {
9899
}
99100
}
100101

102+
/// A convenient alias for `Or<(With<PointLight>, With<SpotLight>,
103+
/// With<DirectionalLight>)>`, for use with [`VisibleEntities`].
104+
pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;
105+
101106
/// Controls the resolution of [`DirectionalLight`] shadow maps.
102107
#[derive(Resource, Clone, Debug, Reflect)]
103108
#[reflect(Resource)]
@@ -432,19 +437,19 @@ fn calculate_cascade(
432437
texel_size: cascade_texel_size,
433438
}
434439
}
435-
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not cast shadows.
440+
/// Add this component to make a [`Mesh`] not cast shadows.
436441
#[derive(Component, Reflect, Default)]
437442
#[reflect(Component, Default)]
438443
pub struct NotShadowCaster;
439-
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not receive shadows.
444+
/// Add this component to make a [`Mesh`] not receive shadows.
440445
///
441446
/// **Note:** If you're using diffuse transmission, setting [`NotShadowReceiver`] will
442447
/// cause both “regular” shadows as well as diffusely transmitted shadows to be disabled,
443448
/// even when [`TransmittedShadowReceiver`] is being used.
444449
#[derive(Component, Reflect, Default)]
445450
#[reflect(Component, Default)]
446451
pub struct NotShadowReceiver;
447-
/// 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`
452+
/// Add this component to make a [`Mesh`] using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0`
448453
/// receive shadows on its diffuse transmission lobe. (i.e. its “backside”)
449454
///
450455
/// Not enabled by default, as it requires carefully setting up [`thickness`](crate::pbr_material::StandardMaterial::thickness)
@@ -1859,7 +1864,11 @@ pub fn check_light_mesh_visibility(
18591864
Option<&Aabb>,
18601865
Option<&GlobalTransform>,
18611866
),
1862-
(Without<NotShadowCaster>, Without<DirectionalLight>),
1867+
(
1868+
Without<NotShadowCaster>,
1869+
Without<DirectionalLight>,
1870+
With<Handle<Mesh>>,
1871+
),
18631872
>,
18641873
) {
18651874
fn shrink_entities(visible_entities: &mut VisibleEntities) {
@@ -1947,7 +1956,7 @@ pub fn check_light_mesh_visibility(
19471956
}
19481957

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

19611970
for frustum_visible_entities in view_visible_entities {
1962-
frustum_visible_entities.entities.push(entity);
1971+
frustum_visible_entities.get_mut::<WithMesh>().push(entity);
19631972
}
19641973
}
19651974
}
@@ -2028,13 +2037,13 @@ pub fn check_light_mesh_visibility(
20282037
{
20292038
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
20302039
view_visibility.set();
2031-
visible_entities.entities.push(entity);
2040+
visible_entities.push::<WithMesh>(entity);
20322041
}
20332042
}
20342043
} else {
20352044
view_visibility.set();
20362045
for visible_entities in cubemap_visible_entities.iter_mut() {
2037-
visible_entities.entities.push(entity);
2046+
visible_entities.push::<WithMesh>(entity);
20382047
}
20392048
}
20402049
}
@@ -2089,11 +2098,11 @@ pub fn check_light_mesh_visibility(
20892098

20902099
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
20912100
view_visibility.set();
2092-
visible_entities.entities.push(entity);
2101+
visible_entities.push::<WithMesh>(entity);
20932102
}
20942103
} else {
20952104
view_visibility.set();
2096-
visible_entities.entities.push(entity);
2105+
visible_entities.push::<WithMesh>(entity);
20972106
}
20982107
}
20992108

crates/bevy_pbr/src/material.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use bevy_render::{
3131
render_resource::*,
3232
renderer::RenderDevice,
3333
texture::FallbackImage,
34-
view::{ExtractedView, Msaa, VisibleEntities},
34+
view::{ExtractedView, Msaa, VisibleEntities, WithMesh},
3535
};
3636
use bevy_utils::tracing::error;
3737
use std::marker::PhantomData;
@@ -645,7 +645,7 @@ pub fn queue_material_meshes<M: Material>(
645645
}
646646

647647
let rangefinder = view.rangefinder3d();
648-
for visible_entity in &visible_entities.entities {
648+
for visible_entity in visible_entities.iter::<WithMesh>() {
649649
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
650650
continue;
651651
};

crates/bevy_pbr/src/prepass/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ mod prepass_bindings;
22

33
use bevy_render::mesh::{GpuMesh, MeshVertexBufferLayoutRef};
44
use bevy_render::render_resource::binding_types::uniform_buffer;
5+
use bevy_render::view::WithMesh;
56
pub use prepass_bindings::*;
67

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

777-
for visible_entity in &visible_entities.entities {
778+
for visible_entity in visible_entities.iter::<WithMesh>() {
778779
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
779780
continue;
780781
};

crates/bevy_pbr/src/render/light.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use bevy_render::{
1515
render_resource::*,
1616
renderer::{RenderContext, RenderDevice, RenderQueue},
1717
texture::*,
18-
view::{ExtractedView, RenderLayers, ViewVisibility, VisibleEntities},
18+
view::{ExtractedView, RenderLayers, ViewVisibility, VisibleEntities, WithMesh},
1919
Extract,
2020
};
2121
use bevy_transform::{components::GlobalTransform, prelude::Transform};
@@ -1647,13 +1647,13 @@ pub fn queue_shadows<M: Material>(
16471647
.get(*light_entity)
16481648
.expect("Failed to get spot light visible entities"),
16491649
};
1650-
// NOTE: Lights with shadow mapping disabled will have no visible entities
1651-
// so no meshes will be queued
1652-
16531650
let mut light_key = MeshPipelineKey::DEPTH_PREPASS;
16541651
light_key.set(MeshPipelineKey::DEPTH_CLAMP_ORTHO, is_directional_light);
16551652

1656-
for entity in visible_entities.iter().copied() {
1653+
// NOTE: Lights with shadow mapping disabled will have no visible entities
1654+
// so no meshes will be queued
1655+
1656+
for entity in visible_entities.iter::<WithMesh>().copied() {
16571657
let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(entity)
16581658
else {
16591659
continue;

crates/bevy_render/src/view/visibility/mod.rs

Lines changed: 82 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
mod render_layers;
22

3+
use std::any::TypeId;
4+
35
use bevy_derive::Deref;
6+
use bevy_ecs::query::QueryFilter;
47
pub use render_layers::*;
58

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

1417
use crate::{
1518
camera::{
@@ -170,23 +173,67 @@ pub struct NoFrustumCulling;
170173
#[reflect(Component, Default)]
171174
pub struct VisibleEntities {
172175
#[reflect(ignore)]
173-
pub entities: Vec<Entity>,
176+
pub entities: TypeIdMap<Vec<Entity>>,
174177
}
175178

176179
impl VisibleEntities {
177-
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
178-
self.entities.iter()
180+
pub fn get<QF>(&self) -> &[Entity]
181+
where
182+
QF: 'static,
183+
{
184+
match self.entities.get(&TypeId::of::<QF>()) {
185+
Some(entities) => &entities[..],
186+
None => &[],
187+
}
188+
}
189+
190+
pub fn get_mut<QF>(&mut self) -> &mut Vec<Entity>
191+
where
192+
QF: 'static,
193+
{
194+
self.entities.entry(TypeId::of::<QF>()).or_default()
195+
}
196+
197+
pub fn iter<QF>(&self) -> impl DoubleEndedIterator<Item = &Entity>
198+
where
199+
QF: 'static,
200+
{
201+
self.get::<QF>().iter()
202+
}
203+
204+
pub fn len<QF>(&self) -> usize
205+
where
206+
QF: 'static,
207+
{
208+
self.get::<QF>().len()
179209
}
180210

181-
pub fn len(&self) -> usize {
182-
self.entities.len()
211+
pub fn is_empty<QF>(&self) -> bool
212+
where
213+
QF: 'static,
214+
{
215+
self.get::<QF>().is_empty()
183216
}
184217

185-
pub fn is_empty(&self) -> bool {
186-
self.entities.is_empty()
218+
pub fn clear<QF>(&mut self)
219+
where
220+
QF: 'static,
221+
{
222+
self.get_mut::<QF>().clear();
223+
}
224+
225+
pub fn push<QF>(&mut self, entity: Entity)
226+
where
227+
QF: 'static,
228+
{
229+
self.get_mut::<QF>().push(entity);
187230
}
188231
}
189232

233+
/// A convenient alias for `With<Handle<Mesh>>`, for use with
234+
/// [`VisibleEntities`].
235+
pub type WithMesh = With<Handle<Mesh>>;
236+
190237
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
191238
pub enum VisibilitySystems {
192239
/// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems,
@@ -238,7 +285,7 @@ impl Plugin for VisibilityPlugin {
238285
.after(camera_system::<Projection>)
239286
.after(TransformSystem::TransformPropagate),
240287
(visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate),
241-
check_visibility
288+
check_visibility::<WithMesh>
242289
.in_set(CheckVisibility)
243290
.after(CalculateBounds)
244291
.after(UpdateOrthographicFrusta)
@@ -366,35 +413,44 @@ fn reset_view_visibility(mut query: Query<&mut ViewVisibility>) {
366413

367414
/// System updating the visibility of entities each frame.
368415
///
369-
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each frame, it updates the
370-
/// [`ViewVisibility`] of all entities, and for each view also compute the [`VisibleEntities`]
371-
/// for that view.
372-
pub fn check_visibility(
416+
/// The system is part of the [`VisibilitySystems::CheckVisibility`] set. Each
417+
/// frame, it updates the [`ViewVisibility`] of all entities, and for each view
418+
/// also compute the [`VisibleEntities`] for that view.
419+
///
420+
/// This system needs to be run for each type of renderable entity. If you add a
421+
/// new type of renderable entity, you'll need to add an instantiation of this
422+
/// system to the [`VisibilitySystems::CheckVisibility`] set so that Bevy will
423+
/// detect visibility properly for those entities.
424+
pub fn check_visibility<QF>(
373425
mut thread_queues: Local<Parallel<Vec<Entity>>>,
374426
mut view_query: Query<(
375427
&mut VisibleEntities,
376428
&Frustum,
377429
Option<&RenderLayers>,
378430
&Camera,
379431
)>,
380-
mut visible_aabb_query: Query<(
381-
Entity,
382-
&InheritedVisibility,
383-
&mut ViewVisibility,
384-
Option<&RenderLayers>,
385-
Option<&Aabb>,
386-
&GlobalTransform,
387-
Has<NoFrustumCulling>,
388-
)>,
389-
) {
432+
mut visible_aabb_query: Query<
433+
(
434+
Entity,
435+
&InheritedVisibility,
436+
&mut ViewVisibility,
437+
Option<&RenderLayers>,
438+
Option<&Aabb>,
439+
&GlobalTransform,
440+
Has<NoFrustumCulling>,
441+
),
442+
QF,
443+
>,
444+
) where
445+
QF: QueryFilter + 'static,
446+
{
390447
for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query {
391448
if !camera.is_active {
392449
continue;
393450
}
394451

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

397-
visible_entities.entities.clear();
398454
visible_aabb_query.par_iter_mut().for_each(|query_item| {
399455
let (
400456
entity,
@@ -442,8 +498,8 @@ pub fn check_visibility(
442498
});
443499
});
444500

445-
visible_entities.entities.clear();
446-
thread_queues.drain_into(&mut visible_entities.entities);
501+
visible_entities.clear::<QF>();
502+
thread_queues.drain_into(visible_entities.get_mut::<QF>());
447503
}
448504
}
449505

crates/bevy_sprite/src/lib.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub mod prelude {
3232
};
3333
}
3434

35+
use bevy_transform::TransformSystem;
3536
pub use bundle::*;
3637
pub use dynamic_texture_atlas_builder::*;
3738
pub use mesh2d::*;
@@ -51,7 +52,7 @@ use bevy_render::{
5152
render_phase::AddRenderCommand,
5253
render_resource::{Shader, SpecializedRenderPipelines},
5354
texture::Image,
54-
view::{NoFrustumCulling, VisibilitySystems},
55+
view::{check_visibility, NoFrustumCulling, VisibilitySystems},
5556
ExtractSchedule, Render, RenderApp, RenderSet,
5657
};
5758

@@ -94,6 +95,14 @@ impl Plugin for SpritePlugin {
9495
compute_slices_on_sprite_change,
9596
)
9697
.in_set(SpriteSystem::ComputeSlices),
98+
check_visibility::<WithMesh2d>
99+
.in_set(VisibilitySystems::CheckVisibility)
100+
.after(VisibilitySystems::CalculateBounds)
101+
.after(VisibilitySystems::UpdateOrthographicFrusta)
102+
.after(VisibilitySystems::UpdatePerspectiveFrusta)
103+
.after(VisibilitySystems::UpdateProjectionFrusta)
104+
.after(VisibilitySystems::VisibilityPropagate)
105+
.after(TransformSystem::TransformPropagate),
97106
),
98107
);
99108

0 commit comments

Comments
 (0)