Commit bdecaf33 by Le Hoang Quyen Committed by Commit Bot

Metal: Implement PBO.

Bug: angleproject:2634 Change-Id: I77f085227298bf46361825d1886e04830dc9987a Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2336558 Commit-Queue: Le Hoang Quyen <le.hoang.q@gmail.com> Reviewed-by: 's avatarJonah Ryan-Davis <jonahr@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent 7b7e52fa
......@@ -28,7 +28,7 @@ namespace rx
// Conversion buffers hold translated index and vertex data.
struct ConversionBufferMtl
{
ConversionBufferMtl(const gl::Context *context, size_t initialSize, size_t alignment);
ConversionBufferMtl(ContextMtl *context, size_t initialSize, size_t alignment);
~ConversionBufferMtl();
// One state value determines if we need to re-stream vertex data.
......@@ -42,13 +42,24 @@ struct ConversionBufferMtl
size_t convertedOffset;
};
struct VertexConversionBufferMtl : public ConversionBufferMtl
{
VertexConversionBufferMtl(ContextMtl *context,
angle::FormatID formatIDIn,
GLuint strideIn,
size_t offsetIn);
// The conversion is identified by the triple of {format, stride, offset}.
angle::FormatID formatID;
GLuint stride;
size_t offset;
};
struct IndexConversionBufferMtl : public ConversionBufferMtl
{
IndexConversionBufferMtl(const gl::Context *context,
gl::DrawElementsType type,
size_t offsetIn);
IndexConversionBufferMtl(ContextMtl *context, gl::DrawElementsType elemType, size_t offsetIn);
const gl::DrawElementsType type;
const gl::DrawElementsType elemType;
const size_t offset;
};
......@@ -108,25 +119,32 @@ class BufferMtl : public BufferImpl, public BufferHolderMtl
bool primitiveRestartEnabled,
gl::IndexRange *outRange) override;
angle::Result getFirstLastIndices(gl::DrawElementsType type,
void onDataChanged() override;
angle::Result getFirstLastIndices(ContextMtl *contextMtl,
gl::DrawElementsType type,
size_t offset,
size_t count,
std::pair<uint32_t, uint32_t> *outIndices) const;
std::pair<uint32_t, uint32_t> *outIndices);
const uint8_t *getClientShadowCopyData(const gl::Context *context);
const uint8_t *getClientShadowCopyData(ContextMtl *contextMtl);
ConversionBufferMtl *getVertexConversionBuffer(const gl::Context *context,
ConversionBufferMtl *getVertexConversionBuffer(ContextMtl *context,
angle::FormatID formatID,
GLuint stride,
size_t offset);
IndexConversionBufferMtl *getIndexConversionBuffer(const gl::Context *context,
gl::DrawElementsType type,
IndexConversionBufferMtl *getIndexConversionBuffer(ContextMtl *context,
gl::DrawElementsType elemType,
size_t offset);
size_t size() const { return static_cast<size_t>(mState.getSize()); }
private:
angle::Result setDataImpl(const gl::Context *context,
const void *data,
size_t size,
gl::BufferUsage usage);
angle::Result setSubDataImpl(const gl::Context *context,
const void *data,
size_t size,
......@@ -138,6 +156,9 @@ class BufferMtl : public BufferImpl, public BufferHolderMtl
void markConversionBuffersDirty();
void clearConversionBuffers();
bool clientShadowCopyDataNeedSync(ContextMtl *contextMtl);
void ensureShadowCopySyncedFromGPU(ContextMtl *contextMtl);
uint8_t *syncAndObtainShadowCopy(ContextMtl *contextMtl);
// Client side shadow buffer
angle::MemoryBuffer mShadowCopy;
......@@ -145,21 +166,8 @@ class BufferMtl : public BufferImpl, public BufferHolderMtl
// GPU side buffers pool
mtl::BufferPool mBufferPool;
struct VertexConversionBuffer : public ConversionBufferMtl
{
VertexConversionBuffer(const gl::Context *context,
angle::FormatID formatIDIn,
GLuint strideIn,
size_t offsetIn);
// The conversion is identified by the triple of {format, stride, offset}.
angle::FormatID formatID;
GLuint stride;
size_t offset;
};
// A cache of converted vertex data.
std::vector<VertexConversionBuffer> mVertexConversionBuffers;
std::vector<VertexConversionBufferMtl> mVertexConversionBuffers;
std::vector<IndexConversionBufferMtl> mIndexConversionBuffers;
};
......
......@@ -12,6 +12,7 @@
#include "common/debug.h"
#include "common/utilities.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
namespace rx
{
......@@ -41,42 +42,37 @@ angle::Result GetFirstLastIndices(const IndexType *indices,
} // namespace
// ConversionBufferMtl implementation.
ConversionBufferMtl::ConversionBufferMtl(const gl::Context *context,
ConversionBufferMtl::ConversionBufferMtl(ContextMtl *contextMtl,
size_t initialSize,
size_t alignment)
: dirty(true), convertedBuffer(nullptr), convertedOffset(0)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
data.initialize(contextMtl, initialSize, alignment);
}
ConversionBufferMtl::~ConversionBufferMtl() = default;
// IndexConversionBufferMtl implementation.
IndexConversionBufferMtl::IndexConversionBufferMtl(const gl::Context *context,
gl::DrawElementsType typeIn,
IndexConversionBufferMtl::IndexConversionBufferMtl(ContextMtl *context,
gl::DrawElementsType elemTypeIn,
size_t offsetIn)
: ConversionBufferMtl(context,
kConvertedElementArrayBufferInitialSize,
mtl::kIndexBufferOffsetAlignment),
type(typeIn),
elemType(elemTypeIn),
offset(offsetIn)
{}
// BufferMtl::VertexConversionBuffer implementation.
BufferMtl::VertexConversionBuffer::VertexConversionBuffer(const gl::Context *context,
angle::FormatID formatIDIn,
GLuint strideIn,
size_t offsetIn)
// VertexConversionBufferMtl implementation.
VertexConversionBufferMtl::VertexConversionBufferMtl(ContextMtl *context,
angle::FormatID formatIDIn,
GLuint strideIn,
size_t offsetIn)
: ConversionBufferMtl(context, 0, mtl::kVertexAttribBufferStrideAlignment),
formatID(formatIDIn),
stride(strideIn),
offset(offsetIn)
{
// Due to Metal's strict requirement for offset and stride, we need to always allocate new
// buffer for every conversion.
data.setAlwaysAllocateNewBuffer(true);
}
{}
// BufferMtl implementation
BufferMtl::BufferMtl(const gl::BufferState &state)
......@@ -101,52 +97,7 @@ angle::Result BufferMtl::setData(const gl::Context *context,
size_t intendedSize,
gl::BufferUsage usage)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
// Invalidate conversion buffers
if (mState.getSize() != static_cast<GLint64>(intendedSize))
{
clearConversionBuffers();
}
else
{
markConversionBuffersDirty();
}
size_t adjustedSize = std::max<size_t>(1, intendedSize);
if (!mShadowCopy.size() || intendedSize > mShadowCopy.size() || usage != mState.getUsage())
{
// Re-create the buffer
ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(adjustedSize), GL_OUT_OF_MEMORY);
size_t maxBuffers;
switch (usage)
{
case gl::BufferUsage::StaticCopy:
case gl::BufferUsage::StaticDraw:
case gl::BufferUsage::StaticRead:
maxBuffers = 1; // static buffer doesn't need high speed data update
break;
default:
// dynamic buffer, allow up to 2 update per frame/encoding without
// waiting for GPU.
maxBuffers = 2;
break;
}
mBufferPool.initialize(contextMtl, adjustedSize, 1, maxBuffers);
}
// Transfer data to shadow copy buffer
if (data)
{
auto ptr = static_cast<const uint8_t *>(data);
std::copy(ptr, ptr + intendedSize, mShadowCopy.data());
}
// Transfer data from shadow copy buffer to GPU buffer.
return commitShadowCopy(context, adjustedSize);
return setDataImpl(context, data, intendedSize, usage);
}
angle::Result BufferMtl::setSubData(const gl::Context *context,
......@@ -169,19 +120,21 @@ angle::Result BufferMtl::copySubData(const gl::Context *context,
return angle::Result::Continue;
}
ASSERT(mShadowCopy.size());
auto srcMtl = GetAs<BufferMtl>(source);
ContextMtl *contextMtl = mtl::GetImpl(context);
auto srcMtl = GetAs<BufferMtl>(source);
// NOTE(hqle): use blit command.
return setSubDataImpl(context, srcMtl->getClientShadowCopyData(context) + sourceOffset, size,
return setSubDataImpl(context, srcMtl->getClientShadowCopyData(contextMtl) + sourceOffset, size,
destOffset);
}
angle::Result BufferMtl::map(const gl::Context *context, GLenum access, void **mapPtr)
{
ASSERT(mShadowCopy.size());
return mapRange(context, 0, size(), 0, mapPtr);
GLbitfield mapRangeAccess = 0;
if ((access & GL_WRITE_ONLY_OES) != 0 || (access & GL_READ_WRITE) != 0)
{
mapRangeAccess |= GL_MAP_WRITE_BIT;
}
return mapRange(context, 0, size(), mapRangeAccess, mapPtr);
}
angle::Result BufferMtl::mapRange(const gl::Context *context,
......@@ -190,12 +143,15 @@ angle::Result BufferMtl::mapRange(const gl::Context *context,
GLbitfield access,
void **mapPtr)
{
ASSERT(mShadowCopy.size());
if (access & GL_MAP_INVALIDATE_BUFFER_BIT)
{
ANGLE_TRY(setDataImpl(context, nullptr, size(), mState.getUsage()));
}
// NOTE(hqle): use access flags
if (mapPtr)
{
*mapPtr = mShadowCopy.data() + offset;
ContextMtl *contextMtl = mtl::GetImpl(context);
*mapPtr = syncAndObtainShadowCopy(contextMtl) + offset;
}
return angle::Result::Continue;
......@@ -221,21 +177,22 @@ angle::Result BufferMtl::getIndexRange(const gl::Context *context,
{
ASSERT(mShadowCopy.size());
const uint8_t *indices = mShadowCopy.data() + offset;
const uint8_t *indices = getClientShadowCopyData(mtl::GetImpl(context)) + offset;
*outRange = gl::ComputeIndexRange(type, indices, count, primitiveRestartEnabled);
return angle::Result::Continue;
}
angle::Result BufferMtl::getFirstLastIndices(gl::DrawElementsType type,
angle::Result BufferMtl::getFirstLastIndices(ContextMtl *contextMtl,
gl::DrawElementsType type,
size_t offset,
size_t count,
std::pair<uint32_t, uint32_t> *outIndices) const
std::pair<uint32_t, uint32_t> *outIndices)
{
ASSERT(mShadowCopy.size());
const uint8_t *indices = mShadowCopy.data() + offset;
const uint8_t *indices = getClientShadowCopyData(contextMtl) + offset;
switch (type)
{
......@@ -255,20 +212,49 @@ angle::Result BufferMtl::getFirstLastIndices(gl::DrawElementsType type,
return angle::Result::Continue;
}
const uint8_t *BufferMtl::getClientShadowCopyData(const gl::Context *context)
void BufferMtl::onDataChanged()
{
markConversionBuffersDirty();
}
/* public */
const uint8_t *BufferMtl::getClientShadowCopyData(ContextMtl *contextMtl)
{
return syncAndObtainShadowCopy(contextMtl);
}
bool BufferMtl::clientShadowCopyDataNeedSync(ContextMtl *contextMtl)
{
return mBuffer->isCPUReadMemDirty();
}
void BufferMtl::ensureShadowCopySyncedFromGPU(ContextMtl *contextMtl)
{
// NOTE(hqle): Support buffer update from GPU.
// Which mean we have to stall the GPU by calling finish and copy
// data back to shadow copy.
if (clientShadowCopyDataNeedSync(contextMtl))
{
// Copy data from GPU to shadow copy.
const uint8_t *ptr = mBuffer->mapReadOnly(contextMtl);
memcpy(mShadowCopy.data(), ptr, size());
mBuffer->unmap(contextMtl);
mBuffer->resetCPUReadMemDirty();
}
}
uint8_t *BufferMtl::syncAndObtainShadowCopy(ContextMtl *contextMtl)
{
ASSERT(mShadowCopy.size());
ensureShadowCopySyncedFromGPU(contextMtl);
return mShadowCopy.data();
}
ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(const gl::Context *context,
ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(ContextMtl *context,
angle::FormatID formatID,
GLuint stride,
size_t offset)
{
for (VertexConversionBuffer &buffer : mVertexConversionBuffers)
for (VertexConversionBufferMtl &buffer : mVertexConversionBuffers)
{
if (buffer.formatID == formatID && buffer.stride == stride && buffer.offset == offset)
{
......@@ -277,33 +263,39 @@ ConversionBufferMtl *BufferMtl::getVertexConversionBuffer(const gl::Context *con
}
mVertexConversionBuffers.emplace_back(context, formatID, stride, offset);
return &mVertexConversionBuffers.back();
ConversionBufferMtl *conv = &mVertexConversionBuffers.back();
const angle::Format &angleFormat = angle::Format::Get(formatID);
conv->data.updateAlignment(context, angleFormat.pixelBytes);
return conv;
}
IndexConversionBufferMtl *BufferMtl::getIndexConversionBuffer(const gl::Context *context,
gl::DrawElementsType type,
IndexConversionBufferMtl *BufferMtl::getIndexConversionBuffer(ContextMtl *context,
gl::DrawElementsType elemType,
size_t offset)
{
for (auto &buffer : mIndexConversionBuffers)
{
if (buffer.type == type && buffer.offset == offset)
if (buffer.elemType == elemType && buffer.offset == offset)
{
return &buffer;
}
}
mIndexConversionBuffers.emplace_back(context, type, offset);
mIndexConversionBuffers.emplace_back(context, elemType, offset);
return &mIndexConversionBuffers.back();
}
void BufferMtl::markConversionBuffersDirty()
{
for (VertexConversionBuffer &buffer : mVertexConversionBuffers)
for (VertexConversionBufferMtl &buffer : mVertexConversionBuffers)
{
buffer.dirty = true;
buffer.dirty = true;
buffer.convertedBuffer = nullptr;
buffer.convertedOffset = 0;
}
for (auto &buffer : mIndexConversionBuffers)
for (IndexConversionBufferMtl &buffer : mIndexConversionBuffers)
{
buffer.dirty = true;
buffer.convertedBuffer = nullptr;
......@@ -317,6 +309,76 @@ void BufferMtl::clearConversionBuffers()
mIndexConversionBuffers.clear();
}
angle::Result BufferMtl::setDataImpl(const gl::Context *context,
const void *data,
size_t intendedSize,
gl::BufferUsage usage)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
// Invalidate conversion buffers
if (mState.getSize() != static_cast<GLint64>(intendedSize))
{
clearConversionBuffers();
}
else
{
markConversionBuffersDirty();
}
size_t adjustedSize = std::max<size_t>(1, intendedSize);
size_t maxBuffers;
switch (usage)
{
case gl::BufferUsage::StaticCopy:
case gl::BufferUsage::StaticDraw:
case gl::BufferUsage::StaticRead:
maxBuffers = 1; // static buffer doesn't need high speed data update
// NOTE(hqle): We don't really need buffer pool in this case. Consider disabling shadow
// copy in this case.
break;
default:
// dynamic buffer, allow up to 2 update per frame/encoding without
// waiting for GPU.
// NOTE(hqle): If buffer size is too large, we should not use buffer pool, instead a
// single buffer should be used.
maxBuffers = 2;
break;
}
// Re-create the buffer
mBuffer = nullptr;
mBufferPool.initialize(contextMtl, adjustedSize, 1, maxBuffers);
// We use shadow copy to maintain consistent data between buffers in pool
ANGLE_MTL_CHECK(contextMtl, mShadowCopy.resize(adjustedSize), GL_OUT_OF_MEMORY);
if (data)
{
// Transfer data to shadow copy buffer
auto ptr = static_cast<const uint8_t *>(data);
std::copy(ptr, ptr + intendedSize, mShadowCopy.data());
// Transfer data from shadow copy buffer to GPU buffer.
ANGLE_TRY(commitShadowCopy(context, adjustedSize));
}
else
{
// This is needed so that first buffer pointer could be available
ANGLE_TRY(commitShadowCopy(context, 0));
}
#ifndef NDEBUG
ANGLE_MTL_OBJC_SCOPE
{
mBuffer->get().label = [NSString stringWithFormat:@"BufferMtl=%p", this];
}
#endif
return angle::Result::Continue;
}
angle::Result BufferMtl::setSubDataImpl(const gl::Context *context,
const void *data,
size_t size,
......@@ -326,18 +388,28 @@ angle::Result BufferMtl::setSubDataImpl(const gl::Context *context,
{
return angle::Result::Continue;
}
ContextMtl *contextMtl = mtl::GetImpl(context);
ASSERT(mShadowCopy.size());
ASSERT(mBuffer);
ContextMtl *contextMtl = mtl::GetImpl(context);
ANGLE_MTL_TRY(contextMtl, offset <= this->size());
ANGLE_MTL_TRY(contextMtl, offset <= mBuffer->size());
auto srcPtr = static_cast<const uint8_t *>(data);
auto sizeToCopy = std::min<size_t>(size, this->size() - offset);
std::copy(srcPtr, srcPtr + sizeToCopy, mShadowCopy.data() + offset);
auto sizeToCopy = std::min<size_t>(size, mBuffer->size() - offset);
markConversionBuffersDirty();
ASSERT(mShadowCopy.size());
// 1. Before copying data from client, we need to synchronize modified data from GPU to shadow
// copy first.
ensureShadowCopySyncedFromGPU(contextMtl);
// 2. Copy data from client to shadow copy.
std::copy(srcPtr, srcPtr + sizeToCopy, mShadowCopy.data() + offset);
// 3. Copy data from shadow copy to GPU.
ANGLE_TRY(commitShadowCopy(context));
return angle::Result::Continue;
......@@ -352,17 +424,25 @@ angle::Result BufferMtl::commitShadowCopy(const gl::Context *context, size_t siz
{
ContextMtl *contextMtl = mtl::GetImpl(context);
uint8_t *ptr = nullptr;
ANGLE_TRY(mBufferPool.allocate(contextMtl, size, &ptr, &mBuffer, nullptr, nullptr));
if (!size)
{
// Skip mapping if size to commit is zero.
// zero size is passed to allocate buffer only.
ANGLE_TRY(mBufferPool.allocate(contextMtl, mShadowCopy.size(), nullptr, &mBuffer, nullptr,
nullptr));
}
else
{
uint8_t *ptr = nullptr;
mBufferPool.releaseInFlightBuffers(contextMtl);
ANGLE_TRY(
mBufferPool.allocate(contextMtl, mShadowCopy.size(), &ptr, &mBuffer, nullptr, nullptr));
std::copy(mShadowCopy.data(), mShadowCopy.data() + size, ptr);
std::copy(mShadowCopy.data(), mShadowCopy.data() + size, ptr);
}
ANGLE_TRY(mBufferPool.commit(contextMtl));
#ifndef NDEBUG
ANGLE_MTL_OBJC_SCOPE { mBuffer->get().label = [NSString stringWithFormat:@"%p", this]; }
#endif
return angle::Result::Continue;
}
......
......@@ -588,7 +588,7 @@ void DisplayMtl::initializeExtensions() const
// Enable this for simple buffer readback testing, but some functionality is missing.
// NOTE(hqle): Support full mapBufferRange extension.
mNativeExtensions.mapBufferOES = true;
mNativeExtensions.mapBufferRange = false;
mNativeExtensions.mapBufferRange = true;
mNativeExtensions.textureStorage = true;
mNativeExtensions.drawBuffers = true;
mNativeExtensions.fragDepth = true;
......@@ -646,6 +646,9 @@ void DisplayMtl::initializeExtensions() const
// GL_APPLE_clip_distance
mNativeExtensions.clipDistanceAPPLE = true;
// GL_NV_pixel_buffer_object
mNativeExtensions.pixelBufferObjectNV = true;
}
void DisplayMtl::initializeTextureCaps() const
......
......@@ -172,6 +172,11 @@ class FramebufferMtl : public FramebufferImpl
const gl::FramebufferAttachment *attachment,
RenderTargetMtl **cachedRenderTarget);
angle::Result readPixelsToPBO(const gl::Context *context,
const gl::Rectangle &area,
const PackPixelsParams &packPixelsParams,
RenderTargetMtl *renderTarget);
// NOTE: we cannot use RenderTargetCache here because it doesn't support separate
// depth & stencil attachments as of now. Separate depth & stencil could be useful to
// save spaces on iOS devices. See doc/PackedDepthStencilSupport.md.
......
......@@ -1068,16 +1068,16 @@ angle::Result FramebufferMtl::readPixelsImpl(const gl::Context *context,
uint8_t *pixels)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
if (packPixelsParams.packBuffer)
{
// NOTE(hqle): PBO is not supported atm
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_OPERATION);
}
if (!renderTarget)
{
return angle::Result::Continue;
}
if (packPixelsParams.packBuffer)
{
return readPixelsToPBO(context, area, packPixelsParams, renderTarget);
}
mtl::TextureRef texture;
if (mBackbuffer)
{
......@@ -1127,4 +1127,114 @@ angle::Result FramebufferMtl::readPixelsImpl(const gl::Context *context,
return angle::Result::Continue;
}
angle::Result FramebufferMtl::readPixelsToPBO(const gl::Context *context,
const gl::Rectangle &area,
const PackPixelsParams &packPixelsParams,
RenderTargetMtl *renderTarget)
{
ASSERT(packPixelsParams.packBuffer);
ASSERT(renderTarget);
ContextMtl *contextMtl = mtl::GetImpl(context);
ANGLE_MTL_CHECK(contextMtl, packPixelsParams.offset <= std::numeric_limits<uint32_t>::max(),
GL_INVALID_OPERATION);
const uint32_t dstBufferOffset = static_cast<uint32_t>(packPixelsParams.offset);
const uint32_t dstBufferRowPitch = packPixelsParams.outputPitch;
const angle::Format &dstAngleFormat = *packPixelsParams.destFormat;
const bool reverseRowOrder = packPixelsParams.reverseRowOrder;
BufferMtl *packBufferMtl = mtl::GetImpl(packPixelsParams.packBuffer);
mtl::BufferRef dstBuffer = packBufferMtl->getCurrentBuffer();
const mtl::Format &readFormat = *renderTarget->getFormat();
const angle::Format &readAngleFormat = readFormat.actualAngleFormat();
mtl::TextureRef texture = renderTarget->getTexture();
if (dstAngleFormat.id != readAngleFormat.id || texture->samples() > 1 ||
(dstBufferOffset % dstAngleFormat.pixelBytes) ||
(dstBufferOffset % mtl::kTextureToBufferBlittingAlignment))
{
const angle::Format *actualDstAngleFormat;
// SRGB is special case: We need to write sRGB values to buffer, not linear values.
switch (readAngleFormat.id)
{
case angle::FormatID::B8G8R8A8_UNORM_SRGB:
case angle::FormatID::R8G8B8_UNORM_SRGB:
case angle::FormatID::R8G8B8A8_UNORM_SRGB:
if (dstAngleFormat.id != readAngleFormat.id)
{
switch (dstAngleFormat.id)
{
case angle::FormatID::B8G8R8A8_UNORM:
actualDstAngleFormat =
&angle::Format::Get(angle::FormatID::B8G8R8A8_UNORM_SRGB);
break;
case angle::FormatID::R8G8B8A8_UNORM:
actualDstAngleFormat =
&angle::Format::Get(angle::FormatID::R8G8B8A8_UNORM_SRGB);
break;
default:
// Unsupported format.
ANGLE_MTL_CHECK(contextMtl, false, GL_INVALID_ENUM);
}
break;
}
OS_FALLTHROUGH;
default:
actualDstAngleFormat = &dstAngleFormat;
}
// Use compute shader
mtl::CopyPixelsToBufferParams params;
params.buffer = dstBuffer;
params.bufferStartOffset = dstBufferOffset;
params.bufferRowPitch = dstBufferRowPitch;
params.texture = texture;
params.textureArea = area;
params.textureLevel = renderTarget->getLevelIndex();
params.textureSliceOrDeph = renderTarget->getLayerIndex();
params.reverseTextureRowOrder = reverseRowOrder;
ANGLE_TRY(contextMtl->getDisplay()->getUtils().packPixelsFromTextureToBuffer(
contextMtl, *actualDstAngleFormat, params));
}
else
{
// Use blit command encoder
if (!reverseRowOrder)
{
ANGLE_TRY(mtl::ReadTexturePerSliceBytesToBuffer(
context, texture, dstBufferRowPitch, area, renderTarget->getLevelIndex(),
renderTarget->getLayerIndex(), dstBufferOffset, dstBuffer));
}
else
{
gl::Rectangle srcRowRegion(area.x, area.y, area.width, 1);
int startRow = area.y1() - 1;
uint32_t bufferRowOffset = dstBufferOffset;
// Copy pixels row by row
for (int r = startRow, copiedRows = 0; copiedRows < area.height;
++copiedRows, --r, bufferRowOffset += dstBufferRowPitch)
{
srcRowRegion.y = r;
// Read the pixels data to the buffer's row
ANGLE_TRY(mtl::ReadTexturePerSliceBytesToBuffer(
context, texture, dstBufferRowPitch, srcRowRegion,
renderTarget->getLevelIndex(), renderTarget->getLayerIndex(), bufferRowOffset,
dstBuffer));
}
}
}
return angle::Result::Continue;
}
}
......@@ -16,6 +16,7 @@
#include "common/mathutil.h"
#include "image_util/imageformats.h"
#include "libANGLE/Surface.h"
#include "libANGLE/renderer/metal/BufferMtl.h"
#include "libANGLE/renderer/metal/ContextMtl.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
#include "libANGLE/renderer/metal/FrameBufferMtl.h"
......@@ -1283,10 +1284,7 @@ angle::Result TextureMtl::setSubImageImpl(const gl::Context *context,
gl::Buffer *unpackBuffer,
const uint8_t *oriPixels)
{
// NOTE(hqle): Support PBO
ASSERT(!unpackBuffer);
if (!oriPixels)
if (!oriPixels && !unpackBuffer)
{
return angle::Result::Continue;
}
......@@ -1354,13 +1352,47 @@ angle::Result TextureMtl::setPerSliceSubImage(const gl::Context *context,
unpackBuffer, pixels, image);
}
// NOTE(hqle): Support PBO
ASSERT(!unpackBuffer);
// No conversion needed.
ContextMtl *contextMtl = mtl::GetImpl(context);
// Upload texture data directly
ANGLE_TRY(UploadTextureContents(context, mFormat.actualAngleFormat(), mtlArea, 0, slice, pixels,
pixelsRowPitch, pixelsDepthPitch, image));
if (unpackBuffer)
{
uintptr_t offset = reinterpret_cast<uintptr_t>(pixels);
if (offset % mFormat.actualAngleFormat().pixelBytes)
{
// offset is not divisible by pixelByte, use convertAndSetPerSliceSubImage() function.
return convertAndSetPerSliceSubImage(context, slice, mtlArea, internalFormat, type,
pixelsAngleFormat, pixelsRowPitch,
pixelsDepthPitch, unpackBuffer, pixels, image);
}
BufferMtl *unpackBufferMtl = mtl::GetImpl(unpackBuffer);
if (mFormat.hasDepthAndStencilBits())
{
// NOTE(hqle): packed depth & stencil texture cannot copy from buffer directly, needs
// to split its depth & stencil data and copy separately.
const uint8_t *clientData = unpackBufferMtl->getClientShadowCopyData(contextMtl);
clientData += offset;
ANGLE_TRY(UploadTextureContents(context, mFormat.actualAngleFormat(), mtlArea, 0, slice,
clientData, pixelsRowPitch, pixelsDepthPitch, image));
}
else
{
// Use blit encoder to copy
mtl::BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->copyBufferToTexture(
unpackBufferMtl->getCurrentBuffer(), offset, pixelsRowPitch, pixelsDepthPitch,
mtlArea.size, image, slice, 0, mtlArea.origin,
mFormat.isPVRTC() ? mtl::kBlitOptionRowLinearPVRTC : MTLBlitOptionNone);
}
}
else
{
// Upload texture data directly
ANGLE_TRY(UploadTextureContents(context, mFormat.actualAngleFormat(), mtlArea, 0, slice,
pixels, pixelsRowPitch, pixelsDepthPitch, image));
}
return angle::Result::Continue;
}
......@@ -1380,78 +1412,125 @@ angle::Result TextureMtl::convertAndSetPerSliceSubImage(const gl::Context *conte
ContextMtl *contextMtl = mtl::GetImpl(context);
// NOTE(hqle): Support PBO
ASSERT(!unpackBuffer);
if (unpackBuffer)
{
ANGLE_MTL_CHECK(contextMtl,
reinterpret_cast<uintptr_t>(pixels) <= std::numeric_limits<uint32_t>::max(),
GL_INVALID_OPERATION);
LoadImageFunctionInfo loadFunctionInfo =
mFormat.textureLoadFunctions ? mFormat.textureLoadFunctions(type) : LoadImageFunctionInfo();
const angle::Format &dstFormat = angle::Format::Get(mFormat.actualFormatId);
const size_t dstRowPitch = dstFormat.pixelBytes * mtlArea.size.width;
uint32_t offset = static_cast<uint32_t>(reinterpret_cast<uintptr_t>(pixels));
// Check if original image data is compressed:
if (mFormat.intendedAngleFormat().isBlock)
BufferMtl *unpackBufferMtl = mtl::GetImpl(unpackBuffer);
if (!mFormat.getCaps().writable || mFormat.hasDepthOrStencilBits() ||
mFormat.intendedAngleFormat().isBlock)
{
// Unsupported format, use CPU path.
const uint8_t *clientData = unpackBufferMtl->getClientShadowCopyData(contextMtl);
clientData += offset;
ANGLE_TRY(convertAndSetPerSliceSubImage(context, slice, mtlArea, internalFormat, type,
pixelsAngleFormat, pixelsRowPitch,
pixelsDepthPitch, nullptr, clientData, image));
}
else
{
// Use compute shader
mtl::CopyPixelsFromBufferParams params;
params.buffer = unpackBufferMtl->getCurrentBuffer();
params.bufferStartOffset = offset;
params.bufferRowPitch = static_cast<uint32_t>(pixelsRowPitch);
params.bufferDepthPitch = static_cast<uint32_t>(pixelsDepthPitch);
params.texture = image;
params.textureArea = mtl::MTLRegionToGLBox(mtlArea);
// If texture is not array, slice must be zero, if texture is array, mtlArea.origin.z
// must be zero.
// This is because this function uses Metal convention: where slice is only used for
// array textures, and z layer of mtlArea.origin is only used for 3D textures.
ASSERT(slice == 0 || params.textureArea.z == 0);
// For mtl::RenderUtils we convert to OpenGL convention: z layer is used as either array
// texture's slice or 3D texture's layer index.
params.textureArea.z += slice;
ANGLE_TRY(contextMtl->getDisplay()->getUtils().unpackPixelsFromBufferToTexture(
contextMtl, pixelsAngleFormat, params));
}
} // if (unpackBuffer)
else
{
ASSERT(loadFunctionInfo.loadFunction);
LoadImageFunctionInfo loadFunctionInfo = mFormat.textureLoadFunctions
? mFormat.textureLoadFunctions(type)
: LoadImageFunctionInfo();
const angle::Format &dstFormat = angle::Format::Get(mFormat.actualFormatId);
const size_t dstRowPitch = dstFormat.pixelBytes * mtlArea.size.width;
// Check if original image data is compressed:
if (mFormat.intendedAngleFormat().isBlock)
{
ASSERT(loadFunctionInfo.loadFunction);
// Need to create a buffer to hold entire decompressed image.
const size_t dstDepthPitch = dstRowPitch * mtlArea.size.height;
angle::MemoryBuffer decompressBuf;
ANGLE_CHECK_GL_ALLOC(contextMtl, decompressBuf.resize(dstDepthPitch * mtlArea.size.depth));
// Need to create a buffer to hold entire decompressed image.
const size_t dstDepthPitch = dstRowPitch * mtlArea.size.height;
angle::MemoryBuffer decompressBuf;
ANGLE_CHECK_GL_ALLOC(contextMtl,
decompressBuf.resize(dstDepthPitch * mtlArea.size.depth));
// Decompress
loadFunctionInfo.loadFunction(mtlArea.size.width, mtlArea.size.height, mtlArea.size.depth,
pixels, pixelsRowPitch, pixelsDepthPitch,
decompressBuf.data(), dstRowPitch, dstDepthPitch);
// Decompress
loadFunctionInfo.loadFunction(
mtlArea.size.width, mtlArea.size.height, mtlArea.size.depth, pixels, pixelsRowPitch,
pixelsDepthPitch, decompressBuf.data(), dstRowPitch, dstDepthPitch);
// Upload to texture
ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlArea, 0, slice, decompressBuf.data(),
dstRowPitch, dstDepthPitch, image));
} // if (mFormat.intendedAngleFormat().isBlock)
else
{
// Create scratch row buffer
angle::MemoryBuffer conversionRow;
ANGLE_CHECK_GL_ALLOC(contextMtl, conversionRow.resize(dstRowPitch));
// Convert row by row:
MTLRegion mtlRow = mtlArea;
mtlRow.size.height = mtlRow.size.depth = 1;
for (NSUInteger d = 0; d < mtlArea.size.depth; ++d)
// Upload to texture
ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlArea, 0, slice,
decompressBuf.data(), dstRowPitch, dstDepthPitch,
image));
} // if (mFormat.intendedAngleFormat().isBlock)
else
{
mtlRow.origin.z = mtlArea.origin.z + d;
for (NSUInteger r = 0; r < mtlArea.size.height; ++r)
// Create scratch row buffer
angle::MemoryBuffer conversionRow;
ANGLE_CHECK_GL_ALLOC(contextMtl, conversionRow.resize(dstRowPitch));
// Convert row by row:
MTLRegion mtlRow = mtlArea;
mtlRow.size.height = mtlRow.size.depth = 1;
for (NSUInteger d = 0; d < mtlArea.size.depth; ++d)
{
const uint8_t *psrc = pixels + d * pixelsDepthPitch + r * pixelsRowPitch;
mtlRow.origin.y = mtlArea.origin.y + r;
// Convert pixels
if (loadFunctionInfo.loadFunction)
{
loadFunctionInfo.loadFunction(mtlRow.size.width, 1, 1, psrc, pixelsRowPitch, 0,
conversionRow.data(), dstRowPitch, 0);
}
else if (mFormat.hasDepthOrStencilBits())
mtlRow.origin.z = mtlArea.origin.z + d;
for (NSUInteger r = 0; r < mtlArea.size.height; ++r)
{
ConvertDepthStencilData(mtlRow.size, pixelsAngleFormat, pixelsRowPitch, 0, psrc,
dstFormat, nullptr, dstRowPitch, 0,
conversionRow.data());
const uint8_t *psrc = pixels + d * pixelsDepthPitch + r * pixelsRowPitch;
mtlRow.origin.y = mtlArea.origin.y + r;
// Convert pixels
if (loadFunctionInfo.loadFunction)
{
loadFunctionInfo.loadFunction(mtlRow.size.width, 1, 1, psrc, pixelsRowPitch,
0, conversionRow.data(), dstRowPitch, 0);
}
else if (mFormat.hasDepthOrStencilBits())
{
ConvertDepthStencilData(mtlRow.size, pixelsAngleFormat, pixelsRowPitch, 0,
psrc, dstFormat, nullptr, dstRowPitch, 0,
conversionRow.data());
}
else
{
CopyImageCHROMIUM(psrc, pixelsRowPitch, pixelsAngleFormat.pixelBytes, 0,
pixelsAngleFormat.pixelReadFunction, conversionRow.data(),
dstRowPitch, dstFormat.pixelBytes, 0,
dstFormat.pixelWriteFunction, internalFormat.format,
dstFormat.componentType, mtlRow.size.width, 1, 1, false,
false, false);
}
// Upload to texture
ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlRow, 0, slice,
conversionRow.data(), dstRowPitch, 0, image));
}
else
{
CopyImageCHROMIUM(psrc, pixelsRowPitch, pixelsAngleFormat.pixelBytes, 0,
pixelsAngleFormat.pixelReadFunction, conversionRow.data(),
dstRowPitch, dstFormat.pixelBytes, 0,
dstFormat.pixelWriteFunction, internalFormat.format,
dstFormat.componentType, mtlRow.size.width, 1, 1, false,
false, false);
}
// Upload to texture
ANGLE_TRY(UploadTextureContents(context, dstFormat, mtlRow, 0, slice,
conversionRow.data(), dstRowPitch, 0, image));
}
}
} // if (mFormat.intendedAngleFormat().isBlock)
} // if (mFormat.intendedAngleFormat().isBlock)
} // if (unpackBuffer)
return angle::Result::Continue;
}
......
......@@ -513,10 +513,11 @@ angle::Result VertexArrayMtl::convertIndexBuffer(const gl::Context *glContext,
ASSERT((offset % mtl::kIndexBufferOffsetAlignment) != 0 ||
indexType == gl::DrawElementsType::UnsignedByte);
BufferMtl *idxBuffer = mtl::GetImpl(getState().getElementArrayBuffer());
ContextMtl *contextMtl = mtl::GetImpl(glContext);
BufferMtl *idxBuffer = mtl::GetImpl(getState().getElementArrayBuffer());
IndexConversionBufferMtl *conversion =
idxBuffer->getIndexConversionBuffer(glContext, indexType, offset);
idxBuffer->getIndexConversionBuffer(contextMtl, indexType, offset);
// Has the content of the buffer has changed since last conversion?
if (!conversion->dirty)
......@@ -594,16 +595,16 @@ angle::Result VertexArrayMtl::convertVertexBuffer(const gl::Context *glContext,
size_t attribIndex,
const mtl::VertexFormat &srcVertexFormat)
{
ContextMtl *contextMtl = mtl::GetImpl(glContext);
const angle::Format &intendedAngleFormat = srcVertexFormat.intendedAngleFormat();
ConversionBufferMtl *conversion = srcBuffer->getVertexConversionBuffer(
glContext, intendedAngleFormat.id, binding.getStride(), binding.getOffset());
contextMtl, intendedAngleFormat.id, binding.getStride(), binding.getOffset());
// Has the content of the buffer has changed since last conversion?
if (!conversion->dirty)
{
ContextMtl *contextMtl = mtl::GetImpl(glContext);
// Buffer's data hasn't been changed. Re-use last converted results
GLuint stride;
const mtl::VertexFormat &vertexFormat =
......@@ -646,7 +647,7 @@ angle::Result VertexArrayMtl::convertVertexBufferCPU(const gl::Context *glContex
return angle::Result::Continue;
}
const uint8_t *srcBytes = srcBuffer->getClientShadowCopyData(glContext);
const uint8_t *srcBytes = srcBuffer->getClientShadowCopyData(contextMtl);
ANGLE_CHECK_GL_ALLOC(contextMtl, srcBytes);
srcBytes += binding.getOffset();
......
......@@ -505,6 +505,17 @@ class BlitCommandEncoder final : public CommandEncoder
MTLOrigin dstOrigin,
MTLBlitOption blitOption);
BlitCommandEncoder &copyTextureToBuffer(const TextureRef &src,
uint32_t srcSlice,
uint32_t srcLevel,
MTLOrigin srcOrigin,
MTLSize srcSize,
const BufferRef &dst,
size_t dstOffset,
size_t dstBytesPerRow,
size_t dstBytesPerImage,
MTLBlitOption blitOption);
BlitCommandEncoder &copyTexture(const TextureRef &src,
uint32_t srcSlice,
uint32_t srcLevel,
......
......@@ -1605,6 +1605,40 @@ BlitCommandEncoder &BlitCommandEncoder::copyBufferToTexture(const BufferRef &src
return *this;
}
BlitCommandEncoder &BlitCommandEncoder::copyTextureToBuffer(const TextureRef &src,
uint32_t srcSlice,
uint32_t srcLevel,
MTLOrigin srcOrigin,
MTLSize srcSize,
const BufferRef &dst,
size_t dstOffset,
size_t dstBytesPerRow,
size_t dstBytesPerImage,
MTLBlitOption blitOption)
{
if (!src || !dst)
{
return *this;
}
cmdBuffer().setReadDependency(src);
cmdBuffer().setWriteDependency(dst);
[get() copyFromTexture:src->get()
sourceSlice:srcSlice
sourceLevel:srcLevel
sourceOrigin:srcOrigin
sourceSize:srcSize
toBuffer:dst->get()
destinationOffset:dstOffset
destinationBytesPerRow:dstBytesPerRow
destinationBytesPerImage:dstBytesPerImage
options:blitOption];
return *this;
}
BlitCommandEncoder &BlitCommandEncoder::copyTexture(const TextureRef &src,
uint32_t srcStartSlice,
uint32_t srcStartLevel,
......
......@@ -114,7 +114,8 @@ constexpr uint32_t kUniformBufferSettingOffsetMinAlignment = 256;
#else
constexpr uint32_t kUniformBufferSettingOffsetMinAlignment = 4;
#endif
constexpr uint32_t kIndexBufferOffsetAlignment = 4;
constexpr uint32_t kIndexBufferOffsetAlignment = 4;
constexpr uint32_t kTextureToBufferBlittingAlignment = 256;
// Front end binding limits
constexpr uint32_t kMaxGLSamplerBindings = 2 * kMaxShaderSamplers;
......@@ -138,6 +139,21 @@ constexpr size_t kOcclusionQueryResultSize = sizeof(uint64_t);
// NOTE(hqle): Support ES 3.0.
constexpr gl::Version kMaxSupportedGLVersion = gl::Version(2, 0);
// Work-around the enum is not available on macOS
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
constexpr MTLBlitOption kBlitOptionRowLinearPVRTC = MTLBlitOptionNone;
#else
constexpr MTLBlitOption kBlitOptionRowLinearPVRTC = MTLBlitOptionRowLinearPVRTC;
#endif
enum class PixelType
{
Int,
UInt,
Float,
EnumCount,
};
template <typename T>
struct ImplTypeHelper;
......
......@@ -128,6 +128,33 @@ struct IndexGenerationParams
uint32_t dstOffset;
};
struct CopyPixelsCommonParams
{
BufferRef buffer;
uint32_t bufferStartOffset = 0;
uint32_t bufferRowPitch = 0;
TextureRef texture;
};
struct CopyPixelsFromBufferParams : CopyPixelsCommonParams
{
uint32_t bufferDepthPitch = 0;
// z offset is:
// - slice index if texture is array.
// - depth if texture is 3d.
gl::Box textureArea;
};
struct CopyPixelsToBufferParams : CopyPixelsCommonParams
{
gl::Rectangle textureArea;
uint32_t textureLevel = 0;
uint32_t textureSliceOrDeph = 0;
bool reverseTextureRowOrder;
};
// Utils class for clear & blitting
class ClearUtils final : angle::NonCopyable
{
......@@ -353,6 +380,40 @@ class MipmapUtils final : angle::NonCopyable
AutoObjCPtr<id<MTLComputePipelineState>> m3DMipGeneratorPipeline;
};
// Util class for handling pixels copy between buffers and textures
class CopyPixelsUtils
{
public:
CopyPixelsUtils() = default;
CopyPixelsUtils(const std::string &readShaderName, const std::string &writeShaderName);
CopyPixelsUtils(const CopyPixelsUtils &src);
void onDestroy();
angle::Result unpackPixelsFromBufferToTexture(ContextMtl *contextMtl,
const angle::Format &srcAngleFormat,
const CopyPixelsFromBufferParams &params);
angle::Result packPixelsFromTextureToBuffer(ContextMtl *contextMtl,
const angle::Format &dstAngleFormat,
const CopyPixelsToBufferParams &params);
private:
AutoObjCPtr<id<MTLComputePipelineState>> getPixelsCopyPipeline(ContextMtl *contextMtl,
const angle::Format &angleFormat,
const TextureRef &texture,
bool bufferWrite);
// Copy pixels between buffer and texture compute pipelines:
// - First dimension: pixel format.
// - Second dimension: texture type * (buffer read/write flag)
using PixelsCopyPipelineArray = std::array<
std::array<AutoObjCPtr<id<MTLComputePipelineState>>, mtl_shader::kTextureTypeCount * 2>,
angle::kNumANGLEFormats>;
PixelsCopyPipelineArray mPixelsCopyPipelineCaches;
const std::string mReadShaderName;
const std::string mWriteShaderName;
};
// RenderUtils: container class of various util classes above
class RenderUtils : public Context, angle::NonCopyable
{
......@@ -411,6 +472,13 @@ class RenderUtils : public Context, angle::NonCopyable
bool sRGBMipmap,
gl::TexLevelArray<mtl::TextureRef> *mipmapOutputViews);
angle::Result unpackPixelsFromBufferToTexture(ContextMtl *contextMtl,
const angle::Format &srcAngleFormat,
const CopyPixelsFromBufferParams &params);
angle::Result packPixelsFromTextureToBuffer(ContextMtl *contextMtl,
const angle::Format &dstAngleFormat,
const CopyPixelsToBufferParams &params);
private:
// override ErrorHandler
void handleError(GLenum error,
......@@ -428,6 +496,7 @@ class RenderUtils : public Context, angle::NonCopyable
IndexGeneratorUtils mIndexUtils;
VisibilityResultUtils mVisibilityResultUtils;
MipmapUtils mMipmapUtils;
std::array<CopyPixelsUtils, angle::EnumSize<PixelType>()> mCopyPixelsUtils;
};
} // namespace mtl
......
......@@ -35,6 +35,8 @@ namespace
#define UNMULTIPLY_ALPHA_CONSTANT_NAME @"kUnmultiplyAlpha"
#define SOURCE_TEXTURE_TYPE_CONSTANT_NAME @"kSourceTextureType"
#define SOURCE_TEXTURE2_TYPE_CONSTANT_NAME @"kSourceTexture2Type"
#define COPY_FORMAT_TYPE_CONSTANT_NAME @"kCopyFormatType"
#define PIXEL_COPY_TEXTURE_TYPE_CONSTANT_NAME @"kCopyTextureType"
#define VISIBILITY_RESULT_KEEP_OLD_VAL_CONSTANT_NAME @"kCombineWithExistingResult"
// See libANGLE/renderer/metal/shaders/clear.metal
......@@ -96,6 +98,34 @@ struct GenerateMipmapUniform
uint8_t padding[7];
};
// See libANGLE/renderer/metal/shaders/copy_buffer.metal
struct CopyPixelFromBufferUniforms
{
uint32_t copySize[3];
uint32_t padding1;
uint32_t textureOffset[3];
uint32_t padding2;
uint32_t bufferStartOffset;
uint32_t pixelSize;
uint32_t bufferRowPitch;
uint32_t bufferDepthPitch;
};
struct WritePixelToBufferUniforms
{
uint32_t copySize[2];
uint32_t textureOffset[2];
uint32_t bufferStartOffset;
uint32_t pixelSize;
uint32_t bufferRowPitch;
uint32_t textureLevel;
uint32_t textureLayer;
uint8_t reverseTextureRowOrder;
uint8_t padding[11];
};
// Class to automatically disable occlusion query upon entering block and re-able it upon
// exiting block.
struct ScopedDisableOcclusionQuery
......@@ -222,6 +252,22 @@ int GetShaderTextureType(const TextureRef &texture)
return 0;
}
int GetPixelTypeIndex(const angle::Format &angleFormat)
{
if (angleFormat.isSint())
{
return static_cast<int>(PixelType::Int);
}
else if (angleFormat.isUint())
{
return static_cast<int>(PixelType::UInt);
}
else
{
return static_cast<int>(PixelType::Float);
}
}
ANGLE_INLINE
void EnsureComputePipelineInitialized(DisplayMtl *display,
NSString *functionName,
......@@ -487,7 +533,13 @@ StencilBlitViaBufferParams::StencilBlitViaBufferParams(const DepthStencilBlitPar
}
// RenderUtils implementation
RenderUtils::RenderUtils(DisplayMtl *display) : Context(display) {}
RenderUtils::RenderUtils(DisplayMtl *display)
: Context(display),
mCopyPixelsUtils(
{CopyPixelsUtils("readFromBufferToIntTexture", "writeFromIntTextureToBuffer"),
CopyPixelsUtils("readFromBufferToUIntTexture", "writeFromUIntTextureToBuffer"),
CopyPixelsUtils("readFromBufferToFloatTexture", "writeFromFloatTextureToBuffer")})
{}
RenderUtils::~RenderUtils() {}
......@@ -502,6 +554,11 @@ void RenderUtils::onDestroy()
mClearUtils.onDestroy();
mColorBlitUtils.onDestroy();
mDepthStencilBlitUtils.onDestroy();
for (CopyPixelsUtils &util : mCopyPixelsUtils)
{
util.onDestroy();
}
}
// override ErrorHandler
......@@ -630,6 +687,23 @@ angle::Result RenderUtils::generateMipmapCS(ContextMtl *contextMtl,
return mMipmapUtils.generateMipmapCS(contextMtl, srcTexture, sRGBMipmap, mipmapOutputViews);
}
angle::Result RenderUtils::unpackPixelsFromBufferToTexture(ContextMtl *contextMtl,
const angle::Format &srcAngleFormat,
const CopyPixelsFromBufferParams &params)
{
int index = GetPixelTypeIndex(srcAngleFormat);
return mCopyPixelsUtils[index].unpackPixelsFromBufferToTexture(contextMtl, srcAngleFormat,
params);
}
angle::Result RenderUtils::packPixelsFromTextureToBuffer(ContextMtl *contextMtl,
const angle::Format &dstAngleFormat,
const CopyPixelsToBufferParams &params)
{
int index = GetPixelTypeIndex(dstAngleFormat);
return mCopyPixelsUtils[index].packPixelsFromTextureToBuffer(contextMtl, dstAngleFormat,
params);
}
// ClearUtils implementation
ClearUtils::ClearUtils() = default;
......@@ -1600,7 +1674,8 @@ angle::Result IndexGeneratorUtils::generateLineLoopLastSegmentFromElementsArray(
BufferMtl *bufferMtl = GetImpl(elementBuffer);
std::pair<uint32_t, uint32_t> firstLast;
ANGLE_TRY(bufferMtl->getFirstLastIndices(params.srcType, static_cast<uint32_t>(srcOffset),
ANGLE_TRY(bufferMtl->getFirstLastIndices(contextMtl, params.srcType,
static_cast<uint32_t>(srcOffset),
params.indexCount, &firstLast));
return generateLineLoopLastSegment(contextMtl, firstLast.first, firstLast.second,
......@@ -1816,5 +1891,142 @@ angle::Result MipmapUtils::generateMipmapCS(ContextMtl *contextMtl,
return angle::Result::Continue;
}
// CopyPixelsUtils implementation
CopyPixelsUtils::CopyPixelsUtils(const std::string &readShaderName,
const std::string &writeShaderName)
: mReadShaderName(readShaderName), mWriteShaderName(writeShaderName)
{}
CopyPixelsUtils::CopyPixelsUtils(const CopyPixelsUtils &src)
: CopyPixelsUtils(src.mReadShaderName, src.mWriteShaderName)
{}
void CopyPixelsUtils::onDestroy()
{
ClearPipelineState2DArray(&mPixelsCopyPipelineCaches);
}
AutoObjCPtr<id<MTLComputePipelineState>> CopyPixelsUtils::getPixelsCopyPipeline(
ContextMtl *contextMtl,
const angle::Format &angleFormat,
const TextureRef &texture,
bool bufferWrite)
{
int formatIDValue = static_cast<int>(angleFormat.id);
int shaderTextureType = GetShaderTextureType(texture);
int index2 = mtl_shader::kTextureTypeCount * (bufferWrite ? 1 : 0) + shaderTextureType;
AutoObjCPtr<id<MTLComputePipelineState>> &cache =
mPixelsCopyPipelineCaches[formatIDValue][index2];
if (!cache)
{
// Pipeline not cached, create it now:
ANGLE_MTL_OBJC_SCOPE
{
auto funcConstants = [[[MTLFunctionConstantValues alloc] init] ANGLE_MTL_AUTORELEASE];
[funcConstants setConstantValue:&formatIDValue
type:MTLDataTypeInt
withName:COPY_FORMAT_TYPE_CONSTANT_NAME];
[funcConstants setConstantValue:&shaderTextureType
type:MTLDataTypeInt
withName:PIXEL_COPY_TEXTURE_TYPE_CONSTANT_NAME];
NSString *shaderName = nil;
if (bufferWrite)
{
shaderName = [NSString stringWithUTF8String:mWriteShaderName.c_str()];
}
else
{
shaderName = [NSString stringWithUTF8String:mReadShaderName.c_str()];
}
EnsureSpecializedComputePipelineInitialized(contextMtl->getDisplay(), shaderName,
funcConstants, &cache);
}
}
return cache;
}
angle::Result CopyPixelsUtils::unpackPixelsFromBufferToTexture(
ContextMtl *contextMtl,
const angle::Format &srcAngleFormat,
const CopyPixelsFromBufferParams &params)
{
ComputeCommandEncoder *cmdEncoder = contextMtl->getComputeCommandEncoder();
ASSERT(cmdEncoder);
AutoObjCPtr<id<MTLComputePipelineState>> pipeline =
getPixelsCopyPipeline(contextMtl, srcAngleFormat, params.texture, false);
cmdEncoder->setComputePipelineState(pipeline);
cmdEncoder->setBuffer(params.buffer, 0, 1);
cmdEncoder->setTextureForWrite(params.texture, 0);
CopyPixelFromBufferUniforms options;
options.copySize[0] = params.textureArea.width;
options.copySize[1] = params.textureArea.height;
options.copySize[2] = params.textureArea.depth;
options.bufferStartOffset = params.bufferStartOffset;
options.pixelSize = srcAngleFormat.pixelBytes;
options.bufferRowPitch = params.bufferRowPitch;
options.bufferDepthPitch = params.bufferDepthPitch;
options.textureOffset[0] = params.textureArea.x;
options.textureOffset[1] = params.textureArea.y;
options.textureOffset[2] = params.textureArea.z;
cmdEncoder->setData(options, 0);
NSUInteger w = pipeline.get().threadExecutionWidth;
MTLSize threadsPerThreadgroup = MTLSizeMake(w, 1, 1);
MTLSize threads =
MTLSizeMake(params.textureArea.width, params.textureArea.height, params.textureArea.depth);
DispatchCompute(contextMtl, cmdEncoder,
/** allowNonUniform */ true, threads, threadsPerThreadgroup);
return angle::Result::Continue;
}
angle::Result CopyPixelsUtils::packPixelsFromTextureToBuffer(ContextMtl *contextMtl,
const angle::Format &dstAngleFormat,
const CopyPixelsToBufferParams &params)
{
ComputeCommandEncoder *cmdEncoder = contextMtl->getComputeCommandEncoder();
ASSERT(cmdEncoder);
AutoObjCPtr<id<MTLComputePipelineState>> pipeline =
getPixelsCopyPipeline(contextMtl, dstAngleFormat, params.texture, true);
cmdEncoder->setComputePipelineState(pipeline);
cmdEncoder->setTexture(params.texture, 0);
cmdEncoder->setBufferForWrite(params.buffer, 0, 1);
WritePixelToBufferUniforms options;
options.copySize[0] = params.textureArea.width;
options.copySize[1] = params.textureArea.height;
options.bufferStartOffset = params.bufferStartOffset;
options.pixelSize = dstAngleFormat.pixelBytes;
options.bufferRowPitch = params.bufferRowPitch;
options.textureOffset[0] = params.textureArea.x;
options.textureOffset[1] = params.textureArea.y;
options.textureLevel = params.textureLevel;
options.textureLayer = params.textureSliceOrDeph;
options.reverseTextureRowOrder = params.reverseTextureRowOrder;
cmdEncoder->setData(options, 0);
NSUInteger w = pipeline.get().threadExecutionWidth;
MTLSize threadsPerThreadgroup = MTLSizeMake(w, 1, 1);
MTLSize threads = MTLSizeMake(params.textureArea.width, params.textureArea.height, 1);
DispatchCompute(contextMtl, cmdEncoder,
/** allowNonUniform */ true, threads, threadsPerThreadgroup);
return angle::Result::Continue;
}
} // namespace mtl
} // namespace rx
......@@ -56,6 +56,15 @@ angle::Result ReadTexturePerSliceBytes(const gl::Context *context,
uint32_t sliceOrDepth,
uint8_t *dataOut);
angle::Result ReadTexturePerSliceBytesToBuffer(const gl::Context *context,
const TextureRef &texture,
size_t bytesPerRow,
const gl::Rectangle &fromRegion,
uint32_t mipLevel,
uint32_t sliceOrDepth,
uint32_t dstOffset,
const BufferRef &dstBuffer);
MTLViewport GetViewport(const gl::Rectangle &rect, double znear = 0, double zfar = 1);
MTLViewport GetViewportFlipY(const gl::Rectangle &rect,
NSUInteger screenHeight,
......@@ -132,6 +141,8 @@ bool IsFormatEmulated(const mtl::Format &mtlFormat);
// has alpha channel.
MTLClearColor EmulatedAlphaClearColor(MTLClearColor color, MTLColorWriteMask colorMask);
gl::Box MTLRegionToGLBox(const MTLRegion &mtlRegion);
NS_ASSUME_NONNULL_END
} // namespace mtl
} // namespace rx
......
......@@ -321,6 +321,42 @@ angle::Result ReadTexturePerSliceBytes(const gl::Context *context,
return angle::Result::Continue;
}
angle::Result ReadTexturePerSliceBytesToBuffer(const gl::Context *context,
const TextureRef &texture,
size_t bytesPerRow,
const gl::Rectangle &fromRegion,
uint32_t mipLevel,
uint32_t sliceOrDepth,
uint32_t dstOffset,
const BufferRef &dstBuffer)
{
ASSERT(texture && texture->valid());
ContextMtl *contextMtl = mtl::GetImpl(context);
GLint layer = 0;
GLint startDepth = 0;
switch (texture->textureType())
{
case MTLTextureTypeCube:
case MTLTextureType2DArray:
layer = sliceOrDepth;
break;
case MTLTextureType3D:
startDepth = sliceOrDepth;
break;
default:
break;
}
MTLRegion mtlRegion = MTLRegionMake3D(fromRegion.x, fromRegion.y, startDepth, fromRegion.width,
fromRegion.height, 1);
BlitCommandEncoder *blitEncoder = contextMtl->getBlitCommandEncoder();
blitEncoder->copyTextureToBuffer(texture, layer, mipLevel, mtlRegion.origin, mtlRegion.size,
dstBuffer, dstOffset, bytesPerRow, 0, MTLBlitOptionNone);
return angle::Result::Continue;
}
MTLViewport GetViewport(const gl::Rectangle &rect, double znear, double zfar)
{
MTLViewport re;
......@@ -783,5 +819,12 @@ MTLClearColor EmulatedAlphaClearColor(MTLClearColor color, MTLColorWriteMask col
return re;
}
gl::Box MTLRegionToGLBox(const MTLRegion &mtlRegion)
{
return gl::Box(static_cast<int>(mtlRegion.origin.x), static_cast<int>(mtlRegion.origin.y),
static_cast<int>(mtlRegion.origin.z), static_cast<int>(mtlRegion.size.width),
static_cast<int>(mtlRegion.size.height), static_cast<int>(mtlRegion.size.depth));
}
} // namespace mtl
} // namespace rx
......@@ -11,6 +11,7 @@
#include <array>
#include "test_utils/gl_raii.h"
#include "util/random_utils.h"
using namespace angle;
......@@ -61,21 +62,23 @@ TEST_P(ReadPixelsTest, OutOfBounds)
}
}
class ReadPixelsPBOTest : public ReadPixelsTest
class ReadPixelsPBONVTest : public ReadPixelsTest
{
protected:
ReadPixelsPBOTest() : mPBO(0), mTexture(0), mFBO(0) {}
ReadPixelsPBONVTest() : mPBO(0), mTexture(0), mFBO(0) {}
void testSetUp() override
{
glGenBuffers(1, &mPBO);
glGenFramebuffers(1, &mFBO);
Reset(4 * getWindowWidth() * getWindowHeight(), 4, 1);
Reset(4 * getWindowWidth() * getWindowHeight(), 4, 4);
}
void Reset(GLuint bufferSize, GLuint fboWidth, GLuint fboHeight)
virtual void Reset(GLuint bufferSize, GLuint fboWidth, GLuint fboHeight)
{
ANGLE_SKIP_TEST_IF(!hasPBOExts());
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO);
glBufferData(GL_PIXEL_PACK_BUFFER, bufferSize, nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
......@@ -83,7 +86,9 @@ class ReadPixelsPBOTest : public ReadPixelsTest
glDeleteTextures(1, &mTexture);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, fboWidth, fboHeight);
glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, fboWidth, fboHeight);
mFBOWidth = fboWidth;
mFBOHeight = fboHeight;
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
......@@ -99,9 +104,183 @@ class ReadPixelsPBOTest : public ReadPixelsTest
glDeleteFramebuffers(1, &mFBO);
}
GLuint mPBO = 0;
GLuint mTexture = 0;
GLuint mFBO = 0;
bool hasPBOExts() const
{
return IsGLExtensionEnabled("GL_NV_pixel_buffer_object") &&
IsGLExtensionEnabled("GL_EXT_texture_storage");
}
GLuint mPBO = 0;
GLuint mTexture = 0;
GLuint mFBO = 0;
GLuint mFBOWidth = 0;
GLuint mFBOHeight = 0;
};
// Test basic usage of PBOs.
TEST_P(ReadPixelsPBONVTest, Basic)
{
ANGLE_SKIP_TEST_IF(!hasPBOExts() || !IsGLExtensionEnabled("GL_EXT_map_buffer_range") ||
!IsGLExtensionEnabled("GL_OES_mapbuffer"));
// http://anglebug.com/5022
ANGLE_SKIP_TEST_IF(IsWindows() && IsDesktopOpenGL());
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Clear last pixel to green
glScissor(15, 15, 1, 1);
glEnable(GL_SCISSOR_TEST);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO);
glReadPixels(0, 0, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, 0);
void *mappedPtr = glMapBufferRangeEXT(GL_PIXEL_PACK_BUFFER, 0, 32, GL_MAP_READ_BIT);
GLColor *dataColor = static_cast<GLColor *>(mappedPtr);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(GLColor::red, dataColor[0]);
EXPECT_EQ(GLColor::red, dataColor[16 * 16 - 2]);
EXPECT_EQ(GLColor::green, dataColor[16 * 16 - 1]);
glUnmapBufferOES(GL_PIXEL_PACK_BUFFER);
EXPECT_GL_NO_ERROR();
}
// Test that calling SubData preserves PBO data.
TEST_P(ReadPixelsPBONVTest, SubDataPreservesContents)
{
ANGLE_SKIP_TEST_IF(!hasPBOExts() || !IsGLExtensionEnabled("GL_EXT_map_buffer_range") ||
!IsGLExtensionEnabled("GL_OES_mapbuffer"));
// anglebug.com/2185
ANGLE_SKIP_TEST_IF(IsOSX() && IsNVIDIA() && IsDesktopOpenGL());
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO);
glReadPixels(0, 0, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, 0);
unsigned char data[4] = {1, 2, 3, 4};
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, mPBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, 4, data);
void *mappedPtr = glMapBufferRangeEXT(GL_ARRAY_BUFFER, 0, 32, GL_MAP_READ_BIT);
GLColor *dataColor = static_cast<GLColor *>(mappedPtr);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(GLColor(1, 2, 3, 4), dataColor[0]);
EXPECT_EQ(GLColor::red, dataColor[1]);
glUnmapBufferOES(GL_ARRAY_BUFFER);
EXPECT_GL_NO_ERROR();
}
// Test that calling ReadPixels with GL_DYNAMIC_DRAW buffer works
TEST_P(ReadPixelsPBONVTest, DynamicPBO)
{
ANGLE_SKIP_TEST_IF(!hasPBOExts() || !IsGLExtensionEnabled("GL_EXT_map_buffer_range") ||
!IsGLExtensionEnabled("GL_OES_mapbuffer"));
// anglebug.com/2185
ANGLE_SKIP_TEST_IF(IsOSX() && IsNVIDIA() && IsDesktopOpenGL());
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO);
glBufferData(GL_PIXEL_PACK_BUFFER, 4 * getWindowWidth() * getWindowHeight(), nullptr,
GL_DYNAMIC_DRAW);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
glReadPixels(0, 0, 16, 16, GL_RGBA, GL_UNSIGNED_BYTE, 0);
unsigned char data[4] = {1, 2, 3, 4};
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, mPBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, 4, data);
void *mappedPtr = glMapBufferRangeEXT(GL_ARRAY_BUFFER, 0, 32, GL_MAP_READ_BIT);
GLColor *dataColor = static_cast<GLColor *>(mappedPtr);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(GLColor(1, 2, 3, 4), dataColor[0]);
EXPECT_EQ(GLColor::red, dataColor[1]);
glUnmapBufferOES(GL_ARRAY_BUFFER);
EXPECT_GL_NO_ERROR();
}
TEST_P(ReadPixelsPBONVTest, ReadFromFBO)
{
ANGLE_SKIP_TEST_IF(!hasPBOExts() || !IsGLExtensionEnabled("GL_EXT_map_buffer_range") ||
!IsGLExtensionEnabled("GL_OES_mapbuffer"));
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glViewport(0, 0, mFBOWidth, mFBOHeight);
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// Clear last pixel to green
glScissor(mFBOWidth - 1, mFBOHeight - 1, 1, 1);
glEnable(GL_SCISSOR_TEST);
glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
EXPECT_GL_NO_ERROR();
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO);
glReadPixels(0, 0, mFBOWidth, mFBOHeight, GL_RGBA, GL_UNSIGNED_BYTE, 0);
void *mappedPtr =
glMapBufferRangeEXT(GL_PIXEL_PACK_BUFFER, 0, 4 * mFBOWidth * mFBOHeight, GL_MAP_READ_BIT);
GLColor *dataColor = static_cast<GLColor *>(mappedPtr);
EXPECT_GL_NO_ERROR();
EXPECT_EQ(GLColor::red, dataColor[0]);
EXPECT_EQ(GLColor::red, dataColor[mFBOWidth * mFBOHeight - 2]);
EXPECT_EQ(GLColor::green, dataColor[mFBOWidth * mFBOHeight - 1]);
glUnmapBufferOES(GL_PIXEL_PACK_BUFFER);
EXPECT_GL_NO_ERROR();
}
class ReadPixelsPBOTest : public ReadPixelsPBONVTest
{
protected:
ReadPixelsPBOTest() : ReadPixelsPBONVTest() {}
void testSetUp() override
{
glGenBuffers(1, &mPBO);
glGenFramebuffers(1, &mFBO);
Reset(4 * getWindowWidth() * getWindowHeight(), 4, 1);
}
void Reset(GLuint bufferSize, GLuint fboWidth, GLuint fboHeight) override
{
glBindBuffer(GL_PIXEL_PACK_BUFFER, mPBO);
glBufferData(GL_PIXEL_PACK_BUFFER, bufferSize, nullptr, GL_STATIC_DRAW);
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
glDeleteTextures(1, &mTexture);
glGenTextures(1, &mTexture);
glBindTexture(GL_TEXTURE_2D, mTexture);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, fboWidth, fboHeight);
glBindFramebuffer(GL_FRAMEBUFFER, mFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
ASSERT_GL_NO_ERROR();
}
};
// Test basic usage of PBOs.
......@@ -696,6 +875,7 @@ TEST_P(ReadPixelsErrorTest, ReadBufferIsNone)
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2(ReadPixelsTest);
ANGLE_INSTANTIATE_TEST_ES2(ReadPixelsPBONVTest);
ANGLE_INSTANTIATE_TEST_ES3(ReadPixelsPBOTest);
ANGLE_INSTANTIATE_TEST_ES3(ReadPixelsPBODrawTest);
ANGLE_INSTANTIATE_TEST_ES3(ReadPixelsMultisampleTest);
......
......@@ -2308,6 +2308,286 @@ TEST_P(Texture2DTest, PBOWithMultipleDraws)
EXPECT_EQ(expected, actual);
}
// Test that glTexSubImage2D combined with a PBO works properly. PBO has all pixels as red
// except the middle one being green.
TEST_P(Texture2DTest, TexStorageWithPBOMiddlePixelDifferent)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
int width = getWindowWidth();
int height = getWindowHeight();
GLuint tex2D;
glGenTextures(1, &tex2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex2D);
std::vector<GLubyte> pixels(3 * 16 * 16);
// Initialize texture with default black color.
glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB8, 16, 16);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 16, 16, GL_RGB, GL_UNSIGNED_BYTE, pixels.data());
// Fill PBO's data with red, with middle one as green
for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
{
if (pixelId == 8 * 7 + 7)
{
pixels[pixelId * 3 + 0] = 0;
pixels[pixelId * 3 + 1] = 255;
pixels[pixelId * 3 + 2] = 0;
}
else
{
pixels[pixelId * 3 + 0] = 255;
pixels[pixelId * 3 + 1] = 0;
pixels[pixelId * 3 + 2] = 0;
}
}
GLuint pbo;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, 3 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
// Update the color of the texture's upper-left 8x8 pixels, leaves the other pixels untouched.
// glTexSubImage2D should take into account that the image is dirty.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
setUpProgram();
glUseProgram(mProgram);
glUniform1i(mTexture2DUniformLocation, 0);
drawQuad(mProgram, "position", 0.5f);
glDeleteTextures(1, &tex2D);
glDeleteBuffers(1, &pbo);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(3 * width / 4, 3 * height / 4, 0, 0, 0, 255);
EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
}
// Test that glTexSubImage2D combined with a PBO works properly when glTexImage2D has
// initialized the image with a luminance color
TEST_P(Texture2DTest, TexImageWithLuminancePBO)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
int width = getWindowWidth();
int height = getWindowHeight();
GLuint tex2D;
glGenTextures(1, &tex2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex2D);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, 16, 16, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE,
nullptr);
// Fill PBO with white, with middle one as grey
std::vector<GLubyte> pixels(16 * 16);
for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
{
if (pixelId == 8 * 7 + 7)
{
pixels[pixelId] = 128;
}
else
{
pixels[pixelId] = 255;
}
}
GLuint pbo;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, 16 * 16, pixels.data(), GL_STATIC_DRAW);
// Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
// glTexSubImage2D should take into account that the image is dirty.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_LUMINANCE, GL_UNSIGNED_BYTE, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
setUpProgram();
glUseProgram(mProgram);
glUniform1i(mTexture2DUniformLocation, 0);
drawQuad(mProgram, "position", 0.5f);
glDeleteTextures(1, &tex2D);
glDeleteBuffers(1, &pbo);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 255, 255, 255);
EXPECT_PIXEL_NEAR(width / 2 - 1, height / 2 - 1, 128, 128, 128, 255, 1);
}
// Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
// initialized the image with a RGB656 color
TEST_P(Texture2DTest, TexImageWithRGB565PBO)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
int width = getWindowWidth();
int height = getWindowHeight();
GLuint tex2D;
glGenTextures(1, &tex2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex2D);
// Fill PBO with red, with middle one as green
std::vector<GLushort> pixels(16 * 16);
for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
{
if (pixelId == 8 * 7 + 8)
{
pixels[pixelId] = 0x7E0;
}
else
{
pixels[pixelId] = 0xF800;
}
}
GLuint pbo;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, 2 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB565, 16, 16);
// Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
// glTexSubImage2D should take into account that the image is dirty.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGB, GL_UNSIGNED_SHORT_5_6_5,
reinterpret_cast<void *>(2));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
setUpProgram();
glUseProgram(mProgram);
glUniform1i(mTexture2DUniformLocation, 0);
drawQuad(mProgram, "position", 0.5f);
glDeleteTextures(1, &tex2D);
glDeleteBuffers(1, &pbo);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
}
// Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
// initialized the image with a RGBA4444 color
TEST_P(Texture2DTest, TexImageWithRGBA4444PBO)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
int width = getWindowWidth();
int height = getWindowHeight();
GLuint tex2D;
glGenTextures(1, &tex2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex2D);
// Fill PBO with red, with middle one as green
std::vector<GLushort> pixels(16 * 16);
for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
{
if (pixelId == 8 * 7 + 8)
{
pixels[pixelId] = 0xF0F;
}
else
{
pixels[pixelId] = 0xF00F;
}
}
GLuint pbo;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, 2 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGBA4, 16, 16);
// Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
// glTexSubImage2D should take into account that the image is dirty.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_SHORT_4_4_4_4,
reinterpret_cast<void *>(2));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
setUpProgram();
glUseProgram(mProgram);
glUniform1i(mTexture2DUniformLocation, 0);
drawQuad(mProgram, "position", 0.5f);
glDeleteTextures(1, &tex2D);
glDeleteBuffers(1, &pbo);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
}
// Test that glTexSubImage2D combined with a PBO works properly when glTexStorage2DEXT has
// initialized the image with a RGBA5551 color
TEST_P(Texture2DTest, TexImageWithRGBA5551PBO)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_pixel_buffer_object"));
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_storage"));
int width = getWindowWidth();
int height = getWindowHeight();
GLuint tex2D;
glGenTextures(1, &tex2D);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex2D);
// Fill PBO with red, with middle one as green
std::vector<GLushort> pixels(16 * 16);
for (size_t pixelId = 0; pixelId < 16 * 16; ++pixelId)
{
if (pixelId == 8 * 7 + 7)
{
pixels[pixelId] = 0x7C1;
}
else
{
pixels[pixelId] = 0xF801;
}
}
GLuint pbo;
glGenBuffers(1, &pbo);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
glBufferData(GL_PIXEL_UNPACK_BUFFER, 2 * 16 * 16, pixels.data(), GL_STATIC_DRAW);
glTexStorage2DEXT(GL_TEXTURE_2D, 1, GL_RGB5_A1, 16, 16);
// Initializes the color of the upper-left 8x8 pixels, leaves the other pixels untouched.
// glTexSubImage2D should take into account that the image is dirty.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 8, 8, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1, nullptr);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
setUpProgram();
glUseProgram(mProgram);
glUniform1i(mTexture2DUniformLocation, 0);
drawQuad(mProgram, "position", 0.5f);
glDeleteTextures(1, &tex2D);
glDeleteBuffers(1, &pbo);
EXPECT_GL_NO_ERROR();
EXPECT_PIXEL_EQ(width / 4, height / 4, 255, 0, 0, 255);
EXPECT_PIXEL_EQ(width / 2 - 1, height / 2 - 1, 0, 255, 0, 255);
}
// Tests CopySubImage for float formats
TEST_P(Texture2DTest, CopySubImageFloat_R_R)
{
......
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