Skip to main content

bevy_render/mesh/
allocator.rs

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