Pyrogenesis trunk
Virtual allocator

As an extra feature, the core allocation algorithm of the library is exposed through a simple and convenient API of "virtual allocator".

It doesn't allocate any real GPU memory. It just keeps track of used and free regions of a "virtual block". You can use it to allocate your own memory or other objects, even completely unrelated to Vulkan. A common use case is sub-allocation of pieces of one large GPU buffer.

Creating virtual block

To use this functionality, there is no main "allocator" object. You don't need to have VmaAllocator object created. All you need to do is to create a separate VmaVirtualBlock object for each block of memory you want to be managed by the allocator:

  1. Fill in VmaVirtualBlockCreateInfo structure.
  2. Call vmaCreateVirtualBlock(). Get new VmaVirtualBlock object.

Example:

VmaVirtualBlockCreateInfo blockCreateInfo = {};
blockCreateInfo.size = 1048576; // 1 MB
VkResult res = vmaCreateVirtualBlock(&blockCreateInfo, &block);
VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateVirtualBlock(const VmaVirtualBlockCreateInfo *VMA_NOT_NULL pCreateInfo, VmaVirtualBlock VMA_NULLABLE *VMA_NOT_NULL pVirtualBlock)
Creates new VmaVirtualBlock object.
Parameters of created VmaVirtualBlock object to be passed to vmaCreateVirtualBlock().
Definition: vk_mem_alloc.h:1492
VkDeviceSize size
Total size of the virtual block.
Definition: vk_mem_alloc.h:1498
Handle to a virtual block object that allows to use core allocation algorithm without allocating any ...
VkResult
Definition: vulkan.h:1020

Making virtual allocations

VmaVirtualBlock object contains internal data structure that keeps track of free and occupied regions using the same code as the main Vulkan memory allocator. Similarly to VmaAllocation for standard GPU allocations, there is VmaVirtualAllocation type that represents an opaque handle to an allocation withing the virtual block.

In order to make such allocation:

  1. Fill in VmaVirtualAllocationCreateInfo structure.
  2. Call vmaVirtualAllocate(). Get new VmaVirtualAllocation object that represents the allocation. You can also receive VkDeviceSize offset that was assigned to the allocation.

Example:

VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = 4096; // 4 KB
VkDeviceSize offset;
res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, &offset);
if(res == VK_SUCCESS)
{
// Use the 4 KB of your memory starting at offset.
}
else
{
// Allocation failed - no space for it could be found. Handle this error!
}
VMA_CALL_PRE VkResult VMA_CALL_POST vmaVirtualAllocate(VmaVirtualBlock VMA_NOT_NULL virtualBlock, const VmaVirtualAllocationCreateInfo *VMA_NOT_NULL pCreateInfo, VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE *VMA_NOT_NULL pAllocation, VkDeviceSize *VMA_NULLABLE pOffset)
Allocates new virtual allocation inside given VmaVirtualBlock.
Parameters of created virtual allocation to be passed to vmaVirtualAllocate().
Definition: vk_mem_alloc.h:1513
VkDeviceSize size
Size of the allocation.
Definition: vk_mem_alloc.h:1518
Represents single memory allocation done inside VmaVirtualBlock.
uint64_t VkDeviceSize
Definition: vulkan.h:2417
@ VK_SUCCESS
Definition: vulkan.h:1021

Deallocation

When no longer needed, an allocation can be freed by calling vmaVirtualFree(). You can only pass to this function an allocation that was previously returned by vmaVirtualAllocate() called for the same VmaVirtualBlock.

When whole block is no longer needed, the block object can be released by calling vmaDestroyVirtualBlock(). All allocations must be freed before the block is destroyed, which is checked internally by an assert. However, if you don't want to call vmaVirtualFree() for each allocation, you can use vmaClearVirtualBlock() to free them all at once - a feature not available in normal Vulkan memory allocator. Example:

vmaVirtualFree(block, alloc);
VMA_CALL_PRE void VMA_CALL_POST vmaDestroyVirtualBlock(VmaVirtualBlock VMA_NULLABLE virtualBlock)
Destroys VmaVirtualBlock object.
VMA_CALL_PRE void VMA_CALL_POST vmaVirtualFree(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaVirtualAllocation VMA_NULLABLE_NON_DISPATCHABLE allocation)
Frees virtual allocation inside given VmaVirtualBlock.

Allocation parameters

You can attach a custom pointer to each allocation by using vmaSetVirtualAllocationUserData(). Its default value is null. It can be used to store any data that needs to be associated with that allocation - e.g. an index, a handle, or a pointer to some larger data structure containing more information. Example:

