Commit e366e2c3 by Shahbaz Youssefi Committed by Commit Bot

Vulkan: Keep dynamic buffer's free list trimmed

ContextVk's staging buffer never gets a chance to free its free buffer list. During application load time, a large amount of memory may be allocated from this buffer to stage texture updates and they would remain throughout the life of the application. This change ensures that the free buffer list doesn't grow unbounded. In the Manhattan trace, this saves >1GB of memory on Linux. There are now three policies for vk::DynamicBuffer: - Always reuse buffers: This is useful for dynamic buffers that make frequent small allocations, such as default uniforms, driver uniforms, default vertex attributes and UBO updates. - Never reuse buffers: This is for situations where the buffer is unlikely to be used after some initial usage, such as texture data upload or vertex format emulation (as the conversion result is cached, so it's never redone). - Limited reuse of buffers: For the staging buffer in the context which is shared by all immutable texture data uploads, it's useful to keep a limited number of buffers (1 in this change) to support future texture streaming while allowing a large number of buffers allocated in a burst to be discarded. Bug: angleproject:5690 Change-Id: Ic39ce61e6beb3165dbce4b668e1d3984a2b35986 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2725499 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com> Reviewed-by: 's avatarCharlie Lao <cclao@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent b23644d3
...@@ -121,7 +121,8 @@ ConversionBuffer::ConversionBuffer(RendererVk *renderer, ...@@ -121,7 +121,8 @@ ConversionBuffer::ConversionBuffer(RendererVk *renderer,
bool hostVisible) bool hostVisible)
: dirty(true), lastAllocationOffset(0) : dirty(true), lastAllocationOffset(0)
{ {
data.init(renderer, usageFlags, alignment, initialSize, hostVisible); data.init(renderer, usageFlags, alignment, initialSize, hostVisible,
vk::DynamicBufferPolicy::OneShotUse);
} }
ConversionBuffer::~ConversionBuffer() = default; ConversionBuffer::~ConversionBuffer() = default;
...@@ -338,7 +339,8 @@ angle::Result BufferVk::setDataWithMemoryType(const gl::Context *context, ...@@ -338,7 +339,8 @@ angle::Result BufferVk::setDataWithMemoryType(const gl::Context *context,
constexpr size_t kBufferHelperPoolInitialSize = 0; constexpr size_t kBufferHelperPoolInitialSize = 0;
mBufferPool.initWithFlags(contextVk->getRenderer(), usageFlags, kBufferHelperAlignment, mBufferPool.initWithFlags(contextVk->getRenderer(), usageFlags, kBufferHelperAlignment,
kBufferHelperPoolInitialSize, memoryPropertyFlags); kBufferHelperPoolInitialSize, memoryPropertyFlags,
vk::DynamicBufferPolicy::FrequentSmallAllocations);
ANGLE_TRY(acquireBufferHelper(contextVk, size, &mBuffer)); ANGLE_TRY(acquireBufferHelper(contextVk, size, &mBuffer));
......
...@@ -344,7 +344,8 @@ void ContextVk::DriverUniformsDescriptorSet::init(RendererVk *rendererVk) ...@@ -344,7 +344,8 @@ void ContextVk::DriverUniformsDescriptorSet::init(RendererVk *rendererVk)
size_t minAlignment = static_cast<size_t>( size_t minAlignment = static_cast<size_t>(
rendererVk->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment); rendererVk->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment);
dynamicBuffer.init(rendererVk, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, minAlignment, dynamicBuffer.init(rendererVk, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, minAlignment,
kDriverUniformsAllocatorPageSize, true); kDriverUniformsAllocatorPageSize, true,
vk::DynamicBufferPolicy::FrequentSmallAllocations);
descriptorSetCache.clear(); descriptorSetCache.clear();
} }
...@@ -664,7 +665,8 @@ angle::Result ContextVk::initialize() ...@@ -664,7 +665,8 @@ angle::Result ContextVk::initialize()
// Initialize current value/default attribute buffers. // Initialize current value/default attribute buffers.
for (vk::DynamicBuffer &buffer : mDefaultAttribBuffers) for (vk::DynamicBuffer &buffer : mDefaultAttribBuffers)
{ {
buffer.init(mRenderer, kVertexBufferUsage, 1, kDefaultBufferSize, true); buffer.init(mRenderer, kVertexBufferUsage, 1, kDefaultBufferSize, true,
vk::DynamicBufferPolicy::FrequentSmallAllocations);
} }
#if ANGLE_ENABLE_VULKAN_GPU_TRACE_EVENTS #if ANGLE_ENABLE_VULKAN_GPU_TRACE_EVENTS
...@@ -703,7 +705,8 @@ angle::Result ContextVk::initialize() ...@@ -703,7 +705,8 @@ angle::Result ContextVk::initialize()
size_t minAlignment = static_cast<size_t>( size_t minAlignment = static_cast<size_t>(
mRenderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment); mRenderer->getPhysicalDeviceProperties().limits.minUniformBufferOffsetAlignment);
mDefaultUniformStorage.init(mRenderer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, minAlignment, mDefaultUniformStorage.init(mRenderer, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, minAlignment,
mRenderer->getDefaultUniformBufferSize(), true); mRenderer->getDefaultUniformBufferSize(), true,
vk::DynamicBufferPolicy::FrequentSmallAllocations);
// Initialize an "empty" buffer for use with default uniform blocks where there are no uniforms, // Initialize an "empty" buffer for use with default uniform blocks where there are no uniforms,
// or atomic counter buffer array indices that are unused. // or atomic counter buffer array indices that are unused.
...@@ -727,7 +730,7 @@ angle::Result ContextVk::initialize() ...@@ -727,7 +730,7 @@ angle::Result ContextVk::initialize()
static_cast<size_t>(mRenderer->getPhysicalDeviceProperties().limits.minMemoryMapAlignment); static_cast<size_t>(mRenderer->getPhysicalDeviceProperties().limits.minMemoryMapAlignment);
constexpr size_t kStagingBufferSize = 1024u * 1024u; // 1M constexpr size_t kStagingBufferSize = 1024u * 1024u; // 1M
mStagingBuffer.init(mRenderer, kStagingBufferUsageFlags, stagingBufferAlignment, mStagingBuffer.init(mRenderer, kStagingBufferUsageFlags, stagingBufferAlignment,
kStagingBufferSize, true); kStagingBufferSize, true, vk::DynamicBufferPolicy::SporadicTextureUpload);
// Add context into the share group // Add context into the share group
mShareGroupVk->getShareContextSet()->insert(this); mShareGroupVk->getShareContextSet()->insert(this);
......
...@@ -342,7 +342,7 @@ FramebufferVk::FramebufferVk(RendererVk *renderer, ...@@ -342,7 +342,7 @@ FramebufferVk::FramebufferVk(RendererVk *renderer,
mReadOnlyDepthFeedbackLoopMode(false) mReadOnlyDepthFeedbackLoopMode(false)
{ {
mReadPixelBuffer.init(renderer, VK_BUFFER_USAGE_TRANSFER_DST_BIT, kReadPixelsBufferAlignment, mReadPixelBuffer.init(renderer, VK_BUFFER_USAGE_TRANSFER_DST_BIT, kReadPixelsBufferAlignment,
kMinReadPixelsBufferSize, true); kMinReadPixelsBufferSize, true, vk::DynamicBufferPolicy::OneShotUse);
} }
FramebufferVk::~FramebufferVk() = default; FramebufferVk::~FramebufferVk() = default;
......
...@@ -130,16 +130,17 @@ VertexArrayVk::VertexArrayVk(ContextVk *contextVk, const gl::VertexArrayState &s ...@@ -130,16 +130,17 @@ VertexArrayVk::VertexArrayVk(ContextVk *contextVk, const gl::VertexArrayState &s
mCurrentArrayBuffers.fill(&emptyBuffer); mCurrentArrayBuffers.fill(&emptyBuffer);
mDynamicVertexData.init(renderer, vk::kVertexBufferUsageFlags, vk::kVertexBufferAlignment, mDynamicVertexData.init(renderer, vk::kVertexBufferUsageFlags, vk::kVertexBufferAlignment,
kDynamicVertexDataSize, true); kDynamicVertexDataSize, true, vk::DynamicBufferPolicy::OneShotUse);
// We use an alignment of four for index data. This ensures that compute shaders can read index // We use an alignment of four for index data. This ensures that compute shaders can read index
// elements from "uint" aligned addresses. // elements from "uint" aligned addresses.
mDynamicIndexData.init(renderer, vk::kIndexBufferUsageFlags, vk::kIndexBufferAlignment, mDynamicIndexData.init(renderer, vk::kIndexBufferUsageFlags, vk::kIndexBufferAlignment,
kDynamicIndexDataSize, true); kDynamicIndexDataSize, true, vk::DynamicBufferPolicy::OneShotUse);
mTranslatedByteIndexData.init(renderer, vk::kIndexBufferUsageFlags, vk::kIndexBufferAlignment, mTranslatedByteIndexData.init(renderer, vk::kIndexBufferUsageFlags, vk::kIndexBufferAlignment,
kDynamicIndexDataSize, true); kDynamicIndexDataSize, true, vk::DynamicBufferPolicy::OneShotUse);
mTranslatedByteIndirectData.init(renderer, vk::kIndirectBufferUsageFlags, mTranslatedByteIndirectData.init(renderer, vk::kIndirectBufferUsageFlags,
vk::kIndirectBufferAlignment, kDynamicIndirectDataSize, true); vk::kIndirectBufferAlignment, kDynamicIndirectDataSize, true,
vk::DynamicBufferPolicy::OneShotUse);
} }
VertexArrayVk::~VertexArrayVk() {} VertexArrayVk::~VertexArrayVk() {}
......
...@@ -721,6 +721,23 @@ void DestroyBufferList(RendererVk *renderer, BufferHelperPointerVector *buffers) ...@@ -721,6 +721,23 @@ void DestroyBufferList(RendererVk *renderer, BufferHelperPointerVector *buffers)
} }
buffers->clear(); buffers->clear();
} }
bool ShouldReleaseFreeBuffer(const vk::BufferHelper &buffer,
size_t dynamicBufferSize,
DynamicBufferPolicy policy,
size_t freeListSize)
{
constexpr size_t kLimitedFreeListMaxSize = 1;
// If the dynamic buffer was resized we cannot reuse the retained buffer. Additionally,
// only reuse the buffer if specifically requested.
const bool sizeMismatch = buffer.getSize() != dynamicBufferSize;
const bool releaseByPolicy = policy == DynamicBufferPolicy::OneShotUse ||
(policy == DynamicBufferPolicy::SporadicTextureUpload &&
freeListSize >= kLimitedFreeListMaxSize);
return sizeMismatch || releaseByPolicy;
}
} // anonymous namespace } // anonymous namespace
// This is an arbitrary max. We can change this later if necessary. // This is an arbitrary max. We can change this later if necessary.
...@@ -1666,6 +1683,7 @@ void CommandBufferHelper::growRenderArea(ContextVk *contextVk, const gl::Rectang ...@@ -1666,6 +1683,7 @@ void CommandBufferHelper::growRenderArea(ContextVk *contextVk, const gl::Rectang
DynamicBuffer::DynamicBuffer() DynamicBuffer::DynamicBuffer()
: mUsage(0), : mUsage(0),
mHostVisible(false), mHostVisible(false),
mPolicy(DynamicBufferPolicy::OneShotUse),
mInitialSize(0), mInitialSize(0),
mNextAllocationOffset(0), mNextAllocationOffset(0),
mLastFlushOrInvalidateOffset(0), mLastFlushOrInvalidateOffset(0),
...@@ -1677,6 +1695,7 @@ DynamicBuffer::DynamicBuffer() ...@@ -1677,6 +1695,7 @@ DynamicBuffer::DynamicBuffer()
DynamicBuffer::DynamicBuffer(DynamicBuffer &&other) DynamicBuffer::DynamicBuffer(DynamicBuffer &&other)
: mUsage(other.mUsage), : mUsage(other.mUsage),
mHostVisible(other.mHostVisible), mHostVisible(other.mHostVisible),
mPolicy(other.mPolicy),
mInitialSize(other.mInitialSize), mInitialSize(other.mInitialSize),
mBuffer(std::move(other.mBuffer)), mBuffer(std::move(other.mBuffer)),
mNextAllocationOffset(other.mNextAllocationOffset), mNextAllocationOffset(other.mNextAllocationOffset),
...@@ -1692,23 +1711,26 @@ void DynamicBuffer::init(RendererVk *renderer, ...@@ -1692,23 +1711,26 @@ void DynamicBuffer::init(RendererVk *renderer,
VkBufferUsageFlags usage, VkBufferUsageFlags usage,
size_t alignment, size_t alignment,
size_t initialSize, size_t initialSize,
bool hostVisible) bool hostVisible,
DynamicBufferPolicy policy)
{ {
VkMemoryPropertyFlags memoryPropertyFlags = VkMemoryPropertyFlags memoryPropertyFlags =
(hostVisible) ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; (hostVisible) ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
initWithFlags(renderer, usage, alignment, initialSize, memoryPropertyFlags); initWithFlags(renderer, usage, alignment, initialSize, memoryPropertyFlags, policy);
} }
void DynamicBuffer::initWithFlags(RendererVk *renderer, void DynamicBuffer::initWithFlags(RendererVk *renderer,
VkBufferUsageFlags usage, VkBufferUsageFlags usage,
size_t alignment, size_t alignment,
size_t initialSize, size_t initialSize,
VkMemoryPropertyFlags memoryPropertyFlags) VkMemoryPropertyFlags memoryPropertyFlags,
DynamicBufferPolicy policy)
{ {
mUsage = usage; mUsage = usage;
mHostVisible = ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0); mHostVisible = ((memoryPropertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) != 0);
mMemoryPropertyFlags = memoryPropertyFlags; mMemoryPropertyFlags = memoryPropertyFlags;
mPolicy = policy;
// Check that we haven't overriden the initial size of the buffer in setMinimumSizeForTesting. // Check that we haven't overriden the initial size of the buffer in setMinimumSizeForTesting.
if (mInitialSize == 0) if (mInitialSize == 0)
...@@ -1817,11 +1839,7 @@ angle::Result DynamicBuffer::allocateWithAlignment(ContextVk *contextVk, ...@@ -1817,11 +1839,7 @@ angle::Result DynamicBuffer::allocateWithAlignment(ContextVk *contextVk,
mSize = std::max(mInitialSize, sizeToAllocate); mSize = std::max(mInitialSize, sizeToAllocate);
// Clear the free list since the free buffers are now too small. // Clear the free list since the free buffers are now too small.
for (std::unique_ptr<BufferHelper> &toFree : mBufferFreeList) ReleaseBufferListToRenderer(contextVk->getRenderer(), &mBufferFreeList);
{
toFree->release(contextVk->getRenderer());
}
mBufferFreeList.clear();
} }
// The front of the free list should be the oldest. Thus if it is in use the rest of the // The front of the free list should be the oldest. Thus if it is in use the rest of the
...@@ -1922,8 +1940,7 @@ void DynamicBuffer::releaseInFlightBuffersToResourceUseList(ContextVk *contextVk ...@@ -1922,8 +1940,7 @@ void DynamicBuffer::releaseInFlightBuffersToResourceUseList(ContextVk *contextVk
{ {
bufferHelper->retain(resourceUseList); bufferHelper->retain(resourceUseList);
// If the dynamic buffer was resized we cannot reuse the retained buffer. if (ShouldReleaseFreeBuffer(*bufferHelper, mSize, mPolicy, mBufferFreeList.size()))
if (bufferHelper->getSize() < mSize)
{ {
bufferHelper->release(contextVk->getRenderer()); bufferHelper->release(contextVk->getRenderer());
} }
...@@ -1940,8 +1957,7 @@ void DynamicBuffer::releaseInFlightBuffers(ContextVk *contextVk) ...@@ -1940,8 +1957,7 @@ void DynamicBuffer::releaseInFlightBuffers(ContextVk *contextVk)
{ {
for (std::unique_ptr<BufferHelper> &toRelease : mInFlightBuffers) for (std::unique_ptr<BufferHelper> &toRelease : mInFlightBuffers)
{ {
// If the dynamic buffer was resized we cannot reuse the retained buffer. if (ShouldReleaseFreeBuffer(*toRelease, mSize, mPolicy, mBufferFreeList.size()))
if (toRelease->getSize() < mSize)
{ {
toRelease->release(contextVk->getRenderer()); toRelease->release(contextVk->getRenderer());
} }
...@@ -2770,9 +2786,11 @@ LineLoopHelper::LineLoopHelper(RendererVk *renderer) ...@@ -2770,9 +2786,11 @@ LineLoopHelper::LineLoopHelper(RendererVk *renderer)
// sum of offset and the address of the range of VkDeviceMemory object that is backing buffer, // sum of offset and the address of the range of VkDeviceMemory object that is backing buffer,
// must be a multiple of the type indicated by indexType'. // must be a multiple of the type indicated by indexType'.
mDynamicIndexBuffer.init(renderer, kLineLoopDynamicBufferUsage, sizeof(uint32_t), mDynamicIndexBuffer.init(renderer, kLineLoopDynamicBufferUsage, sizeof(uint32_t),
kLineLoopDynamicBufferInitialSize, true); kLineLoopDynamicBufferInitialSize, true,
DynamicBufferPolicy::OneShotUse);
mDynamicIndirectBuffer.init(renderer, kLineLoopDynamicIndirectBufferUsage, sizeof(uint32_t), mDynamicIndirectBuffer.init(renderer, kLineLoopDynamicIndirectBufferUsage, sizeof(uint32_t),
kLineLoopDynamicIndirectBufferInitialSize, true); kLineLoopDynamicIndirectBufferInitialSize, true,
DynamicBufferPolicy::OneShotUse);
} }
LineLoopHelper::~LineLoopHelper() = default; LineLoopHelper::~LineLoopHelper() = default;
...@@ -3609,7 +3627,8 @@ void ImageHelper::initStagingBuffer(RendererVk *renderer, ...@@ -3609,7 +3627,8 @@ void ImageHelper::initStagingBuffer(RendererVk *renderer,
VkBufferUsageFlags usageFlags, VkBufferUsageFlags usageFlags,
size_t initialSize) size_t initialSize)
{ {
mStagingBuffer.init(renderer, usageFlags, imageCopyBufferAlignment, initialSize, true); mStagingBuffer.init(renderer, usageFlags, imageCopyBufferAlignment, initialSize, true,
DynamicBufferPolicy::OneShotUse);
} }
angle::Result ImageHelper::init(Context *context, angle::Result ImageHelper::init(Context *context,
...@@ -6245,7 +6264,7 @@ angle::Result ImageHelper::readPixelsForGetImage(ContextVk *contextVk, ...@@ -6245,7 +6264,7 @@ angle::Result ImageHelper::readPixelsForGetImage(ContextVk *contextVk,
// Use a temporary staging buffer. Could be optimized. // Use a temporary staging buffer. Could be optimized.
RendererScoped<DynamicBuffer> stagingBuffer(contextVk->getRenderer()); RendererScoped<DynamicBuffer> stagingBuffer(contextVk->getRenderer());
stagingBuffer.get().init(contextVk->getRenderer(), VK_BUFFER_USAGE_TRANSFER_DST_BIT, 1, stagingBuffer.get().init(contextVk->getRenderer(), VK_BUFFER_USAGE_TRANSFER_DST_BIT, 1,
kStagingBufferSize, true); kStagingBufferSize, true, DynamicBufferPolicy::OneShotUse);
if (mExtents.depth > 1) if (mExtents.depth > 1)
{ {
......
...@@ -61,6 +61,19 @@ struct TextureUnit final ...@@ -61,6 +61,19 @@ struct TextureUnit final
class BufferHelper; class BufferHelper;
using BufferHelperPointerVector = std::vector<std::unique_ptr<BufferHelper>>; using BufferHelperPointerVector = std::vector<std::unique_ptr<BufferHelper>>;
enum class DynamicBufferPolicy
{
// Used where future allocations from the dynamic buffer are unlikely, so it's best to free the
// memory when the allocated buffers are no longer in use.
OneShotUse,
// Used where multiple small allocations are made every frame, so it's worth keeping the free
// buffers around to avoid release/reallocation.
FrequentSmallAllocations,
// Used where bursts of allocation happen occasionally, but the steady state may make
// allocations every now and then. In that case, a limited number of buffers are retained.
SporadicTextureUpload,
};
class DynamicBuffer : angle::NonCopyable class DynamicBuffer : angle::NonCopyable
{ {
public: public:
...@@ -73,14 +86,16 @@ class DynamicBuffer : angle::NonCopyable ...@@ -73,14 +86,16 @@ class DynamicBuffer : angle::NonCopyable
VkBufferUsageFlags usage, VkBufferUsageFlags usage,
size_t alignment, size_t alignment,
size_t initialSize, size_t initialSize,
bool hostVisible); bool hostVisible,
DynamicBufferPolicy policy);
// Init that gives the ability to pass in specified memory property flags for the buffer. // Init that gives the ability to pass in specified memory property flags for the buffer.
void initWithFlags(RendererVk *renderer, void initWithFlags(RendererVk *renderer,
VkBufferUsageFlags usage, VkBufferUsageFlags usage,
size_t alignment, size_t alignment,
size_t initialSize, size_t initialSize,
VkMemoryPropertyFlags memoryProperty); VkMemoryPropertyFlags memoryProperty,
DynamicBufferPolicy policy);
// This call will allocate a new region at the end of the current buffer. If it can't find // This call will allocate a new region at the end of the current buffer. If it can't find
// enough space in the current buffer, it returns false. This gives caller a chance to deal with // enough space in the current buffer, it returns false. This gives caller a chance to deal with
...@@ -152,6 +167,7 @@ class DynamicBuffer : angle::NonCopyable ...@@ -152,6 +167,7 @@ class DynamicBuffer : angle::NonCopyable
VkBufferUsageFlags mUsage; VkBufferUsageFlags mUsage;
bool mHostVisible; bool mHostVisible;
DynamicBufferPolicy mPolicy;
size_t mInitialSize; size_t mInitialSize;
std::unique_ptr<BufferHelper> mBuffer; std::unique_ptr<BufferHelper> mBuffer;
uint32_t mNextAllocationOffset; uint32_t mNextAllocationOffset;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment