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/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; diff --git a/src/vsg/vk/MemoryBufferPools.cpp b/src/vsg/vk/MemoryBufferPools.cpp index 701587129..a8968d546 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,8 +204,13 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR if (deviceMemory) { - reservedSlot = deviceMemory->reserve(totalSize); - // if (!deviceMemory->full()) + reservedSlot = deviceMemory->reserve(totalSize, alignment); + + // 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); @@ -336,6 +354,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(..)");