Commit c773ab98 by Jamie Madill Committed by Commit Bot

Vulkan: Recycle dynamic buffer storage.

This adds a free list to the dynamic buffer storage. Buffers are added to the free list when the retained buffers are released. They are taken from the free list when we allocate a new buffer. We only allocate a new buffer in the ring when we run out of free buffers. This reduces the amount of time we spend in allocation for frequent updates. Now that we're recycling buffers inside of DynamicBuffer we also need to be a bit more careful about when we allow ourselves to reuse them. If they're still in use by the GPU we should not try to modify them. Bug: angleproject:3082 Change-Id: Ibee5a7e2fe4a17f4a2f7af6bc6bcce54bdc413c2 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1646548 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarTobin Ehlis <tobine@google.com>
parent cc82c3f0
...@@ -909,7 +909,7 @@ angle::Result ContextVk::synchronizeCpuGpuTime() ...@@ -909,7 +909,7 @@ angle::Result ContextVk::synchronizeCpuGpuTime()
ANGLE_VK_TRY(this, commandBuffer.end()); ANGLE_VK_TRY(this, commandBuffer.end());
// Submit the command buffer // Submit the command buffer
VkSubmitInfo submitInfo = {}; VkSubmitInfo submitInfo = {};
InitializeSubmitInfo(&submitInfo, commandBatch.get(), {}, {}); InitializeSubmitInfo(&submitInfo, commandBatch.get(), {}, {});
ANGLE_TRY(submitFrame(submitInfo, std::move(commandBuffer))); ANGLE_TRY(submitFrame(submitInfo, std::move(commandBuffer)));
...@@ -1933,7 +1933,7 @@ angle::Result ContextVk::handleDirtyDriverUniforms(const gl::Context *context, ...@@ -1933,7 +1933,7 @@ angle::Result ContextVk::handleDirtyDriverUniforms(const gl::Context *context,
vk::CommandBuffer *commandBuffer) vk::CommandBuffer *commandBuffer)
{ {
// Release any previously retained buffers. // Release any previously retained buffers.
mDriverUniformsBuffer.releaseRetainedBuffers(this); mDriverUniformsBuffer.releaseInFlightBuffers(this);
const gl::Rectangle &glViewport = mState.getViewport(); const gl::Rectangle &glViewport = mState.getViewport();
float halfRenderAreaHeight = float halfRenderAreaHeight =
...@@ -2099,7 +2099,7 @@ angle::Result ContextVk::flushImpl(const gl::Semaphore *clientSignalSemaphore) ...@@ -2099,7 +2099,7 @@ angle::Result ContextVk::flushImpl(const gl::Semaphore *clientSignalSemaphore)
signalSemaphores.push_back(vk::GetImpl(clientSignalSemaphore)->getHandle()); signalSemaphores.push_back(vk::GetImpl(clientSignalSemaphore)->getHandle());
} }
VkSubmitInfo submitInfo = {}; VkSubmitInfo submitInfo = {};
InitializeSubmitInfo(&submitInfo, commandBatch.get(), mWaitSemaphores, signalSemaphores); InitializeSubmitInfo(&submitInfo, commandBatch.get(), mWaitSemaphores, signalSemaphores);
ANGLE_TRY(submitFrame(submitInfo, commandBatch.release())); ANGLE_TRY(submitFrame(submitInfo, commandBatch.release()));
...@@ -2405,7 +2405,7 @@ angle::Result ContextVk::updateDefaultAttribute(size_t attribIndex) ...@@ -2405,7 +2405,7 @@ angle::Result ContextVk::updateDefaultAttribute(size_t attribIndex)
{ {
vk::DynamicBuffer &defaultBuffer = mDefaultAttribBuffers[attribIndex]; vk::DynamicBuffer &defaultBuffer = mDefaultAttribBuffers[attribIndex];
defaultBuffer.releaseRetainedBuffers(this); defaultBuffer.releaseInFlightBuffers(this);
uint8_t *ptr; uint8_t *ptr;
VkBuffer bufferHandle = VK_NULL_HANDLE; VkBuffer bufferHandle = VK_NULL_HANDLE;
......
...@@ -496,7 +496,7 @@ angle::Result FramebufferVk::readPixels(const gl::Context *context, ...@@ -496,7 +496,7 @@ angle::Result FramebufferVk::readPixels(const gl::Context *context,
ANGLE_TRY(readPixelsImpl(contextVk, flippedArea, params, VK_IMAGE_ASPECT_COLOR_BIT, ANGLE_TRY(readPixelsImpl(contextVk, flippedArea, params, VK_IMAGE_ASPECT_COLOR_BIT,
getColorReadRenderTarget(), getColorReadRenderTarget(),
static_cast<uint8_t *>(pixels) + outputSkipBytes)); static_cast<uint8_t *>(pixels) + outputSkipBytes));
mReadPixelBuffer.releaseRetainedBuffers(contextVk); mReadPixelBuffer.releaseInFlightBuffers(contextVk);
return angle::Result::Continue; return angle::Result::Continue;
} }
......
...@@ -112,7 +112,8 @@ egl::Error ImageVk::initialize(const egl::Display *display) ...@@ -112,7 +112,8 @@ egl::Error ImageVk::initialize(const egl::Display *display)
} }
// Make sure a staging buffer is ready to use to upload data // Make sure a staging buffer is ready to use to upload data
mImage->initStagingBuffer(renderer, mImage->getFormat()); mImage->initStagingBuffer(renderer, mImage->getFormat(), vk::kStagingBufferFlags,
vk::kStagingBufferSize);
mOwnsImage = false; mOwnsImage = false;
......
...@@ -120,7 +120,7 @@ angle::Result SyncDefaultUniformBlock(ContextVk *contextVk, ...@@ -120,7 +120,7 @@ angle::Result SyncDefaultUniformBlock(ContextVk *contextVk,
uint32_t *outOffset, uint32_t *outOffset,
bool *outBufferModified) bool *outBufferModified)
{ {
dynamicBuffer->releaseRetainedBuffers(contextVk); dynamicBuffer->releaseInFlightBuffers(contextVk);
ASSERT(!bufferData.empty()); ASSERT(!bufferData.empty());
uint8_t *data = nullptr; uint8_t *data = nullptr;
......
...@@ -118,7 +118,10 @@ angle::Result TextureVk::generateMipmapLevelsWithCPU(ContextVk *contextVk, ...@@ -118,7 +118,10 @@ angle::Result TextureVk::generateMipmapLevelsWithCPU(ContextVk *contextVk,
// TextureVk implementation. // TextureVk implementation.
TextureVk::TextureVk(const gl::TextureState &state, RendererVk *renderer) TextureVk::TextureVk(const gl::TextureState &state, RendererVk *renderer)
: TextureImpl(state), mOwnsImage(false), mImage(nullptr) : TextureImpl(state),
mOwnsImage(false),
mImage(nullptr),
mStagingBufferInitialSize(vk::kStagingBufferSize)
{} {}
TextureVk::~TextureVk() = default; TextureVk::~TextureVk() = default;
...@@ -864,7 +867,8 @@ void TextureVk::setImageHelper(ContextVk *contextVk, ...@@ -864,7 +867,8 @@ void TextureVk::setImageHelper(ContextVk *contextVk,
mImageLevelOffset = imageLevelOffset; mImageLevelOffset = imageLevelOffset;
mImageLayerOffset = imageLayerOffset; mImageLayerOffset = imageLayerOffset;
mImage = imageHelper; mImage = imageHelper;
mImage->initStagingBuffer(contextVk->getRenderer(), format); mImage->initStagingBuffer(contextVk->getRenderer(), format, vk::kStagingBufferFlags,
mStagingBufferInitialSize);
mRenderTarget.init(mImage, &mDrawBaseLevelImageView, &mFetchBaseLevelImageView, mRenderTarget.init(mImage, &mDrawBaseLevelImageView, &mFetchBaseLevelImageView,
getNativeImageLevel(0), getNativeImageLayer(0)); getNativeImageLevel(0), getNativeImageLayer(0));
...@@ -878,7 +882,8 @@ void TextureVk::setImageHelper(ContextVk *contextVk, ...@@ -878,7 +882,8 @@ void TextureVk::setImageHelper(ContextVk *contextVk,
void TextureVk::updateImageHelper(ContextVk *contextVk, const vk::Format &format) void TextureVk::updateImageHelper(ContextVk *contextVk, const vk::Format &format)
{ {
ASSERT(mImage != nullptr); ASSERT(mImage != nullptr);
mImage->initStagingBuffer(contextVk->getRenderer(), format); mImage->initStagingBuffer(contextVk->getRenderer(), format, vk::kStagingBufferFlags,
mStagingBufferInitialSize);
} }
angle::Result TextureVk::redefineImage(const gl::Context *context, angle::Result TextureVk::redefineImage(const gl::Context *context,
......
...@@ -165,6 +165,11 @@ class TextureVk : public TextureImpl ...@@ -165,6 +165,11 @@ class TextureVk : public TextureImpl
Serial getSerial() const { return mSerial; } Serial getSerial() const { return mSerial; }
void overrideStagingBufferSizeForTesting(size_t initialSizeForTesting)
{
mStagingBufferInitialSize = initialSizeForTesting;
}
private: private:
// Transform an image index from the frontend into one that can be used on the backing // Transform an image index from the frontend into one that can be used on the backing
// ImageHelper, taking into account mipmap or cube face offsets // ImageHelper, taking into account mipmap or cube face offsets
...@@ -310,6 +315,9 @@ class TextureVk : public TextureImpl ...@@ -310,6 +315,9 @@ class TextureVk : public TextureImpl
// The serial is used for cache indexing. // The serial is used for cache indexing.
Serial mSerial; Serial mSerial;
// Overridden in some tests.
size_t mStagingBufferInitialSize;
}; };
} // namespace rx } // namespace rx
......
...@@ -139,7 +139,7 @@ angle::Result VertexArrayVk::convertIndexBufferGPU(ContextVk *contextVk, ...@@ -139,7 +139,7 @@ angle::Result VertexArrayVk::convertIndexBufferGPU(ContextVk *contextVk,
intptr_t offsetIntoSrcData = reinterpret_cast<intptr_t>(indices); intptr_t offsetIntoSrcData = reinterpret_cast<intptr_t>(indices);
size_t srcDataSize = static_cast<size_t>(bufferVk->getSize()) - offsetIntoSrcData; size_t srcDataSize = static_cast<size_t>(bufferVk->getSize()) - offsetIntoSrcData;
mTranslatedByteIndexData.releaseRetainedBuffers(contextVk); mTranslatedByteIndexData.releaseInFlightBuffers(contextVk);
ANGLE_TRY(mTranslatedByteIndexData.allocate(contextVk, sizeof(GLushort) * srcDataSize, nullptr, ANGLE_TRY(mTranslatedByteIndexData.allocate(contextVk, sizeof(GLushort) * srcDataSize, nullptr,
nullptr, &mCurrentElementArrayBufferOffset, nullptr, &mCurrentElementArrayBufferOffset,
...@@ -166,7 +166,7 @@ angle::Result VertexArrayVk::convertIndexBufferCPU(ContextVk *contextVk, ...@@ -166,7 +166,7 @@ angle::Result VertexArrayVk::convertIndexBufferCPU(ContextVk *contextVk,
{ {
ASSERT(!mState.getElementArrayBuffer() || indexType == gl::DrawElementsType::UnsignedByte); ASSERT(!mState.getElementArrayBuffer() || indexType == gl::DrawElementsType::UnsignedByte);
mDynamicIndexData.releaseRetainedBuffers(contextVk); mDynamicIndexData.releaseInFlightBuffers(contextVk);
size_t elementSize = gl::GetDrawElementsTypeSize(indexType); size_t elementSize = gl::GetDrawElementsTypeSize(indexType);
if (indexType == gl::DrawElementsType::UnsignedByte) if (indexType == gl::DrawElementsType::UnsignedByte)
...@@ -252,7 +252,7 @@ angle::Result VertexArrayVk::convertVertexBufferGPU(ContextVk *contextVk, ...@@ -252,7 +252,7 @@ angle::Result VertexArrayVk::convertVertexBufferGPU(ContextVk *contextVk,
ASSERT(GetVertexInputAlignment(vertexFormat) <= vk::kVertexBufferAlignment); ASSERT(GetVertexInputAlignment(vertexFormat) <= vk::kVertexBufferAlignment);
// Allocate buffer for results // Allocate buffer for results
conversion->data.releaseRetainedBuffers(contextVk); conversion->data.releaseInFlightBuffers(contextVk);
ANGLE_TRY(conversion->data.allocate(contextVk, numVertices * destFormatSize, nullptr, nullptr, ANGLE_TRY(conversion->data.allocate(contextVk, numVertices * destFormatSize, nullptr, nullptr,
&conversion->lastAllocationOffset, nullptr)); &conversion->lastAllocationOffset, nullptr));
...@@ -287,7 +287,7 @@ angle::Result VertexArrayVk::convertVertexBufferCPU(ContextVk *contextVk, ...@@ -287,7 +287,7 @@ angle::Result VertexArrayVk::convertVertexBufferCPU(ContextVk *contextVk,
unsigned srcFormatSize = vertexFormat.angleFormat().pixelBytes; unsigned srcFormatSize = vertexFormat.angleFormat().pixelBytes;
unsigned dstFormatSize = vertexFormat.bufferFormat().pixelBytes; unsigned dstFormatSize = vertexFormat.bufferFormat().pixelBytes;
conversion->data.releaseRetainedBuffers(contextVk); conversion->data.releaseInFlightBuffers(contextVk);
size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize); size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize);
if (numVertices == 0) if (numVertices == 0)
...@@ -543,7 +543,7 @@ angle::Result VertexArrayVk::updateClientAttribs(const gl::Context *context, ...@@ -543,7 +543,7 @@ angle::Result VertexArrayVk::updateClientAttribs(const gl::Context *context,
indices, 0, &startVertex, &vertexCount)); indices, 0, &startVertex, &vertexCount));
RendererVk *renderer = contextVk->getRenderer(); RendererVk *renderer = contextVk->getRenderer();
mDynamicVertexData.releaseRetainedBuffers(contextVk); mDynamicVertexData.releaseInFlightBuffers(contextVk);
const auto &attribs = mState.getVertexAttributes(); const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings(); const auto &bindings = mState.getVertexBindings();
......
...@@ -28,6 +28,10 @@ constexpr VkBufferUsageFlags kIndexBufferUsageFlags = ...@@ -28,6 +28,10 @@ constexpr VkBufferUsageFlags kIndexBufferUsageFlags =
constexpr size_t kVertexBufferAlignment = 4; constexpr size_t kVertexBufferAlignment = 4;
constexpr size_t kIndexBufferAlignment = 4; constexpr size_t kIndexBufferAlignment = 4;
constexpr VkBufferUsageFlags kStagingBufferFlags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
constexpr size_t kStagingBufferSize = 1024 * 16;
// A dynamic buffer is conceptually an infinitely long buffer. Each time you write to the buffer, // A dynamic buffer is conceptually an infinitely long buffer. Each time you write to the buffer,
// you will always write to a previously unused portion. After a series of writes, you must flush // you will always write to a previously unused portion. After a series of writes, you must flush
// the buffer data to the device. Buffer lifetime currently assumes that each new allocation will // the buffer data to the device. Buffer lifetime currently assumes that each new allocation will
...@@ -35,6 +39,10 @@ constexpr size_t kIndexBufferAlignment = 4; ...@@ -35,6 +39,10 @@ constexpr size_t kIndexBufferAlignment = 4;
// //
// Dynamic buffers are used to implement a variety of data streaming operations in Vulkan, such // Dynamic buffers are used to implement a variety of data streaming operations in Vulkan, such
// as for immediate vertex array and element array data, uniform updates, and other dynamic data. // as for immediate vertex array and element array data, uniform updates, and other dynamic data.
//
// Internally dynamic buffers keep a collection of VkBuffers. When we write past the end of a
// currently active VkBuffer we keep it until it is no longer in use. We then mark it available
// for future allocations in a free list.
class BufferHelper; class BufferHelper;
class DynamicBuffer : angle::NonCopyable class DynamicBuffer : angle::NonCopyable
{ {
...@@ -72,8 +80,7 @@ class DynamicBuffer : angle::NonCopyable ...@@ -72,8 +80,7 @@ class DynamicBuffer : angle::NonCopyable
void release(DisplayVk *display, std::vector<GarbageObjectBase> *garbageQueue); void release(DisplayVk *display, std::vector<GarbageObjectBase> *garbageQueue);
// This releases all the buffers that have been allocated since this was last called. // This releases all the buffers that have been allocated since this was last called.
void releaseRetainedBuffers(ContextVk *contextVk); void releaseInFlightBuffers(ContextVk *contextVk);
void releaseRetainedBuffers(DisplayVk *display, std::vector<GarbageObjectBase> *garbageQueue);
// This frees resources immediately. // This frees resources immediately.
void destroy(VkDevice device); void destroy(VkDevice device);
...@@ -88,6 +95,12 @@ class DynamicBuffer : angle::NonCopyable ...@@ -88,6 +95,12 @@ class DynamicBuffer : angle::NonCopyable
private: private:
void reset(); void reset();
angle::Result allocateNewBuffer(ContextVk *contextVk);
void releaseBufferListToContext(ContextVk *contextVk, std::vector<BufferHelper *> *buffers);
void releaseBufferListToDisplay(DisplayVk *display,
std::vector<GarbageObjectBase> *garbageQueue,
std::vector<BufferHelper *> *buffers);
void destroyBufferList(VkDevice device, std::vector<BufferHelper *> *buffers);
VkBufferUsageFlags mUsage; VkBufferUsageFlags mUsage;
bool mHostVisible; bool mHostVisible;
...@@ -98,7 +111,8 @@ class DynamicBuffer : angle::NonCopyable ...@@ -98,7 +111,8 @@ class DynamicBuffer : angle::NonCopyable
size_t mSize; size_t mSize;
size_t mAlignment; size_t mAlignment;
std::vector<BufferHelper *> mRetainedBuffers; std::vector<BufferHelper *> mInFlightBuffers;
std::vector<BufferHelper *> mBufferFreeList;
}; };
// Uses DescriptorPool to allocate descriptor sets as needed. If a descriptor pool becomes full, we // Uses DescriptorPool to allocate descriptor sets as needed. If a descriptor pool becomes full, we
...@@ -566,7 +580,10 @@ class ImageHelper final : public CommandGraphResource ...@@ -566,7 +580,10 @@ class ImageHelper final : public CommandGraphResource
ImageHelper(ImageHelper &&other); ImageHelper(ImageHelper &&other);
~ImageHelper() override; ~ImageHelper() override;
void initStagingBuffer(RendererVk *renderer, const vk::Format &format); void initStagingBuffer(RendererVk *renderer,
const vk::Format &format,
VkBufferUsageFlags usageFlags,
size_t initialSize);
angle::Result init(Context *context, angle::Result init(Context *context,
gl::TextureType textureType, gl::TextureType textureType,
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#include "libANGLE/angletypes.h" #include "libANGLE/angletypes.h"
#include "libANGLE/renderer/vulkan/ContextVk.h" #include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/ProgramVk.h" #include "libANGLE/renderer/vulkan/ProgramVk.h"
#include "libANGLE/renderer/vulkan/TextureVk.h"
#include "test_utils/gl_raii.h" #include "test_utils/gl_raii.h"
#include "util/EGLWindow.h" #include "util/EGLWindow.h"
#include "util/shader_utils.h" #include "util/shader_utils.h"
...@@ -45,6 +46,14 @@ class VulkanUniformUpdatesTest : public ANGLETest ...@@ -45,6 +46,14 @@ class VulkanUniformUpdatesTest : public ANGLETest
return rx::vk::GetImpl(program); return rx::vk::GetImpl(program);
} }
rx::TextureVk *hackTexture(GLuint handle) const
{
// Hack the angle!
const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
const gl::Texture *texture = context->getTexture(handle);
return rx::vk::GetImpl(texture);
}
static constexpr uint32_t kMaxSetsForTesting = 32; static constexpr uint32_t kMaxSetsForTesting = 32;
void limitMaxSets(GLuint program) void limitMaxSets(GLuint program)
...@@ -67,6 +76,14 @@ class VulkanUniformUpdatesTest : public ANGLETest ...@@ -67,6 +76,14 @@ class VulkanUniformUpdatesTest : public ANGLETest
contextVk->getRenderer()->getMaxActiveTextures()}; contextVk->getRenderer()->getMaxActiveTextures()};
(void)texturePool->init(contextVk, &textureSetSize, 1); (void)texturePool->init(contextVk, &textureSetSize, 1);
} }
static constexpr size_t kTextureStagingBufferSizeForTesting = 128;
void limitTextureStagingBufferSize(GLuint texture)
{
rx::TextureVk *textureVk = hackTexture(texture);
textureVk->overrideStagingBufferSizeForTesting(kTextureStagingBufferSizeForTesting);
}
}; };
// This test updates a uniform until a new buffer is allocated and then make sure the uniform // This test updates a uniform until a new buffer is allocated and then make sure the uniform
...@@ -388,6 +405,67 @@ void main() ...@@ -388,6 +405,67 @@ void main()
ASSERT_GL_NO_ERROR(); ASSERT_GL_NO_ERROR();
} }
// Verify that overflowing a Texture's staging buffer doesn't overwrite current data.
TEST_P(VulkanUniformUpdatesTest, TextureStagingBufferRecycling)
{
ASSERT_TRUE(IsVulkan());
GLTexture tex;
glBindTexture(GL_TEXTURE_2D, tex);
limitTextureStagingBufferSize(tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
const GLColor kColors[4] = {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow};
// Repeatedly update the staging buffer to trigger multiple recyclings.
const GLsizei kHalfX = getWindowWidth() / 2;
const GLsizei kHalfY = getWindowHeight() / 2;
constexpr int kIterations = 4;
for (int x = 0; x < 2; ++x)
{
for (int y = 0; y < 2; ++y)
{
const int kColorIndex = x + y * 2;
const GLColor kColor = kColors[kColorIndex];
for (int iteration = 0; iteration < kIterations; ++iteration)
{
for (int subX = 0; subX < kHalfX; ++subX)
{
for (int subY = 0; subY < kHalfY; ++subY)
{
const GLsizei xoffset = x * kHalfX + subX;
const GLsizei yoffset = y * kHalfY + subY;
// Update a single pixel.
glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, 1, 1, GL_RGBA,
GL_UNSIGNED_BYTE, kColor.data());
}
}
}
}
}
draw2DTexturedQuad(0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
// Verify pixels.
for (int x = 0; x < 2; ++x)
{
for (int y = 0; y < 2; ++y)
{
const GLsizei xoffset = x * kHalfX;
const GLsizei yoffset = y * kHalfY;
const int kColorIndex = x + y * 2;
const GLColor kColor = kColors[kColorIndex];
EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, kColor);
}
}
}
ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN(), ES3_VULKAN()); ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN(), ES3_VULKAN());
} // anonymous namespace } // anonymous namespace
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