1use 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
31pub struct MeshAllocatorPlugin;
33
34#[derive(Resource, Deref, DerefMut)]
45pub struct MeshAllocator {
46 #[deref]
48 slab_allocator: SlabAllocator<MeshSlabItem>,
49
50 general_vertex_slabs_supported: bool,
57}
58
59#[derive(Resource, Deref, DerefMut)]
65pub struct MeshAllocatorSettings {
66 #[deref]
67 pub slab_allocator_settings: SlabAllocatorSettings,
68
69 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#[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
93pub type MeshSlabId = SlabId<MeshSlabItem>;
95
96pub type MeshBufferSlice<'a> = SlabAllocationBufferSlice<'a, MeshSlabItem>;
99
100pub 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#[derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
114pub struct MeshSlabs {
115 pub vertex_slab_id: MeshSlabId,
117 pub index_slab_id: Option<MeshSlabId>,
119 #[cfg(feature = "morph")]
122 pub morph_target_slab_id: Option<MeshSlabId>,
123}
124
125impl Slab<MeshSlabItem> {
126 #[cfg(feature = "morph")]
128 pub fn element_class(&self) -> ElementClass {
129 self.element_layout().class
130 }
131}
132
133#[derive(Clone, Copy, PartialEq, Eq, Hash)]
135pub struct MeshAllocationKey {
136 pub mesh_id: AssetId<Mesh>,
138 pub class: ElementClass,
140}
141
142impl MeshAllocationKey {
143 pub fn new(mesh_id: AssetId<Mesh>, class: ElementClass) -> Self {
146 Self { mesh_id, class }
147 }
148}
149
150#[derive(Clone, Copy, PartialEq, Eq, Hash)]
152pub enum ElementClass {
153 Vertex,
155 Index,
157 #[cfg(feature = "morph")]
158 MorphTarget,
160}
161
162#[derive(Clone, Copy, PartialEq, Eq, Hash)]
176pub struct ElementLayout {
177 class: ElementClass,
179
180 size: u64,
182
183 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 render_app.init_gpu_resource::<MeshAllocator>();
215 }
216}
217
218impl FromWorld for MeshAllocator {
219 fn from_world(world: &mut World) -> Self {
220 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 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
241pub 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 mesh_allocator.free_meshes(&extracted_meshes);
253
254 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 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 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 #[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 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 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 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 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 #[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 #[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 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 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 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 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 #[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 allocation_stage.commit(render_device, render_queue);
422
423 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 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 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 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 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 #[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 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 fn free_meshes(&mut self, extracted_meshes: &ExtractedAssets<RenderMesh>) {
501 let mut deallocation_stage = self.slab_allocator.stage_deallocation();
502
503 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 fn new(class: ElementClass, size: u64) -> ElementLayout {
532 const {
533 assert!(4 == COPY_BUFFER_ALIGNMENT);
534 }
535 let elements_per_slot = [1, 4, 2, 4][size as usize & 3];
538 ElementLayout {
539 class,
540 size,
541 elements_per_slot,
544 }
545 }
546
547 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 #[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 #[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 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 #[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 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 struct SteadyStateCase {
910 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 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 #[test]
975 fn reextracting_the_same_mesh_does_not_grow_the_slabs() {
976 assert_steady_state(SteadyStateCase::default(), 32);
977 }
978
979 #[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 #[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 #[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 #[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}