Changed VmaBlockMetadata_Buddy::m_FreeList into a doubly linked list. Implemented merging of free blocks. Buddy allocation algorithm now works.
diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h
index 8d05441..3a27dc9 100644
--- a/src/vk_mem_alloc.h
+++ b/src/vk_mem_alloc.h
@@ -5001,7 +5001,8 @@
         {

             struct

             {

-                Node* nextFree;

+                Node* prev;

+                Node* next;

             } free;

             struct

             {

@@ -5015,7 +5016,10 @@
     };

 

     Node* m_Root;

-    Node* m_FreeList[MAX_LEVELS];

+    struct {

+        Node* front;

+        Node* back;

+    } m_FreeList[MAX_LEVELS];

 

     void DeleteNode(Node* node);

     bool ValidateNode(const Node* parent, const Node* curr, uint32_t level, VkDeviceSize levelNodeSize) const;

@@ -5024,6 +5028,14 @@
     // Alloc passed just for validation. Can be null.

     void FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset);

     void CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const;

+    // Adds node to the front of FreeList at given level.

+    // node->type must be FREE.

+    // node->free.prev, next can be undefined.

+    void AddToFreeListFront(uint32_t level, Node* node);

+    // Removes node from FreeList at given level.

+    // node->type must be FREE.

+    // node->free.prev, next stay untouched.

+    void RemoveFromFreeList(uint32_t level, Node* node);

 

 #if VMA_STATS_STRING_ENABLED

     void PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const;

@@ -9273,29 +9285,51 @@
     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;

