diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index fbb6ab548fe59..c04e2203ad7d4 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -317,6 +317,14 @@ impl SpecializedRenderPipeline for DeferredLightingLayout { shader_defs.push("SCREEN_SPACE_REFLECTIONS".into()); } + if key.contains(MeshPipelineKey::HAVE_PREVIOUS_SKIN) { + shader_defs.push("HAVE_PREVIOUS_SKIN".into()); + } + + if key.contains(MeshPipelineKey::HAVE_PREVIOUS_MORPH) { + shader_defs.push("HAVE_PREVIOUS_MORPH".into()); + } + // Always true, since we're in the deferred lighting pipeline shader_defs.push("DEFERRED_PREPASS".into()); diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index adf08f3068064..1d2c91abac06c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -701,6 +701,20 @@ pub fn queue_material_meshes( mesh_key |= MeshPipelineKey::VISIBILITY_RANGE_DITHER; } + // If the previous frame have skins or morph targets, note that. + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAVE_PREVIOUS_SKIN) + { + mesh_key |= MeshPipelineKey::HAVE_PREVIOUS_SKIN; + } + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAVE_PREVIOUS_MORPH) + { + mesh_key |= MeshPipelineKey::HAVE_PREVIOUS_MORPH; + } + let pipeline_id = pipelines.specialize( &pipeline_cache, &material_pipeline, diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 1a9695cc6a5bb..2e431c2b9796d 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -443,6 +443,14 @@ where shader_defs.push("MOTION_VECTOR_PREPASS".into()); } + if key.mesh_key.contains(MeshPipelineKey::HAVE_PREVIOUS_SKIN) { + shader_defs.push("HAVE_PREVIOUS_SKIN".into()); + } + + if key.mesh_key.contains(MeshPipelineKey::HAVE_PREVIOUS_MORPH) { + shader_defs.push("HAVE_PREVIOUS_MORPH".into()); + } + if key.mesh_key.intersects( MeshPipelineKey::NORMAL_PREPASS | MeshPipelineKey::MOTION_VECTOR_PREPASS @@ -853,6 +861,20 @@ pub fn queue_prepass_material_meshes( mesh_key |= MeshPipelineKey::LIGHTMAPPED; } + // If the previous frame have skins or morph targets, note that. + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAVE_PREVIOUS_SKIN) + { + mesh_key |= MeshPipelineKey::HAVE_PREVIOUS_SKIN; + } + if mesh_instance + .flags + .contains(RenderMeshInstanceFlags::HAVE_PREVIOUS_MORPH) + { + mesh_key |= MeshPipelineKey::HAVE_PREVIOUS_MORPH; + } + let pipeline_id = pipelines.specialize( &pipeline_cache, &prepass_pipeline, diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 0113618d3b13c..35f901f1a155a 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -31,7 +31,26 @@ fn morph_vertex(vertex_in: Vertex) -> Vertex { } return vertex; } -#endif + +// Returns the morphed position of the given vertex from the previous frame. +// +// This function is used for motion vector calculation, and, as such, it doesn't +// bother morphing the normals and tangents. +fn morph_prev_vertex(vertex_in: Vertex) -> Vertex { + var vertex = vertex_in; + let weight_count = morph::layer_count(); + for (var i: u32 = 0u; i < weight_count; i ++) { + let weight = morph::prev_weight_at(i); + if weight == 0.0 { + continue; + } + vertex.position += weight * morph::morph(vertex.index, morph::position_offset, i); + // Don't bother morphing normals and tangents; we don't need them for + // motion vector calculation. + } + return vertex; +} +#endif // MORPH_TARGETS @vertex fn vertex(vertex_no_morph: Vertex) -> VertexOutput { @@ -93,12 +112,42 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { out.color = vertex.color; #endif + // Compute the motion vector, for TAA. For this we need to know where the + // vertex was last frame. #ifdef MOTION_VECTOR_PREPASS - // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. - // See https://github.com/gfx-rs/naga/issues/2416 + + // Take morph targets into account. +#ifdef MORPH_TARGETS + +#ifdef HAVE_PREVIOUS_MORPH + let prev_vertex = morph_prev_vertex(vertex_no_morph); +#else // HAVE_PREVIOUS_MORPH + let prev_vertex = vertex_no_morph; +#endif // HAVE_PREVIOUS_MORPH + +#else // MORPH_TARGETS + let prev_vertex = vertex_no_morph; +#endif // MORPH_TARGETS + + // Take skinning into account. +#ifdef SKINNED + +#ifdef HAVE_PREVIOUS_SKIN + let prev_model = skinning::skin_prev_model( + prev_vertex.joint_indices, + prev_vertex.joint_weights, + ); +#else // HAVE_PREVIOUS_SKIN + let prev_model = mesh_functions::get_previous_model_matrix(prev_vertex.instance_index); +#endif // HAVE_PREVIOUS_SKIN + +#else // SKINNED + let prev_model = mesh_functions::get_previous_model_matrix(prev_vertex.instance_index); +#endif // SKINNED + out.previous_world_position = mesh_functions::mesh_position_local_to_world( - mesh_functions::get_previous_model_matrix(vertex_no_morph.instance_index), - vec4(vertex.position, 1.0) + prev_model, + vec4(prev_vertex.position, 1.0) ); #endif // MOTION_VECTOR_PREPASS diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 646a72f2ee721..8819ca0503e85 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -46,7 +46,7 @@ use static_assertions::const_assert_eq; use crate::render::{ morph::{ - extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices, MorphUniform, + extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices, MorphUniforms, }, skin::no_automatic_skin_batching, }; @@ -54,8 +54,6 @@ use crate::*; use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; -use super::skin::SkinIndices; - /// Provides support for rendering 3D meshes. #[derive(Default)] pub struct MeshRenderPlugin { @@ -140,9 +138,9 @@ impl Plugin for MeshRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .init_resource::() + .init_resource::() .init_resource::() - .init_resource::() + .init_resource::() .init_resource::() .init_resource::() .add_systems( @@ -157,6 +155,7 @@ impl Plugin for MeshRenderPlugin { .add_systems( Render, ( + set_mesh_motion_vector_flags.before(RenderSet::Queue), prepare_skins.in_set(RenderSet::PrepareResources), prepare_morphs.in_set(RenderSet::PrepareResources), prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups), @@ -412,12 +411,18 @@ bitflags::bitflags! { const AUTOMATIC_BATCHING = 1 << 1; /// The mesh had a transform last frame and so is eligible for TAA. const HAVE_PREVIOUS_TRANSFORM = 1 << 2; + /// The mesh had a skin last frame and so that skin should be taken into + /// account for TAA. + const HAVE_PREVIOUS_SKIN = 1 << 3; + /// The mesh had morph targets last frame and so they should be taken + /// into account for TAA. + const HAVE_PREVIOUS_MORPH = 1 << 4; } } /// CPU data that the render world keeps for each entity, when *not* using GPU /// mesh uniform building. -#[derive(Deref)] +#[derive(Deref, DerefMut)] pub struct RenderMeshInstanceCpu { /// Data shared between both the CPU mesh uniform building and the GPU mesh /// uniform building paths. @@ -431,7 +436,7 @@ pub struct RenderMeshInstanceCpu { /// CPU data that the render world needs to keep for each entity that contains a /// mesh when using GPU mesh uniform building. -#[derive(Deref)] +#[derive(Deref, DerefMut)] pub struct RenderMeshInstanceGpu { /// Data shared between both the CPU mesh uniform building and the GPU mesh /// uniform building paths. @@ -599,6 +604,19 @@ impl RenderMeshInstances { } } } + + /// Inserts the given flags into the CPU or GPU render mesh instance data + /// for the given mesh as appropriate. + fn insert_mesh_instance_flags(&mut self, entity: Entity, flags: RenderMeshInstanceFlags) { + match *self { + RenderMeshInstances::CpuBuilding(ref mut instances) => { + instances.insert_mesh_instance_flags(entity, flags); + } + RenderMeshInstances::GpuBuilding(ref mut instances) => { + instances.insert_mesh_instance_flags(entity, flags); + } + } + } } impl RenderMeshInstancesCpu { @@ -614,6 +632,14 @@ impl RenderMeshInstancesCpu { translation: render_mesh_instance.transforms.transform.translation, }) } + + /// Inserts the given flags into the render mesh instance data for the given + /// mesh. + fn insert_mesh_instance_flags(&mut self, entity: Entity, flags: RenderMeshInstanceFlags) { + if let Some(instance) = self.get_mut(&entity) { + instance.flags.insert(flags); + } + } } impl RenderMeshInstancesGpu { @@ -629,6 +655,14 @@ impl RenderMeshInstancesGpu { translation: render_mesh_instance.translation, }) } + + /// Inserts the given flags into the render mesh instance data for the given + /// mesh. + fn insert_mesh_instance_flags(&mut self, entity: Entity, flags: RenderMeshInstanceFlags) { + if let Some(instance) = self.get_mut(&entity) { + instance.flags.insert(flags); + } + } } impl RenderMeshInstanceGpuQueue { @@ -977,6 +1011,35 @@ pub fn extract_meshes_for_gpu_building( ); } +/// A system that sets the [`RenderMeshInstanceFlags`] for each mesh based on +/// whether the previous frame had skins and/or morph targets. +/// +/// Ordinarily, [`RenderMeshInstanceFlags`] are set during the extraction phase. +/// However, we can't do that for the flags related to skins and morph targets +/// because the previous frame's skin and morph targets are the responsibility +/// of [`extract_skins`] and [`extract_morphs`] respectively. We want to run +/// those systems in parallel with mesh extraction for performance, so we need +/// to defer setting of these mesh instance flags to after extraction, which +/// this system does. An alternative to having skin- and morph-target-related +/// data in [`RenderMeshInstanceFlags`] would be to have +/// [`crate::material::queue_material_meshes`] check the skin and morph target +/// tables for each mesh, but that would be too slow in the hot mesh queuing +/// loop. +fn set_mesh_motion_vector_flags( + mut render_mesh_instances: ResMut, + skin_indices: Res, + morph_indices: Res, +) { + for &entity in skin_indices.prev.keys() { + render_mesh_instances + .insert_mesh_instance_flags(entity, RenderMeshInstanceFlags::HAVE_PREVIOUS_SKIN); + } + for &entity in morph_indices.prev.keys() { + render_mesh_instances + .insert_mesh_instance_flags(entity, RenderMeshInstanceFlags::HAVE_PREVIOUS_MORPH); + } +} + /// Creates the [`RenderMeshInstanceGpu`]s and [`MeshInputUniform`]s when GPU /// mesh uniforms are built. fn collect_meshes_for_gpu_building( @@ -1343,7 +1406,9 @@ bitflags::bitflags! { const IRRADIANCE_VOLUME = 1 << 14; const VISIBILITY_RANGE_DITHER = 1 << 15; const SCREEN_SPACE_REFLECTIONS = 1 << 16; - const LAST_FLAG = Self::SCREEN_SPACE_REFLECTIONS.bits(); + const HAVE_PREVIOUS_SKIN = 1 << 17; + const HAVE_PREVIOUS_MORPH = 1 << 18; + const LAST_FLAG = Self::HAVE_PREVIOUS_MORPH.bits(); // Bitfields const MSAA_RESERVED_BITS = Self::MSAA_MASK_BITS << Self::MSAA_SHIFT_BITS; @@ -1645,6 +1710,14 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("MOTION_VECTOR_PREPASS".into()); } + if key.contains(MeshPipelineKey::HAVE_PREVIOUS_SKIN) { + shader_defs.push("HAVE_PREVIOUS_SKIN".into()); + } + + if key.contains(MeshPipelineKey::HAVE_PREVIOUS_MORPH) { + shader_defs.push("HAVE_PREVIOUS_MORPH".into()); + } + if key.contains(MeshPipelineKey::DEFERRED_PREPASS) { shader_defs.push("DEFERRED_PREPASS".into()); } @@ -1874,8 +1947,8 @@ pub fn prepare_mesh_bind_group( gpu_batched_instance_buffers: Option< Res>, >, - skins_uniform: Res, - weights_uniform: Res, + skins_uniform: Res, + weights_uniform: Res, render_lightmaps: Res, ) { groups.reset(); @@ -1896,18 +1969,34 @@ pub fn prepare_mesh_bind_group( groups.model_only = Some(layouts.model_only(&render_device, &model)); - let skin = skins_uniform.buffer.buffer(); + // Create the skinned mesh bind group with the current and previous buffers + // (the latter being for motion vector computation). If there's no previous + // buffer, just use the current one as the shader will ignore it. + let skin = skins_uniform.current_buffer.buffer(); if let Some(skin) = skin { - groups.skinned = Some(layouts.skinned(&render_device, &model, skin)); + let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin); + groups.skinned = Some(layouts.skinned(&render_device, &model, skin, prev_skin)); } - if let Some(weights) = weights_uniform.buffer.buffer() { + // Create the morphed bind groups just like we did for the skinned bind + // group. + if let Some(weights) = weights_uniform.current_buffer.buffer() { + let prev_weights = weights_uniform.prev_buffer.buffer().unwrap_or(weights); for (id, gpu_mesh) in meshes.iter() { if let Some(targets) = gpu_mesh.morph_targets.as_ref() { let group = if let Some(skin) = skin.filter(|_| is_skinned(&gpu_mesh.layout)) { - layouts.morphed_skinned(&render_device, &model, skin, weights, targets) + let prev_skin = skins_uniform.prev_buffer.buffer().unwrap_or(skin); + layouts.morphed_skinned( + &render_device, + &model, + skin, + weights, + targets, + prev_skin, + prev_weights, + ) } else { - layouts.morphed(&render_device, &model, weights, targets) + layouts.morphed(&render_device, &model, weights, targets, prev_weights) }; groups.morph_targets.insert(id, group); } @@ -1998,11 +2087,13 @@ impl RenderCommand

