Refactor check_light_mesh_visibility for performance #2 (#13906)

# Objective

- Second part of #13900 
- based on #13905 

## Solution

- check_dir_light_mesh_visibility defers setting the entity's
`ViewVisibility `so that Bevy can schedule it to run in parallel with
`check_point_light_mesh_visibility`.

- Reduce HashMap lookups for directional light checking as much as
possible

- Use `par_iter `to parallelize the checking process within each system.

---------

Co-authored-by: Kristoffer Søholm <k.soeholm@gmail.com>
This commit is contained in:
re0312 2024-06-26 20:48:15 +08:00 committed by GitHub
parent a4c621a127
commit a3f91a28fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,3 +1,5 @@
use std::ops::DerefMut;
use bevy_ecs::entity::EntityHashMap;
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, Vec3A, Vec4};
@ -14,6 +16,7 @@ use bevy_render::{
},
};
use bevy_transform::components::{GlobalTransform, Transform};
use bevy_utils::Parallel;
use crate::*;
@ -655,21 +658,21 @@ fn shrink_entities(visible_entities: &mut Vec<Entity>) {
}
pub fn check_dir_light_mesh_visibility(
mut commands: Commands,
mut directional_lights: Query<
(
&DirectionalLight,
&CascadesFrusta,
&mut CascadesVisibleEntities,
Option<&RenderLayers>,
&mut ViewVisibility,
&ViewVisibility,
),
Without<SpotLight>,
>,
mut visible_entity_query: Query<
visible_entity_query: Query<
(
Entity,
&InheritedVisibility,
&mut ViewVisibility,
Option<&RenderLayers>,
Option<&Aabb>,
Option<&GlobalTransform>,
@ -682,14 +685,14 @@ pub fn check_dir_light_mesh_visibility(
),
>,
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
mut defer_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
mut view_visible_entities_queue: Local<Parallel<Vec<Vec<Entity>>>>,
) {
let visible_entity_ranges = visible_entity_ranges.as_deref();
// Directional lights
for (directional_light, frusta, mut visible_entities, maybe_view_mask, light_view_visibility) in
&mut directional_lights
{
// Re-use already allocated entries where possible.
let mut views_to_remove = Vec::new();
for (view, cascade_view_entities) in &mut visible_entities.entities {
match frusta.frusta.get(view) {
@ -708,6 +711,7 @@ pub fn check_dir_light_mesh_visibility(
.entry(*view)
.or_insert_with(|| vec![VisibleEntities::default(); frusta.len()]);
}
for v in views_to_remove {
visible_entities.entities.remove(&v);
}
@ -719,32 +723,30 @@ pub fn check_dir_light_mesh_visibility(
let view_mask = maybe_view_mask.unwrap_or_default();
for (
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
) in &mut visible_entity_query
{
if !inherited_visibility.get() {
continue;
}
for (view, view_frusta) in &frusta.frusta {
visible_entity_query.par_iter().for_each_init(
|| {
let mut entities = view_visible_entities_queue.borrow_local_mut();
entities.resize(view_frusta.len(), Vec::default());
(defer_visible_entities_queue.borrow_local_mut(), entities)
},
|(defer_visible_entities_local_queue, view_visible_entities_local_queue),
(
entity,
inherited_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
)| {
if !inherited_visibility.get() {
return;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
continue;
}
// If we have an aabb and transform, do frustum culling
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
for (view, view_frusta) in &frusta.frusta {
let view_visible_entities = visible_entities
.entities
.get_mut(view)
.expect("Per-view visible entities should have been inserted already");
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
return;
}
// Check visibility ranges.
if has_visibility_range
@ -752,33 +754,47 @@ pub fn check_dir_light_mesh_visibility(
!visible_entity_ranges.entity_is_in_range_of_view(entity, *view)
})
{
continue;
return;
}
for (frustum, frustum_visible_entities) in
view_frusta.iter().zip(view_visible_entities)
{
// Disable near-plane culling, as a shadow caster could lie before the near plane.
if !frustum.intersects_obb(aabb, &transform.affine(), false, true) {
continue;
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let mut visible = false;
for (frustum, frustum_visible_entities) in view_frusta
.iter()
.zip(view_visible_entities_local_queue.iter_mut())
{
// Disable near-plane culling, as a shadow caster could lie before the near plane.
if !frustum.intersects_obb(aabb, &transform.affine(), false, true) {
continue;
}
visible = true;
frustum_visible_entities.push(entity);
}
if visible {
defer_visible_entities_local_queue.push(entity);
}
} else {
defer_visible_entities_local_queue.push(entity);
for frustum_visible_entities in view_visible_entities_local_queue.iter_mut()
{
frustum_visible_entities.push(entity);
}
view_visibility.set();
frustum_visible_entities.get_mut::<WithMesh>().push(entity);
}
}
} else {
view_visibility.set();
for view in frusta.frusta.keys() {
let view_visible_entities = visible_entities
.entities
.get_mut(view)
.expect("Per-view visible entities should have been inserted already");
for frustum_visible_entities in view_visible_entities {
frustum_visible_entities.get_mut::<WithMesh>().push(entity);
}
}
},
);
// collect entities from parallel queue
for entities in view_visible_entities_queue.iter_mut() {
visible_entities
.entities
.get_mut(view)
.unwrap()
.iter_mut()
.map(|v| v.get_mut::<WithMesh>())
.zip(entities.iter_mut())
.for_each(|(dst, source)| {
dst.append(source);
});
}
}
@ -789,6 +805,19 @@ pub fn check_dir_light_mesh_visibility(
.for_each(shrink_entities);
}
}
// Defer marking view visibility so this system can run in parallel with check_point_light_mesh_visibility
// TODO: use resource to avoid unnecessary memory alloc
let mut defer_queue = std::mem::take(defer_visible_entities_queue.deref_mut());
commands.add(move |world: &mut World| {
let mut query = world.query::<&mut ViewVisibility>();
for entities in defer_queue.iter_mut() {
let mut iter = query.iter_many_mut(world, entities.iter());
while let Some(mut view_visibility) = iter.fetch_next() {
view_visibility.set();
}
}
});
}
pub fn check_point_light_mesh_visibility(
@ -824,9 +853,10 @@ pub fn check_point_light_mesh_visibility(
),
>,
visible_entity_ranges: Option<Res<VisibleEntityRanges>>,
mut cubemap_visible_entities_queue: Local<Parallel<[Vec<Entity>; 6]>>,
mut spot_visible_entities_queue: Local<Parallel<Vec<Entity>>>,
) {
let visible_entity_ranges = visible_entity_ranges.as_deref();
for visible_lights in &visible_point_lights {
for light_entity in visible_lights.entities.iter().copied() {
// Point lights
@ -853,57 +883,66 @@ pub fn check_point_light_mesh_visibility(
radius: point_light.range,
};
for (
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
) in &mut visible_entity_query
{
if !inherited_visibility.get() {
continue;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
continue;
}
// Check visibility ranges.
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
})
{
continue;
}
// If we have an aabb and transform, do frustum culling
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.affine();
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
if !light_sphere.intersects_obb(aabb, &model_to_world) {
continue;
visible_entity_query.par_iter_mut().for_each_init(
|| cubemap_visible_entities_queue.borrow_local_mut(),
|cubemap_visible_entities_local_queue,
(
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
)| {
if !inherited_visibility.get() {
return;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
return;
}
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
})
{
return;
}
for (frustum, visible_entities) in cubemap_frusta
.iter()
.zip(cubemap_visible_entities.iter_mut())
{
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
view_visibility.set();
visible_entities.push::<WithMesh>(entity);
// If we have an aabb and transform, do frustum culling
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.affine();
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
if !light_sphere.intersects_obb(aabb, &model_to_world) {
return;
}
for (frustum, visible_entities) in cubemap_frusta
.iter()
.zip(cubemap_visible_entities_local_queue.iter_mut())
{
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
view_visibility.set();
visible_entities.push(entity);
}
}
} else {
view_visibility.set();
for visible_entities in cubemap_visible_entities_local_queue.iter_mut()
{
visible_entities.push(entity);
}
}
} else {
view_visibility.set();
for visible_entities in cubemap_visible_entities.iter_mut() {
visible_entities.push::<WithMesh>(entity);
}
}
},
);
for entities in cubemap_visible_entities_queue.iter_mut() {
cubemap_visible_entities
.iter_mut()
.map(|v| v.get_mut::<WithMesh>())
.zip(entities.iter_mut())
.for_each(|(dst, source)| dst.append(source));
}
for visible_entities in cubemap_visible_entities.iter_mut() {
@ -928,50 +967,55 @@ pub fn check_point_light_mesh_visibility(
radius: point_light.range,
};
for (
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
) in &mut visible_entity_query
{
if !inherited_visibility.get() {
continue;
}
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
continue;
}
// Check visibility ranges.
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
})
{
continue;
}
// If we have an aabb and transform, do frustum culling
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.affine();
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
if !light_sphere.intersects_obb(aabb, &model_to_world) {
continue;
visible_entity_query.par_iter_mut().for_each_init(
|| spot_visible_entities_queue.borrow_local_mut(),
|spot_visible_entities_local_queue,
(
entity,
inherited_visibility,
mut view_visibility,
maybe_entity_mask,
maybe_aabb,
maybe_transform,
has_visibility_range,
)| {
if !inherited_visibility.get() {
return;
}
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
let entity_mask = maybe_entity_mask.unwrap_or_default();
if !view_mask.intersects(entity_mask) {
return;
}
// Check visibility ranges.
if has_visibility_range
&& visible_entity_ranges.is_some_and(|visible_entity_ranges| {
!visible_entity_ranges.entity_is_in_range_of_any_view(entity)
})
{
return;
}
if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) {
let model_to_world = transform.affine();
// Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light
if !light_sphere.intersects_obb(aabb, &model_to_world) {
return;
}
if frustum.intersects_obb(aabb, &model_to_world, true, true) {
view_visibility.set();
spot_visible_entities_local_queue.push(entity);
}
} else {
view_visibility.set();
visible_entities.push::<WithMesh>(entity);
spot_visible_entities_local_queue.push(entity);
}
} else {
view_visibility.set();
visible_entities.push::<WithMesh>(entity);
}
},
);
for entities in spot_visible_entities_queue.iter_mut() {
visible_entities.get_mut::<WithMesh>().append(entities);
}
shrink_entities(visible_entities.get_mut::<WithMesh>());