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 => {
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;
}
let batch_set_key = Opaque3dBatchSetKey {

View File

@ -148,7 +148,7 @@ where
/// We retain these so that, when the entity changes,
/// [`Self::sweep_old_entities`] can quickly find the bin it was located in
/// 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
/// confirmed to be up to date.
@ -185,10 +185,23 @@ where
/// The entity.
main_entity: MainEntity,
/// 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.
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>
where
BPI: BinnedPhaseItem,
@ -200,10 +213,18 @@ where
/// The type of render phase that we use to render the entity: multidraw,
/// plain batch, etc.
pub phase_type: BinnedRenderPhaseType,
/// The last modified tick of the entity.
///
/// We use this to detect when the entity needs to be invalidated.
pub change_tick: Tick,
}
impl<BPI> Clone for CachedBinnedEntity<BPI>
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>
@ -215,11 +236,21 @@ where
batch_set_key: self.batch_set_key.clone(),
bin_key: self.bin_key.clone(),
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.
///
/// Each one of these corresponds to a [`GpuPreprocessingMode`].
@ -504,27 +535,41 @@ where
}
}
let new_bin_key = CachedBinKey {
batch_set_key,
bin_key,
phase_type,
// Update the cache.
self.update_cache(
main_entity,
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,
};
let (index, old_bin_key) = self
let (index, old_cached_binned_entity) = self
.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
// the entity from it.
if let Some(old_bin_key) = old_bin_key {
if old_bin_key.batch_set_key != new_bin_key.batch_set_key
|| old_bin_key.bin_key != new_bin_key.bin_key
|| old_bin_key.phase_type != new_bin_key.phase_type
{
if let Some(old_cached_binned_entity) = old_cached_binned_entity {
if old_cached_binned_entity.cached_bin_key != new_cached_binned_entity.cached_bin_key {
self.entities_that_changed_bins.push(EntityThatChangedBins {
main_entity,
old_bin_key,
old_cached_binned_entity,
});
}
}
@ -826,27 +871,35 @@ where
// reverse order because `swap_remove_index` will potentially invalidate
// all indices after the one we remove.
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)
else {
continue;
};
remove_entity_from_bin(
entity,
&entity_bin_key,
&mut self.multidrawable_meshes,
&mut self.batchable_meshes,
&mut self.unbatchable_meshes,
&mut self.non_mesh_items,
);
if let Some(ref cached_bin_key) = cached_binned_entity.cached_bin_key {
remove_entity_from_bin(
entity,
cached_bin_key,
&mut self.multidrawable_meshes,
&mut self.batchable_meshes,
&mut self.unbatchable_meshes,
&mut self.non_mesh_items,
);
}
}
// 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(..) {
let Some(ref old_cached_bin_key) = entity_that_changed_bins
.old_cached_binned_entity
.cached_bin_key
else {
continue;
};
remove_entity_from_bin(
entity_that_changed_bins.main_entity,
&entity_that_changed_bins.old_bin_key,
old_cached_bin_key,
&mut self.multidrawable_meshes,
&mut self.batchable_meshes,
&mut self.unbatchable_meshes,