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

Despawn unused light-view entity #15902

Merged
merged 6 commits into from
Oct 15, 2024
Merged
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
128 changes: 91 additions & 37 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use bevy_color::ColorToComponents;
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
entity::{EntityHash, EntityHashMap, EntityHashSet},
prelude::*,
system::lifetimeless::Read,
};
Expand Down Expand Up @@ -459,7 +459,9 @@ fn create_render_visible_mesh_entities(
}

#[derive(Component, Default, Deref, DerefMut)]
pub struct LightViewEntities(Vec<Entity>);
/// Component automatically attached to a light entity to track light-view entities
/// for each view.
pub struct LightViewEntities(EntityHashMap<Vec<Entity>>);
akimakinai marked this conversation as resolved.
Show resolved Hide resolved

// TODO: using required component
pub(crate) fn add_light_view_entities(
Expand All @@ -477,9 +479,11 @@ pub(crate) fn remove_light_view_entities(
mut commands: Commands,
) {
if let Ok(entities) = query.get(trigger.entity()) {
for e in entities.0.iter().copied() {
if let Some(mut v) = commands.get_entity(e) {
v.despawn();
for v in entities.0.values() {
for e in v.iter().copied() {
if let Some(mut v) = commands.get_entity(e) {
v.despawn();
}
}
}
}
Expand Down Expand Up @@ -731,7 +735,8 @@ pub fn prepare_lights(
let point_light_count = point_lights
.iter()
.filter(|light| light.1.spot_light_angles.is_none())
.count();
.count()
.min(max_texture_cubes);

let point_light_volumetric_enabled_count = point_lights
.iter()
Expand Down Expand Up @@ -759,6 +764,12 @@ pub fn prepare_lights(
.count()
.min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);

let spot_light_count = point_lights
.iter()
.filter(|(_, light, _)| light.spot_light_angles.is_some())
.count()
.min(max_texture_array_layers - directional_shadow_enabled_count * MAX_CASCADES_PER_LIGHT);

let spot_light_volumetric_enabled_count = point_lights
.iter()
.filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some())
Expand Down Expand Up @@ -956,9 +967,12 @@ pub fn prepare_lights(

live_shadow_mapping_lights.clear();

let mut dir_light_view_offset = 0;
let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash);

// set up light data for each view
for (offset, (entity, extracted_view, clusters, maybe_layers)) in views.iter().enumerate() {
for (entity, extracted_view, clusters, maybe_layers) in views.iter() {
live_views.insert(entity);

let point_light_depth_texture = texture_cache.get(
&render_device,
TextureDescriptor {
Expand Down Expand Up @@ -1032,9 +1046,19 @@ pub fn prepare_lights(
for &(light_entity, light, (point_light_frusta, _)) in point_lights
.iter()
// Lights are sorted, shadow enabled lights are first
.take(point_light_shadow_maps_count)
.filter(|(_, light, _)| light.shadows_enabled)
.take(point_light_count)
{
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
continue;
};

if !light.shadows_enabled {
if let Some(entities) = light_view_entities.remove(&entity) {
despawn_entities(&mut commands, entities);
}
continue;
}

let light_index = *global_light_meta
.entity_to_index
.get(&light_entity)
Expand All @@ -1044,14 +1068,10 @@ pub fn prepare_lights(
// and ignore rotation because we want the shadow map projections to align with the axes
let view_translation = GlobalTransform::from_translation(light.transform.translation());

let Ok(mut light_entities) = light_view_entities.get_mut(light_entity) else {
continue;
};

// for each face of a cube and each view we spawn a light entity
while light_entities.len() < 6 * (offset + 1) {
light_entities.push(commands.spawn_empty().id());
}
let light_view_entities = light_view_entities
.entry(entity)
.or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());

let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
core::f32::consts::FRAC_PI_2,
Expand All @@ -1062,7 +1082,7 @@ pub fn prepare_lights(
for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
.iter()
.zip(&point_light_frusta.unwrap().frusta)
.zip(light_entities.iter().skip(6 * offset).copied())
.zip(light_view_entities.iter().copied())
.enumerate()
{
let depth_texture_view =
Expand Down Expand Up @@ -1119,16 +1139,23 @@ pub fn prepare_lights(
for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights
.iter()
.skip(point_light_count)
.take(spot_light_shadow_maps_count)
.take(spot_light_count)
.enumerate()
{
let spot_world_from_view = spot_light_world_from_view(&light.transform);
let spot_world_from_view = spot_world_from_view.into();

let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
continue;
};

if !light.shadows_enabled {
if let Some(entities) = light_view_entities.remove(&entity) {
despawn_entities(&mut commands, entities);
}
continue;
}

let spot_world_from_view = spot_light_world_from_view(&light.transform);
let spot_world_from_view = spot_world_from_view.into();

let angle = light.spot_light_angles.expect("lights should be sorted so that \
[point_light_count..point_light_count + spot_light_shadow_maps_count] are spot lights").1;
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
Expand All @@ -1147,11 +1174,11 @@ pub fn prepare_lights(
array_layer_count: Some(1u32),
});

while light_view_entities.len() < offset + 1 {
light_view_entities.push(commands.spawn_empty().id());
}
let light_view_entities = light_view_entities
.entry(entity)
.or_insert_with(|| vec![commands.spawn_empty().id()]);

let view_light_entity = light_view_entities[offset];
let view_light_entity = light_view_entities[0];

commands.entity(view_light_entity).insert((
ShadowView {
Expand Down Expand Up @@ -1194,14 +1221,21 @@ pub fn prepare_lights(
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
continue;
};

// Check if the light intersects with the view.
if !view_layers.intersects(&light.render_layers) {
gpu_light.skip = 1u32;
if let Some(entities) = light_view_entities.remove(&entity) {
despawn_entities(&mut commands, entities);
}
continue;
}

// Only deal with cascades when shadows are enabled.
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
if let Some(entities) = light_view_entities.remove(&entity) {
despawn_entities(&mut commands, entities);
}
continue;
}

Expand All @@ -1222,18 +1256,19 @@ pub fn prepare_lights(
.zip(frusta)
.zip(&light.cascade_shadow_config.bounds);

while light_view_entities.len() < dir_light_view_offset + iter.len() {
light_view_entities.push(commands.spawn_empty().id());
let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
(0..iter.len())
.map(|_| commands.spawn_empty().id())
.collect()
});
if light_view_entities.len() != iter.len() {
let entities = core::mem::take(light_view_entities);
despawn_entities(&mut commands, entities);
light_view_entities.extend((0..iter.len()).map(|_| commands.spawn_empty().id()));
}

for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in iter
.zip(
light_view_entities
.iter()
.skip(dir_light_view_offset)
.copied(),
)
.enumerate()
for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
iter.zip(light_view_entities.iter().copied()).enumerate()
{
gpu_lights.directional_lights[light_index].cascades[cascade_index] =
GpuDirectionalCascade {
Expand Down Expand Up @@ -1292,7 +1327,6 @@ pub fn prepare_lights(

shadow_render_phases.insert_or_clear(view_light_entity);
live_shadow_mapping_lights.insert(view_light_entity);
dir_light_view_offset += 1;
}
}

Expand Down Expand Up @@ -1360,9 +1394,29 @@ pub fn prepare_lights(
));
}

// Despawn light-view entities for views that no longer exist
for mut entities in &mut light_view_entities {
for (_, light_view_entities) in
entities.extract_if(|entity, _| !live_views.contains(entity))
{
despawn_entities(&mut commands, light_view_entities);
}
}

shadow_render_phases.retain(|entity, _| live_shadow_mapping_lights.contains(entity));
}

fn despawn_entities(commands: &mut Commands, entities: Vec<Entity>) {
if entities.is_empty() {
return;
}
commands.queue(move |world: &mut World| {
for entity in entities {
world.despawn(entity);
}
});
}

/// For each shadow cascade, iterates over all the meshes "visible" from it and
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
/// appropriate.
Expand Down