From 672099e80450743dbd19dcf66c75cf8eb24abe6d Mon Sep 17 00:00:00 2001 From: CodingDaniel1 Date: Sat, 5 Sep 2026 13:49:31 +0800 Subject: [PATCH 1/7] Flush GpuCulledEntity table when needed --- crates/bevy_pbr/src/render/mesh.rs | 176 +++++++++++++++++------------ 1 file changed, 102 insertions(+), 74 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6afed1b4e1ee8..3b85be88b7356 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1763,6 +1763,10 @@ impl RenderGpuCulledEntities { match self.entities.entry(new_entity) { Entry::Occupied(mut occupied_entry) => { if no_cpu_culling { + if occupied_entry.get().ne(&render_layers) { + self.changed_layers.push(new_entity); + } + occupied_entry.insert(render_layers); } else { occupied_entry.remove(); @@ -1905,7 +1909,6 @@ pub fn extract_meshes_for_cpu_building( /// All the data that we need from a mesh in the main world. type GpuMeshExtractionQuery = ( Entity, - Read, Read, Option>, Option>, @@ -2118,7 +2121,6 @@ pub fn extract_meshes_for_gpu_building( fn extract_mesh_for_gpu_building( ( entity, - view_visibility, transform, previous_transform, lightmap, @@ -2141,11 +2143,8 @@ fn extract_mesh_for_gpu_building( queue: &mut RenderMeshInstanceGpuQueue, any_gpu_culling: bool, ) { - // If the entity is invisible, remove it. - if !view_visibility.get() { - queue.remove(entity.into(), any_gpu_culling); - return; - } + // Note: `ViewVisibility` isn't checked here because of that depends on cpu culling path, + // and the cpu culling path completely ignores Mesh3d tagged with NoCpuCulling // If the entity has a visibility range, determine its LOD index. let mut lod_index = None; @@ -2265,24 +2264,29 @@ impl<'a> Iterator for AtomicU64ZeroBitIter<'a> { pub fn collect_gpu_culled_meshes( mut cameras: Query<(Option<&RenderLayers>, &mut RenderVisibleEntities), With>, mut lights: Query<(Option<&RenderLayers>, &mut RenderShadowMapVisibleEntities)>, - mut render_gpu_culled_entities: ResMut, + render_gpu_culled_entities: Res, ) { // 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, &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 { + let just_added_render_shadow_map_visible_entities = + render_shadow_map_visible_entities.is_added(); for render_visible_entities in render_shadow_map_visible_entities.subviews.values_mut() { collect_gpu_culled_meshes_for_subview( maybe_render_layers, render_visible_entities, - &mut render_gpu_culled_entities, + just_added_render_shadow_map_visible_entities, + &render_gpu_culled_entities, ); } } @@ -2297,89 +2301,113 @@ pub fn collect_gpu_culled_meshes( fn collect_gpu_culled_meshes_for_subview( maybe_view_render_layers: Option<&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 { + maybe_view_render_layers + .is_none_or(|view_render_layers| 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::()) .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 infomation 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(); + } } } From e657f55933fee7ade77b438fe0d8762c93595c53 Mon Sep 17 00:00:00 2001 From: CodingDaniel1 Date: Sat, 5 Sep 2026 13:59:33 +0800 Subject: [PATCH 2/7] Fix toggling Camera::is_active no rendering --- crates/bevy_render/src/camera.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_render/src/camera.rs b/crates/bevy_render/src/camera.rs index 906d5371fdc0e..848005cc0d46f 100644 --- a/crates/bevy_render/src/camera.rs +++ b/crates/bevy_render/src/camera.rs @@ -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, @@ -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` will be removed when camera is not active commands .entity(render_entity) - .remove::(); + .remove::<(ExtractedCameraComponents, RenderVisibleEntities)>(); continue; } From f8be56a9dad15a00947229601efa72e68bae205c Mon Sep 17 00:00:00 2001 From: CodingDaniel1 Date: Sat, 5 Sep 2026 14:14:51 +0800 Subject: [PATCH 3/7] Restore view_visibility to extract_gpu_mesh --- crates/bevy_pbr/src/render/mesh.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3b85be88b7356..3165ab4174ce1 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1909,6 +1909,7 @@ pub fn extract_meshes_for_cpu_building( /// All the data that we need from a mesh in the main world. type GpuMeshExtractionQuery = ( Entity, + Read, Read, Option>, Option>, @@ -2121,6 +2122,7 @@ pub fn extract_meshes_for_gpu_building( fn extract_mesh_for_gpu_building( ( entity, + view_visibility, transform, previous_transform, lightmap, @@ -2143,8 +2145,11 @@ fn extract_mesh_for_gpu_building( queue: &mut RenderMeshInstanceGpuQueue, any_gpu_culling: bool, ) { - // Note: `ViewVisibility` isn't checked here because of that depends on cpu culling path, - // and the cpu culling path completely ignores Mesh3d tagged with NoCpuCulling + // If the entity is invisible, remove it. + if !view_visibility.get() { + queue.remove(entity.into(), any_gpu_culling); + return; + } // If the entity has a visibility range, determine its LOD index. let mut lod_index = None; From 35dfa92f0c1853743b1e84e4635c42a1523b2248 Mon Sep 17 00:00:00 2001 From: CodingDaniel1 Date: Sat, 5 Sep 2026 14:34:08 +0800 Subject: [PATCH 4/7] fix typos --- crates/bevy_pbr/src/render/mesh.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 3165ab4174ce1..39f5a7a70183b 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -2320,7 +2320,7 @@ fn collect_gpu_culled_meshes_for_subview( .entry(TypeId::of::()) .or_default(); - // `RenderGpuCulledEntities` is a global resource that only cares about this frame changed renderables, so when the camera is spawned later, this per frame infomation is gone. + // `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 From ae06d708ebe4a1eba467699ce38029dbc6fe7a51 Mon Sep 17 00:00:00 2001 From: Luo Zhihao Date: Tue, 8 Sep 2026 22:12:25 +0800 Subject: [PATCH 5/7] Merge pull request #1 from beicause/review-25690-suggestions Fix render layer change propagation for GPU-culled meshes --- crates/bevy_pbr/src/render/mesh.rs | 14 ++++++++++++-- crates/bevy_render/src/view/visibility/mod.rs | 13 ++----------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 39f5a7a70183b..4cb5198d8fdf4 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -1754,6 +1754,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, @@ -1763,7 +1769,7 @@ impl RenderGpuCulledEntities { match self.entities.entry(new_entity) { Entry::Occupied(mut occupied_entry) => { if no_cpu_culling { - if occupied_entry.get().ne(&render_layers) { + if *occupied_entry.get() != render_layers { self.changed_layers.push(new_entity); } @@ -1961,6 +1967,7 @@ pub fn extract_meshes_for_gpu_building( )>, Changed, Changed, + Changed, )>, >, >, @@ -1977,6 +1984,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>, Extract>, @@ -1990,6 +1998,7 @@ pub fn extract_meshes_for_gpu_building( Extract>, Extract>, Extract>, + Extract>, ), all_meshes_query: Extract>, mut removed_meshes_query: Extract>, @@ -2029,7 +2038,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 diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 887d3bdc4ae47..51b466a5a7a7d 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -250,15 +250,6 @@ impl RenderVisibleEntitiesClass { } } - /// Adds a new entity to the [`Self::added_entities`] list. - /// - /// After calling this method one or more times, you must call - /// [`Self::sort_added_entities`] to ensure the [`Self::added_entities`] - /// list is sorted. - pub fn add_entity(&mut self, pair: (Entity, MainEntity)) { - self.added_entities.push(pair); - } - /// Returns the list of newly-added entities. pub fn added_entities(&self) -> &[(Entity, MainEntity)] { &self.added_entities @@ -297,8 +288,8 @@ impl RenderVisibleEntitiesClass { /// Sorts the [`Self::added_entities`] list. /// - /// You must call this after adding entities to the list via - /// [`Self::add_entity`]. + /// You must call this after pushing entities onto the list, as the + /// `DirtySpecializations` iterator will binary search it. pub fn sort_added_entities(&mut self) { self.added_entities .sort_unstable_by_key(|(_, main_entity)| *main_entity); From 0f0694ebf24fcf62ba25a579e6d0b1832d1c8bf7 Mon Sep 17 00:00:00 2001 From: CodingDaniel1 Date: Tue, 8 Sep 2026 22:14:25 +0800 Subject: [PATCH 6/7] Fix None render layers view incorrect behavior in gpu collect path. --- crates/bevy_pbr/src/render/mesh.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1be091d7cc9c4..7623e543cdf47 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -2289,11 +2289,13 @@ pub fn collect_gpu_culled_meshes( mut lights: Query<(Option<&RenderLayers>, &mut RenderShadowMapVisibleEntities)>, render_gpu_culled_entities: Res, ) { + 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, just_added_render_visible_entities, &render_gpu_culled_entities, @@ -2306,7 +2308,7 @@ pub fn collect_gpu_culled_meshes( render_shadow_map_visible_entities.is_added(); for 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, just_added_render_shadow_map_visible_entities, &render_gpu_culled_entities, @@ -2322,15 +2324,13 @@ 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, just_added_render_visible_entities: bool, render_mesh_instance_gpu_queues: &RenderGpuCulledEntities, ) { - let is_entity_relevant = |render_layers: &RenderLayers| -> bool { - maybe_view_render_layers - .is_none_or(|view_render_layers| view_render_layers.intersects(render_layers)) - }; + 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 From 9c7cff4d7c29e375d91d29d8da0f9861bf39ca7f Mon Sep 17 00:00:00 2001 From: CodingDaniel1 Date: Wed, 9 Sep 2026 16:51:21 +0800 Subject: [PATCH 7/7] fix shadow cascade full flush --- crates/bevy_pbr/src/render/light.rs | 17 +++++++++++------ crates/bevy_pbr/src/render/mesh.rs | 11 +++++++---- crates/bevy_render/src/view/visibility/mod.rs | 5 +++-- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 87cb4423ff27b..d7187be5be754 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -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::*, @@ -442,6 +443,7 @@ pub fn extract_lights( )>, mut all_lights_found: Local, mut rect_light_missing_luts_warning_emitted: Local, + system_change_tick: SystemChangeTick, ) { let mapper = &visibility_extraction_system_param.mapper; @@ -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 @@ -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 @@ -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 @@ -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, @@ -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` @@ -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 } } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index b1ce2ef17ec44..05c268f4fbc6a 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -2304,13 +2304,16 @@ pub fn collect_gpu_culled_meshes( // Collect shadow maps. for (maybe_render_layers, mut render_shadow_map_visible_entities) in &mut lights { - let just_added_render_shadow_map_visible_entities = - render_shadow_map_visible_entities.is_added(); - 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.unwrap_or(&default_render_layers), render_visible_entities, - just_added_render_shadow_map_visible_entities, + render_visible_entities_added_tick.is_newer_than(last_run, this_run), &render_gpu_culled_entities, ); } diff --git a/crates/bevy_render/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs index 51b466a5a7a7d..aa4e8d78c3c60 100644 --- a/crates/bevy_render/src/view/visibility/mod.rs +++ b/crates/bevy_render/src/view/visibility/mod.rs @@ -1,6 +1,7 @@ use core::{any::TypeId, mem}; use bevy_ecs::{ + change_detection::Tick, component::Component, entity::Entity, prelude::ReflectComponent, @@ -66,7 +67,7 @@ pub struct RenderShadowMapVisibleEntities { /// A mapping from each subview (cascade or cubemap face) to the entities /// visible from it. #[reflect(ignore, clone)] - pub subviews: HashMap, + pub subviews: HashMap, } /// Stores a list of all entities that are visible from a single view for a @@ -351,7 +352,7 @@ pub fn collect_visible_cpu_culled_entities( mut maybe_render_shadow_map_visible_entities_cpu_culling, ) in lights.iter_mut() { - for (subview, render_visible_entities) in + for (subview, (_, render_visible_entities)) in render_shadow_map_visible_entities.subviews.iter_mut() { let mut maybe_render_subview_visible_entities_cpu_culling =