diff --git a/src/Tests.cpp b/src/Tests.cpp index 7586f61..5a6057f 100644 --- a/src/Tests.cpp +++ b/src/Tests.cpp @@ -715,14 +715,17 @@ void AllocInfo::Destroy() if(m_Image) { vkDestroyImage(g_hDevice, m_Image, g_Allocs); + m_Image = VK_NULL_HANDLE; } if(m_Buffer) { vkDestroyBuffer(g_hDevice, m_Buffer, g_Allocs); + m_Buffer = VK_NULL_HANDLE; } if(m_Allocation) { vmaFreeMemory(g_hAllocator, m_Allocation); + m_Allocation = VK_NULL_HANDLE; } } @@ -1986,6 +1989,80 @@ static void TestBasics() 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 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() { const VkDeviceSize HEAP_SIZE_LIMIT = 200ull * 1024 * 1024; // 200 MB @@ -5392,6 +5469,7 @@ void Test() TestDebugMargin(); #else TestPool_SameSize(); + TestPool_MinBlockCount(); TestHeapSizeLimit(); #endif #if VMA_DEBUG_INITIALIZE_ALLOCATIONS diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h index 36ad197..059bbb9 100644 --- a/src/vk_mem_alloc.h +++ b/src/vk_mem_alloc.h @@ -6296,11 +6296,11 @@ private: const uint32_t m_FrameInUseCount; const bool m_ExplicitBlockSize; 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; + + /* 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. VmaVector< VmaDeviceMemoryBlock*, VmaStlAllocator > m_Blocks; uint32_t m_NextBlockId; @@ -6351,6 +6351,8 @@ private: - updated with new data. */ void FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats); + + void UpdateHasEmptyBlock(); }; struct VmaPool_T @@ -12174,15 +12176,11 @@ VkResult VmaBlockVector::AllocatePage( m_FrameInUseCount, &bestRequest)) { - // We no longer have an empty Allocation. - if(pBestRequestBlock->m_pMetadata->IsEmpty()) - { - m_HasEmptyBlock = false; - } // Allocate from this pBlock. *pAllocation = m_hAllocator->m_AllocationObjectAllocator.Allocate(); (*pAllocation)->Ctor(currentFrameIndex, isUserDataString); pBestRequestBlock->m_pMetadata->Alloc(bestRequest, suballocType, size, *pAllocation); + UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBestRequestBlock, bestRequest.offset, @@ -12272,11 +12270,7 @@ void VmaBlockVector::Free( pBlockToDelete = pBlock; Remove(pBlock); } - // We now have first empty block. - else - { - m_HasEmptyBlock = true; - } + // else: We now have an empty block - leave it. } // pBlock didn't become empty, but we have another empty block - find and free that one. // (This is optional, heuristics.) @@ -12287,10 +12281,10 @@ void VmaBlockVector::Free( { pBlockToDelete = pLastBlock; m_Blocks.pop_back(); - m_HasEmptyBlock = false; } } + UpdateHasEmptyBlock(); 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)->Ctor(currentFrameIndex, isUserDataString); pBlock->m_pMetadata->Alloc(currRequest, suballocType, size, *pAllocation); + UpdateHasEmptyBlock(); (*pAllocation)->InitBlockAllocation( pBlock, currRequest.offset, @@ -12650,7 +12639,6 @@ void VmaBlockVector::ApplyDefragmentationMovesGpu( void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationStats) { - m_HasEmptyBlock = false; for(size_t blockIndex = m_Blocks.size(); blockIndex--; ) { VmaDeviceMemoryBlock* pBlock = m_Blocks[blockIndex]; @@ -12668,10 +12656,21 @@ void VmaBlockVector::FreeEmptyBlocks(VmaDefragmentationStats* pDefragmentationSt pBlock->Destroy(m_hAllocator); 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; } } }