Commit 745e0712 by Mohan Maiya Committed by Commit Bot

Vulkan: Enable CPU only buffers for PBOs

Add support for a CPU only buffer for PBOs that serve as the destination for all host operations like MapBuffer*. This removes the latency caused by waiting for the in-flight GPU commands to be complete before handing over the buffer to the app. This change removes a ~6ms wait/sleep on the first call to MapBuffer* in each frame of Manhattan Bug: angleproject:4339 Tests: angle_end2end_tests --gtest_filter=BufferDataTest*Vulkan Change-Id: I52016b160af8a670cc30f01c05e48f699521310f Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2116874 Commit-Queue: Mohan Maiya <m.maiya@samsung.com> Reviewed-by: 's avatarTobin Ehlis <tobine@google.com>
parent dcd98298
......@@ -157,6 +157,7 @@ void BufferVk::release(ContextVk *contextVk)
RendererVk *renderer = contextVk->getRenderer();
mBuffer.release(renderer);
mStagingBuffer.release(renderer);
mShadowBuffer.release();
for (ConversionBuffer &buffer : mVertexConversionBuffers)
{
......@@ -176,6 +177,33 @@ void BufferVk::initializeStagingBuffer(ContextVk *contextVk, gl::BufferBinding t
mStagingBuffer.init(rendererVk, kBufferUsageFlags, alignment, stagingBufferSize, true);
}
angle::Result BufferVk::initializeShadowBuffer(ContextVk *contextVk,
gl::BufferBinding target,
size_t size)
{
// For now, enable shadow buffers only for pixel unpack buffers.
// If usecases present themselves, we can enable them for other buffer types.
if (target == gl::BufferBinding::PixelUnpack)
{
// Initialize the shadow buffer
mShadowBuffer.init(size);
// Allocate required memory. If allocation fails, treat it is a non-fatal error
// since we do not need the shadow buffer for functionality
ANGLE_TRY(mShadowBuffer.allocate(size));
}
return angle::Result::Continue;
}
void BufferVk::updateShadowBuffer(const uint8_t *data, size_t size, size_t offset)
{
if (mShadowBuffer.valid())
{
mShadowBuffer.updateData(data, size, offset);
}
}
angle::Result BufferVk::setData(const gl::Context *context,
gl::BufferBinding target,
const void *data,
......@@ -218,6 +246,9 @@ angle::Result BufferVk::setData(const gl::Context *context,
// Initialize the staging buffer
initializeStagingBuffer(contextVk, target, size);
// Initialize the shadow buffer
ANGLE_TRY(initializeShadowBuffer(contextVk, target, size));
}
if (data && size > 0)
......@@ -252,6 +283,25 @@ angle::Result BufferVk::copySubData(const gl::Context *context,
ContextVk *contextVk = vk::GetImpl(context);
auto *sourceBuffer = GetAs<BufferVk>(source);
ASSERT(sourceBuffer->getBuffer().valid());
// If the shadow buffer is enabled for the destination buffer then
// we need to update that as well. This will require us to complete
// all recorded and in-flight commands involving the source buffer.
if (mShadowBuffer.valid())
{
ANGLE_TRY(sourceBuffer->getBuffer().waitForIdle(contextVk));
// Update the shadow buffer
uint8_t *srcPtr;
ANGLE_VK_TRY(contextVk, sourceBuffer->getBuffer().getDeviceMemory().map(
contextVk->getDevice(), sourceOffset, size, 0, &srcPtr));
updateShadowBuffer(srcPtr, size, destOffset);
// Unmap the source buffer
sourceBuffer->getBuffer().getDeviceMemory().unmap(contextVk->getDevice());
}
vk::CommandBuffer *commandBuffer = nullptr;
......@@ -300,33 +350,36 @@ angle::Result BufferVk::mapRangeImpl(ContextVk *contextVk,
GLbitfield access,
void **mapPtr)
{
ASSERT(mBuffer.valid());
if ((access & GL_MAP_UNSYNCHRONIZED_BIT) == 0)
if (!mShadowBuffer.valid())
{
// If there are pending commands for the buffer, flush them.
if (mBuffer.usedInRecordedCommands())
{
ANGLE_TRY(contextVk->flushImpl(nullptr));
}
ASSERT(mBuffer.valid());
// Make sure the driver is done with the buffer.
if (mBuffer.usedInRunningCommands(contextVk->getLastCompletedQueueSerial()))
if ((access & GL_MAP_UNSYNCHRONIZED_BIT) == 0)
{
ANGLE_TRY(mBuffer.finishRunningCommands(contextVk));
ANGLE_TRY(mBuffer.waitForIdle(contextVk));
}
ASSERT(!mBuffer.isCurrentlyInUse(contextVk->getLastCompletedQueueSerial()));
ANGLE_VK_TRY(contextVk,
mBuffer.getDeviceMemory().map(contextVk->getDevice(), offset, length, 0,
reinterpret_cast<uint8_t **>(mapPtr)));
}
else
{
// If the app requested a GL_MAP_UNSYNCHRONIZED_BIT access, the spec states -
// No GL error is generated if pending operations which source or modify the
// buffer overlap the mapped region, but the result of such previous and any
// subsequent operations is undefined
// To keep the code simple, irrespective of whether the access was GL_MAP_UNSYNCHRONIZED_BIT
// or not, just return the shadow buffer.
mShadowBuffer.map(static_cast<size_t>(offset), mapPtr);
}
ANGLE_VK_TRY(contextVk, mBuffer.getDeviceMemory().map(contextVk->getDevice(), offset, length, 0,
reinterpret_cast<uint8_t **>(mapPtr)));
return angle::Result::Continue;
}
angle::Result BufferVk::unmap(const gl::Context *context, GLboolean *result)
{
unmapImpl(vk::GetImpl(context));
ANGLE_TRY(unmapImpl(vk::GetImpl(context)));
// This should be false if the contents have been corrupted through external means. Vulkan
// doesn't provide such information.
......@@ -335,14 +388,36 @@ angle::Result BufferVk::unmap(const gl::Context *context, GLboolean *result)
return angle::Result::Continue;
}
void BufferVk::unmapImpl(ContextVk *contextVk)
angle::Result BufferVk::unmapImpl(ContextVk *contextVk)
{
ASSERT(mBuffer.valid());
mBuffer.getDeviceMemory().unmap(contextVk->getDevice());
mBuffer.onExternalWrite(VK_ACCESS_HOST_WRITE_BIT);
if (!mShadowBuffer.valid())
{
mBuffer.getDeviceMemory().unmap(contextVk->getDevice());
mBuffer.onExternalWrite(VK_ACCESS_HOST_WRITE_BIT);
}
else
{
bool writeOperation = ((mState.getAccessFlags() & GL_MAP_WRITE_BIT) != 0);
size_t offset = static_cast<size_t>(mState.getMapOffset());
size_t size = static_cast<size_t>(mState.getMapLength());
// If it was a write operation we need to update the GPU buffer.
if (writeOperation)
{
// We do not yet know if thie data will ever be used. Perform a staged
// update which will get flushed if and when necessary.
const uint8_t *data = getShadowBuffer(offset);
ANGLE_TRY(stagedUpdate(contextVk, data, size, offset));
}
mShadowBuffer.unmap();
}
markConversionBuffersDirty();
return angle::Result::Continue;
}
angle::Result BufferVk::getIndexRange(const gl::Context *context,
......@@ -365,17 +440,25 @@ angle::Result BufferVk::getIndexRange(const gl::Context *context,
}
ANGLE_TRACE_EVENT0("gpu.angle", "BufferVk::getIndexRange");
// Needed before reading buffer or we could get stale data.
ANGLE_TRY(mBuffer.finishRunningCommands(contextVk));
// TODO(jmadill): Consider keeping a shadow system memory copy in some cases.
ASSERT(mBuffer.valid());
uint8_t *mapPointer;
if (!mShadowBuffer.valid())
{
// Needed before reading buffer or we could get stale data.
ANGLE_TRY(mBuffer.finishRunningCommands(contextVk));
const GLuint &typeBytes = gl::GetDrawElementsTypeSize(type);
ASSERT(mBuffer.valid());
uint8_t *mapPointer = nullptr;
ANGLE_VK_TRY(contextVk, mBuffer.getDeviceMemory().map(contextVk->getDevice(), offset,
typeBytes * count, 0, &mapPointer));
const GLuint &typeBytes = gl::GetDrawElementsTypeSize(type);
ANGLE_VK_TRY(contextVk, mBuffer.getDeviceMemory().map(contextVk->getDevice(), offset,
typeBytes * count, 0, &mapPointer));
}
else
{
mapPointer = getShadowBuffer(offset);
}
*outRange = gl::ComputeIndexRange(type, mapPointer, count, primitiveRestartEnabled);
......@@ -383,49 +466,71 @@ angle::Result BufferVk::getIndexRange(const gl::Context *context,
return angle::Result::Continue;
}
angle::Result BufferVk::directUpdate(ContextVk *contextVk,
const uint8_t *data,
size_t size,
size_t offset)
{
VkDevice device = contextVk->getDevice();
uint8_t *mapPointer = nullptr;
ANGLE_VK_TRY(contextVk, mBuffer.getDeviceMemory().map(device, offset, size, 0, &mapPointer));
ASSERT(mapPointer);
memcpy(mapPointer, data, size);
mBuffer.getDeviceMemory().unmap(device);
mBuffer.onExternalWrite(VK_ACCESS_HOST_WRITE_BIT);
return angle::Result::Continue;
}
angle::Result BufferVk::stagedUpdate(ContextVk *contextVk,
const uint8_t *data,
size_t size,
size_t offset)
{
// Acquire a "new" staging buffer
bool needToReleasePreviousBuffers = false;
uint8_t *mapPointer = nullptr;
VkDeviceSize stagingBufferOffset = 0;
ANGLE_TRY(mStagingBuffer.allocate(contextVk, size, &mapPointer, nullptr, &stagingBufferOffset,
&needToReleasePreviousBuffers));
if (needToReleasePreviousBuffers)
{
// Release previous staging buffers
mStagingBuffer.releaseInFlightBuffers(contextVk);
}
ASSERT(mapPointer);
memcpy(mapPointer, data, size);
// Enqueue a copy command on the GPU.
VkBufferCopy copyRegion = {stagingBufferOffset, offset, size};
ANGLE_TRY(mBuffer.copyFromBuffer(contextVk, mStagingBuffer.getCurrentBuffer(),
VK_ACCESS_HOST_WRITE_BIT, copyRegion));
mStagingBuffer.getCurrentBuffer()->retain(&contextVk->getResourceUseList());
return angle::Result::Continue;
}
angle::Result BufferVk::setDataImpl(ContextVk *contextVk,
const uint8_t *data,
size_t size,
size_t offset)
{
VkDevice device = contextVk->getDevice();
// Update shadow buffer
updateShadowBuffer(data, size, offset);
// Use map when available.
// If the buffer is currently in use, stage the update. Otherwise update the buffer directly.
if (mBuffer.isCurrentlyInUse(contextVk->getLastCompletedQueueSerial()))
{
// Acquire a "new" staging buffer
bool needToReleasePreviousBuffers = false;
uint8_t *mapPointer = nullptr;
VkDeviceSize stagingBufferOffset = 0;
ANGLE_TRY(mStagingBuffer.allocate(contextVk, size, &mapPointer, nullptr,
&stagingBufferOffset, &needToReleasePreviousBuffers));
if (needToReleasePreviousBuffers)
{
// Release previous staging buffers
mStagingBuffer.releaseInFlightBuffers(contextVk);
}
ASSERT(mapPointer);
memcpy(mapPointer, data, size);
// Enqueue a copy command on the GPU.
VkBufferCopy copyRegion = {stagingBufferOffset, offset, size};
ANGLE_TRY(mBuffer.copyFromBuffer(contextVk, mStagingBuffer.getCurrentBuffer(),
VK_ACCESS_HOST_WRITE_BIT, copyRegion));
mStagingBuffer.getCurrentBuffer()->retain(&contextVk->getResourceUseList());
ANGLE_TRY(stagedUpdate(contextVk, data, size, offset));
}
else
{
uint8_t *mapPointer = nullptr;
ANGLE_VK_TRY(contextVk,
mBuffer.getDeviceMemory().map(device, offset, size, 0, &mapPointer));
ASSERT(mapPointer);
memcpy(mapPointer, data, size);
mBuffer.getDeviceMemory().unmap(device);
mBuffer.onExternalWrite(VK_ACCESS_HOST_WRITE_BIT);
ANGLE_TRY(directUpdate(contextVk, data, size, offset));
}
// Update conversions
......@@ -434,10 +539,10 @@ angle::Result BufferVk::setDataImpl(ContextVk *contextVk,
return angle::Result::Continue;
}
angle::Result BufferVk::copyToBuffer(ContextVk *contextVk,
vk::BufferHelper *destBuffer,
uint32_t copyCount,
const VkBufferCopy *copies)
angle::Result BufferVk::copyToBufferImpl(ContextVk *contextVk,
vk::BufferHelper *destBuffer,
uint32_t copyCount,
const VkBufferCopy *copies)
{
vk::CommandBuffer *commandBuffer;
ANGLE_TRY(contextVk->onBufferWrite(VK_ACCESS_TRANSFER_WRITE_BIT, destBuffer));
......
......@@ -100,13 +100,13 @@ class BufferVk : public BufferImpl
VkDeviceSize length,
GLbitfield access,
void **mapPtr);
void unmapImpl(ContextVk *contextVk);
angle::Result unmapImpl(ContextVk *contextVk);
// Calls copyBuffer internally.
angle::Result copyToBuffer(ContextVk *contextVk,
vk::BufferHelper *destBuffer,
uint32_t copyCount,
const VkBufferCopy *copies);
angle::Result copyToBufferImpl(ContextVk *contextVk,
vk::BufferHelper *destBuffer,
uint32_t copyCount,
const VkBufferCopy *copies);
ConversionBuffer *getVertexConversionBuffer(RendererVk *renderer,
angle::FormatID formatID,
......@@ -116,6 +116,29 @@ class BufferVk : public BufferImpl
private:
void initializeStagingBuffer(ContextVk *contextVk, gl::BufferBinding target, size_t size);
angle::Result initializeShadowBuffer(ContextVk *contextVk,
gl::BufferBinding target,
size_t size);
ANGLE_INLINE uint8_t *getShadowBuffer(size_t offset)
{
return (mShadowBuffer.getCurrentBuffer() + offset);
}
ANGLE_INLINE const uint8_t *getShadowBuffer(size_t offset) const
{
return (mShadowBuffer.getCurrentBuffer() + offset);
}
void updateShadowBuffer(const uint8_t *data, size_t size, size_t offset);
angle::Result directUpdate(ContextVk *contextVk,
const uint8_t *data,
size_t size,
size_t offset);
angle::Result stagedUpdate(ContextVk *contextVk,
const uint8_t *data,
size_t size,
size_t offset);
angle::Result setDataImpl(ContextVk *contextVk,
const uint8_t *data,
size_t size,
......@@ -145,6 +168,13 @@ class BufferVk : public BufferImpl
// All staging buffer support is provided by a DynamicBuffer.
vk::DynamicBuffer mStagingBuffer;
// For GPU-read only buffers glMap* latency is reduced by maintaining a copy
// of the buffer which is writeable only by the CPU. The contents are updated on all
// glData/glSubData/glCopy calls. With this, a glMap* call becomes a non-blocking
// operation by elimnating the need to wait on any recorded or in-flight GPU commands.
// We use DynamicShadowBuffer class to encapsulate all the bookeeping logic.
vk::DynamicShadowBuffer mShadowBuffer;
// A cache of converted vertex data.
std::vector<VertexConversionBuffer> mVertexConversionBuffers;
};
......
......@@ -1000,7 +1000,7 @@ angle::Result ContextVk::setupIndexedDraw(const gl::Context *context,
const size_t byteCount = static_cast<size_t>(elementArrayBuffer->getSize()) -
reinterpret_cast<uintptr_t>(indices);
ANGLE_TRY(mVertexArray->convertIndexBufferCPU(this, indexType, byteCount, src));
bufferVk->unmapImpl(this);
ANGLE_TRY(bufferVk->unmapImpl(this));
}
else
{
......
......@@ -31,6 +31,25 @@ angle::Result Resource::finishRunningCommands(ContextVk *contextVk)
return contextVk->finishToSerial(mUse.getSerial());
}
angle::Result Resource::waitForIdle(ContextVk *contextVk)
{
// If there are pending commands for the resource, flush them.
if (usedInRecordedCommands())
{
ANGLE_TRY(contextVk->flushImpl(nullptr));
}
// Make sure the driver is done with the resource.
if (usedInRunningCommands(contextVk->getLastCompletedQueueSerial()))
{
ANGLE_TRY(finishRunningCommands(contextVk));
}
ASSERT(!isCurrentlyInUse(contextVk->getLastCompletedQueueSerial()));
return angle::Result::Continue;
}
// SharedGarbage implementation.
SharedGarbage::SharedGarbage() = default;
......
......@@ -179,6 +179,9 @@ class Resource : angle::NonCopyable
// Ensures the driver is caught up to this resource and it is only in use by ANGLE.
angle::Result finishRunningCommands(ContextVk *contextVk);
// Complete all recorded and in-flight commands involving this resource
angle::Result waitForIdle(ContextVk *contextVk);
// Adds the resource to a resource use list.
void retain(ResourceUseList *resourceUseList);
......
......@@ -283,7 +283,7 @@ angle::Result TextureVk::setSubImageImpl(const gl::Context *context,
gl::Offset(area.x, area.y, area.z), formatInfo, unpack, type, source, vkFormat,
inputRowPitch, inputDepthPitch, inputSkipBytes));
unpackBufferVk->unmapImpl(contextVk);
ANGLE_TRY(unpackBufferVk->unmapImpl(contextVk));
}
}
else if (pixels)
......
......@@ -425,7 +425,7 @@ angle::Result VertexArrayVk::convertVertexBufferCPU(ContextVk *contextVk,
0, numVertices, binding.getStride(), srcFormatSize,
vertexFormat.vertexLoadFunction, &mCurrentArrayBuffers[attribIndex],
&conversion->lastAllocationOffset, 1));
srcBuffer->unmapImpl(contextVk);
ANGLE_TRY(srcBuffer->unmapImpl(contextVk));
ASSERT(conversion->dirty);
conversion->dirty = false;
......@@ -757,7 +757,7 @@ angle::Result VertexArrayVk::updateStreamedAttribs(const gl::Context *context,
&mCurrentArrayBufferOffsets[attribIndex], divisor));
if (bufferVk)
{
bufferVk->unmapImpl(contextVk);
ANGLE_TRY(bufferVk->unmapImpl(contextVk));
}
}
else
......
......@@ -744,6 +744,69 @@ void DynamicBuffer::reset()
mLastFlushOrInvalidateOffset = 0;
}
// DynamicShadowBuffer implementation.
DynamicShadowBuffer::DynamicShadowBuffer() : mInitialSize(0), mSize(0) {}
DynamicShadowBuffer::DynamicShadowBuffer(DynamicShadowBuffer &&other)
: mInitialSize(other.mInitialSize), mSize(other.mSize), mBuffer(std::move(other.mBuffer))
{}
void DynamicShadowBuffer::init(size_t initialSize)
{
mInitialSize = initialSize;
}
DynamicShadowBuffer::~DynamicShadowBuffer()
{
ASSERT(mBuffer.empty());
}
angle::Result DynamicShadowBuffer::allocate(size_t sizeInBytes)
{
bool result = true;
// Delete the current buffer, if any
if (!mBuffer.empty())
{
result &= mBuffer.resize(0);
}
// Cache the new size
mSize = std::max(mInitialSize, sizeInBytes);
// Allocate the buffer
result &= mBuffer.resize(mSize);
// If allocation failed, release the buffer and return error.
if (!result)
{
release();
return angle::Result::Stop;
}
return angle::Result::Continue;
}
void DynamicShadowBuffer::release()
{
reset();
if (!mBuffer.empty())
{
(void)mBuffer.resize(0);
}
}
void DynamicShadowBuffer::destroy(VkDevice device)
{
release();
}
void DynamicShadowBuffer::reset()
{
mSize = 0;
}
// DescriptorPoolHelper implementation.
DescriptorPoolHelper::DescriptorPoolHelper() : mFreeDescriptorSets(0) {}
......@@ -1371,7 +1434,7 @@ angle::Result LineLoopHelper::getIndexBufferForElementArrayBuffer(ContextVk *con
ANGLE_TRY(streamIndices(contextVk, glIndexType, indexCount,
static_cast<const uint8_t *>(srcDataMapping) + elementArrayOffset,
bufferOut, bufferOffsetOut, indexCountOut));
elementArrayBufferVk->unmapImpl(contextVk);
ANGLE_TRY(elementArrayBufferVk->unmapImpl(contextVk));
return angle::Result::Continue;
}
......@@ -1396,7 +1459,7 @@ angle::Result LineLoopHelper::getIndexBufferForElementArrayBuffer(ContextVk *con
if (contextVk->getRenderer()->getFeatures().extraCopyBufferRegion.enabled)
copies.push_back({sourceOffset, *bufferOffsetOut + (unitCount + 1) * unitSize, 1});
ANGLE_TRY(elementArrayBufferVk->copyToBuffer(
ANGLE_TRY(elementArrayBufferVk->copyToBufferImpl(
contextVk, *bufferOut, static_cast<uint32_t>(copies.size()), copies.data()));
ANGLE_TRY(mDynamicIndexBuffer.flush(contextVk));
return angle::Result::Continue;
......@@ -3594,7 +3657,7 @@ angle::Result ImageHelper::readPixels(ContextVk *contextVk,
uint8_t *dest = static_cast<uint8_t *>(mapPtr) + reinterpret_cast<ptrdiff_t>(pixels);
PackPixels(packPixelsParams, *readFormat, area.width * readFormat->pixelBytes,
readPixelBuffer, static_cast<uint8_t *>(dest));
packBufferVk->unmapImpl(contextVk);
ANGLE_TRY(packBufferVk->unmapImpl(contextVk));
}
else
{
......
......@@ -9,6 +9,7 @@
#ifndef LIBANGLE_RENDERER_VULKAN_VK_HELPERS_H_
#define LIBANGLE_RENDERER_VULKAN_VK_HELPERS_H_
#include "common/MemoryBuffer.h"
#include "libANGLE/renderer/vulkan/ResourceVk.h"
#include "libANGLE/renderer/vulkan/vk_cache_utils.h"
......@@ -121,6 +122,77 @@ class DynamicBuffer : angle::NonCopyable
std::vector<BufferHelper *> mBufferFreeList;
};
// Based off of the DynamicBuffer class, DynamicShadowBuffer provides
// a similar conceptually infinitely long buffer that will only be written
// to and read by the CPU. This can be used to provide CPU cached copies of
// GPU-read only buffers. The value add here is that when an app requests
// CPU access to a buffer we can fullfil such a request in O(1) time since
// we don't need to wait for GPU to be done with in-flight commands.
//
// The hidden cost here is that any operation that updates a buffer, either
// through a buffer sub data update or a buffer-to-buffer copy will have an
// additional overhead of having to update its CPU only buffer
class DynamicShadowBuffer : public angle::NonCopyable
{
public:
DynamicShadowBuffer();
DynamicShadowBuffer(DynamicShadowBuffer &&other);
~DynamicShadowBuffer();
// Initialize the DynamicShadowBuffer.
void init(size_t initialSize);
// Returns whether this DynamicShadowBuffer is active
ANGLE_INLINE bool valid() { return (mSize != 0); }
// This call will actually allocate a new CPU only memory from the heap.
// The size can be different than the one specified during `init`.
angle::Result allocate(size_t sizeInBytes);
ANGLE_INLINE void updateData(const uint8_t *data, size_t size, size_t offset)
{
ASSERT(!mBuffer.empty());
// Memcopy data into the buffer
memcpy((mBuffer.data() + offset), data, size);
}
// Map the CPU only buffer and return the pointer. We map the entire buffer for now.
ANGLE_INLINE void map(size_t offset, void **mapPtr)
{
ASSERT(mapPtr);
ASSERT(!mBuffer.empty());
*mapPtr = mBuffer.data() + offset;
}
// Unmap the CPU only buffer, NOOP for now
ANGLE_INLINE void unmap() {}
// This releases resources when they might currently be in use.
void release();
// This frees resources immediately.
void destroy(VkDevice device);
ANGLE_INLINE uint8_t *getCurrentBuffer()
{
ASSERT(!mBuffer.empty());
return mBuffer.data();
}
ANGLE_INLINE const uint8_t *getCurrentBuffer() const
{
ASSERT(!mBuffer.empty());
return mBuffer.data();
}
private:
void reset();
size_t mInitialSize;
size_t mSize;
angle::MemoryBuffer mBuffer;
};
// Uses DescriptorPool to allocate descriptor sets as needed. If a descriptor pool becomes full, we
// allocate new pools internally as needed. RendererVk takes care of the lifetime of the discarded
// pools. Note that we used a fixed layout for descriptor pools in ANGLE. Uniform buffers must
......
......@@ -411,6 +411,166 @@ TEST_P(BufferDataTestES3, BufferResizing)
EXPECT_GL_NO_ERROR();
}
// Test to verify mapping a buffer after copying to it contains flushed/updated data
TEST_P(BufferDataTestES3, CopyBufferSubDataMapReadTest)
{
const char simpleVertex[] = R"(attribute vec2 position;
attribute vec4 color;
varying vec4 vColor;
void main()
{
gl_Position = vec4(position, 0, 1);
vColor = color;
}
)";
const char simpleFragment[] = R"(precision mediump float;
varying vec4 vColor;
void main()
{
gl_FragColor = vColor;
}
)";
const uint32_t numComponents = 3;
const uint32_t width = 4;
const uint32_t height = 4;
const size_t numElements = width * height * numComponents;
std::vector<uint8_t> srcData(numElements);
std::vector<uint8_t> dstData(numElements);
for (uint8_t i = 0; i < srcData.size(); i++)
{
srcData[i] = 128;
}
for (uint8_t i = 0; i < dstData.size(); i++)
{
dstData[i] = 0;
}
GLBuffer srcBuffer;
GLBuffer dstBuffer;
glBindBuffer(GL_ARRAY_BUFFER, srcBuffer);
glBufferData(GL_ARRAY_BUFFER, srcData.size(), srcData.data(), GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, dstBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, dstData.size(), dstData.data(), GL_STATIC_READ);
ASSERT_GL_NO_ERROR();
ANGLE_GL_PROGRAM(program, simpleVertex, simpleFragment);
glUseProgram(program);
GLint colorLoc = glGetAttribLocation(program, "color");
ASSERT_NE(-1, colorLoc);
glBindBuffer(GL_ARRAY_BUFFER, srcBuffer);
glVertexAttribPointer(colorLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
glEnableVertexAttribArray(colorLoc);
drawQuad(program, "position", 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_PIXEL_UNPACK_BUFFER, 0, 0, numElements);
// With GL_MAP_READ_BIT, we expect the data to be flushed and updated to match srcData
uint8_t *data = reinterpret_cast<uint8_t *>(
glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, numElements, GL_MAP_READ_BIT));
EXPECT_GL_NO_ERROR();
for (size_t i = 0; i < numElements; ++i)
{
EXPECT_EQ(srcData[i], data[i]);
}
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
EXPECT_GL_NO_ERROR();
}
// Test to verify mapping a buffer after copying to it contains expected data
// with GL_MAP_UNSYNCHRONIZED_BIT
TEST_P(BufferDataTestES3, MapBufferUnsynchronizedReadTest)
{
const char simpleVertex[] = R"(attribute vec2 position;
attribute vec4 color;
varying vec4 vColor;
void main()
{
gl_Position = vec4(position, 0, 1);
vColor = color;
}
)";
const char simpleFragment[] = R"(precision mediump float;
varying vec4 vColor;
void main()
{
gl_FragColor = vColor;
}
)";
const uint32_t numComponents = 3;
const uint32_t width = 4;
const uint32_t height = 4;
const size_t numElements = width * height * numComponents;
std::vector<uint8_t> srcData(numElements);
std::vector<uint8_t> dstData(numElements);
for (uint8_t i = 0; i < srcData.size(); i++)
{
srcData[i] = 128;
}
for (uint8_t i = 0; i < dstData.size(); i++)
{
dstData[i] = 0;
}
GLBuffer srcBuffer;
GLBuffer dstBuffer;
glBindBuffer(GL_ARRAY_BUFFER, srcBuffer);
glBufferData(GL_ARRAY_BUFFER, srcData.size(), srcData.data(), GL_STATIC_DRAW);
ASSERT_GL_NO_ERROR();
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, dstBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, dstData.size(), dstData.data(), GL_STATIC_READ);
ASSERT_GL_NO_ERROR();
ANGLE_GL_PROGRAM(program, simpleVertex, simpleFragment);
glUseProgram(program);
GLint colorLoc = glGetAttribLocation(program, "color");
ASSERT_NE(-1, colorLoc);
glBindBuffer(GL_ARRAY_BUFFER, srcBuffer);
glVertexAttribPointer(colorLoc, 3, GL_UNSIGNED_BYTE, GL_TRUE, 0, nullptr);
glEnableVertexAttribArray(colorLoc);
drawQuad(program, "position", 0.5f, 1.0f, true);
ASSERT_GL_NO_ERROR();
glCopyBufferSubData(GL_ARRAY_BUFFER, GL_PIXEL_UNPACK_BUFFER, 0, 0, numElements);
// Synchronize.
glFinish();
// Map with GL_MAP_UNSYNCHRONIZED_BIT and overwrite buffers data with srcData
uint8_t *data = reinterpret_cast<uint8_t *>(glMapBufferRange(
GL_PIXEL_UNPACK_BUFFER, 0, numElements, GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT));
EXPECT_GL_NO_ERROR();
memcpy(data, srcData.data(), srcData.size());
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
EXPECT_GL_NO_ERROR();
// Map without GL_MAP_UNSYNCHRONIZED_BIT and read data. We expect it to be srcData
data = reinterpret_cast<uint8_t *>(
glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, numElements, GL_MAP_READ_BIT));
EXPECT_GL_NO_ERROR();
for (size_t i = 0; i < numElements; ++i)
{
EXPECT_EQ(srcData[i], data[i]);
}
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
EXPECT_GL_NO_ERROR();
}
// Verify the functionality of glMapBufferRange()'s GL_MAP_UNSYNCHRONIZED_BIT
// NOTE: On Vulkan, if we ever use memory that's not `VK_MEMORY_PROPERTY_HOST_COHERENT_BIT`, then
// this could incorrectly pass.
......
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