Vulkan Memory Allocator
|
Interleaved allocations and deallocations of many objects of varying size can cause fragmentation over time, which can lead to a situation where the library is unable to find a continuous range of free memory for a new allocation despite there is enough free space, just scattered across many small free ranges between existing allocations.
To mitigate this problem, you can use defragmentation feature. It doesn't happen automatically though and needs your cooperation, because VMA is a low level library that only allocates memory. It cannot recreate buffers and images in a new place as it doesn't remember the contents of VkBufferCreateInfo
/ VkImageCreateInfo
structures. It cannot copy their contents as it doesn't record any commands to a command buffer.
Example:
Although functions like vmaCreateBuffer(), vmaCreateImage(), vmaDestroyBuffer(), vmaDestroyImage() create/destroy an allocation and a buffer/image at once, these are just a shortcut for creating the resource, allocating memory, and binding them together. Defragmentation works on memory allocations only. You must handle the rest manually. Defragmentation is an iterative process that should repreat "passes" as long as related functions return VK_INCOMPLETE
not VK_SUCCESS
. In each pass:
VkDeviceMemory
+ offset using vmaGetAllocationInfo().VkDeviceMemory
blocks that became empty.Unlike in previous iterations of the defragmentation API, there is no list of "movable" allocations passed as a parameter. Defragmentation algorithm tries to move all suitable allocations. You can, however, refuse to move some of them inside a defragmentation pass, by setting pass.pMoves[i].operation
to VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE. This is not recommended and may result in suboptimal packing of the allocations after defragmentation. If you cannot ensure any allocation can be moved, it is better to keep movable allocations separate in a custom pool.
Inside a pass, for each allocation that should be moved:
vkCmdCopyBuffer()
, vkCmdCopyImage()
.HOST_VISIBLE
and HOST_CACHED
memory, you can copy its data on the CPU using memcpy()
.pass.pMoves[i].operation
to VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE. This will cancel the move.pass.pMoves[i].operation
to VMA_DEFRAGMENTATION_MOVE_OPERATION_DESTROY.You can defragment a specific custom pool by setting VmaDefragmentationInfo::pool (like in the example above) or all the default pools by setting this member to null.
Defragmentation is always performed in each pool separately. Allocations are never moved between different Vulkan memory types. The size of the destination memory reserved for a moved allocation is the same as the original one. Alignment of an allocation as it was determined using vkGetBufferMemoryRequirements()
etc. is also respected after defragmentation. Buffers/images should be recreated with the same VkBufferCreateInfo
/ VkImageCreateInfo
parameters as the original ones.
You can perform the defragmentation incrementally to limit the number of allocations and bytes to be moved in each pass, e.g. to call it in sync with render frames and not to experience too big hitches. See members: VmaDefragmentationInfo::maxBytesPerPass, VmaDefragmentationInfo::maxAllocationsPerPass.
It is also safe to perform the defragmentation asynchronously to render frames and other Vulkan and VMA usage, possibly from multiple threads, with the exception that allocations returned in VmaDefragmentationPassMoveInfo::pMoves shouldn't be destroyed until the defragmentation pass is ended.
Mapping is preserved on allocations that are moved during defragmentation. Whether through VMA_ALLOCATION_CREATE_MAPPED_BIT or vmaMapMemory(), the allocations are mapped at their new place. Of course, pointer to the mapped data changes, so it needs to be queried using VmaAllocationInfo::pMappedData.