Skip to content
Open
17 changes: 11 additions & 6 deletions crates/bevy_pbr/src/render/light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use bevy_color::ColorToComponents;
use bevy_core_pipeline::schedule::RootNonCameraView;
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::schedule::ScheduleLabel;
use bevy_ecs::system::SystemChangeTick;
use bevy_ecs::{
entity::{EntityHashMap, EntityHashSet},
prelude::*,
Expand Down Expand Up @@ -442,6 +443,7 @@ pub fn extract_lights(
)>,
mut all_lights_found: Local<EntityHashSet>,
mut rect_light_missing_luts_warning_emitted: Local<bool>,
system_change_tick: SystemChangeTick,
) {
let mapper = &visibility_extraction_system_param.mapper;

Expand Down Expand Up @@ -515,7 +517,7 @@ pub fn extract_lights(
render_shadow_map_visible_entities
.subviews
.entry(retained_view_entity)
.or_default();
.or_insert_with(|| (system_change_tick.this_run(), Default::default()));

// Extract the visible entities to the list for this face.
let extracted_entities = &mut render_extracted_shadow_map_visible_entities
Expand Down Expand Up @@ -646,7 +648,7 @@ pub fn extract_lights(
render_shadow_map_visible_entities
.subviews
.entry(retained_view_entity)
.or_default();
.or_insert_with(|| (system_change_tick.this_run(), Default::default()));

// Extract the visible CPU culled entities to the list.
let entities_cpu_culling = &mut render_extracted_shadow_map_visible_entities
Expand Down Expand Up @@ -816,7 +818,7 @@ pub fn extract_lights(
existing_shadow_map_visible_entities
.subviews
.entry(retained_view_entity)
.or_default();
.or_insert_with(|| (system_change_tick.this_run(), Default::default()));

// Extract the visible CPU culled entities to the list.
let extracted_entities = &mut existing_extracted_shadow_map_visible_entities
Expand Down Expand Up @@ -3087,12 +3089,13 @@ fn get_shadow_map_visible_entities<'w, 's: 'w>(
match light_entity {
LightEntity::Directional { light_entity, .. } => {
let retained_view_entity = extracted_view_light.retained_view_entity;
shadow_map_visible_entities_query
&shadow_map_visible_entities_query
.get(*light_entity)
.expect("Failed to get directional light visible entities")
.subviews
.get(&retained_view_entity)
.expect("Failed to get directional light visible entities for cascade")
.1
}
LightEntity::Point {
light_entity,
Expand All @@ -3106,12 +3109,13 @@ fn get_shadow_map_visible_entities<'w, 's: 'w>(
auxiliary_entity: None,
subview_index: *face_index as u32,
};
shadow_map_visible_entities_query
&shadow_map_visible_entities_query
.get(*light_entity)
.expect("Failed to get point light visible entities")
.subviews
.get(&retained_view_entity)
.expect("Failed to get point light visible entity for face")
.1
}
LightEntity::Spot { light_entity } => {
// We replace the auxiliary entity with `None`
Expand All @@ -3122,12 +3126,13 @@ fn get_shadow_map_visible_entities<'w, 's: 'w>(
auxiliary_entity: None,
subview_index: 0,
};
shadow_map_visible_entities_query
&shadow_map_visible_entities_query
.get(*light_entity)
.expect("Failed to get spot light visible entities")
.subviews
.get(&retained_view_entity)
.expect("Failed to get spot light visible entity for view")
.1
}
}
}
Expand Down
190 changes: 118 additions & 72 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1762,6 +1762,12 @@ impl RenderGpuCulledEntities {
///
/// The `render_layers` argument specifies the set of render layers that the
/// entity belongs to.
///
/// Note that this method is only called for entities that the extraction
/// systems picked up this frame, so the extraction change detection must be
/// configured to catch render layer changes (see
/// `extract_meshes_for_gpu_building`) in order for layer changes on
/// GPU-culled meshes to propagate.
pub fn update(
&mut self,
new_entity: MainEntity,
Expand All @@ -1771,6 +1777,10 @@ impl RenderGpuCulledEntities {
match self.entities.entry(new_entity) {
Entry::Occupied(mut occupied_entry) => {
if no_cpu_culling {
if *occupied_entry.get() != render_layers {
self.changed_layers.push(new_entity);
}

occupied_entry.insert(render_layers);
} else {
occupied_entry.remove();
Expand Down Expand Up @@ -1965,6 +1975,7 @@ pub fn extract_meshes_for_gpu_building(
)>,
Changed<VisibilityRange>,
Changed<SkinnedMesh>,
Changed<RenderLayers>,
)>,
>,
>,
Expand All @@ -1981,6 +1992,7 @@ pub fn extract_meshes_for_gpu_building(
mut removed_no_cpu_culling_query,
mut removed_visibility_range_query,
mut removed_skinned_mesh_query,
mut removed_render_layers_query,
): (
Extract<RemovedComponents<PreviousGlobalTransform>>,
Extract<RemovedComponents<Lightmap>>,
Expand All @@ -1994,6 +2006,7 @@ pub fn extract_meshes_for_gpu_building(
Extract<RemovedComponents<NoCpuCulling>>,
Extract<RemovedComponents<VisibilityRange>>,
Extract<RemovedComponents<SkinnedMesh>>,
Extract<RemovedComponents<RenderLayers>>,
),
all_meshes_query: Extract<Query<GpuMeshExtractionQuery>>,
mut removed_meshes_query: Extract<RemovedComponents<Mesh3d>>,
Expand Down Expand Up @@ -2033,7 +2046,8 @@ pub fn extract_meshes_for_gpu_building(
.chain(removed_no_automatic_batching_query.read())
.chain(removed_no_cpu_culling_query.read())
.chain(removed_visibility_range_query.read())
.chain(removed_skinned_mesh_query.read()),
.chain(removed_skinned_mesh_query.read())
.chain(removed_render_layers_query.read()),
);

// We have to skip the meshes in the potential reextraction set if we
Expand Down Expand Up @@ -2273,24 +2287,34 @@ impl<'a> Iterator for AtomicU64ZeroBitIter<'a> {
pub fn collect_gpu_culled_meshes(
mut cameras: Query<(Option<&RenderLayers>, &mut RenderVisibleEntities), With<ExtractedView>>,
mut lights: Query<(Option<&RenderLayers>, &mut RenderShadowMapVisibleEntities)>,
mut render_gpu_culled_entities: ResMut<RenderGpuCulledEntities>,
render_gpu_culled_entities: Res<RenderGpuCulledEntities>,
) {
let default_render_layers = RenderLayers::default();

// Collect cameras.
for (maybe_render_layers, mut render_visible_entities) in &mut cameras {
let just_added_render_visible_entities = render_visible_entities.is_added();
collect_gpu_culled_meshes_for_subview(
maybe_render_layers,
maybe_render_layers.unwrap_or(&default_render_layers),
&mut render_visible_entities,
&mut render_gpu_culled_entities,
just_added_render_visible_entities,
&render_gpu_culled_entities,
);
}

// Collect shadow maps.
for (maybe_render_layers, mut render_shadow_map_visible_entities) in &mut lights {
for render_visible_entities in render_shadow_map_visible_entities.subviews.values_mut() {
let last_run = render_shadow_map_visible_entities.last_run();
let this_run = render_shadow_map_visible_entities.this_run();

for (render_visible_entities_added_tick, render_visible_entities) in
render_shadow_map_visible_entities.subviews.values_mut()
{
collect_gpu_culled_meshes_for_subview(
maybe_render_layers,
maybe_render_layers.unwrap_or(&default_render_layers),
render_visible_entities,
&mut render_gpu_culled_entities,
render_visible_entities_added_tick.is_newer_than(last_run, this_run),
&render_gpu_culled_entities,
);
}
}
Expand All @@ -2303,91 +2327,113 @@ pub fn collect_gpu_culled_meshes(
/// corresponding function for entities that are culled on CPU is
/// `collect_visible_cpu_culled_entities_for_subview`.
fn collect_gpu_culled_meshes_for_subview(
maybe_view_render_layers: Option<&RenderLayers>,
view_render_layers: &RenderLayers,
render_visible_entities: &mut RenderVisibleEntities,
render_mesh_instance_gpu_queues: &mut RenderGpuCulledEntities,
just_added_render_visible_entities: bool,
render_mesh_instance_gpu_queues: &RenderGpuCulledEntities,
) {
let is_entity_relevant =
|render_layers: &RenderLayers| -> bool { view_render_layers.intersects(render_layers) };

// Only 3D meshes can be culled on GPU at the moment.
let render_view_visible_mesh_entities = render_visible_entities
.classes
.entry(TypeId::of::<Mesh3d>())
.or_default();

// Update the list with entities that were removed.
for main_entity in &render_mesh_instance_gpu_queues.removed {
if render_view_visible_mesh_entities
.entities_gpu_culling
.remove(main_entity)
.is_some()
{
render_view_visible_mesh_entities
.removed_entities
.push((Entity::PLACEHOLDER, *main_entity));
// `RenderGpuCulledEntities` is a global resource that only cares about this frame changed renderables, so when the camera is spawned later, this per frame information is gone.
// So we do a full flush on the `RenderVisibleEntities` whenever it just got added
if just_added_render_visible_entities {
// We assume `RenderVisibleEntities` is completely fresh so there will only be new entities
for (main_entity, render_layers) in render_mesh_instance_gpu_queues.entities.iter() {
if is_entity_relevant(render_layers) {
// Update the tables. 3D meshes have no render entity, so it's
// appropriate to use `Entity::PLACEHOLDER` here.
render_view_visible_mesh_entities
.entities_gpu_culling
.insert(*main_entity, Entity::PLACEHOLDER);
render_view_visible_mesh_entities
.added_entities
.push((Entity::PLACEHOLDER, *main_entity));
}
}
}

// Update the list with entities that became newly visible.
let mut any_added = false;
for main_entity in &render_mesh_instance_gpu_queues.added {
// Make sure the entity belongs to our set of render layers.
let maybe_entity_render_layers = render_mesh_instance_gpu_queues.entities.get(main_entity);
if let (Some(view_render_layers), Some(entity_render_layers)) =
(maybe_view_render_layers, maybe_entity_render_layers)
&& !view_render_layers.intersects(entity_render_layers)
{
continue;
render_view_visible_mesh_entities.sort_added_entities();
} else {
// Update the list with entities that were removed.
for main_entity in &render_mesh_instance_gpu_queues.removed {
if render_view_visible_mesh_entities
.entities_gpu_culling
.remove(main_entity)
.is_some()
{
render_view_visible_mesh_entities
.removed_entities
.push((Entity::PLACEHOLDER, *main_entity));
}
}

// Update the tables. 3D meshes have no render entity, so it's
// appropriate to use `Entity::PLACEHOLDER` here.
render_view_visible_mesh_entities
.entities_gpu_culling
.insert(*main_entity, Entity::PLACEHOLDER);
render_view_visible_mesh_entities.add_entity((Entity::PLACEHOLDER, *main_entity));
any_added = true;
}

// Process entities that changed layers.
for main_entity in &render_mesh_instance_gpu_queues.changed_layers {
let Some(new_render_layers) = render_mesh_instance_gpu_queues.entities.get(main_entity)
else {
continue;
};

// This is either treated as no change, as an addition, or as a removal.
let entity_was_visible = render_view_visible_mesh_entities
.entities_gpu_culling
.contains_key(main_entity);
let entity_is_visible = maybe_view_render_layers
.is_none_or(|render_layers| render_layers.intersects(new_render_layers));
match (entity_was_visible, entity_is_visible) {
(false, false) | (true, true) => {
// No change; do nothing.
}
(false, true) => {
// The entity became visible. This is an addition.
// Update the list with entities that became newly visible.
let mut any_added = false;
for main_entity in &render_mesh_instance_gpu_queues.added {
// Make sure the entity belongs to our set of render layers.
let maybe_render_layers = render_mesh_instance_gpu_queues.entities.get(main_entity);
if maybe_render_layers.is_none_or(is_entity_relevant) {
// Update the tables. 3D meshes have no render entity, so it's
// appropriate to use `Entity::PLACEHOLDER` here.
render_view_visible_mesh_entities
.entities_gpu_culling
.insert(*main_entity, Entity::PLACEHOLDER);
render_view_visible_mesh_entities.add_entity((Entity::PLACEHOLDER, *main_entity));
any_added = true;
}
(true, false) => {
// The entity became invisible. This is a removal.
render_view_visible_mesh_entities
.entities_gpu_culling
.remove(main_entity);
render_view_visible_mesh_entities
.removed_entities
.added_entities
.push((Entity::PLACEHOLDER, *main_entity));
any_added = true;
}
}
}

// Make sure the `added_entities` list is sorted, as the
// `DirtySpecializations` iterator will binary search it.
if any_added {
render_view_visible_mesh_entities.sort_added_entities();
// Process entities that changed layers.
for main_entity in &render_mesh_instance_gpu_queues.changed_layers {
let Some(render_layers) = render_mesh_instance_gpu_queues.entities.get(main_entity)
else {
continue;
};

// This is either treated as no change, as an addition, or as a removal.
let entity_was_relevant = render_view_visible_mesh_entities
.entities_gpu_culling
.contains_key(main_entity);
let entity_is_relevant = is_entity_relevant(render_layers);
match (entity_was_relevant, entity_is_relevant) {
(false, false) | (true, true) => {
// No change; do nothing.
}
(false, true) => {
// The entity became visible. This is an addition.
render_view_visible_mesh_entities
.entities_gpu_culling
.insert(*main_entity, Entity::PLACEHOLDER);
render_view_visible_mesh_entities
.added_entities
.push((Entity::PLACEHOLDER, *main_entity));
any_added = true;
}
(true, false) => {
// The entity became invisible. This is a removal.
render_view_visible_mesh_entities
.entities_gpu_culling
.remove(main_entity);
render_view_visible_mesh_entities
.removed_entities
.push((Entity::PLACEHOLDER, *main_entity));
}
}
}

// Make sure the `added_entities` list is sorted, as the
// `DirtySpecializations` iterator will binary search it.
if any_added {
render_view_visible_mesh_entities.sort_added_entities();
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/bevy_render/src/camera.rs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,6 @@ pub fn extract_cameras(
) {
main_pass_formats.clear();
let primary_window = primary_window.iter().next();
// Note: RenderVisibleEntities is omitted here as it must persist on a camera entity once it has been created
type ExtractedCameraComponents = (
ExtractedCamera,
ExtractedView,
Expand Down Expand Up @@ -545,9 +544,10 @@ pub fn extract_cameras(
) in query.iter()
{
if !camera.is_active {
// Note: `RenderVisibleEntities` is here because several other retained data `ViewBinnedRenderPhase<Opaque3d>` will be removed when camera is not active
commands
.entity(render_entity)
.remove::<ExtractedCameraComponents>();
.remove::<(ExtractedCameraComponents, RenderVisibleEntities)>();
continue;
}

Expand Down
Loading