Fix management of m_HasEmptyBlock by adding VmaBlockVector::UpdateHasEmptyBlock().

Also added TestPool_MinBlockCount().
This commit is contained in:
Adam Sawicki 2019-11-22 15:22:42 +01:00
parent 69185555f4
commit ddcbf8cdba
2 changed files with 103 additions and 26 deletions

View File

@ -715,14 +715,17 @@ void AllocInfo::Destroy()
if(m_Image) if(m_Image)
{ {
vkDestroyImage(g_hDevice, m_Image, g_Allocs); vkDestroyImage(g_hDevice, m_Image, g_Allocs);
m_Image = VK_NULL_HANDLE;
} }
if(m_Buffer) if(m_Buffer)
{ {
vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs); vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs);
m_Buffer = VK_NULL_HANDLE;
} }
if(m_Allocation) if(m_Allocation)
{ {
vmaFreeMemory(g_hAllocator, m_Allocation); vmaFreeMemory(g_hAllocator, m_Allocation);
m_Allocation = VK_NULL_HANDLE;
} }
} }
@ -1986,6 +1989,80 @@ static void TestBasics()
TestInvalidAllocations(); TestInvalidAllocations();
} }
static void TestPool_MinBlockCount()
{
#if defined(VMA_DEBUG_MARGIN) && VMA_DEBUG_MARGIN > 0
return;
#endif
wprintf(L"Test Pool MinBlockCount\n");
VkResult res;
static const VkDeviceSize ALLOC_SIZE = 512ull * 1024;
static const VkDeviceSize BLOCK_SIZE = ALLOC_SIZE * 2; // Each block can fit 2 allocations.
VmaAllocationCreateInfo allocCreateInfo = {};
allocCreateInfo.usage = VMA_MEMORY_USAGE_CPU_COPY;
VkBufferCreateInfo bufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
bufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
bufCreateInfo.size = ALLOC_SIZE;
VmaPoolCreateInfo poolCreateInfo = {};
poolCreateInfo.blockSize = BLOCK_SIZE;
poolCreateInfo.minBlockCount = 2; // At least 2 blocks always present.
res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &poolCreateInfo.memoryTypeIndex);
TEST(res == VK_SUCCESS);
VmaPool pool = VK_NULL_HANDLE;
res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
TEST(res == VK_SUCCESS && pool != VK_NULL_HANDLE);
// Check that there are 2 blocks preallocated as requested.
VmaPoolStats begPoolStats = {};
vmaGetPoolStats(g_hAllocator, pool, &begPoolStats);
TEST(begPoolStats.blockCount == 2 && begPoolStats.allocationCount == 0 && begPoolStats.size == BLOCK_SIZE * 2);
// Allocate 5 buffers to create 3 blocks.
static const uint32_t BUF_COUNT = 5;
allocCreateInfo.pool = pool;
std::vector<AllocInfo> allocs(BUF_COUNT);
for(uint32_t i = 0; i < BUF_COUNT; ++i)
{
res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo, &allocs[i].m_Buffer, &allocs[i].m_Allocation, nullptr);
TEST(res == VK_SUCCESS && allocs[i].m_Buffer != VK_NULL_HANDLE && allocs[i].m_Allocation != VK_NULL_HANDLE);
}
// Check that there are really 3 blocks.
VmaPoolStats poolStats2 = {};
vmaGetPoolStats(g_hAllocator, pool, &poolStats2);
TEST(poolStats2.blockCount == 3 && poolStats2.allocationCount == BUF_COUNT && poolStats2.size == BLOCK_SIZE * 3);
// Free two first allocations to make one block empty.
allocs[0].Destroy();
allocs[1].Destroy();
// Check that there are still 3 blocks due to hysteresis.
VmaPoolStats poolStats3 = {};
vmaGetPoolStats(g_hAllocator, pool, &poolStats3);
TEST(poolStats3.blockCount == 3 && poolStats3.allocationCount == BUF_COUNT - 2 && poolStats2.size == BLOCK_SIZE * 3);
// Free the last allocation to make second block empty.
allocs[BUF_COUNT - 1].Destroy();
// Check that there are now 2 blocks only.
VmaPoolStats poolStats4 = {};
vmaGetPoolStats(g_hAllocator, pool, &poolStats4);
TEST(poolStats4.blockCount == 2 && poolStats4.allocationCount == BUF_COUNT - 3 && poolStats4.size == BLOCK_SIZE * 2);
// Cleanup.
for(size_t i = allocs.size(); i--; )
{
allocs[i].Destroy();
}
vmaDestroyPool(g_hAllocator, pool);
}
void TestHeapSizeLimit() void TestHeapSizeLimit()
{ {
const VkDeviceSize HEAP_SIZE_LIMIT = 200ull * 1024 * 1024; // 200 MB const VkDeviceSize HEAP_SIZE_LIMIT = 200ull * 1024 * 1024; // 200 MB
@ -5392,6 +5469,7 @@ void Test()
TestDebugMargin(); TestDebugMargin();
#else #else
TestPool_SameSize(); TestPool_SameSize();
TestPool_MinBlockCount();
TestHeapSizeLimit(); TestHeapSizeLimit();
#endif #endif
#if VMA_DEBUG_INITIALIZE_ALLOCATIONS #if VMA_DEBUG_INITIALIZE_ALLOCATIONS

View File

@ -6296,11 +6296,11 @@ private:
const uint32_t m_FrameInUseCount; const uint32_t m_FrameInUseCount;
const bool m_ExplicitBlockSize; const bool m_ExplicitBlockSize;
const uint32_t m_Algorithm; const uint32_t m_Algorithm;
/* There can be at most one allocation that is completely empty - a
hysteresis to avoid pessimistic case of alternating creation and destruction
of a VkDeviceMemory. */
bool m_HasEmptyBlock;
VMA_RW_MUTEX m_Mutex; VMA_RW_MUTEX m_Mutex;
/* There can be at most one allocation that is completely empty (except when minBlockCount > 0) -
a hysteresis to avoid pessimistic case of alternating creation and destruction of a VkDeviceMemory. */
bool m_HasEmptyBlock;
// Incrementally sorted by sumFreeSize, ascending. // Incrementally sorted by sumFreeSize, ascending.
VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator<VmaDeviceMemoryBlock*> > m_Blocks; VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator<VmaDeviceMemoryBlock*> > m_Blocks;
uint32_t m_NextBlockId; uint32_t m_NextBlockId;
@ -6351,6 +6351,8 @@ private:
- updated with new data. - updated with new data.
*/ */
void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats); void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats);
void UpdateHasEmptyBlock();
}; };
struct VmaPool_T struct VmaPool_T
@ -12174,15 +12176,11 @@ VkResult VmaBlockVector::AllocatePage(
m_FrameInUseCount, m_FrameInUseCount,
&bestRequest)) &bestRequest))
{ {
// We no longer have an empty Allocation.
if(pBestRequestBlock->m_pMetadata->IsEmpty())
{
m_HasEmptyBlock = false;
}
// Allocate from this pBlock. // Allocate from this pBlock.
*pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate();
(*pAllocation)->Ctor(currentFrameIndex, isUserDataString); (*pAllocation)->Ctor(currentFrameIndex, isUserDataString);
pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation); pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation);
UpdateHasEmptyBlock();
(*pAllocation)->InitBlockAllocation( (*pAllocation)->InitBlockAllocation(
pBestRequestBlock, pBestRequestBlock,
bestRequest.offset, bestRequest.offset,
@ -12272,11 +12270,7 @@ void VmaBlockVector::Free(
pBlockToDelete = pBlock; pBlockToDelete = pBlock;
Remove(pBlock); Remove(pBlock);
} }
// We now have first empty block. // else: We now have an empty block - leave it.
else
{
m_HasEmptyBlock = true;
}
} }
// pBlock didn't become empty, but we have another empty block - find and free that one. // pBlock didn't become empty, but we have another empty block - find and free that one.
// (This is optional, heuristics.) // (This is optional, heuristics.)
@ -12287,10 +12281,10 @@ void VmaBlockVector::Free(
{ {
pBlockToDelete = pLastBlock; pBlockToDelete = pLastBlock;
m_Blocks.pop_back(); m_Blocks.pop_back();
m_HasEmptyBlock = false;
} }
} }
UpdateHasEmptyBlock();
IncrementallySortBlocks(); IncrementallySortBlocks();
} }
@ -12388,15 +12382,10 @@ VkResult VmaBlockVector::AllocateFromBlock(
} }
} }
// We no longer have an empty Allocation.
if(pBlock->m_pMetadata->IsEmpty())
{
m_HasEmptyBlock = false;
}
*pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate();
(*pAllocation)->Ctor(currentFrameIndex, isUserDataString); (*pAllocation)->Ctor(currentFrameIndex, isUserDataString);
pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation); pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation);
UpdateHasEmptyBlock();
(*pAllocation)->InitBlockAllocation( (*pAllocation)->InitBlockAllocation(
pBlock, pBlock,
currRequest.offset, currRequest.offset,
@ -12650,7 +12639,6 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu(
void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats) void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats)
{ {
m_HasEmptyBlock = false;
for(size_t blockIndex = m_Blocks.size(); blockIndex--; ) for(size_t blockIndex = m_Blocks.size(); blockIndex--; )
{ {
VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex];
@ -12668,10 +12656,21 @@ void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationSt
pBlock->Destroy(m_hAllocator); pBlock->Destroy(m_hAllocator);
vma_delete(m_hAllocator, pBlock); vma_delete(m_hAllocator, pBlock);
} }
else }
{ }
m_HasEmptyBlock = true; UpdateHasEmptyBlock();
} }
void VmaBlockVector::UpdateHasEmptyBlock()
{
m_HasEmptyBlock = false;
for(size_t index = 0, count = m_Blocks.size(); index < count; ++index)
{
VmaDeviceMemoryBlock* const pBlock = m_Blocks[index];
if(pBlock->m_pMetadata->IsEmpty())
{
m_HasEmptyBlock = true;
break;
} }
} }
} }