Internal improvement: Added counting total number of VkDeviceMemory blocks.

Fixed case of spamming dedicated allocations instead of bigger blocks and thus and exceeding maxMemoryAllocationCount when heap size/budget is reached or exceeded.

Added debug macro VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT.
diff --git a/src/vk_mem_alloc.h b/src/vk_mem_alloc.h
index c61e80c..be21d44 100644
--- a/src/vk_mem_alloc.h
+++ b/src/vk_mem_alloc.h
@@ -4409,6 +4409,14 @@
     #define VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY (1)

 #endif

 

+#ifndef VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT

+    /*

+    Set this to 1 to make VMA never exceed VkPhysicalDeviceLimits::maxMemoryAllocationCount

+    and return error instead of leaving up to Vulkan implementation what to do in such cases.

+    */

+    #define VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT (0)

+#endif

+

 #ifndef VMA_SMALL_HEAP_MAX_SIZE

    /// Maximum size of a memory heap in Vulkan to consider it "small".

    #define VMA_SMALL_HEAP_MAX_SIZE (1024ull * 1024 * 1024)

@@ -7919,6 +7927,7 @@
     VMA_RW_MUTEX m_DedicatedAllocationsMutex[VK_MAX_MEMORY_TYPES];

 

     VmaCurrentBudgetData m_Budget;

+    VMA_ATOMIC_UINT32 m_DeviceMemoryCount; // Total number of VkDeviceMemory objects.

 

     VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo);

     VkResult Init(const VmaAllocatorCreateInfo* pCreateInfo);

@@ -15729,6 +15738,7 @@
         *pCreateInfo->pAllocationCallbacks : VmaEmptyAllocationCallbacks),

     m_AllocationObjectAllocator(&m_AllocationCallbacks),

     m_HeapSizeLimitMask(0),

+    m_DeviceMemoryCount(0),

     m_PreferredLargeHeapBlockSize(0),

     m_PhysicalDevice(pCreateInfo->physicalDevice),

     m_CurrentFrameIndex(0),

@@ -16244,34 +16254,40 @@
         {

             return VK_ERROR_OUT_OF_DEVICE_MEMORY;

         }

+

+        // Protection against creating each allocation as dedicated when we reach or exceed heap size/budget,

+        // which can quickly deplete maxMemoryAllocationCount: Don't try dedicated allocations when above

+        // 3/4 of the maximum allocation count.

+        if(m_DeviceMemoryCount.load() > m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount * 3 / 4)

+        {

+            return VK_ERROR_OUT_OF_DEVICE_MEMORY;

+        }

+

+        res = AllocateDedicatedMemory(

+            size,

+            suballocType,

+            memTypeIndex,

+            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0,

+            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,

+            (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,

+            finalCreateInfo.pUserData,

+            finalCreateInfo.priority,

+            dedicatedBuffer,

+            dedicatedBufferUsage,

+            dedicatedImage,

+            allocationCount,

+            pAllocations);

+        if(res == VK_SUCCESS)

+        {

+            // Succeeded: AllocateDedicatedMemory function already filld pMemory, nothing more to do here.

+            VMA_DEBUG_LOG("    Allocated as DedicatedMemory");

+            return VK_SUCCESS;

+        }

         else

         {

-            res = AllocateDedicatedMemory(

-                size,

-                suballocType,

-                memTypeIndex,

-                (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT) != 0,

-                (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_MAPPED_BIT) != 0,

-                (finalCreateInfo.flags & VMA_ALLOCATION_CREATE_USER_DATA_COPY_STRING_BIT) != 0,

-                finalCreateInfo.pUserData,

-                finalCreateInfo.priority,

-                dedicatedBuffer,

-                dedicatedBufferUsage,

-                dedicatedImage,

-                allocationCount,

-                pAllocations);

-            if(res == VK_SUCCESS)

-            {

-                // Succeeded: AllocateDedicatedMemory function already filld pMemory, nothing more to do here.

-                VMA_DEBUG_LOG("    Allocated as DedicatedMemory");

-                return VK_SUCCESS;

-            }

-            else

-            {

-                // Everything failed: Return error code.

-                VMA_DEBUG_LOG("    vkAllocateMemory FAILED");

-                return res;

-            }

+            // Everything failed: Return error code.

+            VMA_DEBUG_LOG("    vkAllocateMemory FAILED");

+            return res;

         }

     }

 }

@@ -17177,8 +17193,41 @@
     (*pAllocation)->InitLost();

 }

 

+// An object that increments given atomic but decrements it back in the destructor unless Commit() is called.

+template<typename AtomicT>

+struct AtomicTransactionalIncrement

+{

+public:

+    ~AtomicTransactionalIncrement()

+    {

+        if(m_Atomic)

+            --(*m_Atomic);

+    }

+    typename AtomicT::value_type Increment(AtomicT* atomic)

+    {

+        m_Atomic = atomic;

+        return m_Atomic->fetch_add(1);

+    }

+    void Commit()

+    {

+        m_Atomic = nullptr;

+    }

+

+private:

+    AtomicT* m_Atomic = nullptr;

+};

+

 VkResult VmaAllocator_T::AllocateVulkanMemory(const VkMemoryAllocateInfo* pAllocateInfo, VkDeviceMemory* pMemory)

 {

+    AtomicTransactionalIncrement<VMA_ATOMIC_UINT32> deviceMemoryCountIncrement;

+    const uint64_t prevDeviceMemoryCount = deviceMemoryCountIncrement.Increment(&m_DeviceMemoryCount);

+#if VMA_DEBUG_DONT_EXCEED_MAX_MEMORY_ALLOCATION_COUNT

+    if(prevDeviceMemoryCount >= m_PhysicalDeviceProperties.limits.maxMemoryAllocationCount)

+    {

+        return VK_ERROR_TOO_MANY_OBJECTS;

+    }

+#endif

+

     const uint32_t heapIndex = MemoryTypeIndexToHeapIndex(pAllocateInfo->memoryTypeIndex);

 

     // HeapSizeLimit is in effect for this heap.

@@ -17218,6 +17267,8 @@
         {

             (*m_DeviceMemoryCallbacks.pfnAllocate)(this, pAllocateInfo->memoryTypeIndex, *pMemory, pAllocateInfo->allocationSize, m_DeviceMemoryCallbacks.pUserData);

         }

+

+        deviceMemoryCountIncrement.Commit();

     }

     else

     {

@@ -17239,6 +17290,8 @@
     (*m_VulkanFunctions.vkFreeMemory)(m_hDevice, hMemory, GetAllocationCallbacks());

 

     m_Budget.m_BlockBytes[MemoryTypeIndexToHeapIndex(memoryType)] -= size;

+

+    --m_DeviceMemoryCount;

 }

 

 VkResult VmaAllocator_T::BindVulkanBuffer(