Path: blob/main/crates/bevy_render/src/slab_allocator.rs
30635 views
//! A general-purpose allocator that manages a set of GPU buffer slabs.12use alloc::borrow::Cow;3use bevy_derive::{Deref, DerefMut};4use bevy_log::error;5use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};6use core::{7cmp::Ordering,8fmt::{self, Debug, Display, Formatter},9hash::{Hash, Hasher},10marker::PhantomData,11ops::Range,12};13use nonmax::NonMaxU32;14use offset_allocator::{Allocation, Allocator};15use wgpu::{BufferDescriptor, BufferSize, BufferUsages, CommandEncoderDescriptor, WriteOnly};1617use crate::{18render_resource::Buffer,19renderer::{RenderDevice, RenderQueue},20};2122/// A general-purpose allocator that manages a set of GPU buffer slabs.23///24/// You can use this allocator to pack data that needs to be accessible by the25/// GPU into a small set of buffers, known as *slabs*. Each individual slab is26/// expected to contain homogeneous data of a single type. However, you can use27/// a single allocator to manage multiple slabs, each of which can have a28/// different data layout. Objects managed by the allocator are referenced with29/// a *key* that you can define.30///31/// To use this allocator, implement the [`SlabItem`] trait; see the32/// documentation of that trait for details.33///34/// For performance, you'll want to batch your allocation and deallocation35/// operations to be performed at a single point in the frame. To perform36/// allocation, call [`Self::stage_allocation`] to obtain an37/// [`AllocationStage`], call [`AllocationStage::allocate`] to allocate38/// individual objects, and then *commit* the allocation transaction using39/// [`AllocationStage::commit`]. Likewise, to perform deallocation, call40/// [`Self::stage_deallocation`] to obtain a [`DeallocationStage`], call41/// [`DeallocationStage::free`] to free objects, and then call42/// [`DeallocationStage::commit`]. Once you've committed an allocation stage,43/// you can copy new data into the slabs via [`Self::copy_element_data`].44///45/// Within each slab, or hardware buffer, the underlying allocation algorithm46/// is [`offset_allocator`], a Rust port of Sebastian Aaltonen's hard-real-time47/// C++ `OffsetAllocator`. Slabs start small and then grow as their contents48/// fill up, up to a maximum size limit. To reduce fragmentation, objects that49/// are too large bypass this system and receive their own buffers.50///51/// The [`SlabAllocatorSettings`] allows you to tune the behavior of the52/// allocator for better performance with your use case.53///54/// See [`crate::mesh::allocator::MeshAllocator`] for an example of usage.55pub struct SlabAllocator<I>56where57I: SlabItem,58{59/// Holds all buffers and allocators.60pub slabs: HashMap<SlabId<I>, Slab<I>>,6162/// The next slab ID to assign.63next_slab_id: SlabId<I>,6465/// Maps slab allocation keys to the ID of the slabs that hold their data.66pub key_to_slab: HashMap<I::Key, SlabId<I>>,6768/// Maps a layout to the slabs that hold elements of that layout.69///70/// This is used when allocating, so that we can find the appropriate slab71/// to place an object in.72slab_layouts: HashMap<I::Layout, Vec<SlabId<I>>>,7374/// Additional buffer usages to add to any vertex or index buffers created.75pub extra_buffer_usages: BufferUsages,76}7778/// Describes the type of the data that a [`SlabAllocator`] will store.79///80/// The actual type that you implement this trait on doesn't matter; only the81/// associated types [`Self::Key`] and [`Self::Layout`] do. Typically, you82/// implement this trait on a unit struct.83///84/// See [`crate::mesh::allocator::MeshSlabItem`] for an example of usage.85pub trait SlabItem {86/// The key that's used to look up items in the allocator.87type Key: Clone + PartialEq + Eq + Hash;8889/// A type that describes the layout of items within a single slab.90///91/// If this slab allocator only allocates items of a single type, this type92/// can simply be a unit struct. However, if you wish to have a single slab93/// allocator that manages slabs of differing types, you can store metadata94/// within values of this type that describes the size and alignment95/// requirements of the objects within the slab. Each slab that the slab96/// allocator manages contains an instance of this value so that it can97/// track size and alignment requirements for that slab.98type Layout: SlabItemLayout;99100/// Returns a suitable debugging label describing the type of elements that101/// this slab item stores.102fn label() -> Cow<'static, str>;103}104105/// A trait that defines information necessary to determine the size and106/// alignment of objects within a slab.107pub trait SlabItemLayout: Clone + PartialEq + Eq + Hash {108/// The size in bytes of a single element.109///110/// This is the smallest size that this allocator can allocate, and all111/// allocations must have a byte size that is a multiple of this value.112fn size(&self) -> u64;113114/// The number of elements that make up a single slot.115fn elements_per_slot(&self) -> u32;116117/// The `wgpu` buffer usages that the slab allocator will specify when118/// creating buffers.119///120/// `BufferUsages::COPY_DST` and `BufferUsages::COPY_SRC` are always121/// included, regardless of what you specify here.122fn buffer_usages(&self) -> BufferUsages;123}124125/// Internal helper methods for [`SlabItemLayout`]s.126trait SlabItemLayoutExt {127/// Returns the size in bytes of a single slot.128fn slot_size(&self) -> u64;129}130131impl<I> SlabItemLayoutExt for I132where133I: SlabItemLayout,134{135fn slot_size(&self) -> u64 {136self.size() * self.elements_per_slot() as u64137}138}139140/// Tunable parameters that customize the behavior of the allocator.141///142/// Generally, these parameters adjust the tradeoff between memory fragmentation143/// and performance. You can adjust them as desired for your application. Most144/// applications can stick with the default values.145pub struct SlabAllocatorSettings {146/// The minimum size of a slab (hardware buffer), in bytes.147///148/// The default value is 1 MiB.149pub min_slab_size: u64,150151/// The maximum size of a slab (hardware buffer), in bytes.152///153/// When a slab reaches this limit, a new slab is created.154///155/// The default value is 512 MiB.156pub max_slab_size: u64,157158/// The maximum size of vertex or index data that can be placed in a general159/// slab, in bytes.160///161/// If an allocation exceeds this size limit, that data is placed in its own162/// slab. This reduces fragmentation at the cost of more buffer management163/// overhead.164///165/// The default value is 256 MiB.166pub large_threshold: u64,167168/// The factor by which we scale a slab when growing it.169///170/// This value must be greater than 1. Higher values result in more171/// fragmentation but fewer expensive copy operations when growing the172/// buffer.173///174/// The default value is 1.5.175pub growth_factor: f64,176}177178impl Default for SlabAllocatorSettings {179fn default() -> Self {180Self {181// 1 MiB182min_slab_size: 1024 * 1024,183// 512 MiB184max_slab_size: 1024 * 1024 * 512,185// 256 MiB186large_threshold: 1024 * 1024 * 256,187// 1.5× growth188growth_factor: 1.5,189}190}191}192193/// The index of a single slab.194#[derive(Deref, DerefMut)]195#[repr(transparent)]196pub struct SlabId<I>197where198I: SlabItem,199{200/// A value that represents the ID of the slab.201#[deref]202pub id: NonMaxU32,203phantom: PhantomData<I>,204}205206impl<I> Clone for SlabId<I>207where208I: SlabItem,209{210fn clone(&self) -> Self {211*self212}213}214215impl<I> Copy for SlabId<I> where I: SlabItem {}216217impl<I> Default for SlabId<I>218where219I: SlabItem,220{221fn default() -> Self {222SlabId {223id: NonMaxU32::default(),224phantom: PhantomData,225}226}227}228229impl<I> PartialEq for SlabId<I>230where231I: SlabItem,232{233fn eq(&self, other: &Self) -> bool {234self.id == other.id235}236}237238impl<I> Eq for SlabId<I> where I: SlabItem {}239240impl<I> PartialOrd for SlabId<I>241where242I: SlabItem,243{244fn partial_cmp(&self, other: &Self) -> Option<Ordering> {245Some(self.cmp(other))246}247}248249impl<I> Ord for SlabId<I>250where251I: SlabItem,252{253fn cmp(&self, other: &Self) -> Ordering {254self.id.cmp(other)255}256}257258impl<I> Hash for SlabId<I>259where260I: SlabItem,261{262fn hash<H: Hasher>(&self, state: &mut H) {263self.id.hash(state);264}265}266267impl<I> Debug for SlabId<I>268where269I: SlabItem,270{271fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {272f.debug_struct("SlabId").field("id", &self.id).finish()273}274}275276/// Data for a single slab.277#[expect(278clippy::large_enum_variant,279reason = "See https://github.com/bevyengine/bevy/issues/19220"280)]281pub enum Slab<I>282where283I: SlabItem,284{285/// A slab that can contain multiple objects.286General(GeneralSlab<I>),287/// A slab that contains a single object.288LargeObject(LargeObjectSlab<I>),289}290291/// A resizable slab that can contain multiple objects.292///293/// This is the normal type of slab used for objects that are below the294/// [`SlabAllocatorSettings::large_threshold`]. Slabs are divided into *slots*,295/// which are described in detail in the [`SlabItemLayout`] documentation.296pub struct GeneralSlab<I>297where298I: SlabItem,299{300/// The [`Allocator`] that manages the objects in this slab.301allocator: Allocator,302303/// The GPU buffer that backs this slab.304///305/// This may be `None` if the buffer hasn't been created yet. We delay306/// creation of buffers until performing all the allocations for a single307/// frame, so that we don't needlessly create and resize buffers when many308/// objects are allocated all at once.309buffer: Option<Buffer>,310311/// Allocations that are on the GPU.312///313/// The range is in slots.314resident_allocations: HashMap<I::Key, SlabAllocation>,315316/// Allocations that are waiting to be uploaded to the GPU.317///318/// The range is in slots.319pending_allocations: HashMap<I::Key, SlabAllocation>,320321/// The layout of a single element (vertex or index).322element_layout: I::Layout,323324/// The size of this slab in slots.325current_slot_capacity: u32,326}327328/// A slab that contains a single object.329///330/// Typically, this is for objects that exceed the331/// [`SlabAllocatorSettings::large_threshold`]. Additionally, some uses of the332/// slab allocator may wish to force objects to possess their own slab. For333/// instance, due to platform limitations (vertex arrays on WebGL 2), the mesh334/// allocator sometimes needs to place meshes that would otherwise be allocated335/// together with other meshes in their own slab.336pub struct LargeObjectSlab<I>337where338I: SlabItem,339{340/// The GPU buffer that backs this slab.341///342/// This may be `None` if the buffer hasn't been created yet.343buffer: Option<Buffer>,344345/// The layout of a single element (vertex or index).346element_layout: I::Layout,347}348349/// The location of an allocation and the slab it's contained in.350struct SlabItemAllocation<I>351where352I: SlabItem,353{354/// The ID of the slab.355slab_id: SlabId<I>,356/// Holds the actual allocation.357slab_allocation: SlabAllocation,358}359360impl<I> Slab<I>361where362I: SlabItem,363{364/// Returns the GPU buffer corresponding to this slab, if it's been365/// uploaded.366pub fn buffer(&self) -> Option<&Buffer> {367match self {368Slab::General(general_slab) => general_slab.buffer.as_ref(),369Slab::LargeObject(large_object_slab) => large_object_slab.buffer.as_ref(),370}371}372373/// Returns the size of this slab in bytes.374pub fn buffer_size(&self) -> u64 {375match self.buffer() {376Some(buffer) => buffer.size(),377None => 0,378}379}380381/// Returns the [`SlabItemLayout`] associated with this slab.382pub fn element_layout(&self) -> &I::Layout {383match self {384Slab::General(general_slab) => &general_slab.element_layout,385Slab::LargeObject(large_object_slab) => &large_object_slab.element_layout,386}387}388}389390/// An object that allows batched allocation.391///392/// In order to perform allocations, you create one of these objects with393/// [`SlabAllocator::stage_allocation`], allocate into it with394/// [`Self::allocate`], and finally commit it with [`Self::commit`]. Always395/// make sure to call [`Self::commit`]; if you don't, buffers that were396/// supposed to be enlarged won't be.397pub struct AllocationStage<'a, I>398where399I: SlabItem,400{401/// The allocator that we're allocating objects into.402pub allocator: &'a mut SlabAllocator<I>,403/// The set of slabs that have grown and need to be reallocated.404slabs_to_reallocate: HashMap<SlabId<I>, SlabToReallocate>,405}406407impl<'a, I> Drop for AllocationStage<'a, I>408where409I: SlabItem,410{411fn drop(&mut self) {412if !self.slabs_to_reallocate.is_empty() {413error!(414"Dropping an `AllocationStage` with uncommitted reallocations. You should call \415`AllocationStage::commit`."416);417}418}419}420421impl<'a, I> AllocationStage<'a, I>422where423I: SlabItem,424{425/// Allocates space for an object of the given size with the given key and layout.426///427/// The key must not correspond to any current allocation.428pub fn allocate(429&mut self,430key: &I::Key,431data_byte_len: u64,432layout: I::Layout,433settings: &SlabAllocatorSettings,434) {435self.allocator.allocate(436key,437data_byte_len,438layout,439&mut self.slabs_to_reallocate,440settings,441);442}443444/// Allocates an object into its own dedicated slab.445///446/// The key must not correspond to any current allocation.447pub fn allocate_large(&mut self, key: &I::Key, layout: I::Layout) {448self.allocator.allocate_large(key, layout);449}450451/// Completes the transaction, performing any queued resize operations.452pub fn commit(mut self, render_device: &RenderDevice, render_queue: &RenderQueue) {453for (slab_id, slab_to_grow) in self.slabs_to_reallocate.drain() {454self.allocator455.reallocate_slab(render_device, render_queue, slab_id, slab_to_grow);456}457}458}459460/// An object that enables batched deallocation.461///462/// To free objects from a [`SlabAllocator`], call463/// [`SlabAllocator::stage_deallocation`] to create a [`DeallocationStage`],464/// call [`Self::free`] to deallocate objects, and finally call465/// [`Self::commit`]. You must call [`Self::commit`] in order to ensure that466/// newly-empty slabs are deallocated.467pub struct DeallocationStage<'a, I>468where469I: SlabItem,470{471/// The allocator in which objects are to be freed.472pub allocator: &'a mut SlabAllocator<I>,473/// IDs of slabs that have become empty.474empty_slabs: HashSet<SlabId<I>>,475}476477impl<'a, I> Drop for DeallocationStage<'a, I>478where479I: SlabItem,480{481fn drop(&mut self) {482if !self.empty_slabs.is_empty() {483error!(484"Dropping a `DeallocationStage` with uncommitted slab free operations. You should \485call `DeallocationStage::commit`."486);487}488}489}490491impl<'a, I> DeallocationStage<'a, I>492where493I: SlabItem,494{495/// Schedules a free operation for the allocation with the given key.496///497/// The key must correspond to a live allocation. An error will be emitted498/// to the log otherwise.499pub fn free(&mut self, key: &I::Key) {500if let Some(slab_id) = self.allocator.key_to_slab.remove(key) {501self.allocator502.free_allocation_in_slab(key, slab_id, &mut self.empty_slabs);503}504}505506/// Performs all the free operations.507///508/// You must call this method if you called [`Self::free`].509pub fn commit(mut self) {510self.allocator.free_empty_slabs(self.empty_slabs.drain());511}512}513514/// An allocation within a slab.515#[derive(Clone)]516struct SlabAllocation {517/// The actual [`Allocator`] handle, needed to free the allocation.518allocation: Allocation,519/// The number of slots that this allocation takes up.520slot_count: u32,521/// The number of slots at the end of the allocation that are considered522/// padding.523padding: u32,524}525526/// The hardware buffer that slab-allocated data lives in, as well as the range527/// within that buffer.528pub struct SlabAllocationBufferSlice<'a, I>529where530I: SlabItem,531{532/// The buffer that the data resides in.533pub buffer: &'a Buffer,534535/// The range of elements within this buffer that the data resides in,536/// measured in elements.537///538/// This is an element range, not a byte range. For vertex data, this is539/// measured in increments of a single vertex. (Thus, if a vertex is 32540/// bytes long, then this range is in units of 32 bytes each.) For index541/// data, this is measured in increments of a single index value (2 or 4542/// bytes). Draw commands generally take their ranges in elements, not543/// bytes, so this is the most convenient unit in this case.544pub range: Range<u32>,545546phantom: PhantomData<I>,547}548549/// Holds information about a slab that's scheduled to be allocated or550/// reallocated.551#[derive(Default)]552pub struct SlabToReallocate {553/// The capacity of the slab before we decided to grow it.554old_slot_capacity: u32,555}556557impl<I> Display for SlabId<I>558where559I: SlabItem,560{561fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {562Debug::fmt(&self.id, f)563}564}565566impl<I> Default for SlabAllocator<I>567where568I: SlabItem,569{570fn default() -> Self {571Self {572slabs: HashMap::default(),573next_slab_id: SlabId {574id: NonMaxU32::default(),575phantom: PhantomData,576},577key_to_slab: HashMap::default(),578slab_layouts: HashMap::default(),579extra_buffer_usages: BufferUsages::empty(),580}581}582}583584impl<I> SlabAllocator<I>585where586I: SlabItem,587{588/// Creates a new empty slab allocator.589pub fn new() -> Self {590Self::default()591}592593/// Creates an [`AllocationStage`], enabling batched allocation of objects594/// in this slab.595///596/// Allocation of objects in the slab requires calling this function,597/// calling [`AllocationStage::allocate`] on the resulting598/// [`AllocationStage`], and finally calling [`AllocationStage::commit`].599/// Grouping allocations into a batch, preferably at most one per frame, is600/// the most efficient way to perform many allocations at once.601pub fn stage_allocation(&'_ mut self) -> AllocationStage<'_, I> {602AllocationStage {603allocator: self,604slabs_to_reallocate: HashMap::default(),605}606}607608/// Creates a [`DeallocationStage`], enabling batched deallocation.609///610/// Deallocation of objects in the slab requires calling this function,611/// calling [`DeallocationStage::free`] on the resulting612/// [`DeallocationStage`], and finally calling613/// [`DeallocationStage::commit`]. Grouping deallocations into a batch,614/// preferably at most one per frame, is the most efficient way to perform615/// many deallocations at once.616pub fn stage_deallocation(&'_ mut self) -> DeallocationStage<'_, I> {617DeallocationStage {618allocator: self,619empty_slabs: HashSet::default(),620}621}622623/// Allocates space for data with the given byte size and layout in the624/// appropriate slab, creating that slab if necessary.625fn allocate(626&mut self,627key: &I::Key,628data_byte_len: u64,629layout: I::Layout,630slabs_to_grow: &mut HashMap<SlabId<I>, SlabToReallocate>,631settings: &SlabAllocatorSettings,632) {633debug_assert!(!self.key_to_slab.contains_key(key));634635let data_element_count = data_byte_len.div_ceil(layout.size()) as u32;636let data_slot_count = data_element_count.div_ceil(layout.elements_per_slot());637let padding = data_slot_count * layout.elements_per_slot() - data_element_count;638639// If the data is too large for a slab, give it a slab of its own.640if data_slot_count as u64 * layout.slot_size()641>= settings.large_threshold.min(settings.max_slab_size)642{643self.allocate_large(key, layout);644} else {645self.allocate_general(646key,647data_slot_count,648padding,649layout,650slabs_to_grow,651settings,652);653}654}655656/// Allocates space for data with the given slot size and layout in the657/// appropriate general slab.658fn allocate_general(659&mut self,660key: &I::Key,661data_slot_count: u32,662padding: u32,663layout: I::Layout,664slabs_to_grow: &mut HashMap<SlabId<I>, SlabToReallocate>,665settings: &SlabAllocatorSettings,666) {667let candidate_slabs = self.slab_layouts.entry(layout.clone()).or_default();668669// Loop through the slabs that accept elements of the appropriate type670// and try to allocate the data inside them. We go with the first one671// that succeeds.672let mut data_allocation = None;673for &slab_id in &*candidate_slabs {674let Some(Slab::General(slab)) = self.slabs.get_mut(&slab_id) else {675unreachable!("Slab not found")676};677678let Some(allocation) = slab.allocator.allocate(data_slot_count) else {679continue;680};681682// Try to fit the object in the slab, growing if necessary.683match slab.grow_if_necessary(allocation.offset + data_slot_count, settings) {684SlabGrowthResult::NoGrowthNeeded => {}685SlabGrowthResult::NeededGrowth(slab_to_reallocate) => {686// If we already grew the slab this frame, don't replace the687// `SlabToReallocate` entry. We want to keep the entry688// corresponding to the size that the slab had at the start689// of the frame, so that we can copy only the used portion690// of the initial buffer to the new one.691if let Entry::Vacant(vacant_entry) = slabs_to_grow.entry(slab_id) {692vacant_entry.insert(slab_to_reallocate);693}694}695SlabGrowthResult::CantGrow => continue,696}697698data_allocation = Some(SlabItemAllocation {699slab_id,700slab_allocation: SlabAllocation {701allocation,702slot_count: data_slot_count,703padding,704},705});706break;707}708709// If we still have no allocation, make a new slab.710if data_allocation.is_none() {711let new_slab_id = self.next_slab_id;712self.next_slab_id.id =713NonMaxU32::new(self.next_slab_id.id.get() + 1).unwrap_or_default();714715let new_slab = GeneralSlab::new(716new_slab_id,717&mut data_allocation,718settings,719layout,720data_slot_count,721padding,722);723724self.slabs.insert(new_slab_id, Slab::General(new_slab));725candidate_slabs.push(new_slab_id);726slabs_to_grow.insert(new_slab_id, SlabToReallocate::default());727}728729let data_allocation = data_allocation.expect("Should have been able to allocate");730731// Mark the allocation as pending. Don't copy it in just yet; further732// data loaded this frame may result in its final allocation location733// changing.734if let Some(Slab::General(general_slab)) = self.slabs.get_mut(&data_allocation.slab_id) {735general_slab736.pending_allocations737.insert(key.clone(), data_allocation.slab_allocation);738};739740self.record_allocation(key, data_allocation.slab_id);741}742743/// Allocates an object into its own dedicated slab.744fn allocate_large(&mut self, key: &I::Key, layout: I::Layout) {745let new_slab_id = self.next_slab_id;746self.next_slab_id.id = NonMaxU32::new(self.next_slab_id.id.get() + 1).unwrap_or_default();747748self.record_allocation(key, new_slab_id);749750self.slabs.insert(751new_slab_id,752Slab::LargeObject(LargeObjectSlab {753buffer: None,754element_layout: layout,755}),756);757}758759/// Given a slab and the key corresponding to an object within it, marks760/// the allocation as free.761///762/// If this results in the slab becoming empty, this function adds the slab763/// to the `empty_slabs` set.764fn free_allocation_in_slab(765&mut self,766key: &I::Key,767slab_id: SlabId<I>,768empty_slabs: &mut HashSet<SlabId<I>>,769) {770let Some(slab) = self.slabs.get_mut(&slab_id) else {771error!("Double free: attempted to free data in a nonexistent slab");772return;773};774775match *slab {776Slab::General(ref mut general_slab) => {777let Some(slab_allocation) = general_slab778.resident_allocations779.remove(key)780.or_else(|| general_slab.pending_allocations.remove(key))781else {782return;783};784785general_slab.allocator.free(slab_allocation.allocation);786787if general_slab.is_empty() {788empty_slabs.insert(slab_id);789}790}791Slab::LargeObject(_) => {792empty_slabs.insert(slab_id);793}794}795}796797/// Reallocates a slab that needs to be resized, or allocates a new slab.798///799/// This performs the actual growth operation that800/// [`GeneralSlab::grow_if_necessary`] scheduled. We do the growth in two801/// phases so that, if a slab grows multiple times in the same frame, only802/// one new buffer is reallocated, rather than reallocating the buffer803/// multiple times.804fn reallocate_slab(805&mut self,806render_device: &RenderDevice,807render_queue: &RenderQueue,808slab_id: SlabId<I>,809slab_to_grow: SlabToReallocate,810) {811let Some(Slab::General(slab)) = self.slabs.get_mut(&slab_id) else {812error!("Couldn't find slab {} to grow", slab_id);813return;814};815816let old_buffer = slab.buffer.take();817818let buffer_usages =819BufferUsages::COPY_SRC | BufferUsages::COPY_DST | slab.element_layout.buffer_usages();820821// Create the buffer.822let new_buffer = render_device.create_buffer(&BufferDescriptor {823label: Some(&format!(824"general {} slab {} ({}buffer)",825I::label(),826slab_id,827buffer_usages_to_str(buffer_usages)828)),829size: slab.current_slot_capacity as u64 * slab.element_layout.slot_size(),830usage: buffer_usages | self.extra_buffer_usages,831mapped_at_creation: false,832});833834slab.buffer = Some(new_buffer.clone());835836let Some(old_buffer) = old_buffer else { return };837838// In order to do buffer copies, we need a command encoder.839let mut encoder = render_device.create_command_encoder(&CommandEncoderDescriptor {840label: Some(&*format!("{} slab resize encoder", I::label())),841});842843// Copy the data from the old buffer into the new one.844encoder.copy_buffer_to_buffer(845&old_buffer,8460,847&new_buffer,8480,849slab_to_grow.old_slot_capacity as u64 * slab.element_layout.slot_size(),850);851852let command_buffer = encoder.finish();853render_queue.submit([command_buffer]);854}855856/// Records the location of the given newly-allocated data in the857/// [`Self::key_to_slab`] table.858fn record_allocation(&mut self, key: &I::Key, slab_id: SlabId<I>) {859self.key_to_slab.insert(key.clone(), slab_id);860}861862/// Returns the GPU buffer corresponding to the slab with the given ID if863/// that slab has been uploaded to the GPU.864pub fn buffer_for_slab(&self, slab_id: SlabId<I>) -> Option<&Buffer> {865self.slabs.get(&slab_id).and_then(|slab| slab.buffer())866}867868/// Given a slab and the key of data located with it, returns the buffer869/// and range of that data within the slab.870pub fn slab_allocation_slice(871&self,872key: &I::Key,873slab_id: SlabId<I>,874) -> Option<SlabAllocationBufferSlice<'_, I>> {875match self.slabs.get(&slab_id)? {876Slab::General(general_slab) => {877let slab_allocation = general_slab.resident_allocations.get(key)?;878Some(SlabAllocationBufferSlice {879buffer: general_slab.buffer.as_ref()?,880range: (slab_allocation.allocation.offset881* general_slab.element_layout.elements_per_slot())882..((slab_allocation.allocation.offset + slab_allocation.slot_count)883* general_slab.element_layout.elements_per_slot())884- slab_allocation.padding,885phantom: PhantomData,886})887}888889Slab::LargeObject(large_object_slab) => {890let buffer = large_object_slab.buffer.as_ref()?;891Some(SlabAllocationBufferSlice {892buffer,893range: 0..((buffer.size() / large_object_slab.element_layout.size()) as u32),894phantom: PhantomData,895})896}897}898}899900fn free_empty_slabs(&mut self, empty_slabs: impl Iterator<Item = SlabId<I>>) {901for empty_slab in empty_slabs {902self.slab_layouts.values_mut().for_each(|slab_ids| {903let idx = slab_ids.iter().position(|&slab_id| slab_id == empty_slab);904if let Some(idx) = idx {905slab_ids.remove(idx);906}907});908self.slabs.remove(&empty_slab);909}910}911912/// Get the number of allocated slabs913pub fn slab_count(&self) -> usize {914self.slabs.len()915}916917/// Get the total size of all allocated slabs918pub fn slabs_size(&self) -> u64 {919self.slabs.iter().map(|slab| slab.1.buffer_size()).sum()920}921922/// Copies data into an allocated slab.923///924/// `len` specifies the size of the data to be copied *in bytes*. The given925/// `fill_data` callback is expected to write the data into the given slice;926/// this callback approach avoids a copy.927pub fn copy_element_data(928&mut self,929key: &I::Key,930len: usize,931fill_data: impl Fn(WriteOnly<[u8]>),932render_device: &RenderDevice,933render_queue: &RenderQueue,934) {935let Some(slab_id) = self.key_to_slab.get(key) else {936error!("Use-after-free: attempted to copy element data for an unallocated key");937return;938};939let Some(slab) = self.slabs.get_mut(slab_id) else {940error!("Use-after-free: attempted to copy element data into a nonexistent slab");941return;942};943944match *slab {945Slab::General(ref mut general_slab) => {946let (Some(buffer), Some(allocated_range)) = (947&general_slab.buffer,948general_slab.pending_allocations.remove(key),949) else {950return;951};952953let slot_size = general_slab.element_layout.slot_size();954955// round up size to a multiple of the slot size to satisfy wgpu956// alignment requirements957if let Some(size) = BufferSize::new((len as u64).next_multiple_of(slot_size)) {958// Write the data in.959if let Some(mut buffer) = render_queue.write_buffer_with(960buffer,961allocated_range.allocation.offset as u64 * slot_size,962size,963) {964let slice = buffer.slice(..len);965fill_data(slice);966}967}968969// Mark the allocation as resident.970general_slab971.resident_allocations972.insert(key.clone(), allocated_range);973}974975Slab::LargeObject(ref mut large_object_slab) => {976debug_assert!(large_object_slab.buffer.is_none());977978// Create the buffer and its data in one go.979let buffer_usages = large_object_slab.element_layout.buffer_usages();980let buffer = render_device.create_buffer(&BufferDescriptor {981label: Some(&format!(982"large {} slab {} ({}buffer)",983I::label(),984slab_id,985buffer_usages_to_str(buffer_usages)986)),987size: len as u64,988usage: buffer_usages | BufferUsages::COPY_DST,989mapped_at_creation: true,990});991{992let mut slice = buffer.slice(..).get_mapped_range_mut();993994fill_data(slice.slice(..len));995}996buffer.unmap();997large_object_slab.buffer = Some(buffer);998}999}1000}1001}10021003/// The results of [`GeneralSlab::grow_if_necessary`].1004enum SlabGrowthResult {1005/// The data already fits in the slab; the slab doesn't need to grow.1006NoGrowthNeeded,1007/// The slab needed to grow.1008///1009/// The [`SlabToReallocate`] contains the old capacity of the slab.1010NeededGrowth(SlabToReallocate),1011/// The slab wanted to grow but couldn't because it hit its maximum size.1012CantGrow,1013}10141015impl<I> GeneralSlab<I>1016where1017I: SlabItem,1018{1019/// Creates a new growable slab big enough to hold a single element of1020/// `data_slot_count` size with the given `layout`.1021fn new(1022new_slab_id: SlabId<I>,1023maybe_slab_item_allocation: &mut Option<SlabItemAllocation<I>>,1024settings: &SlabAllocatorSettings,1025layout: I::Layout,1026data_slot_count: u32,1027padding: u32,1028) -> GeneralSlab<I> {1029let initial_slab_slot_capacity = (settings.min_slab_size.div_ceil(layout.slot_size())1030as u32)1031.max(offset_allocator::ext::min_allocator_size(data_slot_count));1032let max_slab_slot_capacity = (settings.max_slab_size.div_ceil(layout.slot_size()) as u32)1033.max(offset_allocator::ext::min_allocator_size(data_slot_count));10341035let mut new_slab = GeneralSlab {1036allocator: Allocator::new(max_slab_slot_capacity),1037buffer: None,1038resident_allocations: HashMap::default(),1039pending_allocations: HashMap::default(),1040element_layout: layout,1041current_slot_capacity: initial_slab_slot_capacity,1042};10431044// This should never fail.1045if let Some(allocation) = new_slab.allocator.allocate(data_slot_count) {1046*maybe_slab_item_allocation = Some(SlabItemAllocation {1047slab_id: new_slab_id,1048slab_allocation: SlabAllocation {1049slot_count: data_slot_count,1050allocation,1051padding,1052},1053});1054}10551056new_slab1057}10581059/// Checks to see if the size of this slab is at least `new_size_in_slots`1060/// and grows the slab if it isn't.1061///1062/// The returned [`SlabGrowthResult`] describes whether the slab needed to1063/// grow and whether, if so, it was successful in doing so.1064fn grow_if_necessary(1065&mut self,1066new_size_in_slots: u32,1067settings: &SlabAllocatorSettings,1068) -> SlabGrowthResult {1069// Is the slab big enough already?1070let initial_slot_capacity = self.current_slot_capacity;1071if self.current_slot_capacity >= new_size_in_slots {1072return SlabGrowthResult::NoGrowthNeeded;1073}10741075// Try to grow in increments of `SlabAllocatorSettings::growth_factor`1076// until we're big enough.1077while self.current_slot_capacity < new_size_in_slots {1078let new_slab_slot_capacity =1079((self.current_slot_capacity as f64 * settings.growth_factor).ceil() as u32)1080.min((settings.max_slab_size / self.element_layout.slot_size()) as u32);1081if new_slab_slot_capacity == self.current_slot_capacity {1082// The slab is full.1083return SlabGrowthResult::CantGrow;1084}10851086self.current_slot_capacity = new_slab_slot_capacity;1087}10881089// Tell our caller what we did.1090SlabGrowthResult::NeededGrowth(SlabToReallocate {1091old_slot_capacity: initial_slot_capacity,1092})1093}10941095/// Returns true if this slab is empty.1096fn is_empty(&self) -> bool {1097self.resident_allocations.is_empty() && self.pending_allocations.is_empty()1098}1099}11001101/// Returns a string describing the given buffer usages.1102fn buffer_usages_to_str(buffer_usages: BufferUsages) -> &'static str {1103if buffer_usages.contains(BufferUsages::VERTEX) {1104"vertex "1105} else if buffer_usages.contains(BufferUsages::INDEX) {1106"index "1107} else if buffer_usages.contains(BufferUsages::STORAGE) {1108"storage "1109} else {1110""1111}1112}111311141115