From fc48eb1592ce4d28990c6341e3d82d4719b2f80c Mon Sep 17 00:00:00 2001 From: Sidney Just Date: Thu, 12 Mar 2026 10:32:10 -0700 Subject: [PATCH 01/18] Added missing pool locks in the VmaDefragmenterContext constructor and destructor --- include/vk_mem_alloc.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index 24792855..ae2c984f 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -12221,6 +12221,8 @@ VmaDefragmentationContext_T::VmaDefragmentationContext_T( m_BlockVectorCount = 1; m_PoolBlockVector = &info.pool->m_BlockVector; m_pBlockVectors = &m_PoolBlockVector; + + VmaMutexLockWrite lock(m_PoolBlockVector->m_Mutex, hAllocator->m_UseMutex); m_PoolBlockVector->SetIncrementalSort(false); m_PoolBlockVector->SortByFreeSize(); } @@ -12234,6 +12236,7 @@ VmaDefragmentationContext_T::VmaDefragmentationContext_T( VmaBlockVector* vector = m_pBlockVectors[i]; if (vector != VMA_NULL) { + VmaMutexLockWrite lock(vector->m_Mutex, hAllocator->m_UseMutex); vector->SetIncrementalSort(false); vector->SortByFreeSize(); } @@ -12264,6 +12267,7 @@ VmaDefragmentationContext_T::~VmaDefragmentationContext_T() { if (m_PoolBlockVector != VMA_NULL) { + VmaMutexLockWrite lock(m_PoolBlockVector->m_Mutex, m_PoolBlockVector->m_hAllocator->m_UseMutex); m_PoolBlockVector->SetIncrementalSort(true); } else @@ -12272,7 +12276,10 @@ VmaDefragmentationContext_T::~VmaDefragmentationContext_T() { VmaBlockVector* vector = m_pBlockVectors[i]; if (vector != VMA_NULL) + { + VmaMutexLockWrite lock(vector->m_Mutex, vector->m_hAllocator->m_UseMutex); vector->SetIncrementalSort(true); + } } } From cc26b9f978b1b5a1173edf752b9ab0da67e5f30e Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 13 Mar 2026 12:22:12 +0100 Subject: [PATCH 02/18] Updated CHANGELOG.md --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 26f4e9b6..694ed0fc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,8 @@ - Added function `vmaGetMemoryWin32Handle2` offering extra parameter `VkExternalMemoryHandleTypeFlagBits handleType`. - Added `VMA_VERSION` macro with library version number (#507). - Improvements in the algorithm choosing memory type when `VMA_MEMORY_USAGE_AUTO*` is used (#520). -- Fixes for compatibility with C++20 modules on Clang 21 and GCC15 (#513, #514). +- Fixed compatibility with C++20 modules on Clang 21 and GCC15 (#513, #514). +- Fixed race condition in defragmentation (#529, #313). - Other fixes and improvements, including compatibility with various platforms and compilers, improvements in documentation, sample application, and tests. # 3.3.0 (2025-05-12) From c3c7a2c073c3bb721f23767334984c2da65cfba3 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Sat, 16 May 2026 15:52:20 +0200 Subject: [PATCH 03/18] Added missing mutex lock in VmaDefragmentationContext_T::DefragmentPassEnd As proposed by @JustSid in https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/issues/313 --- include/vk_mem_alloc.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index ae2c984f..3934de34 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -12377,7 +12377,11 @@ VkResult VmaDefragmentationContext_T::DefragmentPassEnd(VmaDefragmentationPassMo { case VMA_DEFRAGMENTATION_MOVE_OPERATION_COPY: { - uint8_t mapCount = move.srcAllocation->SwapBlockAllocation(vector->m_hAllocator, move.dstTmpAllocation); + uint8_t mapCount = 0; + { + VmaMutexLockWrite swapLock(vector->GetMutex(), vector->GetAllocator()->m_UseMutex); + mapCount = move.srcAllocation->SwapBlockAllocation(vector->m_hAllocator, move.dstTmpAllocation); + } if (mapCount > 0) { allocator = vector->m_hAllocator; From 2ee9752db8199728c7e4098084d1a4d85655890a Mon Sep 17 00:00:00 2001 From: spencer-lunarg Date: Thu, 21 May 2026 15:14:53 +0200 Subject: [PATCH 04/18] Use vkGetPhysicalDeviceProperties2 when possible --- include/vk_mem_alloc.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index 24792855..3afe14b1 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -1052,6 +1052,7 @@ typedef struct VmaVulkanFunctions #if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 /// Fetch from "vkGetPhysicalDeviceMemoryProperties2" on Vulkan >= 1.1, but you can also fetch it from "vkGetPhysicalDeviceMemoryProperties2KHR" if you enabled extension VK_KHR_get_physical_device_properties2. PFN_vkGetPhysicalDeviceMemoryProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties2KHR; + PFN_vkGetPhysicalDeviceProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceProperties2KHR; #endif #if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 /// Fetch from "vkGetDeviceBufferMemoryRequirements" on Vulkan >= 1.3, but you can also fetch it from "vkGetDeviceBufferMemoryRequirementsKHR" if you enabled extension VK_KHR_maintenance4. @@ -13363,8 +13364,25 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : ImportVulkanFunctions(pCreateInfo->pVulkanFunctions); +#if VMA_VULKAN_VERSION >= 1001000 + VkPhysicalDeviceProperties2KHR props2; + props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; + props2.pNext = nullptr; + + VkPhysicalDeviceMemoryProperties2KHR memProps2; + memProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR; + memProps2.pNext = nullptr; + + (*m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR)(m_PhysicalDevice, &props2); + (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR)(m_PhysicalDevice, &memProps2); + + + memcpy(&m_PhysicalDeviceProperties, &props2.properties, sizeof(m_PhysicalDeviceProperties)); + memcpy(&m_MemProps, &memProps2.memoryProperties, sizeof(m_MemProps)); +#else (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties); (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps); +#endif VMA_ASSERT(VmaIsPow2(VMA_MIN_ALIGNMENT)); VMA_ASSERT(VmaIsPow2(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY)); @@ -13507,6 +13525,7 @@ void VmaAllocator_T::ImportVulkanFunctions_Static() if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR = (PFN_vkGetPhysicalDeviceMemoryProperties2)vkGetPhysicalDeviceMemoryProperties2; + m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR = (PFN_vkGetPhysicalDeviceProperties2)vkGetPhysicalDeviceProperties2; } #endif @@ -13560,6 +13579,7 @@ void VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVul #if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR); + VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties2KHR); #endif #if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 @@ -13624,6 +13644,8 @@ void VmaAllocator_T::ImportVulkanFunctions_Dynamic() VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); // Try to fetch the pointer from the other name, based on suspected driver bug - see issue #410. VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2KHR"); } else if(m_UseExtMemoryBudget) { @@ -13653,6 +13675,7 @@ void VmaAllocator_T::ImportVulkanFunctions_Dynamic() if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2"); } else if(m_UseExtMemoryBudget) { @@ -13727,6 +13750,10 @@ void VmaAllocator_T::ValidateVulkanFunctions() const { VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL); } + if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) + { + VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR != VMA_NULL); + } #endif #if VMA_EXTERNAL_MEMORY_WIN32 if (m_UseKhrExternalMemoryWin32) @@ -15608,6 +15635,7 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaImportVulkanFunctionsFromVolk( if (pAllocatorCreateInfo->vulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { COPY_GLOBAL_TO_VMA_FUNC(vkGetPhysicalDeviceMemoryProperties2, vkGetPhysicalDeviceMemoryProperties2KHR) + COPY_GLOBAL_TO_VMA_FUNC(vkGetPhysicalDeviceProperties2, vkGetPhysicalDeviceProperties2KHR) COPY_DEVICE_TO_VMA_FUNC(vkGetBufferMemoryRequirements2, vkGetBufferMemoryRequirements2KHR) COPY_DEVICE_TO_VMA_FUNC(vkGetImageMemoryRequirements2, vkGetImageMemoryRequirements2KHR) COPY_DEVICE_TO_VMA_FUNC(vkBindBufferMemory2, vkBindBufferMemory2KHR) From ee8ccdc39770e17644c9e4a526a511ad85e05fd7 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 12:17:02 +0200 Subject: [PATCH 05/18] Fix in MyUniformRandomNumberGenerator fixing compilation on Visual Studio 2026 --- src/Common.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Common.h b/src/Common.h index f76e57fa..87cfbb4d 100644 --- a/src/Common.h +++ b/src/Common.h @@ -280,8 +280,8 @@ struct MyUniformRandomNumberGenerator { typedef uint32_t result_type; MyUniformRandomNumberGenerator(RandomNumberGenerator& gen) : m_Gen(gen) { } - static uint32_t min() { return 0; } - static uint32_t max() { return UINT32_MAX; } + static constexpr uint32_t min() { return 0; } + static constexpr uint32_t max() { return UINT32_MAX; } uint32_t operator()() { return m_Gen.Generate(); } private: From c2c925f0ac485efd9e37efcd5728465018e00371 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 12:51:41 +0200 Subject: [PATCH 06/18] Fixes after merge related to VmaVulkanFunctions::vkGetPhysicalDeviceProperties2KHR, VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 --- CHANGELOG.md | 1 + include/vk_mem_alloc.h | 90 +++++++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 694ed0fc..4278f455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Improvements for external memory export & import (#503): - Added functions `vmaCreateDedicatedBuffer`, `vmaCreateDedicatedImage`, `vmaAllocateDedicatedMemory` offering extra parameter `void* pMemoryAllocateNext`. - Added function `vmaGetMemoryWin32Handle2` offering extra parameter `VkExternalMemoryHandleTypeFlagBits handleType`. +- Added member `VmaVulkanFunctions::vkGetPhysicalDeviceProperties2KHR` and macro `VMA_GET_PHYSICAL_DEVICE_PROPERTIES2` to fix validation layer warnings about the usage of legacy commands on Vulkan ≥ 1.1 (#530, #531). - Added `VMA_VERSION` macro with library version number (#507). - Improvements in the algorithm choosing memory type when `VMA_MEMORY_USAGE_AUTO*` is used (#520). - Fixed compatibility with C++20 modules on Clang 21 and GCC15 (#513, #514). diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index b28b0996..e97cbdab 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -194,8 +194,16 @@ extern "C" { #endif #endif +#if !defined(VMA_GET_PHYSICAL_DEVICE_PROPERTIES2) + #if VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000 + #define VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 1 + #else + #define VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 0 + #endif +#endif + #if !defined(VMA_MEMORY_BUDGET) - #if VK_EXT_memory_budget && (VK_KHR_get_physical_device_properties2 || VMA_VULKAN_VERSION >= 1001000) + #if VK_EXT_memory_budget && VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 #define VMA_MEMORY_BUDGET 1 #else #define VMA_MEMORY_BUDGET 0 @@ -1049,10 +1057,9 @@ typedef struct VmaVulkanFunctions /// Fetch "vkBindImageMemory2" on Vulkan >= 1.1, fetch "vkBindImageMemory2KHR" when using VK_KHR_bind_memory2 extension. PFN_vkBindImageMemory2KHR VMA_NULLABLE vkBindImageMemory2KHR; #endif -#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 /// Fetch from "vkGetPhysicalDeviceMemoryProperties2" on Vulkan >= 1.1, but you can also fetch it from "vkGetPhysicalDeviceMemoryProperties2KHR" if you enabled extension VK_KHR_get_physical_device_properties2. PFN_vkGetPhysicalDeviceMemoryProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceMemoryProperties2KHR; - PFN_vkGetPhysicalDeviceProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceProperties2KHR; #endif #if VMA_KHR_MAINTENANCE4 || VMA_VULKAN_VERSION >= 1003000 /// Fetch from "vkGetDeviceBufferMemoryRequirements" on Vulkan >= 1.3, but you can also fetch it from "vkGetDeviceBufferMemoryRequirementsKHR" if you enabled extension VK_KHR_maintenance4. @@ -1065,6 +1072,10 @@ typedef struct VmaVulkanFunctions #else void* VMA_NULLABLE vkGetMemoryWin32HandleKHR; #endif +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 + /// Fetch from "vkGetPhysicalDeviceProperties2" on Vulkan >= 1.1, but you can also fetch it from "vkGetPhysicalDeviceProperties2KHR" if you enabled extension VK_KHR_get_physical_device_properties2. + PFN_vkGetPhysicalDeviceProperties2KHR VMA_NULLABLE vkGetPhysicalDeviceProperties2KHR; +#endif } VmaVulkanFunctions; /// Description of a Allocator to be created. @@ -13299,7 +13310,7 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : } #endif } -#if !(VMA_MEMORY_BUDGET) +#if !(VMA_MEMORY_BUDGET) || !(VMA_GET_PHYSICAL_DEVICE_PROPERTIES2) if((pCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) { VMA_ASSERT(0 && "VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT set but required extension is disabled by preprocessor macros."); @@ -13375,25 +13386,35 @@ VmaAllocator_T::VmaAllocator_T(const VmaAllocatorCreateInfo* pCreateInfo) : ImportVulkanFunctions(pCreateInfo->pVulkanFunctions); -#if VMA_VULKAN_VERSION >= 1001000 - VkPhysicalDeviceProperties2KHR props2; - props2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR; - props2.pNext = nullptr; - - VkPhysicalDeviceMemoryProperties2KHR memProps2; - memProps2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR; - memProps2.pNext = nullptr; - - (*m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR)(m_PhysicalDevice, &props2); - (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR)(m_PhysicalDevice, &memProps2); - + // Call vkGetPhysicalDeviceProperties[2][KHR]. +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 + if(m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR) + { + VkPhysicalDeviceProperties2KHR props2 = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR }; + (*m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR)(m_PhysicalDevice, &props2); + memcpy(&m_PhysicalDeviceProperties, &props2.properties, sizeof(m_PhysicalDeviceProperties)); + } + else +#endif + { + (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties); + } - memcpy(&m_PhysicalDeviceProperties, &props2.properties, sizeof(m_PhysicalDeviceProperties)); - memcpy(&m_MemProps, &memProps2.memoryProperties, sizeof(m_MemProps)); -#else - (*m_VulkanFunctions.vkGetPhysicalDeviceProperties)(m_PhysicalDevice, &m_PhysicalDeviceProperties); - (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps); + // Call vkGetPhysicalDeviceMemoryProperties[2][KHR]. +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 + if(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR) + { + VkPhysicalDeviceMemoryProperties2KHR memProps2 = { + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2_KHR }; + (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR)(m_PhysicalDevice, &memProps2); + memcpy(&m_MemProps, &memProps2.memoryProperties, sizeof(m_MemProps)); + } + else #endif + { + (*m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties)(m_PhysicalDevice, &m_MemProps); + } VMA_ASSERT(VmaIsPow2(VMA_MIN_ALIGNMENT)); VMA_ASSERT(VmaIsPow2(VMA_DEBUG_MIN_BUFFER_IMAGE_GRANULARITY)); @@ -13588,7 +13609,7 @@ void VmaAllocator_T::ImportVulkanFunctions_Custom(const VmaVulkanFunctions* pVul VMA_COPY_IF_NOT_NULL(vkBindImageMemory2KHR); #endif -#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceMemoryProperties2KHR); VMA_COPY_IF_NOT_NULL(vkGetPhysicalDeviceProperties2KHR); #endif @@ -13649,20 +13670,22 @@ void VmaAllocator_T::ImportVulkanFunctions_Dynamic() } #endif -#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2"); // Try to fetch the pointer from the other name, based on suspected driver bug - see issue #410. VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); - VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2"); VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2KHR"); } else if(m_UseExtMemoryBudget) { VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2KHR"); // Try to fetch the pointer from the other name, based on suspected driver bug - see issue #410. VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); + VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2"); } #endif @@ -13682,18 +13705,6 @@ void VmaAllocator_T::ImportVulkanFunctions_Dynamic() } #endif // #if VMA_BIND_MEMORY2 -#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 - if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) - { - VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2"); - VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceProperties2KHR, PFN_vkGetPhysicalDeviceProperties2KHR, "vkGetPhysicalDeviceProperties2"); - } - else if(m_UseExtMemoryBudget) - { - VMA_FETCH_INSTANCE_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, PFN_vkGetPhysicalDeviceMemoryProperties2KHR, "vkGetPhysicalDeviceMemoryProperties2KHR"); - } -#endif // #if VMA_MEMORY_BUDGET - #if VMA_VULKAN_VERSION >= 1003000 if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 3, 0)) { @@ -13756,16 +13767,14 @@ void VmaAllocator_T::ValidateVulkanFunctions() const } #endif -#if VMA_MEMORY_BUDGET || VMA_VULKAN_VERSION >= 1001000 +#if VMA_GET_PHYSICAL_DEVICE_PROPERTIES2 if(m_UseExtMemoryBudget || m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) { VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceMemoryProperties2KHR != VMA_NULL); - } - if(m_VulkanApiVersion >= VK_MAKE_VERSION(1, 1, 0)) - { VMA_ASSERT(m_VulkanFunctions.vkGetPhysicalDeviceProperties2KHR != VMA_NULL); } #endif + #if VMA_EXTERNAL_MEMORY_WIN32 if (m_UseKhrExternalMemoryWin32) { @@ -15685,6 +15694,7 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaImportVulkanFunctionsFromVolk( if ((pAllocatorCreateInfo->flags & VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT) != 0) { COPY_GLOBAL_TO_VMA_FUNC(vkGetPhysicalDeviceMemoryProperties2KHR, vkGetPhysicalDeviceMemoryProperties2KHR) + COPY_GLOBAL_TO_VMA_FUNC(vkGetPhysicalDeviceProperties2KHR, vkGetPhysicalDeviceProperties2KHR) } #endif #if VMA_EXTERNAL_MEMORY_WIN32 From 6db09d940f299c0006656bf3f132f28d2f741576 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 13:44:11 +0200 Subject: [PATCH 07/18] Fix in sample app for validation layer error about extensions VK_KHR_maintenance5, VK_KHR_dynamic_rendering --- src/VulkanSample.cpp | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index 256e2fca..d1ed500d 100644 --- a/src/VulkanSample.cpp +++ b/src/VulkanSample.cpp @@ -1892,6 +1892,8 @@ static void InitializeApplication() physicalDeviceExtensionProperties.data()) ); } + bool maintenance5ExtensionAvailable = false; + for(uint32_t i = 0; i < physicalDeviceExtensionPropertyCount; ++i) { if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME) == 0) @@ -1929,7 +1931,7 @@ static void InitializeApplication() else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME) == 0) VK_EXT_memory_priority_enabled = true; else if(strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_MAINTENANCE_5_EXTENSION_NAME) == 0) - VK_KHR_maintenance5_enabled = true; + maintenance5ExtensionAvailable = true; else if (strcmp(physicalDeviceExtensionProperties[i].extensionName, VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME) == 0) VK_KHR_external_memory_win32_enabled = VMA_DYNAMIC_VULKAN_FUNCTIONS; } @@ -1937,6 +1939,12 @@ static void InitializeApplication() if(GetVulkanApiVersion() >= VK_API_VERSION_1_2) VK_KHR_buffer_device_address_enabled = true; // Promoted to core Vulkan 1.2. + // This sample can use maintenance5 either via core Vulkan 1.4, or via the + // extension on Vulkan 1.3. It doesn't enable the older dynamic-rendering path. + const bool maintenance5CanBeEnabled = + GetVulkanApiVersion() >= VK_API_VERSION_1_4 || + (GetVulkanApiVersion() >= VK_API_VERSION_1_3 && maintenance5ExtensionAvailable); + // Query for features #if VMA_VULKAN_VERSION >= 1001000 @@ -1987,6 +1995,12 @@ static void InitializeApplication() PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceMemoryPriorityFeatures); } + VkPhysicalDeviceMaintenance5FeaturesKHR physicalDeviceMaintenance5Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR }; + if(maintenance5CanBeEnabled) + { + PnextChainPushFront(&physicalDeviceFeatures, &physicalDeviceMaintenance5Features); + } + vkGetPhysicalDeviceFeatures2(g_hPhysicalDevice, &physicalDeviceFeatures); g_SparseBindingEnabled = physicalDeviceFeatures.features.sparseBinding != 0; @@ -1998,6 +2012,9 @@ static void InitializeApplication() VK_KHR_buffer_device_address_enabled = false; if(VK_EXT_memory_priority_enabled && !physicalDeviceMemoryPriorityFeatures.memoryPriority) VK_EXT_memory_priority_enabled = false; + VK_KHR_maintenance5_enabled = + maintenance5CanBeEnabled && + physicalDeviceMaintenance5Features.maintenance5 != VK_FALSE; // Find queue family index @@ -2090,7 +2107,7 @@ static void InitializeApplication() enabledDeviceExtensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); if(VK_EXT_memory_priority_enabled) enabledDeviceExtensions.push_back(VK_EXT_MEMORY_PRIORITY_EXTENSION_NAME); - if(VK_KHR_maintenance5_enabled) + if(VK_KHR_maintenance5_enabled && GetVulkanApiVersion() < VK_API_VERSION_1_4) enabledDeviceExtensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); if (VK_KHR_external_memory_win32_enabled) enabledDeviceExtensions.push_back(VK_KHR_EXTERNAL_MEMORY_WIN32_EXTENSION_NAME); @@ -2114,6 +2131,12 @@ static void InitializeApplication() { PnextChainPushBack(&deviceFeatures, &physicalDeviceMemoryPriorityFeatures); } + if(VK_KHR_maintenance5_enabled) + { + physicalDeviceMaintenance5Features = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_5_FEATURES_KHR }; + physicalDeviceMaintenance5Features.maintenance5 = VK_TRUE; + PnextChainPushBack(&deviceFeatures, &physicalDeviceMaintenance5Features); + } VkDeviceCreateInfo deviceCreateInfo = { VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO }; deviceCreateInfo.pNext = &deviceFeatures; From 983745158e2c11602360e293559c1ed52a70e906 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 13:54:37 +0200 Subject: [PATCH 08/18] Added file AGENTS.md for agentic AI --- AGENTS.md | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++ CHANGELOG.md | 1 + 2 files changed, 98 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..8c7fcd7b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,97 @@ +The directory where this file exists is Vulkan Memory Allocator (VMA) - a software library implemented in C++, with a C interface, intended for developers who use Vulkan API. It is distributed under the MIT license. + +# Requirements + +Using VMA requires a C++ compiler and a Vulkan SDK or Vulkan headers available. The public API is C-compatible. The implementation translation unit must be compiled as C++. VMA requires at least C++14 for the implementation. + +It supports any platform that has a C++ compiler and Vulkan API available (including but not limited to: Windows, Linux, Android) and any Vulkan-compatible GPU (whether a discrete or integrated PC GPU or a mobile SoC). + +# Scope + +Using this library is not required for using Vulkan, but it is helpful and recommended, as it simplifies some aspects of the Vulkan API: allocating device memory, creating buffers and images. Specifically, what the library does is: + +1. It automatically selects the correct and optimal memory type among the ones available on the current GPU, based on the intended usage flags of the buffer/image, memory requirements that Vulkan returns for it, and parameters of the allocation to be created. +2. It allocates large blocks of `VkDeviceMemory` and implements an allocation algorithm to manage parts of them, to be assigned to individual buffers/images. +3. It provides a simple API (with the most important functions being `vmaCreateBuffer`, `vmaCreateImage`) that internally does the following things (so you don't need to directly call these `vk*` functions mentioned below): + - Creates the new resource (buffer or image) using functions like `vkCreateBuffer`, `vkCreateImage`. + - Queries for its memory requirements using a function like `vkGetBufferMemoryRequirements`, `vkGetImageMemoryRequirements` or similar. + - Allocates a new `VkDeviceMemory` block using `vkAllocateMemory` function or assigns a region of an existing one, suitable for the resource, while fulfilling the requirements of alignment and size. + - Binds the resource to the memory using a function like `vkBindBufferMemory`, `vkBindImageMemory`. + +# How to use + +VMA is distributed in source form. All library code lives in `include/vk_mem_alloc.h`. The file is very long, so don't read it entirely! Focus on specific sections or search for specific symbols. + +The library is STB-style single header. It means all its interface and implementation is contained within this one file, but it doesn't mean all C++ functions are `inline`. Instead, it means that the internal implementation needs to be extracted in exactly one .cpp file by defining macro `VMA_IMPLEMENTATION` before the include. + +Other C++ source/header files you can see in this repository are sample, support, and test code, so you don't need to use them when developing software using this library. + +The `include/vk_mem_alloc.h` file consists of the following sections: + +1. From the beginning until the line `#ifdef VMA_IMPLEMENTATION`: Public interface (API) of the library, which has the form of a C header file with all the public enums, structures, functions. + - Accompanying comments before every symbol, in Doxygen format, are complete documentation of the API. + - You may need to analyze pieces of this section to understand how to correctly use the library. +2. From the line `#ifdef VMA_IMPLEMENTATION` to the line `#endif // VMA_IMPLEMENTATION`: Internal implementation of the library in C++. + - You typically don't need to analyze it when using the library. You can use it only when you develop the library itself or when you need to know its internal implementation beyond what the API and its documentation provides. +3. From the line `#endif // VMA_IMPLEMENTATION` until the end: A long comment in Doxygen format providing a generic documentation of the library, with high-level overview chapters, FAQ, and code examples. + - You may use this section if you need to learn more generic information about the library or find some example code with correct and recommended usage of its functions. + +# Library API + +The API of VMA is similar in style to Vulkan. + +Errors are reported using `VkResult` error codes returned from most functions, with `VK_SUCCESS` meaning success. + +Types that represent objects are opaque pointers or handles. Most important types of objects are: + +- `VmaAllocator` - represents the main allocator object. Creating only one per `=` and keeping it alive for the whole time when using Vulkan is recommended. +- `VmaAllocation` - represents a specific region of allocated Vulkan memory - a dedicated `VkDeviceMemory` block or a piece of a larger block. It may or may not be bound to a specific buffer/image. That memory block is managed internally by VMA. + +Example code: + +``` +// 1. Fill in Create structures: +VkBufferCreateInfo bufferCreateInfo = ... + +VmaAllocationCreateInfo allocCreateInfo = {}; +allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO; +allocCreateInfo.flags = 0; + +// 2. Create two objects: buffer and its allocation: +VkBuffer buffer = VK_NULL_HANDLE; +VmaAllocation allocation = VK_NULL_HANDLE; +VkResult res = vmaCreateBuffer(allocator, &bufferCreateInfo, &allocCreateInfo, &buffer, &allocation, nullptr); +if(res != VK_SUCCESS) { ... } + +// 3. Using buffer or allocation here, e.g.: +VkMemoryPropertyFlags memPropFlags = 0; +vmaGetAllocationMemoryProperties(allocator, allocation, &memPropFlags); +// Using memPropFlags... + +// 4. At the end, destroy the buffer and the allocation: +vmaDestroyBuffer(allocator, buffer, allocation); +``` + +The pattern for creating an allocation object, as well as other library objects, looks like in Vulkan: + +1. Fill `Vma*CreateInfo` structure. + - ALWAYS fully initialize such structure with zeros before filling selected members. Don't rely on the number or sizes of members - new ones may be added in future versions! +2. Call `vmaCreate*` function. + - Check the result of this function (for `VK_SUCCESS`) to make sure the creation succeeded. Handle potential failure in a way consistent with the surrounding code of the project you develop. + - If succeeded, it returns via the last parameter a newly created `Vma*` object handle. You receive ownership of it and you are responsible for destroying it when no longer needed! +3. Use the object, passing it to other functions as needed, like `vmaGetAllocationInfo`, `vmaGetAllocationMemoryProperties`. + - DO NOT EVER access members of such object directly, like `allocation->m_Size`, `allocation->m_MemoryTypeIndex`, even if it would compile and work! This would be a violation of the library interface and may not compile in future versions of VMA. Treat them as opaque handles and only pass them as arguments to the library functions to read their parameters or manipulate them. +4. When no longer needed, destroy the object using the corresponding function `vmaDestroy*`. + - Before destroying the object, make sure it is no longer used or needed, on both CPU and GPU. You need to be sure GPU finished executing Vulkan commands that may use the buffer/image/memory, e.g. by waiting on the appropriate `VkFence`. + +Working with images follows the same workflow as in the example shown above for buffers, just use `vmaCreateImage`, `vmaDestroyImage`, and other image-related rather than buffer-related structures and functions. + +# Allocation parameters + +When filling `VmaAllocationCreateInfo` members, using `allocCreateInfo.usage = VMA_MEMORY_USAGE_AUTO` is recommended in most cases, as it makes VMA automatically choose the best memory type among those available on the current GPU and compatible with the buffer/image created. + +When the memory is going to be mapped and one of the `VMA_MEMORY_USAGE_AUTO*` values is used, you need to also set `allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT` or `allocCreateInfo.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT`. + +- Use `VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT` when you only write to the mapped memory (sequential number-by-number or as a destination of `memcpy`), e.g. when using a buffer intended for uploading data from CPU to GPU. + - The memory assigned to such allocation may be uncached and write-combined, which means any reads and scattered accesses may be extremely slow! DO NOT EVER read from such mapped pointer, even implicitly, e.g. when doing `pMappedPointer[i] += x`. +- Use `VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT` when you need to read from the mapped memory, e.g. when doing a readback of some data downloaded from GPU to CPU. diff --git a/CHANGELOG.md b/CHANGELOG.md index 4278f455..3775949e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # 3.4.0 (2026-??-??) +- Added file `AGENTS.md` for agentic AI. - Added member `VmaAllocationCreateInfo::minAlignment` (#523). - Remember to always fully initialize structures with zeros and don't rely on their specific `sizeof` to ensure backward compatibility! - Function `vmaCreateBufferWithAlignment` is now deprecated. From 5d0efb50ae03e682d187b3ce9bd36461a7168fcb Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 14:03:04 +0200 Subject: [PATCH 09/18] Fixed sample app crashing when window is minimized --- src/VulkanSample.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index d1ed500d..3edd48e9 100644 --- a/src/VulkanSample.cpp +++ b/src/VulkanSample.cpp @@ -2420,8 +2420,22 @@ static void PrintAllocatorStats() #endif } +static bool IsWindowMinimizedOrZeroSized() +{ + if((g_hWnd == NULL) || IsIconic(g_hWnd)) + return true; + + RECT clientRect = {}; + GetClientRect(g_hWnd, &clientRect); + return clientRect.right <= clientRect.left + || clientRect.bottom <= clientRect.top; +} + static void RecreateSwapChain() { + if(IsWindowMinimizedOrZeroSized()) + return; + vkDeviceWaitIdle(g_hDevice); DestroySwapchain(false); CreateSwapchain(); @@ -2738,10 +2752,10 @@ int MainWindow() TranslateMessage(&msg); DispatchMessage(&msg); } + else if(IsWindowMinimizedOrZeroSized()) + Sleep(25); else - { DrawFrame(); - } } return (int)msg.wParam;; From 6e4dd3da8a331c77d30facff7898606412dbe52f Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 14:31:36 +0200 Subject: [PATCH 10/18] Fixed a bug in buffer-image granularity handling Hopefully fixes https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/issues/517 --- CHANGELOG.md | 1 + include/vk_mem_alloc.h | 137 +++++++++++++++++++++++++---------------- 2 files changed, 85 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3775949e..0db0db59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Added `VMA_VERSION` macro with library version number (#507). - Improvements in the algorithm choosing memory type when `VMA_MEMORY_USAGE_AUTO*` is used (#520). - Fixed compatibility with C++20 modules on Clang 21 and GCC15 (#513, #514). +- Fixed a bug in buffer-image granularity handling (#517). - Fixed race condition in defragmentation (#529, #313). - Other fixes and improvements, including compatibility with various platforms and compilers, improvements in documentation, sample application, and tests. diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index e97cbdab..48f204f3 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -3137,7 +3137,7 @@ remove them if not needed. */ #if !defined(VMA_CONFIGURATION_USER_INCLUDES_H) #include // for assert - #include // for min, max, swap + #include // for min, max, swap, sort #include #else #include VMA_CONFIGURATION_USER_INCLUDES_H @@ -7178,6 +7178,14 @@ void VmaBlockMetadata::PrintDetailedMap_End(class VmaJsonWriter& json) #ifndef _VMA_BLOCK_BUFFER_IMAGE_GRANULARITY // Before deleting object of this class remember to call 'Destroy()' +/* +Tracks block occupancy at VkPhysicalDeviceLimits::bufferImageGranularity page boundaries. +For each granularity-sized page in a memory block, m_RegionInfo stores: +- allocCount: how many allocations touch this page as their first or last page. +- allocType: the remembered VmaSuballocationType for that page while allocCount > 0. +Only boundary pages are tracked because buffer-image granularity conflicts matter when +adjacent allocations share the same granularity page at the start or end of an allocation. +*/ class VmaBlockBufferImageGranularity final { public: @@ -7200,6 +7208,21 @@ class VmaBlockBufferImageGranularity final VkDeviceSize& inOutAllocSize, VkDeviceSize& inOutAllocAlignment) const; + /* + Checks whether an allocation placed in a free block would conflict with existing + allocations due to buffer-image granularity requirements and, if needed, aligns the + allocation start to the next granularity page. + + Parameters: + - inOutAllocOffset: candidate allocation offset inside the block; may be increased. + - allocSize: size of the allocation being placed. + - blockOffset: start offset of the free block being considered. + - blockSize: size of the free block being considered. + - allocType: VmaSuballocationType of the allocation being placed. + + Returns true when the placement conflicts or no longer fits in the free block after alignment. + Returns false when the placement is valid, possibly after updating inOutAllocOffset. + */ bool CheckConflictAndAlignUp(VkDeviceSize& inOutAllocOffset, VkDeviceSize allocSize, VkDeviceSize blockOffset, @@ -7288,56 +7311,62 @@ bool VmaBlockBufferImageGranularity::CheckConflictAndAlignUp(VkDeviceSize& inOut VkDeviceSize blockSize, VmaSuballocationType allocType) const { - if (IsEnabled()) + if (!IsEnabled()) + return false; + + uint32_t startPage = GetStartPage(inOutAllocOffset); + if (m_RegionInfo[startPage].allocCount > 0 && + VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[startPage].allocType), allocType)) { - uint32_t startPage = GetStartPage(inOutAllocOffset); + inOutAllocOffset = VmaAlignUp(inOutAllocOffset, m_BufferImageGranularity); + if (blockSize < allocSize + inOutAllocOffset - blockOffset) + return true; + startPage = GetStartPage(inOutAllocOffset); if (m_RegionInfo[startPage].allocCount > 0 && VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[startPage].allocType), allocType)) - { - inOutAllocOffset = VmaAlignUp(inOutAllocOffset, m_BufferImageGranularity); - if (blockSize < allocSize + inOutAllocOffset - blockOffset) - return true; - ++startPage; - } - uint32_t endPage = GetEndPage(inOutAllocOffset, allocSize); - if (endPage != startPage && - m_RegionInfo[endPage].allocCount > 0 && - VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[endPage].allocType), allocType)) { return true; } } + uint32_t endPage = GetEndPage(inOutAllocOffset, allocSize); + if (endPage != startPage && + m_RegionInfo[endPage].allocCount > 0 && + VmaIsBufferImageGranularityConflict(static_cast(m_RegionInfo[endPage].allocType), allocType)) + { + return true; + } + return false; } void VmaBlockBufferImageGranularity::AllocPages(uint8_t allocType, VkDeviceSize offset, VkDeviceSize size) { - if (IsEnabled()) - { - uint32_t startPage = GetStartPage(offset); - AllocPage(m_RegionInfo[startPage], allocType); + if (!IsEnabled()) + return; - uint32_t endPage = GetEndPage(offset, size); - if (startPage != endPage) - AllocPage(m_RegionInfo[endPage], allocType); - } + uint32_t startPage = GetStartPage(offset); + AllocPage(m_RegionInfo[startPage], allocType); + + uint32_t endPage = GetEndPage(offset, size); + if (startPage != endPage) + AllocPage(m_RegionInfo[endPage], allocType); } void VmaBlockBufferImageGranularity::FreePages(VkDeviceSize offset, VkDeviceSize size) { - if (IsEnabled()) + if (!IsEnabled()) + return; + + uint32_t startPage = GetStartPage(offset); + --m_RegionInfo[startPage].allocCount; + if (m_RegionInfo[startPage].allocCount == 0) + m_RegionInfo[startPage].allocType = VMA_SUBALLOCATION_TYPE_FREE; + uint32_t endPage = GetEndPage(offset, size); + if (startPage != endPage) { - uint32_t startPage = GetStartPage(offset); - --m_RegionInfo[startPage].allocCount; - if (m_RegionInfo[startPage].allocCount == 0) - m_RegionInfo[startPage].allocType = VMA_SUBALLOCATION_TYPE_FREE; - uint32_t endPage = GetEndPage(offset, size); - if (startPage != endPage) - { - --m_RegionInfo[endPage].allocCount; - if (m_RegionInfo[endPage].allocCount == 0) - m_RegionInfo[endPage].allocType = VMA_SUBALLOCATION_TYPE_FREE; - } + --m_RegionInfo[endPage].allocCount; + if (m_RegionInfo[endPage].allocCount == 0) + m_RegionInfo[endPage].allocType = VMA_SUBALLOCATION_TYPE_FREE; } } @@ -7362,36 +7391,38 @@ VmaBlockBufferImageGranularity::ValidationContext VmaBlockBufferImageGranularity bool VmaBlockBufferImageGranularity::Validate(ValidationContext& ctx, VkDeviceSize offset, VkDeviceSize size) const { - if (IsEnabled()) - { - uint32_t start = GetStartPage(offset); - ++ctx.pageAllocs[start]; - VMA_VALIDATE(m_RegionInfo[start].allocCount > 0); + if (!IsEnabled()) + return true; - uint32_t end = GetEndPage(offset, size); - if (start != end) - { - ++ctx.pageAllocs[end]; - VMA_VALIDATE(m_RegionInfo[end].allocCount > 0); - } + uint32_t start = GetStartPage(offset); + ++ctx.pageAllocs[start]; + VMA_VALIDATE(m_RegionInfo[start].allocCount > 0); + + uint32_t end = GetEndPage(offset, size); + if (start != end) + { + ++ctx.pageAllocs[end]; + VMA_VALIDATE(m_RegionInfo[end].allocCount > 0); } + return true; } bool VmaBlockBufferImageGranularity::FinishValidation(ValidationContext& ctx) const { + if (!IsEnabled()) + return true; + // Check proper page structure - if (IsEnabled()) - { - VMA_ASSERT(ctx.pageAllocs != VMA_NULL && "Validation context not initialized!"); + VMA_ASSERT(ctx.pageAllocs != VMA_NULL && "Validation context not initialized!"); - for (uint32_t page = 0; page < m_RegionCount; ++page) - { - VMA_VALIDATE(ctx.pageAllocs[page] == m_RegionInfo[page].allocCount); - } - vma_delete_array(ctx.allocCallbacks, ctx.pageAllocs, m_RegionCount); - ctx.pageAllocs = VMA_NULL; + for (uint32_t page = 0; page < m_RegionCount; ++page) + { + VMA_VALIDATE(ctx.pageAllocs[page] == m_RegionInfo[page].allocCount); } + vma_delete_array(ctx.allocCallbacks, ctx.pageAllocs, m_RegionCount); + ctx.pageAllocs = VMA_NULL; + return true; } From 4f687952b8c7aa250dfc25f78e269b9834872458 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 17:36:36 +0200 Subject: [PATCH 11/18] Fixed synchronization issue of m_pMappedData Fixes https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator/issues/525 Also added VMA_ATOMIC_BOOL macro. --- include/vk_mem_alloc.h | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index 48f204f3..171a7afe 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -3454,6 +3454,11 @@ If providing your own implementation, you need to implement a subset of std::ato #define VMA_ATOMIC_UINT64 std::atomic #endif +#ifndef VMA_ATOMIC_BOOL + #include + #define VMA_ATOMIC_BOOL std::atomic +#endif + #ifndef VMA_DEBUG_ALWAYS_DEDICATED_MEMORY /** Every allocation will have its own memory block. @@ -6561,6 +6566,7 @@ class VmaDeviceMemoryBlock uint32_t GetMemoryTypeIndex() const { return m_MemoryTypeIndex; } uint32_t GetId() const { return m_Id; } void* GetMappedData() const { return m_pMappedData; } + bool IsMapped() const { return m_IsMapped.load(); } uint32_t GetMapRefCount() const { return m_MapCount; } // Call when allocation/free was made from m_pMetadata. @@ -6608,12 +6614,18 @@ class VmaDeviceMemoryBlock /* Protects access to m_hMemory so it is not used by multiple threads simultaneously, e.g. vkMapMemory, vkBindBufferMemory. Also protects m_MapCount, m_pMappedData. + m_IsMapped mirrors whether m_pMappedData is non-null and can be read without this mutex in allocation heuristics. Allocations, deallocations, any change in m_pMetadata is protected by parent's VmaBlockVector::m_Mutex. */ VMA_MUTEX m_MapAndBindMutex; VmaMappingHysteresis m_MappingHysteresis; uint32_t m_MapCount; void* m_pMappedData; + /* + Mirrors `m_pMappedData != VMA_NULL` for allocation heuristics that only need mapped/unmapped state. + This is atomic so allocation scans don't race with Map/Unmap while the actual mapped pointer remains protected by m_MapAndBindMutex. + */ + VMA_ATOMIC_BOOL m_IsMapped; VmaWin32Handle m_Handle; }; @@ -10964,7 +10976,8 @@ VmaDeviceMemoryBlock::VmaDeviceMemoryBlock(VmaAllocator hAllocator) m_Id(0), m_hMemory(VK_NULL_HANDLE), m_MapCount(0), - m_pMappedData(VMA_NULL){} + m_pMappedData(VMA_NULL), + m_IsMapped(false){} VmaDeviceMemoryBlock::~VmaDeviceMemoryBlock() { @@ -11020,6 +11033,8 @@ void VmaDeviceMemoryBlock::Destroy(VmaAllocator allocator) VMA_ASSERT_LEAK(m_hMemory != VK_NULL_HANDLE); allocator->FreeVulkanMemory(m_MemoryTypeIndex, m_pMetadata->GetSize(), m_hMemory); m_hMemory = VK_NULL_HANDLE; + m_pMappedData = VMA_NULL; + m_IsMapped.store(false); vma_delete(allocator, m_pMetadata); m_pMetadata = VMA_NULL; @@ -11040,6 +11055,7 @@ void VmaDeviceMemoryBlock::PostFree(VmaAllocator hAllocator) if (m_MapCount == 0) { m_pMappedData = VMA_NULL; + m_IsMapped.store(false); (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory); } } @@ -11100,6 +11116,7 @@ VkResult VmaDeviceMemoryBlock::Map(VmaAllocator hAllocator, uint32_t count, void if (result == VK_SUCCESS) { VMA_ASSERT(m_pMappedData != VMA_NULL); + m_IsMapped.store(true); m_MappingHysteresis.PostMap(); m_MapCount = count; if (ppData != VMA_NULL) @@ -11125,6 +11142,7 @@ void VmaDeviceMemoryBlock::Unmap(VmaAllocator hAllocator, uint32_t count) if (totalMapCount == 0) { m_pMappedData = VMA_NULL; + m_IsMapped.store(false); (*hAllocator->GetVulkanFunctions().vkUnmapMemory)(hAllocator->m_hDevice, m_hMemory); } m_MappingHysteresis.PostUnmap(); @@ -11786,7 +11804,7 @@ VkResult VmaBlockVector::AllocatePage( { VmaDeviceMemoryBlock* const pCurrBlock = m_Blocks[blockIndex]; VMA_ASSERT(pCurrBlock); - const bool isBlockMapped = pCurrBlock->GetMappedData() != VMA_NULL; + const bool isBlockMapped = pCurrBlock->IsMapped(); if((mappingI == 0) == (isMappingAllowed == isBlockMapped)) { VkResult res = AllocateFromBlock( From 47172630a0dc9ad1505d8a9e21342574016e41c7 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 17:38:24 +0200 Subject: [PATCH 12/18] Missing update to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0db0db59..e7a64f6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - Improvements in the algorithm choosing memory type when `VMA_MEMORY_USAGE_AUTO*` is used (#520). - Fixed compatibility with C++20 modules on Clang 21 and GCC15 (#513, #514). - Fixed a bug in buffer-image granularity handling (#517). -- Fixed race condition in defragmentation (#529, #313). +- Fixed race conditions in defragmentation (#529, #313) and other places (#525). - Other fixes and improvements, including compatibility with various platforms and compilers, improvements in documentation, sample application, and tests. # 3.3.0 (2025-05-12) From 68ad4f29b06a5b75cb72df48810bcb368c5f2cbb Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 17:42:17 +0200 Subject: [PATCH 13/18] Updated Vulkan SDK version in windows.yaml to 1.4.350.0 In hope to fix the CI build on Windows. --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index d985da33..33159829 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -6,7 +6,7 @@ on: types: [opened, synchronize, reopened] env: - VMA_VULKAN_VERSION: "1.3.283.0" + VMA_VULKAN_VERSION: "1.4.350.0" VMA_VULKAN_SDK_PATH: ${{ github.workspace }}/vulkan_sdk jobs: From d48eed9cd9d805e68a0d756e937236e2f61d47be Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 17:45:25 +0200 Subject: [PATCH 14/18] Updated windows.yaml in hope to fix the CI build on Windows (2) --- .github/workflows/windows.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 33159829..3bdf33b7 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -48,7 +48,7 @@ jobs: run: | if (-Not (Test-Path ${{ env.VMA_VULKAN_SDK_PATH }})) { Write-Host "Vulkan SDK not found in cache. Downloading..." - curl -LS -o vulkansdk.exe https://sdk.lunarg.com/sdk/download/${{ env.VMA_VULKAN_VERSION }}/windows/VulkanSDK-${{ env.VMA_VULKAN_VERSION }}-Installer.exe + curl -LS -o vulkansdk.exe https://sdk.lunarg.com/sdk/download/${{ env.VMA_VULKAN_VERSION }}/windows/vulkansdk-windows-X64-${{ env.VMA_VULKAN_VERSION }}.exe 7z x vulkansdk.exe -o"${{ env.VMA_VULKAN_SDK_PATH }}" } else { Write-Host "Using cached Vulkan SDK" From 424aa00856664d353ea277dd620dfcc73db4e2e6 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 17:57:00 +0200 Subject: [PATCH 15/18] Updates in hope to fix the CI build on Windows (3) --- .github/workflows/windows.yaml | 16 ++++++---------- src/Shaders/CompileShaders.bat | 8 ++++---- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/.github/workflows/windows.yaml b/.github/workflows/windows.yaml index 3bdf33b7..29bb8aca 100644 --- a/.github/workflows/windows.yaml +++ b/.github/workflows/windows.yaml @@ -40,26 +40,22 @@ jobs: uses: actions/cache@v4 with: path: ${{ env.VMA_VULKAN_SDK_PATH }} - key: vulkan-sdk-${{ env.VMA_VULKAN_VERSION }} + key: vulkan-sdk-${{ env.VMA_VULKAN_VERSION }}-installed - - name: Download Vulkan SDK + - name: Install Vulkan SDK if: steps.cache-vulkan.outputs.cache-hit != 'true' shell: pwsh run: | - if (-Not (Test-Path ${{ env.VMA_VULKAN_SDK_PATH }})) { - Write-Host "Vulkan SDK not found in cache. Downloading..." - curl -LS -o vulkansdk.exe https://sdk.lunarg.com/sdk/download/${{ env.VMA_VULKAN_VERSION }}/windows/vulkansdk-windows-X64-${{ env.VMA_VULKAN_VERSION }}.exe - 7z x vulkansdk.exe -o"${{ env.VMA_VULKAN_SDK_PATH }}" - } else { - Write-Host "Using cached Vulkan SDK" - } + Write-Host "Vulkan SDK not found in cache. Installing..." + curl -LS -o vulkansdk.exe https://sdk.lunarg.com/sdk/download/${{ env.VMA_VULKAN_VERSION }}/windows/vulkansdk-windows-X64-${{ env.VMA_VULKAN_VERSION }}.exe + .\vulkansdk.exe --root "${{ env.VMA_VULKAN_SDK_PATH }}" --accept-licenses --default-answer --confirm-command install copy_only=1 - name: Configure CMake shell: pwsh run: | $env:CC="${{ matrix.config.cc }}" $env:CXX="${{ matrix.config.cxx }}" - $env:Path += ";${{ env.VMA_VULKAN_SDK_PATH }}\;${{ env.VMA_VULKAN_SDK_PATH }}\Bin\" + $env:Path += ";${{ env.VMA_VULKAN_SDK_PATH }}\Bin\" $env:VULKAN_SDK="${{ env.VMA_VULKAN_SDK_PATH }}" cmake . ` -Bbuild ` diff --git a/src/Shaders/CompileShaders.bat b/src/Shaders/CompileShaders.bat index 5d9d8150..0e0370e3 100644 --- a/src/Shaders/CompileShaders.bat +++ b/src/Shaders/CompileShaders.bat @@ -1,4 +1,4 @@ -%VULKAN_SDK%/Bin32/glslangValidator.exe -V -o ../../bin/Shader.vert.spv Shader.vert -%VULKAN_SDK%/Bin32/glslangValidator.exe -V -o ../../bin/Shader.frag.spv Shader.frag -%VULKAN_SDK%/Bin32/glslangValidator.exe -V -o ../../bin/SparseBindingTest.comp.spv SparseBindingTest.comp -pause +%VULKAN_SDK%/Bin/glslangValidator.exe -V -o ../../bin/Shader.vert.spv Shader.vert +%VULKAN_SDK%/Bin/glslangValidator.exe -V -o ../../bin/Shader.frag.spv Shader.frag +%VULKAN_SDK%/Bin/glslangValidator.exe -V -o ../../bin/SparseBindingTest.comp.spv SparseBindingTest.comp +pause From 07502a090aee47067288bcd0a7d6fcef2c2778c9 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Fri, 22 May 2026 18:28:44 +0200 Subject: [PATCH 16/18] Fixed synchronization in the sample app --- src/VulkanSample.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/VulkanSample.cpp b/src/VulkanSample.cpp index 3edd48e9..564e59d4 100644 --- a/src/VulkanSample.cpp +++ b/src/VulkanSample.cpp @@ -96,7 +96,6 @@ static VkSemaphore g_hImageAvailableSemaphores[COMMAND_BUFFER_COUNT]; // Notice we need as many semaphores as there are swapchain images. static std::vector g_hRenderFinishedSemaphores; static uint32_t g_SwapchainImageCount = 0; -static uint32_t g_SwapchainImageIndex = 0; static uint32_t g_GraphicsQueueFamilyIndex = UINT_MAX; static uint32_t g_PresentQueueFamilyIndex = UINT_MAX; static uint32_t g_SparseBindingQueueFamilyIndex = UINT_MAX; @@ -2540,7 +2539,7 @@ static void DrawFrame() VkSemaphore submitWaitSemaphores[] = { imageAvailableSemaphore }; VkPipelineStageFlags submitWaitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; - VkSemaphore submitSignalSemaphores[] = { g_hRenderFinishedSemaphores.at(g_SwapchainImageIndex)}; + VkSemaphore submitSignalSemaphores[] = { g_hRenderFinishedSemaphores.at(imageIndex) }; VkSubmitInfo submitInfo = { VK_STRUCTURE_TYPE_SUBMIT_INFO }; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = submitWaitSemaphores; @@ -2551,7 +2550,7 @@ static void DrawFrame() submitInfo.pSignalSemaphores = submitSignalSemaphores; ERR_GUARD_VULKAN( vkQueueSubmit(g_hGraphicsQueue, 1, &submitInfo, hCommandBufferExecutedFence) ); - VkSemaphore presentWaitSemaphores[] = { g_hRenderFinishedSemaphores.at(g_SwapchainImageIndex) }; + VkSemaphore presentWaitSemaphores[] = { g_hRenderFinishedSemaphores.at(imageIndex) }; VkSwapchainKHR swapchains[] = { g_hSwapchain }; VkPresentInfoKHR presentInfo = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR }; @@ -2569,10 +2568,6 @@ static void DrawFrame() else ERR_GUARD_VULKAN(res); - g_SwapchainImageIndex++; - if (g_SwapchainImageIndex >= g_SwapchainImageCount) { - g_SwapchainImageIndex = 0; - } } static void HandlePossibleSizeChange() From 280881cb3324dd40425717c03943c882d971d998 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Thu, 28 May 2026 13:51:25 +0200 Subject: [PATCH 17/18] Added support for macro VMA_VULKAN_HEADERS_ALREADY_INCLUDED --- include/vk_mem_alloc.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/include/vk_mem_alloc.h b/include/vk_mem_alloc.h index 171a7afe..d09d613b 100644 --- a/include/vk_mem_alloc.h +++ b/include/vk_mem_alloc.h @@ -129,8 +129,10 @@ See documentation chapter: \ref statistics. extern "C" { #endif -#if !defined(VULKAN_H_) -#include +#ifndef VMA_VULKAN_HEADERS_ALREADY_INCLUDED + #if !defined(VULKAN_H_) + #include + #endif #endif #define VMA_VERSION (VK_MAKE_VERSION(3, 4, 0)) From 9d548ddc1c5b250646d33ee0d98bcc7fa1c06ea2 Mon Sep 17 00:00:00 2001 From: sawickiap Date: Thu, 28 May 2026 13:54:25 +0200 Subject: [PATCH 18/18] Updated CHANGELOG.md regarding VMA_VULKAN_HEADERS_ALREADY_INCLUDED --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7a64f6a..63643f62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,9 @@ - Improvements for external memory export & import (#503): - Added functions `vmaCreateDedicatedBuffer`, `vmaCreateDedicatedImage`, `vmaAllocateDedicatedMemory` offering extra parameter `void* pMemoryAllocateNext`. - Added function `vmaGetMemoryWin32Handle2` offering extra parameter `VkExternalMemoryHandleTypeFlagBits handleType`. -- Added member `VmaVulkanFunctions::vkGetPhysicalDeviceProperties2KHR` and macro `VMA_GET_PHYSICAL_DEVICE_PROPERTIES2` to fix validation layer warnings about the usage of legacy commands on Vulkan ≥ 1.1 (#530, #531). +- Added member `VmaVulkanFunctions::vkGetPhysicalDeviceProperties2KHR` and macro `VMA_GET_PHYSICAL_DEVICE_PROPERTIES2` to fix validation layer warnings about the usage of legacy commands on Vulkan >= 1.1 (#530, #531). - Added `VMA_VERSION` macro with library version number (#507). +- Added support for `VMA_VULKAN_HEADERS_ALREADY_INCLUDED`. When defined, VMA does not include ``. - Improvements in the algorithm choosing memory type when `VMA_MEMORY_USAGE_AUTO*` is used (#520). - Fixed compatibility with C++20 modules on Clang 21 and GCC15 (#513, #514). - Fixed a bug in buffer-image granularity handling (#517).