From d6bd067b25f5dcc0459302ad6fac70ea26586e13 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Fri, 22 Sep 2023 18:49:47 +0200 Subject: [PATCH 1/2] Add motion vectors support for animated meshes Co-authored-by: Robert Swain --- crates/bevy_core_pipeline/src/taa/mod.rs | 2 - crates/bevy_pbr/src/material.rs | 2 +- crates/bevy_pbr/src/prepass/mod.rs | 21 +- crates/bevy_pbr/src/prepass/prepass.wgsl | 31 +- crates/bevy_pbr/src/render/double_buffer.rs | 62 +++ crates/bevy_pbr/src/render/mesh.rs | 168 +++---- crates/bevy_pbr/src/render/mesh_bindings.rs | 501 ++++++++++++-------- crates/bevy_pbr/src/render/mod.rs | 4 +- crates/bevy_pbr/src/render/morph.rs | 30 +- crates/bevy_pbr/src/render/morph.wgsl | 13 + crates/bevy_pbr/src/render/skin.rs | 34 +- crates/bevy_pbr/src/render/skinning.wgsl | 17 + examples/shader/shader_instancing.rs | 2 +- 13 files changed, 574 insertions(+), 313 deletions(-) create mode 100644 crates/bevy_pbr/src/render/double_buffer.rs diff --git a/crates/bevy_core_pipeline/src/taa/mod.rs b/crates/bevy_core_pipeline/src/taa/mod.rs index de4069c9abe7d..47ee8a0686532 100644 --- a/crates/bevy_core_pipeline/src/taa/mod.rs +++ b/crates/bevy_core_pipeline/src/taa/mod.rs @@ -134,8 +134,6 @@ pub struct TemporalAntiAliasBundle { /// /// [Currently](https://github.com/bevyengine/bevy/issues/8423) cannot be used with [`bevy_render::camera::OrthographicProjection`]. /// -/// Currently does not support skinned meshes and morph targets. -/// There will probably be ghosting artifacts if used with them. /// Does not work well with alpha-blended meshes as it requires depth writing to determine motion. /// /// It is very important that correct motion vectors are written for everything on screen. diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index d5884bb65530c..ed170fd19e75c 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -369,7 +369,7 @@ type DrawMaterial = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMaterialBindGroup, - SetMeshBindGroup<2>, + SetMeshBindGroup<2, false>, DrawMesh, ); diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 6f2dfed69e12d..b222777856bab 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -29,6 +29,7 @@ use bevy_render::{ use bevy_transform::prelude::GlobalTransform; use bevy_utils::tracing::error; +use crate::render::{MorphIndices, SkinIndices}; use crate::*; use std::{hash::Hash, marker::PhantomData}; @@ -344,7 +345,7 @@ where ) -> Result { let mut bind_group_layouts = vec![if key .mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) + .contains(MeshPipelineKey::VIEW_MOTION_VECTOR_PREPASS) { self.view_layout_motion_vectors.clone() } else { @@ -453,7 +454,7 @@ where shader_defs.push("PREPASS_FRAGMENT".into()); } - let bind_group = setup_morph_and_skinning_defs( + let bind_group = setup_morph_and_skinning_defs::( &self.mesh_layouts, layout, 4, @@ -465,6 +466,8 @@ where let vertex_buffer_layout = layout.get_layout(&vertex_attributes)?; + // Note that if you are updating this code, you probably need to update the equivalent + // code that depends on color target states in crates/bevy_core_pipeline/src/prepass/node.rs // Setup prepass fragment targets - normals in slot 0 (or None if not needed), motion vectors in slot 1 let mut targets = vec![ key.mesh_key @@ -476,7 +479,7 @@ where write_mask: ColorWrites::ALL, }), key.mesh_key - .contains(MeshPipelineKey::MOTION_VECTOR_PREPASS) + .contains(MeshPipelineKey::VIEW_MOTION_VECTOR_PREPASS) .then_some(ColorTargetState { format: MOTION_VECTOR_PREPASS_FORMAT, // BlendState::REPLACE is not needed here, and None will be potentially much faster in some cases. @@ -721,6 +724,8 @@ pub fn queue_prepass_material_meshes( render_mesh_instances: Res, render_materials: Res>, render_material_instances: Res>, + morph_indices: Res, + skin_indices: Res, mut views: Query< ( &ExtractedView, @@ -781,6 +786,7 @@ pub fn queue_prepass_material_meshes( view_key |= MeshPipelineKey::NORMAL_PREPASS; } if motion_vector_prepass.is_some() { + view_key |= MeshPipelineKey::VIEW_MOTION_VECTOR_PREPASS; view_key |= MeshPipelineKey::MOTION_VECTOR_PREPASS; } @@ -802,6 +808,13 @@ pub fn queue_prepass_material_meshes( let mut mesh_key = MeshPipelineKey::from_primitive_topology(mesh.primitive_topology) | view_key; + + let missing_motion_vector = morph_indices.missing_previous(visible_entity) + || skin_indices.missing_previous(visible_entity); + + if missing_motion_vector { + mesh_key &= !MeshPipelineKey::MOTION_VECTOR_PREPASS; + } if mesh.morph_targets.is_some() { mesh_key |= MeshPipelineKey::MORPH_TARGETS; } @@ -960,7 +973,7 @@ pub type DrawPrepass = ( SetItemPipeline, SetPrepassViewBindGroup<0>, SetMaterialBindGroup, - SetMeshBindGroup<2>, + SetMeshBindGroup<2, true>, DrawMesh, ); diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index fef278324fbe4..3c8438e30b3fa 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -32,6 +32,21 @@ fn morph_vertex(vertex_in: Vertex) -> Vertex { } return vertex; } + + +#ifdef MOTION_VECTOR_PREPASS +fn morph_previous_position(vertex: Vertex) -> vec3 { + var vertex_position = vertex.position; + let weight_count = bevy_pbr::morph::layer_count(); + for (var i: u32 = 0u; i < weight_count; i++) { + let weight = bevy_pbr::morph::previous_weight_at(i); + if weight != 0.0 { + vertex_position += weight * bevy_pbr::morph::morph(vertex.index, bevy_pbr::morph::position_offset, i); + } + } + return vertex_position; +} +#endif // MOTION_VECTOR_PREPASS #endif @vertex @@ -39,7 +54,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { var out: VertexOutput; #ifdef MORPH_TARGETS - var vertex = morph::morph_vertex(vertex_no_morph); + var vertex = morph_vertex(vertex_no_morph); #else var vertex = vertex_no_morph; #endif @@ -94,11 +109,21 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { #endif // MOTION_VECTOR_PREPASS_OR_DEFERRED_PREPASS #ifdef MOTION_VECTOR_PREPASS +#ifdef MORPH_TARGETS + var previous_position = morph_previous_position(vertex_no_morph); +#else + var previous_position = vertex.position; +#endif // MORPH_TARGETS +#ifdef SKINNED + var previous_model = skinning::previous_skin_model(vertex.joint_indices, vertex.joint_weights); +#else // 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 + var previous_model = mesh_functions::get_previous_model_matrix(vertex_no_morph.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) + previous_model, + vec4(previous_position, 1.0) ); #endif // MOTION_VECTOR_PREPASS diff --git a/crates/bevy_pbr/src/render/double_buffer.rs b/crates/bevy_pbr/src/render/double_buffer.rs new file mode 100644 index 0000000000000..0b89511b157ff --- /dev/null +++ b/crates/bevy_pbr/src/render/double_buffer.rs @@ -0,0 +1,62 @@ +use std::mem; + +use bevy_ecs::prelude::Entity; +use bevy_render::render_resource::{BufferUsages, BufferVec}; +use bevy_utils::EntityHashMap; +use bytemuck::Pod; + +/// A double buffer of `T`. +/// +/// Use [`DoubleBuffer::swap`] to swap buffer, +/// access the current buffer with [`DoubleBuffer::current`], +/// and the previous one with [`DoubleBuffer::previous`]. +#[derive(Default)] +pub struct DoubleBuffer { + pub previous: T, + pub current: T, +} + +impl DoubleBuffer { + pub fn swap(&mut self, swap_buffer: bool) { + if swap_buffer { + mem::swap(&mut self.current, &mut self.previous); + } + } +} + +pub type DoubleBufferVec = DoubleBuffer>; + +impl DoubleBufferVec { + pub const fn new(buffer_usage: BufferUsages) -> Self { + DoubleBufferVec { + previous: BufferVec::new(buffer_usage), + current: BufferVec::new(buffer_usage), + } + } + + pub fn clear(&mut self, swap_buffer: bool) { + self.swap(swap_buffer); + self.current.clear(); + } +} + +pub type DoubleEntityMap = DoubleBuffer>; + +impl DoubleEntityMap { + pub fn clear(&mut self, swap_buffer: bool) { + self.swap(swap_buffer); + self.current.clear(); + } + + pub fn insert(&mut self, entity: Entity, value: T) { + self.current.insert(entity, value); + } + + pub fn missing_previous(&self, entity: &Entity) -> bool { + let current = self.current.contains_key(entity); + let previous = self.previous.contains_key(entity); + // Either it's already missing (therefore there is no "previous" to miss) + // or it's not missing and there is no "previous", so we miss previous. + current && !previous + } +} diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 8c87d27129c2d..b967034c428cc 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -3,6 +3,7 @@ use bevy_asset::{load_internal_asset, AssetId, Handle}; use bevy_core_pipeline::{ core_3d::{AlphaMask3d, Opaque3d, Transmissive3d, Transparent3d, CORE_3D_DEPTH_FORMAT}, deferred::{AlphaMask3dDeferred, Opaque3dDeferred}, + prepass::MotionVectorPrepass, }; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ @@ -26,7 +27,7 @@ use bevy_render::{ Extract, ExtractSchedule, Render, RenderApp, RenderSet, }; use bevy_transform::components::GlobalTransform; -use bevy_utils::{tracing::error, EntityHashMap, HashMap, Hashed}; +use bevy_utils::{tracing::error, EntityHashMap, Hashed}; use std::cell::Cell; use thread_local::ThreadLocal; @@ -40,16 +41,15 @@ use std::sync::{ }; use crate::render::{ + mesh_bindings::MeshBindGroups, morph::{ extract_morphs, no_automatic_morph_batching, prepare_morphs, MorphIndices, MorphUniform, }, - skin::{extract_skins, no_automatic_skin_batching, prepare_skins, SkinUniform}, + skin::{extract_skins, no_automatic_skin_batching, prepare_skins, SkinIndices, SkinUniform}, MeshLayouts, }; use crate::*; -use super::skin::SkinIndices; - #[derive(Default)] pub struct MeshRenderPlugin; @@ -496,6 +496,19 @@ bitflags::bitflags! { const DEPTH_CLAMP_ORTHO = (1 << 10); const TEMPORAL_JITTER = (1 << 11); const MORPH_TARGETS = (1 << 12); + /// Whether the view has motion vectors. + /// + /// This is distinct from [`MeshPipelineKey::MOTION_VECTOR_PREPASS`] as the key + /// represents whether the specific mesh being rendered supports motion vectors, + /// while the `VIEW_MOTION_VECTOR_PREPASS` represents whether the whole view should support + /// motion vectors. + /// + /// This is an important distinction. There may be several meshes drawn into a + /// buffer, including the motion vector buffer. + /// + /// Used in the prepass pipeline. + const VIEW_MOTION_VECTOR_PREPASS = (1 << 13); + const BLEND_RESERVED_BITS = Self::BLEND_MASK_BITS << Self::BLEND_SHIFT_BITS; // ← Bitmask reserving bits for the blend state const BLEND_OPAQUE = (0 << Self::BLEND_SHIFT_BITS); // ← Values are just sequential within the mask, and can range from 0 to 3 const BLEND_PREMULTIPLIED_ALPHA = (1 << Self::BLEND_SHIFT_BITS); // @@ -599,7 +612,7 @@ impl MeshPipelineKey { fn is_skinned(layout: &Hashed) -> bool { layout.contains(Mesh::ATTRIBUTE_JOINT_INDEX) && layout.contains(Mesh::ATTRIBUTE_JOINT_WEIGHT) } -pub fn setup_morph_and_skinning_defs( +pub fn setup_morph_and_skinning_defs( mesh_layouts: &MeshLayouts, layout: &Hashed, offset: u32, @@ -607,28 +620,19 @@ pub fn setup_morph_and_skinning_defs( shader_defs: &mut Vec, vertex_attributes: &mut Vec, ) -> BindGroupLayout { - let mut add_skin_data = || { + let skin = is_skinned(layout); + let morph = key.contains(MeshPipelineKey::MORPH_TARGETS); + let motion_vectors = PREPASS && key.contains(MeshPipelineKey::MOTION_VECTOR_PREPASS); + + if skin { shader_defs.push("SKINNED".into()); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset)); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1)); - }; - let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS); - match (is_skinned(layout), is_morphed) { - (true, false) => { - add_skin_data(); - mesh_layouts.skinned.clone() - } - (true, true) => { - add_skin_data(); - shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed_skinned.clone() - } - (false, true) => { - shader_defs.push("MORPH_TARGETS".into()); - mesh_layouts.morphed.clone() - } - (false, false) => mesh_layouts.model_only.clone(), } + if morph { + shader_defs.push("MORPH_TARGETS".into()); + } + mesh_layouts.get(skin, morph, motion_vectors).clone() } impl SpecializedMeshPipeline for MeshPipeline { @@ -687,7 +691,7 @@ impl SpecializedMeshPipeline for MeshPipeline { shader_defs.push("MULTISAMPLED".into()); }; - bind_group_layout.push(setup_morph_and_skinning_defs( + bind_group_layout.push(setup_morph_and_skinning_defs::( &self.mesh_layouts, layout, 6, @@ -920,34 +924,6 @@ impl SpecializedMeshPipeline for MeshPipeline { } } -/// Bind groups for meshes currently loaded. -#[derive(Resource, Default)] -pub struct MeshBindGroups { - model_only: Option, - skinned: Option, - morph_targets: HashMap, BindGroup>, -} -impl MeshBindGroups { - pub fn reset(&mut self) { - self.model_only = None; - self.skinned = None; - self.morph_targets.clear(); - } - /// Get the `BindGroup` for `GpuMesh` with given `handle_id`. - pub fn get( - &self, - asset_id: AssetId, - is_skinned: bool, - morph: bool, - ) -> Option<&BindGroup> { - match (is_skinned, morph) { - (_, true) => self.morph_targets.get(&asset_id), - (true, false) => self.skinned.as_ref(), - (false, false) => self.model_only.as_ref(), - } - } -} - pub fn prepare_mesh_bind_group( meshes: Res>, mut groups: ResMut, @@ -957,29 +933,35 @@ pub fn prepare_mesh_bind_group( skins_uniform: Res, weights_uniform: Res, ) { - groups.reset(); - let layouts = &mesh_pipeline.mesh_layouts; let Some(model) = mesh_uniforms.binding() else { return; }; - groups.model_only = Some(layouts.model_only(&render_device, &model)); + let layouts = &mesh_pipeline.mesh_layouts; + let mut bind_groups = groups.bind_group_builder(&render_device, model, layouts); + + let skin = skins_uniform.buffer.current.buffer(); + let previous_skin = skins_uniform.buffer.previous.buffer(); - let skin = skins_uniform.buffer.buffer(); - if let Some(skin) = skin { - groups.skinned = Some(layouts.skinned(&render_device, &model, skin)); + bind_groups.add_variant(None, None, None, None); + if skin.is_some() { + bind_groups.add_variant(skin, None, None, None); + bind_groups.add_variant(skin, None, previous_skin, None); } + let Some(weights) = weights_uniform.buffer.current.buffer() else { + return; + }; + let previous_weights = weights_uniform.buffer.previous.buffer(); - if let Some(weights) = weights_uniform.buffer.buffer() { - 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) - } else { - layouts.morphed(&render_device, &model, weights, targets) - }; - groups.morph_targets.insert(id, group); - } - } + for (id, gpu_mesh) in meshes.iter() { + let skin = skin.filter(|_| is_skinned(&gpu_mesh.layout)); + let previous_skin = previous_skin.filter(|_| is_skinned(&gpu_mesh.layout)); + + let Some(targets) = gpu_mesh.morph_targets.as_ref() else { + continue; + }; + let morph = Some((id, weights, targets)); + bind_groups.add_variant(skin, morph, previous_skin, previous_weights); + bind_groups.add_variant(skin, morph, previous_skin, None); } } @@ -1015,21 +997,23 @@ impl RenderCommand

for SetMeshViewBindGroup } } -pub struct SetMeshBindGroup; -impl RenderCommand

