#[cfg(feature = "std")] use alloc::sync::Arc; use alloc::{boxed::Box, string::ToString, vec::Vec}; #[cfg(feature = "std")] use std::backtrace::Backtrace;
use log::debug; use objc2::{rc::Retained, runtime::ProtocolObject}; use objc2_foundation::{ns_string, NSString}; #[cfg(doc)] use objc2_metal::{MTLAllocation, MTLResource}; use objc2_metal::{
MTLCPUCacheMode, MTLDevice, MTLHeap, MTLHeapDescriptor, MTLHeapType, MTLResidencySet,
MTLResourceOptions, MTLStorageMode, MTLTextureDescriptor,
};
#[cfg(feature = "visualizer")] mod visualizer; #[cfg(feature = "visualizer")] pubuse visualizer::AllocatorVisualizer;
impl Allocation { /// Returns the [`MTLHeap`] object that is backing this allocation. /// /// This heap object can be shared with multiple other allocations and shouldn't be allocated from /// without this library, because that will lead to undefined behavior. /// /// # Safety /// When allocating new buffers, textures, or other resources on this [`MTLHeap`], be sure to /// pass [`Self::offset()`] and not exceed [`Self::size()`] to not allocate new resources on top /// of existing [`Allocation`]s. /// /// Also, this [`Allocation`] must not be [`Allocator::free()`]d while such a created resource /// on this [`MTLHeap`] is still live. pubunsafefn heap(&self) -> &ProtocolObject<dyn MTLHeap> {
&self.heap
}
/// Returns the size of the allocation pubfn size(&self) -> u64 { self.size
}
/// Returns the offset of the allocation on the [`MTLHeap`]. /// /// Since all [`Allocation`]s are suballocated within a [`MTLHeap`], this offset always needs to /// be supplied. See the safety documentation on [`Self::heap()`]. pubfn offset(&self) -> u64 { self.offset
}
#[derive(Clone, Debug)] pubstruct AllocationCreateDesc<'a> { /// Name of the allocation, for tracking and debugging purposes pub name: &'a str, /// Location where the memory allocation should be stored pub location: MemoryLocation, pub size: u64, pub alignment: u64,
}
pubfn acceleration_structure_with_size(
device: &ProtocolObject<dyn MTLDevice>,
name: &'a str,
size: u64, // TODO: usize
location: MemoryLocation,
) -> Self { // TODO: See if we can mark this function as safe, after checking what happens if size is too large? // What other preconditions need to be upheld? let size_and_align = unsafe { device.heapAccelerationStructureSizeAndAlignWithSize(size as usize) }; Self {
name,
location,
size: size_and_align.size as u64,
alignment: size_and_align.align as u64,
}
}
}
#[derive(Debug)] pubstruct AllocatorCreateDesc { pub device: Retained<ProtocolObject<dyn MTLDevice>>, pub debug_settings: AllocatorDebugSettings, pub allocation_sizes: AllocationSizes, /// Whether to create a [`MTLResidencySet`] containing all live heaps, that can be retrieved via /// [`Allocator::residency_set()`]. Only supported on `MacOS 15.0+` / `iOS 18.0+`. pub create_residency_set: bool,
}
let is_host = self.heap_properties.storageMode() != MTLStorageMode::Private; let memblock_size = allocation_sizes.get_memblock_size(is_host, self.active_general_blocks);
let size = desc.size; let alignment = desc.alignment;
// Create a dedicated block for large memory allocations if size > memblock_size { let mem_block = MemoryBlock::new(
device,
size,
&self.heap_properties, true, self.memory_location,
)?;
let mem_block = self.memory_blocks[new_block_index]
.as_mut()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some".into()))?; let allocation = mem_block.sub_allocator.allocate(
size,
alignment,
allocation_type, 1,
desc.name, #[cfg(feature = "std")]
backtrace,
); let (offset, chunk_id) = match allocation {
Err(AllocationError::OutOfMemory) => Err(AllocationError::Internal( "Allocation that must succeed failed. This is a bug in the allocator.".into(),
)),
a => a,
}?;
// We only want to destroy this now-empty block if it is either a dedicated/personal // allocation, or a block supporting sub-allocations that is not the last one (ensuring // there's always at least one block/allocator readily available). let is_dedicated_or_not_last_general_block =
!mem_block.sub_allocator.supports_general_allocations()
|| self.active_general_blocks > 1; if mem_block.sub_allocator.is_empty() && is_dedicated_or_not_last_general_block { let block = self.memory_blocks[block_idx]
.take()
.ok_or_else(|| AllocationError::Internal("Memory block must be Some.".into()))?;
if block.sub_allocator.supports_general_allocations() { self.active_general_blocks -= 1;
}
/// Current total capacity of memory blocks allocated on the device, in bytes pubfn capacity(&self) -> u64 { letmut total_capacity_bytes = 0;
for memory_type in &self.memory_types { for block in memory_type.memory_blocks.iter().flatten() {
total_capacity_bytes += block.size;
}
}
total_capacity_bytes
}
/// Optional residency set containing all heap allocations created/owned by this allocator to /// be made resident at once when its allocations are used on the GPU. The caller _must_ invoke /// [`MTLResidencySet::commit()`] whenever these resources are used to make sure the latest /// changes are visible to Metal, e.g. before committing a command buffer. /// /// This residency set can be attached to individual command buffers or to a queue directly /// since usage of allocated resources is expected to be global. /// /// Alternatively callers can build up their own residency set(s) based on individual /// [`MTLAllocation`]s [^heap-allocation] rather than making all heaps allocated via /// `gpu-allocator` resident at once. /// /// [^heap-allocation]: Note that [`MTLHeap`]s returned by [`Allocator::heaps()`] are also /// allocations. If individual placed [`MTLResource`]s on a heap are made resident, the entire /// heap will be made resident. /// /// Callers still need to be careful to make resources created outside of `gpu-allocator` /// resident on the GPU, such as indirect command buffers. /// /// This residency set is only available when requested via /// [`AllocatorCreateDesc::create_residency_set`], otherwise this function returns [`None`]. pubfn residency_set(&self) -> Option<&Retained<ProtocolObject<dyn MTLResidencySet>>> { // Return the retained object so that the caller also has a way to store it, since we will // keep using and updating the same object going forward. self.global_residency_set.as_ref()
}
}