From 95fbb2493a65f8be521fdebcdf4084158ce41547 Mon Sep 17 00:00:00 2001 From: Luo Zhiaho Date: Wed, 17 Jun 2026 11:20:39 +0800 Subject: [PATCH 1/4] Reuse heap allocations in `prepare_mesh_bind_groups` --- crates/bevy_pbr/src/render/mesh.rs | 48 +++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 6a90c9793f4f1..db4f6e81ce274 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3945,6 +3945,7 @@ pub fn prepare_mesh_bind_groups( render_morph_target_allocator: Res, mut render_lightmaps: ResMut, metadata_fallback_buffer: Res, + mut mesh_bind_groups: Option>, ) { // CPU mesh preprocessing path. if let Some(cpu_batched_instance_buffer) = cpu_batched_instance_buffer @@ -3952,8 +3953,23 @@ pub fn prepare_mesh_bind_groups( .into_inner() .instance_data_binding() { + // Reuse allocations + let mut cpu_preprocessing_mesh_bind_groups = match mesh_bind_groups.as_deref_mut() { + None | Some(MeshBindGroups::GpuPreprocessing(_)) => { + MeshPhaseBindGroups::new(&render_device) + } + Some(MeshBindGroups::CpuPreprocessing(cpu_preprocessing_mesh_bind_groups)) => { + core::mem::replace( + cpu_preprocessing_mesh_bind_groups, + MeshPhaseBindGroups::new(&render_device), + ) + } + }; + cpu_preprocessing_mesh_bind_groups.reset(); + // In this path, we only have a single set of bind groups for all phases. - let cpu_preprocessing_mesh_bind_groups = prepare_mesh_bind_groups_for_phase( + prepare_mesh_bind_groups_for_phase( + &mut cpu_preprocessing_mesh_bind_groups, instance_data_binding, &meshes, &mesh_pipeline, @@ -3975,7 +3991,18 @@ pub fn prepare_mesh_bind_groups( // GPU mesh preprocessing path. if let Some(gpu_batched_instance_buffers) = gpu_batched_instance_buffers { - let mut gpu_preprocessing_mesh_bind_groups = TypeIdMap::default(); + // Reuse allocations + let mut gpu_preprocessing_mesh_bind_groups = match mesh_bind_groups.as_deref_mut() { + None | Some(MeshBindGroups::CpuPreprocessing(_)) => TypeIdMap::default(), + Some(MeshBindGroups::GpuPreprocessing(gpu_preprocessing_mesh_bind_groups)) => { + core::mem::take(gpu_preprocessing_mesh_bind_groups) + } + }; + gpu_preprocessing_mesh_bind_groups.retain(|key, _| { + gpu_batched_instance_buffers + .phase_instance_buffers + .contains_key(key) + }); // Loop over each phase. for (phase_type_id, batched_phase_instance_buffers) in @@ -3984,10 +4011,16 @@ pub fn prepare_mesh_bind_groups( let Some(instance_data_binding) = batched_phase_instance_buffers.instance_data_binding() else { + gpu_preprocessing_mesh_bind_groups.swap_remove(phase_type_id); continue; }; + let groups = gpu_preprocessing_mesh_bind_groups + .entry(*phase_type_id) + .or_insert(MeshPhaseBindGroups::new(&render_device)); + groups.reset(); - let mesh_phase_bind_groups = prepare_mesh_bind_groups_for_phase( + prepare_mesh_bind_groups_for_phase( + groups, instance_data_binding, &meshes, &mesh_pipeline, @@ -4000,8 +4033,6 @@ pub fn prepare_mesh_bind_groups( &mut render_lightmaps, &metadata_fallback_buffer, ); - - gpu_preprocessing_mesh_bind_groups.insert(*phase_type_id, mesh_phase_bind_groups); } commands.insert_resource(MeshBindGroups::GpuPreprocessing( @@ -4012,6 +4043,7 @@ pub fn prepare_mesh_bind_groups( /// Creates the per-mesh bind groups for each type of mesh, for a single phase. fn prepare_mesh_bind_groups_for_phase( + groups: &mut MeshPhaseBindGroups, model: BindingResource, meshes: &RenderAssets, mesh_pipeline: &MeshPipeline, @@ -4023,10 +4055,8 @@ fn prepare_mesh_bind_groups_for_phase( render_morph_target_allocator: &RenderMorphTargetAllocator, render_lightmaps: &mut RenderLightmaps, metadata_fallback_buffer: &MeshMetadataFallbackBuffer, -) -> MeshPhaseBindGroups { +) { let layouts = &mesh_pipeline.mesh_layouts; - // TODO: Reuse allocations. - let mut groups = MeshPhaseBindGroups::new(render_device); for metadata_slab_id in mesh_allocator.metadata_slabs() { let metadata_buffer = mesh_allocator @@ -4154,8 +4184,6 @@ fn prepare_mesh_bind_groups_for_phase( } } } - - groups } /// Creates per-mesh morph target bind groups for a single phase. From a83306ffcae31a8aa574a896e38b4ea9d6c787f8 Mon Sep 17 00:00:00 2001 From: Luo Zhiaho Date: Wed, 17 Jun 2026 12:23:07 +0800 Subject: [PATCH 2/4] Don't check and remove unused phases Them are not accessed in render() --- crates/bevy_pbr/src/render/mesh.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index db4f6e81ce274..2da41542de73d 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3998,11 +3998,6 @@ pub fn prepare_mesh_bind_groups( core::mem::take(gpu_preprocessing_mesh_bind_groups) } }; - gpu_preprocessing_mesh_bind_groups.retain(|key, _| { - gpu_batched_instance_buffers - .phase_instance_buffers - .contains_key(key) - }); // Loop over each phase. for (phase_type_id, batched_phase_instance_buffers) in @@ -4011,7 +4006,6 @@ pub fn prepare_mesh_bind_groups( let Some(instance_data_binding) = batched_phase_instance_buffers.instance_data_binding() else { - gpu_preprocessing_mesh_bind_groups.swap_remove(phase_type_id); continue; }; let groups = gpu_preprocessing_mesh_bind_groups From 397c9ecae8a4e11b19bca51ddb75e002b3b60e5c Mon Sep 17 00:00:00 2001 From: Luo Zhiaho Date: Wed, 17 Jun 2026 12:28:55 +0800 Subject: [PATCH 3/4] unnecessary &mut --- crates/bevy_pbr/src/render/mesh.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 2da41542de73d..81aa7bbcea4f0 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3943,7 +3943,7 @@ pub fn prepare_mesh_bind_groups( weights_uniform: Res, mesh_allocator: Res, render_morph_target_allocator: Res, - mut render_lightmaps: ResMut, + render_lightmaps: Res, metadata_fallback_buffer: Res, mut mesh_bind_groups: Option>, ) { @@ -3979,7 +3979,7 @@ pub fn prepare_mesh_bind_groups( &weights_uniform, &mesh_allocator, &render_morph_target_allocator, - &mut render_lightmaps, + &render_lightmaps, &metadata_fallback_buffer, ); @@ -4024,7 +4024,7 @@ pub fn prepare_mesh_bind_groups( &weights_uniform, &mesh_allocator, &render_morph_target_allocator, - &mut render_lightmaps, + &render_lightmaps, &metadata_fallback_buffer, ); } @@ -4047,7 +4047,7 @@ fn prepare_mesh_bind_groups_for_phase( weights_uniform: &MorphUniforms, mesh_allocator: &MeshAllocator, render_morph_target_allocator: &RenderMorphTargetAllocator, - render_lightmaps: &mut RenderLightmaps, + render_lightmaps: &RenderLightmaps, metadata_fallback_buffer: &MeshMetadataFallbackBuffer, ) { let layouts = &mesh_pipeline.mesh_layouts; @@ -4127,7 +4127,7 @@ fn prepare_mesh_bind_groups_for_phase( // Create lightmap bindgroups. There will be one bindgroup for each slab. let bindless_supported = render_lightmaps.bindless_supported; - for (lightmap_slab_id, lightmap_slab) in render_lightmaps.slabs.iter_mut().enumerate() { + for (lightmap_slab_id, lightmap_slab) in render_lightmaps.slabs.iter().enumerate() { groups.lightmaps.insert( LightmapSlabIndex(NonMaxU32::new(lightmap_slab_id as u32).unwrap()), layouts.lightmapped( From 9cf11c97790ff66b2314955f6365fe3d3772c1b6 Mon Sep 17 00:00:00 2001 From: Luo Zhiaho Date: Wed, 17 Jun 2026 13:43:12 +0800 Subject: [PATCH 4/4] fix lightmapped mesh doesn't use metadata_slab_id as key --- crates/bevy_pbr/src/render/mesh.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 81aa7bbcea4f0..4ad8cce2c6c21 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3748,7 +3748,7 @@ pub struct MeshPhaseBindGroups { skinned: HashMap, /// Bind groups for meshes with morph targets. morph_targets: MeshMorphTargetBindGroups, - lightmaps: HashMap, + lightmaps: HashMap<(MeshSlabId, LightmapSlabIndex), BindGroup>, } /// Stores bind groups for each mesh with morph targets. @@ -3888,7 +3888,7 @@ impl MeshPhaseBindGroups { .get(&metadata_slab_id) .map(|bind_group_pair| bind_group_pair.get(motion_vectors)), (false, MeshMorphBindGroupKey::NoMorphTargets, Some(lightmap_slab)) => { - self.lightmaps.get(&lightmap_slab) + self.lightmaps.get(&(metadata_slab_id, lightmap_slab)) } (false, MeshMorphBindGroupKey::NoMorphTargets, None) => { self.model_only.get(&metadata_slab_id) @@ -4129,7 +4129,10 @@ fn prepare_mesh_bind_groups_for_phase( let bindless_supported = render_lightmaps.bindless_supported; for (lightmap_slab_id, lightmap_slab) in render_lightmaps.slabs.iter().enumerate() { groups.lightmaps.insert( - LightmapSlabIndex(NonMaxU32::new(lightmap_slab_id as u32).unwrap()), + ( + metadata_slab_id, + LightmapSlabIndex(NonMaxU32::new(lightmap_slab_id as u32).unwrap()), + ), layouts.lightmapped( render_device, pipeline_cache,