Buddy allocator - more coding.
diff --git a/src/Tests.cpp b/src/Tests.cpp
index c5cf05b..21b82f2 100644
--- a/src/Tests.cpp
+++ b/src/Tests.cpp
@@ -4079,15 +4079,82 @@
}
}
+static void BasicTestBuddyAllocator()
+{
+ wprintf(L"Basic test buddy allocator\n");
+
+ RandomNumberGenerator rand{76543};
+
+ VkBufferCreateInfo sampleBufCreateInfo = { VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO };
+ sampleBufCreateInfo.size = 1024; // Whatever.
+ sampleBufCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT;
+
+ VmaAllocationCreateInfo sampleAllocCreateInfo = {};
+ sampleAllocCreateInfo.usage = VMA_MEMORY_USAGE_GPU_ONLY;
+
+ VmaPoolCreateInfo poolCreateInfo = {};
+ VkResult res = vmaFindMemoryTypeIndexForBufferInfo(g_hAllocator, &sampleBufCreateInfo, &sampleAllocCreateInfo, &poolCreateInfo.memoryTypeIndex);
+ assert(res == VK_SUCCESS);
+
+ poolCreateInfo.blockSize = 1024 * 1024;
+ poolCreateInfo.flags = VMA_POOL_CREATE_BUDDY_ALGORITHM_BIT;
+ poolCreateInfo.minBlockCount = poolCreateInfo.maxBlockCount = 1;
+
+ VmaPool pool = nullptr;
+ res = vmaCreatePool(g_hAllocator, &poolCreateInfo, &pool);
+ assert(res == VK_SUCCESS);
+
+ VkBufferCreateInfo bufCreateInfo = sampleBufCreateInfo;
+
+ VmaAllocationCreateInfo allocCreateInfo = {};
+ allocCreateInfo.pool = pool;
+
+ std::vector<BufferInfo> bufInfo;
+ BufferInfo newBufInfo;
+ VmaAllocationInfo allocInfo;
+
+ bufCreateInfo.size = 1024 * 256;
+ res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
+ &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
+ assert(res == VK_SUCCESS);
+ bufInfo.push_back(newBufInfo);
+
+ bufCreateInfo.size = 1024 * 512;
+ res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
+ &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
+ assert(res == VK_SUCCESS);
+ bufInfo.push_back(newBufInfo);
+
+ bufCreateInfo.size = 1024 * 128;
+ res = vmaCreateBuffer(g_hAllocator, &bufCreateInfo, &allocCreateInfo,
+ &newBufInfo.Buffer, &newBufInfo.Allocation, &allocInfo);
+ assert(res == VK_SUCCESS);
+ bufInfo.push_back(newBufInfo);
+
+ SaveAllocatorStatsToFile(L"BuddyTest01.json");
+
+ // Destroy the buffers in random order.
+ while(!bufInfo.empty())
+ {
+ const size_t indexToDestroy = rand.Generate() % bufInfo.size();
+ const BufferInfo& currBufInfo = bufInfo[indexToDestroy];
+ vmaDestroyBuffer(g_hAllocator, currBufInfo.Buffer, currBufInfo.Allocation);
+ bufInfo.erase(bufInfo.begin() + indexToDestroy);
+ }
+
+ vmaDestroyPool(g_hAllocator, pool);
+}
+
void Test()
{
wprintf(L"TESTING:\n");
- if(false)
+ if(true)
{
// # Temporarily insert custom tests here
// ########################################
// ########################################
+ BasicTestBuddyAllocator();
return;
}
diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h
index 299b866..af6b08a 100644
--- a/src/vk_mem_alloc.h
+++ b/src/vk_mem_alloc.h
@@ -2964,15 +2964,27 @@
// Returns smallest power of 2 greater or equal to v.
static inline uint32_t VmaNextPow2(uint32_t v)
{
- v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; v++; return v;
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ v++;
+ return v;
}
-// Returns biggest power of 2 less or equal to v.
-/*
-static inline uint32_t VmaPrevPow2(uint32_t v)
+static inline uint64_t VmaNextPow2(uint64_t v)
{
-
+ v--;
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v |= v >> 16;
+ v |= v >> 32;
+ v++;
+ return v;
}
-*/
static inline bool VmaStrIsEmpty(const char* pStr)
{
@@ -4556,6 +4568,7 @@
VkDeviceSize sumItemSize; // Sum size of items to make lost that overlap with proposed allocation.
VmaSuballocationList::iterator item;
size_t itemsToMakeLostCount;
+ void* customData;
VkDeviceSize CalcCost() const
{
@@ -4967,10 +4980,11 @@
virtual void FreeAtOffset(VkDeviceSize offset);
private:
- static const size_t MAX_LEVELS = 30;
+ static const size_t MAX_LEVELS = 30; // TODO
struct Node
{
+ VkDeviceSize offset;
enum TYPE
{
TYPE_FREE,
@@ -5001,7 +5015,16 @@
Node* m_FreeList[MAX_LEVELS];
void DeleteNode(Node* node);
- bool ValidateNode(const Node* parent, const Node* curr) const;
+ bool ValidateNode(const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const;
+ uint32_t AllocSizeToLevel(VkDeviceSize allocSize) const;
+ VkDeviceSize LevelToNodeSize(uint32_t level) const;
+ // Alloc passed just for validation. Can be null.
+ void FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset);
+ void CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const;
+
+#if VMA_STATS_STRING_ENABLED
+ void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const;
+#endif
};
/*
@@ -9243,21 +9266,36 @@
VmaBlockMetadata::Init(size);
Node* rootNode = new Node();
+ rootNode->offset = 0;
rootNode->type = Node::TYPE_FREE;
rootNode->parent = VMA_NULL;
rootNode->buddy = VMA_NULL;
rootNode->free.nextFree = VMA_NULL;
+ m_Root = rootNode;
m_FreeList[0] = rootNode;
}
bool VmaBlockMetadata_Buddy::Validate() const
{
- if(!ValidateNode(VMA_NULL, m_Root))
+ if(!ValidateNode(VMA_NULL, m_Root, 0, GetSize()))
{
return false;
}
+ for(uint32_t level = 0; level < MAX_LEVELS; ++level)
+ {
+ for(Node* freeNode = m_FreeList[level];
+ freeNode != VMA_NULL;
+ freeNode = freeNode->free.nextFree)
+ {
+ if(freeNode->type != Node::TYPE_FREE)
+ {
+ return false;
+ }
+ }
+ }
+
return true;
}
@@ -9283,7 +9321,16 @@
void VmaBlockMetadata_Buddy::CalcAllocationStatInfo(VmaStatInfo& outInfo) const
{
- // TODO
+ outInfo.blockCount = 1;
+
+ outInfo.allocationCount = outInfo.unusedRangeCount = 0;
+ outInfo.unusedBytes = outInfo.unusedBytes = 0;
+
+ outInfo.allocationSizeMax = outInfo.unusedRangeSizeMax = 0;
+ outInfo.allocationSizeMin = outInfo.unusedRangeSizeMin = UINT64_MAX;
+ outInfo.allocationSizeAvg = outInfo.unusedRangeSizeAvg = 0; // Unused.
+
+ CalcAllocationStatInfoNode(outInfo, m_Root, GetSize());
}
void VmaBlockMetadata_Buddy::AddPoolStats(VmaPoolStats& inoutStats) const
@@ -9295,7 +9342,19 @@
void VmaBlockMetadata_Buddy::PrintDetailedMap(class VmaJsonWriter& json) const
{
- // TODO
+ // TODO optimize
+ VmaStatInfo stat;
+ CalcAllocationStatInfo(stat);
+
+ PrintDetailedMap_Begin(
+ json,
+ stat.unusedBytes,
+ stat.allocationCount,
+ stat.unusedRangeCount);
+
+ PrintDetailedMapNode(json, m_Root, GetSize());
+
+ PrintDetailedMap_End(json);
}
#endif // #if VMA_STATS_STRING_ENABLED
@@ -9312,8 +9371,81 @@
uint32_t strategy,
VmaAllocationRequest* pAllocationRequest)
{
+ VMA_ASSERT(!upperAddress && "VMA_ALLOCATION_CREATE_UPPER_ADDRESS_BIT can be used only with linear algorithm.");
+
+ const VkDeviceSize size = GetSize();
+ if(allocSize > size)
+ {
+ return false;
+ }
+
+ const uint32_t targetLevel = AllocSizeToLevel(allocSize);
+
+ // No free node with intended size.
+ if(m_FreeList[targetLevel] == VMA_NULL)
+ {
+ // Go up until we find free node with larget size.
+ uint32_t level = targetLevel;
+ while(m_FreeList[level] == VMA_NULL)
+ {
+ if(level == 0)
+ {
+ return false;
+ }
+ --level;
+ }
+
+ // Go down, splitting free nodes.
+ while(level < targetLevel)
+ {
+ // Get first free node at current level.
+ Node* node = m_FreeList[level];
+ // Remove it from list of free nodes at this level.
+ m_FreeList[level] = node->free.nextFree;
+
+ const uint32_t childrenLevel = level + 1;
+
+ // Create two free sub-nodes.
+ Node* leftChild = new Node();
+ Node* rightChild = new Node();
+
+ leftChild->offset = node->offset;
+ leftChild->type = Node::TYPE_FREE;
+ leftChild->parent = node;
+ leftChild->buddy = rightChild;
+ leftChild->free.nextFree = VMA_NULL;
+
+ rightChild->offset = node->offset + LevelToNodeSize(childrenLevel);
+ rightChild->type = Node::TYPE_FREE;
+ rightChild->parent = node;
+ rightChild->buddy = leftChild;
+ rightChild->free.nextFree = VMA_NULL;
+
+ // Convert current node to split type.
+ node->type = Node::TYPE_SPLIT;
+ node->split.leftChild = leftChild;
+
+ // Add child nodes to free list.
+ leftChild->free.nextFree = m_FreeList[childrenLevel];
+ m_FreeList[childrenLevel] = leftChild;
+
+ rightChild->free.nextFree = m_FreeList[childrenLevel];
+ m_FreeList[childrenLevel] = rightChild;
+
+ ++level;
+ }
+ }
+
+ Node* freeNode = m_FreeList[targetLevel];
+ VMA_ASSERT(freeNode != VMA_NULL);
+
+ pAllocationRequest->offset = freeNode->offset;
// TODO
- return false;
+ pAllocationRequest->sumFreeSize = 0;
+ pAllocationRequest->sumItemSize = 0;
+ pAllocationRequest->itemsToMakeLostCount = 0;
+ pAllocationRequest->customData = (void*)(uintptr_t)targetLevel;
+ return true;
}
bool VmaBlockMetadata_Buddy::MakeRequestedAllocationsLost(
@@ -9341,14 +9473,27 @@
bool upperAddress,
VmaAllocation hAllocation)
{
+ const uint32_t targetLevel = (uint32_t)(uintptr_t)request.customData;
+ VMA_ASSERT(m_FreeList[targetLevel] != VMA_NULL);
+ Node* node = m_FreeList[targetLevel];
+ VMA_ASSERT(node->type == Node::TYPE_FREE);
+
+ // Remove from free list.
+ m_FreeList[targetLevel] = node->free.nextFree;
+
+ // Convert to allocation node.
+ node->type = Node::TYPE_ALLOCATION;
+ node->allocation.alloc = hAllocation;
}
void VmaBlockMetadata_Buddy::Free(const VmaAllocation allocation)
{
+ FreeAtOffset(allocation, allocation->GetOffset());
}
void VmaBlockMetadata_Buddy::FreeAtOffset(VkDeviceSize offset)
{
+ FreeAtOffset(VMA_NULL, offset);
}
void VmaBlockMetadata_Buddy::DeleteNode(Node* node)
@@ -9362,7 +9507,7 @@
delete node;
}
-bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr) const
+bool VmaBlockMetadata_Buddy::ValidateNode(const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const
{
if(curr->parent != parent)
{
@@ -9387,17 +9532,31 @@
}
break;
case Node::TYPE_SPLIT:
- if(curr->split.leftChild == VMA_NULL)
{
- return false;
- }
- if(!ValidateNode(curr, curr->split.leftChild))
- {
- return false;
- }
- if(!ValidateNode(curr, curr->split.leftChild->buddy))
- {
- return false;
+ const uint32_t childrenLevel = level + 1;
+ const VkDeviceSize childrenLevelNodeSize = levelNodeSize / 2;
+ const Node* const leftChild = curr->split.leftChild;
+ if(leftChild == VMA_NULL)
+ {
+ return false;
+ }
+ if(leftChild->offset != curr->offset)
+ {
+ return false;
+ }
+ if(!ValidateNode(curr, leftChild, childrenLevel, childrenLevelNodeSize))
+ {
+ return false;
+ }
+ const Node* const rightChild = leftChild->buddy;
+ if(rightChild->offset != curr->offset + levelNodeSize)
+ {
+ return false;
+ }
+ if(!ValidateNode(curr, rightChild, childrenLevel, childrenLevelNodeSize))
+ {
+ return false;
+ }
}
break;
default:
@@ -9407,6 +9566,118 @@
return true;
}
+uint32_t VmaBlockMetadata_Buddy::AllocSizeToLevel(VkDeviceSize allocSize) const
+{
+ // TODO optimize
+ uint32_t level = 0;
+ VkDeviceSize currLevelNodeSize = GetSize();
+ VkDeviceSize nextLevelNodeSize = currLevelNodeSize / 2;
+ while(allocSize <= nextLevelNodeSize && level + 1 < MAX_LEVELS)
+ {
+ ++level;
+ currLevelNodeSize = nextLevelNodeSize;
+ nextLevelNodeSize = currLevelNodeSize / 2;
+ }
+ return level;
+}
+
+VkDeviceSize VmaBlockMetadata_Buddy::LevelToNodeSize(uint32_t level) const
+{
+ // TODO optimize
+ VkDeviceSize result = GetSize();
+ for(uint32_t i = 0; i < level; ++i)
+ {
+ result /= 2;
+ }
+ return result;
+}
+
+void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset)
+{
+ Node* node = m_Root;
+ uint32_t level = 0;
+ VkDeviceSize levelNodeSize = GetSize();
+ while(node->type == Node::TYPE_SPLIT)
+ {
+ Node* leftChild = node->split.leftChild;
+ Node* rightChild = leftChild->buddy;
+ if(offset < rightChild->offset) // TODO could be calculated
+ {
+ node = leftChild;
+ }
+ else
+ {
+ node = rightChild;
+ }
+ ++level;
+ }
+
+ VMA_ASSERT(node != VMA_NULL && node->type == Node::TYPE_ALLOCATION);
+ VMA_ASSERT(alloc == VK_NULL_HANDLE || node->allocation.alloc == alloc);
+
+ node->type = Node::TYPE_FREE;
+ node->free.nextFree = m_FreeList[level];
+ m_FreeList[level] = node;
+
+ // TODO join free nodes if possible.
+}
+
+void VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const
+{
+ switch(node->type)
+ {
+ case Node::TYPE_FREE:
+ ++outInfo.unusedRangeCount;
+ outInfo.unusedBytes += levelNodeSize;
+ outInfo.unusedRangeSizeMax = VMA_MAX(outInfo.unusedRangeSizeMax, levelNodeSize);
+ outInfo.unusedRangeSizeMin = VMA_MAX(outInfo.unusedRangeSizeMin, levelNodeSize);
+ break;
+ case Node::TYPE_ALLOCATION:
+ ++outInfo.allocationCount;
+ outInfo.usedBytes += levelNodeSize;
+ outInfo.allocationSizeMax = VMA_MAX(outInfo.allocationSizeMax, levelNodeSize);
+ outInfo.allocationSizeMin = VMA_MAX(outInfo.allocationSizeMin, levelNodeSize);
+ break;
+ case Node::TYPE_SPLIT:
+ {
+ const VkDeviceSize childrenNodeSize = levelNodeSize / 2;
+ const Node* const leftChild = node->split.leftChild;
+ CalcAllocationStatInfoNode(outInfo, leftChild, childrenNodeSize);
+ const Node* const rightChild = leftChild->buddy;
+ CalcAllocationStatInfoNode(outInfo, rightChild, childrenNodeSize);
+ }
+ break;
+ default:
+ VMA_ASSERT(0);
+ }
+}
+
+#if VMA_STATS_STRING_ENABLED
+void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const
+{
+ switch(node->type)
+ {
+ case Node::TYPE_FREE:
+ PrintDetailedMap_UnusedRange(json, node->offset, levelNodeSize);
+ break;
+ case Node::TYPE_ALLOCATION:
+ PrintDetailedMap_Allocation(json, node->offset, node->allocation.alloc);
+ break;
+ case Node::TYPE_SPLIT:
+ {
+ const VkDeviceSize childrenNodeSize = levelNodeSize / 2;
+ const Node* const leftChild = node->split.leftChild;
+ PrintDetailedMapNode(json, leftChild, childrenNodeSize);
+ const Node* const rightChild = leftChild->buddy;
+ PrintDetailedMapNode(json, rightChild, childrenNodeSize);
+ }
+ break;
+ default:
+ VMA_ASSERT(0);
+ }
+}
+#endif // #if VMA_STATS_STRING_ENABLED
+
////////////////////////////////////////////////////////////////////////////////
// class VmaDeviceMemoryBlock