Cache opaque deferred entities so we don't have to continuously re-queue them. (#18007)

Even though opaque deferred entities aren't placed into the `Opaque3d`
bin, we still want to cache them as though they were, so that we don't
have to re-queue them every frame. This commit implements that logic,
reducing the time of `queue_material_meshes` to near-zero on Caldera.
This commit is contained in:
Patrick Walton 2025-02-24 13:44:24 -08:00 committed by GitHub
parent 5d7a60592d
commit 172c020b60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 87 additions and 29 deletions

View File

@ -1036,6 +1036,11 @@ pub fn queue_material_meshes<M: Material>(
} }
RenderPhaseType::Opaque => { RenderPhaseType::Opaque => {
if material.properties.render_method == OpaqueRendererMethod::Deferred { if material.properties.render_method == OpaqueRendererMethod::Deferred {
// Even though we aren't going to insert the entity into
// a bin, we still want to update its cache entry. That
// way, we know we don't need to re-examine it in future
// frames.
opaque_phase.update_cache(*visible_entity, None, current_change_tick);
continue; continue;
} }
let batch_set_key = Opaque3dBatchSetKey { let batch_set_key = Opaque3dBatchSetKey {

View File

@ -148,7 +148,7 @@ where
/// We retain these so that, when the entity changes, /// We retain these so that, when the entity changes,
/// [`Self::sweep_old_entities`] can quickly find the bin it was located in /// [`Self::sweep_old_entities`] can quickly find the bin it was located in
/// and remove it. /// and remove it.
cached_entity_bin_keys: IndexMap<MainEntity, CachedBinKey<BPI>, EntityHash>, cached_entity_bin_keys: IndexMap<MainEntity, CachedBinnedEntity<BPI>, EntityHash>,
/// The set of indices in [`Self::cached_entity_bin_keys`] that are /// The set of indices in [`Self::cached_entity_bin_keys`] that are
/// confirmed to be up to date. /// confirmed to be up to date.
@ -185,10 +185,23 @@ where
/// The entity. /// The entity.
main_entity: MainEntity, main_entity: MainEntity,
/// The key that identifies the bin that this entity used to be in. /// The key that identifies the bin that this entity used to be in.
old_bin_key: CachedBinKey<BPI>, old_cached_binned_entity: CachedBinnedEntity<BPI>,
} }
/// Information that we keep about an entity currently within a bin. /// Information that we keep about an entity currently within a bin.
pub struct CachedBinnedEntity<BPI>
where
BPI: BinnedPhaseItem,
{
/// Information that we use to identify a cached entity in a bin.
pub cached_bin_key: Option<CachedBinKey<BPI>>,
/// The last modified tick of the entity.
///
/// We use this to detect when the entity needs to be invalidated.
pub change_tick: Tick,
}
/// Information that we use to identify a cached entity in a bin.
pub struct CachedBinKey<BPI> pub struct CachedBinKey<BPI>
where where
BPI: BinnedPhaseItem, BPI: BinnedPhaseItem,
@ -200,10 +213,18 @@ where
/// The type of render phase that we use to render the entity: multidraw, /// The type of render phase that we use to render the entity: multidraw,
/// plain batch, etc. /// plain batch, etc.
pub phase_type: BinnedRenderPhaseType, pub phase_type: BinnedRenderPhaseType,
/// The last modified tick of the entity. }
///
/// We use this to detect when the entity needs to be invalidated. impl<BPI> Clone for CachedBinnedEntity<BPI>
pub change_tick: Tick, where
BPI: BinnedPhaseItem,
{
fn clone(&self) -> Self {
CachedBinnedEntity {
cached_bin_key: self.cached_bin_key.clone(),
change_tick: self.change_tick,
}
}
} }
impl<BPI> Clone for CachedBinKey<BPI> impl<BPI> Clone for CachedBinKey<BPI>
@ -215,11 +236,21 @@ where
batch_set_key: self.batch_set_key.clone(), batch_set_key: self.batch_set_key.clone(),
bin_key: self.bin_key.clone(), bin_key: self.bin_key.clone(),
phase_type: self.phase_type, phase_type: self.phase_type,
change_tick: self.change_tick,
} }
} }
} }
impl<BPI> PartialEq for CachedBinKey<BPI>
where
BPI: BinnedPhaseItem,
{
fn eq(&self, other: &Self) -> bool {
self.batch_set_key == other.batch_set_key
&& self.bin_key == other.bin_key
&& self.phase_type == other.phase_type
}
}
/// How we store and render the batch sets. /// How we store and render the batch sets.
/// ///
/// Each one of these corresponds to a [`GpuPreprocessingMode`]. /// Each one of these corresponds to a [`GpuPreprocessingMode`].
@ -504,27 +535,41 @@ where
} }
} }
let new_bin_key = CachedBinKey { // Update the cache.
batch_set_key, self.update_cache(
bin_key, main_entity,
phase_type, Some(CachedBinKey {
batch_set_key,
bin_key,
phase_type,
}),
change_tick,
);
}
/// Inserts an entity into the cache with the given change tick.
pub fn update_cache(
&mut self,
main_entity: MainEntity,
cached_bin_key: Option<CachedBinKey<BPI>>,
change_tick: Tick,
) {
let new_cached_binned_entity = CachedBinnedEntity {
cached_bin_key,
change_tick, change_tick,
}; };
let (index, old_bin_key) = self let (index, old_cached_binned_entity) = self
.cached_entity_bin_keys .cached_entity_bin_keys
.insert_full(main_entity, new_bin_key.clone()); .insert_full(main_entity, new_cached_binned_entity.clone());
// If the entity changed bins, record its old bin so that we can remove // If the entity changed bins, record its old bin so that we can remove
// the entity from it. // the entity from it.
if let Some(old_bin_key) = old_bin_key { if let Some(old_cached_binned_entity) = old_cached_binned_entity {
if old_bin_key.batch_set_key != new_bin_key.batch_set_key if old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key {
|| old_bin_key.bin_key != new_bin_key.bin_key
|| old_bin_key.phase_type != new_bin_key.phase_type
{
self.entities_that_changed_bins.push(EntityThatChangedBins { self.entities_that_changed_bins.push(EntityThatChangedBins {
main_entity, main_entity,
old_bin_key, old_cached_binned_entity,
}); });
} }
} }
@ -826,27 +871,35 @@ where
// reverse order because `swap_remove_index` will potentially invalidate // reverse order because `swap_remove_index` will potentially invalidate
// all indices after the one we remove. // all indices after the one we remove.
for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) { for index in ReverseFixedBitSetZeroesIterator::new(&self.valid_cached_entity_bin_keys) {
let Some((entity, entity_bin_key)) = let Some((entity, cached_binned_entity)) =
self.cached_entity_bin_keys.swap_remove_index(index) self.cached_entity_bin_keys.swap_remove_index(index)
else { else {
continue; continue;
}; };
remove_entity_from_bin( if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {
entity, remove_entity_from_bin(
&entity_bin_key, entity,
&mut self.multidrawable_meshes, cached_bin_key,
&mut self.batchable_meshes, &mut self.multidrawable_meshes,
&mut self.unbatchable_meshes, &mut self.batchable_meshes,
&mut self.non_mesh_items, &mut self.unbatchable_meshes,
); &mut self.non_mesh_items,
);
}
} }
// If an entity changed bins, we need to remove it from its old bin. // If an entity changed bins, we need to remove it from its old bin.
for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) { for entity_that_changed_bins in self.entities_that_changed_bins.drain(..) {
let Some(ref old_cached_bin_key) = entity_that_changed_bins
.old_cached_binned_entity
.cached_bin_key
else {
continue;
};
remove_entity_from_bin( remove_entity_from_bin(
entity_that_changed_bins.main_entity, entity_that_changed_bins.main_entity,
&entity_that_changed_bins.old_bin_key, old_cached_bin_key,
&mut self.multidrawable_meshes, &mut self.multidrawable_meshes,
&mut self.batchable_meshes, &mut self.batchable_meshes,
&mut self.unbatchable_meshes, &mut self.unbatchable_meshes,