Skip to main content

bevy_pbr/render/
skin.rs

1use core::mem::{self, size_of};
2
3use 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::{
12    batching::NoAutomaticBatching,
13    render_resource::BufferUsages,
14    renderer::{RenderDevice, RenderQueue},
15    Extract,
16};
17use bevy_transform::prelude::GlobalTransform;
18use offset_allocator::{Allocation, Allocator};
19use tracing::error;
20
21/// 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;
29
30/// 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;
35
36/// 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;
42
43/// The location of the first joint matrix in the skin uniform buffer.
44#[derive(Clone, Copy)]
45pub struct SkinByteOffset {
46    /// The byte offset of the first joint matrix.
47    pub byte_offset: u32,
48}
49
50impl SkinByteOffset {
51    /// Index to be in address space based on the size of a skin uniform.
52    const fn from_index(index: usize) -> Self {
53        SkinByteOffset {
54            byte_offset: (index * size_of::<Mat4>()) as u32,
55        }
56    }
57
58    /// Returns this skin index in elements (not bytes).
59    ///
60    /// Each element is a 4x4 matrix.
61    pub fn index(&self) -> u32 {
62        self.byte_offset / size_of::<Mat4>() as u32
63    }
64}
65
66/// 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(Resource)]
76pub struct SkinUniforms {
77    /// The CPU-side buffer that stores the joint matrices for skinned meshes in
78    /// the current frame.
79    pub current_staging_buffer: Vec<Mat4>,
80    /// The GPU-side buffer that stores the joint matrices for skinned meshes in
81    /// the current frame.
82    pub current_buffer: Buffer,
83    /// The GPU-side buffer that stores the joint matrices for skinned meshes in
84    /// the previous frame.
85    pub prev_buffer: Buffer,
86    /// The offset allocator that manages the placement of the joints within the
87    /// [`Self::current_buffer`].
88    allocator: Allocator,
89    /// Allocation information that we keep about each skin.
90    skin_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.
95    total_joints: usize,
96}
97
98pub fn skin_uniforms_from_world(device: Res<RenderDevice>, mut commands: Commands) {
99    let buffer_usages = (if skins_use_uniform_buffers(&device.limits()) {
100        BufferUsages::UNIFORM
101    } else {
102        BufferUsages::STORAGE
103    }) | BufferUsages::COPY_DST;
104
105    // Create the current and previous buffer with the minimum sizes.
106    //
107    // These will be swapped every frame.
108    let current_buffer = device.create_buffer(&BufferDescriptor {
109        label: Some("skin uniform buffer"),
110        size: MAX_JOINTS as u64 * size_of::<Mat4>() as u64,
111        usage: buffer_usages,
112        mapped_at_creation: false,
113    });
114    let prev_buffer = device.create_buffer(&BufferDescriptor {
115        label: Some("skin uniform buffer"),
116        size: MAX_JOINTS as u64 * size_of::<Mat4>() as u64,
117        usage: buffer_usages,
118        mapped_at_creation: false,
119    });
120
121    let res = SkinUniforms {
122        current_staging_buffer: vec![],
123        current_buffer,
124        prev_buffer,
125        allocator: Allocator::new(MAX_TOTAL_JOINTS),
126        skin_uniform_info: MainEntityHashMap::default(),
127        total_joints: 0,
128    };
129
130    commands.insert_resource(res);
131}
132
133impl SkinUniforms {
134    /// Returns the current offset in joints of the skin in the buffer.
135    pub fn skin_index(&self, skin: MainEntity) -> Option<u32> {
136        self.skin_uniform_info
137            .get(&skin)
138            .map(SkinUniformInfo::offset)
139    }
140
141    /// Returns the current offset in bytes of the skin in the buffer.
142    pub fn skin_byte_offset(&self, skin: MainEntity) -> Option<SkinByteOffset> {
143        self.skin_uniform_info.get(&skin).map(|skin_uniform_info| {
144            SkinByteOffset::from_index(skin_uniform_info.offset() as usize)
145        })
146    }
147
148    /// Returns an iterator over all skins in the scene.
149    pub fn all_skins(&self) -> impl Iterator<Item = &MainEntity> {
150        self.skin_uniform_info.keys()
151    }
152}
153
154/// Allocation information about each skin.
155struct SkinUniformInfo {
156    /// The allocation of the joints within the [`SkinUniforms::current_buffer`].
157    allocation: Allocation,
158    /// The entities that comprise the joints.
159    joints: Vec<MainEntity>,
160}
161
162impl SkinUniformInfo {
163    /// The offset in joints within the [`SkinUniforms::current_staging_buffer`].
164    fn offset(&self) -> u32 {
165        self.allocation.offset * JOINTS_PER_ALLOCATION_UNIT
166    }
167}
168
169/// 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}
174
175/// 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) {
181    let uniform = uniform.into_inner();
182
183    if uniform.current_staging_buffer.is_empty() {
184        return;
185    }
186
187    // Swap current and previous buffers.
188    mem::swap(&mut uniform.current_buffer, &mut uniform.prev_buffer);
189
190    // 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.
193    let needed_size = (uniform.current_staging_buffer.len() as u64 + MAX_JOINTS as u64)
194        * size_of::<Mat4>() as u64;
195    if uniform.current_buffer.size() < needed_size {
196        let mut new_size = uniform.current_buffer.size();
197        while new_size < needed_size {
198            // 1.5× growth factor.
199            new_size = (new_size + new_size / 2).next_multiple_of(4);
200        }
201
202        // Create the new buffers.
203        let buffer_usages = if skins_use_uniform_buffers(&render_device.limits()) {
204            BufferUsages::UNIFORM
205        } else {
206            BufferUsages::STORAGE
207        } | BufferUsages::COPY_DST;
208        uniform.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        });
214        uniform.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        });
220
221        // 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.
227        render_queue.write_buffer(
228            &uniform.prev_buffer,
229            0,
230            bytemuck::must_cast_slice(&uniform.current_staging_buffer[..]),
231        );
232    }
233
234    // Write the data from `uniform.current_staging_buffer` into
235    // `uniform.current_buffer`.
236    render_queue.write_buffer(
237        &uniform.current_buffer,
238        0,
239        bytemuck::must_cast_slice(&uniform.current_staging_buffer[..]),
240    );
241
242    // 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}
245
246// 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<
276        Query<
277            (Entity, &ViewVisibility, &SkinnedMesh),
278            Or<(
279                Changed<ViewVisibility>,
280                Changed<SkinnedMesh>,
281                AssetChanged<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>>,
288    mut removed_skinned_meshes_query: Extract<RemovedComponents<SkinnedMesh>>,
289) {
290    let skin_uniforms = skin_uniforms.into_inner();
291
292    // Find skins that have become visible or invisible on this frame. Allocate,
293    // reallocate, or free space for them as necessary.
294    add_or_delete_skins(
295        skin_uniforms,
296        &changed_skinned_meshes,
297        &skinned_mesh_inverse_bindposes,
298        &joints,
299    );
300
301    // Extract the transforms for all joints from the scene, and write them into
302    // the staging buffer at the appropriate spot.
303    for (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    }
313
314    // Delete skins that became invisible.
315    for 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.
319        if !changed_skinned_meshes.contains(skinned_mesh_entity) {
320            remove_skin(skin_uniforms, skinned_mesh_entity.into());
321        }
322    }
323}
324
325/// 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),
331        Or<(
332            Changed<ViewVisibility>,
333            Changed<SkinnedMesh>,
334            AssetChanged<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.
343    for (skinned_mesh_entity, skinned_mesh_view_visibility, skinned_mesh) in changed_skinned_meshes
344    {
345        // Remove the skin if it existed last frame.
346        let skinned_mesh_entity = MainEntity::from(skinned_mesh_entity);
347        remove_skin(skin_uniforms, skinned_mesh_entity);
348
349        // If the skin is invisible, we're done.
350        if !(*skinned_mesh_view_visibility).get() {
351            continue;
352        }
353
354        // Initialize the skin.
355        add_skin(
356            skinned_mesh_entity,
357            skinned_mesh,
358            skin_uniforms,
359            skinned_mesh_inverse_bindposes,
360            joints,
361        );
362    }
363}
364
365/// 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),
373        Or<(
374            Changed<ViewVisibility>,
375            Changed<SkinnedMesh>,
376            AssetChanged<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.
384    if changed_skinned_meshes.contains(*skin_entity) {
385        return;
386    }
387
388    // Fetch information about the skin.
389    let Some(skin_uniform_info) = skin_uniforms.skin_uniform_info.get(&skin_entity) else {
390        return;
391    };
392    let Some(skinned_mesh_inverse_bindposes) =
393        skinned_mesh_inverse_bindposes.get(&skin.inverse_bindposes)
394    else {
395        return;
396    };
397
398    // Calculate and write in the new joint matrices, if they changed this frame.
399    for (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.
406        let Ok((_, joint_transform)) = changed_transforms.get(joint) else {
407            continue;
408        };
409
410        let 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}
415
416/// 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.
425    let Some(allocation) = skin_uniforms.allocator.allocate(
426        skinned_mesh
427            .joints
428            .len()
429            .div_ceil(JOINTS_PER_ALLOCATION_UNIT as usize) as u32,
430    ) else {
431        error!(
432            "Out of space for skin: {:?}. Tried to allocate space for {:?} joints.",
433            skinned_mesh_entity,
434            skinned_mesh.joints.len()
435        );
436        return;
437    };
438
439    // Store that allocation.
440    let skin_uniform_info = SkinUniformInfo {
441        allocation,
442        joints: skinned_mesh
443            .joints
444            .iter()
445            .map(|entity| MainEntity::from(*entity))
446            .collect(),
447    };
448
449    let skinned_mesh_inverse_bindposes =
450        skinned_mesh_inverse_bindposes.get(&skinned_mesh.inverse_bindposes);
451
452    for (joint_index, &joint) in skinned_mesh.joints.iter().enumerate() {
453        // Calculate the initial joint matrix.
454        let skinned_mesh_inverse_bindpose =
455            skinned_mesh_inverse_bindposes.and_then(|skinned_mesh_inverse_bindposes| {
456                skinned_mesh_inverse_bindposes.get(joint_index)
457            });
458        let 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        };
464
465        // Write in the new joint matrix, growing the staging buffer if
466        // necessary.
467        let buffer_index = skin_uniform_info.offset() as usize + joint_index;
468        if 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    }
475
476    // Record the number of joints.
477    skin_uniforms.total_joints += skinned_mesh.joints.len();
478
479    skin_uniforms
480        .skin_uniform_info
481        .insert(skinned_mesh_entity, skin_uniform_info);
482}
483
484/// Deallocates a skin and removes it from the [`SkinUniforms`].
485fn remove_skin(skin_uniforms: &mut SkinUniforms, skinned_mesh_entity: MainEntity) {
486    let Some(old_skin_uniform_info) = skin_uniforms.skin_uniform_info.remove(&skinned_mesh_entity)
487    else {
488        return;
489    };
490
491    // Free the allocation.
492    skin_uniforms
493        .allocator
494        .free(old_skin_uniform_info.allocation);
495
496    // Update the total number of joints.
497    skin_uniforms.total_joints -= old_skin_uniform_info.joints.len();
498}
499
500// 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(
503    mut commands: Commands,
504    query: Query<Entity, (With<SkinnedMesh>, Without<NoAutomaticBatching>)>,
505    render_device: Res<RenderDevice>,
506) {
507    if !skins_use_uniform_buffers(&render_device.limits()) {
508        return;
509    }
510
511    for entity in &query {
512        commands.entity(entity).try_insert(NoAutomaticBatching);
513    }
514}