Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/vsg/vk/MemoryBufferPools.h
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
5 changes: 5 additions & 0 deletions src/vsg/state/Buffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ ref_ptr<Buffer> 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;
Expand Down
66 changes: 60 additions & 6 deletions src/vsg/vk/MemoryBufferPools.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ ref_ptr<BufferInfo> MemoryBufferPools::reserveBuffer(VkDeviceSize totalSize, VkD
std::scoped_lock<std::mutex> 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)
Expand Down Expand Up @@ -151,12 +151,20 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR
ref_ptr<DeviceMemory> 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;
Expand All @@ -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 (...)
{
Expand All @@ -191,8 +204,13 @@ MemoryBufferPools::DeviceMemoryOffset MemoryBufferPools::reserveMemory(VkMemoryR

if (deviceMemory)
{
reservedSlot = deviceMemory->reserve(totalSize);
// if (!deviceMemory->full())
reservedSlot = deviceMemory->reserve(totalSize, alignment);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This in essence just commenting back in the original if (!device->full()) line. This original usage is clearer in intent than the new if (..) check.

I commented out this check to make sure the MemoryBufferPools full keeps track of all DeviceMemory objects, as I found it useful to take memory usage etc. I think if we want to re-enable check before adding it to track memory then having an option in MemoryBufferPools to control this would be appropriate rather than having dualling commits.

// 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);
Expand Down Expand Up @@ -336,6 +354,42 @@ VkResult MemoryBufferPools::reserve(ResourceRequirements& requirements)
}
}

VkDeviceSize MemoryBufferPools::releaseUnusedPools()
{
std::scoped_lock<std::mutex> 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(..)");
Expand Down
Loading