Despawn unused light-view entity (#15902)
# Objective - Fixes #15897 ## Solution - Despawn light view entities when they go unused or when the corresponding view is not alive. ## Testing - `scene_viewer` example no longer prints "The preprocessing index buffer wasn't present" warning - modified an example to try toggling shadows for all kinds of light: https://gist.github.com/akimakinai/ddb0357191f5052b654370699d2314cf
This commit is contained in:
parent
acbed6040e
commit
4ac528a579
@ -4,7 +4,7 @@ use bevy_color::ColorToComponents;
|
|||||||
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
|
use bevy_core_pipeline::core_3d::{Camera3d, CORE_3D_DEPTH_FORMAT};
|
||||||
use bevy_derive::{Deref, DerefMut};
|
use bevy_derive::{Deref, DerefMut};
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
entity::{EntityHashMap, EntityHashSet},
|
entity::{EntityHash, EntityHashMap, EntityHashSet},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::lifetimeless::Read,
|
system::lifetimeless::Read,
|
||||||
};
|
};
|
||||||
@ -459,7 +459,9 @@ fn create_render_visible_mesh_entities(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Default, Deref, DerefMut)]
|
#[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>>);
|
||||||
|
|
||||||
// TODO: using required component
|
// TODO: using required component
|
||||||
pub(crate) fn add_light_view_entities(
|
pub(crate) fn add_light_view_entities(
|
||||||
@ -477,12 +479,14 @@ pub(crate) fn remove_light_view_entities(
|
|||||||
mut commands: Commands,
|
mut commands: Commands,
|
||||||
) {
|
) {
|
||||||
if let Ok(entities) = query.get(trigger.entity()) {
|
if let Ok(entities) = query.get(trigger.entity()) {
|
||||||
for e in entities.0.iter().copied() {
|
for v in entities.0.values() {
|
||||||
|
for e in v.iter().copied() {
|
||||||
if let Some(mut v) = commands.get_entity(e) {
|
if let Some(mut v) = commands.get_entity(e) {
|
||||||
v.despawn();
|
v.despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct CubeMapFace {
|
pub(crate) struct CubeMapFace {
|
||||||
@ -731,7 +735,8 @@ pub fn prepare_lights(
|
|||||||
let point_light_count = point_lights
|
let point_light_count = point_lights
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|light| light.1.spot_light_angles.is_none())
|
.filter(|light| light.1.spot_light_angles.is_none())
|
||||||
.count();
|
.count()
|
||||||
|
.min(max_texture_cubes);
|
||||||
|
|
||||||
let point_light_volumetric_enabled_count = point_lights
|
let point_light_volumetric_enabled_count = point_lights
|
||||||
.iter()
|
.iter()
|
||||||
@ -759,6 +764,12 @@ pub fn prepare_lights(
|
|||||||
.count()
|
.count()
|
||||||
.min(max_texture_array_layers / MAX_CASCADES_PER_LIGHT);
|
.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
|
let spot_light_volumetric_enabled_count = point_lights
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some())
|
.filter(|(_, light, _)| light.volumetric && light.spot_light_angles.is_some())
|
||||||
@ -956,9 +967,12 @@ pub fn prepare_lights(
|
|||||||
|
|
||||||
live_shadow_mapping_lights.clear();
|
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
|
// 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(
|
let point_light_depth_texture = texture_cache.get(
|
||||||
&render_device,
|
&render_device,
|
||||||
TextureDescriptor {
|
TextureDescriptor {
|
||||||
@ -1032,9 +1046,19 @@ pub fn prepare_lights(
|
|||||||
for &(light_entity, light, (point_light_frusta, _)) in point_lights
|
for &(light_entity, light, (point_light_frusta, _)) in point_lights
|
||||||
.iter()
|
.iter()
|
||||||
// Lights are sorted, shadow enabled lights are first
|
// Lights are sorted, shadow enabled lights are first
|
||||||
.take(point_light_shadow_maps_count)
|
.take(point_light_count)
|
||||||
.filter(|(_, light, _)| light.shadows_enabled)
|
|
||||||
{
|
{
|
||||||
|
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
|
let light_index = *global_light_meta
|
||||||
.entity_to_index
|
.entity_to_index
|
||||||
.get(&light_entity)
|
.get(&light_entity)
|
||||||
@ -1044,14 +1068,10 @@ pub fn prepare_lights(
|
|||||||
// and ignore rotation because we want the shadow map projections to align with the axes
|
// 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 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
|
// for each face of a cube and each view we spawn a light entity
|
||||||
while light_entities.len() < 6 * (offset + 1) {
|
let light_view_entities = light_view_entities
|
||||||
light_entities.push(commands.spawn_empty().id());
|
.entry(entity)
|
||||||
}
|
.or_insert_with(|| (0..6).map(|_| commands.spawn_empty().id()).collect());
|
||||||
|
|
||||||
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
|
let cube_face_projection = Mat4::perspective_infinite_reverse_rh(
|
||||||
core::f32::consts::FRAC_PI_2,
|
core::f32::consts::FRAC_PI_2,
|
||||||
@ -1062,7 +1082,7 @@ pub fn prepare_lights(
|
|||||||
for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
|
for (face_index, ((view_rotation, frustum), view_light_entity)) in cube_face_rotations
|
||||||
.iter()
|
.iter()
|
||||||
.zip(&point_light_frusta.unwrap().frusta)
|
.zip(&point_light_frusta.unwrap().frusta)
|
||||||
.zip(light_entities.iter().skip(6 * offset).copied())
|
.zip(light_view_entities.iter().copied())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
let depth_texture_view =
|
let depth_texture_view =
|
||||||
@ -1119,16 +1139,23 @@ pub fn prepare_lights(
|
|||||||
for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights
|
for (light_index, &(light_entity, light, (_, spot_light_frustum))) in point_lights
|
||||||
.iter()
|
.iter()
|
||||||
.skip(point_light_count)
|
.skip(point_light_count)
|
||||||
.take(spot_light_shadow_maps_count)
|
.take(spot_light_count)
|
||||||
.enumerate()
|
.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 {
|
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
|
||||||
continue;
|
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 \
|
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;
|
[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);
|
let spot_projection = spot_light_clip_from_view(angle, light.shadow_map_near_z);
|
||||||
@ -1147,11 +1174,11 @@ pub fn prepare_lights(
|
|||||||
array_layer_count: Some(1u32),
|
array_layer_count: Some(1u32),
|
||||||
});
|
});
|
||||||
|
|
||||||
while light_view_entities.len() < offset + 1 {
|
let light_view_entities = light_view_entities
|
||||||
light_view_entities.push(commands.spawn_empty().id());
|
.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((
|
commands.entity(view_light_entity).insert((
|
||||||
ShadowView {
|
ShadowView {
|
||||||
@ -1194,14 +1221,21 @@ pub fn prepare_lights(
|
|||||||
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
|
let Ok(mut light_view_entities) = light_view_entities.get_mut(light_entity) else {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if the light intersects with the view.
|
// Check if the light intersects with the view.
|
||||||
if !view_layers.intersects(&light.render_layers) {
|
if !view_layers.intersects(&light.render_layers) {
|
||||||
gpu_light.skip = 1u32;
|
gpu_light.skip = 1u32;
|
||||||
|
if let Some(entities) = light_view_entities.remove(&entity) {
|
||||||
|
despawn_entities(&mut commands, entities);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only deal with cascades when shadows are enabled.
|
// Only deal with cascades when shadows are enabled.
|
||||||
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
|
if (gpu_light.flags & DirectionalLightFlags::SHADOWS_ENABLED.bits()) == 0u32 {
|
||||||
|
if let Some(entities) = light_view_entities.remove(&entity) {
|
||||||
|
despawn_entities(&mut commands, entities);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1222,18 +1256,19 @@ pub fn prepare_lights(
|
|||||||
.zip(frusta)
|
.zip(frusta)
|
||||||
.zip(&light.cascade_shadow_config.bounds);
|
.zip(&light.cascade_shadow_config.bounds);
|
||||||
|
|
||||||
while light_view_entities.len() < dir_light_view_offset + iter.len() {
|
let light_view_entities = light_view_entities.entry(entity).or_insert_with(|| {
|
||||||
light_view_entities.push(commands.spawn_empty().id());
|
(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
|
for (cascade_index, (((cascade, frustum), bound), view_light_entity)) in
|
||||||
.zip(
|
iter.zip(light_view_entities.iter().copied()).enumerate()
|
||||||
light_view_entities
|
|
||||||
.iter()
|
|
||||||
.skip(dir_light_view_offset)
|
|
||||||
.copied(),
|
|
||||||
)
|
|
||||||
.enumerate()
|
|
||||||
{
|
{
|
||||||
gpu_lights.directional_lights[light_index].cascades[cascade_index] =
|
gpu_lights.directional_lights[light_index].cascades[cascade_index] =
|
||||||
GpuDirectionalCascade {
|
GpuDirectionalCascade {
|
||||||
@ -1292,7 +1327,6 @@ pub fn prepare_lights(
|
|||||||
|
|
||||||
shadow_render_phases.insert_or_clear(view_light_entity);
|
shadow_render_phases.insert_or_clear(view_light_entity);
|
||||||
live_shadow_mapping_lights.insert(view_light_entity);
|
live_shadow_mapping_lights.insert(view_light_entity);
|
||||||
dir_light_view_offset += 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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));
|
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
|
/// For each shadow cascade, iterates over all the meshes "visible" from it and
|
||||||
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
|
/// adds them to [`BinnedRenderPhase`]s or [`SortedRenderPhase`]s as
|
||||||
/// appropriate.
|
/// appropriate.
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user