blob: e124095cb4ad10f9a10971310668f19272522421 [file] [log] [blame]
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// FramebufferMtl.mm:
// Implements the class methods for FramebufferMtl.
//
#include "libANGLE/renderer/metal/ContextMtl.h"
#include <TargetConditionals.h>
#include "common/MemoryBuffer.h"
#include "common/angleutils.h"
#include "common/debug.h"
#include "libANGLE/renderer/metal/DisplayMtl.h"
#include "libANGLE/renderer/metal/FrameBufferMtl.h"
#include "libANGLE/renderer/metal/SurfaceMtl.h"
#include "libANGLE/renderer/metal/mtl_utils.h"
#include "libANGLE/renderer/renderer_utils.h"
namespace rx
{
namespace
{
const gl::InternalFormat &GetReadAttachmentInfo(const gl::Context *context,
RenderTargetMtl *renderTarget)
{
GLenum implFormat;
if (renderTarget && renderTarget->getFormat())
{
implFormat = renderTarget->getFormat()->actualAngleFormat().fboImplementationInternalFormat;
}
else
{
implFormat = GL_NONE;
}
return gl::GetSizedInternalFormatInfo(implFormat);
}
}
// FramebufferMtl implementation
FramebufferMtl::FramebufferMtl(const gl::FramebufferState &state, bool flipY)
: FramebufferImpl(state), mFlipY(flipY)
{
reset();
}
FramebufferMtl::~FramebufferMtl() {}
void FramebufferMtl::reset()
{
for (auto &rt : mColorRenderTargets)
{
rt = nullptr;
}
mDepthRenderTarget = mStencilRenderTarget = nullptr;
}
void FramebufferMtl::destroy(const gl::Context *context)
{
reset();
}
angle::Result FramebufferMtl::discard(const gl::Context *context,
size_t count,
const GLenum *attachments)
{
return invalidate(context, count, attachments);
}
angle::Result FramebufferMtl::invalidate(const gl::Context *context,
size_t count,
const GLenum *attachments)
{
return invalidateImpl(mtl::GetImpl(context), count, attachments);
}
angle::Result FramebufferMtl::invalidateSub(const gl::Context *context,
size_t count,
const GLenum *attachments,
const gl::Rectangle &area)
{
// NOTE(hqle): ES 3.0 feature.
UNIMPLEMENTED();
return angle::Result::Stop;
}
angle::Result FramebufferMtl::clear(const gl::Context *context, GLbitfield mask)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
mtl::ClearRectParams clearOpts;
bool clearColor = IsMaskFlagSet(mask, static_cast<GLbitfield>(GL_COLOR_BUFFER_BIT));
bool clearDepth = IsMaskFlagSet(mask, static_cast<GLbitfield>(GL_DEPTH_BUFFER_BIT));
bool clearStencil = IsMaskFlagSet(mask, static_cast<GLbitfield>(GL_STENCIL_BUFFER_BIT));
gl::DrawBufferMask clearColorBuffers;
if (clearColor)
{
clearColorBuffers = mState.getEnabledDrawBuffers();
clearOpts.clearColor = contextMtl->getClearColorValue();
}
if (clearDepth)
{
clearOpts.clearDepth = contextMtl->getClearDepthValue();
}
if (clearStencil)
{
clearOpts.clearStencil = contextMtl->getClearStencilValue();
}
return clearImpl(context, clearColorBuffers, &clearOpts);
}
angle::Result FramebufferMtl::clearBufferfv(const gl::Context *context,
GLenum buffer,
GLint drawbuffer,
const GLfloat *values)
{
// NOTE(hqle): ES 3.0 feature.
UNIMPLEMENTED();
return angle::Result::Stop;
}
angle::Result FramebufferMtl::clearBufferuiv(const gl::Context *context,
GLenum buffer,
GLint drawbuffer,
const GLuint *values)
{
// NOTE(hqle): ES 3.0 feature.
UNIMPLEMENTED();
return angle::Result::Stop;
}
angle::Result FramebufferMtl::clearBufferiv(const gl::Context *context,
GLenum buffer,
GLint drawbuffer,
const GLint *values)
{
// NOTE(hqle): ES 3.0 feature.
UNIMPLEMENTED();
return angle::Result::Stop;
}
angle::Result FramebufferMtl::clearBufferfi(const gl::Context *context,
GLenum buffer,
GLint drawbuffer,
GLfloat depth,
GLint stencil)
{
// NOTE(hqle): ES 3.0 feature.
UNIMPLEMENTED();
return angle::Result::Stop;
}
GLenum FramebufferMtl::getImplementationColorReadFormat(const gl::Context *context) const
{
return GetReadAttachmentInfo(context, getColorReadRenderTarget()).format;
}
GLenum FramebufferMtl::getImplementationColorReadType(const gl::Context *context) const
{
return GetReadAttachmentInfo(context, getColorReadRenderTarget()).type;
}
angle::Result FramebufferMtl::readPixels(const gl::Context *context,
const gl::Rectangle &area,
GLenum format,
GLenum type,
void *pixels)
{
// Clip read area to framebuffer.
const gl::Extents &fbSize = getState().getReadAttachment()->getSize();
const gl::Rectangle fbRect(0, 0, fbSize.width, fbSize.height);
gl::Rectangle clippedArea;
if (!ClipRectangle(area, fbRect, &clippedArea))
{
// nothing to read
return angle::Result::Continue;
}
gl::Rectangle flippedArea = getReadPixelArea(clippedArea);
ContextMtl *contextMtl = mtl::GetImpl(context);
const gl::State &glState = context->getState();
const gl::PixelPackState &packState = glState.getPackState();
const gl::InternalFormat &sizedFormatInfo = gl::GetInternalFormatInfo(format, type);
GLuint outputPitch = 0;
ANGLE_CHECK_GL_MATH(contextMtl,
sizedFormatInfo.computeRowPitch(type, area.width, packState.alignment,
packState.rowLength, &outputPitch));
GLuint outputSkipBytes = 0;
ANGLE_CHECK_GL_MATH(contextMtl, sizedFormatInfo.computeSkipBytes(
type, outputPitch, 0, packState, false, &outputSkipBytes));
outputSkipBytes += (clippedArea.x - area.x) * sizedFormatInfo.pixelBytes +
(clippedArea.y - area.y) * outputPitch;
const angle::Format &angleFormat = GetFormatFromFormatType(format, type);
PackPixelsParams params(flippedArea, angleFormat, outputPitch, packState.reverseRowOrder,
glState.getTargetBuffer(gl::BufferBinding::PixelPack), 0);
if (mFlipY)
{
params.reverseRowOrder = !params.reverseRowOrder;
}
ANGLE_TRY(readPixelsImpl(context, flippedArea, params, getColorReadRenderTarget(),
static_cast<uint8_t *>(pixels) + outputSkipBytes));
return angle::Result::Continue;
}
angle::Result FramebufferMtl::blit(const gl::Context *context,
const gl::Rectangle &sourceArea,
const gl::Rectangle &destArea,
GLbitfield mask,
GLenum filter)
{
// NOTE(hqle): MSAA feature.
UNIMPLEMENTED();
return angle::Result::Stop;
}
bool FramebufferMtl::checkStatus(const gl::Context *context) const
{
if (!mState.attachmentsHaveSameDimensions())
{
return false;
}
ContextMtl *contextMtl = mtl::GetImpl(context);
if (!contextMtl->getDisplay()->getFeatures().allowSeparatedDepthStencilBuffers.enabled &&
mState.hasSeparateDepthAndStencilAttachments())
{
return false;
}
return true;
}
angle::Result FramebufferMtl::syncState(const gl::Context *context,
const gl::Framebuffer::DirtyBits &dirtyBits)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
ASSERT(dirtyBits.any());
for (size_t dirtyBit : dirtyBits)
{
switch (dirtyBit)
{
case gl::Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT:
ANGLE_TRY(updateDepthRenderTarget(context));
break;
case gl::Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT:
ANGLE_TRY(updateStencilRenderTarget(context));
break;
case gl::Framebuffer::DIRTY_BIT_DEPTH_BUFFER_CONTENTS:
case gl::Framebuffer::DIRTY_BIT_STENCIL_BUFFER_CONTENTS:
// NOTE(hqle): What are we supposed to do?
break;
case gl::Framebuffer::DIRTY_BIT_DRAW_BUFFERS:
case gl::Framebuffer::DIRTY_BIT_READ_BUFFER:
case gl::Framebuffer::DIRTY_BIT_DEFAULT_WIDTH:
case gl::Framebuffer::DIRTY_BIT_DEFAULT_HEIGHT:
case gl::Framebuffer::DIRTY_BIT_DEFAULT_SAMPLES:
case gl::Framebuffer::DIRTY_BIT_DEFAULT_FIXED_SAMPLE_LOCATIONS:
break;
default:
{
static_assert(gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0 == 0, "FB dirty bits");
if (dirtyBit < gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX)
{
size_t colorIndexGL = static_cast<size_t>(
dirtyBit - gl::Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0);
ANGLE_TRY(updateColorRenderTarget(context, colorIndexGL));
}
else
{
ASSERT(dirtyBit >= gl::Framebuffer::DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 &&
dirtyBit < gl::Framebuffer::DIRTY_BIT_COLOR_BUFFER_CONTENTS_MAX);
// NOTE: might need to notify context.
}
break;
}
}
}
auto oldRenderPassDesc = mRenderPassDesc;
ANGLE_TRY(prepareRenderPass(context, mState.getEnabledDrawBuffers(), &mRenderPassDesc));
if (!oldRenderPassDesc.equalIgnoreLoadStoreOptions(mRenderPassDesc))
{
FramebufferMtl *currentDrawFramebuffer =
mtl::GetImpl(context->getState().getDrawFramebuffer());
if (currentDrawFramebuffer == this)
{
contextMtl->onDrawFrameBufferChange(context, this);
}
}
return angle::Result::Continue;
}
angle::Result FramebufferMtl::getSamplePosition(const gl::Context *context,
size_t index,
GLfloat *xy) const
{
UNIMPLEMENTED();
return angle::Result::Stop;
}
RenderTargetMtl *FramebufferMtl::getColorReadRenderTarget() const
{
if (mState.getReadIndex() >= mColorRenderTargets.size())
{
return nullptr;
}
return mColorRenderTargets[mState.getReadIndex()];
}
gl::Rectangle FramebufferMtl::getCompleteRenderArea() const
{
return gl::Rectangle(0, 0, mState.getDimensions().width, mState.getDimensions().height);
}
const mtl::RenderPassDesc &FramebufferMtl::getRenderPassDesc(ContextMtl *context)
{
return mRenderPassDesc;
}
void FramebufferMtl::onStartedDrawingToFrameBuffer(const gl::Context *context)
{
// Compute loadOp based on previous storeOp and reset storeOp flags:
for (mtl::RenderPassColorAttachmentDesc &colorAttachment : mRenderPassDesc.colorAttachments)
{
if (colorAttachment.storeAction == MTLStoreActionDontCare)
{
// If we previously discarded attachment's content, then don't need to load it.
colorAttachment.loadAction = MTLLoadActionDontCare;
}
else
{
colorAttachment.loadAction = MTLLoadActionLoad;
}
colorAttachment.storeAction = MTLStoreActionStore; // Default action is store
}
// Depth load/store
if (mRenderPassDesc.depthAttachment.storeAction == MTLStoreActionDontCare)
{
mRenderPassDesc.depthAttachment.loadAction = MTLLoadActionDontCare;
}
else
{
mRenderPassDesc.depthAttachment.loadAction = MTLLoadActionLoad;
}
mRenderPassDesc.depthAttachment.storeAction = MTLStoreActionStore;
// Stencil load/store
if (mRenderPassDesc.stencilAttachment.storeAction == MTLStoreActionDontCare)
{
mRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionDontCare;
}
else
{
mRenderPassDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
}
mRenderPassDesc.stencilAttachment.storeAction = MTLStoreActionStore;
}
angle::Result FramebufferMtl::updateColorRenderTarget(const gl::Context *context,
size_t colorIndexGL)
{
ASSERT(colorIndexGL < mtl::kMaxRenderTargets);
// Reset load store action
mRenderPassDesc.colorAttachments[colorIndexGL].reset();
return updateCachedRenderTarget(context, mState.getColorAttachment(colorIndexGL),
&mColorRenderTargets[colorIndexGL]);
}
angle::Result FramebufferMtl::updateDepthRenderTarget(const gl::Context *context)
{
// Reset load store action
mRenderPassDesc.depthAttachment.reset();
return updateCachedRenderTarget(context, mState.getDepthAttachment(), &mDepthRenderTarget);
}
angle::Result FramebufferMtl::updateStencilRenderTarget(const gl::Context *context)
{
// Reset load store action
mRenderPassDesc.stencilAttachment.reset();
return updateCachedRenderTarget(context, mState.getStencilAttachment(), &mStencilRenderTarget);
}
angle::Result FramebufferMtl::updateCachedRenderTarget(const gl::Context *context,
const gl::FramebufferAttachment *attachment,
RenderTargetMtl **cachedRenderTarget)
{
RenderTargetMtl *newRenderTarget = nullptr;
if (attachment)
{
ASSERT(attachment->isAttached());
ANGLE_TRY(attachment->getRenderTarget(context, attachment->getRenderToTextureSamples(),
&newRenderTarget));
}
*cachedRenderTarget = newRenderTarget;
return angle::Result::Continue;
}
angle::Result FramebufferMtl::prepareRenderPass(const gl::Context *context,
gl::DrawBufferMask drawColorBuffers,
mtl::RenderPassDesc *pDescOut)
{
auto &desc = *pDescOut;
desc.numColorAttachments = static_cast<uint32_t>(drawColorBuffers.count());
size_t attachmentIdx = 0;
for (size_t colorIndexGL : drawColorBuffers)
{
if (colorIndexGL >= mtl::kMaxRenderTargets)
{
continue;
}
const RenderTargetMtl *colorRenderTarget = mColorRenderTargets[colorIndexGL];
ASSERT(colorRenderTarget);
mtl::RenderPassColorAttachmentDesc &colorAttachment =
desc.colorAttachments[attachmentIdx++];
colorRenderTarget->toRenderPassAttachmentDesc(&colorAttachment);
}
if (mDepthRenderTarget)
{
mDepthRenderTarget->toRenderPassAttachmentDesc(&desc.depthAttachment);
}
if (mStencilRenderTarget)
{
mStencilRenderTarget->toRenderPassAttachmentDesc(&desc.stencilAttachment);
}
return angle::Result::Continue;
}
// Override clear color based on texture's write mask
void FramebufferMtl::overrideClearColor(const mtl::TextureRef &texture,
MTLClearColor clearColor,
MTLClearColor *colorOut)
{
*colorOut = mtl::EmulatedAlphaClearColor(clearColor, texture->getColorWritableMask());
}
angle::Result FramebufferMtl::clearWithLoadOp(const gl::Context *context,
gl::DrawBufferMask clearColorBuffers,
const mtl::ClearRectParams &clearOpts)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
bool startedRenderPass = contextMtl->hasStartedRenderPass(mRenderPassDesc);
mtl::RenderCommandEncoder *encoder = nullptr;
mtl::RenderPassDesc tempDesc = mRenderPassDesc;
if (startedRenderPass)
{
encoder = contextMtl->getRenderCommandEncoder();
}
size_t attachmentCount = 0;
for (size_t colorIndexGL : mState.getEnabledDrawBuffers())
{
ASSERT(colorIndexGL < mtl::kMaxRenderTargets);
uint32_t attachmentIdx = static_cast<uint32_t>(attachmentCount++);
mtl::RenderPassColorAttachmentDesc &colorAttachment =
tempDesc.colorAttachments[attachmentIdx];
const mtl::TextureRef &texture = colorAttachment.texture;
if (clearColorBuffers.test(colorIndexGL))
{
if (startedRenderPass)
{
// Render pass already started, and we want to clear this buffer,
// then discard its content before clearing.
encoder->setColorStoreAction(MTLStoreActionDontCare, attachmentIdx);
}
colorAttachment.loadAction = MTLLoadActionClear;
overrideClearColor(texture, clearOpts.clearColor.value(), &colorAttachment.clearColor);
}
else if (startedRenderPass)
{
// If render pass already started and we don't want to clear this buffer,
// then store it with current render encoder and load it before clearing step
encoder->setColorStoreAction(MTLStoreActionStore, attachmentIdx);
colorAttachment.loadAction = MTLLoadActionLoad;
}
}
MTLStoreAction preClearDethpStoreAction = MTLStoreActionStore,
preClearStencilStoreAction = MTLStoreActionStore;
if (clearOpts.clearDepth.valid())
{
preClearDethpStoreAction = MTLStoreActionDontCare;
tempDesc.depthAttachment.loadAction = MTLLoadActionClear;
tempDesc.depthAttachment.clearDepth = clearOpts.clearDepth.value();
}
else if (startedRenderPass)
{
// If render pass already started and we don't want to clear this buffer,
// then store it with current render encoder and load it before clearing step
preClearDethpStoreAction = MTLStoreActionStore;
tempDesc.depthAttachment.loadAction = MTLLoadActionLoad;
}
if (clearOpts.clearStencil.valid())
{
preClearStencilStoreAction = MTLStoreActionDontCare;
tempDesc.stencilAttachment.loadAction = MTLLoadActionClear;
tempDesc.stencilAttachment.clearStencil = clearOpts.clearStencil.value();
}
else if (startedRenderPass)
{
// If render pass already started and we don't want to clear this buffer,
// then store it with current render encoder and load it before clearing step
preClearStencilStoreAction = MTLStoreActionStore;
tempDesc.stencilAttachment.loadAction = MTLLoadActionLoad;
}
// End current render encoder.
if (startedRenderPass)
{
encoder->setDepthStencilStoreAction(preClearDethpStoreAction, preClearStencilStoreAction);
contextMtl->endEncoding(encoder);
}
// Start new render encoder with loadOp=Clear
contextMtl->getRenderCommandEncoder(tempDesc);
return angle::Result::Continue;
}
angle::Result FramebufferMtl::clearWithDraw(const gl::Context *context,
gl::DrawBufferMask clearColorBuffers,
const mtl::ClearRectParams &clearOpts)
{
ContextMtl *contextMtl = mtl::GetImpl(context);
DisplayMtl *display = contextMtl->getDisplay();
// Start new render encoder if not already.
mtl::RenderCommandEncoder *encoder = contextMtl->getRenderCommandEncoder(mRenderPassDesc);
display->getUtils().clearWithDraw(context, encoder, clearOpts);
return angle::Result::Continue;
}
angle::Result FramebufferMtl::clearImpl(const gl::Context *context,
gl::DrawBufferMask clearColorBuffers,
mtl::ClearRectParams *pClearOpts)
{
auto &clearOpts = *pClearOpts;
if (!clearOpts.clearColor.valid() && !clearOpts.clearDepth.valid() &&
!clearOpts.clearStencil.valid())
{
// No Op.
return angle::Result::Continue;
}
ContextMtl *contextMtl = mtl::GetImpl(context);
const gl::Rectangle renderArea(0, 0, mState.getDimensions().width,
mState.getDimensions().height);
clearOpts.clearArea = ClipRectToScissor(contextMtl->getState(), renderArea, false);
clearOpts.flipY = mFlipY;
// Discard clear altogether if scissor has 0 width or height.
if (clearOpts.clearArea.width == 0 || clearOpts.clearArea.height == 0)
{
return angle::Result::Continue;
}
MTLColorWriteMask colorMask = contextMtl->getColorMask();
uint32_t stencilMask = contextMtl->getStencilMask();
if (!contextMtl->isDepthWriteEnabled())
{
// Disable depth clearing, since depth write is disable
clearOpts.clearDepth.reset();
}
if (clearOpts.clearArea == renderArea &&
(!clearOpts.clearColor.valid() || colorMask == MTLColorWriteMaskAll) &&
(!clearOpts.clearStencil.valid() ||
(stencilMask & mtl::kStencilMaskAll) == mtl::kStencilMaskAll))
{
return clearWithLoadOp(context, clearColorBuffers, clearOpts);
}
return clearWithDraw(context, clearColorBuffers, clearOpts);
}
angle::Result FramebufferMtl::invalidateImpl(ContextMtl *contextMtl,
size_t count,
const GLenum *attachments)
{
gl::DrawBufferMask invalidateColorBuffers;
bool invalidateDepthBuffer = false;
bool invalidateStencilBuffer = false;
for (size_t i = 0; i < count; ++i)
{
const GLenum attachment = attachments[i];
switch (attachment)
{
case GL_DEPTH:
case GL_DEPTH_ATTACHMENT:
invalidateDepthBuffer = true;
break;
case GL_STENCIL:
case GL_STENCIL_ATTACHMENT:
invalidateStencilBuffer = true;
break;
case GL_DEPTH_STENCIL_ATTACHMENT:
invalidateDepthBuffer = true;
invalidateStencilBuffer = true;
break;
default:
ASSERT(
(attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15) ||
(attachment == GL_COLOR));
invalidateColorBuffers.set(
attachment == GL_COLOR ? 0u : (attachment - GL_COLOR_ATTACHMENT0));
}
}
// Set the appropriate storeOp for attachments.
// If we already start the render pass, then need to set the store action now.
bool renderPassStarted = contextMtl->hasStartedRenderPass(mRenderPassDesc);
mtl::RenderCommandEncoder *encoder =
renderPassStarted ? contextMtl->getRenderCommandEncoder() : nullptr;
for (uint32_t i = 0; i < mRenderPassDesc.numColorAttachments; ++i)
{
if (invalidateColorBuffers.test(i))
{
mtl::RenderPassColorAttachmentDesc &colorAttachment =
mRenderPassDesc.colorAttachments[i];
colorAttachment.storeAction = MTLStoreActionDontCare;
if (renderPassStarted)
{
encoder->setColorStoreAction(MTLStoreActionDontCare, i);
}
}
}
if (invalidateDepthBuffer && mDepthRenderTarget)
{
mRenderPassDesc.depthAttachment.storeAction = MTLStoreActionDontCare;
if (renderPassStarted)
{
encoder->setDepthStoreAction(MTLStoreActionDontCare);
}
}
if (invalidateStencilBuffer && mStencilRenderTarget)
{
mRenderPassDesc.stencilAttachment.storeAction = MTLStoreActionDontCare;
if (renderPassStarted)
{
encoder->setStencilStoreAction(MTLStoreActionDontCare);
}
}
return angle::Result::Continue;
}
gl::Rectangle FramebufferMtl::getReadPixelArea(const gl::Rectangle &glArea)
{
RenderTargetMtl *colorReadRT = getColorReadRenderTarget();
ASSERT(colorReadRT);
gl::Rectangle flippedArea = glArea;
if (mFlipY)
{
flippedArea.y =
colorReadRT->getTexture()->height(static_cast<uint32_t>(colorReadRT->getLevelIndex())) -
flippedArea.y - flippedArea.height;
}
return flippedArea;
}
angle::Result FramebufferMtl::readPixelsImpl(const gl::Context *context,
const gl::Rectangle &area,
const PackPixelsParams &packPixelsParams,
RenderTargetMtl *renderTarget,
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;
}
const mtl::TextureRef &texture = renderTarget->getTexture();
if (!texture)
{
return angle::Result::Continue;
}
const mtl::Format &readFormat = *renderTarget->getFormat();
const angle::Format &readAngleFormat = readFormat.actualAngleFormat();
// NOTE(hqle): resolve MSAA texture before readback
int srcRowPitch = area.width * readAngleFormat.pixelBytes;
angle::MemoryBuffer readPixelRowBuffer;
ANGLE_CHECK_GL_ALLOC(contextMtl, readPixelRowBuffer.resize(srcRowPitch));
auto packPixelsRowParams = packPixelsParams;
MTLRegion mtlSrcRowRegion = MTLRegionMake2D(area.x, area.y, area.width, 1);
int rowOffset = packPixelsParams.reverseRowOrder ? -1 : 1;
int startRow = packPixelsParams.reverseRowOrder ? (area.y1() - 1) : area.y;
// Copy pixels row by row
packPixelsRowParams.area.height = 1;
packPixelsRowParams.reverseRowOrder = false;
for (int r = startRow, i = 0; i < area.height;
++i, r += rowOffset, pixels += packPixelsRowParams.outputPitch)
{
mtlSrcRowRegion.origin.y = r;
packPixelsRowParams.area.y = packPixelsParams.area.y + i;
// Read the pixels data to the row buffer
texture->getBytes(contextMtl, srcRowPitch, mtlSrcRowRegion,
static_cast<uint32_t>(renderTarget->getLevelIndex()),
readPixelRowBuffer.data());
// Convert to destination format
PackPixels(packPixelsRowParams, readAngleFormat, srcRowPitch, readPixelRowBuffer.data(),
pixels);
}
return angle::Result::Continue;
}
}