bevy_render/slab_allocator.rs
1//! A general-purpose allocator that manages a set of GPU buffer slabs.
2
3use 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
23/// A general-purpose allocator that manages a set of GPU buffer slabs.
24///
25/// You can use this allocator to pack data that needs to be accessible by the
26/// GPU into a small set of buffers, known as *slabs*. Each individual slab is
27/// expected to contain homogeneous data of a single type. However, you can use
28/// a single allocator to manage multiple slabs, each of which can have a
29/// different data layout. Objects managed by the allocator are referenced with
30/// a *key* that you can define.
31///
32/// To use this allocator, implement the [`SlabItem`] trait; see the
33/// documentation of that trait for details.
34///
35/// For performance, you'll want to batch your allocation and deallocation
36/// operations to be performed at a single point in the frame. To perform
37/// allocation, call [`Self::stage_allocation`] to obtain an
38/// [`AllocationStage`], call [`AllocationStage::allocate`] to allocate
39/// individual objects, and then *commit* the allocation transaction using
40/// [`AllocationStage::commit`]. Likewise, to perform deallocation, call
41/// [`Self::stage_deallocation`] to obtain a [`DeallocationStage`], call
42/// [`DeallocationStage::free`] to free objects, and then call
43/// [`DeallocationStage::commit`]. Once you've committed an allocation stage,
44/// you can copy new data into the slabs via [`Self::copy_element_data`].
45///
46/// Within each slab, or hardware buffer, the underlying allocation algorithm
47/// is [`offset_allocator`], a Rust port of Sebastian Aaltonen's hard-real-time
48/// C++ `OffsetAllocator`. Slabs start small and then grow as their contents
49/// fill up, up to a maximum size limit. To reduce fragmentation, objects that
50/// are too large bypass this system and receive their own buffers.
51///
52/// The [`SlabAllocatorSettings`] allows you to tune the behavior of the
53/// allocator for better performance with your use case.
54///
55/// See [`crate::mesh::allocator::MeshAllocator`] for an example of usage.
56pub struct SlabAllocator<I>
57where
58 I: SlabItem,
59{
60 /// Holds all buffers and allocators.
61 pub slabs: HashMap<SlabId<I>, Slab<I>>,
62
63 /// The next slab ID to assign.
64 next_slab_id: SlabId<I>,
65
66 /// Maps slab allocation keys to the ID of the slabs that hold their data.
67 pub key_to_slab: HashMap<I::Key, SlabId<I>>,
68
69 /// Maps a layout to the slabs that hold elements of that layout.
70 ///
71 /// This is used when allocating, so that we can find the appropriate slab
72 /// to place an object in.
73 slab_layouts: HashMap<I::Layout, Vec<SlabId<I>>>,
74
75 /// Additional buffer usages to add to any vertex or index buffers created.
76 pub extra_buffer_usages: BufferUsages,
77
78 /// Keys that were resident in a slab whose [`Buffer`] was replaced by growth since
79 /// [`Self::clear_displaced_keys`] was last called.
80 displaced_keys: Vec<I::Key>,
81}
82
83/// Describes the type of the data that a [`SlabAllocator`] will store.
84///
85/// The actual type that you implement this trait on doesn't matter; only the
86/// associated types [`Self::Key`] and [`Self::Layout`] do. Typically, you
87/// implement this trait on a unit struct.
88///
89/// See [`crate::mesh::allocator::MeshSlabItem`] for an example of usage.
90pub trait SlabItem {
91 /// The key that's used to look up items in the allocator.
92 type Key: Clone + PartialEq + Eq + Hash;
93
94 /// A type that describes the layout of items within a single slab.
95 ///
96 /// If this slab allocator only allocates items of a single type, this type
97 /// can simply be a unit struct. However, if you wish to have a single slab
98 /// allocator that manages slabs of differing types, you can store metadata
99 /// within values of this type that describes the size and alignment
100 /// requirements of the objects within the slab. Each slab that the slab
101 /// allocator manages contains an instance of this value so that it can
102 /// track size and alignment requirements for that slab.
103 type Layout: SlabItemLayout;
104
105 /// Returns a suitable debugging label describing the type of elements that
106 /// this slab item stores.
107 fn label() -> Cow<'static, str>;
108}
109
110/// A trait that defines information necessary to determine the size and
111/// alignment of objects within a slab.
112pub trait SlabItemLayout: Clone + PartialEq + Eq + Hash {
113 /// The size in bytes of a single element.
114 ///
115 /// This is the smallest size that this allocator can allocate, and all
116 /// allocations must have a byte size that is a multiple of this value.
117 fn size(&self) -> u64;
118
119 /// The number of elements that make up a single slot.
120 fn elements_per_slot(&self) -> u32;
121
122 /// The `wgpu` buffer usages that the slab allocator will specify when
123 /// creating buffers.
124 ///
125 /// `BufferUsages::COPY_DST` and `BufferUsages::COPY_SRC` are always
126 /// included, regardless of what you specify here.
127 fn buffer_usages(&self) -> BufferUsages;
128}
129
130/// Internal helper methods for [`SlabItemLayout`]s.
131trait SlabItemLayoutExt {
132 /// Returns the size in bytes of a single slot.
133 fn slot_size(&self) -> u64;
134}
135
136impl<I> SlabItemLayoutExt for I
137where
138 I: SlabItemLayout,
139{
140 fn slot_size(&self) -> u64 {
141 self.size() * self.elements_per_slot() as u64
142 }
143}
144
145/// Tunable parameters that customize the behavior of the allocator.
146///
147/// Generally, these parameters adjust the tradeoff between memory fragmentation
148/// and performance. You can adjust them as desired for your application. Most
149/// applications can stick with the default values.
150pub struct SlabAllocatorSettings {
151 /// The minimum size of a slab (hardware buffer), in bytes.
152 ///
153 /// The default value is 1 MiB.
154 pub min_slab_size: u64,
155
156 /// The maximum size of a slab (hardware buffer), in bytes.
157 ///
158 /// When a slab reaches this limit, a new slab is created.
159 ///
160 /// The default value is 512 MiB.
161 pub max_slab_size: u64,
162
163 /// The maximum size of vertex or index data that can be placed in a general
164 /// slab, in bytes.
165 ///
166 /// If an allocation exceeds this size limit, that data is placed in its own
167 /// slab. This reduces fragmentation at the cost of more buffer management
168 /// overhead.
169 ///
170 /// The default value is 256 MiB.
171 pub large_threshold: u64,
172
173 /// The factor by which we scale a slab when growing it.
174 ///
175 /// This value must be greater than 1. Higher values result in more
176 /// fragmentation but fewer expensive copy operations when growing the
177 /// buffer.
178 ///
179 /// The default value is 1.5.
180 pub growth_factor: f64,
181}
182
183impl Default for SlabAllocatorSettings {
184 fn default() -> Self {
185 Self {
186 // 1 MiB
187 min_slab_size: 1024 * 1024,
188 // 512 MiB
189 max_slab_size: 1024 * 1024 * 512,
190 // 256 MiB
191 large_threshold: 1024 * 1024 * 256,
192 // 1.5× growth
193 growth_factor: 1.5,
194 }
195 }
196}
197
198/// The index of a single slab.
199#[derive(Deref, DerefMut)]
200#[repr(transparent)]
201pub struct SlabId<I>
202where
203 I: SlabItem,
204{
205 /// A value that represents the ID of the slab.
206 #[deref]
207 pub id: NonMaxU32,
208 phantom: PhantomData<I>,
209}
210
211impl<I> Clone for SlabId<I>
212where
213 I: SlabItem,
214{
215 fn clone(&self) -> Self {
216 *self
217 }
218}
219
220impl<I> Copy for SlabId<I> where I: SlabItem {}
221
222impl<I> Default for SlabId<I>
223where
224 I: SlabItem,
225{
226 fn default() -> Self {
227 SlabId {
228 id: NonMaxU32::default(),
229 phantom: PhantomData,
230 }
231 }
232}
233
234impl<I> PartialEq for SlabId<I>
235where
236 I: SlabItem,
237{
238 fn eq(&self, other: &Self) -> bool {
239 self.id == other.id
240 }
241}
242
243impl<I> Eq for SlabId<I> where I: SlabItem {}
244
245impl<I> PartialOrd for SlabId<I>
246where
247 I: SlabItem,
248{
249 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
250 Some(self.cmp(other))
251 }
252}
253
254impl<I> Ord for SlabId<I>
255where
256 I: SlabItem,
257{
258 fn cmp(&self, other: &Self) -> Ordering {
259 self.id.cmp(other)
260 }
261}
262
263impl<I> Hash for SlabId<I>
264where
265 I: SlabItem,
266{
267 fn hash<H: Hasher>(&self, state: &mut H) {
268 self.id.hash(state);
269 }
270}
271
272impl<I> Debug for SlabId<I>
273where
274 I: SlabItem,
275{
276 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
277 f.debug_struct("SlabId").field("id", &self.id).finish()
278 }
279}
280
281/// Data for a single slab.
282#[expect(
283 clippy::large_enum_variant,
284 reason = "See https://github.com/bevyengine/bevy/issues/19220"
285)]
286pub enum Slab<I>
287where
288 I: SlabItem,
289{
290 /// A slab that can contain multiple objects.
291 General(GeneralSlab<I>),
292 /// A slab that contains a single object.
293 LargeObject(LargeObjectSlab<I>),
294}
295
296/// A resizable slab that can contain multiple objects.
297///
298/// This is the normal type of slab used for objects that are below the
299/// [`SlabAllocatorSettings::large_threshold`]. Slabs are divided into *slots*,
300/// which are described in detail in the [`SlabItemLayout`] documentation.
301pub struct GeneralSlab<I>
302where
303 I: SlabItem,
304{
305 /// The [`Allocator`] that manages the objects in this slab.
306 allocator: Allocator,
307
308 /// The GPU buffer that backs this slab.
309 ///
310 /// This may be `None` if the buffer hasn't been created yet. We delay
311 /// creation of buffers until performing all the allocations for a single
312 /// frame, so that we don't needlessly create and resize buffers when many
313 /// objects are allocated all at once.
314 buffer: Option<Buffer>,
315
316 /// Allocations that are on the GPU.
317 ///
318 /// The range is in slots.
319 resident_allocations: HashMap<I::Key, SlabAllocation>,
320
321 /// Allocations that are waiting to be uploaded to the GPU.
322 ///
323 /// The range is in slots.
324 pending_allocations: HashMap<I::Key, SlabAllocation>,
325
326 /// The layout of a single element (vertex or index).
327 element_layout: I::Layout,
328
329 /// The size of this slab in slots.
330 current_slot_capacity: u32,
331}
332
333/// A slab that contains a single object.
334///
335/// Typically, this is for objects that exceed the
336/// [`SlabAllocatorSettings::large_threshold`]. Additionally, some uses of the
337/// slab allocator may wish to force objects to possess their own slab. For
338/// instance, due to platform limitations (vertex arrays on WebGL 2), the mesh
339/// allocator sometimes needs to place meshes that would otherwise be allocated
340/// together with other meshes in their own slab.
341pub struct LargeObjectSlab<I>
342where
343 I: SlabItem,
344{
345 /// The GPU buffer that backs this slab.
346 ///
347 /// This may be `None` if the buffer hasn't been created yet.
348 buffer: Option<Buffer>,
349
350 /// The layout of a single element (vertex or index).
351 element_layout: I::Layout,
352}
353
354/// The location of an allocation and the slab it's contained in.
355struct SlabItemAllocation<I>
356where
357 I: SlabItem,
358{
359 /// The ID of the slab.
360 slab_id: SlabId<I>,
361 /// Holds the actual allocation.
362 slab_allocation: SlabAllocation,
363}
364
365impl<I> Slab<I>
366where
367 I: SlabItem,
368{
369 /// Returns the GPU buffer corresponding to this slab, if it's been
370 /// uploaded.
371 pub fn buffer(&self) -> Option<&Buffer> {
372 match self {
373 Slab::General(general_slab) => general_slab.buffer.as_ref(),
374 Slab::LargeObject(large_object_slab) => large_object_slab.buffer.as_ref(),
375 }
376 }
377
378 /// Returns the size of this slab in bytes.
379 pub fn buffer_size(&self) -> u64 {
380 match self.buffer() {
381 Some(buffer) => buffer.size(),
382 None => 0,
383 }
384 }
385
386 /// Returns the [`SlabItemLayout`] associated with this slab.
387 pub fn element_layout(&self) -> &I::Layout {
388 match self {
389 Slab::General(general_slab) => &general_slab.element_layout,
390 Slab::LargeObject(large_object_slab) => &large_object_slab.element_layout,
391 }
392 }
393}
394
395/// An object that allows batched allocation.
396///
397/// In order to perform allocations, you create one of these objects with
398/// [`SlabAllocator::stage_allocation`], allocate into it with
399/// [`Self::allocate`], and finally commit it with [`Self::commit`]. Always
400/// make sure to call [`Self::commit`]; if you don't, buffers that were
401/// supposed to be enlarged won't be.
402pub struct AllocationStage<'a, I>
403where
404 I: SlabItem,
405{
406 /// The allocator that we're allocating objects into.
407 pub allocator: &'a mut SlabAllocator<I>,
408 /// The set of slabs that have grown and need to be reallocated.
409 slabs_to_reallocate: HashMap<SlabId<I>, SlabToReallocate>,
410 /// IDs of slabs that became empty because everything in them was
411 /// reallocated elsewhere.
412 empty_slabs: HashSet<SlabId<I>>,
413}
414
415impl<'a, I> Drop for AllocationStage<'a, I>
416where
417 I: SlabItem,
418{
419 fn drop(&mut self) {
420 if !self.slabs_to_reallocate.is_empty() || !self.empty_slabs.is_empty() {
421 error!(
422 "Dropping an `AllocationStage` with uncommitted reallocations or slab free \
423 operations. You should call `AllocationStage::commit`."
424 );
425 }
426 }
427}
428
429impl<'a, I> AllocationStage<'a, I>
430where
431 I: SlabItem,
432{
433 /// Allocates space for an object of the given size with the given key and layout.
434 ///
435 /// If the key already corresponds to a live allocation, that allocation is
436 /// freed first. Prefer freeing it through a [`DeallocationStage`] before the
437 /// allocation stage begins, so that the allocator has every one of the
438 /// frame's holes to choose from rather than just this object's.
439 pub fn allocate(
440 &mut self,
441 key: &I::Key,
442 data_byte_len: u64,
443 layout: I::Layout,
444 settings: &SlabAllocatorSettings,
445 ) {
446 self.allocator
447 .free_existing_allocation(key, &mut self.empty_slabs);
448 self.allocator.allocate(
449 key,
450 data_byte_len,
451 layout,
452 &mut self.slabs_to_reallocate,
453 settings,
454 );
455 }
456
457 /// Allocates an object into its own dedicated slab.
458 ///
459 /// As with [`Self::allocate`], a live allocation under the same key is freed
460 /// first.
461 pub fn allocate_large(&mut self, key: &I::Key, layout: I::Layout) {
462 self.allocator
463 .free_existing_allocation(key, &mut self.empty_slabs);
464 self.allocator.allocate_large(key, layout);
465 }
466
467 /// Completes the transaction, performing any queued resize operations.
468 pub fn commit(mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {
469 // Drop slabs that were emptied by their contents being reallocated
470 // elsewhere. Do this before growing anything, so that we never create a
471 // buffer for a slab we're about to throw away.
472 self.allocator.free_empty_slabs(self.empty_slabs.drain());
473
474 for (slab_id, slab_to_grow) in self.slabs_to_reallocate.drain() {
475 // The slab may have been freed just above.
476 if !self.allocator.slabs.contains_key(&slab_id) {
477 continue;
478 }
479 self.allocator
480 .reallocate_slab(render_device, render_queue, slab_id, slab_to_grow);
481 }
482 }
483}
484
485/// An object that enables batched deallocation.
486///
487/// To free objects from a [`SlabAllocator`], call
488/// [`SlabAllocator::stage_deallocation`] to create a [`DeallocationStage`],
489/// call [`Self::free`] to deallocate objects, and finally call
490/// [`Self::commit`]. You must call [`Self::commit`] in order to ensure that
491/// newly-empty slabs are deallocated.
492pub struct DeallocationStage<'a, I>
493where
494 I: SlabItem,
495{
496 /// The allocator in which objects are to be freed.
497 pub allocator: &'a mut SlabAllocator<I>,
498 /// IDs of slabs that have become empty.
499 empty_slabs: HashSet<SlabId<I>>,
500}
501
502impl<'a, I> Drop for DeallocationStage<'a, I>
503where
504 I: SlabItem,
505{
506 fn drop(&mut self) {
507 if !self.empty_slabs.is_empty() {
508 error!(
509 "Dropping a `DeallocationStage` with uncommitted slab free operations. You should \
510 call `DeallocationStage::commit`."
511 );
512 }
513 }
514}
515
516impl<'a, I> DeallocationStage<'a, I>
517where
518 I: SlabItem,
519{
520 /// Schedules a free operation for the allocation with the given key.
521 ///
522 /// Freeing a key that holds no allocation is a no-op, so callers are free to
523 /// speculatively free keys that may never have been allocated.
524 pub fn free(&mut self, key: &I::Key) {
525 self.allocator
526 .free_existing_allocation(key, &mut self.empty_slabs);
527 }
528
529 /// Performs all the free operations.
530 ///
531 /// You must call this method if you called [`Self::free`].
532 pub fn commit(mut self) {
533 self.allocator.free_empty_slabs(self.empty_slabs.drain());
534 }
535}
536
537/// An allocation within a slab.
538#[derive(Clone)]
539struct SlabAllocation {
540 /// The actual [`Allocator`] handle, needed to free the allocation.
541 allocation: Allocation,
542 /// The number of slots that this allocation takes up.
543 slot_count: u32,
544 /// The number of slots at the end of the allocation that are considered
545 /// padding.
546 padding: u32,
547}
548
549/// The hardware buffer that slab-allocated data lives in, as well as the range
550/// within that buffer.
551pub struct SlabAllocationBufferSlice<'a, I>
552where
553 I: SlabItem,
554{
555 /// The buffer that the data resides in.
556 pub buffer: &'a Buffer,
557
558 /// The range of elements within this buffer that the data resides in,
559 /// measured in elements.
560 ///
561 /// This is an element range, not a byte range. For vertex data, this is
562 /// measured in increments of a single vertex. (Thus, if a vertex is 32
563 /// bytes long, then this range is in units of 32 bytes each.) For index
564 /// data, this is measured in increments of a single index value (2 or 4
565 /// bytes). Draw commands generally take their ranges in elements, not
566 /// bytes, so this is the most convenient unit in this case.
567 pub range: Range<u32>,
568
569 phantom: PhantomData<I>,
570}
571
572/// Holds information about a slab that's scheduled to be allocated or
573/// reallocated.
574#[derive(Default)]
575pub struct SlabToReallocate {
576 /// The capacity of the slab before we decided to grow it.
577 old_slot_capacity: u32,
578}
579
580impl<I> Display for SlabId<I>
581where
582 I: SlabItem,
583{
584 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
585 Debug::fmt(&self.id, f)
586 }
587}
588
589impl<I> Default for SlabAllocator<I>
590where
591 I: SlabItem,
592{
593 fn default() -> Self {
594 Self {
595 slabs: HashMap::default(),
596 next_slab_id: SlabId {
597 id: NonMaxU32::default(),
598 phantom: PhantomData,
599 },
600 key_to_slab: HashMap::default(),
601 slab_layouts: HashMap::default(),
602 extra_buffer_usages: BufferUsages::empty(),
603 displaced_keys: Vec::new(),
604 }
605 }
606}
607
608impl<I> SlabAllocator<I>
609where
610 I: SlabItem,
611{
612 /// Creates a new empty slab allocator.
613 pub fn new() -> Self {
614 Self::default()
615 }
616
617 /// The keys that were already resident in a slab and had the [`Buffer`] behind them replaced by
618 /// that slab growing, so that consumers caching buffers can rebuild just the affected entries.
619 ///
620 /// This accumulates until [`Self::clear_displaced_keys`], so it is only correct
621 /// for a consumer that runs after the allocator has been updated for the frame and before the
622 /// list is cleared.
623 pub fn keys_displaced_by_slab_growth(&self) -> &[I::Key] {
624 &self.displaced_keys
625 }
626
627 /// Drops the accumulated [`Self::keys_displaced_by_slab_growth`], starting a new round of
628 /// invalidations.
629 pub fn clear_displaced_keys(&mut self) {
630 self.displaced_keys.clear();
631 }
632
633 /// Creates an [`AllocationStage`], enabling batched allocation of objects
634 /// in this slab.
635 ///
636 /// Allocation of objects in the slab requires calling this function,
637 /// calling [`AllocationStage::allocate`] on the resulting
638 /// [`AllocationStage`], and finally calling [`AllocationStage::commit`].
639 /// Grouping allocations into a batch, preferably at most one per frame, is
640 /// the most efficient way to perform many allocations at once.
641 pub fn stage_allocation(&'_ mut self) -> AllocationStage<'_, I> {
642 AllocationStage {
643 allocator: self,
644 slabs_to_reallocate: HashMap::default(),
645 empty_slabs: HashSet::default(),
646 }
647 }
648
649 /// Creates a [`DeallocationStage`], enabling batched deallocation.
650 ///
651 /// Deallocation of objects in the slab requires calling this function,
652 /// calling [`DeallocationStage::free`] on the resulting
653 /// [`DeallocationStage`], and finally calling
654 /// [`DeallocationStage::commit`]. Grouping deallocations into a batch,
655 /// preferably at most one per frame, is the most efficient way to perform
656 /// many deallocations at once.
657 pub fn stage_deallocation(&'_ mut self) -> DeallocationStage<'_, I> {
658 DeallocationStage {
659 allocator: self,
660 empty_slabs: HashSet::default(),
661 }
662 }
663
664 /// Allocates space for data with the given byte size and layout in the
665 /// appropriate slab, creating that slab if necessary.
666 fn allocate(
667 &mut self,
668 key: &I::Key,
669 data_byte_len: u64,
670 layout: I::Layout,
671 slabs_to_grow: &mut HashMap<SlabId<I>, SlabToReallocate>,
672 settings: &SlabAllocatorSettings,
673 ) {
674 debug_assert!(!self.key_to_slab.contains_key(key));
675
676 let data_element_count = data_byte_len.div_ceil(layout.size()) as u32;
677 let data_slot_count = data_element_count.div_ceil(layout.elements_per_slot());
678 let padding = data_slot_count * layout.elements_per_slot() - data_element_count;
679
680 // If the data is too large for a slab, give it a slab of its own.
681 if data_slot_count as u64 * layout.slot_size()
682 >= settings.large_threshold.min(settings.max_slab_size)
683 {
684 self.allocate_large(key, layout);
685 } else {
686 self.allocate_general(
687 key,
688 data_slot_count,
689 padding,
690 layout,
691 slabs_to_grow,
692 settings,
693 );
694 }
695 }
696
697 /// Allocates space for data with the given slot size and layout in the
698 /// appropriate general slab.
699 fn allocate_general(
700 &mut self,
701 key: &I::Key,
702 data_slot_count: u32,
703 padding: u32,
704 layout: I::Layout,
705 slabs_to_grow: &mut HashMap<SlabId<I>, SlabToReallocate>,
706 settings: &SlabAllocatorSettings,
707 ) {
708 let candidate_slabs = self.slab_layouts.entry(layout.clone()).or_default();
709
710 // Loop through the slabs that accept elements of the appropriate type
711 // and try to allocate the data inside them. We go with the first one
712 // that succeeds.
713 let mut data_allocation = None;
714 for &slab_id in &*candidate_slabs {
715 let Some(Slab::General(slab)) = self.slabs.get_mut(&slab_id) else {
716 unreachable!("Slab not found")
717 };
718
719 let Some(allocation) = slab.allocator.allocate(data_slot_count) else {
720 continue;
721 };
722
723 // Try to fit the object in the slab, growing if necessary.
724 match slab.grow_if_necessary(allocation.offset + data_slot_count, settings) {
725 SlabGrowthResult::NoGrowthNeeded => {}
726 SlabGrowthResult::NeededGrowth(slab_to_reallocate) => {
727 // If we already grew the slab this frame, don't replace the
728 // `SlabToReallocate` entry. We want to keep the entry
729 // corresponding to the size that the slab had at the start
730 // of the frame, so that we can copy only the used portion
731 // of the initial buffer to the new one.
732 if let Entry::Vacant(vacant_entry) = slabs_to_grow.entry(slab_id) {
733 vacant_entry.insert(slab_to_reallocate);
734 }
735 }
736 SlabGrowthResult::CantGrow => continue,
737 }
738
739 data_allocation = Some(SlabItemAllocation {
740 slab_id,
741 slab_allocation: SlabAllocation {
742 allocation,
743 slot_count: data_slot_count,
744 padding,
745 },
746 });
747 break;
748 }
749
750 // If we still have no allocation, make a new slab.
751 if data_allocation.is_none() {
752 let new_slab_id = self.next_slab_id;
753 self.next_slab_id.id =
754 NonMaxU32::new(self.next_slab_id.id.get() + 1).unwrap_or_default();
755
756 let new_slab = GeneralSlab::new(
757 new_slab_id,
758 &mut data_allocation,
759 settings,
760 layout,
761 data_slot_count,
762 padding,
763 );
764
765 self.slabs.insert(new_slab_id, Slab::General(new_slab));
766 candidate_slabs.push(new_slab_id);
767 slabs_to_grow.insert(new_slab_id, SlabToReallocate::default());
768 }
769
770 let data_allocation = data_allocation.expect("Should have been able to allocate");
771
772 // Mark the allocation as pending. Don't copy it in just yet; further
773 // data loaded this frame may result in its final allocation location
774 // changing.
775 if let Some(Slab::General(general_slab)) = self.slabs.get_mut(&data_allocation.slab_id) {
776 general_slab
777 .pending_allocations
778 .insert(key.clone(), data_allocation.slab_allocation);
779 };
780
781 self.record_allocation(key, data_allocation.slab_id);
782 }
783
784 /// Allocates an object into its own dedicated slab.
785 fn allocate_large(&mut self, key: &I::Key, layout: I::Layout) {
786 let new_slab_id = self.next_slab_id;
787 self.next_slab_id.id = NonMaxU32::new(self.next_slab_id.id.get() + 1).unwrap_or_default();
788
789 self.record_allocation(key, new_slab_id);
790
791 self.slabs.insert(
792 new_slab_id,
793 Slab::LargeObject(LargeObjectSlab {
794 buffer: None,
795 element_layout: layout,
796 }),
797 );
798 }
799
800 /// Frees whatever allocation the given key currently holds, if any.
801 ///
802 /// If this empties the allocation's slab, that slab is added to the
803 /// `empty_slabs` set for the caller to reclaim on commit.
804 fn free_existing_allocation(&mut self, key: &I::Key, empty_slabs: &mut HashSet<SlabId<I>>) {
805 if let Some(slab_id) = self.key_to_slab.remove(key) {
806 self.free_allocation_in_slab(key, slab_id, empty_slabs);
807 }
808 }
809
810 /// Given a slab and the key corresponding to an object within it, marks
811 /// the allocation as free.
812 ///
813 /// If this results in the slab becoming empty, this function adds the slab
814 /// to the `empty_slabs` set.
815 fn free_allocation_in_slab(
816 &mut self,
817 key: &I::Key,
818 slab_id: SlabId<I>,
819 empty_slabs: &mut HashSet<SlabId<I>>,
820 ) {
821 let Some(slab) = self.slabs.get_mut(&slab_id) else {
822 error!("Double free: attempted to free data in a nonexistent slab");
823 return;
824 };
825
826 match *slab {
827 Slab::General(ref mut general_slab) => {
828 let Some(slab_allocation) = general_slab
829 .resident_allocations
830 .remove(key)
831 .or_else(|| general_slab.pending_allocations.remove(key))
832 else {
833 return;
834 };
835
836 general_slab.allocator.free(slab_allocation.allocation);
837
838 if general_slab.is_empty() {
839 empty_slabs.insert(slab_id);
840 }
841 }
842 Slab::LargeObject(_) => {
843 empty_slabs.insert(slab_id);
844 }
845 }
846 }
847
848 /// Reallocates a slab that needs to be resized, or allocates a new slab.
849 ///
850 /// This performs the actual growth operation that
851 /// [`GeneralSlab::grow_if_necessary`] scheduled. We do the growth in two
852 /// phases so that, if a slab grows multiple times in the same frame, only
853 /// one new buffer is reallocated, rather than reallocating the buffer
854 /// multiple times.
855 fn reallocate_slab(
856 &mut self,
857 render_device: &RenderDevice,
858 render_queue: &RenderQueue,
859 slab_id: SlabId<I>,
860 slab_to_grow: SlabToReallocate,
861 ) {
862 let Some(Slab::General(slab)) = self.slabs.get_mut(&slab_id) else {
863 error!("Couldn't find slab {} to grow", slab_id);
864 return;
865 };
866
867 let old_buffer = slab.buffer.take();
868
869 if old_buffer.is_some() {
870 self.displaced_keys
871 .extend(slab.resident_allocations.keys().cloned());
872 }
873
874 let buffer_usages = BufferUsages::COPY_SRC
875 | BufferUsages::COPY_DST
876 | slab.element_layout.buffer_usages()
877 | self.extra_buffer_usages;
878
879 // Create the buffer.
880 let new_buffer = render_device.create_buffer(&BufferDescriptor {
881 label: Some(&format!(
882 "general {} slab {} ({}buffer)",
883 I::label(),
884 slab_id,
885 buffer_usages_to_str(buffer_usages)
886 )),
887 size: slab.current_slot_capacity as u64 * slab.element_layout.slot_size(),
888 usage: buffer_usages,
889 mapped_at_creation: false,
890 });
891
892 slab.buffer = Some(new_buffer.clone());
893
894 let Some(old_buffer) = old_buffer else { return };
895
896 // In order to do buffer copies, we need a command encoder.
897 let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {
898 label: Some(&*format!("{} slab resize encoder", I::label())),
899 });
900
901 // Copy the data from the old buffer into the new one.
902 encoder.copy_buffer_to_buffer(
903 &old_buffer,
904 0,
905 &new_buffer,
906 0,
907 slab_to_grow.old_slot_capacity as u64 * slab.element_layout.slot_size(),
908 );
909
910 let command_buffer = encoder.finish();
911 render_queue.submit([command_buffer]);
912 }
913
914 /// Records the location of the given newly-allocated data in the
915 /// [`Self::key_to_slab`] table.
916 fn record_allocation(&mut self, key: &I::Key, slab_id: SlabId<I>) {
917 self.key_to_slab.insert(key.clone(), slab_id);
918 }
919
920 /// Returns the GPU buffer corresponding to the slab with the given ID if
921 /// that slab has been uploaded to the GPU.
922 pub fn buffer_for_slab(&self, slab_id: SlabId<I>) -> Option<&Buffer> {
923 self.slabs.get(&slab_id).and_then(|slab| slab.buffer())
924 }
925
926 /// Given a slab and the key of data located with it, returns the buffer
927 /// and range of that data within the slab.
928 pub fn slab_allocation_slice(
929 &self,
930 key: &I::Key,
931 slab_id: SlabId<I>,
932 ) -> Option<SlabAllocationBufferSlice<'_, I>> {
933 match self.slabs.get(&slab_id)? {
934 Slab::General(general_slab) => {
935 let slab_allocation = general_slab.resident_allocations.get(key)?;
936 Some(SlabAllocationBufferSlice {
937 buffer: general_slab.buffer.as_ref()?,
938 range: (slab_allocation.allocation.offset
939 * general_slab.element_layout.elements_per_slot())
940 ..((slab_allocation.allocation.offset + slab_allocation.slot_count)
941 * general_slab.element_layout.elements_per_slot())
942 - slab_allocation.padding,
943 phantom: PhantomData,
944 })
945 }
946
947 Slab::LargeObject(large_object_slab) => {
948 let buffer = large_object_slab.buffer.as_ref()?;
949 Some(SlabAllocationBufferSlice {
950 buffer,
951 range: 0..((buffer.size() / large_object_slab.element_layout.size()) as u32),
952 phantom: PhantomData,
953 })
954 }
955 }
956 }
957
958 fn free_empty_slabs(&mut self, empty_slabs: impl Iterator<Item = SlabId<I>>) {
959 for empty_slab in empty_slabs {
960 // The slab may have been refilled since it was marked empty, because
961 // a reallocated object usually lands back in the hole it just left.
962 // Destroying the slab here would take live data with it, so skip.
963 if let Some(Slab::General(general_slab)) = self.slabs.get(&empty_slab)
964 && !general_slab.is_empty()
965 {
966 continue;
967 }
968
969 self.slab_layouts.values_mut().for_each(|slab_ids| {
970 let idx = slab_ids.iter().position(|&slab_id| slab_id == empty_slab);
971 if let Some(idx) = idx {
972 slab_ids.remove(idx);
973 }
974 });
975 self.slabs.remove(&empty_slab);
976 }
977 }
978
979 /// Get the number of allocated slabs
980 pub fn slab_count(&self) -> usize {
981 self.slabs.len()
982 }
983
984 /// Get the total size of all allocated slabs
985 pub fn slabs_size(&self) -> u64 {
986 self.slabs.iter().map(|slab| slab.1.buffer_size()).sum()
987 }
988
989 /// Copies data into an allocated slab.
990 ///
991 /// `len` specifies the size of the data to be copied *in bytes*. The given
992 /// `fill_data` callback is expected to write the data into the given slice;
993 /// this callback approach avoids a copy.
994 pub fn copy_element_data(
995 &mut self,
996 key: &I::Key,
997 len: usize,
998 fill_data: impl Fn(WriteOnly<[u8]>),
999 render_device: &RenderDevice,
1000 render_queue: &RenderQueue,
1001 ) {
1002 let Some(slab_id) = self.key_to_slab.get(key) else {
1003 error!("Use-after-free: attempted to copy element data for an unallocated key");
1004 return;
1005 };
1006 let Some(slab) = self.slabs.get_mut(slab_id) else {
1007 error!("Use-after-free: attempted to copy element data into a nonexistent slab");
1008 return;
1009 };
1010
1011 match *slab {
1012 Slab::General(ref mut general_slab) => {
1013 let (Some(buffer), Some(allocated_range)) = (
1014 &general_slab.buffer,
1015 general_slab.pending_allocations.remove(key),
1016 ) else {
1017 return;
1018 };
1019
1020 let slot_size = general_slab.element_layout.slot_size();
1021
1022 // round up size to a multiple of the slot size to satisfy wgpu
1023 // alignment requirements
1024 if let Some(size) = BufferSize::new((len as u64).next_multiple_of(slot_size)) {
1025 // Write the data in.
1026 if let Some(mut buffer) = render_queue.write_buffer_with(
1027 buffer,
1028 allocated_range.allocation.offset as u64 * slot_size,
1029 size,
1030 ) {
1031 let slice = buffer.slice(..len);
1032 fill_data(slice);
1033 }
1034 }
1035
1036 // Mark the allocation as resident.
1037 general_slab
1038 .resident_allocations
1039 .insert(key.clone(), allocated_range);
1040 }
1041
1042 Slab::LargeObject(ref mut large_object_slab) => {
1043 debug_assert!(large_object_slab.buffer.is_none());
1044
1045 // Create the buffer and its data in one go.
1046 let buffer_usages = large_object_slab.element_layout.buffer_usages()
1047 | BufferUsages::COPY_DST
1048 | self.extra_buffer_usages;
1049 let buffer = render_device.create_buffer(&BufferDescriptor {
1050 label: Some(&format!(
1051 "large {} slab {} ({}buffer)",
1052 I::label(),
1053 slab_id,
1054 buffer_usages_to_str(buffer_usages)
1055 )),
1056 size: len as u64,
1057 usage: buffer_usages,
1058 mapped_at_creation: true,
1059 });
1060 {
1061 let mut slice = buffer.slice(..).get_mapped_range_mut().unwrap();
1062
1063 fill_data(slice.slice(..len));
1064 }
1065 buffer.unmap();
1066 large_object_slab.buffer = Some(buffer);
1067 }
1068 }
1069 }
1070}
1071
1072/// The results of [`GeneralSlab::grow_if_necessary`].
1073enum SlabGrowthResult {
1074 /// The data already fits in the slab; the slab doesn't need to grow.
1075 NoGrowthNeeded,
1076 /// The slab needed to grow.
1077 ///
1078 /// The [`SlabToReallocate`] contains the old capacity of the slab.
1079 NeededGrowth(SlabToReallocate),
1080 /// The slab wanted to grow but couldn't because it hit its maximum size.
1081 CantGrow,
1082}
1083
1084impl<I> GeneralSlab<I>
1085where
1086 I: SlabItem,
1087{
1088 /// Creates a new growable slab big enough to hold a single element of
1089 /// `data_slot_count` size with the given `layout`.
1090 fn new(
1091 new_slab_id: SlabId<I>,
1092 maybe_slab_item_allocation: &mut Option<SlabItemAllocation<I>>,
1093 settings: &SlabAllocatorSettings,
1094 layout: I::Layout,
1095 data_slot_count: u32,
1096 padding: u32,
1097 ) -> GeneralSlab<I> {
1098 let initial_slab_slot_capacity = (settings.min_slab_size.div_ceil(layout.slot_size())
1099 as u32)
1100 .max(offset_allocator::ext::min_allocator_size(data_slot_count));
1101 let max_slab_slot_capacity = (settings.max_slab_size.div_ceil(layout.slot_size()) as u32)
1102 .max(offset_allocator::ext::min_allocator_size(data_slot_count));
1103
1104 let mut new_slab = GeneralSlab {
1105 allocator: Allocator::new(max_slab_slot_capacity),
1106 buffer: None,
1107 resident_allocations: HashMap::default(),
1108 pending_allocations: HashMap::default(),
1109 element_layout: layout,
1110 current_slot_capacity: initial_slab_slot_capacity,
1111 };
1112
1113 // This should never fail.
1114 if let Some(allocation) = new_slab.allocator.allocate(data_slot_count) {
1115 *maybe_slab_item_allocation = Some(SlabItemAllocation {
1116 slab_id: new_slab_id,
1117 slab_allocation: SlabAllocation {
1118 slot_count: data_slot_count,
1119 allocation,
1120 padding,
1121 },
1122 });
1123 }
1124
1125 new_slab
1126 }
1127
1128 /// Checks to see if the size of this slab is at least `new_size_in_slots`
1129 /// and grows the slab if it isn't.
1130 ///
1131 /// The returned [`SlabGrowthResult`] describes whether the slab needed to
1132 /// grow and whether, if so, it was successful in doing so.
1133 fn grow_if_necessary(
1134 &mut self,
1135 new_size_in_slots: u32,
1136 settings: &SlabAllocatorSettings,
1137 ) -> SlabGrowthResult {
1138 // Is the slab big enough already?
1139 let initial_slot_capacity = self.current_slot_capacity;
1140 if self.current_slot_capacity >= new_size_in_slots {
1141 return SlabGrowthResult::NoGrowthNeeded;
1142 }
1143
1144 // Try to grow in increments of `SlabAllocatorSettings::growth_factor`
1145 // until we're big enough.
1146 while self.current_slot_capacity < new_size_in_slots {
1147 let new_slab_slot_capacity =
1148 ((self.current_slot_capacity as f64 * settings.growth_factor).ceil() as u32)
1149 .min((settings.max_slab_size / self.element_layout.slot_size()) as u32);
1150 if new_slab_slot_capacity == self.current_slot_capacity {
1151 // The slab is full.
1152 return SlabGrowthResult::CantGrow;
1153 }
1154
1155 self.current_slot_capacity = new_slab_slot_capacity;
1156 }
1157
1158 // Tell our caller what we did.
1159 SlabGrowthResult::NeededGrowth(SlabToReallocate {
1160 old_slot_capacity: initial_slot_capacity,
1161 })
1162 }
1163
1164 /// Returns true if this slab is empty.
1165 fn is_empty(&self) -> bool {
1166 self.resident_allocations.is_empty() && self.pending_allocations.is_empty()
1167 }
1168}
1169
1170/// Returns a string describing the given buffer usages.
1171fn buffer_usages_to_str(buffer_usages: BufferUsages) -> &'static str {
1172 if buffer_usages.contains(BufferUsages::VERTEX) {
1173 "vertex "
1174 } else if buffer_usages.contains(BufferUsages::INDEX) {
1175 "index "
1176 } else if buffer_usages.contains(BufferUsages::STORAGE) {
1177 "storage "
1178 } else {
1179 ""
1180 }
1181}
1182
1183#[cfg(test)]
1184mod tests {
1185 use super::*;
1186 use crate::test_utils::create_dummy_device;
1187
1188 /// A [`SlabItem`] for tests, keyed by a plain integer.
1189 struct TestItem;
1190
1191 impl SlabItem for TestItem {
1192 type Key = u32;
1193 type Layout = TestLayout;
1194
1195 fn label() -> Cow<'static, str> {
1196 "test".into()
1197 }
1198 }
1199
1200 /// A four-byte element, one element per slot.
1201 #[derive(Clone, PartialEq, Eq, Hash)]
1202 struct TestLayout;
1203
1204 impl SlabItemLayout for TestLayout {
1205 fn size(&self) -> u64 {
1206 4
1207 }
1208
1209 fn elements_per_slot(&self) -> u32 {
1210 1
1211 }
1212
1213 fn buffer_usages(&self) -> BufferUsages {
1214 BufferUsages::VERTEX
1215 }
1216 }
1217
1218 /// Small slabs, so that a leak shows up as slab growth within a few rounds.
1219 fn test_settings() -> SlabAllocatorSettings {
1220 SlabAllocatorSettings {
1221 // 256 slots.
1222 min_slab_size: 1024,
1223 // 1024 slots.
1224 max_slab_size: 4096,
1225 large_threshold: 4096,
1226 growth_factor: 1.5,
1227 }
1228 }
1229
1230 /// Allocates `byte_len` bytes under each of `keys` and marks the results
1231 /// resident, as a frame of [`MeshAllocator`](crate::mesh::MeshAllocator)
1232 /// would.
1233 fn allocate_round(
1234 allocator: &mut SlabAllocator<TestItem>,
1235 keys: impl Iterator<Item = u32> + Clone,
1236 byte_len: u64,
1237 device: &RenderDevice,
1238 queue: &RenderQueue,
1239 ) {
1240 let mut stage = allocator.stage_allocation();
1241 for key in keys.clone() {
1242 stage.allocate(&key, byte_len, TestLayout, &test_settings());
1243 }
1244 stage.commit(device, queue);
1245
1246 for key in keys {
1247 allocator.copy_element_data(&key, byte_len as usize, |_| {}, device, queue);
1248 }
1249 }
1250
1251 /// Reallocating a key that's still live must free its previous allocation
1252 /// rather than orphaning it.
1253 #[test]
1254 fn reallocating_a_live_key_frees_the_old_allocation() {
1255 let (device, queue) = create_dummy_device();
1256 let mut allocator = SlabAllocator::<TestItem>::new();
1257
1258 allocate_round(&mut allocator, 0..8, 256, &device, &queue);
1259
1260 let baseline_size = allocator.slabs_size();
1261 let baseline_slabs = allocator.slab_count();
1262 assert!(baseline_size > 0, "nothing was allocated");
1263
1264 // Reallocate the same keys, without ever freeing them, many times over.
1265 for _ in 0..32 {
1266 allocate_round(&mut allocator, 0..8, 256, &device, &queue);
1267 }
1268
1269 assert_eq!(
1270 allocator.slabs_size(),
1271 baseline_size,
1272 "reallocating live keys grew the slabs, so old allocations leaked"
1273 );
1274 assert_eq!(allocator.slab_count(), baseline_slabs);
1275 assert_eq!(allocator.key_to_slab.len(), 8);
1276
1277 // Every key must still be readable.
1278 for key in 0..8u32 {
1279 let slab_id = allocator.key_to_slab[&key];
1280 assert!(allocator.slab_allocation_slice(&key, slab_id).is_some());
1281 }
1282 }
1283
1284 /// A slab emptied by a reallocation that lands back in that same slab must
1285 /// not be destroyed on commit.
1286 #[test]
1287 fn slab_refilled_during_allocation_is_not_freed() {
1288 let (device, queue) = create_dummy_device();
1289 let mut allocator = SlabAllocator::<TestItem>::new();
1290
1291 allocate_round(&mut allocator, 0..1, 256, &device, &queue);
1292 let slab_id = allocator.key_to_slab[&0];
1293
1294 // Freeing the sole occupant marks the slab empty mid-stage; the
1295 // reallocation then drops straight back into it.
1296 allocate_round(&mut allocator, 0..1, 256, &device, &queue);
1297
1298 assert_eq!(allocator.slab_count(), 1, "the live slab was destroyed");
1299 assert_eq!(allocator.key_to_slab[&0], slab_id);
1300 assert!(allocator.slab_allocation_slice(&0, slab_id).is_some());
1301 }
1302
1303 /// Allocates a dedicated slab for each of `keys` through
1304 /// [`AllocationStage::allocate_large`].
1305 fn allocate_large_round(
1306 allocator: &mut SlabAllocator<TestItem>,
1307 keys: impl Iterator<Item = u32>,
1308 device: &RenderDevice,
1309 queue: &RenderQueue,
1310 ) {
1311 let mut stage = allocator.stage_allocation();
1312 for key in keys {
1313 stage.allocate_large(&key, TestLayout);
1314 }
1315 stage.commit(device, queue);
1316 }
1317
1318 /// [`AllocationStage::allocate_large`] must free a live allocation under the
1319 /// same key, just as [`AllocationStage::allocate`] does. Each one takes a
1320 /// brand new slab, so a missed free leaks a whole slab per round.
1321 #[test]
1322 fn reallocating_a_live_key_with_allocate_large_frees_the_old_allocation() {
1323 let (device, queue) = create_dummy_device();
1324 let mut allocator = SlabAllocator::<TestItem>::new();
1325
1326 allocate_large_round(&mut allocator, 0..4, &device, &queue);
1327 assert_eq!(allocator.slab_count(), 4);
1328
1329 for _ in 0..32 {
1330 allocate_large_round(&mut allocator, 0..4, &device, &queue);
1331 }
1332
1333 assert_eq!(
1334 allocator.slab_count(),
1335 4,
1336 "reallocating live keys left the previous dedicated slabs behind"
1337 );
1338 assert_eq!(allocator.key_to_slab.len(), 4);
1339 }
1340
1341 /// A slab genuinely emptied during an allocation stage must be reclaimed, or
1342 /// the fix for reallocation would just trade one leak for another.
1343 #[test]
1344 fn slab_emptied_during_allocation_is_freed() {
1345 let (device, queue) = create_dummy_device();
1346 let mut allocator = SlabAllocator::<TestItem>::new();
1347
1348 allocate_round(&mut allocator, 0..1, 256, &device, &queue);
1349 assert_eq!(allocator.slab_count(), 1);
1350
1351 // Reallocate the sole occupant at a size that forces it into a slab of
1352 // its own, leaving the general slab empty.
1353 allocate_round(&mut allocator, 0..1, 8192, &device, &queue);
1354
1355 assert_eq!(
1356 allocator.slab_count(),
1357 1,
1358 "the emptied general slab was not reclaimed"
1359 );
1360 let slab_id = allocator.key_to_slab[&0];
1361 assert!(matches!(
1362 allocator.slabs.get(&slab_id),
1363 Some(Slab::LargeObject(_))
1364 ));
1365 }
1366}