From 5caf085dacf74bf553a0428a5eb7f4574a9bb99c Mon Sep 17 00:00:00 2001 From: Patrick Walton Date: Thu, 11 Apr 2024 15:33:20 -0500 Subject: [PATCH] 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) --- crates/bevy_pbr/src/lib.rs | 10 +- crates/bevy_pbr/src/light/mod.rs | 31 +++-- crates/bevy_pbr/src/material.rs | 4 +- crates/bevy_pbr/src/prepass/mod.rs | 3 +- crates/bevy_pbr/src/render/light.rs | 10 +- crates/bevy_render/src/view/visibility/mod.rs | 108 +++++++++++++----- crates/bevy_sprite/src/lib.rs | 11 +- crates/bevy_sprite/src/mesh2d/material.rs | 4 +- crates/bevy_sprite/src/mesh2d/mesh.rs | 4 + crates/bevy_sprite/src/render/mod.rs | 8 +- crates/bevy_ui/src/lib.rs | 16 ++- examples/2d/mesh2d_manual.rs | 4 +- 12 files changed, 159 insertions(+), 54 deletions(-) diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index 7d7b7c9afe9ab..0f5da6cbcc25d 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -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; @@ -349,6 +349,14 @@ impl Plugin for PbrPlugin { .in_set(SimulationLightSystems::UpdateLightFrusta) .after(TransformSystem::TransformPropagate) .after(SimulationLightSystems::AssignLightsToClusters), + check_visibility:: + .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) diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index 642c6d0808a35..2b77c5579109c 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -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; @@ -98,6 +99,10 @@ impl Default for PointLightShadowMap { } } +/// A convenient alias for `Or<(With, With, +/// With)>`, for use with [`VisibleEntities`]. +pub type WithLight = Or<(With, With, With)>; + /// Controls the resolution of [`DirectionalLight`] shadow maps. #[derive(Resource, Clone, Debug, Reflect)] #[reflect(Resource)] @@ -432,11 +437,11 @@ 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, @@ -444,7 +449,7 @@ pub struct NotShadowCaster; #[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) @@ -1859,7 +1864,11 @@ pub fn check_light_mesh_visibility( Option<&Aabb>, Option<&GlobalTransform>, ), - (Without, Without), + ( + Without, + Without, + With>, + ), >, ) { fn shrink_entities(visible_entities: &mut VisibleEntities) { @@ -1947,7 +1956,7 @@ pub fn check_light_mesh_visibility( } view_visibility.set(); - frustum_visible_entities.entities.push(entity); + frustum_visible_entities.get_mut::().push(entity); } } } else { @@ -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::().push(entity); } } } @@ -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::(entity); } } } else { view_visibility.set(); for visible_entities in cubemap_visible_entities.iter_mut() { - visible_entities.entities.push(entity); + visible_entities.push::(entity); } } } @@ -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::(entity); } } else { view_visibility.set(); - visible_entities.entities.push(entity); + visible_entities.push::(entity); } } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index a616db5b6973c..f80815c88f65f 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -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; @@ -645,7 +645,7 @@ pub fn queue_material_meshes( } let rangefinder = view.rangefinder3d(); - for visible_entity in &visible_entities.entities { + for visible_entity in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 230b7455d83c8..fea98175531ae 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -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}; @@ -774,7 +775,7 @@ pub fn queue_prepass_material_meshes( view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } - for visible_entity in &visible_entities.entities { + for visible_entity in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 070e1a9d7b99d..7b3464c12472f 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -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}; @@ -1647,13 +1647,13 @@ pub fn queue_shadows( .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::().copied() { let Some(mesh_instance) = render_mesh_instances.render_mesh_queue_data(entity) else { continue; diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index ee8ec2e7d4640..9418f7847a7d1 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -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}; @@ -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::{ @@ -170,23 +173,67 @@ pub struct NoFrustumCulling; #[reflect(Component, Default)] pub struct VisibleEntities { #[reflect(ignore)] - pub entities: Vec, + pub entities: TypeIdMap>, } impl VisibleEntities { - pub fn iter(&self) -> impl DoubleEndedIterator { - self.entities.iter() + pub fn get(&self) -> &[Entity] + where + QF: 'static, + { + match self.entities.get(&TypeId::of::()) { + Some(entities) => &entities[..], + None => &[], + } + } + + pub fn get_mut(&mut self) -> &mut Vec + where + QF: 'static, + { + self.entities.entry(TypeId::of::()).or_default() + } + + pub fn iter(&self) -> impl DoubleEndedIterator + where + QF: 'static, + { + self.get::().iter() + } + + pub fn len(&self) -> usize + where + QF: 'static, + { + self.get::().len() } - pub fn len(&self) -> usize { - self.entities.len() + pub fn is_empty(&self) -> bool + where + QF: 'static, + { + self.get::().is_empty() } - pub fn is_empty(&self) -> bool { - self.entities.is_empty() + pub fn clear(&mut self) + where + QF: 'static, + { + self.get_mut::().clear(); + } + + pub fn push(&mut self, entity: Entity) + where + QF: 'static, + { + self.get_mut::().push(entity); } } +/// A convenient alias for `With>`, for use with +/// [`VisibleEntities`]. +pub type WithMesh = With>; + #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)] pub enum VisibilitySystems { /// Label for the [`calculate_bounds`], `calculate_bounds_2d` and `calculate_bounds_text2d` systems, @@ -238,7 +285,7 @@ impl Plugin for VisibilityPlugin { .after(camera_system::) .after(TransformSystem::TransformPropagate), (visibility_propagate_system, reset_view_visibility).in_set(VisibilityPropagate), - check_visibility + check_visibility:: .in_set(CheckVisibility) .after(CalculateBounds) .after(UpdateOrthographicFrusta) @@ -366,10 +413,15 @@ 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( mut thread_queues: Local>>, mut view_query: Query<( &mut VisibleEntities, @@ -377,16 +429,21 @@ pub fn check_visibility( Option<&RenderLayers>, &Camera, )>, - mut visible_aabb_query: Query<( - Entity, - &InheritedVisibility, - &mut ViewVisibility, - Option<&RenderLayers>, - Option<&Aabb>, - &GlobalTransform, - Has, - )>, -) { + mut visible_aabb_query: Query< + ( + Entity, + &InheritedVisibility, + &mut ViewVisibility, + Option<&RenderLayers>, + Option<&Aabb>, + &GlobalTransform, + Has, + ), + QF, + >, +) where + QF: QueryFilter + 'static, +{ for (mut visible_entities, frustum, maybe_view_mask, camera) in &mut view_query { if !camera.is_active { continue; @@ -394,7 +451,6 @@ pub fn check_visibility( 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, @@ -442,8 +498,8 @@ pub fn check_visibility( }); }); - visible_entities.entities.clear(); - thread_queues.drain_into(&mut visible_entities.entities); + visible_entities.clear::(); + thread_queues.drain_into(visible_entities.get_mut::()); } } diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 78bed9c8f6a5a..42b715e07a49e 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -32,6 +32,7 @@ pub mod prelude { }; } +use bevy_transform::TransformSystem; pub use bundle::*; pub use dynamic_texture_atlas_builder::*; pub use mesh2d::*; @@ -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, }; @@ -94,6 +95,14 @@ impl Plugin for SpritePlugin { compute_slices_on_sprite_change, ) .in_set(SpriteSystem::ComputeSlices), + check_visibility:: + .in_set(VisibilitySystems::CheckVisibility) + .after(VisibilitySystems::CalculateBounds) + .after(VisibilitySystems::UpdateOrthographicFrusta) + .after(VisibilitySystems::UpdatePerspectiveFrusta) + .after(VisibilitySystems::UpdateProjectionFrusta) + .after(VisibilitySystems::VisibilityPropagate) + .after(TransformSystem::TransformPropagate), ), ); diff --git a/crates/bevy_sprite/src/mesh2d/material.rs b/crates/bevy_sprite/src/mesh2d/material.rs index 9322b5f4b25a1..682d2296817ff 100644 --- a/crates/bevy_sprite/src/mesh2d/material.rs +++ b/crates/bevy_sprite/src/mesh2d/material.rs @@ -37,7 +37,7 @@ use std::marker::PhantomData; use crate::{ DrawMesh2d, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, RenderMesh2dInstances, - SetMesh2dBindGroup, SetMesh2dViewBindGroup, + SetMesh2dBindGroup, SetMesh2dViewBindGroup, WithMesh2d, }; /// Materials are used alongside [`Material2dPlugin`] and [`MaterialMesh2dBundle`] @@ -403,7 +403,7 @@ pub fn queue_material2d_meshes( view_key |= Mesh2dPipelineKey::DEBAND_DITHER; } } - for visible_entity in &visible_entities.entities { + for visible_entity in visible_entities.iter::() { let Some(material_asset_id) = render_material_instances.get(visible_entity) else { continue; }; diff --git a/crates/bevy_sprite/src/mesh2d/mesh.rs b/crates/bevy_sprite/src/mesh2d/mesh.rs index 257ad61dfbb7f..21d4fe4fc5710 100644 --- a/crates/bevy_sprite/src/mesh2d/mesh.rs +++ b/crates/bevy_sprite/src/mesh2d/mesh.rs @@ -48,6 +48,10 @@ impl From> for Mesh2dHandle { } } +/// A convenient alias for `With`, for use with +/// [`bevy_render::view::VisibleEntities`]. +pub type WithMesh2d = With; + #[derive(Default)] pub struct Mesh2dRenderPlugin; diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index 202a68dbe20da..8df063e93b5ff 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -2,7 +2,7 @@ use std::ops::Range; use crate::{ texture_atlas::{TextureAtlas, TextureAtlasLayout}, - ComputedTextureSlices, Sprite, SPRITE_SHADER_HANDLE, + ComputedTextureSlices, Sprite, WithMesh2d, SPRITE_SHADER_HANDLE, }; use bevy_asset::{AssetEvent, AssetId, Assets, Handle}; use bevy_color::LinearRgba; @@ -488,7 +488,11 @@ pub fn queue_sprites( let pipeline = pipelines.specialize(&pipeline_cache, &sprite_pipeline, view_key); view_entities.clear(); - view_entities.extend(visible_entities.entities.iter().map(|e| e.index() as usize)); + view_entities.extend( + visible_entities + .iter::() + .map(|e| e.index() as usize), + ); transparent_phase .items diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index e1eacd8d559a9..8db70a2e6c0a5 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -53,7 +53,10 @@ pub mod prelude { use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_input::InputSystem; -use bevy_render::RenderApp; +use bevy_render::{ + view::{check_visibility, VisibilitySystems}, + RenderApp, +}; use bevy_transform::TransformSystem; use layout::ui_surface::UiSurface; use stack::ui_stack_system; @@ -98,6 +101,10 @@ struct AmbiguousWithTextSystem; #[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)] struct AmbiguousWithUpdateText2DLayout; +/// A convenient alias for `With`, for use with +/// [`bevy_render::view::VisibleEntities`]. +pub type WithNode = With; + impl Plugin for UiPlugin { fn build(&self, app: &mut App) { app.init_resource::() @@ -130,6 +137,13 @@ impl Plugin for UiPlugin { app.add_systems( PostUpdate, ( + check_visibility:: + .in_set(VisibilitySystems::CheckVisibility) + .after(VisibilitySystems::CalculateBounds) + .after(VisibilitySystems::UpdateOrthographicFrusta) + .after(VisibilitySystems::UpdatePerspectiveFrusta) + .after(VisibilitySystems::UpdateProjectionFrusta) + .after(VisibilitySystems::VisibilityPropagate), update_target_camera_system.before(UiSystem::Layout), apply_deferred .after(update_target_camera_system) diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 462b0bbfd6c4d..82d65753253ef 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -28,7 +28,7 @@ use bevy::{ sprite::{ extract_mesh2d, DrawMesh2d, Material2dBindGroupId, Mesh2dHandle, Mesh2dPipeline, Mesh2dPipelineKey, Mesh2dTransforms, MeshFlags, RenderMesh2dInstance, - RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, + RenderMesh2dInstances, SetMesh2dBindGroup, SetMesh2dViewBindGroup, WithMesh2d, }, }; use std::f32::consts::PI; @@ -375,7 +375,7 @@ pub fn queue_colored_mesh2d( | Mesh2dPipelineKey::from_hdr(view.hdr); // Queue all entities visible to that view - for visible_entity in &visible_entities.entities { + for visible_entity in visible_entities.iter::() { if let Some(mesh_instance) = render_mesh_instances.get(visible_entity) { let mesh2d_handle = mesh_instance.mesh_asset_id; let mesh2d_transforms = &mesh_instance.transforms;