+    AddToFreeListFront(0, rootNode);

 }

 

 bool VmaBlockMetadata_Buddy::Validate() const

 {

+    // Validate tree.

     if(!ValidateNode(VMA_NULL, m_Root, 0, GetSize()))

     {

         return false;

     }

 

+    // Validate free node lists.

     for(uint32_t level = 0; level < MAX_LEVELS; ++level)

     {

-        for(Node* freeNode = m_FreeList[level];

-            freeNode != VMA_NULL;

-            freeNode = freeNode->free.nextFree)

+        if(m_FreeList[level].front != VMA_NULL &&

+            m_FreeList[level].front->free.prev != VMA_NULL)

         {

-            if(freeNode->type != Node::TYPE_FREE)

+            return false;

+        }

+

+        for(Node* node = m_FreeList[level].front;

+            node != VMA_NULL;

+            node = node->free.next)

+        {

+            if(node->type != Node::TYPE_FREE)

             {

                 return false;

             }

+            

+            if(node->free.next == VMA_NULL)

+            {

+                if(m_FreeList[level].back != node)

+                {

+                    return false;

+                }

+            }

+            else

+            {

+                if(node->free.next->free.prev != node)

+                {

+                    return false;

+                }

+            }

         }

     }

 

@@ -9385,9 +9419,9 @@
     const uint32_t targetLevel = AllocSizeToLevel(allocSize);

     for(uint32_t level = targetLevel + 1; level--; )

     {

-        if(m_FreeList[level] != VMA_NULL)

+        if(m_FreeList[level].front != VMA_NULL)

         {

-            pAllocationRequest->offset = m_FreeList[level]->offset;

+            pAllocationRequest->offset = m_FreeList[level].front->offset;

             pAllocationRequest->sumFreeSize = LevelToNodeSize(level);

             pAllocationRequest->sumItemSize = 0;

             pAllocationRequest->itemsToMakeLostCount = 0;

@@ -9426,8 +9460,8 @@
 {

     const uint32_t targetLevel = AllocSizeToLevel(allocSize);

     uint32_t currLevel = (uint32_t)(uintptr_t)request.customData;

-    VMA_ASSERT(m_FreeList[currLevel] != VMA_NULL);

-    Node* currNode = m_FreeList[currLevel];

+    VMA_ASSERT(m_FreeList[currLevel].front != VMA_NULL);

+    Node* currNode = m_FreeList[currLevel].front;

     VMA_ASSERT(currNode->type == Node::TYPE_FREE);

     VMA_ASSERT(currNode->offset == request.offset);

     

@@ -9436,7 +9470,7 @@
     {

         // currNode is already first free node at currLevel.

         // Remove it from list of free nodes at this currLevel.

-        m_FreeList[currLevel] = currNode->free.nextFree;

+        RemoveFromFreeList(currLevel, currNode);

          

         const uint32_t childrenLevel = currLevel + 1;

 

@@ -9448,32 +9482,27 @@
         leftChild->type = Node::TYPE_FREE;

         leftChild->parent = currNode;

         leftChild->buddy = rightChild;

-        leftChild->free.nextFree = VMA_NULL;

 

         rightChild->offset = currNode->offset + LevelToNodeSize(childrenLevel);

         rightChild->type = Node::TYPE_FREE;

         rightChild->parent = currNode;

         rightChild->buddy = leftChild;

-        rightChild->free.nextFree = VMA_NULL;

 

         // Convert current currNode to split type.

         currNode->type = Node::TYPE_SPLIT;

         currNode->split.leftChild = leftChild;

 

-        // Add child nodes to free list.

-        rightChild->free.nextFree = m_FreeList[childrenLevel];

-        m_FreeList[childrenLevel] = rightChild;

-

-        leftChild->free.nextFree = m_FreeList[childrenLevel];

-        m_FreeList[childrenLevel] = leftChild;

+        // Add child nodes to free list. Order is important!

+        AddToFreeListFront(childrenLevel, rightChild);

+        AddToFreeListFront(childrenLevel, leftChild);

 

         ++currLevel;

-        currNode = m_FreeList[currLevel];

+        currNode = m_FreeList[currLevel].front;

     }

 

     // Remove from free list.

     VMA_ASSERT(currLevel == targetLevel && currNode != VMA_NULL && currNode->type == Node::TYPE_FREE);

-    m_FreeList[targetLevel] = currNode->free.nextFree;

+    RemoveFromFreeList(currLevel, currNode);

 

     // Convert to allocation node.

     currNode->type = Node::TYPE_ALLOCATION;

@@ -9588,9 +9617,9 @@
 

 void VmaBlockMetadata_Buddy::FreeAtOffset(VmaAllocation alloc, VkDeviceSize offset)

 {

+    // Find node and level.

     Node* node = m_Root;

     uint32_t level = 0;

-    VkDeviceSize levelNodeSize = GetSize();

     while(node->type == Node::TYPE_SPLIT)

     {

         Node* leftChild = node->split.leftChild;

@@ -9610,10 +9639,22 @@
     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.

+    // Join free nodes if possible.

+    while(level > 0 && node->buddy->type == Node::TYPE_FREE)

+    {

+        RemoveFromFreeList(level, node->buddy);

+        Node* const parent = node->parent;

+

+        delete node->buddy;

+        delete node;

+        parent->type = Node::TYPE_FREE;

+        

+        node = parent;

+        --level;

+    }

+

+    AddToFreeListFront(level, node);

 }

 

 void VmaBlockMetadata_Buddy::CalcAllocationStatInfoNode(VmaStatInfo& outInfo, const Node* node, VkDeviceSize levelNodeSize) const

@@ -9646,6 +9687,59 @@
     }

 }

 

+void VmaBlockMetadata_Buddy::AddToFreeListFront(uint32_t level, Node* node)

+{

+    VMA_ASSERT(node->type == Node::TYPE_FREE);

+

+    // List is empty.

+    Node* const frontNode = m_FreeList[level].front;

+    if(frontNode == VMA_NULL)

+    {

+        VMA_ASSERT(m_FreeList[level].back == VMA_NULL);

+        node->free.prev = node->free.next = VMA_NULL;

+        m_FreeList[level].front = m_FreeList[level].back = node;

+    }

+    else

+    {

+        VMA_ASSERT(frontNode->free.prev == VMA_NULL);

+        node->free.prev = VMA_NULL;

+        node->free.next = frontNode;

+        frontNode->free.prev = node;

+        m_FreeList[level].front = node;

+    }

+}

+

+void VmaBlockMetadata_Buddy::RemoveFromFreeList(uint32_t level, Node* node)

+{

+    VMA_ASSERT(m_FreeList[level].front != VMA_NULL);

+

+    // It is at the front.

+    if(node->free.prev == VMA_NULL)

+    {

+        VMA_ASSERT(m_FreeList[level].front == node);

+        m_FreeList[level].front = node->free.next;

+    }

+    else

+    {

+        Node* const prevFreeNode = node->free.prev;

+        VMA_ASSERT(prevFreeNode->free.next == node);

+        prevFreeNode->free.next = node->free.next;

+    }

+

+    // It is at the back.

+    if(node->free.next == VMA_NULL)

+    {

+        VMA_ASSERT(m_FreeList[level].back == node);

+        m_FreeList[level].back = node->free.prev;

+    }

+    else

+    {

+        Node* const nextFreeNode = node->free.next;

+        VMA_ASSERT(nextFreeNode->free.prev == node);

+        nextFreeNode->free.prev = node->free.prev;

+    }

+}

+

 #if VMA_STATS_STRING_ENABLED

 void VmaBlockMetadata_Buddy::PrintDetailedMapNode(class VmaJsonWriter& json, const Node* node, VkDeviceSize levelNodeSize) const

 {