struct CustomAllocData
{
std::string m_AllocName;
};
CustomAllocData* allocData = new CustomAllocData();
allocData->m_AllocName = "My allocation 1";
vmaSetVirtualAllocationUserData(block, alloc, allocData);
VMA_CALL_PRE void VMA_CALL_POST vmaSetVirtualAllocationUserData(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, void *VMA_NULLABLE pUserData)
Changes custom pointer associated with given virtual allocation.

The pointer can later be fetched, along with allocation offset and size, by passing the allocation handle to function vmaGetVirtualAllocationInfo() and inspecting returned structure VmaVirtualAllocationInfo. If you allocated a new object to be used as the custom pointer, don't forget to delete that object before freeing the allocation! Example:

vmaGetVirtualAllocationInfo(block, alloc, &allocInfo);
delete (CustomAllocData*)allocInfo.pUserData;
vmaVirtualFree(block, alloc);
VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualAllocationInfo(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaVirtualAllocation VMA_NOT_NULL_NON_DISPATCHABLE allocation, VmaVirtualAllocationInfo *VMA_NOT_NULL pVirtualAllocInfo)
Returns information about a specific virtual allocation within a virtual block, like its size and pUs...
Parameters of an existing virtual allocation, returned by vmaGetVirtualAllocationInfo().
Definition: vk_mem_alloc.h:1536
void *VMA_NULLABLE pUserData
Custom pointer associated with the allocation.
Definition: vk_mem_alloc.h:1551

Alignment and units

It feels natural to express sizes and offsets in bytes. If an offset of an allocation needs to be aligned to a multiply of some number (e.g. 4 bytes), you can fill optional member VmaVirtualAllocationCreateInfo::alignment to request it. Example:

VmaVirtualAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.size = 4096; // 4 KB
allocCreateInfo.alignment = 4; // Returned offset must be a multiply of 4 B
res = vmaVirtualAllocate(block, &allocCreateInfo, &alloc, nullptr);
VkDeviceSize alignment
Required alignment of the allocation.
Definition: vk_mem_alloc.h:1523

Alignments of different allocations made from one block may vary. However, if all alignments and sizes are always multiply of some size e.g. 4 B or sizeof(MyDataStruct), you can express all sizes, alignments, and offsets in multiples of that size instead of individual bytes. It might be more convenient, but you need to make sure to use this new unit consistently in all the places:

Statistics

You can obtain statistics of a virtual block using vmaGetVirtualBlockStatistics() (to get brief statistics that are fast to calculate) or vmaCalculateVirtualBlockStatistics() (to get more detailed statistics, slower to calculate). The functions fill structures VmaStatistics, VmaDetailedStatistics respectively - same as used by the normal Vulkan memory allocator. Example:

printf("My virtual block has %llu bytes used by %u virtual allocations\n",
VMA_CALL_PRE void VMA_CALL_POST vmaGetVirtualBlockStatistics(VmaVirtualBlock VMA_NOT_NULL virtualBlock, VmaStatistics *VMA_NOT_NULL pStats)
Calculates and returns statistics about virtual allocations and memory usage in given VmaVirtualBlock...
Calculated statistics of memory usage e.g.
Definition: vk_mem_alloc.h:1111
VkDeviceSize allocationBytes
Total number of bytes occupied by all VmaAllocation objects.
Definition: vk_mem_alloc.h:1133
uint32_t allocationCount
Number of VmaAllocation objects allocated.
Definition: vk_mem_alloc.h:1119

You can also request a full list of allocations and free regions as a string in JSON format by calling vmaBuildVirtualBlockStatsString(). Returned string must be later freed using vmaFreeVirtualBlockStatsString(). The format of this string differs from the one returned by the main Vulkan allocator, but it is similar.

Additional considerations

The "virtual allocator" functionality is implemented on a level of individual memory blocks. Keeping track of a whole collection of blocks, allocating new ones when out of free space, deleting empty ones, and deciding which one to try first for a new allocation must be implemented by the user.

Alternative allocation algorithms are supported, just like in custom pools of the real GPU memory. See enum VmaVirtualBlockCreateFlagBits to learn how to specify them (e.g. VMA_VIRTUAL_BLOCK_CREATE_LINEAR_ALGORITHM_BIT). You can find their description in chapter Custom memory pools. Allocation strategies are also supported. See enum VmaVirtualAllocationCreateFlagBits to learn how to specify them (e.g. VMA_VIRTUAL_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT).

Following features are supported only by the allocator of the real GPU memory and not by virtual allocations: buffer-image granularity, VMA_DEBUG_MARGIN, VMA_MIN_ALIGNMENT.