bevy/crates/bevy_render/src/mesh/mod.rs
Patrick Walton f9cc91d5a1
Intern mesh vertex buffer layouts so that we don't have to compare them over and over. (#12216)
Although we cached hashes of `MeshVertexBufferLayout`, we were paying
the cost of `PartialEq` on `InnerMeshVertexBufferLayout` for every
entity, every frame. This patch changes that logic to place
`MeshVertexBufferLayout`s in `Arc`s so that they can be compared and
hashed by pointer. This results in a 28% speedup in the
`queue_material_meshes` phase of `many_cubes`, with frustum culling
disabled.

Additionally, this patch contains two minor changes:

1. This commit flattens the specialized mesh pipeline cache to one level
of hash tables instead of two. This saves a hash lookup.

2. The example `many_cubes` has been given a `--no-frustum-culling`
flag, to aid in benchmarking.

See the Tracy profile:

<img width="1064" alt="Screenshot 2024-02-29 144406"
src="https://github.com/bevyengine/bevy/assets/157897/18632f1d-1fdd-4ac7-90ed-2d10306b2a1e">

## Migration guide

* Duplicate `MeshVertexBufferLayout`s are now combined into a single
object, `MeshVertexBufferLayoutRef`, which contains an
atomically-reference-counted pointer to the layout. Code that was using
`MeshVertexBufferLayout` may need to be updated to use
`MeshVertexBufferLayoutRef` instead.
2024-03-01 20:56:21 +00:00

90 lines
3.2 KiB
Rust

#[allow(clippy::module_inception)]
mod mesh;
pub mod morph;
pub mod primitives;
use bevy_utils::HashSet;
pub use mesh::*;
pub use primitives::*;
use std::{
hash::{Hash, Hasher},
sync::Arc,
};
use crate::{prelude::Image, render_asset::RenderAssetPlugin, RenderApp};
use bevy_app::{App, Plugin};
use bevy_asset::{AssetApp, Handle};
use bevy_ecs::{entity::Entity, system::Resource};
/// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU.
pub struct MeshPlugin;
impl Plugin for MeshPlugin {
fn build(&self, app: &mut App) {
app.init_asset::<Mesh>()
.init_asset::<skinning::SkinnedMeshInverseBindposes>()
.register_asset_reflect::<Mesh>()
.register_type::<Option<Handle<Image>>>()
.register_type::<Option<Vec<String>>>()
.register_type::<Option<Indices>>()
.register_type::<Indices>()
.register_type::<skinning::SkinnedMesh>()
.register_type::<Vec<Entity>>()
// 'Mesh' must be prepared after 'Image' as meshes rely on the morph target image being ready
.add_plugins(RenderAssetPlugin::<Mesh, Image>::default());
let Ok(render_app) = app.get_sub_app_mut(RenderApp) else {
return;
};
render_app.init_resource::<MeshVertexBufferLayouts>();
}
}
/// Describes the layout of the mesh vertices in GPU memory.
///
/// At most one copy of a mesh vertex buffer layout ever exists in GPU memory at
/// once. Therefore, comparing these for equality requires only a single pointer
/// comparison, and this type's [`PartialEq`] and [`Hash`] implementations take
/// advantage of this. To that end, this type doesn't implement
/// [`bevy_derive::Deref`] or [`bevy_derive::DerefMut`] in order to reduce the
/// possibility of accidental deep comparisons, which would be needlessly
/// expensive.
#[derive(Clone, Debug)]
pub struct MeshVertexBufferLayoutRef(pub Arc<MeshVertexBufferLayout>);
/// Stores the single copy of each mesh vertex buffer layout.
#[derive(Clone, Default, Resource)]
pub struct MeshVertexBufferLayouts(HashSet<Arc<MeshVertexBufferLayout>>);
impl MeshVertexBufferLayouts {
/// Inserts a new mesh vertex buffer layout in the store and returns a
/// reference to it, reusing the existing reference if this mesh vertex
/// buffer layout was already in the store.
pub(crate) fn insert(&mut self, layout: MeshVertexBufferLayout) -> MeshVertexBufferLayoutRef {
// Because the special `PartialEq` and `Hash` implementations that
// compare by pointer are on `MeshVertexBufferLayoutRef`, not on
// `Arc<MeshVertexBufferLayout>`, this compares the mesh vertex buffer
// structurally, not by pointer.
MeshVertexBufferLayoutRef(
self.0
.get_or_insert_with(&layout, |layout| Arc::new(layout.clone()))
.clone(),
)
}
}
impl PartialEq for MeshVertexBufferLayoutRef {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.0, &other.0)
}
}
impl Eq for MeshVertexBufferLayoutRef {}
impl Hash for MeshVertexBufferLayoutRef {
fn hash<H: Hasher>(&self, state: &mut H) {
(&*self.0 as *const MeshVertexBufferLayout as usize).hash(state);
}
}