Skip to content

Commit

Permalink
Add support for growing the memory block sizes
Browse files Browse the repository at this point in the history
And fix the vulkan allocator ignoring the allocation_sizes parameter.
  • Loading branch information
nical committed Nov 5, 2024
1 parent 222b3d8 commit 1341e1d
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 49 deletions.
7 changes: 2 additions & 5 deletions src/d3d12/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -440,11 +440,8 @@ impl MemoryType {
) -> Result<Allocation> {
let allocation_type = AllocationType::Linear;

let memblock_size = if self.heap_properties.Type == D3D12_HEAP_TYPE_DEFAULT {
allocation_sizes.device_memblock_size
} else {
allocation_sizes.host_memblock_size
};
let is_host = self.heap_properties.Type != D3D12_HEAP_TYPE_DEFAULT;
let memblock_size = allocation_size.get_memblock_size(is_host, self.active_general_blocks);

let size = desc.size;
let alignment = desc.alignment;
Expand Down
135 changes: 104 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,62 +273,135 @@ impl Default for AllocatorDebugSettings {

/// The sizes of the memory blocks that the allocator will create.
///
/// Useful for tuning the allocator to your application's needs. For example most games will be fine with the default
/// Useful for tuning the allocator to your application's needs. For example most games will be fine with the defaultsize
/// values, but eg. an app might want to use smaller block sizes to reduce the amount of memory used.
///
/// Clamped between 4MB and 256MB, and rounds up to the nearest multiple of 4MB for alignment reasons.
///
/// # Fixed or growable block size
///
/// This structure represents ranges of allowed sizes for shared memory blocks.
/// By default, If the bounds of a given range are equal, the allocator will
/// be configured to used a fixed memory block size for shared allocations.
///
/// Otherwise, the allocator will pick a memory block size within the specifed
/// range, dependending on the number of existing allocations for the memory
/// type.
/// As a rule of thumb, the allocator will start with the minimum block size
/// and double the size with each new allocation, up to the specified maximum
/// block size. This growth is tracked independently for each memory type.
/// The block size also decreases when blocks are deallocated.
///
/// # Example
///
/// ```
/// const MB: u64 = 1024 * 1024;
/// // This configuration uses fixed memory block sizes.
/// let fixed = AllocationSizes::new(256 * MB, 64 * MB);
///
/// // This configuration starts with 8MB memory blocks
/// // and grows the block size of a given memory type each
/// // time a new allocation is needed, up to a limit of
/// // 256MB for device memory and 64MB for host memory.
/// let growing = AllocationSizes::new(8 * MB, 8 * MB)
/// .with_max_device_memblock_size(256 * MB)
/// .with_max_host_memblock_size(64 * MB)
/// ```
#[derive(Clone, Copy, Debug)]
pub struct AllocationSizes {
/// The size of the memory blocks that will be created for the GPU only memory type.
/// The initial size of the memory blocks that will be created for the GPU only memory type.
///
/// Defaults to 256MB.
device_memblock_size: u64,
min_device_memblock_size: u64,
/// The size of device memory blocks doubles each time a new allocation is needed, up to
/// `device_maximum_memblock_size`.
max_device_memblock_size: u64,
/// The size of the memory blocks that will be created for the CPU visible memory types.
///
/// Defaults to 64MB.
host_memblock_size: u64,
min_host_memblock_size: u64,
/// The size of host memory blocks doubles each time a new allocation is needed, up to
/// `host_maximum_memblock_size`.
max_host_memblock_size: u64,
}

impl AllocationSizes {
/// Sets the minimum device and host memory block sizes.
///
/// The maximum block sizes are initialized to the minimum sizes and
/// can be changed using `with_max_device_memblock_size
pub fn new(device_memblock_size: u64, host_memblock_size: u64) -> Self {
const FOUR_MB: u64 = 4 * 1024 * 1024;
const TWO_HUNDRED_AND_FIFTY_SIX_MB: u64 = 256 * 1024 * 1024;

let mut device_memblock_size =
device_memblock_size.clamp(FOUR_MB, TWO_HUNDRED_AND_FIFTY_SIX_MB);
let mut host_memblock_size =
host_memblock_size.clamp(FOUR_MB, TWO_HUNDRED_AND_FIFTY_SIX_MB);
let device_memblock_size = Self::adjust_memblock_size(device_memblock_size, "Device");
let host_memblock_size = Self::adjust_memblock_size(host_memblock_size, "Host");

if device_memblock_size % FOUR_MB != 0 {
let val = device_memblock_size / FOUR_MB + 1;
device_memblock_size = val * FOUR_MB;
log::warn!(
"Device memory block size must be a multiple of 4MB, clamping to {}MB",
device_memblock_size / 1024 / 1024
)
Self {
min_device_memblock_size: device_memblock_size,
max_device_memblock_size: device_memblock_size,
min_host_memblock_size: host_memblock_size,
max_host_memblock_size: host_memblock_size,
}
}

if host_memblock_size % FOUR_MB != 0 {
let val = host_memblock_size / FOUR_MB + 1;
host_memblock_size = val * FOUR_MB;
log::warn!(
"Host memory block size must be a multiple of 4MB, clamping to {}MB",
host_memblock_size / 1024 / 1024
)
}
/// Sets a maximum device memblock size, in bytes.
pub fn with_max_device_memblock_size(mut self, size: u64) -> Self {
self.max_device_memblock_size =
Self::adjust_memblock_size(size, "Device").max(self.min_device_memblock_size);

Self {
device_memblock_size,
host_memblock_size,
self
}

pub fn with_max_host_memblock_size(mut self, size: u64) -> Self {
self.max_host_memblock_size =
Self::adjust_memblock_size(size, "Host").max(self.min_host_memblock_size);

self
}

fn adjust_memblock_size(size: u64, kind: &str) -> u64 {
const MB: u64 = 1024 * 1024;

let size = size.clamp(4 * MB, 256 * MB);

if size % (4 * MB) == 0 {
return size;
}

let val = size / (4 * MB) + 1;
let new_size = val * 4 * MB;
log::warn!(
"{kind} memory block size must be a multiple of 4MB, clamping to {}MB",
new_size / MB
);

new_size
}

/// Used internally to decide the size of a shared memory block
/// based withing the allowed range, based on the number of
/// existing allocations
pub(crate) fn get_memblock_size(&self, is_host: bool, count: usize) -> u64 {
let (min_size, max_size) = if is_host {
(self.min_host_memblock_size, self.max_host_memblock_size)
} else {
(self.min_device_memblock_size, self.max_device_memblock_size)
};

// The ranges are clamped to 4MB..256MB so we never need to
// shift by more than 7 bits. Clamping here to avoid having
// to worry about overflows.
let shift = count.min(7) as u64;
(min_size << shift).min(max_size)
}
}

impl Default for AllocationSizes {
fn default() -> Self {
const MB: u64 = 1024 * 1024;
Self {
device_memblock_size: 256 * 1024 * 1024,
host_memblock_size: 64 * 1024 * 1024,
min_device_memblock_size: 256 * MB,
max_device_memblock_size: 256 * MB,
min_host_memblock_size: 64 * MB,
max_host_memblock_size: 64 * MB,
}
}
}
8 changes: 3 additions & 5 deletions src/metal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,9 @@ impl MemoryType {
) -> Result<Allocation> {
let allocation_type = allocator::AllocationType::Linear;

let memblock_size = if self.heap_properties.storageMode() == MTLStorageMode::Private {
allocation_sizes.device_memblock_size
} else {
allocation_sizes.host_memblock_size
};
let is_host = self.heap_properties.storageMode() != MTLStorageMode::Private;
let memblock_size =
allocation_sizes.get_memblock_sizes(is_host, self.active_general_blocks);

let size = desc.size;
let alignment = desc.alignment;
Expand Down
13 changes: 5 additions & 8 deletions src/vulkan/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,14 +461,11 @@ impl MemoryType {
allocator::AllocationType::NonLinear
};

let memblock_size = if self
let is_host = self
.memory_properties
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE)
{
allocation_sizes.host_memblock_size
} else {
allocation_sizes.device_memblock_size
};
.contains(vk::MemoryPropertyFlags::HOST_VISIBLE);

let memblock_size = allocation_sizes.get_memblock_size(is_host, self.active_general_blocks);

let size = desc.requirements.size;
let alignment = desc.requirements.alignment;
Expand Down Expand Up @@ -760,7 +757,7 @@ impl Allocator {
device: desc.device.clone(),
buffer_image_granularity: granularity,
debug_settings: desc.debug_settings,
allocation_sizes: AllocationSizes::default(),
allocation_sizes: desc.allocation_sizes,
})
}

Expand Down

0 comments on commit 1341e1d

Please sign in to comment.