use self::host::SlotId;
use super::{array_vec::ArrayVec, AllocationCreateInfo, AllocationCreationError};
use crate::{
device::{Device, DeviceOwned},
image::ImageTiling,
memory::DeviceMemory,
DeviceSize, OomError, VulkanError, VulkanObject,
};
use crossbeam_queue::ArrayQueue;
use parking_lot::Mutex;
use std::{
cell::Cell,
error::Error,
ffi::c_void,
fmt::{self, Display},
mem::{self, ManuallyDrop, MaybeUninit},
num::NonZeroU64,
ops::Range,
ptr::{self, NonNull},
slice,
sync::{
atomic::{AtomicU64, Ordering},
Arc,
},
};
#[derive(Debug)]
pub struct MemoryAlloc {
offset: DeviceSize,
size: DeviceSize,
allocation_type: AllocationType,
mapped_ptr: Option<NonNull<c_void>>,
atom_size: Option<NonZeroU64>,
parent: AllocParent,
}
#[derive(Debug)]
enum AllocParent {
FreeList {
allocator: Arc<FreeListAllocator>,
id: SlotId,
},
Buddy {
allocator: Arc<BuddyAllocator>,
order: usize,
offset: DeviceSize,
},
Pool {
allocator: Arc<PoolAllocatorInner>,
index: DeviceSize,
},
Bump(Arc<BumpAllocator>),
Root(Arc<DeviceMemory>),
Dedicated(DeviceMemory),
}
unsafe impl Send for MemoryAlloc {}
unsafe impl Sync for MemoryAlloc {}
impl MemoryAlloc {
#[inline]
pub fn new(device_memory: DeviceMemory) -> Result<Self, AllocationCreationError> {
let device = device_memory.device();
let physical_device = device.physical_device();
let memory_type_index = device_memory.memory_type_index();
let property_flags = &physical_device.memory_properties().memory_types
[memory_type_index as usize]
.property_flags;
let mapped_ptr = if property_flags.host_visible {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
unsafe {
(fns.v1_0.map_memory)(
device.handle(),
device_memory.handle(),
0,
ash::vk::WHOLE_SIZE,
ash::vk::MemoryMapFlags::empty(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
NonNull::new(output.assume_init())
}
} else {
None
};
let atom_size = (property_flags.host_visible && !property_flags.host_coherent)
.then_some(physical_device.properties().non_coherent_atom_size)
.and_then(NonZeroU64::new);
Ok(MemoryAlloc {
offset: 0,
size: device_memory.allocation_size(),
allocation_type: AllocationType::Unknown,
mapped_ptr,
atom_size,
parent: if device_memory.is_dedicated() {
AllocParent::Dedicated(device_memory)
} else {
AllocParent::Root(Arc::new(device_memory))
},
})
}
#[inline]
pub fn offset(&self) -> DeviceSize {
self.offset
}
#[inline]
pub fn size(&self) -> DeviceSize {
self.size
}
#[inline]
pub fn allocation_type(&self) -> AllocationType {
self.allocation_type
}
#[inline]
pub fn mapped_ptr(&self) -> Option<NonNull<c_void>> {
self.mapped_ptr
}
#[inline]
pub unsafe fn mapped_slice(&self) -> Option<&[u8]> {
self.mapped_ptr
.map(|ptr| slice::from_raw_parts(ptr.as_ptr().cast(), self.size as usize))
}
#[inline]
pub unsafe fn mapped_slice_mut(&mut self) -> Option<&mut [u8]> {
self.mapped_ptr
.map(|ptr| slice::from_raw_parts_mut(ptr.as_ptr().cast(), self.size as usize))
}
pub(crate) unsafe fn write(&self, range: Range<DeviceSize>) -> Option<&mut [u8]> {
debug_assert!(!range.is_empty() && range.end <= self.size);
self.mapped_ptr.map(|ptr| {
slice::from_raw_parts_mut(
ptr.as_ptr().add(range.start as usize).cast(),
(range.end - range.start) as usize,
)
})
}
#[inline]
pub unsafe fn invalidate_range(&self, range: Range<DeviceSize>) -> Result<(), OomError> {
if let Some(atom_size) = self.atom_size {
let range = self.create_memory_range(range, atom_size.get());
let device = self.device();
let fns = device.fns();
(fns.v1_0.invalidate_mapped_memory_ranges)(device.handle(), 1, &range)
.result()
.map_err(VulkanError::from)?;
} else {
}
Ok(())
}
#[inline]
pub unsafe fn flush_range(&self, range: Range<DeviceSize>) -> Result<(), OomError> {
if let Some(atom_size) = self.atom_size {
let range = self.create_memory_range(range, atom_size.get());
let device = self.device();
let fns = device.fns();
(fns.v1_0.flush_mapped_memory_ranges)(device.handle(), 1, &range)
.result()
.map_err(VulkanError::from)?;
} else {
}
Ok(())
}
fn create_memory_range(
&self,
range: Range<DeviceSize>,
atom_size: DeviceSize,
) -> ash::vk::MappedMemoryRange {
assert!(!range.is_empty() && range.end <= self.size);
assert!(
range.start % atom_size == 0 && (range.end % atom_size == 0 || range.end == self.size)
);
let offset = self.offset + range.start;
let mut size = range.end - range.start;
let device_memory = self.device_memory();
if offset + size < device_memory.allocation_size() {
size = align_up(size, atom_size);
}
ash::vk::MappedMemoryRange {
memory: device_memory.handle(),
offset,
size,
..Default::default()
}
}
#[allow(dead_code)]
fn debug_validate_memory_range(&self, range: &Range<DeviceSize>) {
debug_assert!(!range.is_empty() && range.end <= self.size);
debug_assert!({
let atom_size = self
.device()
.physical_device()
.properties()
.non_coherent_atom_size;
range.start % atom_size == 0 && (range.end % atom_size == 0 || range.end == self.size)
});
}
pub(crate) fn atom_size(&self) -> Option<NonZeroU64> {
self.atom_size
}
#[inline]
pub fn device_memory(&self) -> &DeviceMemory {
match &self.parent {
AllocParent::FreeList { allocator, .. } => &allocator.device_memory,
AllocParent::Buddy { allocator, .. } => &allocator.device_memory,
AllocParent::Pool { allocator, .. } => &allocator.device_memory,
AllocParent::Bump(allocator) => &allocator.device_memory,
AllocParent::Root(device_memory) => device_memory,
AllocParent::Dedicated(device_memory) => device_memory,
}
}
#[inline]
pub fn parent_allocation(&self) -> Option<&Self> {
match &self.parent {
AllocParent::FreeList { allocator, .. } => Some(&allocator.region),
AllocParent::Buddy { allocator, .. } => Some(&allocator.region),
AllocParent::Pool { allocator, .. } => Some(&allocator.region),
AllocParent::Bump(allocator) => Some(&allocator.region),
AllocParent::Root(_) => None,
AllocParent::Dedicated(_) => None,
}
}
#[inline]
pub fn is_root(&self) -> bool {
matches!(&self.parent, AllocParent::Root(_))
}
#[inline]
pub fn is_dedicated(&self) -> bool {
matches!(&self.parent, AllocParent::Dedicated(_))
}
#[inline]
pub fn try_unwrap(self) -> Result<DeviceMemory, Self> {
let this = ManuallyDrop::new(self);
match unsafe { ptr::read(&this.parent) } {
AllocParent::Root(device_memory) => {
Arc::try_unwrap(device_memory).map_err(|device_memory| {
mem::forget(device_memory);
ManuallyDrop::into_inner(this)
})
}
parent => {
mem::forget(parent);
Err(ManuallyDrop::into_inner(this))
}
}
}
#[inline]
pub unsafe fn alias(&self) -> Option<Self> {
self.root().map(|device_memory| MemoryAlloc {
parent: AllocParent::Root(device_memory.clone()),
..*self
})
}
fn root(&self) -> Option<&Arc<DeviceMemory>> {
match &self.parent {
AllocParent::FreeList { allocator, .. } => Some(&allocator.device_memory),
AllocParent::Buddy { allocator, .. } => Some(&allocator.device_memory),
AllocParent::Pool { allocator, .. } => Some(&allocator.device_memory),
AllocParent::Bump(allocator) => Some(&allocator.device_memory),
AllocParent::Root(device_memory) => Some(device_memory),
AllocParent::Dedicated(_) => None,
}
}
#[inline]
pub fn shift(&mut self, amount: DeviceSize) {
assert!(amount <= self.size);
self.offset += amount;
self.size -= amount;
}
#[inline]
pub fn shrink(&mut self, new_size: DeviceSize) {
assert!(new_size <= self.size);
self.size = new_size;
}
#[inline]
pub unsafe fn set_offset(&mut self, new_offset: DeviceSize) {
self.offset = new_offset;
}
#[inline]
pub unsafe fn set_size(&mut self, new_size: DeviceSize) {
self.size = new_size;
}
#[inline]
pub unsafe fn set_allocation_type(&mut self, new_type: AllocationType) {
self.allocation_type = new_type;
}
}
impl Drop for MemoryAlloc {
#[inline]
fn drop(&mut self) {
match &self.parent {
AllocParent::FreeList { allocator, id } => {
allocator.free(*id);
}
AllocParent::Buddy {
allocator,
order,
offset,
} => {
allocator.free(*order, *offset);
}
AllocParent::Pool { allocator, index } => {
allocator.free(*index);
}
AllocParent::Bump(_) => {}
AllocParent::Root(_) => {}
AllocParent::Dedicated(_) => {}
}
}
}
unsafe impl DeviceOwned for MemoryAlloc {
#[inline]
fn device(&self) -> &Arc<Device> {
self.device_memory().device()
}
}
pub unsafe trait Suballocator: DeviceOwned {
const IS_BLOCKING: bool;
const NEEDS_CLEANUP: bool;
fn new(region: MemoryAlloc) -> Self
where
Self: Sized;
fn allocate(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError>;
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
unsafe fn allocate_unchecked(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError>;
fn region(&self) -> &MemoryAlloc;
fn try_into_region(self) -> Result<MemoryAlloc, Self>
where
Self: Sized;
fn free_size(&self) -> DeviceSize;
fn cleanup(&mut self);
}
#[derive(Clone, Debug)]
pub struct SuballocationCreateInfo {
pub size: DeviceSize,
pub alignment: DeviceSize,
pub allocation_type: AllocationType,
pub _ne: crate::NonExhaustive,
}
impl Default for SuballocationCreateInfo {
#[inline]
fn default() -> Self {
SuballocationCreateInfo {
size: 0,
alignment: 0,
allocation_type: AllocationType::Unknown,
_ne: crate::NonExhaustive(()),
}
}
}
impl From<AllocationCreateInfo<'_>> for SuballocationCreateInfo {
#[inline]
fn from(create_info: AllocationCreateInfo<'_>) -> Self {
SuballocationCreateInfo {
size: create_info.requirements.size,
alignment: create_info.requirements.alignment,
allocation_type: create_info.allocation_type,
_ne: crate::NonExhaustive(()),
}
}
}
impl SuballocationCreateInfo {
pub(super) fn validate(&self) {
assert!(self.size > 0);
assert!(self.alignment > 0);
assert!(self.alignment.is_power_of_two());
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AllocationType {
Unknown = 0,
Linear = 1,
NonLinear = 2,
}
impl From<ImageTiling> for AllocationType {
#[inline]
fn from(tiling: ImageTiling) -> Self {
match tiling {
ImageTiling::Optimal => AllocationType::NonLinear,
ImageTiling::Linear => AllocationType::Linear,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum SuballocationCreationError {
OutOfRegionMemory,
FragmentedRegion,
BlockSizeExceeded,
}
impl Error for SuballocationCreationError {}
impl Display for SuballocationCreationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
Self::OutOfRegionMemory => "out of region memory",
Self::FragmentedRegion => "the region is too fragmented",
Self::BlockSizeExceeded =>
"the allocation size was greater than the suballocator's block size",
}
)
}
}
#[derive(Debug)]
pub struct FreeListAllocator {
region: MemoryAlloc,
device_memory: Arc<DeviceMemory>,
buffer_image_granularity: DeviceSize,
atom_size: DeviceSize,
free_size: AtomicU64,
state: Mutex<FreeListAllocatorState>,
}
impl FreeListAllocator {
#[inline]
pub fn new(region: MemoryAlloc) -> Arc<Self> {
const AVERAGE_ALLOCATION_SIZE: DeviceSize = 64 * 1024;
assert!(region.allocation_type == AllocationType::Unknown);
let device_memory = region
.root()
.expect("dedicated allocations can't be suballocated")
.clone();
let buffer_image_granularity = device_memory
.device()
.physical_device()
.properties()
.buffer_image_granularity;
let atom_size = region.atom_size.map(NonZeroU64::get).unwrap_or(1);
let free_size = AtomicU64::new(region.size);
let capacity = (region.size / AVERAGE_ALLOCATION_SIZE) as usize;
let mut nodes = host::PoolAllocator::new(capacity + 64);
let mut free_list = Vec::with_capacity(capacity / 16 + 16);
let root_id = nodes.allocate(SuballocationListNode {
prev: None,
next: None,
offset: region.offset,
size: region.size,
ty: SuballocationType::Free,
});
free_list.push(root_id);
let state = Mutex::new(FreeListAllocatorState { nodes, free_list });
Arc::new(FreeListAllocator {
region,
device_memory,
buffer_image_granularity,
atom_size,
free_size,
state,
})
}
fn free(&self, id: SlotId) {
let mut state = self.state.lock();
self.free_size
.fetch_add(state.nodes.get(id).size, Ordering::Release);
state.nodes.get_mut(id).ty = SuballocationType::Free;
state.coalesce(id);
state.free(id);
}
}
unsafe impl Suballocator for Arc<FreeListAllocator> {
const IS_BLOCKING: bool = true;
const NEEDS_CLEANUP: bool = false;
#[inline]
fn new(region: MemoryAlloc) -> Self {
FreeListAllocator::new(region)
}
#[inline]
fn allocate(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
create_info.validate();
unsafe { self.allocate_unchecked(create_info) }
}
#[inline]
unsafe fn allocate_unchecked(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
fn has_granularity_conflict(prev_ty: SuballocationType, ty: AllocationType) -> bool {
if prev_ty == SuballocationType::Free {
false
} else if prev_ty == SuballocationType::Unknown {
true
} else {
prev_ty != ty.into()
}
}
let SuballocationCreateInfo {
size,
alignment,
allocation_type,
_ne: _,
} = create_info;
let alignment = DeviceSize::max(alignment, self.atom_size);
let mut state = self.state.lock();
match state.free_list.last() {
Some(&last) if state.nodes.get(last).size >= size => {
let index = match state
.free_list
.binary_search_by_key(&size, |&x| state.nodes.get(x).size)
{
Ok(index) => index,
Err(index) => index,
};
for &id in &state.free_list[index..] {
let suballoc = state.nodes.get(id);
let mut offset = align_up(suballoc.offset, alignment);
if let Some(prev_id) = suballoc.prev {
let prev = state.nodes.get(prev_id);
if are_blocks_on_same_page(
prev.offset,
prev.size,
offset,
self.buffer_image_granularity,
) && has_granularity_conflict(prev.ty, allocation_type)
{
offset = align_up(offset, self.buffer_image_granularity);
}
}
if offset + size <= suballoc.offset + suballoc.size {
state.allocate(id);
state.split(id, offset, size);
state.nodes.get_mut(id).ty = allocation_type.into();
self.free_size.fetch_sub(size, Ordering::Release);
return Ok(MemoryAlloc {
offset,
size,
allocation_type,
mapped_ptr: self.region.mapped_ptr.and_then(|ptr| {
NonNull::new(
ptr.as_ptr().add((offset - self.region.offset) as usize),
)
}),
atom_size: self.region.atom_size,
parent: AllocParent::FreeList {
allocator: self.clone(),
id,
},
});
}
}
Err(SuballocationCreationError::OutOfRegionMemory)
}
Some(_) if self.free_size() >= size => {
Err(SuballocationCreationError::FragmentedRegion)
}
Some(_) => Err(SuballocationCreationError::OutOfRegionMemory),
None => Err(SuballocationCreationError::OutOfRegionMemory),
}
}
#[inline]
fn region(&self) -> &MemoryAlloc {
&self.region
}
#[inline]
fn try_into_region(self) -> Result<MemoryAlloc, Self> {
Arc::try_unwrap(self).map(|allocator| allocator.region)
}
#[inline]
fn free_size(&self) -> DeviceSize {
self.free_size.load(Ordering::Acquire)
}
#[inline]
fn cleanup(&mut self) {}
}
unsafe impl DeviceOwned for FreeListAllocator {
#[inline]
fn device(&self) -> &Arc<Device> {
self.device_memory.device()
}
}
#[derive(Debug)]
struct FreeListAllocatorState {
nodes: host::PoolAllocator<SuballocationListNode>,
free_list: Vec<SlotId>,
}
#[derive(Clone, Copy, Debug)]
struct SuballocationListNode {
prev: Option<SlotId>,
next: Option<SlotId>,
offset: DeviceSize,
size: DeviceSize,
ty: SuballocationType,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SuballocationType {
Unknown,
Linear,
NonLinear,
Free,
}
impl From<AllocationType> for SuballocationType {
fn from(ty: AllocationType) -> Self {
match ty {
AllocationType::Unknown => SuballocationType::Unknown,
AllocationType::Linear => SuballocationType::Linear,
AllocationType::NonLinear => SuballocationType::NonLinear,
}
}
}
impl FreeListAllocatorState {
fn allocate(&mut self, node_id: SlotId) {
debug_assert!(self.free_list.contains(&node_id));
let node = self.nodes.get(node_id);
match self
.free_list
.binary_search_by_key(&node.size, |&x| self.nodes.get(x).size)
{
Ok(index) => {
if self.free_list[index] == node_id {
self.free_list.remove(index);
return;
}
{
let mut index = index;
loop {
index = index.wrapping_sub(1);
if let Some(&id) = self.free_list.get(index) {
if id == node_id {
self.free_list.remove(index);
return;
}
if self.nodes.get(id).size != node.size {
break;
}
} else {
break;
}
}
}
{
let mut index = index;
loop {
index += 1;
if let Some(&id) = self.free_list.get(index) {
if id == node_id {
self.free_list.remove(index);
return;
}
if self.nodes.get(id).size != node.size {
break;
}
} else {
break;
}
}
}
unreachable!();
}
Err(_) => unreachable!(),
}
}
fn split(&mut self, node_id: SlotId, offset: DeviceSize, size: DeviceSize) {
let node = self.nodes.get(node_id);
debug_assert!(node.ty == SuballocationType::Free);
debug_assert!(offset >= node.offset);
debug_assert!(offset + size <= node.offset + node.size);
let padding_front = offset - node.offset;
let padding_back = node.offset + node.size - offset - size;
if padding_front > 0 {
let padding = SuballocationListNode {
prev: node.prev,
next: Some(node_id),
offset: node.offset,
size: padding_front,
ty: SuballocationType::Free,
};
let padding_id = self.nodes.allocate(padding);
if let Some(prev_id) = padding.prev {
self.nodes.get_mut(prev_id).next = Some(padding_id);
}
let node = self.nodes.get_mut(node_id);
node.prev = Some(padding_id);
node.offset = offset;
node.size -= padding.size;
self.free(padding_id);
}
if padding_back > 0 {
let padding = SuballocationListNode {
prev: Some(node_id),
next: node.next,
offset: offset + size,
size: padding_back,
ty: SuballocationType::Free,
};
let padding_id = self.nodes.allocate(padding);
if let Some(next_id) = padding.next {
self.nodes.get_mut(next_id).prev = Some(padding_id);
}
let node = self.nodes.get_mut(node_id);
node.next = Some(padding_id);
node.size -= padding.size;
self.free(padding_id);
}
}
fn free(&mut self, node_id: SlotId) {
debug_assert!(!self.free_list.contains(&node_id));
let node = self.nodes.get(node_id);
let (Ok(index) | Err(index)) = self
.free_list
.binary_search_by_key(&node.size, |&x| self.nodes.get(x).size);
self.free_list.insert(index, node_id);
}
fn coalesce(&mut self, node_id: SlotId) {
let node = self.nodes.get(node_id);
debug_assert!(node.ty == SuballocationType::Free);
if let Some(prev_id) = node.prev {
let prev = self.nodes.get(prev_id);
if prev.ty == SuballocationType::Free {
self.allocate(prev_id);
self.nodes.free(prev_id);
let node = self.nodes.get_mut(node_id);
node.prev = prev.prev;
node.offset = prev.offset;
node.size += prev.size;
if let Some(prev_id) = node.prev {
self.nodes.get_mut(prev_id).next = Some(node_id);
}
}
}
if let Some(next_id) = node.next {
let next = self.nodes.get(next_id);
if next.ty == SuballocationType::Free {
self.allocate(next_id);
self.nodes.free(next_id);
let node = self.nodes.get_mut(node_id);
node.next = next.next;
node.size += next.size;
if let Some(next_id) = node.next {
self.nodes.get_mut(next_id).prev = Some(node_id);
}
}
}
}
}
#[derive(Debug)]
pub struct BuddyAllocator {
region: MemoryAlloc,
device_memory: Arc<DeviceMemory>,
buffer_image_granularity: DeviceSize,
atom_size: DeviceSize,
free_size: AtomicU64,
state: Mutex<BuddyAllocatorState>,
}
impl BuddyAllocator {
const MIN_NODE_SIZE: DeviceSize = 16;
const MAX_ORDERS: usize = 32;
#[inline]
pub fn new(region: MemoryAlloc) -> Arc<Self> {
const EMPTY_FREE_LIST: Vec<DeviceSize> = Vec::new();
let max_order = (region.size / Self::MIN_NODE_SIZE).trailing_zeros() as usize;
assert!(region.allocation_type == AllocationType::Unknown);
assert!(region.size.is_power_of_two());
assert!(region.size >= Self::MIN_NODE_SIZE && max_order < Self::MAX_ORDERS);
let device_memory = region
.root()
.expect("dedicated allocations can't be suballocated")
.clone();
let buffer_image_granularity = device_memory
.device()
.physical_device()
.properties()
.buffer_image_granularity;
let atom_size = region.atom_size.map(NonZeroU64::get).unwrap_or(1);
let free_size = AtomicU64::new(region.size);
let mut free_list = ArrayVec::new(max_order + 1, [EMPTY_FREE_LIST; Self::MAX_ORDERS]);
free_list[max_order].push(region.offset);
let state = Mutex::new(BuddyAllocatorState { free_list });
Arc::new(BuddyAllocator {
region,
device_memory,
buffer_image_granularity,
atom_size,
free_size,
state,
})
}
fn free(&self, min_order: usize, mut offset: DeviceSize) {
let mut state = self.state.lock();
for (order, free_list) in state.free_list.iter_mut().enumerate().skip(min_order) {
let size = Self::MIN_NODE_SIZE << order;
let buddy_offset = ((offset - self.region.offset) ^ size) + self.region.offset;
match free_list.binary_search(&buddy_offset) {
Ok(index) => {
free_list.remove(index);
offset = DeviceSize::min(offset, buddy_offset);
}
Err(_) => {
let (Ok(index) | Err(index)) = free_list.binary_search(&offset);
free_list.insert(index, offset);
self.free_size
.fetch_add(Self::MIN_NODE_SIZE << min_order, Ordering::Release);
break;
}
}
}
}
}
unsafe impl Suballocator for Arc<BuddyAllocator> {
const IS_BLOCKING: bool = true;
const NEEDS_CLEANUP: bool = false;
#[inline]
fn new(region: MemoryAlloc) -> Self {
BuddyAllocator::new(region)
}
#[inline]
fn allocate(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
create_info.validate();
unsafe { self.allocate_unchecked(create_info) }
}
#[inline]
unsafe fn allocate_unchecked(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
fn prev_power_of_two(val: DeviceSize) -> DeviceSize {
const MAX_POWER_OF_TWO: DeviceSize = 1 << (DeviceSize::BITS - 1);
MAX_POWER_OF_TWO
.checked_shr(val.leading_zeros())
.unwrap_or(0)
}
let SuballocationCreateInfo {
mut size,
mut alignment,
allocation_type,
_ne: _,
} = create_info;
if allocation_type == AllocationType::Unknown
|| allocation_type == AllocationType::NonLinear
{
size = align_up(size, self.buffer_image_granularity);
alignment = DeviceSize::max(alignment, self.buffer_image_granularity);
}
let size = DeviceSize::max(size, BuddyAllocator::MIN_NODE_SIZE).next_power_of_two();
let alignment = DeviceSize::max(alignment, self.atom_size);
let min_order = (size / BuddyAllocator::MIN_NODE_SIZE).trailing_zeros() as usize;
let mut state = self.state.lock();
for (order, free_list) in state.free_list.iter_mut().enumerate().skip(min_order) {
for (index, &offset) in free_list.iter().enumerate() {
if offset % alignment == 0 {
free_list.remove(index);
for (order, free_list) in state
.free_list
.iter_mut()
.enumerate()
.skip(min_order)
.take(order - min_order)
.rev()
{
let size = BuddyAllocator::MIN_NODE_SIZE << order;
let right_child = offset + size;
let (Ok(index) | Err(index)) = free_list.binary_search(&right_child);
free_list.insert(index, right_child);
}
self.free_size.fetch_sub(size, Ordering::Release);
return Ok(MemoryAlloc {
offset,
size: create_info.size,
allocation_type,
mapped_ptr: self.region.mapped_ptr.and_then(|ptr| {
NonNull::new(ptr.as_ptr().add((offset - self.region.offset) as usize))
}),
atom_size: self.region.atom_size,
parent: AllocParent::Buddy {
allocator: self.clone(),
order: min_order,
offset, },
});
}
}
}
if prev_power_of_two(self.free_size()) >= create_info.size {
Err(SuballocationCreationError::FragmentedRegion)
} else {
Err(SuballocationCreationError::OutOfRegionMemory)
}
}
#[inline]
fn region(&self) -> &MemoryAlloc {
&self.region
}
#[inline]
fn try_into_region(self) -> Result<MemoryAlloc, Self> {
Arc::try_unwrap(self).map(|allocator| allocator.region)
}
#[inline]
fn free_size(&self) -> DeviceSize {
self.free_size.load(Ordering::Acquire)
}
#[inline]
fn cleanup(&mut self) {}
}
unsafe impl DeviceOwned for BuddyAllocator {
#[inline]
fn device(&self) -> &Arc<Device> {
self.device_memory.device()
}
}
#[derive(Debug)]
struct BuddyAllocatorState {
free_list: ArrayVec<Vec<DeviceSize>, { BuddyAllocator::MAX_ORDERS }>,
}
#[derive(Debug)]
#[repr(transparent)]
pub struct PoolAllocator<const BLOCK_SIZE: DeviceSize> {
inner: PoolAllocatorInner,
}
impl<const BLOCK_SIZE: DeviceSize> PoolAllocator<BLOCK_SIZE> {
#[inline]
pub fn new(
region: MemoryAlloc,
#[cfg(test)] buffer_image_granularity: DeviceSize,
) -> Arc<Self> {
Arc::new(PoolAllocator {
inner: PoolAllocatorInner::new(
region,
BLOCK_SIZE,
#[cfg(test)]
buffer_image_granularity,
),
})
}
#[inline]
pub fn block_size(&self) -> DeviceSize {
self.inner.block_size
}
#[inline]
pub fn block_count(&self) -> usize {
self.inner.free_list.capacity()
}
#[inline]
pub fn free_count(&self) -> usize {
self.inner.free_list.len()
}
}
unsafe impl<const BLOCK_SIZE: DeviceSize> Suballocator for Arc<PoolAllocator<BLOCK_SIZE>> {
const IS_BLOCKING: bool = false;
const NEEDS_CLEANUP: bool = false;
#[inline]
fn new(region: MemoryAlloc) -> Self {
PoolAllocator::new(
region,
#[cfg(test)]
1,
)
}
#[inline]
fn allocate(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
create_info.validate();
unsafe { self.allocate_unchecked(create_info) }
}
#[inline]
unsafe fn allocate_unchecked(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
Arc::from_raw(Arc::into_raw(self.clone()).cast::<PoolAllocatorInner>())
.allocate_unchecked(create_info)
}
#[inline]
fn region(&self) -> &MemoryAlloc {
&self.inner.region
}
#[inline]
fn try_into_region(self) -> Result<MemoryAlloc, Self> {
Arc::try_unwrap(self).map(|allocator| allocator.inner.region)
}
#[inline]
fn free_size(&self) -> DeviceSize {
self.free_count() as DeviceSize * self.block_size()
}
#[inline]
fn cleanup(&mut self) {}
}
unsafe impl<const BLOCK_SIZE: DeviceSize> DeviceOwned for PoolAllocator<BLOCK_SIZE> {
#[inline]
fn device(&self) -> &Arc<Device> {
self.inner.device_memory.device()
}
}
#[derive(Debug)]
struct PoolAllocatorInner {
region: MemoryAlloc,
device_memory: Arc<DeviceMemory>,
atom_size: DeviceSize,
block_size: DeviceSize,
free_list: ArrayQueue<DeviceSize>,
}
impl PoolAllocatorInner {
fn new(
region: MemoryAlloc,
mut block_size: DeviceSize,
#[cfg(test)] buffer_image_granularity: DeviceSize,
) -> Self {
let device_memory = region
.root()
.expect("dedicated allocations can't be suballocated")
.clone();
#[cfg(not(test))]
let buffer_image_granularity = device_memory
.device()
.physical_device()
.properties()
.buffer_image_granularity;
let atom_size = region.atom_size.map(NonZeroU64::get).unwrap_or(1);
if region.allocation_type == AllocationType::Unknown {
block_size = align_up(block_size, buffer_image_granularity);
}
let block_count = region.size / block_size;
let free_list = ArrayQueue::new(block_count as usize);
for i in 0..block_count {
free_list.push(i).unwrap();
}
PoolAllocatorInner {
region,
device_memory,
atom_size,
block_size,
free_list,
}
}
unsafe fn allocate_unchecked(
self: Arc<Self>,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
let SuballocationCreateInfo {
size,
alignment,
allocation_type: _,
_ne: _,
} = create_info;
let alignment = DeviceSize::max(alignment, self.atom_size);
let index = self
.free_list
.pop()
.ok_or(SuballocationCreationError::OutOfRegionMemory)?;
let unaligned_offset = self.region.offset + index * self.block_size;
let offset = align_up(unaligned_offset, alignment);
if offset + size > unaligned_offset + self.block_size {
self.free_list.push(index).unwrap();
return if size > self.block_size {
Err(SuballocationCreationError::BlockSizeExceeded)
} else {
Err(SuballocationCreationError::OutOfRegionMemory)
};
}
Ok(MemoryAlloc {
offset,
size,
allocation_type: self.region.allocation_type,
mapped_ptr: self.region.mapped_ptr.and_then(|ptr| {
NonNull::new(ptr.as_ptr().add((offset - self.region.offset) as usize))
}),
atom_size: self.region.atom_size,
parent: AllocParent::Pool {
allocator: self,
index,
},
})
}
fn free(&self, index: DeviceSize) {
self.free_list.push(index).unwrap();
}
}
#[derive(Debug)]
pub struct BumpAllocator {
region: MemoryAlloc,
device_memory: Arc<DeviceMemory>,
buffer_image_granularity: DeviceSize,
atom_size: DeviceSize,
state: AtomicU64,
}
impl BumpAllocator {
#[inline]
pub fn new(region: MemoryAlloc) -> Arc<Self> {
let device_memory = region
.root()
.expect("dedicated allocations can't be suballocated")
.clone();
let buffer_image_granularity = device_memory
.device()
.physical_device()
.properties()
.buffer_image_granularity;
let atom_size = region.atom_size.map(NonZeroU64::get).unwrap_or(1);
let state = AtomicU64::new(region.allocation_type as u64);
Arc::new(BumpAllocator {
region,
device_memory,
buffer_image_granularity,
atom_size,
state,
})
}
#[inline]
pub fn try_reset(self: &mut Arc<Self>) -> Result<(), BumpAllocatorResetError> {
Arc::get_mut(self)
.map(|allocator| {
*allocator.state.get_mut() = allocator.region.allocation_type as u64;
})
.ok_or(BumpAllocatorResetError)
}
#[inline]
pub unsafe fn reset_unchecked(&self) {
self.state
.store(self.region.allocation_type as u64, Ordering::Release);
}
}
unsafe impl Suballocator for Arc<BumpAllocator> {
const IS_BLOCKING: bool = false;
const NEEDS_CLEANUP: bool = true;
#[inline]
fn new(region: MemoryAlloc) -> Self {
BumpAllocator::new(region)
}
#[inline]
fn allocate(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
create_info.validate();
unsafe { self.allocate_unchecked(create_info) }
}
#[inline]
unsafe fn allocate_unchecked(
&self,
create_info: SuballocationCreateInfo,
) -> Result<MemoryAlloc, SuballocationCreationError> {
const SPIN_LIMIT: u32 = 6;
struct Backoff {
step: Cell<u32>,
}
impl Backoff {
fn new() -> Self {
Backoff { step: Cell::new(0) }
}
fn spin(&self) {
for _ in 0..1 << self.step.get().min(SPIN_LIMIT) {
core::hint::spin_loop();
}
if self.step.get() <= SPIN_LIMIT {
self.step.set(self.step.get() + 1);
}
}
}
fn has_granularity_conflict(prev_ty: AllocationType, ty: AllocationType) -> bool {
prev_ty == AllocationType::Unknown || prev_ty != ty
}
let SuballocationCreateInfo {
size,
alignment,
allocation_type,
_ne: _,
} = create_info;
let alignment = DeviceSize::max(alignment, self.atom_size);
let backoff = Backoff::new();
let mut state = self.state.load(Ordering::Relaxed);
loop {
let free_start = state >> 2;
let prev_alloc_type = match state & 0b11 {
0 => AllocationType::Unknown,
1 => AllocationType::Linear,
2 => AllocationType::NonLinear,
_ => unreachable!(),
};
let prev_end = self.region.offset + free_start;
let mut offset = align_up(prev_end, alignment);
if prev_end > 0
&& are_blocks_on_same_page(prev_end, 0, offset, self.buffer_image_granularity)
&& has_granularity_conflict(prev_alloc_type, allocation_type)
{
offset = align_up(offset, self.buffer_image_granularity);
}
let free_start = offset - self.region.offset + size;
if free_start > self.region.size {
return Err(SuballocationCreationError::OutOfRegionMemory);
}
let new_state = free_start << 2 | allocation_type as u64;
match self.state.compare_exchange_weak(
state,
new_state,
Ordering::Release,
Ordering::Relaxed,
) {
Ok(_) => {
return Ok(MemoryAlloc {
offset,
size,
allocation_type,
mapped_ptr: self.region.mapped_ptr.and_then(|ptr| {
NonNull::new(ptr.as_ptr().add((offset - self.region.offset) as usize))
}),
atom_size: self.region.atom_size,
parent: AllocParent::Bump(self.clone()),
});
}
Err(new_state) => {
state = new_state;
backoff.spin();
}
}
}
}
#[inline]
fn region(&self) -> &MemoryAlloc {
&self.region
}
#[inline]
fn try_into_region(self) -> Result<MemoryAlloc, Self> {
Arc::try_unwrap(self).map(|allocator| allocator.region)
}
#[inline]
fn free_size(&self) -> DeviceSize {
self.region.size - (self.state.load(Ordering::Acquire) >> 2)
}
#[inline]
fn cleanup(&mut self) {
let _ = self.try_reset();
}
}
unsafe impl DeviceOwned for BumpAllocator {
#[inline]
fn device(&self) -> &Arc<Device> {
self.device_memory.device()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BumpAllocatorResetError;
impl Error for BumpAllocatorResetError {}
impl Display for BumpAllocatorResetError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("the allocator is still in use")
}
}
pub(crate) fn align_up(val: DeviceSize, alignment: DeviceSize) -> DeviceSize {
align_down(val + alignment - 1, alignment)
}
fn align_down(val: DeviceSize, alignment: DeviceSize) -> DeviceSize {
debug_assert!(alignment.is_power_of_two());
val & !(alignment - 1)
}
fn are_blocks_on_same_page(
a_offset: DeviceSize,
a_size: DeviceSize,
b_offset: DeviceSize,
page_size: DeviceSize,
) -> bool {
debug_assert!(a_offset + a_size > 0);
debug_assert!(a_offset + a_size <= b_offset);
let a_end = a_offset + a_size - 1;
let a_end_page = align_down(a_end, page_size);
let b_start_page = align_down(b_offset, page_size);
a_end_page == b_start_page
}
mod host {
use std::num::NonZeroUsize;
#[derive(Debug)]
pub(super) struct PoolAllocator<T> {
pool: Vec<T>,
free_list: Vec<SlotId>,
}
impl<T> PoolAllocator<T> {
pub fn new(capacity: usize) -> Self {
debug_assert!(capacity > 0);
let mut pool = Vec::new();
let mut free_list = Vec::new();
pool.reserve_exact(capacity);
free_list.reserve_exact(capacity);
for index in (1..=capacity).rev() {
free_list.push(SlotId(NonZeroUsize::new(index).unwrap()));
}
PoolAllocator { pool, free_list }
}
pub fn allocate(&mut self, val: T) -> SlotId {
let id = self.free_list.pop().unwrap_or_else(|| {
let new_len = self.pool.len() * 3 / 2;
let additional = new_len - self.pool.len();
self.pool.reserve_exact(additional);
self.free_list.reserve_exact(additional);
let len = self.pool.len();
let cap = self.pool.capacity();
for id in (len + 2..=cap).rev() {
let id = SlotId(unsafe { NonZeroUsize::new_unchecked(id) });
self.free_list.push(id);
}
SlotId(NonZeroUsize::new(len + 1).unwrap())
});
if let Some(x) = self.pool.get_mut(id.0.get() - 1) {
*x = val;
} else {
debug_assert!(id.0.get() - 1 == self.pool.len());
self.pool.push(val);
}
id
}
pub fn free(&mut self, id: SlotId) {
debug_assert!(!self.free_list.contains(&id));
self.free_list.push(id);
}
pub fn get_mut(&mut self, id: SlotId) -> &mut T {
debug_assert!(!self.free_list.contains(&id));
unsafe { self.pool.get_unchecked_mut(id.0.get() - 1) }
}
}
impl<T: Copy> PoolAllocator<T> {
pub fn get(&self, id: SlotId) -> T {
debug_assert!(!self.free_list.contains(&id));
*unsafe { self.pool.get_unchecked(id.0.get() - 1) }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) struct SlotId(NonZeroUsize);
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
const DUMMY_INFO: SuballocationCreateInfo = SuballocationCreateInfo {
size: 1,
alignment: 1,
allocation_type: AllocationType::Unknown,
_ne: crate::NonExhaustive(()),
};
const DUMMY_INFO_LINEAR: SuballocationCreateInfo = SuballocationCreateInfo {
allocation_type: AllocationType::Linear,
..DUMMY_INFO
};
#[test]
fn free_list_allocator_capacity() {
const THREADS: DeviceSize = 12;
const ALLOCATIONS_PER_THREAD: DeviceSize = 100;
const ALLOCATION_STEP: DeviceSize = 117;
const REGION_SIZE: DeviceSize =
(ALLOCATION_STEP * (THREADS + 1) * THREADS / 2) * ALLOCATIONS_PER_THREAD;
let allocator = dummy_allocator!(FreeListAllocator, REGION_SIZE);
let allocs = ArrayQueue::new((ALLOCATIONS_PER_THREAD * THREADS) as usize);
thread::scope(|scope| {
for i in 1..=THREADS {
let (allocator, allocs) = (&allocator, &allocs);
scope.spawn(move || {
let size = i * ALLOCATION_STEP;
for _ in 0..ALLOCATIONS_PER_THREAD {
allocs
.push(
allocator
.allocate(SuballocationCreateInfo { size, ..DUMMY_INFO })
.unwrap(),
)
.unwrap();
}
});
}
});
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.free_size() == 0);
drop(allocs);
assert!(allocator.free_size() == REGION_SIZE);
assert!(allocator
.allocate(SuballocationCreateInfo {
size: REGION_SIZE,
..DUMMY_INFO
})
.is_ok());
}
#[test]
fn free_list_allocator_respects_alignment() {
const INFO: SuballocationCreateInfo = SuballocationCreateInfo {
alignment: 256,
..DUMMY_INFO
};
const REGION_SIZE: DeviceSize = 10 * INFO.alignment;
let allocator = dummy_allocator!(FreeListAllocator, REGION_SIZE);
let mut allocs = Vec::with_capacity(10);
for _ in 0..10 {
allocs.push(allocator.allocate(INFO).unwrap());
}
assert!(allocator.allocate(INFO).is_err());
assert!(allocator.free_size() == REGION_SIZE - 10);
}
#[test]
fn free_list_allocator_respects_granularity() {
const GRANULARITY: DeviceSize = 16;
const REGION_SIZE: DeviceSize = 2 * GRANULARITY;
let allocator = dummy_allocator!(FreeListAllocator, REGION_SIZE, GRANULARITY);
let mut linear_allocs = Vec::with_capacity(GRANULARITY as usize);
let mut non_linear_allocs = Vec::with_capacity(GRANULARITY as usize);
for i in 0..REGION_SIZE {
if i % 2 == 0 {
linear_allocs.push(
allocator
.allocate(SuballocationCreateInfo {
allocation_type: AllocationType::Linear,
..DUMMY_INFO
})
.unwrap(),
);
} else {
non_linear_allocs.push(
allocator
.allocate(SuballocationCreateInfo {
allocation_type: AllocationType::NonLinear,
..DUMMY_INFO
})
.unwrap(),
);
}
}
assert!(allocator.allocate(DUMMY_INFO_LINEAR).is_err());
assert!(allocator.free_size() == 0);
drop(linear_allocs);
assert!(allocator
.allocate(SuballocationCreateInfo {
size: GRANULARITY,
..DUMMY_INFO
})
.is_ok());
let _alloc = allocator.allocate(DUMMY_INFO).unwrap();
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.allocate(DUMMY_INFO_LINEAR).is_err());
}
#[test]
fn pool_allocator_capacity() {
const BLOCK_SIZE: DeviceSize = 1024;
fn dummy_allocator(
device: Arc<Device>,
allocation_size: DeviceSize,
) -> Arc<PoolAllocator<BLOCK_SIZE>> {
let device_memory = DeviceMemory::allocate(
device,
MemoryAllocateInfo {
allocation_size,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
PoolAllocator::new(MemoryAlloc::new(device_memory).unwrap(), 1)
}
let (device, _) = gfx_dev_and_queue!();
assert_should_panic!({ dummy_allocator(device.clone(), BLOCK_SIZE - 1) });
let allocator = dummy_allocator(device.clone(), 2 * BLOCK_SIZE - 1);
{
let alloc = allocator.allocate(DUMMY_INFO).unwrap();
assert!(allocator.allocate(DUMMY_INFO).is_err());
drop(alloc);
let _alloc = allocator.allocate(DUMMY_INFO).unwrap();
}
let allocator = dummy_allocator(device, 2 * BLOCK_SIZE);
{
let alloc1 = allocator.allocate(DUMMY_INFO).unwrap();
let alloc2 = allocator.allocate(DUMMY_INFO).unwrap();
assert!(allocator.allocate(DUMMY_INFO).is_err());
drop(alloc1);
let alloc1 = allocator.allocate(DUMMY_INFO).unwrap();
assert!(allocator.allocate(DUMMY_INFO).is_err());
drop(alloc1);
drop(alloc2);
let _alloc1 = allocator.allocate(DUMMY_INFO).unwrap();
let _alloc2 = allocator.allocate(DUMMY_INFO).unwrap();
}
}
#[test]
fn pool_allocator_respects_alignment() {
const BLOCK_SIZE: DeviceSize = 1024 + 128;
const INFO_A: SuballocationCreateInfo = SuballocationCreateInfo {
size: BLOCK_SIZE,
alignment: 256,
..DUMMY_INFO
};
const INFO_B: SuballocationCreateInfo = SuballocationCreateInfo {
size: 1024,
..INFO_A
};
let allocator = {
let (device, _) = gfx_dev_and_queue!();
let device_memory = DeviceMemory::allocate(
device,
MemoryAllocateInfo {
allocation_size: 10 * BLOCK_SIZE,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
PoolAllocator::<BLOCK_SIZE>::new(MemoryAlloc::new(device_memory).unwrap(), 1)
};
allocator.allocate(INFO_A).unwrap();
assert!(allocator.allocate(INFO_A).is_err());
allocator.allocate(INFO_A).unwrap();
assert!(allocator.allocate(INFO_A).is_err());
for _ in 0..10 {
allocator.allocate(INFO_B).unwrap();
}
}
#[test]
fn pool_allocator_respects_granularity() {
const BLOCK_SIZE: DeviceSize = 128;
fn dummy_allocator(
device: Arc<Device>,
allocation_type: AllocationType,
) -> Arc<PoolAllocator<BLOCK_SIZE>> {
let device_memory = DeviceMemory::allocate(
device,
MemoryAllocateInfo {
allocation_size: 1024,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
let mut region = MemoryAlloc::new(device_memory).unwrap();
unsafe { region.set_allocation_type(allocation_type) };
PoolAllocator::new(region, 256)
}
let (device, _) = gfx_dev_and_queue!();
let allocator = dummy_allocator(device.clone(), AllocationType::Unknown);
assert!(allocator.block_count() == 4);
let allocator = dummy_allocator(device.clone(), AllocationType::Linear);
assert!(allocator.block_count() == 8);
let allocator = dummy_allocator(device, AllocationType::NonLinear);
assert!(allocator.block_count() == 8);
}
#[test]
fn buddy_allocator_capacity() {
const MAX_ORDER: usize = 10;
const REGION_SIZE: DeviceSize = BuddyAllocator::MIN_NODE_SIZE << MAX_ORDER;
let allocator = dummy_allocator!(BuddyAllocator, REGION_SIZE);
let mut allocs = Vec::with_capacity(1 << MAX_ORDER);
for order in 0..=MAX_ORDER {
let size = BuddyAllocator::MIN_NODE_SIZE << order;
for _ in 0..1 << (MAX_ORDER - order) {
allocs.push(
allocator
.allocate(SuballocationCreateInfo { size, ..DUMMY_INFO })
.unwrap(),
);
}
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.free_size() == 0);
allocs.clear();
}
let mut orders = (0..MAX_ORDER).collect::<Vec<_>>();
for mid in 0..MAX_ORDER {
orders.rotate_left(mid);
for &order in &orders {
let size = BuddyAllocator::MIN_NODE_SIZE << order;
allocs.push(
allocator
.allocate(SuballocationCreateInfo { size, ..DUMMY_INFO })
.unwrap(),
);
}
let _alloc = allocator.allocate(DUMMY_INFO).unwrap();
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.free_size() == 0);
allocs.clear();
}
}
#[test]
fn buddy_allocator_respects_alignment() {
const REGION_SIZE: DeviceSize = 4096;
let allocator = dummy_allocator!(BuddyAllocator, REGION_SIZE);
{
const INFO: SuballocationCreateInfo = SuballocationCreateInfo {
alignment: 4096,
..DUMMY_INFO
};
let _alloc = allocator.allocate(INFO).unwrap();
assert!(allocator.allocate(INFO).is_err());
assert!(allocator.free_size() == REGION_SIZE - BuddyAllocator::MIN_NODE_SIZE);
}
{
const INFO_A: SuballocationCreateInfo = SuballocationCreateInfo {
alignment: 256,
..DUMMY_INFO
};
const ALLOCATIONS_A: DeviceSize = REGION_SIZE / INFO_A.alignment;
const INFO_B: SuballocationCreateInfo = SuballocationCreateInfo {
alignment: 16,
..DUMMY_INFO
};
const ALLOCATIONS_B: DeviceSize = REGION_SIZE / INFO_B.alignment - ALLOCATIONS_A;
let mut allocs =
Vec::with_capacity((REGION_SIZE / BuddyAllocator::MIN_NODE_SIZE) as usize);
for _ in 0..ALLOCATIONS_A {
allocs.push(allocator.allocate(INFO_A).unwrap());
}
assert!(allocator.allocate(INFO_A).is_err());
assert!(
allocator.free_size()
== REGION_SIZE - ALLOCATIONS_A * BuddyAllocator::MIN_NODE_SIZE
);
for _ in 0..ALLOCATIONS_B {
allocs.push(allocator.allocate(INFO_B).unwrap());
}
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.free_size() == 0);
}
}
#[test]
fn buddy_allocator_respects_granularity() {
const GRANULARITY: DeviceSize = 256;
const REGION_SIZE: DeviceSize = 2 * GRANULARITY;
let allocator = dummy_allocator!(BuddyAllocator, REGION_SIZE, GRANULARITY);
{
const ALLOCATIONS: DeviceSize = REGION_SIZE / BuddyAllocator::MIN_NODE_SIZE;
let mut allocs = Vec::with_capacity(ALLOCATIONS as usize);
for _ in 0..ALLOCATIONS {
allocs.push(allocator.allocate(DUMMY_INFO_LINEAR).unwrap());
}
assert!(allocator.allocate(DUMMY_INFO_LINEAR).is_err());
assert!(allocator.free_size() == 0);
}
{
let _alloc1 = allocator.allocate(DUMMY_INFO).unwrap();
let _alloc2 = allocator.allocate(DUMMY_INFO).unwrap();
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.free_size() == 0);
}
}
#[test]
fn bump_allocator_respects_alignment() {
const INFO: SuballocationCreateInfo = SuballocationCreateInfo {
alignment: 16,
..DUMMY_INFO
};
let allocator = dummy_allocator!(BumpAllocator, INFO.alignment * 10);
for _ in 0..10 {
allocator.allocate(INFO).unwrap();
}
assert!(allocator.allocate(INFO).is_err());
for _ in 0..INFO.alignment - 1 {
allocator.allocate(DUMMY_INFO).unwrap();
}
assert!(allocator.allocate(INFO).is_err());
assert!(allocator.free_size() == 0);
}
#[test]
fn bump_allocator_respects_granularity() {
const ALLOCATIONS: DeviceSize = 10;
const GRANULARITY: DeviceSize = 1024;
let mut allocator = dummy_allocator!(BumpAllocator, GRANULARITY * ALLOCATIONS, GRANULARITY);
for i in 0..ALLOCATIONS {
for _ in 0..GRANULARITY {
allocator
.allocate(SuballocationCreateInfo {
allocation_type: if i % 2 == 0 {
AllocationType::NonLinear
} else {
AllocationType::Linear
},
..DUMMY_INFO
})
.unwrap();
}
}
assert!(allocator.allocate(DUMMY_INFO_LINEAR).is_err());
assert!(allocator.free_size() == 0);
allocator.try_reset().unwrap();
for i in 0..ALLOCATIONS {
allocator
.allocate(SuballocationCreateInfo {
allocation_type: if i % 2 == 0 {
AllocationType::Linear
} else {
AllocationType::NonLinear
},
..DUMMY_INFO
})
.unwrap();
}
assert!(allocator.allocate(DUMMY_INFO_LINEAR).is_err());
assert!(allocator.free_size() == GRANULARITY - 1);
}
#[test]
fn bump_allocator_syncness() {
const THREADS: DeviceSize = 12;
const ALLOCATIONS_PER_THREAD: DeviceSize = 100_000;
const ALLOCATION_STEP: DeviceSize = 117;
const REGION_SIZE: DeviceSize =
(ALLOCATION_STEP * (THREADS + 1) * THREADS / 2) * ALLOCATIONS_PER_THREAD;
let mut allocator = dummy_allocator!(BumpAllocator, REGION_SIZE);
thread::scope(|scope| {
for i in 1..=THREADS {
let allocator = &allocator;
scope.spawn(move || {
let size = i * ALLOCATION_STEP;
for _ in 0..ALLOCATIONS_PER_THREAD {
allocator
.allocate(SuballocationCreateInfo { size, ..DUMMY_INFO })
.unwrap();
}
});
}
});
assert!(allocator.allocate(DUMMY_INFO).is_err());
assert!(allocator.free_size() == 0);
allocator.try_reset().unwrap();
assert!(allocator.free_size() == REGION_SIZE);
}
macro_rules! dummy_allocator {
($type:ty, $size:expr) => {
dummy_allocator!($type, $size, 1)
};
($type:ty, $size:expr, $granularity:expr) => {
dummy_allocator!($type, $size, $granularity, AllocationType::Unknown)
};
($type:ty, $size:expr, $granularity:expr, $allocation_type:expr) => {{
let (device, _) = gfx_dev_and_queue!();
let device_memory = DeviceMemory::allocate(
device,
MemoryAllocateInfo {
allocation_size: $size,
memory_type_index: 0,
..Default::default()
},
)
.unwrap();
let mut allocator = <$type>::new(MemoryAlloc::new(device_memory).unwrap());
Arc::get_mut(&mut allocator)
.unwrap()
.buffer_image_granularity = $granularity;
allocator
}};
}
use crate::memory::MemoryAllocateInfo;
pub(self) use dummy_allocator;
}