From 85af36677049302669fc18bd94437a829e7647be Mon Sep 17 00:00:00 2001 From: Roland Hill Date: Fri, 14 Aug 2026 12:50:54 +1000 Subject: [PATCH 1/4] Pool DeviceMemory allocations in MemoryBufferPools::reserveMemory --- src/vsg/vk/MemoryBufferPools.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/src/vsg/vk/MemoryBufferPools.cpp b/src/vsg/vk/MemoryBufferPools.cpp index 701587129..b75f28500 100644 --- a/src/vsg/vk/MemoryBufferPools.cpp +++ b/src/vsg/vk/MemoryBufferPools.cpp @@ -82,7 +82,7 @@ ref_ptr MemoryBufferPools::reserveBuffer(VkDeviceSize totalSize, VkD std::scoped_lock lock(_mutex); for (auto& bufferFromPool : bufferPools) { - if (bufferFromPool->usage == bufferUsageFlags && bufferFromPool->size >= totalSize) + if (bufferFromPool->usage == bufferUsageFlags && bufferFromPool->size >= totalSize && bufferFromPool->maximumAvailableSpace() >= totalSize) { MemorySlots::OptionalOffset reservedBufferSlot = bufferFromPool->reserve(totalSize, alignment); if (reservedBufferSlot.first) @@ -151,12 +151,20 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR ref_ptr deviceMemory; MemorySlots::OptionalOffset reservedSlot(false, 0); + // Pools can hold linear buffers and optimal tiling images side by side, so every slot is aligned + // to bufferImageGranularity to satisfy the Vulkan granularity rule for adjacent resources. + const VkDeviceSize granularity = device->getPhysicalDevice()->getProperties().limits.bufferImageGranularity; + const VkDeviceSize alignment = std::max(memRequirements.alignment, granularity); + for (auto& memoryPool : memoryPools) { - if (((memoryPool->getMemoryRequirements().memoryTypeBits & memRequirements.memoryTypeBits) == memRequirements.memoryTypeBits) && + // memoryTypeBits and property flags equality guarantees the pool's chosen memory type index + // is also valid for the new resource. + if (memoryPool->getMemoryRequirements().memoryTypeBits == memRequirements.memoryTypeBits && + memoryPool->getMemoryPropertyFlags() == memoryPropertiesFlags && memoryPool->maximumAvailableSpace() >= totalSize) { - reservedSlot = memoryPool->reserve(totalSize, memRequirements.alignment); + reservedSlot = memoryPool->reserve(totalSize, alignment); if (reservedSlot.first) { deviceMemory = memoryPool; @@ -179,10 +187,15 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR if (deviceSize <= availableMemory) { + // Allocate a pool sized block so subsequent resources suballocate from it rather than + // each getting a dedicated vkAllocateMemory. Explicit dedicated allocations + // (pNextAllocInfo) must keep their exact size. + VkMemoryRequirements poolRequirements = memRequirements; + if (!pNextAllocInfo) poolRequirements.size = deviceSize; try { - deviceMemory = vsg::DeviceMemory::create(device, memRequirements, memoryPropertiesFlags, pNextAllocInfo); + deviceMemory = vsg::DeviceMemory::create(device, poolRequirements, memoryPropertiesFlags, pNextAllocInfo); } catch (...) { @@ -191,7 +204,7 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR if (deviceMemory) { - reservedSlot = deviceMemory->reserve(totalSize); + reservedSlot = deviceMemory->reserve(totalSize, alignment); // if (!deviceMemory->full()) { //debug(" inserting DeviceMemory into memoryPool ", deviceMemory.get()); From c63e09b03204d046bd4e2ce5dd88c604658d5c19 Mon Sep 17 00:00:00 2001 From: Roland Hill Date: Fri, 14 Aug 2026 12:57:35 +1000 Subject: [PATCH 2/4] Add MemoryBufferPools::releaseUnusedPools() method --- include/vsg/vk/MemoryBufferPools.h | 5 +++++ src/vsg/vk/MemoryBufferPools.cpp | 36 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/include/vsg/vk/MemoryBufferPools.h b/include/vsg/vk/MemoryBufferPools.h index 7487cfb0f..a2136b70a 100644 --- a/include/vsg/vk/MemoryBufferPools.h +++ b/include/vsg/vk/MemoryBufferPools.h @@ -58,6 +58,11 @@ namespace vsg VkResult reserve(const BufferInfoList& bufferInfoList, VkDeviceSize alignment, VkBufferUsageFlags bufferUsageFlags, VkSharingMode sharingMode, VkMemoryPropertyFlags memoryProperties); VkResult reserve(ResourceRequirements& requirements); + /// Release wholly unused Buffer and DeviceMemory pool entries, returning their memory to the driver. + /// Pools normally retain freed capacity for reuse, so call this on application memory pressure rather than routinely. + /// Returns the number of bytes of device memory released. + VkDeviceSize releaseUnusedPools(); + void report(LogOutput& out) const; protected: diff --git a/src/vsg/vk/MemoryBufferPools.cpp b/src/vsg/vk/MemoryBufferPools.cpp index b75f28500..1a43dfb58 100644 --- a/src/vsg/vk/MemoryBufferPools.cpp +++ b/src/vsg/vk/MemoryBufferPools.cpp @@ -349,6 +349,42 @@ VkResult MemoryBufferPools::reserve(ResourceRequirements& requirements) } } +VkDeviceSize MemoryBufferPools::releaseUnusedPools() +{ + std::scoped_lock lock(_mutex); + + // release empty Buffers first so their DeviceMemory slots are returned before the memory pools are checked + for (auto itr = bufferPools.begin(); itr != bufferPools.end();) + { + auto& buffer = *itr; + if (buffer->totalReservedSize() == 0 && buffer->referenceCount() == 1) + { + itr = bufferPools.erase(itr); + } + else + { + ++itr; + } + } + + VkDeviceSize totalReleased = 0; + for (auto itr = memoryPools.begin(); itr != memoryPools.end();) + { + auto& memoryPool = *itr; + if (memoryPool->totalReservedSize() == 0 && memoryPool->referenceCount() == 1) + { + totalReleased += memoryPool->totalMemorySize(); + itr = memoryPools.erase(itr); + } + else + { + ++itr; + } + } + + return totalReleased; +} + void MemoryBufferPools::report(LogOutput& out) const { out.enter("MemoryBufferPools::report(..)"); From c2a4a020e83f3760ae870b934b84a16d5d13d3a8 Mon Sep 17 00:00:00 2001 From: Roland Hill Date: Mon, 17 Aug 2026 20:24:02 +1000 Subject: [PATCH 3/4] Check reserveMemory returned valid memory --- src/vsg/state/Buffer.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/vsg/state/Buffer.cpp b/src/vsg/state/Buffer.cpp index fa6aa3dce..0f942596f 100644 --- a/src/vsg/state/Buffer.cpp +++ b/src/vsg/state/Buffer.cpp @@ -166,6 +166,11 @@ ref_ptr vsg::createBufferAndMemory(Device* device, VkDeviceSize size, Vk if (deviceMemoryBufferPools) { auto [memory, offset] = deviceMemoryBufferPools->reserveMemory(memRequirements, memoryProperties); + if (!memory) + { + warn("vsg::createBufferAndMemory(.., size = ", size, ", ..) failed to reserve DeviceMemory."); + return {}; + } buffer->bind(memory, offset); return buffer; From 48a9614ce62d3b1490923264a894dfbe5a9c16a4 Mon Sep 17 00:00:00 2001 From: Roland Hill Date: Mon, 17 Aug 2026 20:27:26 +1000 Subject: [PATCH 4/4] Check poolRequirements.size > totalSize --- src/vsg/vk/MemoryBufferPools.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/vsg/vk/MemoryBufferPools.cpp b/src/vsg/vk/MemoryBufferPools.cpp index 1a43dfb58..a8968d546 100644 --- a/src/vsg/vk/MemoryBufferPools.cpp +++ b/src/vsg/vk/MemoryBufferPools.cpp @@ -205,7 +205,12 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR if (deviceMemory) { reservedSlot = deviceMemory->reserve(totalSize, alignment); - // if (!deviceMemory->full()) + + // Only blocks inflated beyond the request have spare capacity worth sharing. An + // exact fit block is wholly consumed by its one resource, so pooling it would just + // keep the memory alive after the resource is destroyed; leaving it out means the + // resource's own reference returns it to the driver. + if (poolRequirements.size > totalSize) { //debug(" inserting DeviceMemory into memoryPool ", deviceMemory.get()); memoryPools.push_back(deviceMemory);