Open 3D Engine Atom Gem API Reference 23.10.0
O3DE is an open-source, fully-featured, high-fidelity, modular 3D engine for building games and simulations, available to every industry.
Custom memory pools

A memory pool contains a number of VkDeviceMemory blocks. The library automatically creates and manages default pool for each memory type available on the device. Default memory pool automatically grows in size. Size of allocated blocks is also variable and managed automatically.

You can create custom pool and allocate memory out of it. It can be useful if you want to:

  • Keep certain kind of allocations separate from others.
  • Enforce particular, fixed size of Vulkan memory blocks.
  • Limit maximum amount of Vulkan memory allocated for that pool.
  • Reserve minimum or fixed amount of Vulkan memory always preallocated for that pool.
  • Use extra parameters for a set of your allocations that are available in VmaPoolCreateInfo but not in VmaAllocationCreateInfo - e.g., custom minimum alignment, custom pNext chain.
  • Perform defragmentation on a specific subset of your allocations.

To use custom memory pools:

  1. Fill VmaPoolCreateInfo structure.
  2. Call vmaCreatePool() to obtain VmaPool handle.
  3. When making an allocation, set VmaAllocationCreateInfo::pool to this handle. You don't need to specify any other parameters of this structure, like usage.

Example:

// Find memoryTypeIndex for the pool.
VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
sampleBufCreateInfo.size = 0x10000; // Doesn't matter.
sampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo sampleAllocCreateInfo = {};
sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
uint32_t memTypeIndex;
VkResult res = vmaFindMemoryTypeIndexForBufferInfo(allocator,
&sampleBufCreateInfo, &sampleAllocCreateInfo, &memTypeIndex);
// Check res...
// Create a pool that can have at most 2 blocks, 128 MiB each.
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.memoryTypeIndex = memTypeIndex;
poolCreateInfo.blockSize = 128ull * 1024 * 1024;
poolCreateInfo.maxBlockCount = 2;
VmaPool pool;
res = vmaCreatePool(allocator, &poolCreateInfo, &pool);
// Check res...
// Allocate a buffer out of it.
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.size = 1024;
bufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.pool = pool;
VkBuffer buf;
res = vmaCreateBuffer(allocator, &bufCreateInfo, &allocCreateInfo, &buf, &alloc, nullptr);
// Check res...
VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreatePool(VmaAllocator VMA_NOT_NULL allocator, const VmaPoolCreateInfo *VMA_NOT_NULL pCreateInfo, VmaPool VMA_NULLABLE *VMA_NOT_NULL pPool)
Allocates Vulkan device memory and creates VmaPool object.
VMA_CALL_PRE VkResult VMA_CALL_POST vmaFindMemoryTypeIndexForBufferInfo(VmaAllocator VMA_NOT_NULL allocator, const VkBufferCreateInfo *VMA_NOT_NULL pBufferCreateInfo, const VmaAllocationCreateInfo *VMA_NOT_NULL pAllocationCreateInfo, uint32_t *VMA_NOT_NULL pMemoryTypeIndex)
Helps to find memoryTypeIndex, given VkBufferCreateInfo and VmaAllocationCreateInfo.
VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(VmaAllocator VMA_NOT_NULL allocator, const VkBufferCreateInfo *VMA_NOT_NULL pBufferCreateInfo, const VmaAllocationCreateInfo *VMA_NOT_NULL pAllocationCreateInfo, VkBuffer VMA_NULLABLE_NON_DISPATCHABLE *VMA_NOT_NULL pBuffer, VmaAllocation VMA_NULLABLE *VMA_NOT_NULL pAllocation, VmaAllocationInfo *VMA_NULLABLE pAllocationInfo)
Creates a new VkBuffer, allocates and binds memory for it.
@ VMA_MEMORY_USAGE_AUTO
Definition: vk_mem_alloc.h:489
Definition: vulkan.h:9995
Parameters of new VmaAllocation.
Definition: vk_mem_alloc.h:1219
VmaMemoryUsage usage
Intended usage of memory.
Definition: vk_mem_alloc.h:1227
VmaPool VMA_NULLABLE pool
Pool that this allocation should be created in.
Definition: vk_mem_alloc.h:1251
Represents single memory allocation.
Describes parameter of created VmaPool.
Definition: vk_mem_alloc.h:1270
uint32_t memoryTypeIndex
Vulkan memory type index to allocate this pool from.
Definition: vk_mem_alloc.h:1273
VkDeviceSize blockSize
Size of a single VkDeviceMemory block to be allocated as part of this pool, in bytes....
Definition: vk_mem_alloc.h:1286
size_t maxBlockCount
Maximum number of blocks that can be allocated in this pool. Optional.
Definition: vk_mem_alloc.h:1299
Represents custom memory pool.

You have to free all allocations made from this pool before destroying it.

