1use core::mem::{self, size_of};
23use bevy_asset::{prelude::AssetChanged, Assets};
4use bevy_camera::visibility::ViewVisibility;
5use bevy_ecs::prelude::*;
6use bevy_math::Mat4;
7use bevy_mesh::skinning::{SkinnedMesh, SkinnedMeshInverseBindposes};
8use bevy_render::render_resource::{Buffer, BufferDescriptor};
9use bevy_render::settings::WgpuLimits;
10use bevy_render::sync_world::{MainEntity, MainEntityHashMap};
11use bevy_render::{
12batching::NoAutomaticBatching,
13render_resource::BufferUsages,
14 renderer::{RenderDevice, RenderQueue},
15Extract,
16};
17use bevy_transform::prelude::GlobalTransform;
18use offset_allocator::{Allocation, Allocator};
19use tracing::error;
2021/// Maximum number of joints supported for skinned meshes.
22///
23/// It is used to allocate buffers.
24/// The correctness of the value depends on the GPU/platform.
25/// The current value is chosen because it is guaranteed to work everywhere.
26/// To allow for bigger values, a check must be made for the limits
27/// of the GPU at runtime, which would mean not using consts anymore.
28pub const MAX_JOINTS: usize = 256;
2930/// The total number of joints we support.
31///
32/// This is 256 GiB worth of joint matrices, which we will never hit under any
33/// reasonable circumstances.
34const MAX_TOTAL_JOINTS: u32 = 1024 * 1024 * 1024;
3536/// The number of joints that we allocate at a time.
37///
38/// Some hardware requires that uniforms be allocated on 256-byte boundaries, so
39/// we need to allocate 4 64-byte matrices at a time to satisfy alignment
40/// requirements.
41const JOINTS_PER_ALLOCATION_UNIT: u32 = (256 / size_of::<Mat4>()) as u32;
4243/// The location of the first joint matrix in the skin uniform buffer.
44#[derive(#[automatically_derived]
impl ::core::clone::Clone for SkinByteOffset {
#[inline]
fn clone(&self) -> SkinByteOffset {
let _: ::core::clone::AssertParamIsClone<u32>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SkinByteOffset { }Copy)]
45pub struct SkinByteOffset {
46/// The byte offset of the first joint matrix.
47pub byte_offset: u32,
48}
4950impl SkinByteOffset {
51/// Index to be in address space based on the size of a skin uniform.
52const fn from_index(index: usize) -> Self {
53SkinByteOffset {
54 byte_offset: (index * size_of::<Mat4>()) as u32,
55 }
56 }
5758/// Returns this skin index in elements (not bytes).
59 ///
60 /// Each element is a 4x4 matrix.
61pub fn index(&self) -> u32 {
62self.byte_offset / size_of::<Mat4>() as u3263 }
64}
6566/// The GPU buffers containing joint matrices for all skinned meshes.
67///
68/// This is double-buffered: we store the joint matrices of each mesh for the
69/// previous frame in addition to those of each mesh for the current frame. This
70/// is for motion vector calculation. Every frame, we swap buffers and overwrite
71/// the joint matrix buffer from two frames ago with the data for the current
72/// frame.
73///
74/// Notes on implementation: see comment on top of the `extract_skins` system.
75#[derive(impl bevy_ecs::resource::Resource for SkinUniforms where
Self: ::core::marker::Send + ::core::marker::Sync + 'static {}Resource)]
76pub struct SkinUniforms {
77/// The CPU-side buffer that stores the joint matrices for skinned meshes in
78 /// the current frame.
79pub current_staging_buffer: Vec<Mat4>,
80/// The GPU-side buffer that stores the joint matrices for skinned meshes in
81 /// the current frame.
82pub current_buffer: Buffer,
83/// The GPU-side buffer that stores the joint matrices for skinned meshes in
84 /// the previous frame.
85pub prev_buffer: Buffer,
86/// The offset allocator that manages the placement of the joints within the
87 /// [`Self::current_buffer`].
88allocator: Allocator,
89/// Allocation information that we keep about each skin.
90skin_uniform_info: MainEntityHashMap<SkinUniformInfo>,
91/// The total number of joints in the scene.
92 ///
93 /// We use this as part of our heuristic to decide whether to use
94 /// fine-grained change detection.
95total_joints: usize,
96}
9798pub fn skin_uniforms_from_world(device: Res<RenderDevice>, mut commands: Commands) {
99let buffer_usages = (if skins_use_uniform_buffers(&device.limits()) {
100BufferUsages::UNIFORM
101 } else {
102BufferUsages::STORAGE
103 }) | BufferUsages::COPY_DST;
104105// Create the current and previous buffer with the minimum sizes.
106 //
107 // These will be swapped every frame.
108let current_buffer = device.create_buffer(&BufferDescriptor {
109 label: Some("skin uniform buffer"),
110 size: MAX_JOINTSas u64 * size_of::<Mat4>() as u64,
111 usage: buffer_usages,
112 mapped_at_creation: false,
113 });
114let prev_buffer = device.create_buffer(&BufferDescriptor {
115 label: Some("skin uniform buffer"),
116 size: MAX_JOINTSas u64 * size_of::<Mat4>() as u64,
117 usage: buffer_usages,
118 mapped_at_creation: false,
119 });
120121let res = SkinUniforms {
122 current_staging_buffer: ::alloc::vec::Vec::new()vec![],
123current_buffer,
124prev_buffer,
125 allocator: Allocator::new(MAX_TOTAL_JOINTS),
126 skin_uniform_info: MainEntityHashMap::default(),
127 total_joints: 0,
128 };
129130commands.insert_resource(res);
131}
132133impl SkinUniforms {
134/// Returns the current offset in joints of the skin in the buffer.
135pub fn skin_index(&self, skin: MainEntity) -> Option<u32> {
136self.skin_uniform_info
137 .get(&skin)
138 .map(SkinUniformInfo::offset)
139 }
140141/// Returns the current offset in bytes of the skin in the buffer.
142pub fn skin_byte_offset(&self, skin: MainEntity) -> Option<SkinByteOffset> {
143self.skin_uniform_info.get(&skin).map(|skin_uniform_info| {
144SkinByteOffset::from_index(skin_uniform_info.offset() as usize)
145 })
146 }
147148/// Returns an iterator over all skins in the scene.
149pub fn all_skins(&self) -> impl Iterator<Item = &MainEntity> {
150self.skin_uniform_info.keys()
151 }
152}
153154/// Allocation information about each skin.
155struct SkinUniformInfo {
156/// The allocation of the joints within the [`SkinUniforms::current_buffer`].
157allocation: Allocation,
158/// The entities that comprise the joints.
159joints: Vec<MainEntity>,
160}
161162impl SkinUniformInfo {
163/// The offset in joints within the [`SkinUniforms::current_staging_buffer`].
164fn offset(&self) -> u32 {
165self.allocation.offset * JOINTS_PER_ALLOCATION_UNIT166 }
167}
168169/// Returns true if skinning must use uniforms (and dynamic offsets) because
170/// storage buffers aren't supported on the current platform.
171pub fn skins_use_uniform_buffers(limits: &WgpuLimits) -> bool {
172 bevy_render::storage_buffers_are_unsupported(limits)
173}
174175/// Uploads the buffers containing the joints to the GPU.
176pub fn prepare_skins(
177 render_device: Res<RenderDevice>,
178 render_queue: Res<RenderQueue>,
179 uniform: ResMut<SkinUniforms>,
180) {
181let uniform = uniform.into_inner();
182183if uniform.current_staging_buffer.is_empty() {
184return;
185 }
186187// Swap current and previous buffers.
188mem::swap(&mut uniform.current_buffer, &mut uniform.prev_buffer);
189190// Resize the buffers if necessary. Include extra space equal to `MAX_JOINTS`
191 // because we need to be able to bind a full uniform buffer's worth of data
192 // if skins use uniform buffers on this platform.
193let needed_size = (uniform.current_staging_buffer.len() as u64 + MAX_JOINTSas u64)
194 * size_of::<Mat4>() as u64;
195if uniform.current_buffer.size() < needed_size {
196let mut new_size = uniform.current_buffer.size();
197while new_size < needed_size {
198// 1.5× growth factor.
199new_size = (new_size + new_size / 2).next_multiple_of(4);
200 }
201202// Create the new buffers.
203let buffer_usages = if skins_use_uniform_buffers(&render_device.limits()) {
204BufferUsages::UNIFORM
205 } else {
206BufferUsages::STORAGE
207 } | BufferUsages::COPY_DST;
208uniform.current_buffer = render_device.create_buffer(&BufferDescriptor {
209 label: Some("skin uniform buffer"),
210 usage: buffer_usages,
211 size: new_size,
212 mapped_at_creation: false,
213 });
214uniform.prev_buffer = render_device.create_buffer(&BufferDescriptor {
215 label: Some("skin uniform buffer"),
216 usage: buffer_usages,
217 size: new_size,
218 mapped_at_creation: false,
219 });
220221// We've created a new `prev_buffer` but we don't have the previous joint
222 // data needed to fill it out correctly. Use the current joint data
223 // instead.
224 //
225 // TODO: This is a bug - will cause motion blur to ignore joint movement
226 // for one frame.
227render_queue.write_buffer(
228&uniform.prev_buffer,
2290,
230 bytemuck::must_cast_slice(&uniform.current_staging_buffer[..]),
231 );
232 }
233234// Write the data from `uniform.current_staging_buffer` into
235 // `uniform.current_buffer`.
236render_queue.write_buffer(
237&uniform.current_buffer,
2380,
239 bytemuck::must_cast_slice(&uniform.current_staging_buffer[..]),
240 );
241242// We don't need to write `uniform.prev_buffer` because we already wrote it
243 // last frame, and the data should still be on the GPU.
244}
245246// Notes on implementation:
247// We define the uniform binding as an array<mat4x4<f32>, N> in the shader,
248// where N is the maximum number of Mat4s we can fit in the uniform binding,
249// which may be as little as 16kB or 64kB. But, we may not need all N.
250// We may only need, for example, 10.
251//
252// If we used uniform buffers ‘normally’ then we would have to write a full
253// binding of data for each dynamic offset binding, which is wasteful, makes
254// the buffer much larger than it needs to be, and uses more memory bandwidth
255// to transfer the data, which then costs frame time So @superdump came up
256// with this design: just bind data at the specified offset and interpret
257// the data at that offset as an array<T, N> regardless of what is there.
258//
259// So instead of writing N Mat4s when you only need 10, you write 10, and
260// then pad up to the next dynamic offset alignment. Then write the next.
261// And for the last dynamic offset binding, make sure there is a full binding
262// of data after it so that the buffer is of size
263// `last dynamic offset` + `array<mat4x4<f32>>`.
264//
265// Then when binding the first dynamic offset, the first 10 entries in the array
266// are what you expect, but if you read the 11th you’re reading ‘invalid’ data
267// which could be padding or could be from the next binding.
268//
269// In this way, we can pack ‘variable sized arrays’ into uniform buffer bindings
270// which normally only support fixed size arrays. You just have to make sure
271// in the shader that you only read the values that are valid for that binding.
272pub fn extract_skins(
273 skin_uniforms: ResMut<SkinUniforms>,
274 skinned_meshes: Extract<Query<(Entity, &SkinnedMesh)>>,
275 changed_skinned_meshes: Extract<
276Query<
277 (Entity, &ViewVisibility, &SkinnedMesh),
278Or<(
279Changed<ViewVisibility>,
280Changed<SkinnedMesh>,
281AssetChanged<SkinnedMesh>,
282 )>,
283 >,
284 >,
285 skinned_mesh_inverse_bindposes: Extract<Res<Assets<SkinnedMeshInverseBindposes>>>,
286 changed_transforms: Extract<Query<(Entity, &GlobalTransform), Changed<GlobalTransform>>>,
287 joints: Extract<Query<&GlobalTransform>>,
288mut removed_skinned_meshes_query: Extract<RemovedComponents<SkinnedMesh>>,
289) {
290let skin_uniforms = skin_uniforms.into_inner();
291292// Find skins that have become visible or invisible on this frame. Allocate,
293 // reallocate, or free space for them as necessary.
294add_or_delete_skins(
295skin_uniforms,
296&changed_skinned_meshes,
297&skinned_mesh_inverse_bindposes,
298&joints,
299 );
300301// Extract the transforms for all joints from the scene, and write them into
302 // the staging buffer at the appropriate spot.
303for (skin_entity, skin) in &skinned_meshes {
304 extract_joints_for_skin(
305 skin_entity.into(),
306 skin,
307 skin_uniforms,
308&changed_skinned_meshes,
309&skinned_mesh_inverse_bindposes,
310&changed_transforms,
311 );
312 }
313314// Delete skins that became invisible.
315for skinned_mesh_entity in removed_skinned_meshes_query.read() {
316// Only remove a skin if we didn't pick it up in `add_or_delete_skins`.
317 // It's possible that a necessary component was removed and re-added in
318 // the same frame.
319if !changed_skinned_meshes.contains(skinned_mesh_entity) {
320 remove_skin(skin_uniforms, skinned_mesh_entity.into());
321 }
322 }
323}
324325/// Searches for all skins that have become visible or invisible this frame and
326/// allocations for them as necessary.
327fn add_or_delete_skins(
328 skin_uniforms: &mut SkinUniforms,
329 changed_skinned_meshes: &Query<
330 (Entity, &ViewVisibility, &SkinnedMesh),
331Or<(
332Changed<ViewVisibility>,
333Changed<SkinnedMesh>,
334AssetChanged<SkinnedMesh>,
335 )>,
336 >,
337 skinned_mesh_inverse_bindposes: &Assets<SkinnedMeshInverseBindposes>,
338 joints: &Query<&GlobalTransform>,
339) {
340// Find every skinned mesh that changed one of (1) visibility; (2) joint
341 // entities (part of `SkinnedMesh`); (3) the associated
342 // `SkinnedMeshInverseBindposes` asset.
343for (skinned_mesh_entity, skinned_mesh_view_visibility, skinned_mesh) in changed_skinned_meshes
344 {
345// Remove the skin if it existed last frame.
346let skinned_mesh_entity = MainEntity::from(skinned_mesh_entity);
347 remove_skin(skin_uniforms, skinned_mesh_entity);
348349// If the skin is invisible, we're done.
350if !(*skinned_mesh_view_visibility).get() {
351continue;
352 }
353354// Initialize the skin.
355add_skin(
356 skinned_mesh_entity,
357 skinned_mesh,
358 skin_uniforms,
359 skinned_mesh_inverse_bindposes,
360 joints,
361 );
362 }
363}
364365/// Extracts all joints for a single skin and writes their transforms into the
366/// CPU staging buffer.
367fn extract_joints_for_skin(
368 skin_entity: MainEntity,
369 skin: &SkinnedMesh,
370 skin_uniforms: &mut SkinUniforms,
371 changed_skinned_meshes: &Query<
372 (Entity, &ViewVisibility, &SkinnedMesh),
373Or<(
374Changed<ViewVisibility>,
375Changed<SkinnedMesh>,
376AssetChanged<SkinnedMesh>,
377 )>,
378 >,
379 skinned_mesh_inverse_bindposes: &Assets<SkinnedMeshInverseBindposes>,
380 changed_transforms: &Query<(Entity, &GlobalTransform), Changed<GlobalTransform>>,
381) {
382// If we initialized the skin this frame, we already populated all
383 // the joints, so there's no need to populate them again.
384if changed_skinned_meshes.contains(*skin_entity) {
385return;
386 }
387388// Fetch information about the skin.
389let Some(skin_uniform_info) = skin_uniforms.skin_uniform_info.get(&skin_entity) else {
390return;
391 };
392let Some(skinned_mesh_inverse_bindposes) =
393skinned_mesh_inverse_bindposes.get(&skin.inverse_bindposes)
394else {
395return;
396 };
397398// Calculate and write in the new joint matrices, if they changed this frame.
399for (joint_index, (&joint, skinned_mesh_inverse_bindpose)) in skin
400 .joints
401 .iter()
402 .zip(skinned_mesh_inverse_bindposes.iter())
403 .enumerate()
404 {
405// Skip if the global transform for this joint didn't change.
406let Ok((_, joint_transform)) = changed_transforms.get(joint) else {
407continue;
408 };
409410let joint_matrix = joint_transform.affine() * *skinned_mesh_inverse_bindpose;
411 skin_uniforms.current_staging_buffer[skin_uniform_info.offset() as usize + joint_index] =
412 joint_matrix;
413 }
414}
415416/// Allocates space for a new skin in the buffers, and populates its joints.
417fn add_skin(
418 skinned_mesh_entity: MainEntity,
419 skinned_mesh: &SkinnedMesh,
420 skin_uniforms: &mut SkinUniforms,
421 skinned_mesh_inverse_bindposes: &Assets<SkinnedMeshInverseBindposes>,
422 joints: &Query<&GlobalTransform>,
423) {
424// Allocate space for the joints.
425let Some(allocation) = skin_uniforms.allocator.allocate(
426skinned_mesh427 .joints
428 .len()
429 .div_ceil(JOINTS_PER_ALLOCATION_UNITas usize) as u32,
430 ) else {
431{
use ::tracing::__macro_support::Callsite as _;
static __CALLSITE: ::tracing::callsite::DefaultCallsite =
{
static META: ::tracing::Metadata<'static> =
{
::tracing_core::metadata::Metadata::new("event src/render/skin.rs:431",
"bevy_pbr::render::skin", ::tracing::Level::ERROR,
::tracing_core::__macro_support::Option::Some("src/render/skin.rs"),
::tracing_core::__macro_support::Option::Some(431u32),
::tracing_core::__macro_support::Option::Some("bevy_pbr::render::skin"),
::tracing_core::field::FieldSet::new(&["message"],
::tracing_core::callsite::Identifier(&__CALLSITE)),
::tracing::metadata::Kind::EVENT)
};
::tracing::callsite::DefaultCallsite::new(&META)
};
let enabled =
::tracing::Level::ERROR <= ::tracing::level_filters::STATIC_MAX_LEVEL
&&
::tracing::Level::ERROR <=
::tracing::level_filters::LevelFilter::current() &&
{
let interest = __CALLSITE.interest();
!interest.is_never() &&
::tracing::__macro_support::__is_enabled(__CALLSITE.metadata(),
interest)
};
if enabled {
(|value_set: ::tracing::field::ValueSet|
{
let meta = __CALLSITE.metadata();
::tracing::Event::dispatch(meta, &value_set);
;
})({
#[allow(unused_imports)]
use ::tracing::field::{debug, display, Value};
__CALLSITE.metadata().fields().value_set_all(&[(::tracing::__macro_support::Option::Some(&format_args!("Out of space for skin: {0:?}. Tried to allocate space for {1:?} joints.",
skinned_mesh_entity, skinned_mesh.joints.len()) as
&dyn ::tracing::field::Value))])
});
} else { ; }
};error!(
432"Out of space for skin: {:?}. Tried to allocate space for {:?} joints.",
433 skinned_mesh_entity,
434 skinned_mesh.joints.len()
435 );
436return;
437 };
438439// Store that allocation.
440let skin_uniform_info = SkinUniformInfo {
441allocation,
442 joints: skinned_mesh443 .joints
444 .iter()
445 .map(|entity| MainEntity::from(*entity))
446 .collect(),
447 };
448449let skinned_mesh_inverse_bindposes =
450skinned_mesh_inverse_bindposes.get(&skinned_mesh.inverse_bindposes);
451452for (joint_index, &joint) in skinned_mesh.joints.iter().enumerate() {
453// Calculate the initial joint matrix.
454let skinned_mesh_inverse_bindpose =
455 skinned_mesh_inverse_bindposes.and_then(|skinned_mesh_inverse_bindposes| {
456 skinned_mesh_inverse_bindposes.get(joint_index)
457 });
458let joint_matrix = match (skinned_mesh_inverse_bindpose, joints.get(joint)) {
459 (Some(skinned_mesh_inverse_bindpose), Ok(transform)) => {
460 transform.affine() * *skinned_mesh_inverse_bindpose
461 }
462_ => Mat4::IDENTITY,
463 };
464465// Write in the new joint matrix, growing the staging buffer if
466 // necessary.
467let buffer_index = skin_uniform_info.offset() as usize + joint_index;
468if skin_uniforms.current_staging_buffer.len() < buffer_index + 1 {
469 skin_uniforms
470 .current_staging_buffer
471 .resize(buffer_index + 1, Mat4::IDENTITY);
472 }
473 skin_uniforms.current_staging_buffer[buffer_index] = joint_matrix;
474 }
475476// Record the number of joints.
477skin_uniforms.total_joints += skinned_mesh.joints.len();
478479skin_uniforms480 .skin_uniform_info
481 .insert(skinned_mesh_entity, skin_uniform_info);
482}
483484/// Deallocates a skin and removes it from the [`SkinUniforms`].
485fn remove_skin(skin_uniforms: &mut SkinUniforms, skinned_mesh_entity: MainEntity) {
486let Some(old_skin_uniform_info) = skin_uniforms.skin_uniform_info.remove(&skinned_mesh_entity)
487else {
488return;
489 };
490491// Free the allocation.
492skin_uniforms493 .allocator
494 .free(old_skin_uniform_info.allocation);
495496// Update the total number of joints.
497skin_uniforms.total_joints -= old_skin_uniform_info.joints.len();
498}
499500// NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per
501// entity and so cannot currently be batched on WebGL 2.
502pub fn no_automatic_skin_batching(
503mut commands: Commands,
504 query: Query<Entity, (With<SkinnedMesh>, Without<NoAutomaticBatching>)>,
505 render_device: Res<RenderDevice>,
506) {
507if !skins_use_uniform_buffers(&render_device.limits()) {
508return;
509 }
510511for entity in &query {
512 commands.entity(entity).try_insert(NoAutomaticBatching);
513 }
514}