1use alloc::borrow::Cow;
4use bevy_derive::{Deref, DerefMut};
5use bevy_log::error;
6use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
7use core::{
8 cmp::Ordering,
9 fmt::{self, Debug, Display, Formatter},
10 hash::{Hash, Hasher},
11 marker::PhantomData,
12 ops::Range,
13};
14use nonmax::NonMaxU32;
15use offset_allocator::{Allocation, Allocator};
16use wgpu::{BufferDescriptor, BufferSize, BufferUsages, CommandEncoderDescriptor, WriteOnly};
17
18use crate::{
19 render_resource::Buffer,
20 renderer::{RenderDevice, RenderQueue},
21};
22
23pub struct SlabAllocator<I>
57where
58 I: SlabItem,
59{
60 pub slabs: HashMap<SlabId<I>, Slab<I>>,
62
63 next_slab_id: SlabId<I>,
65
66 pub key_to_slab: HashMap<I::Key, SlabId<I>>,
68
69 slab_layouts: HashMap<I::Layout, Vec<SlabId<I>>>,
74
75 pub extra_buffer_usages: BufferUsages,
77}
78
79pub trait SlabItem {
87 type Key: Clone + PartialEq + Eq + Hash;
89
90 type Layout: SlabItemLayout;
100
101 fn label() -> Cow<'static, str>;
104}
105
106pub trait SlabItemLayout: Clone + PartialEq + Eq + Hash {
109 fn size(&self) -> u64;
114
115 fn elements_per_slot(&self) -> u32;
117
118 fn buffer_usages(&self) -> BufferUsages;
124}
125
126trait SlabItemLayoutExt {
128 fn slot_size(&self) -> u64;
130}
131
132impl<I> SlabItemLayoutExt for I
133where
134 I: SlabItemLayout,
135{
136 fn slot_size(&self) -> u64 {
137 self.size() * self.elements_per_slot() as u64
138 }
139}
140
141pub struct SlabAllocatorSettings {
147 pub min_slab_size: u64,
151
152 pub max_slab_size: u64,
158
159 pub large_threshold: u64,
168
169 pub growth_factor: f64,
177}
178
179impl Default for SlabAllocatorSettings {
180 fn default() -> Self {
181 Self {
182 min_slab_size: 1024 * 1024,
184 max_slab_size: 1024 * 1024 * 512,
186 large_threshold: 1024 * 1024 * 256,
188 growth_factor: 1.5,
190 }
191 }
192}
193
194#[derive(Deref, DerefMut)]
196#[repr(transparent)]
197pub struct SlabId<I>
198where
199 I: SlabItem,
200{
201 #[deref]
203 pub id: NonMaxU32,
204 phantom: PhantomData<I>,
205}
206
207impl<I> Clone for SlabId<I>
208where
209 I: SlabItem,
210{
211 fn clone(&self) -> Self {
212 *self
213 }
214}
215
216impl<I> Copy for SlabId<I> where I: SlabItem {}
217
218impl<I> Default for SlabId<I>
219where
220 I: SlabItem,
221{
222 fn default() -> Self {
223 SlabId {
224 id: NonMaxU32::default(),
225 phantom: PhantomData,
226 }
227 }
228}
229
230impl<I> PartialEq for SlabId<I>
231where
232 I: SlabItem,
233{
234 fn eq(&self, other: &Self) -> bool {
235 self.id == other.id
236 }
237}
238
239impl<I> Eq for SlabId<I> where I: SlabItem {}
240
241impl<I> PartialOrd for SlabId<I>
242where
243 I: SlabItem,
244{
245 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
246 Some(self.cmp(other))
247 }
248}
249
250impl<I> Ord for SlabId<I>
251where
252 I: SlabItem,
253{
254 fn cmp(&self, other: &Self) -> Ordering {
255 self.id.cmp(other)
256 }
257}
258
259impl<I> Hash for SlabId<I>
260where
261 I: SlabItem,
262{
263 fn hash<H: Hasher>(&self, state: &mut H) {
264 self.id.hash(state);
265 }
266}
267
268impl<I> Debug for SlabId<I>
269where
270 I: SlabItem,
271{
272 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
273 f.debug_struct("SlabId").field("id", &self.id).finish()
274 }
275}
276
277#[expect(
279 clippy::large_enum_variant,
280 reason = "See https://github.com/bevyengine/bevy/issues/19220"
281)]
282pub enum Slab<I>
283where
284 I: SlabItem,
285{
286 General(GeneralSlab<I>),
288 LargeObject(LargeObjectSlab<I>),
290}
291
292pub struct GeneralSlab<I>
298where
299 I: SlabItem,
300{
301 allocator: Allocator,
303
304 buffer: Option<Buffer>,
311
312 resident_allocations: HashMap<I::Key, SlabAllocation>,
316
317 pending_allocations: HashMap<I::Key, SlabAllocation>,
321
322 element_layout: I::Layout,
324
325 current_slot_capacity: u32,
327}
328
329pub struct LargeObjectSlab<I>
338where
339 I: SlabItem,
340{
341 buffer: Option<Buffer>,
345
346 element_layout: I::Layout,
348}
349
350struct SlabItemAllocation<I>
352where
353 I: SlabItem,
354{
355 slab_id: SlabId<I>,
357 slab_allocation: SlabAllocation,
359}
360
361impl<I> Slab<I>
362where
363 I: SlabItem,
364{
365 pub fn buffer(&self) -> Option<&Buffer> {
368 match self {
369 Slab::General(general_slab) => general_slab.buffer.as_ref(),
370 Slab::LargeObject(large_object_slab) => large_object_slab.buffer.as_ref(),
371 }
372 }
373
374 pub fn buffer_size(&self) -> u64 {
376 match self.buffer() {
377 Some(buffer) => buffer.size(),
378 None => 0,
379 }
380 }
381
382 pub fn element_layout(&self) -> &I::Layout {
384 match self {
385 Slab::General(general_slab) => &general_slab.element_layout,
386 Slab::LargeObject(large_object_slab) => &large_object_slab.element_layout,
387 }
388 }
389}
390
391pub struct AllocationStage<'a, I>
399where
400 I: SlabItem,
401{
402 pub allocator: &'a mut SlabAllocator<I>,
404 slabs_to_reallocate: HashMap<SlabId<I>, SlabToReallocate>,
406 empty_slabs: HashSet<SlabId<I>>,
409}
410
411impl<'a, I> Drop for AllocationStage<'a, I>
412where
413 I: SlabItem,
414{
415 fn drop(&mut self) {
416 if !self.slabs_to_reallocate.is_empty() || !self.empty_slabs.is_empty() {
417 error!(
418 "Dropping an `AllocationStage` with uncommitted reallocations or slab free \
419 operations. You should call `AllocationStage::commit`."
420 );
421 }
422 }
423}
424
425impl<'a, I> AllocationStage<'a, I>
426where
427 I: SlabItem,
428{
429 pub fn allocate(
436 &mut self,
437 key: &I::Key,
438 data_byte_len: u64,
439 layout: I::Layout,
440 settings: &SlabAllocatorSettings,
441 ) {
442 self.allocator
443 .free_existing_allocation(key, &mut self.empty_slabs);
444 self.allocator.allocate(
445 key,
446 data_byte_len,
447 layout,
448 &mut self.slabs_to_reallocate,
449 settings,
450 );
451 }
452
453 pub fn allocate_large(&mut self, key: &I::Key, layout: I::Layout) {
458 self.allocator
459 .free_existing_allocation(key, &mut self.empty_slabs);
460 self.allocator.allocate_large(key, layout);
461 }
462
463 pub fn commit(mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
465 self.allocator.free_empty_slabs(self.empty_slabs.drain());
469
470 for (slab_id, slab_to_grow) in self.slabs_to_reallocate.drain() {
471 if !self.allocator.slabs.contains_key(&slab_id) {
473 continue;
474 }
475 self.allocator
476 .reallocate_slab(render_device, render_queue, slab_id, slab_to_grow);
477 }
478 }
479}
480
481pub struct DeallocationStage<'a, I>
489where
490 I: SlabItem,
491{
492 pub allocator: &'a mut SlabAllocator<I>,
494 empty_slabs: HashSet<SlabId<I>>,
496}
497
498impl<'a, I> Drop for DeallocationStage<'a, I>
499where
500 I: SlabItem,
501{
502 fn drop(&mut self) {
503 if !self.empty_slabs.is_empty() {
504 error!(
505 "Dropping a `DeallocationStage` with uncommitted slab free operations. You should \
506 call `DeallocationStage::commit`."
507 );
508 }
509 }
510}
511
512impl<'a, I> DeallocationStage<'a, I>
513where
514 I: SlabItem,
515{
516 pub fn free(&mut self, key: &I::Key) {
521 self.allocator
522 .free_existing_allocation(key, &mut self.empty_slabs);
523 }
524
525 pub fn commit(mut self) {
529 self.allocator.free_empty_slabs(self.empty_slabs.drain());
530 }
531}
532
533#[derive(Clone)]
535struct SlabAllocation {
536 allocation: Allocation,
538 slot_count: u32,
540 padding: u32,
543}
544
545pub struct SlabAllocationBufferSlice<'a, I>
548where
549 I: SlabItem,
550{
551 pub buffer: &'a Buffer,
553
554 pub range: Range<u32>,
564
565 phantom: PhantomData<I>,
566}
567
568#[derive(Default)]
571pub struct SlabToReallocate {
572 old_slot_capacity: u32,
574}
575
576impl<I> Display for SlabId<I>
577where
578 I: SlabItem,
579{
580 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
581 Debug::fmt(&self.id, f)
582 }
583}
584
585impl<I> Default for SlabAllocator<I>
586where
587 I: SlabItem,
588{
589 fn default() -> Self {
590 Self {
591 slabs: HashMap::default(),
592 next_slab_id: SlabId {
593 id: NonMaxU32::default(),
594 phantom: PhantomData,
595 },
596 key_to_slab: HashMap::default(),
597 slab_layouts: HashMap::default(),
598 extra_buffer_usages: BufferUsages::empty(),
599 }
600 }
601}
602
603impl<I> SlabAllocator<I>
604where
605 I: SlabItem,
606{
607 pub fn new() -> Self {
609 Self::default()
610 }
611
612 pub fn stage_allocation(&'_ mut self) -> AllocationStage<'_, I> {
621 AllocationStage {
622 allocator: self,
623 slabs_to_reallocate: HashMap::default(),
624 empty_slabs: HashSet::default(),
625 }
626 }
627
628 pub fn stage_deallocation(&'_ mut self) -> DeallocationStage<'_, I> {
637 DeallocationStage {
638 allocator: self,
639 empty_slabs: HashSet::default(),
640 }
641 }
642
643 fn allocate(
646 &mut self,
647 key: &I::Key,
648 data_byte_len: u64,
649 layout: I::Layout,
650 slabs_to_grow: &mut HashMap<SlabId<I>, SlabToReallocate>,
651 settings: &SlabAllocatorSettings,
652 ) {
653 debug_assert!(!self.key_to_slab.contains_key(key));
654
655 let data_element_count = data_byte_len.div_ceil(layout.size()) as u32;
656 let data_slot_count = data_element_count.div_ceil(layout.elements_per_slot());
657 let padding = data_slot_count * layout.elements_per_slot() - data_element_count;
658
659 if data_slot_count as u64 * layout.slot_size()
661 >= settings.large_threshold.min(settings.max_slab_size)
662 {
663 self.allocate_large(key, layout);
664 } else {
665 self.allocate_general(
666 key,
667 data_slot_count,
668 padding,
669 layout,
670 slabs_to_grow,
671 settings,
672 );
673 }
674 }
675
676 fn allocate_general(
679 &mut self,
680 key: &I::Key,
681 data_slot_count: u32,
682 padding: u32,
683 layout: I::Layout,
684 slabs_to_grow: &mut HashMap<SlabId<I>, SlabToReallocate>,
685 settings: &SlabAllocatorSettings,
686 ) {
687 let candidate_slabs = self.slab_layouts.entry(layout.clone()).or_default();
688
689 let mut data_allocation = None;
693 for &slab_id in &*candidate_slabs {
694 let Some(Slab::General(slab)) = self.slabs.get_mut(&slab_id) else {
695 unreachable!("Slab not found")
696 };
697
698 let Some(allocation) = slab.allocator.allocate(data_slot_count) else {
699 continue;
700 };
701
702 match slab.grow_if_necessary(allocation.offset + data_slot_count, settings) {
704 SlabGrowthResult::NoGrowthNeeded => {}
705 SlabGrowthResult::NeededGrowth(slab_to_reallocate) => {
706 if let Entry::Vacant(vacant_entry) = slabs_to_grow.entry(slab_id) {
712 vacant_entry.insert(slab_to_reallocate);
713 }
714 }
715 SlabGrowthResult::CantGrow => continue,
716 }
717
718 data_allocation = Some(SlabItemAllocation {
719 slab_id,
720 slab_allocation: SlabAllocation {
721 allocation,
722 slot_count: data_slot_count,
723 padding,
724 },
725 });
726 break;
727 }
728
729 if data_allocation.is_none() {
731 let new_slab_id = self.next_slab_id;
732 self.next_slab_id.id =
733 NonMaxU32::new(self.next_slab_id.id.get() + 1).unwrap_or_default();
734
735 let new_slab = GeneralSlab::new(
736 new_slab_id,
737 &mut data_allocation,
738 settings,
739 layout,
740 data_slot_count,
741 padding,
742 );
743
744 self.slabs.insert(new_slab_id, Slab::General(new_slab));
745 candidate_slabs.push(new_slab_id);
746 slabs_to_grow.insert(new_slab_id, SlabToReallocate::default());
747 }
748
749 let data_allocation = data_allocation.expect("Should have been able to allocate");
750
751 if let Some(Slab::General(general_slab)) = self.slabs.get_mut(&data_allocation.slab_id) {
755 general_slab
756 .pending_allocations
757 .insert(key.clone(), data_allocation.slab_allocation);
758 };
759
760 self.record_allocation(key, data_allocation.slab_id);
761 }
762
763 fn allocate_large(&mut self, key: &I::Key, layout: I::Layout) {
765 let new_slab_id = self.next_slab_id;
766 self.next_slab_id.id = NonMaxU32::new(self.next_slab_id.id.get() + 1).unwrap_or_default();
767
768 self.record_allocation(key, new_slab_id);
769
770 self.slabs.insert(
771 new_slab_id,
772 Slab::LargeObject(LargeObjectSlab {
773 buffer: None,
774 element_layout: layout,
775 }),
776 );
777 }
778
779 fn free_existing_allocation(&mut self, key: &I::Key, empty_slabs: &mut HashSet<SlabId<I>>) {
784 if let Some(slab_id) = self.key_to_slab.remove(key) {
785 self.free_allocation_in_slab(key, slab_id, empty_slabs);
786 }
787 }
788
789 fn free_allocation_in_slab(
795 &mut self,
796 key: &I::Key,
797 slab_id: SlabId<I>,
798 empty_slabs: &mut HashSet<SlabId<I>>,
799 ) {
800 let Some(slab) = self.slabs.get_mut(&slab_id) else {
801 error!("Double free: attempted to free data in a nonexistent slab");
802 return;
803 };
804
805 match *slab {
806 Slab::General(ref mut general_slab) => {
807 let Some(slab_allocation) = general_slab
808 .resident_allocations
809 .remove(key)
810 .or_else(|| general_slab.pending_allocations.remove(key))
811 else {
812 return;
813 };
814
815 general_slab.allocator.free(slab_allocation.allocation);
816
817 if general_slab.is_empty() {
818 empty_slabs.insert(slab_id);
819 }
820 }
821 Slab::LargeObject(_) => {
822 empty_slabs.insert(slab_id);
823 }
824 }
825 }
826
827 fn reallocate_slab(
835 &mut self,
836 render_device: &RenderDevice,
837 render_queue: &RenderQueue,
838 slab_id: SlabId<I>,
839 slab_to_grow: SlabToReallocate,
840 ) {
841 let Some(Slab::General(slab)) = self.slabs.get_mut(&slab_id) else {
842 error!("Couldn't find slab {} to grow", slab_id);
843 return;
844 };
845
846 let old_buffer = slab.buffer.take();
847
848 let buffer_usages =
849 BufferUsages::COPY_SRC | BufferUsages::COPY_DST | slab.element_layout.buffer_usages();
850
851 let new_buffer = render_device.create_buffer(&BufferDescriptor {
853 label: Some(&format!(
854 "general {} slab {} ({}buffer)",
855 I::label(),
856 slab_id,
857 buffer_usages_to_str(buffer_usages)
858 )),
859 size: slab.current_slot_capacity as u64 * slab.element_layout.slot_size(),
860 usage: buffer_usages | self.extra_buffer_usages,
861 mapped_at_creation: false,
862 });
863
864 slab.buffer = Some(new_buffer.clone());
865
866 let Some(old_buffer) = old_buffer else { return };
867
868 let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {
870 label: Some(&*format!("{} slab resize encoder", I::label())),
871 });
872
873 encoder.copy_buffer_to_buffer(
875 &old_buffer,
876 0,
877 &new_buffer,
878 0,
879 slab_to_grow.old_slot_capacity as u64 * slab.element_layout.slot_size(),
880 );
881
882 let command_buffer = encoder.finish();
883 render_queue.submit([command_buffer]);
884 }
885
886 fn record_allocation(&mut self, key: &I::Key, slab_id: SlabId<I>) {
889 self.key_to_slab.insert(key.clone(), slab_id);
890 }
891
892 pub fn buffer_for_slab(&self, slab_id: SlabId<I>) -> Option<&Buffer> {
895 self.slabs.get(&slab_id).and_then(|slab| slab.buffer())
896 }
897
898 pub fn slab_allocation_slice(
901 &self,
902 key: &I::Key,
903 slab_id: SlabId<I>,
904 ) -> Option<SlabAllocationBufferSlice<'_, I>> {
905 match self.slabs.get(&slab_id)? {
906 Slab::General(general_slab) => {
907 let slab_allocation = general_slab.resident_allocations.get(key)?;
908 Some(SlabAllocationBufferSlice {
909 buffer: general_slab.buffer.as_ref()?,
910 range: (slab_allocation.allocation.offset
911 * general_slab.element_layout.elements_per_slot())
912 ..((slab_allocation.allocation.offset + slab_allocation.slot_count)
913 * general_slab.element_layout.elements_per_slot())
914 - slab_allocation.padding,
915 phantom: PhantomData,
916 })
917 }
918
919 Slab::LargeObject(large_object_slab) => {
920 let buffer = large_object_slab.buffer.as_ref()?;
921 Some(SlabAllocationBufferSlice {
922 buffer,
923 range: 0..((buffer.size() / large_object_slab.element_layout.size()) as u32),
924 phantom: PhantomData,
925 })
926 }
927 }
928 }
929
930 fn free_empty_slabs(&mut self, empty_slabs: impl Iterator<Item = SlabId<I>>) {
931 for empty_slab in empty_slabs {
932 if let Some(Slab::General(general_slab)) = self.slabs.get(&empty_slab)
936 && !general_slab.is_empty()
937 {
938 continue;
939 }
940
941 self.slab_layouts.values_mut().for_each(|slab_ids| {
942 let idx = slab_ids.iter().position(|&slab_id| slab_id == empty_slab);
943 if let Some(idx) = idx {
944 slab_ids.remove(idx);
945 }
946 });
947 self.slabs.remove(&empty_slab);
948 }
949 }
950
951 pub fn slab_count(&self) -> usize {
953 self.slabs.len()
954 }
955
956 pub fn slabs_size(&self) -> u64 {
958 self.slabs.iter().map(|slab| slab.1.buffer_size()).sum()
959 }
960
961 pub fn copy_element_data(
967 &mut self,
968 key: &I::Key,
969 len: usize,
970 fill_data: impl Fn(WriteOnly<[u8]>),
971 render_device: &RenderDevice,
972 render_queue: &RenderQueue,
973 ) {
974 let Some(slab_id) = self.key_to_slab.get(key) else {
975 error!("Use-after-free: attempted to copy element data for an unallocated key");
976 return;
977 };
978 let Some(slab) = self.slabs.get_mut(slab_id) else {
979 error!("Use-after-free: attempted to copy element data into a nonexistent slab");
980 return;
981 };
982
983 match *slab {
984 Slab::General(ref mut general_slab) => {
985 let (Some(buffer), Some(allocated_range)) = (
986 &general_slab.buffer,
987 general_slab.pending_allocations.remove(key),
988 ) else {
989 return;
990 };
991
992 let slot_size = general_slab.element_layout.slot_size();
993
994 if let Some(size) = BufferSize::new((len as u64).next_multiple_of(slot_size)) {
997 if let Some(mut buffer) = render_queue.write_buffer_with(
999 buffer,
1000 allocated_range.allocation.offset as u64 * slot_size,
1001 size,
1002 ) {
1003 let slice = buffer.slice(..len);
1004 fill_data(slice);
1005 }
1006 }
1007
1008 general_slab
1010 .resident_allocations
1011 .insert(key.clone(), allocated_range);
1012 }
1013
1014 Slab::LargeObject(ref mut large_object_slab) => {
1015 debug_assert!(large_object_slab.buffer.is_none());
1016
1017 let buffer_usages = large_object_slab.element_layout.buffer_usages();
1019 let buffer = render_device.create_buffer(&BufferDescriptor {
1020 label: Some(&format!(
1021 "large {} slab {} ({}buffer)",
1022 I::label(),
1023 slab_id,
1024 buffer_usages_to_str(buffer_usages)
1025 )),
1026 size: len as u64,
1027 usage: buffer_usages | BufferUsages::COPY_DST,
1028 mapped_at_creation: true,
1029 });
1030 {
1031 let mut slice = buffer.slice(..).get_mapped_range_mut();
1032
1033 fill_data(slice.slice(..len));
1034 }
1035 buffer.unmap();
1036 large_object_slab.buffer = Some(buffer);
1037 }
1038 }
1039 }
1040}
1041
1042enum SlabGrowthResult {
1044 NoGrowthNeeded,
1046 NeededGrowth(SlabToReallocate),
1050 CantGrow,
1052}
1053
1054impl<I> GeneralSlab<I>
1055where
1056 I: SlabItem,
1057{
1058 fn new(
1061 new_slab_id: SlabId<I>,
1062 maybe_slab_item_allocation: &mut Option<SlabItemAllocation<I>>,
1063 settings: &SlabAllocatorSettings,
1064 layout: I::Layout,
1065 data_slot_count: u32,
1066 padding: u32,
1067 ) -> GeneralSlab<I> {
1068 let initial_slab_slot_capacity = (settings.min_slab_size.div_ceil(layout.slot_size())
1069 as u32)
1070 .max(offset_allocator::ext::min_allocator_size(data_slot_count));
1071 let max_slab_slot_capacity = (settings.max_slab_size.div_ceil(layout.slot_size()) as u32)
1072 .max(offset_allocator::ext::min_allocator_size(data_slot_count));
1073
1074 let mut new_slab = GeneralSlab {
1075 allocator: Allocator::new(max_slab_slot_capacity),
1076 buffer: None,
1077 resident_allocations: HashMap::default(),
1078 pending_allocations: HashMap::default(),
1079 element_layout: layout,
1080 current_slot_capacity: initial_slab_slot_capacity,
1081 };
1082
1083 if let Some(allocation) = new_slab.allocator.allocate(data_slot_count) {
1085 *maybe_slab_item_allocation = Some(SlabItemAllocation {
1086 slab_id: new_slab_id,
1087 slab_allocation: SlabAllocation {
1088 slot_count: data_slot_count,
1089 allocation,
1090 padding,
1091 },
1092 });
1093 }
1094
1095 new_slab
1096 }
1097
1098 fn grow_if_necessary(
1104 &mut self,
1105 new_size_in_slots: u32,
1106 settings: &SlabAllocatorSettings,
1107 ) -> SlabGrowthResult {
1108 let initial_slot_capacity = self.current_slot_capacity;
1110 if self.current_slot_capacity >= new_size_in_slots {
1111 return SlabGrowthResult::NoGrowthNeeded;
1112 }
1113
1114 while self.current_slot_capacity < new_size_in_slots {
1117 let new_slab_slot_capacity =
1118 ((self.current_slot_capacity as f64 * settings.growth_factor).ceil() as u32)
1119 .min((settings.max_slab_size / self.element_layout.slot_size()) as u32);
1120 if new_slab_slot_capacity == self.current_slot_capacity {
1121 return SlabGrowthResult::CantGrow;
1123 }
1124
1125 self.current_slot_capacity = new_slab_slot_capacity;
1126 }
1127
1128 SlabGrowthResult::NeededGrowth(SlabToReallocate {
1130 old_slot_capacity: initial_slot_capacity,
1131 })
1132 }
1133
1134 fn is_empty(&self) -> bool {
1136 self.resident_allocations.is_empty() && self.pending_allocations.is_empty()
1137 }
1138}
1139
1140fn buffer_usages_to_str(buffer_usages: BufferUsages) -> &'static str {
1142 if buffer_usages.contains(BufferUsages::VERTEX) {
1143 "vertex "
1144 } else if buffer_usages.contains(BufferUsages::INDEX) {
1145 "index "
1146 } else if buffer_usages.contains(BufferUsages::STORAGE) {
1147 "storage "
1148 } else {
1149 ""
1150 }
1151}
1152
1153#[cfg(test)]
1154mod tests {
1155 use super::*;
1156 use crate::test_utils::create_dummy_device;
1157
1158 struct TestItem;
1160
1161 impl SlabItem for TestItem {
1162 type Key = u32;
1163 type Layout = TestLayout;
1164
1165 fn label() -> Cow<'static, str> {
1166 "test".into()
1167 }
1168 }
1169
1170 #[derive(Clone, PartialEq, Eq, Hash)]
1172 struct TestLayout;
1173
1174 impl SlabItemLayout for TestLayout {
1175 fn size(&self) -> u64 {
1176 4
1177 }
1178
1179 fn elements_per_slot(&self) -> u32 {
1180 1
1181 }
1182
1183 fn buffer_usages(&self) -> BufferUsages {
1184 BufferUsages::VERTEX
1185 }
1186 }
1187
1188 fn test_settings() -> SlabAllocatorSettings {
1190 SlabAllocatorSettings {
1191 min_slab_size: 1024,
1193 max_slab_size: 4096,
1195 large_threshold: 4096,
1196 growth_factor: 1.5,
1197 }
1198 }
1199
1200 fn allocate_round(
1204 allocator: &mut SlabAllocator<TestItem>,
1205 keys: impl Iterator<Item = u32> + Clone,
1206 byte_len: u64,
1207 device: &RenderDevice,
1208 queue: &RenderQueue,
1209 ) {
1210 let mut stage = allocator.stage_allocation();
1211 for key in keys.clone() {
1212 stage.allocate(&key, byte_len, TestLayout, &test_settings());
1213 }
1214 stage.commit(device, queue);
1215
1216 for key in keys {
1217 allocator.copy_element_data(&key, byte_len as usize, |_| {}, device, queue);
1218 }
1219 }
1220
1221 #[test]
1224 fn reallocating_a_live_key_frees_the_old_allocation() {
1225 let (device, queue) = create_dummy_device();
1226 let mut allocator = SlabAllocator::<TestItem>::new();
1227
1228 allocate_round(&mut allocator, 0..8, 256, &device, &queue);
1229
1230 let baseline_size = allocator.slabs_size();
1231 let baseline_slabs = allocator.slab_count();
1232 assert!(baseline_size > 0, "nothing was allocated");
1233
1234 for _ in 0..32 {
1236 allocate_round(&mut allocator, 0..8, 256, &device, &queue);
1237 }
1238
1239 assert_eq!(
1240 allocator.slabs_size(),
1241 baseline_size,
1242 "reallocating live keys grew the slabs, so old allocations leaked"
1243 );
1244 assert_eq!(allocator.slab_count(), baseline_slabs);
1245 assert_eq!(allocator.key_to_slab.len(), 8);
1246
1247 for key in 0..8u32 {
1249 let slab_id = allocator.key_to_slab[&key];
1250 assert!(allocator.slab_allocation_slice(&key, slab_id).is_some());
1251 }
1252 }
1253
1254 #[test]
1257 fn slab_refilled_during_allocation_is_not_freed() {
1258 let (device, queue) = create_dummy_device();
1259 let mut allocator = SlabAllocator::<TestItem>::new();
1260
1261 allocate_round(&mut allocator, 0..1, 256, &device, &queue);
1262 let slab_id = allocator.key_to_slab[&0];
1263
1264 allocate_round(&mut allocator, 0..1, 256, &device, &queue);
1267
1268 assert_eq!(allocator.slab_count(), 1, "the live slab was destroyed");
1269 assert_eq!(allocator.key_to_slab[&0], slab_id);
1270 assert!(allocator.slab_allocation_slice(&0, slab_id).is_some());
1271 }
1272
1273 fn allocate_large_round(
1276 allocator: &mut SlabAllocator<TestItem>,
1277 keys: impl Iterator<Item = u32>,
1278 device: &RenderDevice,
1279 queue: &RenderQueue,
1280 ) {
1281 let mut stage = allocator.stage_allocation();
1282 for key in keys {
1283 stage.allocate_large(&key, TestLayout);
1284 }
1285 stage.commit(device, queue);
1286 }
1287
1288 #[test]
1292 fn reallocating_a_live_key_with_allocate_large_frees_the_old_allocation() {
1293 let (device, queue) = create_dummy_device();
1294 let mut allocator = SlabAllocator::<TestItem>::new();
1295
1296 allocate_large_round(&mut allocator, 0..4, &device, &queue);
1297 assert_eq!(allocator.slab_count(), 4);
1298
1299 for _ in 0..32 {
1300 allocate_large_round(&mut allocator, 0..4, &device, &queue);
1301 }
1302
1303 assert_eq!(
1304 allocator.slab_count(),
1305 4,
1306 "reallocating live keys left the previous dedicated slabs behind"
1307 );
1308 assert_eq!(allocator.key_to_slab.len(), 4);
1309 }
1310
1311 #[test]
1314 fn slab_emptied_during_allocation_is_freed() {
1315 let (device, queue) = create_dummy_device();
1316 let mut allocator = SlabAllocator::<TestItem>::new();
1317
1318 allocate_round(&mut allocator, 0..1, 256, &device, &queue);
1319 assert_eq!(allocator.slab_count(), 1);
1320
1321 allocate_round(&mut allocator, 0..1, 8192, &device, &queue);
1324
1325 assert_eq!(
1326 allocator.slab_count(),
1327 1,
1328 "the emptied general slab was not reclaimed"
1329 );
1330 let slab_id = allocator.key_to_slab[&0];
1331 assert!(matches!(
1332 allocator.slabs.get(&slab_id),
1333 Some(Slab::LargeObject(_))
1334 ));
1335 }
1336}