Vulkan Memory 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.
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:
Example:
VmaVirtualBlock object contains internal data structure that keeps track of free and occupied regions using the same code as the main Vulkan memory allocator. However, there is no "virtual allocation" object. When you request a new allocation, a VkDeviceSize
number is returned. It is an offset inside the block where the allocation has been placed, but it also uniquely identifies the allocation within this block.
In order to make an allocation:
VkDeviceSize offset
that identifies the allocation.Example:
When no longer needed, an allocation can be freed by calling vmaVirtualFree(). You can only pass to this function the exact offset that was previously returned by vmaVirtualAllocate() and not any other location within the memory.
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:
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:
The pointer can later be fetched, along with allocation size, by passing the allocation offset 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:
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:
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:
You can obtain statistics of a virtual block using vmaCalculateVirtualBlockStats(). The function fills structure VmaStatInfo - same as used by the normal Vulkan memory allocator. Example:
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.
Note that 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.