Skip to content

Commit

Permalink
Intern mesh vertex buffer layouts so that we don't have to compare th…
Browse files Browse the repository at this point in the history
…em 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.
  • Loading branch information
pcwalton authored Mar 1, 2024
1 parent fc0aa4f commit f9cc91d
Show file tree
Hide file tree
Showing 19 changed files with 168 additions and 88 deletions.
8 changes: 4 additions & 4 deletions crates/bevy_pbr/src/extended_material.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use bevy_asset::{Asset, Handle};
use bevy_reflect::{impl_type_path, Reflect};
use bevy_render::{
mesh::MeshVertexBufferLayout,
mesh::MeshVertexBufferLayoutRef,
render_asset::RenderAssets,
render_resource::{
AsBindGroup, AsBindGroupError, BindGroupLayout, RenderPipelineDescriptor, Shader,
Expand Down Expand Up @@ -68,14 +68,14 @@ pub trait MaterialExtension: Asset + AsBindGroup + Clone + Sized {
}

/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input.
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.
/// Specialization for the base material is applied before this function is called.
#[allow(unused_variables)]
#[inline]
fn specialize(
pipeline: &MaterialExtensionPipeline,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
layout: &MeshVertexBufferLayoutRef,
key: MaterialExtensionKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
Expand Down Expand Up @@ -214,7 +214,7 @@ impl<B: Material, E: MaterialExtension> Material for ExtendedMaterial<B, E> {
fn specialize(
pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
// Call the base material's specialize function
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/lightmap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ fn extract_lightmaps(
|| !render_mesh_instances
.get(&entity)
.and_then(|mesh_instance| meshes.get(mesh_instance.mesh_asset_id))
.is_some_and(|mesh| mesh.layout.contains(Mesh::ATTRIBUTE_UV_1.id))
.is_some_and(|mesh| mesh.layout.0.contains(Mesh::ATTRIBUTE_UV_1.id))
{
continue;
}
Expand Down
9 changes: 5 additions & 4 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use bevy_render::{
camera::TemporalJitter,
extract_instances::{ExtractInstancesPlugin, ExtractedInstances},
extract_resource::ExtractResource,
mesh::{Mesh, MeshVertexBufferLayout},
mesh::{Mesh, MeshVertexBufferLayoutRef},
render_asset::RenderAssets,
render_phase::*,
render_resource::*,
Expand Down Expand Up @@ -171,13 +171,13 @@ pub trait Material: Asset + AsBindGroup + Clone + Sized {
}

/// Customizes the default [`RenderPipelineDescriptor`] for a specific entity using the entity's
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayout`] as input.
/// [`MaterialPipelineKey`] and [`MeshVertexBufferLayoutRef`] as input.
#[allow(unused_variables)]
#[inline]
fn specialize(
pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
layout: &MeshVertexBufferLayout,
layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
Ok(())
Expand Down Expand Up @@ -326,7 +326,7 @@ where
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut descriptor = self.mesh_pipeline.specialize(key.mesh_key, layout)?;
if let Some(vertex_shader) = &self.vertex_shader {
Expand Down Expand Up @@ -585,6 +585,7 @@ pub fn queue_material_meshes<M: Material>(
camera_3d.screen_space_specular_transmission_quality,
);
}

let rangefinder = view.rangefinder3d();
for visible_entity in &visible_entities.entities {
let Some(material_asset_id) = render_material_instances.get(visible_entity) else {
Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_pbr/src/pbr_material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use bevy_asset::Asset;
use bevy_color::Alpha;
use bevy_math::{Affine2, Mat3, Vec4};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{mesh::MeshVertexBufferLayout, render_asset::RenderAssets, render_resource::*};
use bevy_render::{
mesh::MeshVertexBufferLayoutRef, render_asset::RenderAssets, render_resource::*,
};

use crate::deferred::DEFAULT_PBR_DEFERRED_LIGHTING_PASS_ID;
use crate::*;
Expand Down Expand Up @@ -813,7 +815,7 @@ impl Material for StandardMaterial {
fn specialize(
_pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayout,
_layout: &MeshVertexBufferLayoutRef,
key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
if let Some(fragment) = descriptor.fragment.as_mut() {
Expand Down
16 changes: 8 additions & 8 deletions crates/bevy_pbr/src/prepass/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod prepass_bindings;

use bevy_render::mesh::MeshVertexBufferLayoutRef;
use bevy_render::render_resource::binding_types::uniform_buffer;
pub use prepass_bindings::*;

Expand All @@ -17,7 +18,6 @@ use bevy_math::{Affine3A, Mat4};
use bevy_render::{
batching::batch_and_prepare_render_phase,
globals::{GlobalsBuffer, GlobalsUniform},
mesh::MeshVertexBufferLayout,
prelude::{Camera, Mesh},
render_asset::RenderAssets,
render_phase::*,
Expand Down Expand Up @@ -302,7 +302,7 @@ where
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut bind_group_layouts = vec![if key
.mesh_key
Expand Down Expand Up @@ -347,7 +347,7 @@ where
shader_defs.push("BLEND_ALPHA".into());
}

if layout.contains(Mesh::ATTRIBUTE_POSITION) {
if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
shader_defs.push("VERTEX_POSITIONS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
}
Expand All @@ -363,12 +363,12 @@ where
shader_defs.push("PREPASS_FRAGMENT".into());
}

if layout.contains(Mesh::ATTRIBUTE_UV_0) {
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(1));
}

if layout.contains(Mesh::ATTRIBUTE_UV_1) {
if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
shader_defs.push("VERTEX_UVS_B".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(2));
}
Expand All @@ -383,7 +383,7 @@ where
{
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(3));
shader_defs.push("NORMAL_PREPASS_OR_DEFERRED_PREPASS".into());
if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push("VERTEX_TANGENTS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4));
}
Expand All @@ -400,7 +400,7 @@ where
shader_defs.push("DEFERRED_PREPASS".into());
}

if layout.contains(Mesh::ATTRIBUTE_COLOR) {
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
shader_defs.push("VERTEX_COLORS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(7));
}
Expand Down Expand Up @@ -430,7 +430,7 @@ where
);
bind_group_layouts.insert(1, bind_group);

let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;

// Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1
let mut targets = vec![
Expand Down
25 changes: 13 additions & 12 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use bevy_render::{
Extract,
};
use bevy_transform::components::GlobalTransform;
use bevy_utils::{tracing::error, Entry, HashMap, Hashed, Parallel};
use bevy_utils::{tracing::error, Entry, HashMap, Parallel};

#[cfg(debug_assertions)]
use bevy_utils::warn_once;
Expand Down Expand Up @@ -595,12 +595,13 @@ impl MeshPipelineKey {
}
}

fn is_skinned(layout: &Hashed<InnerMeshVertexBufferLayout>) -> bool {
layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
fn is_skinned(layout: &MeshVertexBufferLayoutRef) -> bool {
layout.0.contains(Mesh::ATTRIBUTE_JOINT_INDEX)
&& layout.0.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT)
}
pub fn setup_morph_and_skinning_defs(
mesh_layouts: &MeshLayouts,
layout: &Hashed<InnerMeshVertexBufferLayout>,
layout: &MeshVertexBufferLayoutRef,
offset: u32,
key: &MeshPipelineKey,
shader_defs: &mut Vec<ShaderDefVal>,
Expand Down Expand Up @@ -638,7 +639,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
fn specialize(
&self,
key: Self::Key,
layout: &MeshVertexBufferLayout,
layout: &MeshVertexBufferLayoutRef,
) -> Result<RenderPipelineDescriptor, SpecializedMeshPipelineError> {
let mut shader_defs = Vec::new();
let mut vertex_attributes = Vec::new();
Expand All @@ -648,32 +649,32 @@ impl SpecializedMeshPipeline for MeshPipeline {

shader_defs.push("VERTEX_OUTPUT_INSTANCE_INDEX".into());

if layout.contains(Mesh::ATTRIBUTE_POSITION) {
if layout.0.contains(Mesh::ATTRIBUTE_POSITION) {
shader_defs.push("VERTEX_POSITIONS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0));
}

if layout.contains(Mesh::ATTRIBUTE_NORMAL) {
if layout.0.contains(Mesh::ATTRIBUTE_NORMAL) {
shader_defs.push("VERTEX_NORMALS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(1));
}

if layout.contains(Mesh::ATTRIBUTE_UV_0) {
if layout.0.contains(Mesh::ATTRIBUTE_UV_0) {
shader_defs.push("VERTEX_UVS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_0.at_shader_location(2));
}

if layout.contains(Mesh::ATTRIBUTE_UV_1) {
if layout.0.contains(Mesh::ATTRIBUTE_UV_1) {
shader_defs.push("VERTEX_UVS_B".into());
vertex_attributes.push(Mesh::ATTRIBUTE_UV_1.at_shader_location(3));
}

if layout.contains(Mesh::ATTRIBUTE_TANGENT) {
if layout.0.contains(Mesh::ATTRIBUTE_TANGENT) {
shader_defs.push("VERTEX_TANGENTS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(4));
}

if layout.contains(Mesh::ATTRIBUTE_COLOR) {
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
shader_defs.push("VERTEX_COLORS".into());
vertex_attributes.push(Mesh::ATTRIBUTE_COLOR.at_shader_location(5));
}
Expand Down Expand Up @@ -701,7 +702,7 @@ impl SpecializedMeshPipeline for MeshPipeline {
shader_defs.push("SCREEN_SPACE_AMBIENT_OCCLUSION".into());
}

let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?;
let vertex_buffer_layout = layout.0.get_layout(&vertex_attributes)?;

let (label, blend, depth_write_enabled);
let pass = key.intersection(MeshPipelineKey::BLEND_RESERVED_BITS);
Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_pbr/src/wireframe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ use bevy_color::{Color, LinearRgba};
use bevy_ecs::prelude::*;
use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
use bevy_render::{
extract_resource::ExtractResource, mesh::MeshVertexBufferLayout, prelude::*, render_resource::*,
extract_resource::ExtractResource, mesh::MeshVertexBufferLayoutRef, prelude::*,
render_resource::*,
};

pub const WIREFRAME_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(192598014480025766);
Expand Down Expand Up @@ -208,7 +209,7 @@ impl Material for WireframeMaterial {
fn specialize(
_pipeline: &MaterialPipeline<Self>,
descriptor: &mut RenderPipelineDescriptor,
_layout: &MeshVertexBufferLayout,
_layout: &MeshVertexBufferLayoutRef,
_key: MaterialPipelineKey<Self>,
) -> Result<(), SpecializedMeshPipelineError> {
descriptor.primitive.polygon_mode = PolygonMode::Line;
Expand Down
40 changes: 27 additions & 13 deletions crates/bevy_render/src/mesh/mesh/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,23 @@ use crate::{
use bevy_asset::{Asset, Handle};
use bevy_core::cast_slice;
use bevy_derive::EnumVariantMeta;
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_ecs::system::{
lifetimeless::{SRes, SResMut},
SystemParamItem,
};
use bevy_log::warn;
use bevy_math::*;
use bevy_reflect::Reflect;
use bevy_utils::{tracing::error, Hashed};
use bevy_utils::tracing::error;
use std::{collections::BTreeMap, hash::Hash, iter::FusedIterator};
use thiserror::Error;
use wgpu::{
util::BufferInitDescriptor, BufferUsages, IndexFormat, VertexAttribute, VertexFormat,
VertexStepMode,
};

use super::{MeshVertexBufferLayoutRef, MeshVertexBufferLayouts};

pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0;
pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10;

Expand Down Expand Up @@ -377,7 +382,10 @@ impl Mesh {
/// Get this `Mesh`'s [`MeshVertexBufferLayout`], used in [`SpecializedMeshPipeline`].
///
/// [`SpecializedMeshPipeline`]: crate::render_resource::SpecializedMeshPipeline
pub fn get_mesh_vertex_buffer_layout(&self) -> MeshVertexBufferLayout {
pub fn get_mesh_vertex_buffer_layout(
&self,
mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
) -> MeshVertexBufferLayoutRef {
let mut attributes = Vec::with_capacity(self.attributes.len());
let mut attribute_ids = Vec::with_capacity(self.attributes.len());
let mut accumulated_offset = 0;
Expand All @@ -391,14 +399,15 @@ impl Mesh {
accumulated_offset += data.attribute.format.get_size();
}

MeshVertexBufferLayout::new(InnerMeshVertexBufferLayout {
let layout = MeshVertexBufferLayout {
layout: VertexBufferLayout {
array_stride: accumulated_offset,
step_mode: VertexStepMode::Vertex,
attributes,
},
attribute_ids,
})
};
mesh_vertex_buffer_layouts.insert(layout)
}

/// Counts all vertices of the mesh.
Expand Down Expand Up @@ -967,15 +976,13 @@ impl From<MeshVertexAttribute> for MeshVertexAttributeId {
}
}

pub type MeshVertexBufferLayout = Hashed<InnerMeshVertexBufferLayout>;

#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct InnerMeshVertexBufferLayout {
pub struct MeshVertexBufferLayout {
attribute_ids: Vec<MeshVertexAttributeId>,
layout: VertexBufferLayout,
}

impl InnerMeshVertexBufferLayout {
impl MeshVertexBufferLayout {
pub fn new(attribute_ids: Vec<MeshVertexAttributeId>, layout: VertexBufferLayout) -> Self {
Self {
attribute_ids,
Expand Down Expand Up @@ -1350,7 +1357,7 @@ pub struct GpuMesh {
pub morph_targets: Option<TextureView>,
pub buffer_info: GpuBufferInfo,
pub primitive_topology: PrimitiveTopology,
pub layout: MeshVertexBufferLayout,
pub layout: MeshVertexBufferLayoutRef,
}

/// The index/vertex buffer info of a [`GpuMesh`].
Expand All @@ -1367,7 +1374,11 @@ pub enum GpuBufferInfo {

impl RenderAsset for Mesh {
type PreparedAsset = GpuMesh;
type Param = (SRes<RenderDevice>, SRes<RenderAssets<Image>>);
type Param = (
SRes<RenderDevice>,
SRes<RenderAssets<Image>>,
SResMut<MeshVertexBufferLayouts>,
);

fn asset_usage(&self) -> RenderAssetUsages {
self.asset_usage
Expand All @@ -1376,7 +1387,9 @@ impl RenderAsset for Mesh {
/// Converts the extracted mesh a into [`GpuMesh`].
fn prepare_asset(
self,
(render_device, images): &mut SystemParamItem<Self::Param>,
(render_device, images, ref mut mesh_vertex_buffer_layouts): &mut SystemParamItem<
Self::Param,
>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self>> {
let vertex_buffer_data = self.get_vertex_buffer_data();
let vertex_buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
Expand All @@ -1399,7 +1412,8 @@ impl RenderAsset for Mesh {
GpuBufferInfo::NonIndexed
};

let mesh_vertex_buffer_layout = self.get_mesh_vertex_buffer_layout();
let mesh_vertex_buffer_layout =
self.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts);

Ok(GpuMesh {
vertex_buffer,
Expand Down
Loading

0 comments on commit f9cc91d

Please sign in to comment.