Skip to main content

bevy_render/mesh/
allocator.rs

1//! Manages mesh vertex and index buffers.
2
3use alloc::borrow::Cow;
4use bevy_app::{App, Plugin};
5use bevy_asset::AssetId;
6use bevy_derive::{Deref, DerefMut};
7use bevy_ecs::{
8    resource::Resource,
9    schedule::IntoScheduleConfigs as _,
10    system::{Res, ResMut},
11    world::{FromWorld, World},
12};
13use bevy_log::warn;
14use bevy_mesh::Indices;
15use bevy_shape::{Aabb2d, BoundingVolume};
16use glam::Vec4;
17use wgpu::{BufferUsages, DownlevelFlags, COPY_BUFFER_ALIGNMENT};
18
19#[cfg(feature = "morph")]
20use bevy_mesh::morph::MorphAttributes;
21
22use crate::{
23    mesh::{Mesh, MeshMetadata, MeshVertexBufferLayouts, RenderMesh},
24    render_asset::{prepare_assets, ExtractedAssets},
25    renderer::{RenderAdapter, RenderDevice, RenderQueue},
26    slab_allocator::{
27        Slab, SlabAllocationBufferSlice, SlabAllocator, SlabAllocatorSettings, SlabId, SlabItem,
28        SlabItemLayout,
29    },
30    GpuResourceAppExt, Render, RenderApp, RenderSystems,
31};
32
33/// A plugin that manages GPU memory for mesh data.
34pub struct MeshAllocatorPlugin;
35
36/// Manages the assignment of mesh data to GPU buffers.
37///
38/// The Bevy renderer tries to pack vertex and index data for multiple meshes
39/// together so that multiple meshes can be drawn back-to-back without any
40/// rebinding. This resource manages these buffers.
41///
42/// The [`MeshAllocatorSettings`] allows you to tune the behavior of the
43/// allocator for better performance with your use case. Most applications won't
44/// need to change the settings from their default values.
45///
46#[derive(Resource, Deref, DerefMut)]
47pub struct MeshAllocator {
48    /// Holds all buffers and offset allocators.
49    #[deref]
50    slab_allocator: SlabAllocator<MeshSlabItem>,
51
52    /// Whether we can pack multiple vertex arrays into a single slab on this
53    /// platform.
54    ///
55    /// This corresponds to [`DownlevelFlags::BASE_VERTEX`], which is unset on
56    /// WebGL 2. On this platform, we must give each vertex array its own
57    /// buffer, because we can't adjust the first vertex when we perform a draw.
58    general_vertex_slabs_supported: bool,
59}
60
61/// Tunable parameters that customize the behavior of the allocator.
62///
63/// Generally, these parameters adjust the tradeoff between memory fragmentation
64/// and speed. You can adjust them as desired for your application. Most
65/// applications can stick with the default values.
66#[derive(Resource, Deref, DerefMut)]
67pub struct MeshAllocatorSettings {
68    #[deref]
69    pub slab_allocator_settings: SlabAllocatorSettings,
70
71    /// Additional buffer usages to add to any vertex or index buffers created.
72    pub extra_buffer_usages: BufferUsages,
73}
74
75impl Default for MeshAllocatorSettings {
76    fn default() -> MeshAllocatorSettings {
77        MeshAllocatorSettings {
78            slab_allocator_settings: SlabAllocatorSettings::default(),
79            extra_buffer_usages: BufferUsages::empty(),
80        }
81    }
82}
83
84/// The [`ElementLayout`] for morph displacements.
85///
86/// All morph displacements currently have the same element layout, so we only
87/// need one of these.
88#[cfg(feature = "morph")]
89const MORPH_ATTRIBUTE_ELEMENT_LAYOUT: ElementLayout = ElementLayout {
90    class: ElementClass::MorphTarget,
91    size: size_of::<MorphAttributes>() as u64,
92    elements_per_slot: 1,
93    buffer_usages: ElementClass::MorphTarget.buffer_usages(true),
94};
95
96/// The ID of a single slab.
97pub type MeshSlabId = SlabId<MeshSlabItem>;
98
99/// The slab buffer and location within that slab in which each mesh is
100/// allocated.
101pub type MeshBufferSlice<'a> = SlabAllocationBufferSlice<'a, MeshSlabItem>;
102
103/// The [`SlabItem`] implementation that describes the information needed to
104/// allocate and free meshes.
105pub struct MeshSlabItem;
106
107impl SlabItem for MeshSlabItem {
108    type Key = MeshAllocationKey;
109    type Layout = ElementLayout;
110    fn label() -> Cow<'static, str> {
111        "mesh".into()
112    }
113}
114
115/// IDs of the slabs associated with a single mesh.
116#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
117pub struct MeshSlabs {
118    /// The slab storing the mesh's vertex data.
119    pub vertex_slab_id: MeshSlabId,
120    /// The slab storing the mesh's index data, if the mesh is indexed.
121    pub index_slab_id: Option<MeshSlabId>,
122    /// The slab storing the mesh's metadata.
123    pub metadata_slab_id: Option<MeshSlabId>,
124    /// The slab storing the mesh's morph target displacements, if the mesh has
125    /// morph targets.
126    #[cfg(feature = "morph")]
127    pub morph_target_slab_id: Option<MeshSlabId>,
128}
129
130impl Slab<MeshSlabItem> {
131    /// Returns the type of buffer that this is: vertex, index, metadata or morph target.
132    pub fn element_class(&self) -> ElementClass {
133        self.element_layout().class
134    }
135}
136
137/// The handle used to retrieve a single mesh allocation.
138#[derive(Clone, Copy, PartialEq, Eq, Hash)]
139pub struct MeshAllocationKey {
140    /// The ID of the mesh asset.
141    pub mesh_id: AssetId<Mesh>,
142    /// The type of data: vertex data, index data, or morph data.
143    pub class: ElementClass,
144}
145
146impl MeshAllocationKey {
147    /// Creates a new [`MeshAllocationKey`] for the given mesh asset ID and
148    /// class.
149    pub fn new(mesh_id: AssetId<Mesh>, class: ElementClass) -> Self {
150        Self { mesh_id, class }
151    }
152}
153
154/// The type of element that a mesh slab can store.
155#[derive(Clone, Copy, PartialEq, Eq, Hash)]
156pub enum ElementClass {
157    /// Per-mesh metadata, except for meshes without `final_aabb` and `final_uv_ranges`.
158    Metadata,
159    /// Data for a vertex.
160    Vertex,
161    /// A vertex index.
162    Index,
163    #[cfg(feature = "morph")]
164    /// Displacement data for a morph target.
165    MorphTarget,
166}
167
168/// Information about the size of individual elements within a slab.
169///
170/// Slab objects are allocated in units of *slots*. Usually, each element takes
171/// up one slot, and so elements and slots are equivalent. Occasionally,
172/// however, a slot may consist of 2 or even 4 elements. This occurs when the
173/// size of an element isn't divisible by [`COPY_BUFFER_ALIGNMENT`]. When we
174/// resize buffers, we perform GPU-to-GPU copies to shuffle the existing
175/// elements into their new positions, and such copies must be on
176/// [`COPY_BUFFER_ALIGNMENT`] boundaries. Slots solve this problem by
177/// guaranteeing that the size of an allocation quantum is divisible by both the
178/// size of an element and [`COPY_BUFFER_ALIGNMENT`], so we can relocate it
179/// freely.
180#[derive(Clone, Copy, PartialEq, Eq, Hash)]
181pub struct ElementLayout {
182    class: ElementClass,
183
184    /// The size in bytes of a single element.
185    size: u64,
186
187    /// The number of elements that make up a single slot.
188    ///
189    /// Usually, this is 1, but it can be different if [`ElementLayout::size`]
190    /// isn't divisible by 4. See the comment in [`ElementLayout`] for more
191    /// details.
192    elements_per_slot: u32,
193
194    buffer_usages: BufferUsages,
195}
196
197impl Plugin for MeshAllocatorPlugin {
198    fn build(&self, app: &mut App) {
199        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
200            return;
201        };
202
203        render_app
204            .init_resource::<MeshAllocatorSettings>()
205            .add_systems(
206                Render,
207                allocate_and_free_meshes
208                    .in_set(RenderSystems::PrepareAssets)
209                    .before(prepare_assets::<RenderMesh>),
210            );
211    }
212
213    fn finish(&self, app: &mut App) {
214        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
215            return;
216        };
217
218        // The `RenderAdapter` isn't available until now, so we can't do this in
219        // [`Plugin::build`].
220        render_app.init_gpu_resource::<MeshAllocator>();
221    }
222}
223
224impl FromWorld for MeshAllocator {
225    fn from_world(world: &mut World) -> Self {
226        // Note whether we're on WebGL 2. In this case, we must give every
227        // vertex array its own slab.
228        let render_adapter = world.resource::<RenderAdapter>();
229        let general_vertex_slabs_supported = render_adapter
230            .get_downlevel_capabilities()
231            .flags
232            .contains(DownlevelFlags::BASE_VERTEX);
233
234        // Take the `extra_buffer_usages` from the mesh allocator settings into
235        // account.
236        let mesh_allocator_settings = world.resource::<MeshAllocatorSettings>();
237        let mut slab_allocator = SlabAllocator::new();
238        slab_allocator.extra_buffer_usages |= mesh_allocator_settings.extra_buffer_usages;
239
240        Self {
241            slab_allocator,
242            general_vertex_slabs_supported,
243        }
244    }
245}
246
247/// A system that processes newly-extracted or newly-removed meshes and writes
248/// their data into buffers or frees their data as appropriate.
249pub fn allocate_and_free_meshes(
250    mut mesh_allocator: ResMut<MeshAllocator>,
251    mesh_allocator_settings: Res<MeshAllocatorSettings>,
252    extracted_meshes: Res<ExtractedAssets<RenderMesh>>,
253    mut mesh_vertex_buffer_layouts: ResMut<MeshVertexBufferLayouts>,
254    render_device: Res<RenderDevice>,
255    render_queue: Res<RenderQueue>,
256) {
257    // Clear the list of meshes displaced by last frame's slab growth.
258    mesh_allocator.clear_displaced_keys();
259
260    // Process removed or modified meshes.
261    mesh_allocator.free_meshes(&extracted_meshes);
262
263    // Process newly-added or modified meshes.
264    mesh_allocator.allocate_meshes(
265        &mesh_allocator_settings,
266        &extracted_meshes,
267        &mut mesh_vertex_buffer_layouts,
268        &render_device,
269        &render_queue,
270    );
271}
272
273impl MeshAllocator {
274    /// Returns the buffer and range within that buffer of the metadata for
275    /// the mesh with the given ID.
276    ///
277    /// If the mesh wasn't allocated, returns None.
278    pub fn mesh_metadata_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
279        self.slab_allocation_slice(
280            &MeshAllocationKey::new(*mesh_id, ElementClass::Metadata),
281            *self.mesh_id_to_metadata_slab(mesh_id)?,
282        )
283    }
284
285    /// Meshes that had the buffer under their vertex or index data replaced this frame by a slab
286    /// they were already resident in growing, so that anything caching those buffers can rebuild
287    /// just the affected entries.
288    ///
289    /// **It is not the full set of meshes whose buffers changed this frame, and is not meant to be.**
290    /// Callers must handle [`ExtractedAssets`] themselves if they want the full set of changed buffers.
291    ///
292    /// A mesh is yielded twice if both its vertex and its index slab grew, since those are separate
293    /// allocations, so deduplicate if repeating the work per mesh would be expensive.
294    ///
295    /// Morph target and metadata allocations are filtered out.
296    ///
297    /// See [`SlabAllocator::keys_displaced_by_slab_growth`], which this wraps.
298    pub fn meshes_displaced_by_slab_growth(&self) -> impl Iterator<Item = AssetId<Mesh>> {
299        self.keys_displaced_by_slab_growth()
300            .iter()
301            .filter(|key| matches!(key.class, ElementClass::Vertex | ElementClass::Index))
302            .map(|key| key.mesh_id)
303    }
304
305    /// Returns the buffer and range within that buffer of the vertex data for
306    /// the mesh with the given ID.
307    ///
308    /// If the mesh wasn't allocated, returns None.
309    pub fn mesh_vertex_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
310        self.slab_allocation_slice(
311            &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
312            *self.mesh_id_to_vertex_slab(mesh_id)?,
313        )
314    }
315
316    /// Returns the buffer and range within that buffer of the index data for
317    /// the mesh with the given ID.
318    ///
319    /// If the mesh has no index data or wasn't allocated, returns None.
320    pub fn mesh_index_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
321        self.slab_allocation_slice(
322            &MeshAllocationKey::new(*mesh_id, ElementClass::Index),
323            *self.mesh_id_to_index_slab(mesh_id)?,
324        )
325    }
326
327    /// Returns the buffer and range within that buffer of the morph target data
328    /// for the mesh with the given ID.
329    ///
330    /// If the mesh has no morph target data or wasn't allocated, returns None.
331    #[cfg(feature = "morph")]
332    pub fn mesh_morph_target_slice(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshBufferSlice<'_>> {
333        self.slab_allocation_slice(
334            &MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget),
335            *self.mesh_id_to_morph_target_slab(mesh_id)?,
336        )
337    }
338
339    /// Returns the IDs of the vertex buffer and index buffer respectively for
340    /// the mesh with the given ID.
341    ///
342    /// If the mesh wasn't allocated, or has no index data in the case of the
343    /// index buffer, the corresponding element in the returned tuple will be
344    /// None.
345    pub fn mesh_slabs(&self, mesh_id: &AssetId<Mesh>) -> Option<MeshSlabs> {
346        Some(MeshSlabs {
347            vertex_slab_id: self.mesh_id_to_vertex_slab(mesh_id).cloned()?,
348            index_slab_id: self.mesh_id_to_index_slab(mesh_id).cloned(),
349            metadata_slab_id: self.mesh_id_to_metadata_slab(mesh_id).cloned(),
350            #[cfg(feature = "morph")]
351            morph_target_slab_id: self.mesh_id_to_morph_target_slab(mesh_id).cloned(),
352        })
353    }
354
355    /// Returns the number of index allocations that this mesh allocator
356    /// manages.
357    pub fn index_allocation_count(&self) -> usize {
358        self.key_to_slab
359            .keys()
360            .filter(|key| key.class == ElementClass::Index)
361            .count()
362    }
363
364    /// Given the ID of a mesh, returns the ID of the slab that contains the
365    /// metadata for that mesh, if it exists.
366    fn mesh_id_to_metadata_slab(&self, mesh_id: &AssetId<Mesh>) -> Option<&SlabId<MeshSlabItem>> {
367        self.key_to_slab
368            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::Metadata))
369    }
370
371    /// Given the ID of a mesh, returns the ID of the slab that contains the
372    /// vertex data for that mesh, if it exists.
373    fn mesh_id_to_vertex_slab(&self, mesh_id: &AssetId<Mesh>) -> Option<&SlabId<MeshSlabItem>> {
374        self.key_to_slab
375            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::Vertex))
376    }
377
378    /// Given the ID of a mesh, returns the ID of the slab that contains the
379    /// index data for that mesh, if it exists.
380    fn mesh_id_to_index_slab(&self, mesh_id: &AssetId<Mesh>) -> Option<&SlabId<MeshSlabItem>> {
381        self.key_to_slab
382            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::Index))
383    }
384
385    /// Given the ID of a mesh, returns the ID of the slab that contains the
386    /// morph target data for that mesh, if it exists.
387    #[cfg(feature = "morph")]
388    fn mesh_id_to_morph_target_slab(
389        &self,
390        mesh_id: &AssetId<Mesh>,
391    ) -> Option<&SlabId<MeshSlabItem>> {
392        self.key_to_slab
393            .get(&MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget))
394    }
395
396    /// Returns an iterator over all slabs that contain morph targets.
397    #[cfg(feature = "morph")]
398    pub fn morph_target_slabs(&self) -> impl Iterator<Item = MeshSlabId> {
399        self.slabs.iter().filter_map(|(slab_id, slab)| {
400            if matches!(slab.element_class(), ElementClass::MorphTarget) {
401                Some(*slab_id)
402            } else {
403                None
404            }
405        })
406    }
407
408    pub fn metadata_slabs(&self) -> impl Iterator<Item = MeshSlabId> {
409        self.slabs.iter().filter_map(|(slab_id, slab)| {
410            if matches!(slab.element_class(), ElementClass::Metadata) {
411                Some(*slab_id)
412            } else {
413                None
414            }
415        })
416    }
417
418    /// Processes newly-loaded meshes, allocating room in the slabs for their
419    /// mesh data and performing upload operations as appropriate.
420    fn allocate_meshes(
421        &mut self,
422        mesh_allocator_settings: &MeshAllocatorSettings,
423        extracted_meshes: &ExtractedAssets<RenderMesh>,
424        mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
425        render_device: &RenderDevice,
426        render_queue: &RenderQueue,
427    ) {
428        let mut allocation_stage = self.slab_allocator.stage_allocation();
429
430        // Loop over each mesh that was extracted this frame.
431        for (mesh_id, mesh) in &extracted_meshes.extracted {
432            let vertex_buffer_size = mesh.get_vertex_buffer_size() as u64;
433            if vertex_buffer_size == 0 {
434                warn!("Mesh {:?} contains no vertices.", mesh_id);
435                continue;
436            }
437
438            // Allocate metadata.
439            if mesh.final_aabb.is_some() || mesh.final_uv_ranges.iter().any(Option::is_some) {
440                // If storage buffers are unsupported, we allocate uniform buffers for each mesh.
441                if crate::storage_buffers_are_unsupported(&render_device.limits()) {
442                    allocation_stage.allocate_large(
443                        &MeshAllocationKey::new(*mesh_id, ElementClass::Metadata),
444                        ElementLayout::metadata(false),
445                    );
446                } else {
447                    allocation_stage.allocate(
448                        &MeshAllocationKey::new(*mesh_id, ElementClass::Metadata),
449                        size_of::<MeshMetadata>() as u64,
450                        ElementLayout::metadata(true),
451                        mesh_allocator_settings,
452                    );
453                }
454            }
455
456            // Allocate vertex data. Note that we can only pack mesh vertex data
457            // together if the platform supports it.
458            let vertex_element_layout = ElementLayout::vertex(mesh_vertex_buffer_layouts, mesh);
459            if self.general_vertex_slabs_supported {
460                allocation_stage.allocate(
461                    &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
462                    vertex_buffer_size,
463                    vertex_element_layout,
464                    mesh_allocator_settings,
465                );
466            } else {
467                allocation_stage.allocate_large(
468                    &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
469                    vertex_element_layout,
470                );
471            }
472
473            // Allocate index data.
474            if let (Some(index_buffer_data), Some(index_element_layout)) =
475                (mesh.get_index_buffer_bytes(), ElementLayout::index(mesh))
476            {
477                allocation_stage.allocate(
478                    &MeshAllocationKey::new(*mesh_id, ElementClass::Index),
479                    index_buffer_data.len() as u64,
480                    index_element_layout,
481                    mesh_allocator_settings,
482                );
483            }
484
485            // Allocate morph target data.
486            #[cfg(feature = "morph")]
487            if let Some(morph_targets) = mesh.get_morph_targets() {
488                allocation_stage.allocate(
489                    &MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget),
490                    morph_targets.len() as u64 * size_of::<MorphAttributes>() as u64,
491                    MORPH_ATTRIBUTE_ELEMENT_LAYOUT,
492                    mesh_allocator_settings,
493                );
494            }
495        }
496
497        // Perform growth.
498        allocation_stage.commit(render_device, render_queue);
499
500        // Copy new mesh data in.
501        for (mesh_id, mesh) in &extracted_meshes.extracted {
502            let vertex_buffer_size = mesh.get_vertex_buffer_size() as u64;
503            if vertex_buffer_size == 0 {
504                continue;
505            }
506            self.copy_mesh_metadata(mesh_id, mesh, render_device, render_queue);
507            self.copy_mesh_vertex_data(mesh_id, mesh, render_device, render_queue);
508            self.copy_mesh_index_data(mesh_id, mesh, render_device, render_queue);
509            #[cfg(feature = "morph")]
510            self.copy_mesh_morph_target_data(mesh_id, mesh, render_device, render_queue);
511        }
512    }
513
514    /// Copies vertex array data from a mesh into the appropriate spot in the
515    /// slab.
516    fn copy_mesh_metadata(
517        &mut self,
518        mesh_id: &AssetId<Mesh>,
519        mesh: &Mesh,
520        render_device: &RenderDevice,
521        render_queue: &RenderQueue,
522    ) {
523        const UV_RANGES_NONE: [Option<Aabb2d>; 2] = [None; 2];
524        let metadata = match (mesh.final_aabb, mesh.final_uv_ranges) {
525            (None, UV_RANGES_NONE) => return,
526            _ => {
527                let (aabb_center, aabb_half_extents) = mesh
528                    .final_aabb
529                    .map(|aabb| (aabb.center().into(), aabb.half_size().into()))
530                    .unwrap_or_default();
531                let uv_channels_min_and_extents = mesh.final_uv_ranges.map(|maybe_uv| {
532                    maybe_uv
533                        .map(|aabb2d| {
534                            Vec4::new(
535                                aabb2d.min.x,
536                                aabb2d.min.y,
537                                aabb2d.max.x - aabb2d.min.x,
538                                aabb2d.max.y - aabb2d.min.y,
539                            )
540                        })
541                        .unwrap_or(Vec4::new(0.0, 0.0, 1.0, 1.0))
542                });
543                MeshMetadata {
544                    aabb_center,
545                    aabb_half_extents,
546                    uv_channels_min_and_extents,
547                    ..Default::default()
548                }
549            }
550        };
551        // Call the generic function.
552        self.copy_element_data(
553            &MeshAllocationKey::new(*mesh_id, ElementClass::Metadata),
554            size_of::<MeshMetadata>(),
555            |mut slice| slice.copy_from_slice(bytemuck::cast_slice(&[metadata])),
556            render_device,
557            render_queue,
558        );
559    }
560
561    /// Copies vertex array data from a mesh into the appropriate spot in the
562    /// slab.
563    fn copy_mesh_vertex_data(
564        &mut self,
565        mesh_id: &AssetId<Mesh>,
566        mesh: &Mesh,
567        render_device: &RenderDevice,
568        render_queue: &RenderQueue,
569    ) {
570        // Call the generic function.
571        self.copy_element_data(
572            &MeshAllocationKey::new(*mesh_id, ElementClass::Vertex),
573            mesh.get_vertex_buffer_size(),
574            |slice| mesh.write_packed_vertex_buffer_data(slice),
575            render_device,
576            render_queue,
577        );
578    }
579
580    /// Copies index array data from a mesh into the appropriate spot in the
581    /// slab.
582    fn copy_mesh_index_data(
583        &mut self,
584        mesh_id: &AssetId<Mesh>,
585        mesh: &Mesh,
586        render_device: &RenderDevice,
587        render_queue: &RenderQueue,
588    ) {
589        let Some(index_data) = mesh.get_index_buffer_bytes() else {
590            return;
591        };
592
593        // Call the generic function.
594        self.copy_element_data(
595            &MeshAllocationKey::new(*mesh_id, ElementClass::Index),
596            index_data.len(),
597            |mut slice| slice.copy_from_slice(index_data),
598            render_device,
599            render_queue,
600        );
601    }
602
603    /// Copies morph target array data from a mesh into the appropriate spot in
604    /// the slab.
605    #[cfg(feature = "morph")]
606    fn copy_mesh_morph_target_data(
607        &mut self,
608        mesh_id: &AssetId<Mesh>,
609        mesh: &Mesh,
610        render_device: &RenderDevice,
611        render_queue: &RenderQueue,
612    ) {
613        let Some(morph_targets) = mesh.get_morph_targets() else {
614            return;
615        };
616
617        // Call the generic function.
618        self.copy_element_data(
619            &MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget),
620            size_of_val(morph_targets),
621            |mut slice| slice.copy_from_slice(bytemuck::cast_slice(morph_targets)),
622            render_device,
623            render_queue,
624        );
625    }
626
627    /// Frees allocations for meshes that were removed, modified, or re-extracted
628    /// this frame.
629    fn free_meshes(&mut self, extracted_meshes: &ExtractedAssets<RenderMesh>) {
630        let mut deallocation_stage = self.slab_allocator.stage_deallocation();
631
632        // TODO: Consider explicitly reusing allocations for changed meshes of
633        // the same size
634
635        // Free every mesh that `allocate_meshes` is about to reallocate. Despite
636        // its name, `added` holds every mesh extracted this frame rather than only
637        // the new ones, so it's exactly that set. This catches a mesh
638        // removed from `Assets` and reinserted under the same ID, which arrives as
639        // `Removed` then `Added` and so never appears in `modified`.
640        let meshes_to_free = extracted_meshes
641            .removed
642            .iter()
643            .chain(extracted_meshes.modified.iter())
644            .chain(extracted_meshes.added.iter());
645
646        for mesh_id in meshes_to_free {
647            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::Metadata));
648            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::Vertex));
649            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::Index));
650            #[cfg(feature = "morph")]
651            deallocation_stage.free(&MeshAllocationKey::new(*mesh_id, ElementClass::MorphTarget));
652        }
653
654        deallocation_stage.commit();
655    }
656}
657
658impl ElementLayout {
659    /// Creates an [`ElementLayout`] for mesh data of the given class (vertex or
660    /// index) with the given byte size.
661    fn new(class: ElementClass, size: u64) -> ElementLayout {
662        const {
663            assert!(4 == COPY_BUFFER_ALIGNMENT);
664        }
665
666        // Use `ElementLayout::metadata` instead, as it needs to specify whether to use storage buffer.
667        assert!(class != ElementClass::Metadata);
668
669        // this is equivalent to `4 / gcd(4,size)` but lets us not implement gcd.
670        // ping @atlv if above assert ever fails (likely never)
671        let elements_per_slot = [1, 4, 2, 4][size as usize & 3];
672        ElementLayout {
673            class,
674            size,
675            // Make sure that slot boundaries begin and end on
676            // `COPY_BUFFER_ALIGNMENT`-byte (4-byte) boundaries.
677            elements_per_slot,
678            buffer_usages: class.buffer_usages(true),
679        }
680    }
681
682    fn metadata(storage_buffers_are_usable: bool) -> ElementLayout {
683        ElementLayout {
684            class: ElementClass::Metadata,
685            size: size_of::<MeshMetadata>() as u64,
686            elements_per_slot: 1,
687            buffer_usages: ElementClass::Metadata.buffer_usages(storage_buffers_are_usable),
688        }
689    }
690
691    /// Creates the appropriate [`ElementLayout`] for the given mesh's vertex
692    /// data.
693    fn vertex(
694        mesh_vertex_buffer_layouts: &mut MeshVertexBufferLayouts,
695        mesh: &Mesh,
696    ) -> ElementLayout {
697        let mesh_vertex_buffer_layout =
698            mesh.get_mesh_vertex_buffer_layout(mesh_vertex_buffer_layouts);
699        ElementLayout::new(
700            ElementClass::Vertex,
701            mesh_vertex_buffer_layout.0.layout().array_stride,
702        )
703    }
704
705    /// Creates the appropriate [`ElementLayout`] for the given mesh's index
706    /// data.
707    fn index(mesh: &Mesh) -> Option<ElementLayout> {
708        let size = match mesh.indices()? {
709            Indices::U16(_) => 2,
710            Indices::U32(_) => 4,
711        };
712        Some(ElementLayout::new(ElementClass::Index, size))
713    }
714}
715
716impl SlabItemLayout for ElementLayout {
717    fn size(&self) -> u64 {
718        self.size
719    }
720
721    fn elements_per_slot(&self) -> u32 {
722        self.elements_per_slot
723    }
724
725    fn buffer_usages(&self) -> BufferUsages {
726        self.buffer_usages
727    }
728}
729
730impl ElementClass {
731    /// Returns the `wgpu` [`BufferUsages`] appropriate for a buffer of this
732    /// class.
733    const fn buffer_usages(&self, metadata_use_storage_buffer: bool) -> BufferUsages {
734        match *self {
735            ElementClass::Metadata => {
736                if metadata_use_storage_buffer {
737                    BufferUsages::STORAGE
738                } else {
739                    BufferUsages::UNIFORM
740                }
741            }
742            ElementClass::Vertex => BufferUsages::VERTEX,
743            ElementClass::Index => BufferUsages::INDEX,
744            #[cfg(feature = "morph")]
745            ElementClass::MorphTarget => BufferUsages::STORAGE,
746        }
747    }
748}
749
750#[cfg(test)]
751mod tests {
752    use super::*;
753    use crate::test_utils::create_dummy_device;
754    use bevy_asset::{uuid::Uuid, RenderAssetUsages};
755    use bevy_mesh::PrimitiveTopology;
756    use bevy_shape::Aabb3d;
757    use glam::{Vec2, Vec3};
758
759    fn test_mesh() -> Mesh {
760        let mut mesh = Mesh::new(
761            PrimitiveTopology::TriangleList,
762            RenderAssetUsages::default(),
763        );
764        mesh.insert_attribute(Mesh::ATTRIBUTE_POSITION, vec![[0.0f32, 0.0, 0.0]; 64]);
765        mesh
766    }
767
768    /// A mesh that exercises every [`ElementClass`] at once.
769    fn full_mesh() -> Mesh {
770        let mut mesh = test_mesh();
771        mesh.insert_indices(Indices::U32((0..64).collect()));
772        mesh.final_aabb = Some(Aabb3d::new(Vec3::ZERO, Vec3::ONE));
773        mesh.final_uv_ranges[0] = Some(Aabb2d::new(Vec2::ZERO, Vec2::ONE));
774        #[cfg(feature = "morph")]
775        mesh.set_morph_targets(vec![MorphAttributes::default(); 64]);
776        mesh
777    }
778
779    /// A mesh whose vertex layout differs from [`test_mesh`], so that it sorts
780    /// into a different general slab.
781    fn wider_vertex_mesh() -> Mesh {
782        let mut mesh = test_mesh();
783        mesh.insert_attribute(Mesh::ATTRIBUTE_NORMAL, vec![[0.0f32, 0.0, 1.0]; 64]);
784        mesh
785    }
786
787    /// Builds a [`MeshAllocator`] backed by an empty slab allocator.
788    ///
789    /// Clearing `general_vertex_slabs_supported` sends every vertex array into a
790    /// slab of its own.
791    fn mesh_allocator(general_vertex_slabs_supported: bool) -> MeshAllocator {
792        MeshAllocator {
793            slab_allocator: SlabAllocator::new(),
794            general_vertex_slabs_supported,
795        }
796    }
797
798    /// Allocator tuning with a small large-object threshold, so that an
799    /// otherwise modest test mesh is big enough to demand a slab of its own.
800    fn small_slab_settings() -> MeshAllocatorSettings {
801        MeshAllocatorSettings {
802            slab_allocator_settings: SlabAllocatorSettings {
803                min_slab_size: 1024,
804                max_slab_size: 4096,
805                large_threshold: 512,
806                growth_factor: 1.5,
807            },
808            extra_buffer_usages: BufferUsages::empty(),
809        }
810    }
811
812    fn mesh_id(id: u128) -> AssetId<Mesh> {
813        AssetId::<Mesh>::Uuid {
814            uuid: Uuid::from_u128(id),
815        }
816    }
817
818    /// Whether the allocator currently holds an allocation of the given class
819    /// for the given mesh.
820    fn has_allocation(
821        mesh_allocator: &MeshAllocator,
822        mesh_id: AssetId<Mesh>,
823        class: ElementClass,
824    ) -> bool {
825        mesh_allocator
826            .key_to_slab
827            .contains_key(&MeshAllocationKey::new(mesh_id, class))
828    }
829
830    /// Builds the extraction output for a mesh that was extracted this frame.
831    ///
832    /// This mirrors what `extract_render_asset` produces for an
833    /// `AssetEvent::Added`: the mesh lands in `extracted` and `added`, and in
834    /// neither `removed` nor `modified`.
835    fn extracted_mesh(id: AssetId<Mesh>, mesh: Mesh) -> ExtractedAssets<RenderMesh> {
836        let mut extracted_meshes = ExtractedAssets::<RenderMesh>::default();
837        extracted_meshes.extracted.push((id, mesh));
838        extracted_meshes.added.insert(id);
839        extracted_meshes
840    }
841
842    /// Builds the extraction output for a mesh modified in place this frame.
843    ///
844    /// This mirrors what `extract_render_asset` produces for an
845    /// `AssetEvent::Modified`, as caused by `Assets::get_mut`: the mesh is
846    /// re-extracted, so it lands in `modified` on top of `extracted` and `added`.
847    fn modified_mesh(id: AssetId<Mesh>, mesh: Mesh) -> ExtractedAssets<RenderMesh> {
848        let mut extracted_meshes = extracted_mesh(id, mesh);
849        extracted_meshes.modified.insert(id);
850        extracted_meshes
851    }
852
853    /// `free_meshes` must release meshes that are merely being re-extracted, not
854    /// only those flagged `removed` or `modified`.
855    #[test]
856    fn free_meshes_releases_reextracted_meshes() {
857        let (render_device, render_queue) = create_dummy_device();
858        let settings = MeshAllocatorSettings::default();
859        let mut mesh_vertex_buffer_layouts = MeshVertexBufferLayouts::default();
860        let mut mesh_allocator = mesh_allocator(true);
861
862        let mesh_id = mesh_id(1);
863        let extracted_meshes = extracted_mesh(mesh_id, test_mesh());
864
865        mesh_allocator.allocate_meshes(
866            &settings,
867            &extracted_meshes,
868            &mut mesh_vertex_buffer_layouts,
869            &render_device,
870            &render_queue,
871        );
872        assert!(mesh_allocator.mesh_vertex_slice(&mesh_id).is_some());
873
874        // Being present in `added` alone must be enough to release the previous
875        // allocation.
876        mesh_allocator.free_meshes(&extracted_meshes);
877
878        assert!(
879            mesh_allocator.key_to_slab.is_empty(),
880            "a re-extracted mesh was not freed, so its old allocation would leak"
881        );
882        assert_eq!(mesh_allocator.slab_count(), 0);
883    }
884
885    /// A mesh flagged `modified` must be freed even when it isn't re-extracted.
886    ///
887    /// `added` covers meshes that come back around for reallocation, but a mesh
888    /// can be modified and then leave `Assets` without emitting `Unused`, in
889    /// which case `modified` is the only record we get of it.
890    #[test]
891    fn free_meshes_releases_modified_meshes_that_were_not_reextracted() {
892        let (render_device, render_queue) = create_dummy_device();
893        let settings = MeshAllocatorSettings::default();
894        let mut mesh_vertex_buffer_layouts = MeshVertexBufferLayouts::default();
895        let mut mesh_allocator = mesh_allocator(true);
896
897        let mesh_id = mesh_id(1);
898        mesh_allocator.allocate_meshes(
899            &settings,
900            &extracted_mesh(mesh_id, test_mesh()),
901            &mut mesh_vertex_buffer_layouts,
902            &render_device,
903            &render_queue,
904        );
905        assert!(mesh_allocator.mesh_vertex_slice(&mesh_id).is_some());
906
907        let mut extracted_meshes = ExtractedAssets::<RenderMesh>::default();
908        extracted_meshes.modified.insert(mesh_id);
909        mesh_allocator.free_meshes(&extracted_meshes);
910
911        assert!(
912            mesh_allocator.key_to_slab.is_empty(),
913            "a modified mesh that wasn't re-extracted was not freed"
914        );
915        assert_eq!(mesh_allocator.slab_count(), 0);
916    }
917
918    /// `free_meshes` must release every class of allocation a mesh holds, not
919    /// just its vertex data.
920    #[test]
921    fn free_meshes_releases_every_element_class() {
922        let (render_device, render_queue) = create_dummy_device();
923        let settings = MeshAllocatorSettings::default();
924        let mut mesh_vertex_buffer_layouts = MeshVertexBufferLayouts::default();
925        let mut mesh_allocator = mesh_allocator(true);
926
927        let mesh_id = mesh_id(1);
928        let extracted_meshes = extracted_mesh(mesh_id, full_mesh());
929        mesh_allocator.allocate_meshes(
930            &settings,
931            &extracted_meshes,
932            &mut mesh_vertex_buffer_layouts,
933            &render_device,
934            &render_queue,
935        );
936
937        assert!(has_allocation(
938            &mesh_allocator,
939            mesh_id,
940            ElementClass::Vertex
941        ));
942        assert!(has_allocation(
943            &mesh_allocator,
944            mesh_id,
945            ElementClass::Index
946        ));
947        assert!(has_allocation(
948            &mesh_allocator,
949            mesh_id,
950            ElementClass::Metadata
951        ));
952        #[cfg(feature = "morph")]
953        assert!(has_allocation(
954            &mesh_allocator,
955            mesh_id,
956            ElementClass::MorphTarget
957        ));
958
959        mesh_allocator.free_meshes(&extracted_meshes);
960
961        assert!(
962            mesh_allocator.key_to_slab.is_empty(),
963            "at least one element class was left allocated, so it would leak"
964        );
965        assert_eq!(mesh_allocator.slab_count(), 0);
966    }
967
968    /// A mesh that loses part of its data on re-extraction must give up the
969    /// matching allocations, which nothing will reallocate.
970    #[test]
971    fn reextracting_a_mesh_that_drops_its_extra_data_frees_those_allocations() {
972        let (render_device, render_queue) = create_dummy_device();
973        let settings = MeshAllocatorSettings::default();
974        let mut mesh_vertex_buffer_layouts = MeshVertexBufferLayouts::default();
975        let mut mesh_allocator = mesh_allocator(true);
976
977        let mesh_id = mesh_id(1);
978        mesh_allocator.allocate_meshes(
979            &settings,
980            &extracted_mesh(mesh_id, full_mesh()),
981            &mut mesh_vertex_buffer_layouts,
982            &render_device,
983            &render_queue,
984        );
985        assert!(has_allocation(
986            &mesh_allocator,
987            mesh_id,
988            ElementClass::Index
989        ));
990
991        // The same ID comes back as a bare vertex-only mesh.
992        let extracted_meshes = extracted_mesh(mesh_id, test_mesh());
993        mesh_allocator.free_meshes(&extracted_meshes);
994        mesh_allocator.allocate_meshes(
995            &settings,
996            &extracted_meshes,
997            &mut mesh_vertex_buffer_layouts,
998            &render_device,
999            &render_queue,
1000        );
1001
1002        assert!(has_allocation(
1003            &mesh_allocator,
1004            mesh_id,
1005            ElementClass::Vertex
1006        ));
1007        assert!(
1008            !has_allocation(&mesh_allocator, mesh_id, ElementClass::Index),
1009            "index data was dropped by the mesh but its allocation survived"
1010        );
1011        assert!(
1012            !has_allocation(&mesh_allocator, mesh_id, ElementClass::Metadata),
1013            "metadata was dropped by the mesh but its allocation survived"
1014        );
1015        #[cfg(feature = "morph")]
1016        assert!(
1017            !has_allocation(&mesh_allocator, mesh_id, ElementClass::MorphTarget),
1018            "morph targets were dropped by the mesh but their allocation survived"
1019        );
1020    }
1021
1022    /// Changing a mesh's vertex layout moves it to a different slab, and the
1023    /// slab it leaves behind must be reclaimed.
1024    #[test]
1025    fn reextracting_a_mesh_with_a_new_vertex_layout_reclaims_the_old_slab() {
1026        let (render_device, render_queue) = create_dummy_device();
1027        let settings = MeshAllocatorSettings::default();
1028        let mut mesh_vertex_buffer_layouts = MeshVertexBufferLayouts::default();
1029        let mut mesh_allocator = mesh_allocator(true);
1030
1031        let mesh_id = mesh_id(1);
1032        mesh_allocator.allocate_meshes(
1033            &settings,
1034            &extracted_mesh(mesh_id, test_mesh()),
1035            &mut mesh_vertex_buffer_layouts,
1036            &render_device,
1037            &render_queue,
1038        );
1039        assert_eq!(mesh_allocator.slab_count(), 1);
1040        let original_slab =
1041            mesh_allocator.key_to_slab[&MeshAllocationKey::new(mesh_id, ElementClass::Vertex)];
1042
1043        // Adding a normal attribute widens the vertex, which needs a slab with a
1044        // different element layout.
1045        let extracted_meshes = extracted_mesh(mesh_id, wider_vertex_mesh());
1046        mesh_allocator.free_meshes(&extracted_meshes);
1047        mesh_allocator.allocate_meshes(
1048            &settings,
1049            &extracted_meshes,
1050            &mut mesh_vertex_buffer_layouts,
1051            &render_device,
1052            &render_queue,
1053        );
1054
1055        let new_slab =
1056            mesh_allocator.key_to_slab[&MeshAllocationKey::new(mesh_id, ElementClass::Vertex)];
1057        assert_ne!(
1058            new_slab, original_slab,
1059            "the wider vertex should have landed in a slab with a different layout"
1060        );
1061        assert_eq!(
1062            mesh_allocator.slab_count(),
1063            1,
1064            "the slab the mesh moved out of was not reclaimed"
1065        );
1066        assert!(mesh_allocator.mesh_vertex_slice(&mesh_id).is_some());
1067    }
1068
1069    /// One frame-loop scenario for [`assert_steady_state`].
1070    struct SteadyStateCase {
1071        /// Whether the mesh arrives as merely re-extracted or as modified.
1072        build: fn(AssetId<Mesh>, Mesh) -> ExtractedAssets<RenderMesh>,
1073        build_mesh: fn() -> Mesh,
1074        settings: MeshAllocatorSettings,
1075        general_vertex_slabs_supported: bool,
1076    }
1077
1078    impl Default for SteadyStateCase {
1079        fn default() -> Self {
1080            Self {
1081                build: extracted_mesh,
1082                build_mesh: test_mesh,
1083                settings: MeshAllocatorSettings::default(),
1084                general_vertex_slabs_supported: true,
1085            }
1086        }
1087    }
1088
1089    /// Runs `rounds` frames of free-then-allocate over a single mesh ID and
1090    /// asserts that slab memory reaches a steady state.
1091    fn assert_steady_state(case: SteadyStateCase, rounds: usize) {
1092        let SteadyStateCase {
1093            build,
1094            build_mesh,
1095            settings,
1096            general_vertex_slabs_supported,
1097        } = case;
1098
1099        let (render_device, render_queue) = create_dummy_device();
1100        let mut mesh_vertex_buffer_layouts = MeshVertexBufferLayouts::default();
1101        let mut mesh_allocator = mesh_allocator(general_vertex_slabs_supported);
1102
1103        let mesh_id = mesh_id(1);
1104
1105        let mut baseline = None;
1106        for _ in 0..rounds {
1107            let extracted_meshes = build(mesh_id, build_mesh());
1108            mesh_allocator.free_meshes(&extracted_meshes);
1109            mesh_allocator.allocate_meshes(
1110                &settings,
1111                &extracted_meshes,
1112                &mut mesh_vertex_buffer_layouts,
1113                &render_device,
1114                &render_queue,
1115            );
1116
1117            let size = mesh_allocator.slabs_size();
1118            let slab_count = mesh_allocator.slab_count();
1119            match baseline {
1120                None => baseline = Some((size, slab_count)),
1121                Some(baseline) => {
1122                    assert_eq!(
1123                        (size, slab_count),
1124                        baseline,
1125                        "slab memory grew across frames"
1126                    );
1127                }
1128            }
1129        }
1130
1131        assert!(mesh_allocator.mesh_vertex_slice(&mesh_id).is_some());
1132    }
1133
1134    /// Re-extracting the same mesh ID every frame must reach a steady state.
1135    #[test]
1136    fn reextracting_the_same_mesh_does_not_grow_the_slabs() {
1137        assert_steady_state(SteadyStateCase::default(), 32);
1138    }
1139
1140    /// Modifying the same mesh in place every frame must reach a steady state.
1141    #[test]
1142    fn modifying_a_mesh_in_place_does_not_grow_the_slabs() {
1143        assert_steady_state(
1144            SteadyStateCase {
1145                build: modified_mesh,
1146                ..SteadyStateCase::default()
1147            },
1148            32,
1149        );
1150    }
1151
1152    /// A mesh carrying every element class must reach a steady state too, so
1153    /// that a leak confined to one class cannot hide behind the vertex data.
1154    #[test]
1155    fn reextracting_a_mesh_with_every_element_class_does_not_grow_the_slabs() {
1156        assert_steady_state(
1157            SteadyStateCase {
1158                build_mesh: full_mesh,
1159                ..SteadyStateCase::default()
1160            },
1161            32,
1162        );
1163    }
1164
1165    /// Data too big to share a general slab gets one of its own, so a missed
1166    /// free leaks a whole slab rather than a slot inside one.
1167    #[test]
1168    fn reextracting_a_mesh_too_large_for_a_general_slab_does_not_grow_the_slabs() {
1169        assert_steady_state(
1170            SteadyStateCase {
1171                build_mesh: full_mesh,
1172                settings: small_slab_settings(),
1173                ..SteadyStateCase::default()
1174            },
1175            32,
1176        );
1177    }
1178
1179    /// The other route to a dedicated slab, taken when the platform cannot
1180    /// share vertex slabs at all.
1181    #[test]
1182    fn reextracting_a_mesh_without_general_vertex_slabs_does_not_grow_the_slabs() {
1183        assert_steady_state(
1184            SteadyStateCase {
1185                build_mesh: full_mesh,
1186                general_vertex_slabs_supported: false,
1187                ..SteadyStateCase::default()
1188            },
1189            32,
1190        );
1191    }
1192}