Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move skin code to a separate module #9899

Merged
merged 5 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 10 additions & 138 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
};
use bevy_app::Plugin;
use bevy_asset::{load_internal_asset, AssetId, Assets, Handle};
use bevy_asset::{load_internal_asset, AssetId, Handle};
use bevy_core_pipeline::{
core_3d::{AlphaMask3d, Opaque3d, Transparent3d},
prepass::ViewPrepassTextures,
Expand All @@ -19,15 +19,11 @@ use bevy_ecs::{
query::{QueryItem, ROQueryItem},
system::{lifetimeless::*, SystemParamItem, SystemState},
};
use bevy_math::{Affine3, Mat4, Vec2, Vec4};
use bevy_math::{Affine3, Vec2, Vec4};
use bevy_render::{
batching::{
batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData,
NoAutomaticBatching,
},
batching::{batch_and_prepare_render_phase, write_batched_instance_buffer, GetBatchData},
globals::{GlobalsBuffer, GlobalsUniform},
mesh::{
skinning::{SkinnedMesh, SkinnedMeshInverseBindposes},
GpuBufferInfo, InnerMeshVertexBufferLayout, Mesh, MeshVertexBufferLayout,
VertexAttributeDescriptor,
},
Expand All @@ -48,17 +44,13 @@ use bevy_utils::{tracing::error, HashMap, Hashed};

use crate::render::{
morph::{extract_morphs, prepare_morphs, MorphIndex, MorphUniform},
skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform},
MeshLayouts,
};

#[derive(Default)]
pub struct MeshRenderPlugin;

/// Maximum number of joints supported for skinned meshes.
pub const MAX_JOINTS: usize = 256;
const JOINT_SIZE: usize = std::mem::size_of::<Mat4>();
pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE;

pub const MESH_VERTEX_OUTPUT: Handle<Shader> = Handle::weak_from_u128(2645551199423808407);
pub const MESH_VIEW_TYPES_HANDLE: Handle<Shader> = Handle::weak_from_u128(8140454348013264787);
pub const MESH_VIEW_BINDINGS_HANDLE: Handle<Shader> = Handle::weak_from_u128(9076678235888822571);
Expand Down Expand Up @@ -112,12 +104,12 @@ impl Plugin for MeshRenderPlugin {

if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<SkinnedMeshUniform>()
.init_resource::<MeshBindGroups>()
.init_resource::<SkinUniform>()
.init_resource::<MorphUniform>()
.add_systems(
ExtractSchedule,
(extract_meshes, extract_skinned_meshes, extract_morphs),
(extract_meshes, extract_skins, extract_morphs),
)
.add_systems(
Render,
Expand All @@ -131,7 +123,7 @@ impl Plugin for MeshRenderPlugin {
.in_set(RenderSet::PrepareResources),
write_batched_instance_buffer::<MeshPipeline>
.in_set(RenderSet::PrepareResourcesFlush),
prepare_skinned_meshes.in_set(RenderSet::PrepareResources),
prepare_skins.in_set(RenderSet::PrepareResources),
prepare_morphs.in_set(RenderSet::PrepareResources),
prepare_mesh_bind_group.in_set(RenderSet::PrepareBindGroups),
prepare_mesh_view_bind_groups.in_set(RenderSet::PrepareBindGroups),
Expand Down Expand Up @@ -270,91 +262,6 @@ pub fn extract_meshes(
commands.insert_or_spawn_batch(not_caster_commands);
}

#[derive(Component)]
pub struct SkinnedMeshJoints {
pub index: u32,
}

impl SkinnedMeshJoints {
#[inline]
pub fn build(
skin: &SkinnedMesh,
inverse_bindposes: &Assets<SkinnedMeshInverseBindposes>,
joints: &Query<&GlobalTransform>,
buffer: &mut BufferVec<Mat4>,
) -> Option<Self> {
let inverse_bindposes = inverse_bindposes.get(&skin.inverse_bindposes)?;
let start = buffer.len();
let target = start + skin.joints.len().min(MAX_JOINTS);
buffer.extend(
joints
.iter_many(&skin.joints)
.zip(inverse_bindposes.iter())
.take(MAX_JOINTS)
.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
while buffer.len() % 4 != 0 {
buffer.push(Mat4::ZERO);
}
Some(Self {
index: start as u32,
})
}

/// Updated index to be in address space based on [`SkinnedMeshUniform`] size.
pub fn to_buffer_index(mut self) -> Self {
self.index *= std::mem::size_of::<Mat4>() as u32;
self
}
}

pub fn extract_skinned_meshes(
mut commands: Commands,
mut previous_len: Local<usize>,
mut uniform: ResMut<SkinnedMeshUniform>,
query: Extract<Query<(Entity, &ViewVisibility, &SkinnedMesh)>>,
inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
joint_query: Extract<Query<&GlobalTransform>>,
) {
uniform.buffer.clear();
let mut values = Vec::with_capacity(*previous_len);
let mut last_start = 0;

for (entity, view_visibility, skin) in &query {
if !view_visibility.get() {
continue;
}
// PERF: This can be expensive, can we move this to prepare?
if let Some(skinned_joints) =
SkinnedMeshJoints::build(skin, &inverse_bindposes, &joint_query, &mut uniform.buffer)
{
last_start = last_start.max(skinned_joints.index as usize);
// NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per
// entity and so cannot currently be batched.
values.push((
entity,
(skinned_joints.to_buffer_index(), NoAutomaticBatching),
));
}
}

// 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);
}

*previous_len = values.len();
commands.insert_or_spawn_batch(values);
}

#[derive(Resource, Clone)]
pub struct MeshPipeline {
pub view_layout: BindGroupLayout,
Expand Down Expand Up @@ -1043,7 +950,7 @@ pub fn prepare_mesh_bind_group(
mesh_pipeline: Res<MeshPipeline>,
render_device: Res<RenderDevice>,
mesh_uniforms: Res<GpuArrayBuffer<MeshUniform>>,
skinned_mesh_uniform: Res<SkinnedMeshUniform>,
skins_uniform: Res<SkinUniform>,
weights_uniform: Res<MorphUniform>,
) {
groups.reset();
Expand All @@ -1053,7 +960,7 @@ pub fn prepare_mesh_bind_group(
};
groups.model_only = Some(layouts.model_only(&render_device, &model));

let skin = skinned_mesh_uniform.buffer.buffer();
let skin = skins_uniform.buffer.buffer();
if let Some(skin) = skin {
groups.skinned = Some(layouts.skinned(&render_device, &model, skin));
}
Expand All @@ -1072,41 +979,6 @@ pub fn prepare_mesh_bind_group(
}
}

// NOTE: This is using BufferVec because it is using a trick to allow a fixed-size array
// in a uniform buffer to be used like a variable-sized array by only writing the valid data
// into the buffer, knowing the number of valid items starting from the dynamic offset, and
// ignoring the rest, whether they're valid for other dynamic offsets or not. This trick may
// be supported later in encase, and then we should make use of it.

#[derive(Resource)]
pub struct SkinnedMeshUniform {
pub buffer: BufferVec<Mat4>,
}

impl Default for SkinnedMeshUniform {
fn default() -> Self {
Self {
buffer: BufferVec::new(BufferUsages::UNIFORM),
}
}
}

pub fn prepare_skinned_meshes(
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut skinned_mesh_uniform: ResMut<SkinnedMeshUniform>,
) {
if skinned_mesh_uniform.buffer.is_empty() {
return;
}

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);
}

#[derive(Component)]
pub struct MeshViewBindGroup {
pub value: BindGroup,
Expand Down Expand Up @@ -1308,7 +1180,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshBindGroup<I> {
type ViewWorldQuery = ();
type ItemWorldQuery = (
Read<Handle<Mesh>>,
Option<Read<SkinnedMeshJoints>>,
Option<Read<SkinIndex>>,
Option<Read<MorphIndex>>,
);

Expand Down
12 changes: 8 additions & 4 deletions crates/bevy_pbr/src/render/mesh_bindings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Bind group layout related definitions for the mesh pipeline.

use bevy_math::Mat4;
use bevy_render::{
mesh::morph::MAX_MORPH_WEIGHTS,
render_resource::{
Expand All @@ -9,13 +10,17 @@ use bevy_render::{
renderer::RenderDevice,
};

use crate::render::skin::MAX_JOINTS;

const MORPH_WEIGHT_SIZE: usize = std::mem::size_of::<f32>();
pub const MORPH_BUFFER_SIZE: usize = MAX_MORPH_WEIGHTS * MORPH_WEIGHT_SIZE;

const JOINT_SIZE: usize = std::mem::size_of::<Mat4>();
pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE;

/// Individual layout entries.
mod layout_entry {
use super::MORPH_BUFFER_SIZE;
use crate::render::mesh::JOINT_BUFFER_SIZE;
use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE};
use crate::MeshUniform;
use bevy_render::{
render_resource::{
Expand Down Expand Up @@ -66,8 +71,7 @@ mod layout_entry {
/// Individual [`BindGroupEntry`](bevy_render::render_resource::BindGroupEntry)
/// for bind groups.
mod entry {
use super::MORPH_BUFFER_SIZE;
use crate::render::mesh::JOINT_BUFFER_SIZE;
use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE};
use bevy_render::render_resource::{
BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, TextureView,
};
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_pbr/src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ mod light;
pub(crate) mod mesh;
mod mesh_bindings;
mod morph;
mod skin;

pub use fog::*;
pub use light::*;
pub use mesh::*;
pub use mesh_bindings::MeshLayouts;
pub use skin::{extract_skins, prepare_skins, SkinIndex, SkinUniform, MAX_JOINTS};
12 changes: 7 additions & 5 deletions crates/bevy_pbr/src/render/morph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,16 @@ impl Default for MorphUniform {
}

pub fn prepare_morphs(
device: Res<RenderDevice>,
queue: Res<RenderQueue>,
render_device: Res<RenderDevice>,
render_queue: Res<RenderQueue>,
mut uniform: ResMut<MorphUniform>,
) {
if uniform.buffer.is_empty() {
return;
}
let buffer = &mut uniform.buffer;
buffer.reserve(buffer.len(), &device);
buffer.write_buffer(&device, &queue);
let len = uniform.buffer.len();
uniform.buffer.reserve(len, &render_device);
uniform.buffer.write_buffer(&render_device, &render_queue);
}

const fn can_align(step: usize, target: usize) -> bool {
Expand Down Expand Up @@ -69,6 +69,8 @@ fn add_to_alignment<T: Pod + Default>(buffer: &mut BufferVec<T>) {
buffer.extend(iter::repeat_with(T::default).take(ts_to_add));
}

// 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 commands: Commands,
mut previous_len: Local<usize>,
Expand Down
Loading