for SetMeshBindGroup { +pub struct SetMeshBindGroup; +impl RenderCommand

+ for SetMeshBindGroup +{ type Param = ( SRes, SRes, SRes, SRes, ); - type ViewWorldQuery = (); + type ViewWorldQuery = Has; type ItemWorldQuery = (); #[inline] fn render<'w>( item: &P, - _view: (), + has_motion_vectors: bool, _item_query: (), (bind_groups, mesh_instances, skin_indices, morph_indices): SystemParamItem< 'w, @@ -1048,22 +1032,34 @@ impl RenderCommand

for SetMeshBindGroup { let Some(mesh) = mesh_instances.get(entity) else { return RenderCommandResult::Success; }; - let skin_index = skin_indices.get(entity); - let morph_index = morph_indices.get(entity); + let skin_index = skin_indices.current.get(entity); + let morph_index = morph_indices.current.get(entity); + let mut previous_skin_index = None; + let mut previous_morph_index = None; let is_skinned = skin_index.is_some(); - let is_morphed = morph_index.is_some(); - - let Some(bind_group) = bind_groups.get(mesh.mesh_asset_id, is_skinned, is_morphed) else { + let morph_id = morph_index.is_some().then_some(mesh.mesh_asset_id); + let mut is_motion_vectors = PREPASS && has_motion_vectors; + + if is_motion_vectors { + // disable is_motion_vectors if this mesh doesn't have a previous value for + // skins or morphs. This way, we get the shader variant without the bindings for + // the previous value, which doesn't exist. + previous_skin_index = skin_indices.previous.get(entity); + is_motion_vectors &= !skin_indices.missing_previous(entity); + + previous_morph_index = morph_indices.previous.get(entity); + is_motion_vectors &= !morph_indices.missing_previous(entity); + } + let Some(bind_group) = bind_groups.get(is_skinned, morph_id, is_motion_vectors) else { error!( - "The MeshBindGroups resource wasn't set in the render phase. \ - It should be set by the queue_mesh_bind_group system.\n\ + "The MeshBindGroups is divergent in render and prepare_mesh_bind_group.\n\ This is a bevy bug! Please open an issue." ); return RenderCommandResult::Failure; }; - let mut dynamic_offsets: [u32; 3] = Default::default(); + let mut dynamic_offsets: [u32; 5] = Default::default(); let mut offset_count = 0; if let Some(dynamic_offset) = item.dynamic_offset() { dynamic_offsets[offset_count] = dynamic_offset.get(); @@ -1077,6 +1073,16 @@ impl RenderCommand

for SetMeshBindGroup { dynamic_offsets[offset_count] = morph_index.index; offset_count += 1; } + if is_motion_vectors { + if let Some(skin_index) = previous_skin_index { + dynamic_offsets[offset_count] = skin_index.index; + offset_count += 1; + } + if let Some(morph_index) = previous_morph_index { + dynamic_offsets[offset_count] = morph_index.index; + 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 df935e1805c8a..99742cf455d6a 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -1,9 +1,17 @@ //! Bind group layout related definitions for the mesh pipeline. +use std::{array, fmt}; +use bevy_asset::AssetId; +use bevy_ecs::prelude::Resource; use bevy_math::Mat4; -use bevy_render::{mesh::morph::MAX_MORPH_WEIGHTS, render_resource::*, renderer::RenderDevice}; +use bevy_render::mesh::{morph::MAX_MORPH_WEIGHTS, Mesh}; +use bevy_render::{render_resource::*, renderer::RenderDevice}; +use bevy_utils::HashMap; +use bitflags::bitflags; +use smallvec::SmallVec; use crate::render::skin::MAX_JOINTS; +use crate::MeshUniform; const MORPH_WEIGHT_SIZE: usize = std::mem::size_of::(); pub const MORPH_BUFFER_SIZE: usize = MAX_MORPH_WEIGHTS * MORPH_WEIGHT_SIZE; @@ -11,219 +19,328 @@ pub const MORPH_BUFFER_SIZE: usize = MAX_MORPH_WEIGHTS * MORPH_WEIGHT_SIZE; const JOINT_SIZE: usize = std::mem::size_of::(); pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE; -/// Individual layout entries. -mod layout_entry { - use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; - use crate::MeshUniform; - use bevy_render::{ - render_resource::{ - BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, GpuArrayBuffer, - ShaderStages, TextureSampleType, TextureViewDimension, +// --- Individual layout entries and individual bind group entries --- + +fn buffer_layout(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry { + BindGroupLayoutEntry { + binding, + visibility, + count: None, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: BufferSize::new(size), }, - renderer::RenderDevice, - }; - - fn buffer(binding: u32, size: u64, visibility: ShaderStages) -> BindGroupLayoutEntry { - BindGroupLayoutEntry { - binding, - visibility, - count: None, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: true, - min_binding_size: BufferSize::new(size), - }, - } } - pub(super) fn model(render_device: &RenderDevice, binding: u32) -> BindGroupLayoutEntry { - GpuArrayBuffer::::binding_layout( - binding, - ShaderStages::VERTEX_FRAGMENT, - render_device, - ) - } - pub(super) fn skinning(binding: u32) -> BindGroupLayoutEntry { - buffer(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX) - } - pub(super) fn weights(binding: u32) -> BindGroupLayoutEntry { - buffer(binding, MORPH_BUFFER_SIZE as u64, ShaderStages::VERTEX) - } - pub(super) fn targets(binding: u32) -> BindGroupLayoutEntry { - BindGroupLayoutEntry { - binding, - visibility: ShaderStages::VERTEX, - ty: BindingType::Texture { - view_dimension: TextureViewDimension::D3, - sample_type: TextureSampleType::Float { filterable: false }, - multisampled: false, - }, - count: None, - } +} +fn buffer_entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry { + BindGroupEntry { + binding, + resource: BindingResource::Buffer(BufferBinding { + buffer, + offset: 0, + size: Some(BufferSize::new(size).unwrap()), + }), } } -/// Individual [`BindGroupEntry`] -/// for bind groups. -mod entry { - use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; - use bevy_render::render_resource::{ - BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView, - }; - - fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry { - BindGroupEntry { - binding, - resource: BindingResource::Buffer(BufferBinding { - buffer, - offset: 0, - size: Some(BufferSize::new(size).unwrap()), - }), - } + +fn model_layout(render_device: &RenderDevice, binding: u32) -> BindGroupLayoutEntry { + GpuArrayBuffer::::binding_layout( + binding, + ShaderStages::VERTEX_FRAGMENT, + render_device, + ) +} +fn model_entry(binding: u32, resource: BindingResource) -> BindGroupEntry { + BindGroupEntry { binding, resource } +} + +fn skinning_layout(binding: u32) -> BindGroupLayoutEntry { + buffer_layout(binding, JOINT_BUFFER_SIZE as u64, ShaderStages::VERTEX) +} +fn skinning_entry(binding: u32, buffer: &Buffer) -> BindGroupEntry { + buffer_entry(binding, JOINT_BUFFER_SIZE as u64, buffer) +} + +fn weights_layout(binding: u32) -> BindGroupLayoutEntry { + buffer_layout(binding, MORPH_BUFFER_SIZE as u64, ShaderStages::VERTEX) +} +fn weights_entry(binding: u32, buffer: &Buffer) -> BindGroupEntry { + buffer_entry(binding, MORPH_BUFFER_SIZE as u64, buffer) +} + +fn targets_layout(binding: u32) -> BindGroupLayoutEntry { + BindGroupLayoutEntry { + binding, + visibility: ShaderStages::VERTEX, + ty: BindingType::Texture { + view_dimension: TextureViewDimension::D3, + sample_type: TextureSampleType::Float { filterable: false }, + multisampled: false, + }, + count: None, + } +} +fn targets_entry(binding: u32, texture: &TextureView) -> BindGroupEntry { + BindGroupEntry { + binding, + resource: BindingResource::TextureView(texture), + } +} + +/// Create a layout for a specific [`ActiveVariant`] by combining the layouts of +/// all the active shader features. +fn variant_layout(active_variant: ActiveVariant, device: &RenderDevice) -> BindGroupLayout { + let skin = active_variant.contains(ActiveVariant::SKIN); + let morph = active_variant.contains(ActiveVariant::MORPH); + let motion_vectors = active_variant.contains(ActiveVariant::MOTION_VECTORS); + + let mut layout_entries = SmallVec::<[BindGroupLayoutEntry; 6]>::new(); + + layout_entries.push(model_layout(device, 0)); + + if skin { + layout_entries.push(skinning_layout(1)); } - pub(super) fn model(binding: u32, resource: BindingResource) -> BindGroupEntry { - BindGroupEntry { binding, resource } + if morph { + layout_entries.extend([weights_layout(2), targets_layout(3)]); } - pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry { - entry(binding, JOINT_BUFFER_SIZE as u64, buffer) + if skin && motion_vectors { + layout_entries.push(skinning_layout(4)); } - pub(super) fn weights(binding: u32, buffer: &Buffer) -> BindGroupEntry { - entry(binding, MORPH_BUFFER_SIZE as u64, buffer) + if morph && motion_vectors { + layout_entries.push(weights_layout(5)); } - pub(super) fn targets(binding: u32, texture: &TextureView) -> BindGroupEntry { - BindGroupEntry { - binding, - resource: BindingResource::TextureView(texture), + + device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &layout_entries, + label: Some(&*format!("{active_variant}_layout")), + }) +} + +/// Create [`BindGroup`]s and add them to a [`MeshBindGroups`]. +/// +/// Use [`MeshBindGroups::bind_group_builder`] to get a [`MeshBindGroupBuilder`], +/// and use [`MeshBindGroupBuilder::add_variant`] to add new bind group variants. +pub struct MeshBindGroupBuilder<'a> { + layouts: &'a MeshLayouts, + device: &'a RenderDevice, + model: BindingResource<'a>, + pub bind_groups: &'a mut MeshBindGroups, +} +impl<'a> MeshBindGroupBuilder<'a> { + /// Create a [`BindGroup`] with the provided active features, and add it to [`Self::bind_groups`]. + /// + /// Each parameter to `add_variant` is a shader feature. When the parameter + /// is `None`, it means that the shader feature is disabled. + pub fn add_variant( + &mut self, + skin: Option<&Buffer>, + morph: Option<(AssetId, &Buffer, &TextureView)>, + previous_skin: Option<&Buffer>, + previous_weights: Option<&Buffer>, + ) { + let mut bind_group_entries = SmallVec::<[BindGroupEntry; 6]>::new(); + + bind_group_entries.push(model_entry(0, self.model.clone())); + + if let Some(skin) = skin { + bind_group_entries.push(skinning_entry(1, skin)); + } + if let Some((_, weights, targets)) = morph { + bind_group_entries.extend([weights_entry(2, weights), targets_entry(3, targets)]); + } + if let Some(skin) = previous_skin { + bind_group_entries.push(skinning_entry(4, skin)); + } + if let Some(weights) = previous_weights { + bind_group_entries.push(weights_entry(5, weights)); } + + let skin = skin.is_some(); + let motion_vectors = previous_skin.is_some() || previous_weights.is_some(); + let active_variant = ActiveVariant::new(skin, morph.is_some(), motion_vectors); + + let bind_group = self.device.create_bind_group( + Some(&*format!("{active_variant}_bind_group")), + self.layouts.get_variant(active_variant), + &bind_group_entries, + ); + self.bind_groups + .insert(skin, morph.map(|m| m.0), motion_vectors, bind_group); } } -/// All possible [`BindGroupLayout`]s in bevy's default mesh shader (`mesh.wgsl`). -#[derive(Clone)] -pub struct MeshLayouts { - /// The mesh model uniform (transform) and nothing else. - pub model_only: BindGroupLayout, +/// The [`BindGroup`]s for individual existing mesh shader variants. +/// +/// Morph targets allow several different bind groups, because individual mesh +/// may have a different [`TextureView`] that represents the morph target's pose +/// vertex attribute values. +/// +/// Non-morph target bind groups are optional. We don't know at compile time +/// whether motion vectors or skinned meshes will be used. +#[derive(Default, Resource)] +pub struct MeshBindGroups { + shared: HashMap, + distinct: HashMap<(AssetId, ActiveVariant), BindGroup>, +} - /// Also includes the uniform for skinning - pub skinned: BindGroupLayout, +impl MeshBindGroups { + pub fn new() -> Self { + MeshBindGroups::default() + } + /// Get a specific [`BindGroup`] that was previously added. + pub fn get( + &self, + skin: bool, + morph_id: Option>, + motion_vectors: bool, + ) -> Option<&BindGroup> { + let variant = ActiveVariant::new(skin, morph_id.is_some(), motion_vectors); + if let Some(id) = morph_id { + self.distinct.get(&(id, variant)) + } else { + self.shared.get(&variant) + } + } + fn insert( + &mut self, + skin: bool, + morph_id: Option>, + motion_vectors: bool, + bind_group: BindGroup, + ) { + let variant = ActiveVariant::new(skin, morph_id.is_some(), motion_vectors); - /// Also includes the uniform and [`MorphAttributes`] for morph targets. - /// - /// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes - pub morphed: BindGroupLayout, + if let Some(id) = morph_id { + self.distinct.insert((id, variant), bind_group); + } else { + self.shared.insert(variant, bind_group); + } + } + pub fn clear(&mut self) { + self.shared.clear(); + self.distinct.clear(); + } + /// Clears `self` and returns a [`MeshBindGroupBuilder`]. + pub fn bind_group_builder<'a>( + &'a mut self, + device: &'a RenderDevice, + model: BindingResource<'a>, + layouts: &'a MeshLayouts, + ) -> MeshBindGroupBuilder<'a> { + self.clear(); - /// Also includes both uniforms for skinning and morph targets, also the - /// morph target [`MorphAttributes`] binding. - /// - /// [`MorphAttributes`]: bevy_render::mesh::morph::MorphAttributes - pub morphed_skinned: BindGroupLayout, + MeshBindGroupBuilder { + layouts, + device, + model, + bind_groups: self, + } + } } +/// All possible [`BindGroupLayout`]s in bevy's default mesh shader (`mesh.wgsl`). +#[derive(Clone)] +pub struct MeshLayouts([BindGroupLayout; ActiveVariant::COUNT]); + impl MeshLayouts { /// Prepare the layouts used by the default bevy [`Mesh`]. /// /// [`Mesh`]: bevy_render::prelude::Mesh - pub fn new(render_device: &RenderDevice) -> Self { - MeshLayouts { - model_only: Self::model_only_layout(render_device), - skinned: Self::skinned_layout(render_device), - morphed: Self::morphed_layout(render_device), - morphed_skinned: Self::morphed_skinned_layout(render_device), - } + pub fn new(device: &RenderDevice) -> Self { + let layout = |bits| variant_layout(ActiveVariant::from_bits_truncate(bits), device); + MeshLayouts(array::from_fn(layout)) + } + // TODO: Passing 3 bools is pretty bad + pub fn get(&self, skin: bool, morph: bool, motion_vectors: bool) -> &BindGroupLayout { + let variant = ActiveVariant::new(skin, morph, motion_vectors); + self.get_variant(variant) + } + fn get_variant(&self, active_variant: ActiveVariant) -> &BindGroupLayout { + &self.0[active_variant.bits()] + } +} + +bitflags! { + /// The set of active features for a given mesh shader instance. + /// + /// Individual meshes may have different features. For example, + /// one mesh may have morph targets, another skinning. + /// + /// Each of those features enable different shader code and bind groups through + /// the naga C-like pre-processor (`#ifdef FOO`). + /// + /// As a result, different meshes may use different variants of the shader and need + /// different bind group layouts. + #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] + struct ActiveVariant: usize { + /// Whether this mesh uses skeletal skinning. + /// + /// It is true whenever the mesh being rendered has both `Mesh::ATTRIBUTE_JOINT_INDEX` + /// and `Mesh::ATTRIBUTE_JOINT_WEIGHT` vertex attributes. + const SKIN = 1 << 0; + + /// Whether this mesh uses morph targets. + /// + /// This is determined by the `MeshPipelineKey::MORPH_TARGETS` pipeline key + /// flag. + /// + /// This pipeline key flag is set whenever the mesh being rendered has the + /// `morph_targets` field set to `Some`. + const MORPH = 1 << 1; + + /// Whether this mesh is being rendered with motion vectors. + /// + /// This is determined by the `MeshPipelineKey::MOTION_VECTOR_PREPASS` pipeline key + /// flag. + /// + /// This pipeline key flag is set whenever the view rendering the mesh has the + /// `MotionVectorPrepass` component, **and** the mesh, if it has morph targets + /// or skinning, was rendered last frame. + /// + /// Note that the same mesh can be rendered by different views, some of them + /// with or without motion vectors. + const MOTION_VECTORS = 1 << 2; + + // NOTE: ADDING A NEW VARIANT + // You'll have to add handling for the new variants in the various places + // in this file: + // - fmt::Display for ActiveVariant + // - ActiveVariant::new + // - MeshBindGroupBuilder::add_variant + // - variant_layout + // - setup_morph_and_skinning_defs in render/mesh.rs + // - RenderCommand

for SetMeshBindGroup in render/mesh.rs } +} +impl ActiveVariant { + const COUNT: usize = Self::all().bits() + 1; - // ---------- create individual BindGroupLayouts ---------- - - fn model_only_layout(render_device: &RenderDevice) -> BindGroupLayout { - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[layout_entry::model(render_device, 0)], - label: Some("mesh_layout"), - }) - } - fn skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - layout_entry::model(render_device, 0), - layout_entry::skinning(1), - ], - label: Some("skinned_mesh_layout"), - }) - } - fn morphed_layout(render_device: &RenderDevice) -> BindGroupLayout { - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - layout_entry::model(render_device, 0), - layout_entry::weights(2), - layout_entry::targets(3), - ], - label: Some("morphed_mesh_layout"), - }) - } - fn morphed_skinned_layout(render_device: &RenderDevice) -> BindGroupLayout { - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - entries: &[ - layout_entry::model(render_device, 0), - layout_entry::skinning(1), - layout_entry::weights(2), - layout_entry::targets(3), - ], - label: Some("morphed_skinned_mesh_layout"), - }) - } - - // ---------- BindGroup methods ---------- - - pub fn model_only(&self, render_device: &RenderDevice, model: &BindingResource) -> BindGroup { - render_device.create_bind_group( - "model_only_mesh_bind_group", - &self.model_only, - &[entry::model(0, model.clone())], - ) - } - pub fn skinned( - &self, - render_device: &RenderDevice, - model: &BindingResource, - skin: &Buffer, - ) -> BindGroup { - render_device.create_bind_group( - "skinned_mesh_bind_group", - &self.skinned, - &[entry::model(0, model.clone()), entry::skinning(1, skin)], - ) - } - pub fn morphed( - &self, - render_device: &RenderDevice, - model: &BindingResource, - weights: &Buffer, - targets: &TextureView, - ) -> BindGroup { - render_device.create_bind_group( - "morphed_mesh_bind_group", - &self.morphed, - &[ - entry::model(0, model.clone()), - entry::weights(2, weights), - entry::targets(3, targets), - ], - ) - } - pub fn morphed_skinned( - &self, - render_device: &RenderDevice, - model: &BindingResource, - skin: &Buffer, - weights: &Buffer, - targets: &TextureView, - ) -> 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::targets(3, targets), - ], - ) + fn new(skin: bool, morph: bool, mut motion_vectors: bool) -> Self { + let if_enabled = |flag, value| if flag { value } else { Self::empty() }; + + // Meshes with and without motion vectors, but also without skins/morphs + // share the same bind group. + if !skin && !morph { + motion_vectors = false; + } + if_enabled(skin, Self::SKIN) + | if_enabled(morph, Self::MORPH) + | if_enabled(motion_vectors, Self::MOTION_VECTORS) + } +} +impl fmt::Display for ActiveVariant { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.contains(Self::SKIN) { + f.write_str("skinned_")?; + } + if self.contains(Self::MORPH) { + f.write_str("morphed_")?; + } + if self.contains(Self::MOTION_VECTORS) { + f.write_str("motion_vectors_")?; + } + f.write_str("mesh") } } diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 7efffc05681f8..5fc418f41083f 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -1,3 +1,4 @@ +mod double_buffer; mod fog; mod light; pub(crate) mod mesh; @@ -11,4 +12,5 @@ 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 morph::{MorphIndex, MorphIndices}; +pub use skin::{extract_skins, prepare_skins, SkinIndex, SkinIndices, SkinUniform, MAX_JOINTS}; diff --git a/crates/bevy_pbr/src/render/morph.rs b/crates/bevy_pbr/src/render/morph.rs index 61dfef75d5280..7b754d3122b24 100644 --- a/crates/bevy_pbr/src/render/morph.rs +++ b/crates/bevy_pbr/src/render/morph.rs @@ -1,5 +1,6 @@ use std::{iter, mem}; +use bevy_core_pipeline::prepass::MotionVectorPrepass; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_render::{ @@ -10,26 +11,27 @@ use bevy_render::{ view::ViewVisibility, Extract, }; -use bevy_utils::EntityHashMap; use bytemuck::Pod; +use super::double_buffer::{DoubleBufferVec, DoubleEntityMap}; + #[derive(Component)] pub struct MorphIndex { pub(super) index: u32, } #[derive(Default, Resource, Deref, DerefMut)] -pub struct MorphIndices(EntityHashMap); +pub struct MorphIndices(DoubleEntityMap); #[derive(Resource)] pub struct MorphUniform { - pub buffer: BufferVec, + pub buffer: DoubleBufferVec, } impl Default for MorphUniform { fn default() -> Self { Self { - buffer: BufferVec::new(BufferUsages::UNIFORM), + buffer: DoubleBufferVec::new(BufferUsages::UNIFORM), } } } @@ -39,12 +41,12 @@ pub fn prepare_morphs( render_queue: Res, mut uniform: ResMut, ) { - if uniform.buffer.is_empty() { + if uniform.buffer.current.is_empty() { return; } - let len = uniform.buffer.len(); - uniform.buffer.reserve(len, &render_device); - uniform.buffer.write_buffer(&render_device, &render_queue); + let current = &mut uniform.buffer.current; + current.reserve(current.len(), &render_device); + current.write_buffer(&render_device, &render_queue); } const fn can_align(step: usize, target: usize) -> bool { @@ -83,19 +85,21 @@ pub fn extract_morphs( mut morph_indices: ResMut, mut uniform: ResMut, query: Extract>, + has_motion_vectors: Extract>>, ) { - morph_indices.clear(); - uniform.buffer.clear(); + let swap_buffer = !has_motion_vectors.is_empty(); + morph_indices.clear(swap_buffer); + uniform.buffer.clear(swap_buffer); for (entity, view_visibility, morph_weights) in &query { if !view_visibility.get() { continue; } - let start = uniform.buffer.len(); + let start = uniform.buffer.current.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.buffer.current.extend(legal_weights); + add_to_alignment::(&mut uniform.buffer.current); let index = (start * mem::size_of::()) as u32; morph_indices.insert(entity, MorphIndex { index }); diff --git a/crates/bevy_pbr/src/render/morph.wgsl b/crates/bevy_pbr/src/render/morph.wgsl index 7355f95f33e0c..ff1505801de41 100644 --- a/crates/bevy_pbr/src/render/morph.wgsl +++ b/crates/bevy_pbr/src/render/morph.wgsl @@ -9,11 +9,17 @@ @group(1) @binding(2) var morph_weights: MorphWeights; @group(1) @binding(3) var morph_targets: texture_3d; +#ifdef MOTION_VECTOR_PREPASS +@group(1) @binding(5) var previous_morph_weights: MorphWeights; +#endif #else @group(2) @binding(2) var morph_weights: MorphWeights; @group(2) @binding(3) var morph_targets: texture_3d; +#ifdef MOTION_VECTOR_PREPASS +@group(2) @binding(5) var previous_morph_weights: MorphWeights; +#endif #endif @@ -54,4 +60,11 @@ fn morph(vertex_index: u32, component_offset: u32, weight_index: u32) -> vec3 f32 { + let i = weight_index; + return previous_morph_weights.weights[i / 4u][i % 4u]; +} +#endif + #endif // MORPH_TARGETS diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index bfb12fd794427..64f9ae1ff35cd 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,17 +1,19 @@ use bevy_asset::Assets; +use bevy_core_pipeline::prepass::MotionVectorPrepass; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::prelude::*; use bevy_math::Mat4; use bevy_render::{ batching::NoAutomaticBatching, mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes}, - render_resource::{BufferUsages, BufferVec}, + render_resource::BufferUsages, renderer::{RenderDevice, RenderQueue}, view::ViewVisibility, Extract, }; use bevy_transform::prelude::GlobalTransform; -use bevy_utils::EntityHashMap; + +use super::double_buffer::{DoubleBufferVec, DoubleEntityMap}; /// Maximum number of joints supported for skinned meshes. pub const MAX_JOINTS: usize = 256; @@ -31,18 +33,18 @@ impl SkinIndex { } #[derive(Default, Resource, Deref, DerefMut)] -pub struct SkinIndices(EntityHashMap); +pub struct SkinIndices(DoubleEntityMap); // Notes on implementation: see comment on top of the `extract_skins` system. #[derive(Resource)] pub struct SkinUniform { - pub buffer: BufferVec, + pub buffer: DoubleBufferVec, } impl Default for SkinUniform { fn default() -> Self { Self { - buffer: BufferVec::new(BufferUsages::UNIFORM), + buffer: DoubleBufferVec::new(BufferUsages::UNIFORM), } } } @@ -52,13 +54,13 @@ pub fn prepare_skins( render_queue: Res, mut uniform: ResMut, ) { - if uniform.buffer.is_empty() { + if uniform.buffer.current.is_empty() { return; } - let len = uniform.buffer.len(); - uniform.buffer.reserve(len, &render_device); - uniform.buffer.write_buffer(&render_device, &render_queue); + let current = &mut uniform.buffer.current; + current.reserve(current.len(), &render_device); + current.write_buffer(&render_device, &render_queue); } // Notes on implementation: @@ -93,9 +95,11 @@ pub fn extract_skins( query: Extract>, inverse_bindposes: Extract>>, joints: Extract>, + has_motion_vectors: Extract>>, ) { - uniform.buffer.clear(); - skin_indices.clear(); + let swap_buffer = !has_motion_vectors.is_empty(); + uniform.buffer.clear(swap_buffer); + skin_indices.clear(swap_buffer); let mut last_start = 0; // PERF: This can be expensive, can we move this to prepare? @@ -103,7 +107,7 @@ pub fn extract_skins( if !view_visibility.get() { continue; } - let buffer = &mut uniform.buffer; + let buffer = &mut uniform.buffer.current; let Some(inverse_bindposes) = inverse_bindposes.get(&skin.inverse_bindposes) else { continue; }; @@ -130,12 +134,12 @@ pub fn extract_skins( buffer.push(Mat4::ZERO); } - skin_indices.insert(entity, SkinIndex::new(start)); + skin_indices.0.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.buffer.current.len() - last_start < MAX_JOINTS { + uniform.buffer.current.push(Mat4::ZERO); } } diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 3f23629d1d4df..6cf5439f6ed40 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -6,8 +6,14 @@ #ifdef MESH_BINDGROUP_1 @group(1) @binding(1) var joint_matrices: SkinnedMesh; + #ifdef MOTION_VECTOR_PREPASS + @group(1) @binding(4) var previous_joint_matrices: SkinnedMesh; + #endif // MOTION_VECTOR_PREPASS #else @group(2) @binding(1) var joint_matrices: SkinnedMesh; + #ifdef MOTION_VECTOR_PREPASS + @group(2) @binding(4) var previous_joint_matrices: SkinnedMesh; + #endif // MOTION_VECTOR_PREPASS #endif @@ -48,4 +54,15 @@ fn skin_normals( ); } +#ifdef MOTION_VECTOR_PREPASS +fn previous_skin_model( + indexes: vec4, + weights: vec4, +) -> mat4x4 { + return weights.x * previous_joint_matrices.data[indexes.x] + + weights.y * previous_joint_matrices.data[indexes.y] + + weights.z * previous_joint_matrices.data[indexes.z] + + weights.w * previous_joint_matrices.data[indexes.w]; +} +#endif // MOTION_VECTOR_PREPASS #endif diff --git a/examples/shader/shader_instancing.rs b/examples/shader/shader_instancing.rs index fc18bb63c73b7..64010634bb850 100644 --- a/examples/shader/shader_instancing.rs +++ b/examples/shader/shader_instancing.rs @@ -237,7 +237,7 @@ impl SpecializedMeshPipeline for CustomPipeline { type DrawCustom = ( SetItemPipeline, SetMeshViewBindGroup<0>, - SetMeshBindGroup<1>, + SetMeshBindGroup<1, false>, DrawMeshInstanced, ); From f01ce089a3bc387675a47df4c76f62a41dd044f4 Mon Sep 17 00:00:00 2001 From: Nicola Papale Date: Mon, 23 Oct 2023 16:46:32 +0200 Subject: [PATCH 2/2] Add some docs to MeshBindGroups --- crates/bevy_pbr/src/render/mesh_bindings.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index 99742cf455d6a..dd62c20cb7cad 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -173,12 +173,17 @@ impl<'a> MeshBindGroupBuilder<'a> { /// The [`BindGroup`]s for individual existing mesh shader variants. /// -/// Morph targets allow several different bind groups, because individual mesh -/// may have a different [`TextureView`] that represents the morph target's pose -/// vertex attribute values. +/// To add bind groups to it, you would first create a `MeshBindGroupBuilder` +/// using `MeshBindGroups::bind_group_builder`. Then, call `add_variant` for +/// each `BindGroup` that will be needed for rendering. /// -/// Non-morph target bind groups are optional. We don't know at compile time -/// whether motion vectors or skinned meshes will be used. +/// `MeshBindGroups` has two `HashMap`, one `shared` that contains bind groups that +/// can be shared between different entities with the same set of `ActiveVariant`. +/// +/// The other `HashMap` is `distinct`, which contain bind groups that are unique per-mesh. +/// This is currently only relevant for meshes with morph targets. (Morph targets have +/// a texture binding, therefore need to bind to a different individual texture for each +/// individual mesh). #[derive(Default, Resource)] pub struct MeshBindGroups { shared: HashMap,