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: Tobin Ehlis <tobine@google.com>
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
index f6e0408..f7b39ad 100644
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
@@ -909,7 +909,7 @@
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 @@
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 @@
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 @@
{
vk::DynamicBuffer &defaultBuffer = mDefaultAttribBuffers[attribIndex];
- defaultBuffer.releaseRetainedBuffers(this);
+ defaultBuffer.releaseInFlightBuffers(this);
uint8_t *ptr;
VkBuffer bufferHandle = VK_NULL_HANDLE;
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
index b0b6645..4c91b3d 100644
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
@@ -496,7 +496,7 @@
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;
}
diff --git a/src/libANGLE/renderer/vulkan/ImageVk.cpp b/src/libANGLE/renderer/vulkan/ImageVk.cpp
index fb7ea8c..8fdfea5 100644
--- a/src/libANGLE/renderer/vulkan/ImageVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ImageVk.cpp
@@ -112,7 +112,8 @@
}
// 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;
diff --git a/src/libANGLE/renderer/vulkan/ProgramVk.cpp b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
index 110f60e..1502675 100644
--- a/src/libANGLE/renderer/vulkan/ProgramVk.cpp
+++ b/src/libANGLE/renderer/vulkan/ProgramVk.cpp
@@ -120,7 +120,7 @@
uint32_t *outOffset,
bool *outBufferModified)
{
- dynamicBuffer->releaseRetainedBuffers(contextVk);
+ dynamicBuffer->releaseInFlightBuffers(contextVk);
ASSERT(!bufferData.empty());
uint8_t *data = nullptr;
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
index 48b7b09..8427886 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
@@ -118,7 +118,10 @@
// 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 @@
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::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,
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.h b/src/libANGLE/renderer/vulkan/TextureVk.h
index def82e6..29bc726 100644
--- a/src/libANGLE/renderer/vulkan/TextureVk.h
+++ b/src/libANGLE/renderer/vulkan/TextureVk.h
@@ -165,6 +165,11 @@
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 @@
// The serial is used for cache indexing.
Serial mSerial;
+
+ // Overridden in some tests.
+ size_t mStagingBufferInitialSize;
};
} // namespace rx
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
index b60b438..80f9316 100644
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
@@ -139,7 +139,7 @@
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 @@
{
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 @@
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 @@
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 @@
indices, 0, &startVertex, &vertexCount));
RendererVk *renderer = contextVk->getRenderer();
- mDynamicVertexData.releaseRetainedBuffers(contextVk);
+ mDynamicVertexData.releaseInFlightBuffers(contextVk);
const auto &attribs = mState.getVertexAttributes();
const auto &bindings = mState.getVertexBindings();
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
index 23a651c..1caa6a2 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
@@ -262,7 +262,7 @@
mLastFlushOrInvalidateOffset(other.mLastFlushOrInvalidateOffset),
mSize(other.mSize),
mAlignment(other.mAlignment),
- mRetainedBuffers(std::move(other.mRetainedBuffers))
+ mInFlightBuffers(std::move(other.mInFlightBuffers))
{
other.mBuffer = nullptr;
}
@@ -274,14 +274,20 @@
bool hostVisible)
{
mUsage = usage;
- mInitialSize = initialSize;
mHostVisible = hostVisible;
+ // Check that we haven't overriden the initial size of the buffer in setMinimumSizeForTesting.
+ if (mInitialSize == 0)
+ {
+ mInitialSize = initialSize;
+ mSize = 0;
+ }
+
// Workaround for the mock ICD not supporting allocations greater than 0x1000.
// Could be removed if https://github.com/KhronosGroup/Vulkan-Tools/issues/84 is fixed.
if (renderer->isMockICDEnabled())
{
- mInitialSize = std::min<size_t>(mInitialSize, 0x1000);
+ mSize = std::min<size_t>(mSize, 0x1000);
}
updateAlignment(renderer, alignment);
@@ -292,6 +298,29 @@
ASSERT(mBuffer == nullptr);
}
+angle::Result DynamicBuffer::allocateNewBuffer(ContextVk *contextVk)
+{
+ std::unique_ptr<BufferHelper> buffer = std::make_unique<BufferHelper>();
+
+ VkBufferCreateInfo createInfo = {};
+ createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ createInfo.flags = 0;
+ createInfo.size = mSize;
+ createInfo.usage = mUsage;
+ createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ createInfo.queueFamilyIndexCount = 0;
+ createInfo.pQueueFamilyIndices = nullptr;
+
+ const VkMemoryPropertyFlags memoryProperty =
+ mHostVisible ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
+ ANGLE_TRY(buffer->init(contextVk, createInfo, memoryProperty));
+
+ ASSERT(!mBuffer);
+ mBuffer = buffer.release();
+
+ return angle::Result::Continue;
+}
+
angle::Result DynamicBuffer::allocate(ContextVk *contextVk,
size_t sizeInBytes,
uint8_t **ptrOut,
@@ -310,29 +339,37 @@
{
ANGLE_TRY(flush(contextVk));
mBuffer->unmap(contextVk->getDevice());
+ mBuffer->updateQueueSerial(contextVk->getCurrentQueueSerial());
- mRetainedBuffers.push_back(mBuffer);
+ mInFlightBuffers.push_back(mBuffer);
mBuffer = nullptr;
}
- mSize = std::max(sizeToAllocate, mInitialSize);
+ if (sizeToAllocate > mSize)
+ {
+ mSize = std::max(mInitialSize, sizeToAllocate);
- std::unique_ptr<BufferHelper> buffer = std::make_unique<BufferHelper>();
+ // Clear the free list since the free buffers are now too small.
+ for (BufferHelper *toFree : mBufferFreeList)
+ {
+ toFree->release(contextVk);
+ }
+ mBufferFreeList.clear();
+ }
- VkBufferCreateInfo createInfo = {};
- createInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
- createInfo.flags = 0;
- createInfo.size = mSize;
- createInfo.usage = mUsage;
- createInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
- createInfo.queueFamilyIndexCount = 0;
- createInfo.pQueueFamilyIndices = nullptr;
+ // The front of the free list should be the oldest. Thus if it is in use the rest of the
+ // free list should be in use as well.
+ if (mBufferFreeList.empty() || mBufferFreeList.front()->isResourceInUse(contextVk))
+ {
+ ANGLE_TRY(allocateNewBuffer(contextVk));
+ }
+ else
+ {
+ mBuffer = mBufferFreeList.front();
+ mBufferFreeList.erase(mBufferFreeList.begin());
+ }
- const VkMemoryPropertyFlags memoryProperty = mHostVisible
- ? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
- : VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT;
- ANGLE_TRY(buffer->init(contextVk, createInfo, memoryProperty));
- mBuffer = buffer.release();
+ ASSERT(mBuffer->getSize() == mSize);
mNextAllocationOffset = 0;
mLastFlushOrInvalidateOffset = 0;
@@ -392,10 +429,48 @@
return angle::Result::Continue;
}
+void DynamicBuffer::releaseBufferListToContext(ContextVk *contextVk,
+ std::vector<BufferHelper *> *buffers)
+{
+ for (BufferHelper *toFree : *buffers)
+ {
+ toFree->release(contextVk);
+ delete toFree;
+ }
+
+ buffers->clear();
+}
+
+void DynamicBuffer::releaseBufferListToDisplay(DisplayVk *display,
+ std::vector<GarbageObjectBase> *garbageQueue,
+ std::vector<BufferHelper *> *buffers)
+{
+ for (BufferHelper *toFree : *buffers)
+ {
+ toFree->release(display, garbageQueue);
+ delete toFree;
+ }
+
+ buffers->clear();
+}
+
+void DynamicBuffer::destroyBufferList(VkDevice device, std::vector<BufferHelper *> *buffers)
+{
+ for (BufferHelper *toFree : *buffers)
+ {
+ toFree->destroy(device);
+ delete toFree;
+ }
+
+ buffers->clear();
+}
+
void DynamicBuffer::release(ContextVk *contextVk)
{
reset();
- releaseRetainedBuffers(contextVk);
+
+ releaseBufferListToContext(contextVk, &mInFlightBuffers);
+ releaseBufferListToContext(contextVk, &mBufferFreeList);
if (mBuffer)
{
@@ -414,7 +489,9 @@
void DynamicBuffer::release(DisplayVk *display, std::vector<GarbageObjectBase> *garbageQueue)
{
reset();
- releaseRetainedBuffers(display, garbageQueue);
+
+ releaseBufferListToDisplay(display, garbageQueue, &mInFlightBuffers);
+ releaseBufferListToDisplay(display, garbageQueue, &mBufferFreeList);
if (mBuffer)
{
@@ -426,42 +503,30 @@
}
}
-void DynamicBuffer::releaseRetainedBuffers(ContextVk *contextVk)
+void DynamicBuffer::releaseInFlightBuffers(ContextVk *contextVk)
{
- for (BufferHelper *toFree : mRetainedBuffers)
+ for (BufferHelper *toRelease : mInFlightBuffers)
{
- // See note in release().
- toFree->updateQueueSerial(contextVk->getCurrentQueueSerial());
- toFree->release(contextVk);
- delete toFree;
+ // If the dynamic buffer was resized we cannot reuse the retained buffer.
+ if (toRelease->getSize() < mSize)
+ {
+ toRelease->release(contextVk);
+ }
+ else
+ {
+ mBufferFreeList.push_back(toRelease);
+ }
}
- mRetainedBuffers.clear();
-}
-
-void DynamicBuffer::releaseRetainedBuffers(DisplayVk *display,
- std::vector<GarbageObjectBase> *garbageQueue)
-{
- for (BufferHelper *toFree : mRetainedBuffers)
- {
- toFree->release(display, garbageQueue);
- delete toFree;
- }
-
- mRetainedBuffers.clear();
+ mInFlightBuffers.clear();
}
void DynamicBuffer::destroy(VkDevice device)
{
reset();
- for (BufferHelper *toFree : mRetainedBuffers)
- {
- toFree->destroy(device);
- delete toFree;
- }
-
- mRetainedBuffers.clear();
+ destroyBufferList(device, &mInFlightBuffers);
+ destroyBufferList(device, &mBufferFreeList);
if (mBuffer)
{
@@ -1023,7 +1088,7 @@
uint32_t *indices = nullptr;
size_t allocateBytes = sizeof(uint32_t) * (static_cast<size_t>(clampedVertexCount) + 1);
- mDynamicIndexBuffer.releaseRetainedBuffers(contextVk);
+ mDynamicIndexBuffer.releaseInFlightBuffers(contextVk);
ANGLE_TRY(mDynamicIndexBuffer.allocate(contextVk, allocateBytes,
reinterpret_cast<uint8_t **>(&indices), nullptr,
offsetOut, nullptr));
@@ -1080,7 +1145,7 @@
auto unitSize = (indexType == VK_INDEX_TYPE_UINT16 ? sizeof(uint16_t) : sizeof(uint32_t));
size_t allocateBytes = unitSize * (indexCount + 1) + 1;
- mDynamicIndexBuffer.releaseRetainedBuffers(contextVk);
+ mDynamicIndexBuffer.releaseInFlightBuffers(contextVk);
ANGLE_TRY(mDynamicIndexBuffer.allocate(contextVk, allocateBytes,
reinterpret_cast<uint8_t **>(&indices), nullptr,
bufferOffsetOut, nullptr));
@@ -1361,14 +1426,6 @@
return angle::Result::Continue;
}
-namespace
-{
-constexpr VkBufferUsageFlags kStagingBufferFlags =
- VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
-constexpr size_t kStagingBufferSize = 1024 * 16;
-
-} // anonymous namespace
-
// ImageHelper implementation.
ImageHelper::ImageHelper()
: CommandGraphResource(CommandGraphResourceType::Image),
@@ -1405,10 +1462,13 @@
ASSERT(!valid());
}
-void ImageHelper::initStagingBuffer(RendererVk *renderer, const vk::Format &format)
+void ImageHelper::initStagingBuffer(RendererVk *renderer,
+ const vk::Format &format,
+ VkBufferUsageFlags usageFlags,
+ size_t initialSize)
{
- mStagingBuffer.init(renderer, kStagingBufferFlags, format.getImageCopyBufferAlignment(),
- kStagingBufferSize, true);
+ mStagingBuffer.init(renderer, usageFlags, format.getImageCopyBufferAlignment(), initialSize,
+ true);
}
angle::Result ImageHelper::init(Context *context,
@@ -2397,7 +2457,7 @@
if (mSubresourceUpdates.empty())
{
- mStagingBuffer.releaseRetainedBuffers(contextVk);
+ mStagingBuffer.releaseInFlightBuffers(contextVk);
}
return angle::Result::Continue;
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.h b/src/libANGLE/renderer/vulkan/vk_helpers.h
index e8b590f..98635d3 100644
--- a/src/libANGLE/renderer/vulkan/vk_helpers.h
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.h
@@ -28,6 +28,10 @@
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 @@
//
// 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 @@
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 @@
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 @@
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 @@
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,
diff --git a/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp b/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp
index 5f237ca..484243b 100644
--- a/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp
+++ b/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp
@@ -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 @@
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 @@
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 @@
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