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 D3D12MA is a low level library that only allocates memory. It cannot recreate buffers and textures in a new place as it doesn't remember the contents of D3D12_RESOURCE_DESC
structure. It cannot copy their contents as it doesn't record any commands to a command list.
Example:
defragDesc.
Flags = D3D12MA::DEFRAGMENTATION_FLAG_ALGORITHM_FAST;
allocator->BeginDefragmentation(&defragDesc, &defragCtx);
for(;;)
{
if(hr == S_OK)
break;
else if(hr != S_FALSE)
{
MyEngineResourceData* resData = (MyEngineResourceData*)pMoves[i].pSrcAllocation->GetPrivateData();
D3D12_RESOURCE_DESC resDesc = ...
ID3D12Resource* newRes;
hr = device->CreatePlacedResource(
D3D12_RESOURCE_STATE_COPY_DEST, NULL, IID_PPV_ARGS(&newRes));
cmdList->CopyResource(
}
cmdQueue->ExecuteCommandLists(...);
WaitForSingleObject(fenceEvent, INFINITE);
if(hr == S_OK)
break;
else if(hr != S_FALSE)
}
defragCtx->Release();
void SetResource(ID3D12Resource *pResource)
Releases the resource currently pointed by the allocation (if any), sets it to new one,...
UINT64 GetOffset() const
Returns offset in bytes from the start of memory heap.
ID3D12Resource * GetResource() const
Returns D3D12 resource associated with this object.
Definition: D3D12MemAlloc.h:491
ID3D12Heap * GetHeap() const
Returns memory heap that the resource is created in.
Represents defragmentation process in progress.
Definition: D3D12MemAlloc.h:770
HRESULT BeginPass(DEFRAGMENTATION_PASS_MOVE_INFO *pPassInfo)
Starts single defragmentation pass.
HRESULT EndPass(DEFRAGMENTATION_PASS_MOVE_INFO *pPassInfo)
Ends single defragmentation pass.
Parameters for defragmentation.
Definition: D3D12MemAlloc.h:672
DEFRAGMENTATION_FLAGS Flags
Flags.
Definition: D3D12MemAlloc.h:674
Allocation * pSrcAllocation
Allocation that should be moved.
Definition: D3D12MemAlloc.h:708
Allocation * pDstTmpAllocation
Temporary allocation pointing to destination memory that will replace pSrcAllocation.
Definition: D3D12MemAlloc.h:716
Parameters for incremental defragmentation steps.
Definition: D3D12MemAlloc.h:724
DEFRAGMENTATION_MOVE * pMoves
Array of moves to be performed by the user in the current defragmentation pass.
Definition: D3D12MemAlloc.h:748
UINT32 MoveCount
Number of elements in the pMoves array.
Definition: D3D12MemAlloc.h:726
Although functions like D3D12MA::Allocator::CreateResource() create an allocation and a buffer/texture at once, these are just a shortcut for allocating memory and creating a placed resource. 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 S_FALSE
not S_OK
. In each pass:
- D3D12MA::DefragmentationContext::BeginPass() function call:
- Calculates and returns the list of allocations to be moved in this pass. Note this can be a time-consuming process.
- Reserves destination memory for them by creating temporary destination allocations that you can query for their
ID3D12Heap
+ offset using methods like D3D12MA::Allocation::GetHeap().
- Inside the pass, you should:
- Inspect the returned list of allocations to be moved.
- Create new buffers/textures as placed at the returned destination temporary allocations.
- Copy data from source to destination resources if necessary.
- Store the pointer to the new resource in the temporary destination allocation.
- D3D12MA::DefragmentationContext::EndPass() function call:
- Frees the source memory reserved for the allocations that are moved.
- Modifies source D3D12MA::Allocation objects that are moved to point to the destination reserved memory and destination resource, while source resource is released.
- Frees
ID3D12Heap
blocks that became empty.
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 D3D12MA::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:
- You should copy its data from the source to the destination place by calling e.g.
CopyResource()
.
- If a resource doesn't contain any meaningful data, e.g. it is a transient render-target texture to be cleared, filled, and used temporarily in each rendering frame, you can just recreate this texture without copying its data.
- If the resource is in
D3D12_HEAP_TYPE_READBACK
memory, you can copy its data on the CPU using memcpy()
.
- If you cannot move the allocation, you can set
pass.pMoves[i].Operation
to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_IGNORE. This will cancel the move.
- If you decide the allocation is unimportant and can be destroyed instead of moved (e.g. it wasn't used for long time), you can set
pass.pMoves[i].Operation
to D3D12MA::DEFRAGMENTATION_MOVE_OPERATION_DESTROY.
You can defragment a specific custom pool by calling D3D12MA::Pool::BeginDefragmentation or all the default pools by calling D3D12MA::Allocator::BeginDefragmentation (like in the example above).
Defragmentation is always performed in each pool separately. Allocations are never moved between different heap 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 GetResourceAllocationInfo()
is also respected after defragmentation. Buffers/textures should be recreated with the same D3D12_RESOURCE_DESC
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: D3D12MA::DEFRAGMENTATION_DESC::MaxBytesPerPass, D3D12MA::DEFRAGMENTATION_DESC::MaxAllocationsPerPass.
It is also safe to perform the defragmentation asynchronously to render frames and other Direct3D 12 and D3D12MA usage, possibly from multiple threads, with the exception that allocations returned in D3D12MA::DEFRAGMENTATION_PASS_MOVE_INFO::pMoves shouldn't be released until the defragmentation pass is ended.
Mapping is out of scope of this library and so it is not preserved after an allocation is moved during defragmentation. You need to map the new resource yourself if needed.
- Note
- Defragmentation is not supported in custom pools created with D3D12MA::POOL_FLAG_ALGORITHM_LINEAR.
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:
defragInfo.
pool = myPool;
defragInfo.
flags = VMA_DEFRAGMENTATION_FLAG_ALGORITHM_FAST_BIT;
for(;;)
{
if(res == VK_SUCCESS)
break;
else if(res != VK_INCOMPLETE)
{
MyEngineResourceData* resData = (MyEngineResourceData*)allocInfo.
pUserData;
VkImage newImg;
res = vkCreateImage(device, &imgCreateInfo, nullptr, &newImg);
vkCmdCopyImage(cmdBuf, resData->img, ..., newImg, ...);
}
vkWaitForFences(...);
{
vkDestroyImage(device, resData->img, nullptr);
}
if(res == VK_SUCCESS)
break;
else if(res != VK_INCOMPLETE)
}
VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentation(VmaAllocator VMA_NOT_NULL allocator, const VmaDefragmentationInfo *VMA_NOT_NULL pInfo, VmaDefragmentationContext VMA_NULLABLE *VMA_NOT_NULL pContext)
Begins defragmentation process.
VMA_CALL_PRE VkResult VMA_CALL_POST vmaBindImageMemory(VmaAllocator VMA_NOT_NULL allocator, VmaAllocation VMA_NOT_NULL allocation, VkImage VMA_NOT_NULL_NON_DISPATCHABLE image)
Binds image to allocation.
VMA_CALL_PRE void VMA_CALL_POST vmaGetAllocationInfo(VmaAllocator VMA_NOT_NULL allocator, VmaAllocation VMA_NOT_NULL allocation, VmaAllocationInfo *VMA_NOT_NULL pAllocationInfo)
Returns current information about specified allocation.
VMA_CALL_PRE VkResult VMA_CALL_POST vmaEndDefragmentationPass(VmaAllocator VMA_NOT_NULL allocator, VmaDefragmentationContext VMA_NOT_NULL context, VmaDefragmentationPassMoveInfo *VMA_NOT_NULL pPassInfo)
Ends single defragmentation pass.
VMA_CALL_PRE VkResult VMA_CALL_POST vmaBeginDefragmentationPass(VmaAllocator VMA_NOT_NULL allocator, VmaDefragmentationContext VMA_NOT_NULL context, VmaDefragmentationPassMoveInfo *VMA_NOT_NULL pPassInfo)
Starts single defragmentation pass.
VMA_CALL_PRE void VMA_CALL_POST vmaEndDefragmentation(VmaAllocator VMA_NOT_NULL allocator, VmaDefragmentationContext VMA_NOT_NULL context, VmaDefragmentationStats *VMA_NULLABLE pStats)
Ends defragmentation process.
Definition: vulkan.h:10069
Parameters of VmaAllocation objects, that can be retrieved using function vmaGetAllocationInfo().
Definition: vk_mem_alloc.h:1334
void *VMA_NULLABLE pUserData
Custom general-purpose pointer that was passed as VmaAllocationCreateInfo::pUserData or set using vma...
Definition: vk_mem_alloc.h:1381
An opaque object that represents started defragmentation process.
Parameters for defragmentation.
Definition: vk_mem_alloc.h:1397
VmaPool VMA_NULLABLE pool
Custom pool to be defragmented.
Definition: vk_mem_alloc.h:1404
VmaDefragmentationFlags flags
Use combination of VmaDefragmentationFlagBits.
Definition: vk_mem_alloc.h:1399
Parameters for incremental defragmentation steps.
Definition: vk_mem_alloc.h:1438
uint32_t moveCount
Number of elements in the pMoves array.
Definition: vk_mem_alloc.h:1440
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:
- vmaBeginDefragmentationPass() function call:
- Calculates and returns the list of allocations to be moved in this pass. Note this can be a time-consuming process.
- Reserves destination memory for them by creating temporary destination allocations that you can query for their
VkDeviceMemory
+ offset using vmaGetAllocationInfo().
- Inside the pass, you should:
- Inspect the returned list of allocations to be moved.
- Create new buffers/images and bind them at the returned destination temporary allocations.
- Copy data from source to destination resources if necessary.
- Destroy the source buffers/images, but NOT their allocations.
- vmaEndDefragmentationPass() function call:
- Frees the source memory reserved for the allocations that are moved.
- Modifies source VmaAllocation objects that are moved to point to the destination reserved memory.
- Frees
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:
- You should copy its data from the source to the destination place by calling e.g.
vkCmdCopyBuffer()
, vkCmdCopyImage()
.
- You need to make sure these commands finished executing before destroying the source buffers/images and before calling vmaEndDefragmentationPass().
- If a resource doesn't contain any meaningful data, e.g. it is a transient color attachment image to be cleared, filled, and used temporarily in each rendering frame, you can just recreate this image without copying its data.
- If the resource is in
HOST_VISIBLE
and HOST_CACHED
memory, you can copy its data on the CPU using memcpy()
.
- If you cannot move the allocation, you can set
pass.pMoves[i].operation
to VMA_DEFRAGMENTATION_MOVE_OPERATION_IGNORE. This will cancel the move.
- If you decide the allocation is unimportant and can be destroyed instead of moved (e.g. it wasn't used for long time), you can set
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.
- Note
- Defragmentation is not supported in custom pools created with VMA_POOL_CREATE_LINEAR_ALGORITHM_BIT.