Skip to content

Commit

Permalink
Add support for growing the memory block sizes (#254)
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 authored Dec 3, 2024
1 parent 222b3d8 commit e6dbe11
Show file tree
Hide file tree
Showing 4 changed files with 116 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_sizes.get_memblock_size(is_host, self.active_general_blocks);

let size = desc.size;
let alignment = desc.alignment;
Expand Down
138 changes: 107 additions & 31 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -273,62 +273,138 @@ 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
///
/// ```
/// use gpu_allocator::AllocationSizes;
/// 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` and
/// `with_max_host_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 the 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
}

/// Sets the maximum host memblock size, in bytes.
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,
}
}
}
7 changes: 2 additions & 5 deletions src/metal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,11 +248,8 @@ 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_size(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 e6dbe11

Please sign in to comment.