vmaDestroyBuffer(allocator, buf, alloc);
vmaDestroyPool(allocator, pool);
VMA_CALL_PRE void VMA_CALL_POST vmaDestroyPool(VmaAllocator VMA_NOT_NULL allocator, VmaPool VMA_NULLABLE pool)
Destroys VmaPool object and frees Vulkan device memory.
VMA_CALL_PRE void VMA_CALL_POST vmaDestroyBuffer(VmaAllocator VMA_NOT_NULL allocator, VkBuffer VMA_NULLABLE_NON_DISPATCHABLE buffer, VmaAllocation VMA_NULLABLE allocation)
Destroys Vulkan buffer and frees allocated memory.

New versions of this library support creating dedicated allocations in custom pools. It is supported only when VmaPoolCreateInfo::blockSize = 0. To use this feature, set VmaAllocationCreateInfo::pool to the pointer to your custom pool and VmaAllocationCreateInfo::flags to VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT.

Note
Excessive use of custom pools is a common mistake when using this library. Custom pools may be useful for special purposes - when you want to keep certain type of resources separate e.g. to reserve minimum amount of memory for them or limit maximum amount of memory they can occupy. For most resources this is not needed and so it is not recommended to create VmaPool objects and allocations out of them. Allocating from the default pool is sufficient.

Choosing memory type index

When creating a pool, you must explicitly specify memory type index. To find the one suitable for your buffers or images, you can use helper functions vmaFindMemoryTypeIndexForBufferInfo(), vmaFindMemoryTypeIndexForImageInfo(). You need to provide structures with example parameters of buffers or images that you are going to create in that pool.

VkBufferCreateInfo exampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
exampleBufCreateInfo.size = 1024; // Doesn't matter
exampleBufCreateInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO;
uint32_t memTypeIndex;
vmaFindMemoryTypeIndexForBufferInfo(allocator, &exampleBufCreateInfo, &allocCreateInfo, &memTypeIndex);
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.memoryTypeIndex = memTypeIndex;
// ...

When creating buffers/images allocated in that pool, provide following parameters:

  • VkBufferCreateInfo: Prefer to pass same parameters as above. Otherwise you risk creating resources in a memory type that is not suitable for them, which may result in undefined behavior. Using different VK_BUFFER_USAGE_ flags may work, but you shouldn't create images in a pool intended for buffers or the other way around.
  • VmaAllocationCreateInfo: You don't need to pass same parameters. Fill only pool member. Other members are ignored anyway.

Linear allocation algorithm

Each Vulkan memory block managed by this library has accompanying metadata that keeps track of used and unused regions. By default, the metadata structure and algorithm tries to find best place for new allocations among free regions to optimize memory usage. This way you can allocate and free objects in any order.

Default allocation algorithm

Sometimes there is a need to use simpler, linear allocation algorithm. You can create custom pool that uses such algorithm by adding flag VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT to VmaPoolCreateInfo::flags while creating VmaPool object. Then an alternative metadata management is used. It always creates new allocations after last one and doesn't reuse free regions after allocations freed in the middle. It results in better allocation performance and less memory consumed by metadata.

Linear allocation algorithm

With this one flag, you can create a custom pool that can be used in many ways: free-at-once, stack, double stack, and ring buffer. See below for details. You don't need to specify explicitly which of these options you are going to use - it is detected automatically.

Free-at-once

In a pool that uses linear algorithm, you still need to free all the allocations individually, e.g. by using vmaFreeMemory() or vmaDestroyBuffer(). You can free them in any order. New allocations are always made after last one - free space in the middle is not reused. However, when you release all the allocation and the pool becomes empty, allocation starts from the beginning again. This way you can use linear algorithm to speed up creation of allocations that you are going to release all at once.

Free-at-once

This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount value that allows multiple memory blocks.

Stack

When you free an allocation that was created last, its space can be reused. Thanks to this, if you always release allocations in the order opposite to their creation (LIFO - Last In First Out), you can achieve behavior of a stack.

Stack

This mode is also available for pools created with VmaPoolCreateInfo::maxBlockCount value that allows multiple memory blocks.

Double stack

The space reserved by a custom pool with linear algorithm may be used by two stacks:

  • First, default one, growing up from offset 0.
  • Second, "upper" one, growing down from the end towards lower offsets.

To make allocation from the upper stack, add flag VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT to VmaAllocationCreateInfo::flags.

Double stack

Double stack is available only in pools with one memory block - VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.

When the two stacks' ends meet so there is not enough space between them for a new allocation, such allocation fails with usual VK_ERROR_OUT_OF_DEVICE_MEMORY error.

Ring buffer

When you free some allocations from the beginning and there is not enough free space for a new one at the end of a pool, allocator's "cursor" wraps around to the beginning and starts allocation there. Thanks to this, if you always release allocations in the same order as you created them (FIFO - First In First Out), you can achieve behavior of a ring buffer / queue.

Ring buffer

Ring buffer is available only in pools with one memory block - VmaPoolCreateInfo::maxBlockCount must be 1. Otherwise behavior is undefined.

Note
Defragmentation is not supported in custom pools created with VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT.