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()
ANGLE_VK_TRY(this, commandBuffer.end());
// Submit the command buffer
VkSubmitInfo submitInfo = {};
VkSubmitInfo submitInfo = {};
InitializeSubmitInfo(&submitInfo, commandBatch.get(), {}, {});
ANGLE_TRY(submitFrame(submitInfo, std::move(commandBuffer)));
......@@ -1933,7 +1933,7 @@ angle::Result ContextVk::handleDirtyDriverUniforms(const gl::Context *context,
vk::CommandBuffer *commandBuffer)
{
// Release any previously retained buffers.
mDriverUniformsBuffer.releaseRetainedBuffers(this);
mDriverUniformsBuffer.releaseInFlightBuffers(this);
const gl::Rectangle &glViewport = mState.getViewport();
float halfRenderAreaHeight =
......@@ -2099,7 +2099,7 @@ angle::Result ContextVk::flushImpl(const gl::Semaphore *clientSignalSemaphore)
signalSemaphores.push_back(vk::GetImpl(clientSignalSemaphore)->getHandle());
}
VkSubmitInfo submitInfo = {};
VkSubmitInfo submitInfo = {};
InitializeSubmitInfo(&submitInfo, commandBatch.get(), mWaitSemaphores, signalSemaphores);
ANGLE_TRY(submitFrame(submitInfo, commandBatch.release()));
......@@ -2405,7 +2405,7 @@ angle::Result ContextVk::updateDefaultAttribute(size_t attribIndex)
{
vk::DynamicBuffer &defaultBuffer = mDefaultAttribBuffers[attribIndex];
defaultBuffer.releaseRetainedBuffers(this);
defaultBuffer.releaseInFlightBuffers(this);
uint8_t *ptr;
VkBuffer bufferHandle = VK_NULL_HANDLE;
......
......@@ -496,7 +496,7 @@ angle::Result FramebufferVk::readPixels(const gl::Context *context,
ANGLE_TRY(readPixelsImpl(contextVk, flippedArea, params, VK_IMAGE_ASPECT_COLOR_BIT,
getColorReadRenderTarget(),
static_cast<uint8_t *>(pixels) + outputSkipBytes));
mReadPixelBuffer.releaseRetainedBuffers(contextVk);
mReadPixelBuffer.releaseInFlightBuffers(contextVk);
return angle::Result::Continue;
}
......
......@@ -112,7 +112,8 @@ egl::Error ImageVk::initialize(const egl::Display *display)
}
// 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;
......
......@@ -120,7 +120,7 @@ angle::Result SyncDefaultUniformBlock(ContextVk *contextVk,
uint32_t *outOffset,
bool *outBufferModified)
{
dynamicBuffer->releaseRetainedBuffers(contextVk);
dynamicBuffer->releaseInFlightBuffers(contextVk);
ASSERT(!bufferData.empty());
uint8_t *data = nullptr;
......
......@@ -118,7 +118,10 @@ angle::Result TextureVk::generateMipmapLevelsWithCPU(ContextVk *contextVk,
// TextureVk implementation.
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;
......@@ -864,7 +867,8 @@ void TextureVk::setImageHelper(ContextVk *contextVk,
mImageLevelOffset = imageLevelOffset;
mImageLayerOffset = imageLayerOffset;
mImage = imageHelper;
mImage->initStagingBuffer(contextVk->getRenderer(), format);
mImage->initStagingBuffer(contextVk->getRenderer(), format, vk::kStagingBufferFlags,
mStagingBufferInitialSize);
mRenderTarget.init(mImage, &mDrawBaseLevelImageView, &mFetchBaseLevelImageView,
getNativeImageLevel(0), getNativeImageLayer(0));
......@@ -878,7 +882,8 @@ void TextureVk::setImageHelper(ContextVk *contextVk,
void TextureVk::updateImageHelper(ContextVk *contextVk, const vk::Format &format)
{
ASSERT(mImage != nullptr);
mImage->initStagingBuffer(contextVk->getRenderer(), format);
mImage->initStagingBuffer(contextVk->getRenderer(), format, vk::kStagingBufferFlags,
mStagingBufferInitialSize);
}
angle::Result TextureVk::redefineImage(const gl::Context *context,
......
......@@ -165,6 +165,11 @@ class TextureVk : public TextureImpl
Serial getSerial() const { return mSerial; }
void overrideStagingBufferSizeForTesting(size_t initialSizeForTesting)
{
mStagingBufferInitialSize = initialSizeForTesting;
}
private:
// 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
......@@ -310,6 +315,9 @@ class TextureVk : public TextureImpl
// The serial is used for cache indexing.
Serial mSerial;
// Overridden in some tests.
size_t mStagingBufferInitialSize;
};
} // namespace rx
......
......@@ -139,7 +139,7 @@ angle::Result VertexArrayVk::convertIndexBufferGPU(ContextVk *contextVk,
intptr_t offsetIntoSrcData = reinterpret_cast<intptr_t>(indices);
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,
nullptr, &mCurrentElementArrayBufferOffset,
......@@ -166,7 +166,7 @@ angle::Result VertexArrayVk::convertIndexBufferCPU(ContextVk *contextVk,
{
ASSERT(!mState.getElementArrayBuffer() || indexType == gl::DrawElementsType::UnsignedByte);
mDynamicIndexData.releaseRetainedBuffers(contextVk);
mDynamicIndexData.releaseInFlightBuffers(contextVk);
size_t elementSize = gl::GetDrawElementsTypeSize(indexType);
if (indexType == gl::DrawElementsType::UnsignedByte)
......@@ -252,7 +252,7 @@ angle::Result VertexArrayVk::convertVertexBufferGPU(ContextVk *contextVk,
ASSERT(GetVertexInputAlignment(vertexFormat) <= vk::kVertexBufferAlignment);
// Allocate buffer for results
conversion->data.releaseRetainedBuffers(contextVk);
conversion->data.releaseInFlightBuffers(contextVk);
ANGLE_TRY(conversion->data.allocate(contextVk, numVertices * destFormatSize, nullptr, nullptr,
&conversion->lastAllocationOffset, nullptr));
......@@ -287,7 +287,7 @@ angle::Result VertexArrayVk::convertVertexBufferCPU(ContextVk *contextVk,
unsigned srcFormatSize = vertexFormat.angleFormat().pixelBytes;
unsigned dstFormatSize = vertexFormat.bufferFormat().pixelBytes;
conversion->data.releaseRetainedBuffers(contextVk);
conversion->data.releaseInFlightBuffers(contextVk);
size_t numVertices = GetVertexCount(srcBuffer, binding, srcFormatSize);
if (numVertices == 0)
......@@ -543,7 +543,7 @@ angle::Result VertexArrayVk::updateClientAttribs(const gl::Context *context,
indices, 0, &startVertex, &vertexCount));
RendererVk *renderer = contextVk->getRenderer();
mDynamicVertexData.releaseRetainedBuffers(contextVk);
mDynamicVertexData.releaseInFlightBuffers(contextVk);
const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings();
......
......@@ -28,6 +28,10 @@ constexpr VkBufferUsageFlags kIndexBufferUsageFlags =
constexpr size_t kVertexBufferAlignment = 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,
// 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
......@@ -35,6 +39,10 @@ constexpr size_t kIndexBufferAlignment = 4;
//
// 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.
//
// 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 DynamicBuffer : angle::NonCopyable
{
......@@ -72,8 +80,7 @@ class DynamicBuffer : angle::NonCopyable
void release(DisplayVk *display, std::vector<GarbageObjectBase> *garbageQueue);
// This releases all the buffers that have been allocated since this was last called.
void releaseRetainedBuffers(ContextVk *contextVk);
void releaseRetainedBuffers(DisplayVk *display, std::vector<GarbageObjectBase> *garbageQueue);
void releaseInFlightBuffers(ContextVk *contextVk);
// This frees resources immediately.
void destroy(VkDevice device);
......@@ -88,6 +95,12 @@ class DynamicBuffer : angle::NonCopyable
private:
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;
bool mHostVisible;
......@@ -98,7 +111,8 @@ class DynamicBuffer : angle::NonCopyable
size_t mSize;
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
......@@ -566,7 +580,10 @@ class ImageHelper final : public CommandGraphResource
ImageHelper(ImageHelper &&other);
~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,
gl::TextureType textureType,
......
......@@ -18,6 +18,7 @@
#include "libANGLE/angletypes.h"
#include "libANGLE/renderer/vulkan/ContextVk.h"
#include "libANGLE/renderer/vulkan/ProgramVk.h"
#include "libANGLE/renderer/vulkan/TextureVk.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include "util/shader_utils.h"
......@@ -45,6 +46,14 @@ class VulkanUniformUpdatesTest : public ANGLETest
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;
void limitMaxSets(GLuint program)
......@@ -67,6 +76,14 @@ class VulkanUniformUpdatesTest : public ANGLETest
contextVk->getRenderer()->getMaxActiveTextures()};
(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
......@@ -388,6 +405,67 @@ void main()
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());
} // 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