From 1523c38ce831ae952c28cbd774cc21d919119170 Mon Sep 17 00:00:00 2001 From: James Liu Date: Tue, 20 Dec 2022 16:17:05 +0000 Subject: [PATCH] Directly extract joints into SkinnedMeshJoints (#6833) # Objective Following #4402, extract systems run on the render world instead of the main world, and allow retained state operations on it's resources. We're currently extracting to `ExtractedJoints` and then copying it twice during Prepare. Once into `SkinnedMeshJoints` and again into the actual GPU buffer. This makes #4902 obsolete. ## Solution Cut out the middle copy and directly extract joints into `SkinnedMeshJoints` and remove `ExtractedJoints` entirely. This also removes the per-frame allocation that is being made to send `ExtractedJoints` into the render world. ## Performance On my local machine, this halves the time for `prepare_skinned _meshes` on `many_foxes` (195.75us -> 93.93us on average). ![image](https://user-images.githubusercontent.com/3137680/205427455-ab91a8a3-a6b0-4f0a-bd48-e54482c563b2.png) --- ## Changelog Added: `BufferVec::truncate` Added: `BufferVec::extend` Changed: `SkinnedMeshJoints::build` now takes a `&mut BufferVec` instead of a `&mut Vec` as a parameter. Removed: `ExtractedJoints`. ## Migration Guide `ExtractedJoints` has been removed. Read the bound bones from `SkinnedMeshJoints` instead. --- crates/bevy_pbr/src/render/mesh.rs | 52 ++++++++----------- .../src/render_resource/buffer_vec.rs | 11 ++++ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 1c88cc99883bb..bf3b181913a70 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -172,11 +172,6 @@ pub fn extract_meshes( commands.insert_or_spawn_batch(not_caster_commands); } -#[derive(Resource, Debug, Default)] -pub struct ExtractedJoints { - pub buffer: Vec, -} - #[derive(Component)] pub struct SkinnedMeshJoints { pub index: u32, @@ -188,19 +183,22 @@ impl SkinnedMeshJoints { skin: &SkinnedMesh, inverse_bindposes: &Assets, joints: &Query<&GlobalTransform>, - buffer: &mut Vec, + buffer: &mut BufferVec, ) -> Option { let inverse_bindposes = inverse_bindposes.get(&skin.inverse_bindposes)?; - let bindposes = inverse_bindposes.iter(); - let skin_joints = skin.joints.iter(); let start = buffer.len(); - for (inverse_bindpose, joint) in bindposes.zip(skin_joints).take(MAX_JOINTS) { - if let Ok(joint) = joints.get(*joint) { - buffer.push(joint.affine() * *inverse_bindpose); - } else { - buffer.truncate(start); - return None; - } + let target = start + skin.joints.len().min(MAX_JOINTS); + buffer.extend( + joints + .iter_many(&skin.joints) + .zip(inverse_bindposes.iter()) + .map(|(joint, bindpose)| joint.affine() * *bindpose), + ); + // iter_many will skip any failed fetches. This will cause it to assign the wrong bones, + // so just bail by truncating to the start. + if buffer.len() != target { + buffer.truncate(start); + return None; } // Pad to 256 byte alignment @@ -221,13 +219,13 @@ impl SkinnedMeshJoints { pub fn extract_skinned_meshes( mut commands: Commands, mut previous_len: Local, - mut previous_joint_len: Local, + mut uniform: ResMut, query: Extract>, inverse_bindposes: Extract>>, joint_query: Extract>, ) { + uniform.buffer.clear(); let mut values = Vec::with_capacity(*previous_len); - let mut joints = Vec::with_capacity(*previous_joint_len); let mut last_start = 0; for (entity, computed_visibility, skin) in &query { @@ -236,7 +234,7 @@ pub fn extract_skinned_meshes( } // PERF: This can be expensive, can we move this to prepare? if let Some(skinned_joints) = - SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut joints) + SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut uniform.buffer) { last_start = last_start.max(skinned_joints.index as usize); values.push((entity, skinned_joints.to_buffer_index())); @@ -244,13 +242,11 @@ pub fn extract_skinned_meshes( } // Pad out the buffer to ensure that there's enough space for bindings - while joints.len() - last_start < MAX_JOINTS { - joints.push(Mat4::ZERO); + while uniform.buffer.len() - last_start < MAX_JOINTS { + uniform.buffer.push(Mat4::ZERO); } *previous_len = values.len(); - *previous_joint_len = joints.len(); - commands.insert_resource(ExtractedJoints { buffer: joints }); commands.insert_or_spawn_batch(values); } @@ -779,20 +775,14 @@ impl Default for SkinnedMeshUniform { pub fn prepare_skinned_meshes( render_device: Res, render_queue: Res, - extracted_joints: Res, mut skinned_mesh_uniform: ResMut, ) { - if extracted_joints.buffer.is_empty() { + if skinned_mesh_uniform.buffer.is_empty() { return; } - skinned_mesh_uniform.buffer.clear(); - skinned_mesh_uniform - .buffer - .reserve(extracted_joints.buffer.len(), &render_device); - for joint in &extracted_joints.buffer { - skinned_mesh_uniform.buffer.push(*joint); - } + let len = skinned_mesh_uniform.buffer.len(); + skinned_mesh_uniform.buffer.reserve(len, &render_device); skinned_mesh_uniform .buffer .write_buffer(&render_device, &render_queue); diff --git a/crates/bevy_render/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs index 1d3086796dbc5..4a3c744e071c9 100644 --- a/crates/bevy_render/src/render_resource/buffer_vec.rs +++ b/crates/bevy_render/src/render_resource/buffer_vec.rs @@ -131,7 +131,18 @@ impl BufferVec { } } + pub fn truncate(&mut self, len: usize) { + self.values.truncate(len); + } + pub fn clear(&mut self) { self.values.clear(); } } + +impl Extend for BufferVec { + #[inline] + fn extend>(&mut self, iter: I) { + self.values.extend(iter); + } +}