for SetMeshBindGroup { let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(*entity) else { return RenderCommandResult::Success; }; - let skin_index = skin_indices.get(entity); - let morph_index = morph_indices.get(entity); + let current_skin_index = skin_indices.current.get(entity); + let prev_skin_index = skin_indices.prev.get(entity); + let current_morph_index = morph_indices.current.get(entity); + let prev_morph_index = morph_indices.prev.get(entity); - let is_skinned = skin_index.is_some(); - let is_morphed = morph_index.is_some(); + let is_skinned = current_skin_index.is_some(); + let is_morphed = current_morph_index.is_some(); let lightmap = lightmaps .render_lightmaps @@ -2025,14 +2116,35 @@ impl RenderCommand

for SetMeshBindGroup { dynamic_offsets[offset_count] = dynamic_offset.get(); offset_count += 1; } - if let Some(skin_index) = skin_index { - dynamic_offsets[offset_count] = skin_index.index; + if let Some(current_skin_index) = current_skin_index { + dynamic_offsets[offset_count] = current_skin_index.index; offset_count += 1; } - if let Some(morph_index) = morph_index { - dynamic_offsets[offset_count] = morph_index.index; + if let Some(current_morph_index) = current_morph_index { + dynamic_offsets[offset_count] = current_morph_index.index; offset_count += 1; } + + // Attach the previous skin index for motion vector computation. If + // there isn't one, just use zero as the shader will ignore it. + if current_skin_index.is_some() { + match prev_skin_index { + Some(prev_skin_index) => dynamic_offsets[offset_count] = prev_skin_index.index, + None => dynamic_offsets[offset_count] = 0, + } + offset_count += 1; + } + + // Attach the previous morph index for motion vector computation. If + // there isn't one, just use zero as the shader will ignore it. + if current_morph_index.is_some() { + match prev_morph_index { + Some(prev_morph_index) => dynamic_offsets[offset_count] = prev_morph_index.index, + None => dynamic_offsets[offset_count] = 0, + } + offset_count += 1; + } + pass.set_bind_group(I, bind_group, &dynamic_offsets[0..offset_count]); RenderCommandResult::Success diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 60beb9911530a..3cdae40ef939b 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -143,6 +143,8 @@ impl MeshLayouts { ), ) } + + /// Creates the layout for skinned meshes. fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout( "skinned_mesh_layout", @@ -150,11 +152,16 @@ impl MeshLayouts { ShaderStages::VERTEX, ( (0, layout_entry::model(render_device)), + // The current frame's joint matrix buffer. (1, layout_entry::skinning()), + // The previous frame's joint matrix buffer. + (6, layout_entry::skinning()), ), ), ) } + + /// Creates the layout for meshes with morph targets. fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout( "morphed_mesh_layout", @@ -162,12 +169,18 @@ impl MeshLayouts { ShaderStages::VERTEX, ( (0, layout_entry::model(render_device)), + // The current frame's morph weight buffer. (2, layout_entry::weights()), (3, layout_entry::targets()), + // The previous frame's morph weight buffer. + (7, layout_entry::weights()), ), ), ) } + + /// Creates the bind group layout for meshes with both skins and morph + /// targets. fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout( "morphed_skinned_mesh_layout", @@ -175,13 +188,20 @@ impl MeshLayouts { ShaderStages::VERTEX, ( (0, layout_entry::model(render_device)), + // The current frame's joint matrix buffer. (1, layout_entry::skinning()), + // The current frame's morph weight buffer. (2, layout_entry::weights()), (3, layout_entry::targets()), + // The previous frame's joint matrix buffer. + (6, layout_entry::skinning()), + // The previous frame's morph weight buffer. + (7, layout_entry::weights()), ), ), ) } + fn lightmapped_layout(render_device: &RenderDevice) -> BindGroupLayout { render_device.create_bind_group_layout( "lightmapped_mesh_layout", @@ -205,6 +225,7 @@ impl MeshLayouts { &[entry::model(0, model.clone())], ) } + pub fn lightmapped( &self, render_device: &RenderDevice, @@ -221,51 +242,83 @@ impl MeshLayouts { ], ) } + + /// Creates the bind group for skinned meshes with no morph targets. + /// + /// `current_skin` is the buffer of joint matrices for this frame; + /// `prev_skin` is the buffer for the previous frame. The latter is used for + /// motion vector computation. If there is no such applicable buffer, + /// `current_skin` and `prev_skin` will reference the same buffer. pub fn skinned( &self, render_device: &RenderDevice, model: &BindingResource, - skin: &Buffer, + current_skin: &Buffer, + prev_skin: &Buffer, ) -> BindGroup { render_device.create_bind_group( "skinned_mesh_bind_group", &self.skinned, - &[entry::model(0, model.clone()), entry::skinning(1, skin)], + &[ + entry::model(0, model.clone()), + entry::skinning(1, current_skin), + entry::skinning(6, prev_skin), + ], ) } + + /// Creates the bind group for meshes with no skins but morph targets. + /// + /// `current_weights` is the buffer of morph weights for this frame; + /// `prev_weights` is the buffer for the previous frame. The latter is used + /// for motion vector computation. If there is no such applicable buffer, + /// `current_weights` and `prev_weights` will reference the same buffer. pub fn morphed( &self, render_device: &RenderDevice, model: &BindingResource, - weights: &Buffer, + current_weights: &Buffer, targets: &TextureView, + prev_weights: &Buffer, ) -> BindGroup { render_device.create_bind_group( "morphed_mesh_bind_group", &self.morphed, &[ entry::model(0, model.clone()), - entry::weights(2, weights), + entry::weights(2, current_weights), entry::targets(3, targets), + entry::weights(7, prev_weights), ], ) } + + /// Creates the bind group for meshes with skins and morph targets. + /// + /// See the documentation for [`skinned`] and [`morphed`] above for more + /// information about the `current_skin`, `prev_skin`, `current_weights`, + /// and `prev_weights` buffers. + #[allow(clippy::too_many_arguments)] pub fn morphed_skinned( &self, render_device: &RenderDevice, model: &BindingResource, - skin: &Buffer, - weights: &Buffer, + current_skin: &Buffer, + current_weights: &Buffer, targets: &TextureView, + prev_skin: &Buffer, + prev_weights: &Buffer, ) -> BindGroup { render_device.create_bind_group( "morphed_skinned_mesh_bind_group", &self.morphed_skinned, &[ entry::model(0, model.clone()), - entry::skinning(1, skin), - entry::weights(2, weights), + entry::skinning(1, current_skin), + entry::weights(2, current_weights), entry::targets(3, targets), + entry::skinning(6, prev_skin), + entry::weights(7, prev_weights), ], ) } diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 53bc9bcde14b4..2a69e28bf3a44 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -13,4 +13,4 @@ pub use light::*; pub use mesh::*; pub use mesh_bindings::MeshLayouts; pub use mesh_view_bindings::*; -pub use skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform, MAX_JOINTS}; +pub use skin::{extract_skins, prepare_skins, SkinIndices, SkinUniforms, MAX_JOINTS}; diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index 692af99621966..1aa82ec80e9f6 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -1,6 +1,5 @@ use std::{iter, mem}; -use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; use bevy_render::{ @@ -18,18 +17,41 @@ pub struct MorphIndex { pub(super) index: u32, } -#[derive(Default, Resource, Deref, DerefMut)] -pub struct MorphIndices(EntityHashMap); +/// Maps each mesh affected by morph targets to the applicable offset within the +/// [`MorphUniforms`] buffer. +/// +/// We store both the current frame's mapping and the previous frame's mapping +/// for the purposes of motion vector calculation. +#[derive(Default, Resource)] +pub struct MorphIndices { + /// Maps each entity with a morphed mesh to the appropriate offset within + /// [`MorphUniforms::current_buffer`]. + pub current: EntityHashMap, + /// Maps each entity with a morphed mesh to the appropriate offset within + /// [`MorphUniforms::prev_buffer`]. + pub prev: EntityHashMap, +} + +/// The GPU buffers containing morph weights for all meshes with morph targets. +/// +/// This is double-buffered: we store the weights of the previous frame in +/// addition to those of the current frame. This is for motion vector +/// calculation. Every frame, we swap buffers and reuse the morph target weight +/// buffer from two frames ago for the current frame. #[derive(Resource)] -pub struct MorphUniform { - pub buffer: RawBufferVec, +pub struct MorphUniforms { + /// The morph weights for the current frame. + pub current_buffer: RawBufferVec, + /// The morph weights for the previous frame. + pub prev_buffer: RawBufferVec, } -impl Default for MorphUniform { +impl Default for MorphUniforms { fn default() -> Self { Self { - buffer: RawBufferVec::new(BufferUsages::UNIFORM), + current_buffer: RawBufferVec::new(BufferUsages::UNIFORM), + prev_buffer: RawBufferVec::new(BufferUsages::UNIFORM), } } } @@ -37,14 +59,19 @@ impl Default for MorphUniform { pub fn prepare_morphs( render_device: Res, render_queue: Res, - mut uniform: ResMut, + mut uniform: ResMut, ) { - if uniform.buffer.is_empty() { + if uniform.current_buffer.is_empty() { return; } - let len = uniform.buffer.len(); - uniform.buffer.reserve(len, &render_device); - uniform.buffer.write_buffer(&render_device, &render_queue); + let len = uniform.current_buffer.len(); + uniform.current_buffer.reserve(len, &render_device); + uniform + .current_buffer + .write_buffer(&render_device, &render_queue); + + // We don't need to write `uniform.prev_buffer` because we already wrote it + // last frame, and the data should still be on the GPU. } const fn can_align(step: usize, target: usize) -> bool { @@ -80,25 +107,32 @@ fn add_to_alignment(buffer: &mut RawBufferVec) { // Notes on implementation: see comment on top of the extract_skins system in skin module. // This works similarly, but for `f32` instead of `Mat4` pub fn extract_morphs( - mut morph_indices: ResMut, - mut uniform: ResMut, + morph_indices: ResMut, + uniform: ResMut, query: Extract>, ) { - morph_indices.clear(); - uniform.buffer.clear(); + // Borrow check workaround. + let (morph_indices, uniform) = (morph_indices.into_inner(), uniform.into_inner()); + + // Swap buffers. We need to keep the previous frame's buffer around for the + // purposes of motion vector computation. + mem::swap(&mut morph_indices.current, &mut morph_indices.prev); + mem::swap(&mut uniform.current_buffer, &mut uniform.prev_buffer); + morph_indices.current.clear(); + uniform.current_buffer.clear(); for (entity, view_visibility, morph_weights) in &query { if !view_visibility.get() { continue; } - let start = uniform.buffer.len(); + let start = uniform.current_buffer.len(); let weights = morph_weights.weights(); let legal_weights = weights.iter().take(MAX_MORPH_WEIGHTS).copied(); - uniform.buffer.extend(legal_weights); - add_to_alignment::(&mut uniform.buffer); + uniform.current_buffer.extend(legal_weights); + add_to_alignment::(&mut uniform.current_buffer); let index = (start * mem::size_of::()) as u32; - morph_indices.insert(entity, MorphIndex { index }); + morph_indices.current.insert(entity, MorphIndex { index }); } } diff --git a/crates/bevy_pbr/src/render/morph.wgsl b/crates/bevy_pbr/src/render/morph.wgsl index bd6eb1d041832..939b714c777ed 100644 --- a/crates/bevy_pbr/src/render/morph.wgsl +++ b/crates/bevy_pbr/src/render/morph.wgsl @@ -6,6 +6,7 @@ @group(1) @binding(2) var morph_weights: MorphWeights; @group(1) @binding(3) var morph_targets: texture_3d; +@group(1) @binding(7) var prev_morph_weights: MorphWeights; // NOTE: Those are the "hardcoded" values found in `MorphAttributes` struct // in crates/bevy_render/src/mesh/morph/visitors.rs @@ -29,6 +30,10 @@ fn weight_at(weight_index: u32) -> f32 { let i = weight_index; return morph_weights.weights[i / 4u][i % 4u]; } +fn prev_weight_at(weight_index: u32) -> f32 { + let i = weight_index; + return prev_morph_weights.weights[i / 4u][i % 4u]; +} fn morph_pixel(vertex: u32, component: u32, weight: u32) -> f32 { let coord = component_texture_coord(vertex, component); // Due to https://gpuweb.github.io/gpuweb/wgsl/#texel-formats diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index 5d3e2ba2c9369..0f7cc7a9f3cb4 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,5 +1,6 @@ +use std::mem; + use bevy_asset::Assets; -use bevy_derive::{Deref, DerefMut}; use bevy_ecs::entity::EntityHashMap; use bevy_ecs::prelude::*; use bevy_math::Mat4; @@ -30,19 +31,44 @@ impl SkinIndex { } } -#[derive(Default, Resource, Deref, DerefMut)] -pub struct SkinIndices(EntityHashMap); +/// Maps each skinned mesh to the applicable offset within the [`SkinUniforms`] +/// buffer. +/// +/// We store both the current frame's joint matrices and the previous frame's +/// joint matrices for the purposes of motion vector calculation. +#[derive(Default, Resource)] +pub struct SkinIndices { + /// Maps each skinned mesh to the applicable offset within + /// [`SkinUniforms::current_buffer`]. + pub current: EntityHashMap, + + /// Maps each skinned mesh to the applicable offset within + /// [`SkinUniforms::prev_buffer`]. + pub prev: EntityHashMap, +} -// Notes on implementation: see comment on top of the `extract_skins` system. +/// The GPU buffers containing joint matrices for all skinned meshes. +/// +/// This is double-buffered: we store the joint matrices of each mesh for the +/// previous frame in addition to those of each mesh for the current frame. This +/// is for motion vector calculation. Every frame, we swap buffers and overwrite +/// the joint matrix buffer from two frames ago with the data for the current +/// frame. +/// +/// Notes on implementation: see comment on top of the `extract_skins` system. #[derive(Resource)] -pub struct SkinUniform { - pub buffer: RawBufferVec, +pub struct SkinUniforms { + /// Stores all the joint matrices for skinned meshes in the current frame. + pub current_buffer: RawBufferVec, + /// Stores all the joint matrices for skinned meshes in the previous frame. + pub prev_buffer: RawBufferVec, } -impl Default for SkinUniform { +impl Default for SkinUniforms { fn default() -> Self { Self { - buffer: RawBufferVec::new(BufferUsages::UNIFORM), + current_buffer: RawBufferVec::new(BufferUsages::UNIFORM), + prev_buffer: RawBufferVec::new(BufferUsages::UNIFORM), } } } @@ -50,15 +76,20 @@ impl Default for SkinUniform { pub fn prepare_skins( render_device: Res, render_queue: Res, - mut uniform: ResMut, + mut uniform: ResMut, ) { - if uniform.buffer.is_empty() { + if uniform.current_buffer.is_empty() { return; } - let len = uniform.buffer.len(); - uniform.buffer.reserve(len, &render_device); - uniform.buffer.write_buffer(&render_device, &render_queue); + let len = uniform.current_buffer.len(); + uniform.current_buffer.reserve(len, &render_device); + uniform + .current_buffer + .write_buffer(&render_device, &render_queue); + + // We don't need to write `uniform.prev_buffer` because we already wrote it + // last frame, and the data should still be on the GPU. } // Notes on implementation: @@ -88,14 +119,22 @@ pub fn prepare_skins( // which normally only support fixed size arrays. You just have to make sure // in the shader that you only read the values that are valid for that binding. pub fn extract_skins( - mut skin_indices: ResMut, - mut uniform: ResMut, + skin_indices: ResMut, + uniform: ResMut, query: Extract>, inverse_bindposes: Extract>>, joints: Extract>, ) { - uniform.buffer.clear(); - skin_indices.clear(); + // Borrow check workaround. + let (skin_indices, uniform) = (skin_indices.into_inner(), uniform.into_inner()); + + // Swap buffers. We need to keep the previous frame's buffer around for the + // purposes of motion vector computation. + mem::swap(&mut skin_indices.current, &mut skin_indices.prev); + mem::swap(&mut uniform.current_buffer, &mut uniform.prev_buffer); + skin_indices.current.clear(); + uniform.current_buffer.clear(); + let mut last_start = 0; // PERF: This can be expensive, can we move this to prepare? @@ -103,7 +142,7 @@ pub fn extract_skins( if !view_visibility.get() { continue; } - let buffer = &mut uniform.buffer; + let buffer = &mut uniform.current_buffer; let Some(inverse_bindposes) = inverse_bindposes.get(&skin.inverse_bindposes) else { continue; }; @@ -130,12 +169,12 @@ pub fn extract_skins( buffer.push(Mat4::ZERO); } - skin_indices.insert(entity, SkinIndex::new(start)); + skin_indices.current.insert(entity, SkinIndex::new(start)); } // Pad out the buffer to ensure that there's enough space for bindings - while uniform.buffer.len() - last_start < MAX_JOINTS { - uniform.buffer.push(Mat4::ZERO); + while uniform.current_buffer.len() - last_start < MAX_JOINTS { + uniform.current_buffer.push(Mat4::ZERO); } } diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 4307a22ddffa0..1ec9ef15ebe03 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -6,6 +6,14 @@ @group(1) @binding(1) var joint_matrices: SkinnedMesh; +// An array of matrices specifying the joint positions from the previous frame. +// +// This is used for motion vector computation. +// +// If this is the first frame, or we're otherwise prevented from using data from +// the previous frame, this is simply the same as `joint_matrices` above. +@group(1) @binding(6) var prev_joint_matrices: SkinnedMesh; + fn skin_model( indexes: vec4, weights: vec4, @@ -16,6 +24,20 @@ fn skin_model( + weights.w * joint_matrices.data[indexes.w]; } +// Returns the skinned position of a vertex with the given weights from the +// previous frame. +// +// This is used for motion vector computation. +fn skin_prev_model( + indexes: vec4, + weights: vec4, +) -> mat4x4 { + return weights.x * prev_joint_matrices.data[indexes.x] + + weights.y * prev_joint_matrices.data[indexes.y] + + weights.z * prev_joint_matrices.data[indexes.z] + + weights.w * prev_joint_matrices.data[indexes.w]; +} + fn inverse_transpose_3x3m(in: mat3x3) -> mat3x3 { let x = cross(in[1], in[2]); let y = cross(in[2], in[0]);