//
// Copyright 2015 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.
//

// FramebufferGL.cpp: Implements the class methods for FramebufferGL.

#include "libANGLE/renderer/gl/FramebufferGL.h"

#include "common/bitset_utils.h"
#include "common/debug.h"
#include "libANGLE/ErrorStrings.h"
#include "libANGLE/FramebufferAttachment.h"
#include "libANGLE/State.h"
#include "libANGLE/angletypes.h"
#include "libANGLE/formatutils.h"
#include "libANGLE/queryconversions.h"
#include "libANGLE/renderer/ContextImpl.h"
#include "libANGLE/renderer/gl/BlitGL.h"
#include "libANGLE/renderer/gl/ClearMultiviewGL.h"
#include "libANGLE/renderer/gl/ContextGL.h"
#include "libANGLE/renderer/gl/FunctionsGL.h"
#include "libANGLE/renderer/gl/RenderbufferGL.h"
#include "libANGLE/renderer/gl/StateManagerGL.h"
#include "libANGLE/renderer/gl/TextureGL.h"
#include "libANGLE/renderer/gl/formatutilsgl.h"
#include "libANGLE/renderer/gl/renderergl_utils.h"
#include "platform/PlatformMethods.h"
#include "platform/autogen/FeaturesGL_autogen.h"

using namespace gl;
using angle::CheckedNumeric;

namespace rx
{

namespace
{

struct BlitFramebufferBounds
{
    gl::Rectangle sourceBounds;
    gl::Rectangle sourceRegion;

    gl::Rectangle destBounds;
    gl::Rectangle destRegion;

    bool xFlipped;
    bool yFlipped;
};

static BlitFramebufferBounds GetBlitFramebufferBounds(const gl::Context *context,
                                                      const gl::Rectangle &sourceArea,
                                                      const gl::Rectangle &destArea)
{
    BlitFramebufferBounds bounds;

    const Framebuffer *sourceFramebuffer = context->getState().getReadFramebuffer();
    const Framebuffer *destFramebuffer   = context->getState().getDrawFramebuffer();

    gl::Extents readSize = sourceFramebuffer->getExtents();
    gl::Extents drawSize = destFramebuffer->getExtents();

    bounds.sourceBounds = gl::Rectangle(0, 0, readSize.width, readSize.height);
    bounds.sourceRegion = sourceArea.removeReversal();

    bounds.destBounds = gl::Rectangle(0, 0, drawSize.width, drawSize.height);
    bounds.destRegion = destArea.removeReversal();

    bounds.xFlipped = sourceArea.isReversedX() != destArea.isReversedX();
    bounds.yFlipped = sourceArea.isReversedY() != destArea.isReversedY();

    return bounds;
}

void BindFramebufferAttachment(const FunctionsGL *functions,
                               GLenum attachmentPoint,
                               const FramebufferAttachment *attachment,
                               const angle::FeaturesGL &features)
{
    if (attachment)
    {
        if (attachment->type() == GL_TEXTURE)
        {
            const Texture *texture     = attachment->getTexture();
            const TextureGL *textureGL = GetImplAs<TextureGL>(texture);

            if (texture->getType() == TextureType::_2D ||
                texture->getType() == TextureType::_2DMultisample ||
                texture->getType() == TextureType::Rectangle ||
                texture->getType() == TextureType::External)
            {
                if (attachment->isRenderToTexture())
                {
                    if (functions->framebufferTexture2DMultisampleEXT)
                    {
                        functions->framebufferTexture2DMultisampleEXT(
                            GL_FRAMEBUFFER, attachmentPoint, ToGLenum(texture->getType()),
                            textureGL->getTextureID(), attachment->mipLevel(),
                            attachment->getSamples());
                    }
                    else
                    {
                        ASSERT(functions->framebufferTexture2DMultisampleIMG);
                        functions->framebufferTexture2DMultisampleIMG(
                            GL_FRAMEBUFFER, attachmentPoint, ToGLenum(texture->getType()),
                            textureGL->getTextureID(), attachment->mipLevel(),
                            attachment->getSamples());
                    }
                }
                else
                {
                    functions->framebufferTexture2D(
                        GL_FRAMEBUFFER, attachmentPoint, ToGLenum(texture->getType()),
                        textureGL->getTextureID(), attachment->mipLevel());
                }
            }
            else if (attachment->isLayered())
            {
                TextureType textureType = texture->getType();
                ASSERT(textureType == TextureType::_2DArray || textureType == TextureType::_3D ||
                       textureType == TextureType::CubeMap ||
                       textureType == TextureType::_2DMultisampleArray ||
                       textureType == TextureType::CubeMapArray);
                functions->framebufferTexture(GL_FRAMEBUFFER, attachmentPoint,
                                              textureGL->getTextureID(), attachment->mipLevel());
            }
            else if (texture->getType() == TextureType::CubeMap)
            {
                functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint,
                                                ToGLenum(attachment->cubeMapFace()),
                                                textureGL->getTextureID(), attachment->mipLevel());
            }
            else if (texture->getType() == TextureType::_2DArray ||
                     texture->getType() == TextureType::_3D ||
                     texture->getType() == TextureType::_2DMultisampleArray ||
                     texture->getType() == TextureType::CubeMapArray)
            {
                if (attachment->isMultiview())
                {
                    ASSERT(functions->framebufferTexture);
                    functions->framebufferTexture(GL_FRAMEBUFFER, attachmentPoint,
                                                  textureGL->getTextureID(),
                                                  attachment->mipLevel());
                }
                else
                {
                    functions->framebufferTextureLayer(GL_FRAMEBUFFER, attachmentPoint,
                                                       textureGL->getTextureID(),
                                                       attachment->mipLevel(), attachment->layer());
                }
            }
            else
            {
                UNREACHABLE();
            }
        }
        else if (attachment->type() == GL_RENDERBUFFER)
        {
            const Renderbuffer *renderbuffer     = attachment->getRenderbuffer();
            const RenderbufferGL *renderbufferGL = GetImplAs<RenderbufferGL>(renderbuffer);

            if (features.alwaysUnbindFramebufferTexture2D.enabled)
            {
                functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, 0,
                                                0);
            }

            functions->framebufferRenderbuffer(GL_FRAMEBUFFER, attachmentPoint, GL_RENDERBUFFER,
                                               renderbufferGL->getRenderbufferID());
        }
        else
        {
            UNREACHABLE();
        }
    }
    else
    {
        // Unbind this attachment
        functions->framebufferTexture2D(GL_FRAMEBUFFER, attachmentPoint, GL_TEXTURE_2D, 0, 0);
    }
}

bool AreAllLayersActive(const FramebufferAttachment &attachment)
{
    int baseViewIndex = attachment.getBaseViewIndex();
    if (baseViewIndex != 0)
    {
        return false;
    }
    const ImageIndex &imageIndex = attachment.getTextureImageIndex();
    int numLayers                = static_cast<int>(
        attachment.getTexture()->getDepth(imageIndex.getTarget(), imageIndex.getLevelIndex()));
    return (attachment.getNumViews() == numLayers);
}

bool RequiresMultiviewClear(const FramebufferState &state, bool scissorTestEnabled)
{
    // Get one attachment and check whether all layers are attached.
    const FramebufferAttachment *attachment = nullptr;
    bool allTextureArraysAreFullyAttached   = true;
    for (const FramebufferAttachment &colorAttachment : state.getColorAttachments())
    {
        if (colorAttachment.isAttached())
        {
            if (!colorAttachment.isMultiview())
            {
                return false;
            }
            attachment = &colorAttachment;
            allTextureArraysAreFullyAttached =
                allTextureArraysAreFullyAttached && AreAllLayersActive(*attachment);
        }
    }

    const FramebufferAttachment *depthAttachment = state.getDepthAttachment();
    if (depthAttachment)
    {
        if (!depthAttachment->isMultiview())
        {
            return false;
        }
        attachment = depthAttachment;
        allTextureArraysAreFullyAttached =
            allTextureArraysAreFullyAttached && AreAllLayersActive(*attachment);
    }
    const FramebufferAttachment *stencilAttachment = state.getStencilAttachment();
    if (stencilAttachment)
    {
        if (!stencilAttachment->isMultiview())
        {
            return false;
        }
        attachment = stencilAttachment;
        allTextureArraysAreFullyAttached =
            allTextureArraysAreFullyAttached && AreAllLayersActive(*attachment);
    }

    if (attachment == nullptr)
    {
        return false;
    }
    if (attachment->isMultiview())
    {
        // If all layers of each texture array are active, then there is no need to issue a
        // special multiview clear.
        return !allTextureArraysAreFullyAttached;
    }
    return false;
}

bool IsEmulatedAlphaChannelTextureAttachment(const FramebufferAttachment *attachment)
{
    if (!attachment || attachment->type() != GL_TEXTURE)
    {
        return false;
    }

    const Texture *texture     = attachment->getTexture();
    const TextureGL *textureGL = GetImplAs<TextureGL>(texture);
    return textureGL->hasEmulatedAlphaChannel(attachment->getTextureImageIndex());
}

class [[nodiscard]] ScopedEXTTextureNorm16ReadbackWorkaround
{
  public:
    ScopedEXTTextureNorm16ReadbackWorkaround()
        : tmpPixels(nullptr), clientPixels(nullptr), enabled(false)
    {}

    ~ScopedEXTTextureNorm16ReadbackWorkaround()
    {
        if (tmpPixels)
        {
            delete[] tmpPixels;
        }
    }

    angle::Result Initialize(const gl::Context *context,
                             const gl::Rectangle &area,
                             GLenum originalReadFormat,
                             GLenum format,
                             GLenum type,
                             GLuint skipBytes,
                             GLuint rowBytes,
                             GLuint pixelBytes,
                             GLubyte *pixels)
    {
        // Separate from constructor as there may be checked math result exception that needs to
        // early return
        ASSERT(tmpPixels == nullptr);
        ASSERT(clientPixels == nullptr);

        ContextGL *contextGL              = GetImplAs<ContextGL>(context);
        const angle::FeaturesGL &features = GetFeaturesGL(context);

        enabled = features.readPixelsUsingImplementationColorReadFormatForNorm16.enabled &&
                  type == GL_UNSIGNED_SHORT && originalReadFormat == GL_RGBA &&
                  (format == GL_RED || format == GL_RG);

        clientPixels = pixels;

        if (enabled)
        {
            CheckedNumeric<GLuint> checkedRowBytes(rowBytes);
            CheckedNumeric<GLuint> checkedRows(area.height);
            CheckedNumeric<GLuint> checkedSkipBytes(skipBytes);
            auto checkedAllocatedBytes = checkedSkipBytes + checkedRowBytes * checkedRows;
            if (rowBytes < area.width * pixelBytes)
            {
                checkedAllocatedBytes += area.width * pixelBytes - rowBytes;
            }
            ANGLE_CHECK_GL_MATH(contextGL, checkedAllocatedBytes.IsValid());
            const GLuint allocatedBytes = checkedAllocatedBytes.ValueOrDie();
            tmpPixels                   = new GLubyte[allocatedBytes];
            memset(tmpPixels, 0, allocatedBytes);
        }

        return angle::Result::Continue;
    }

    GLubyte *Pixels() const { return tmpPixels ? tmpPixels : clientPixels; }

    bool IsEnabled() const { return enabled; }

  private:
    // Temporarily allocated pixel readback buffer
    GLubyte *tmpPixels;
    // Client pixel array pointer passed to readPixels
    GLubyte *clientPixels;

    bool enabled;
};

// Workaround to rearrange pixels read by RED/RG to RGBA for RGBA/UNSIGNED_SHORT pixel type
// combination
angle::Result RearrangeEXTTextureNorm16Pixels(const gl::Context *context,
                                              const gl::Rectangle &area,
                                              GLenum originalReadFormat,
                                              GLenum format,
                                              GLenum type,
                                              GLuint skipBytes,
                                              GLuint rowBytes,
                                              GLuint pixelBytes,
                                              const gl::PixelPackState &pack,
                                              GLubyte *clientPixels,
                                              GLubyte *tmpPixels)
{
    ASSERT(tmpPixels != nullptr);
    ASSERT(originalReadFormat == GL_RGBA);
    ASSERT(format == GL_RED_EXT || format == GL_RG_EXT);
    ASSERT(type == GL_UNSIGNED_SHORT);

    ContextGL *contextGL = GetImplAs<ContextGL>(context);

    const gl::InternalFormat &glFormatOriginal =
        gl::GetInternalFormatInfo(originalReadFormat, type);

    GLuint originalReadFormatRowBytes = 0;
    ANGLE_CHECK_GL_MATH(
        contextGL, glFormatOriginal.computeRowPitch(type, area.width, pack.alignment,
                                                    pack.rowLength, &originalReadFormatRowBytes));
    GLuint originalReadFormatSkipBytes = 0;
    ANGLE_CHECK_GL_MATH(contextGL,
                        glFormatOriginal.computeSkipBytes(type, originalReadFormatRowBytes, 0, pack,
                                                          false, &originalReadFormatSkipBytes));

    GLuint originalReadFormatPixelBytes = glFormatOriginal.computePixelBytes(type);
    GLuint alphaChannelBytes            = glFormatOriginal.alphaBits / 8;

    ASSERT(originalReadFormatPixelBytes > pixelBytes);
    ASSERT(originalReadFormatPixelBytes > alphaChannelBytes);
    ASSERT(alphaChannelBytes != 0);
    ASSERT(glFormatOriginal.alphaBits % 8 == 0);

    // Populating rearrangedPixels values from pixels
    GLubyte *srcRowStart = tmpPixels;
    GLubyte *dstRowStart = clientPixels;

    srcRowStart += skipBytes;
    dstRowStart += originalReadFormatSkipBytes;

    for (GLint y = 0; y < area.height; ++y)
    {
        GLubyte *src = srcRowStart;
        GLubyte *dst = dstRowStart;
        for (GLint x = 0; x < area.width; ++x)
        {
            GLushort *srcPixel = reinterpret_cast<GLushort *>(src);
            GLushort *dstPixel = reinterpret_cast<GLushort *>(dst);
            dstPixel[0]        = srcPixel[0];
            dstPixel[1]        = format == GL_RG ? srcPixel[1] : 0;
            // Set other channel of RGBA to 0 (GB when format == GL_RED, B when format == GL_RG)
            dstPixel[2] = 0;
            // Set alpha channel to 1
            dstPixel[3] = 0xFFFF;

            src += pixelBytes;
            dst += originalReadFormatPixelBytes;
        }

        srcRowStart += rowBytes;
        dstRowStart += originalReadFormatRowBytes;
    }

    return angle::Result::Continue;
}

bool IsValidUnsignedShortReadPixelsFormat(GLenum readFormat, const gl::Context *context)
{
    return (readFormat == GL_RED) || (readFormat == GL_RG) || (readFormat == GL_RGBA) ||
           ((readFormat == GL_DEPTH_COMPONENT) && (context->getExtensions().readDepthNV));
}

}  // namespace

FramebufferGL::FramebufferGL(const gl::FramebufferState &data, GLuint id, bool emulatedAlpha)
    : FramebufferImpl(data),
      mFramebufferID(id),
      mHasEmulatedAlphaAttachment(emulatedAlpha),
      mAppliedEnabledDrawBuffers(1)
{
    ASSERT((isDefault() && id == 0) || !isDefault());
}

FramebufferGL::~FramebufferGL()
{
    ASSERT(mFramebufferID == 0);
}

void FramebufferGL::destroy(const gl::Context *context)
{
    if (mFramebufferID)
    {
        ASSERT(!isDefault());
        StateManagerGL *stateManager = GetStateManagerGL(context);
        stateManager->deleteFramebuffer(mFramebufferID);
        mFramebufferID = 0;
    }
}

angle::Result FramebufferGL::discard(const gl::Context *context,
                                     size_t count,
                                     const GLenum *attachments)
{
    // glInvalidateFramebuffer accepts the same enums as glDiscardFramebufferEXT
    return invalidate(context, count, attachments);
}

angle::Result FramebufferGL::invalidate(const gl::Context *context,
                                        size_t count,
                                        const GLenum *attachments)
{
    const GLenum *finalAttachmentsPtr = attachments;

    std::vector<GLenum> modifiedAttachments;
    if (modifyInvalidateAttachmentsForEmulatedDefaultFBO(count, attachments, &modifiedAttachments))
    {
        finalAttachmentsPtr = modifiedAttachments.data();
    }

    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    // Since this function is just a hint, only call a native function if it exists.
    if (functions->invalidateFramebuffer)
    {
        stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
        functions->invalidateFramebuffer(GL_FRAMEBUFFER, static_cast<GLsizei>(count),
                                         finalAttachmentsPtr);
    }
    else if (functions->discardFramebufferEXT)
    {
        stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
        functions->discardFramebufferEXT(GL_FRAMEBUFFER, static_cast<GLsizei>(count),
                                         finalAttachmentsPtr);
    }

    return angle::Result::Continue;
}

angle::Result FramebufferGL::invalidateSub(const gl::Context *context,
                                           size_t count,
                                           const GLenum *attachments,
                                           const gl::Rectangle &area)
{

    const GLenum *finalAttachmentsPtr = attachments;

    std::vector<GLenum> modifiedAttachments;
    if (modifyInvalidateAttachmentsForEmulatedDefaultFBO(count, attachments, &modifiedAttachments))
    {
        finalAttachmentsPtr = modifiedAttachments.data();
    }

    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    // Since this function is just a hint and not available until OpenGL 4.3, only call it if it is
    // available.
    if (functions->invalidateSubFramebuffer)
    {
        stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
        functions->invalidateSubFramebuffer(GL_FRAMEBUFFER, static_cast<GLsizei>(count),
                                            finalAttachmentsPtr, area.x, area.y, area.width,
                                            area.height);
    }

    return angle::Result::Continue;
}

angle::Result FramebufferGL::clear(const gl::Context *context, GLbitfield mask)
{
    ContextGL *contextGL         = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    syncClearState(context, mask);
    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);

    if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled()))
    {
        functions->clear(mask);
    }
    else
    {
        ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context);
        multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(),
                                            ClearMultiviewGL::ClearCommandType::Clear, mask,
                                            GL_NONE, 0, nullptr, 0.0f, 0);
    }

    contextGL->markWorkSubmitted();
    return angle::Result::Continue;
}

angle::Result FramebufferGL::clearBufferfv(const gl::Context *context,
                                           GLenum buffer,
                                           GLint drawbuffer,
                                           const GLfloat *values)
{
    ContextGL *contextGL         = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    syncClearBufferState(context, buffer, drawbuffer);
    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);

    if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled()))
    {
        functions->clearBufferfv(buffer, drawbuffer, values);
    }
    else
    {
        ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context);
        multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(),
                                            ClearMultiviewGL::ClearCommandType::ClearBufferfv,
                                            static_cast<GLbitfield>(0u), buffer, drawbuffer,
                                            reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
    }

    contextGL->markWorkSubmitted();
    return angle::Result::Continue;
}

angle::Result FramebufferGL::clearBufferuiv(const gl::Context *context,
                                            GLenum buffer,
                                            GLint drawbuffer,
                                            const GLuint *values)
{
    ContextGL *contextGL         = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    syncClearBufferState(context, buffer, drawbuffer);
    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);

    if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled()))
    {
        functions->clearBufferuiv(buffer, drawbuffer, values);
    }
    else
    {
        ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context);
        multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(),
                                            ClearMultiviewGL::ClearCommandType::ClearBufferuiv,
                                            static_cast<GLbitfield>(0u), buffer, drawbuffer,
                                            reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
    }

    contextGL->markWorkSubmitted();
    return angle::Result::Continue;
}

angle::Result FramebufferGL::clearBufferiv(const gl::Context *context,
                                           GLenum buffer,
                                           GLint drawbuffer,
                                           const GLint *values)
{
    ContextGL *contextGL         = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    syncClearBufferState(context, buffer, drawbuffer);
    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);

    if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled()))
    {
        functions->clearBufferiv(buffer, drawbuffer, values);
    }
    else
    {
        ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context);
        multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(),
                                            ClearMultiviewGL::ClearCommandType::ClearBufferiv,
                                            static_cast<GLbitfield>(0u), buffer, drawbuffer,
                                            reinterpret_cast<const uint8_t *>(values), 0.0f, 0);
    }

    contextGL->markWorkSubmitted();
    return angle::Result::Continue;
}

angle::Result FramebufferGL::clearBufferfi(const gl::Context *context,
                                           GLenum buffer,
                                           GLint drawbuffer,
                                           GLfloat depth,
                                           GLint stencil)
{
    ContextGL *contextGL         = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    syncClearBufferState(context, buffer, drawbuffer);
    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);

    if (!RequiresMultiviewClear(mState, context->getState().isScissorTestEnabled()))
    {
        functions->clearBufferfi(buffer, drawbuffer, depth, stencil);
    }
    else
    {
        ClearMultiviewGL *multiviewClearer = GetMultiviewClearer(context);
        multiviewClearer->clearMultiviewFBO(mState, context->getState().getScissor(),
                                            ClearMultiviewGL::ClearCommandType::ClearBufferfi,
                                            static_cast<GLbitfield>(0u), buffer, drawbuffer,
                                            nullptr, depth, stencil);
    }

    contextGL->markWorkSubmitted();
    return angle::Result::Continue;
}

angle::Result FramebufferGL::readPixels(const gl::Context *context,
                                        const gl::Rectangle &area,
                                        GLenum format,
                                        GLenum type,
                                        const gl::PixelPackState &pack,
                                        gl::Buffer *packBuffer,
                                        void *pixels)
{
    ContextGL *contextGL              = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions      = GetFunctionsGL(context);
    StateManagerGL *stateManager      = GetStateManagerGL(context);
    const angle::FeaturesGL &features = GetFeaturesGL(context);
    gl::PixelPackState packState      = pack;

    // Clip read area to framebuffer.
    const auto *readAttachment = mState.getReadPixelsAttachment(format);
    const gl::Extents fbSize   = readAttachment->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;
    }

    GLenum attachmentReadFormat =
        readAttachment->getFormat().info->getReadPixelsFormat(context->getExtensions());
    nativegl::ReadPixelsFormat readPixelsFormat =
        nativegl::GetReadPixelsFormat(functions, features, attachmentReadFormat, format, type);
    GLenum readFormat = readPixelsFormat.format;
    GLenum readType   = readPixelsFormat.type;
    if (features.readPixelsUsingImplementationColorReadFormatForNorm16.enabled &&
        readType == GL_UNSIGNED_SHORT)
    {
        ANGLE_CHECK(contextGL, IsValidUnsignedShortReadPixelsFormat(readFormat, context),
                    "glReadPixels: GL_IMPLEMENTATION_COLOR_READ_FORMAT advertised by the driver is "
                    "not handled by RGBA16 readPixels workaround.",
                    GL_INVALID_OPERATION);
    }

    GLenum framebufferTarget =
        stateManager->getHasSeparateFramebufferBindings() ? GL_READ_FRAMEBUFFER : GL_FRAMEBUFFER;
    stateManager->bindFramebuffer(framebufferTarget, mFramebufferID);

    bool useOverlappingRowsWorkaround = features.packOverlappingRowsSeparatelyPackBuffer.enabled &&
                                        packBuffer && packState.rowLength != 0 &&
                                        packState.rowLength < clippedArea.width;

    GLubyte *outPtr = static_cast<GLubyte *>(pixels);
    int leftClip    = clippedArea.x - area.x;
    int topClip     = clippedArea.y - area.y;
    if (leftClip || topClip)
    {
        // Adjust destination to match portion clipped off left and/or top.
        const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(readFormat, readType);

        GLuint rowBytes = 0;
        ANGLE_CHECK_GL_MATH(contextGL,
                            glFormat.computeRowPitch(readType, area.width, packState.alignment,
                                                     packState.rowLength, &rowBytes));
        outPtr += leftClip * glFormat.pixelBytes + topClip * rowBytes;
    }

    if (packState.rowLength == 0 && clippedArea.width != area.width)
    {
        // No rowLength was specified so it will derive from read width, but clipping changed the
        // read width.  Use the original width so we fill the user's buffer as they intended.
        packState.rowLength = area.width;
    }

    // We want to use rowLength, but that might not be supported.
    bool cannotSetDesiredRowLength =
        packState.rowLength && !GetImplAs<ContextGL>(context)->getNativeExtensions().packSubimageNV;

    bool usePackSkipWorkaround = features.emulatePackSkipRowsAndPackSkipPixels.enabled &&
                                 (packState.skipRows != 0 || packState.skipPixels != 0);

    if (cannotSetDesiredRowLength || useOverlappingRowsWorkaround || usePackSkipWorkaround)
    {
        return readPixelsRowByRow(context, clippedArea, format, readFormat, readType, packState,
                                  outPtr);
    }

    bool useLastRowPaddingWorkaround = false;
    if (features.packLastRowSeparatelyForPaddingInclusion.enabled)
    {
        ANGLE_TRY(ShouldApplyLastRowPaddingWorkaround(
            contextGL, gl::Extents(clippedArea.width, clippedArea.height, 1), packState, packBuffer,
            readFormat, readType, false, outPtr, &useLastRowPaddingWorkaround));
    }

    return readPixelsAllAtOnce(context, clippedArea, format, readFormat, readType, packState,
                               outPtr, useLastRowPaddingWorkaround);
}

angle::Result FramebufferGL::blit(const gl::Context *context,
                                  const gl::Rectangle &sourceArea,
                                  const gl::Rectangle &destArea,
                                  GLbitfield mask,
                                  GLenum filter)
{
    ContextGL *contextGL              = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions      = GetFunctionsGL(context);
    StateManagerGL *stateManager      = GetStateManagerGL(context);
    const angle::FeaturesGL &features = GetFeaturesGL(context);

    const Framebuffer *sourceFramebuffer = context->getState().getReadFramebuffer();
    const Framebuffer *destFramebuffer   = context->getState().getDrawFramebuffer();

    const FramebufferAttachment *colorReadAttachment = sourceFramebuffer->getReadColorAttachment();

    GLsizei readAttachmentSamples = 0;
    if (colorReadAttachment != nullptr)
    {
        // Blitting requires that the textures be single sampled. getSamples will return
        // emulated sample number, but the EXT_multisampled_render_to_texture extension will
        // take care of resolving the texture, so even if emulated samples > 0, we should still
        // be able to blit as long as the underlying resource samples is single sampled.
        readAttachmentSamples = colorReadAttachment->getResourceSamples();
    }

    bool needManualColorBlit = false;

    // TODO(cwallez) when the filter is LINEAR and both source and destination are SRGB, we
    // could avoid doing a manual blit.

    // Prior to OpenGL 4.4 BlitFramebuffer (section 18.3.1 of GL 4.3 core profile) reads:
    //      When values are taken from the read buffer, no linearization is performed, even
    //      if the format of the buffer is SRGB.
    // Starting from OpenGL 4.4 (section 18.3.1) it reads:
    //      When values are taken from the read buffer, if FRAMEBUFFER_SRGB is enabled and the
    //      value of FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING for the framebuffer attachment
    //      corresponding to the read buffer is SRGB, the red, green, and blue components are
    //      converted from the non-linear sRGB color space according [...].
    {
        bool sourceSRGB =
            colorReadAttachment != nullptr && colorReadAttachment->getColorEncoding() == GL_SRGB;
        needManualColorBlit =
            needManualColorBlit || (sourceSRGB && functions->isAtMostGL(gl::Version(4, 3)));
    }

    // Prior to OpenGL 4.2 BlitFramebuffer (section 4.3.2 of GL 4.1 core profile) reads:
    //      Blit operations bypass the fragment pipeline. The only fragment operations which
    //      affect a blit are the pixel ownership test and scissor test.
    // Starting from OpenGL 4.2 (section 4.3.2) it reads:
    //      When values are written to the draw buffers, blit operations bypass the fragment
    //      pipeline. The only fragment operations which affect a blit are the pixel ownership
    //      test,  the scissor test and sRGB conversion.
    if (!needManualColorBlit)
    {
        bool destSRGB = false;
        for (size_t i = 0; i < destFramebuffer->getDrawbufferStateCount(); ++i)
        {
            const FramebufferAttachment *attachment = destFramebuffer->getDrawBuffer(i);
            if (attachment && attachment->getColorEncoding() == GL_SRGB)
            {
                destSRGB = true;
                break;
            }
        }

        needManualColorBlit =
            needManualColorBlit || (destSRGB && functions->isAtMostGL(gl::Version(4, 1)));
    }

    // If the destination has an emulated alpha channel, we need to blit with a shader with alpha
    // writes disabled.
    if (mHasEmulatedAlphaAttachment)
    {
        needManualColorBlit = true;
    }

    // Enable FRAMEBUFFER_SRGB if needed
    stateManager->setFramebufferSRGBEnabledForFramebuffer(context, true, this);

    GLenum blitMask = mask;
    if (needManualColorBlit && (mask & GL_COLOR_BUFFER_BIT) && readAttachmentSamples <= 1)
    {
        BlitGL *blitter = GetBlitGL(context);
        ANGLE_TRY(blitter->blitColorBufferWithShader(context, sourceFramebuffer, destFramebuffer,
                                                     sourceArea, destArea, filter,
                                                     !mHasEmulatedAlphaAttachment));
        blitMask &= ~GL_COLOR_BUFFER_BIT;
    }

    if (blitMask == 0)
    {
        return angle::Result::Continue;
    }

    const FramebufferGL *sourceFramebufferGL = GetImplAs<FramebufferGL>(sourceFramebuffer);
    stateManager->bindFramebuffer(GL_READ_FRAMEBUFFER, sourceFramebufferGL->getFramebufferID());
    stateManager->bindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebufferID);

    gl::Rectangle finalSourceArea(sourceArea);
    gl::Rectangle finalDestArea(destArea);

    if (features.adjustSrcDstRegionForBlitFramebuffer.enabled)
    {
        angle::Result result = adjustSrcDstRegion(context, finalSourceArea, finalDestArea,
                                                  &finalSourceArea, &finalDestArea);
        if (result != angle::Result::Continue)
        {
            return result;
        }
    }
    if (features.clipSrcRegionForBlitFramebuffer.enabled)
    {
        angle::Result result = clipSrcRegion(context, finalSourceArea, finalDestArea,
                                             &finalSourceArea, &finalDestArea);
        if (result != angle::Result::Continue)
        {
            return result;
        }
    }

    functions->blitFramebuffer(finalSourceArea.x, finalSourceArea.y, finalSourceArea.x1(),
                               finalSourceArea.y1(), finalDestArea.x, finalDestArea.y,
                               finalDestArea.x1(), finalDestArea.y1(), blitMask, filter);

    contextGL->markWorkSubmitted();
    return angle::Result::Continue;
}

angle::Result FramebufferGL::adjustSrcDstRegion(const gl::Context *context,
                                                const gl::Rectangle &sourceArea,
                                                const gl::Rectangle &destArea,
                                                gl::Rectangle *newSourceArea,
                                                gl::Rectangle *newDestArea)
{
    BlitFramebufferBounds bounds = GetBlitFramebufferBounds(context, sourceArea, destArea);

    if (bounds.destRegion.width == 0 || bounds.sourceRegion.width == 0 ||
        bounds.destRegion.height == 0 || bounds.sourceRegion.height == 0)
    {
        return angle::Result::Stop;
    }
    if (!ClipRectangle(bounds.destBounds, bounds.destRegion, nullptr))
    {
        return angle::Result::Stop;
    }

    if (!bounds.destBounds.encloses(bounds.destRegion))
    {
        // destRegion is not within destBounds. We want to adjust it to a
        // reasonable size. This is done by halving the destRegion until it is at
        // most twice the size of the framebuffer. We cut it in half instead
        // of arbitrarily shrinking it to fit so that we don't end up with
        // non-power-of-two scale factors which could mess up pixel interpolation.
        // Naively clipping the dst rect and then proportionally sizing the
        // src rect yields incorrect results.

        GLuint destXHalvings = 0;
        GLuint destYHalvings = 0;
        GLint destOriginX    = bounds.destRegion.x;
        GLint destOriginY    = bounds.destRegion.y;

        GLint destClippedWidth = bounds.destRegion.width;
        while (destClippedWidth > 2 * bounds.destBounds.width)
        {
            destClippedWidth = destClippedWidth / 2;
            destXHalvings++;
        }

        GLint destClippedHeight = bounds.destRegion.height;
        while (destClippedHeight > 2 * bounds.destBounds.height)
        {
            destClippedHeight = destClippedHeight / 2;
            destYHalvings++;
        }

        // Before this block, we check that the two rectangles intersect.
        // Now, compute the location of a new region origin such that we use the
        // scaled dimensions but the new region has the same intersection as the
        // original region.

        GLint left   = bounds.destRegion.x0();
        GLint right  = bounds.destRegion.x1();
        GLint top    = bounds.destRegion.y0();
        GLint bottom = bounds.destRegion.y1();

        GLint extraXOffset = 0;
        if (left >= 0 && left < bounds.destBounds.width)
        {
            // Left edge is in-bounds
            destOriginX = bounds.destRegion.x;
        }
        else if (right > 0 && right <= bounds.destBounds.width)
        {
            // Right edge is in-bounds
            destOriginX = right - destClippedWidth;
        }
        else
        {
            // Region completely spans bounds
            extraXOffset = (bounds.destRegion.width - destClippedWidth) / 2;
            destOriginX  = bounds.destRegion.x + extraXOffset;
        }

        GLint extraYOffset = 0;
        if (top >= 0 && top < bounds.destBounds.height)
        {
            // Top edge is in-bounds
            destOriginY = bounds.destRegion.y;
        }
        else if (bottom > 0 && bottom <= bounds.destBounds.height)
        {
            // Bottom edge is in-bounds
            destOriginY = bottom - destClippedHeight;
        }
        else
        {
            // Region completely spans bounds
            extraYOffset = (bounds.destRegion.height - destClippedHeight) / 2;
            destOriginY  = bounds.destRegion.y + extraYOffset;
        }

        // Offsets from the bottom left corner of the original region to
        // the bottom left corner of the clipped region.
        // This value (after it is scaled) is the respective offset we will apply
        // to the src origin.

        CheckedNumeric<GLuint> checkedXOffset(destOriginX - bounds.destRegion.x - extraXOffset / 2);
        CheckedNumeric<GLuint> checkedYOffset(destOriginY - bounds.destRegion.y - extraYOffset / 2);

        // if X/Y is reversed, use the top/right out-of-bounds region to compute
        // the origin offset instead of the left/bottom out-of-bounds region
        if (bounds.xFlipped)
        {
            checkedXOffset =
                (bounds.destRegion.x1() - (destOriginX + destClippedWidth) + extraXOffset / 2);
        }
        if (bounds.yFlipped)
        {
            checkedYOffset =
                (bounds.destRegion.y1() - (destOriginY + destClippedHeight) + extraYOffset / 2);
        }

        // These offsets should never overflow
        GLuint xOffset, yOffset;
        if (!checkedXOffset.AssignIfValid(&xOffset) || !checkedYOffset.AssignIfValid(&yOffset))
        {
            UNREACHABLE();
            return angle::Result::Stop;
        }

        bounds.destRegion =
            gl::Rectangle(destOriginX, destOriginY, destClippedWidth, destClippedHeight);

        // Adjust the src region by the same factor
        bounds.sourceRegion = gl::Rectangle(bounds.sourceRegion.x + (xOffset >> destXHalvings),
                                            bounds.sourceRegion.y + (yOffset >> destYHalvings),
                                            bounds.sourceRegion.width >> destXHalvings,
                                            bounds.sourceRegion.height >> destYHalvings);

        // if the src was scaled to 0, set it to 1 so the src is non-empty
        if (bounds.sourceRegion.width == 0)
        {
            bounds.sourceRegion.width = 1;
        }
        if (bounds.sourceRegion.height == 0)
        {
            bounds.sourceRegion.height = 1;
        }
    }

    if (!bounds.sourceBounds.encloses(bounds.sourceRegion))
    {
        // sourceRegion is not within sourceBounds. We want to adjust it to a
        // reasonable size. This is done by halving the sourceRegion until it is at
        // most twice the size of the framebuffer. We cut it in half instead
        // of arbitrarily shrinking it to fit so that we don't end up with
        // non-power-of-two scale factors which could mess up pixel interpolation.
        // Naively clipping the source rect and then proportionally sizing the
        // dest rect yields incorrect results.

        GLuint sourceXHalvings = 0;
        GLuint sourceYHalvings = 0;
        GLint sourceOriginX    = bounds.sourceRegion.x;
        GLint sourceOriginY    = bounds.sourceRegion.y;

        GLint sourceClippedWidth = bounds.sourceRegion.width;
        while (sourceClippedWidth > 2 * bounds.sourceBounds.width)
        {
            sourceClippedWidth = sourceClippedWidth / 2;
            sourceXHalvings++;
        }

        GLint sourceClippedHeight = bounds.sourceRegion.height;
        while (sourceClippedHeight > 2 * bounds.sourceBounds.height)
        {
            sourceClippedHeight = sourceClippedHeight / 2;
            sourceYHalvings++;
        }

        // Before this block, we check that the two rectangles intersect.
        // Now, compute the location of a new region origin such that we use the
        // scaled dimensions but the new region has the same intersection as the
        // original region.

        GLint left   = bounds.sourceRegion.x0();
        GLint right  = bounds.sourceRegion.x1();
        GLint top    = bounds.sourceRegion.y0();
        GLint bottom = bounds.sourceRegion.y1();

        GLint extraXOffset = 0;
        if (left >= 0 && left < bounds.sourceBounds.width)
        {
            // Left edge is in-bounds
            sourceOriginX = bounds.sourceRegion.x;
        }
        else if (right > 0 && right <= bounds.sourceBounds.width)
        {
            // Right edge is in-bounds
            sourceOriginX = right - sourceClippedWidth;
        }
        else
        {
            // Region completely spans bounds
            extraXOffset  = (bounds.sourceRegion.width - sourceClippedWidth) / 2;
            sourceOriginX = bounds.sourceRegion.x + extraXOffset;
        }

        GLint extraYOffset = 0;
        if (top >= 0 && top < bounds.sourceBounds.height)
        {
            // Top edge is in-bounds
            sourceOriginY = bounds.sourceRegion.y;
        }
        else if (bottom > 0 && bottom <= bounds.sourceBounds.height)
        {
            // Bottom edge is in-bounds
            sourceOriginY = bottom - sourceClippedHeight;
        }
        else
        {
            // Region completely spans bounds
            extraYOffset  = (bounds.sourceRegion.height - sourceClippedHeight) / 2;
            sourceOriginY = bounds.sourceRegion.y + extraYOffset;
        }

        // Offsets from the bottom left corner of the original region to
        // the bottom left corner of the clipped region.
        // This value (after it is scaled) is the respective offset we will apply
        // to the dest origin.

        CheckedNumeric<GLuint> checkedXOffset(sourceOriginX - bounds.sourceRegion.x -
                                              extraXOffset / 2);
        CheckedNumeric<GLuint> checkedYOffset(sourceOriginY - bounds.sourceRegion.y -
                                              extraYOffset / 2);

        // if X/Y is reversed, use the top/right out-of-bounds region to compute
        // the origin offset instead of the left/bottom out-of-bounds region
        if (bounds.xFlipped)
        {
            checkedXOffset = (bounds.sourceRegion.x1() - (sourceOriginX + sourceClippedWidth) +
                              extraXOffset / 2);
        }
        if (bounds.yFlipped)
        {
            checkedYOffset = (bounds.sourceRegion.y1() - (sourceOriginY + sourceClippedHeight) +
                              extraYOffset / 2);
        }

        // These offsets should never overflow
        GLuint xOffset, yOffset;
        if (!checkedXOffset.AssignIfValid(&xOffset) || !checkedYOffset.AssignIfValid(&yOffset))
        {
            UNREACHABLE();
            return angle::Result::Stop;
        }

        bounds.sourceRegion =
            gl::Rectangle(sourceOriginX, sourceOriginY, sourceClippedWidth, sourceClippedHeight);

        // Adjust the dest region by the same factor
        bounds.destRegion = gl::Rectangle(bounds.destRegion.x + (xOffset >> sourceXHalvings),
                                          bounds.destRegion.y + (yOffset >> sourceYHalvings),
                                          bounds.destRegion.width >> sourceXHalvings,
                                          bounds.destRegion.height >> sourceYHalvings);
    }
    // Set the src and dst endpoints. If they were previously flipped,
    // set them as flipped.
    *newSourceArea = bounds.sourceRegion.flip(sourceArea.isReversedX(), sourceArea.isReversedY());
    *newDestArea   = bounds.destRegion.flip(destArea.isReversedX(), destArea.isReversedY());

    return angle::Result::Continue;
}

angle::Result FramebufferGL::clipSrcRegion(const gl::Context *context,
                                           const gl::Rectangle &sourceArea,
                                           const gl::Rectangle &destArea,
                                           gl::Rectangle *newSourceArea,
                                           gl::Rectangle *newDestArea)
{
    BlitFramebufferBounds bounds = GetBlitFramebufferBounds(context, sourceArea, destArea);

    if (bounds.destRegion.width == 0 || bounds.sourceRegion.width == 0 ||
        bounds.destRegion.height == 0 || bounds.sourceRegion.height == 0)
    {
        return angle::Result::Stop;
    }
    if (!ClipRectangle(bounds.destBounds, bounds.destRegion, nullptr))
    {
        return angle::Result::Stop;
    }

    if (!bounds.sourceBounds.encloses(bounds.sourceRegion))
    {
        // If pixels lying outside the read framebuffer, adjust src region
        // and dst region to appropriate in-bounds regions respectively.
        gl::Rectangle realSourceRegion;
        if (!ClipRectangle(bounds.sourceRegion, bounds.sourceBounds, &realSourceRegion))
        {
            return angle::Result::Stop;
        }
        GLuint xOffset = realSourceRegion.x - bounds.sourceRegion.x;
        GLuint yOffset = realSourceRegion.y - bounds.sourceRegion.y;

        // if X/Y is reversed, use the top/right out-of-bounds region for mapping
        // to dst region, instead of left/bottom out-of-bounds region for mapping.
        if (bounds.xFlipped)
        {
            xOffset = bounds.sourceRegion.x1() - realSourceRegion.x1();
        }
        if (bounds.yFlipped)
        {
            yOffset = bounds.sourceRegion.y1() - realSourceRegion.y1();
        }

        GLfloat destMappingWidth = static_cast<GLfloat>(realSourceRegion.width) *
                                   bounds.destRegion.width / bounds.sourceRegion.width;
        GLfloat destMappingHeight = static_cast<GLfloat>(realSourceRegion.height) *
                                    bounds.destRegion.height / bounds.sourceRegion.height;
        GLfloat destMappingXOffset =
            static_cast<GLfloat>(xOffset) * bounds.destRegion.width / bounds.sourceRegion.width;
        GLfloat destMappingYOffset =
            static_cast<GLfloat>(yOffset) * bounds.destRegion.height / bounds.sourceRegion.height;

        GLuint destMappingX0 =
            static_cast<GLuint>(std::round(bounds.destRegion.x + destMappingXOffset));
        GLuint destMappingY0 =
            static_cast<GLuint>(std::round(bounds.destRegion.y + destMappingYOffset));

        GLuint destMappingX1 = static_cast<GLuint>(
            std::round(bounds.destRegion.x + destMappingXOffset + destMappingWidth));
        GLuint destMappingY1 = static_cast<GLuint>(
            std::round(bounds.destRegion.y + destMappingYOffset + destMappingHeight));

        bounds.destRegion =
            gl::Rectangle(destMappingX0, destMappingY0, destMappingX1 - destMappingX0,
                          destMappingY1 - destMappingY0);

        bounds.sourceRegion = realSourceRegion;
    }
    // Set the src and dst endpoints. If they were previously flipped,
    // set them as flipped.
    *newSourceArea = bounds.sourceRegion.flip(sourceArea.isReversedX(), sourceArea.isReversedY());
    *newDestArea   = bounds.destRegion.flip(destArea.isReversedX(), destArea.isReversedY());

    return angle::Result::Continue;
}

angle::Result FramebufferGL::getSamplePosition(const gl::Context *context,
                                               size_t index,
                                               GLfloat *xy) const
{
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
    functions->getMultisamplefv(GL_SAMPLE_POSITION, static_cast<GLuint>(index), xy);
    return angle::Result::Continue;
}

bool FramebufferGL::shouldSyncStateBeforeCheckStatus() const
{
    return true;
}

gl::FramebufferStatus FramebufferGL::checkStatus(const gl::Context *context) const
{
    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);
    GLenum status = functions->checkFramebufferStatus(GL_FRAMEBUFFER);
    if (status != GL_FRAMEBUFFER_COMPLETE)
    {
        WARN() << "GL framebuffer returned incomplete: " << gl::FmtHex(status);
        return gl::FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNSUPPORTED,
                                                 gl::err::kFramebufferIncompleteDriverUnsupported);
    }

    return gl::FramebufferStatus::Complete();
}

angle::Result FramebufferGL::syncState(const gl::Context *context,
                                       GLenum binding,
                                       const gl::Framebuffer::DirtyBits &dirtyBits,
                                       gl::Command command)
{
    // Don't need to sync state for the default FBO.
    if (isDefault())
    {
        return angle::Result::Continue;
    }

    const FunctionsGL *functions = GetFunctionsGL(context);
    StateManagerGL *stateManager = GetStateManagerGL(context);

    stateManager->bindFramebuffer(GL_FRAMEBUFFER, mFramebufferID);

    // A pointer to one of the attachments for which the texture or the render buffer is not zero.
    const FramebufferAttachment *attachment = nullptr;

    for (auto dirtyBit : dirtyBits)
    {
        switch (dirtyBit)
        {
            case Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT:
            {
                const FramebufferAttachment *newAttachment = mState.getDepthAttachment();
                BindFramebufferAttachment(functions, GL_DEPTH_ATTACHMENT, newAttachment,
                                          GetFeaturesGL(context));
                if (newAttachment)
                {
                    attachment = newAttachment;
                }
                break;
            }
            case Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT:
            {
                const FramebufferAttachment *newAttachment = mState.getStencilAttachment();
                BindFramebufferAttachment(functions, GL_STENCIL_ATTACHMENT, newAttachment,
                                          GetFeaturesGL(context));
                if (newAttachment)
                {
                    attachment = newAttachment;
                }
                break;
            }
            case Framebuffer::DIRTY_BIT_DRAW_BUFFERS:
            {
                const auto &drawBuffers = mState.getDrawBufferStates();
                functions->drawBuffers(static_cast<GLsizei>(drawBuffers.size()),
                                       drawBuffers.data());
                mAppliedEnabledDrawBuffers = mState.getEnabledDrawBuffers();
                break;
            }
            case Framebuffer::DIRTY_BIT_READ_BUFFER:
                functions->readBuffer(mState.getReadBufferState());
                break;
            case Framebuffer::DIRTY_BIT_DEFAULT_WIDTH:
                functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_WIDTH,
                                                 mState.getDefaultWidth());
                break;
            case Framebuffer::DIRTY_BIT_DEFAULT_HEIGHT:
                functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_HEIGHT,
                                                 mState.getDefaultHeight());
                break;
            case Framebuffer::DIRTY_BIT_DEFAULT_SAMPLES:
                functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_SAMPLES,
                                                 mState.getDefaultSamples());
                break;
            case Framebuffer::DIRTY_BIT_DEFAULT_FIXED_SAMPLE_LOCATIONS:
                functions->framebufferParameteri(
                    GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_FIXED_SAMPLE_LOCATIONS,
                    gl::ConvertToGLBoolean(mState.getDefaultFixedSampleLocations()));
                break;
            case Framebuffer::DIRTY_BIT_DEFAULT_LAYERS:
                functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_DEFAULT_LAYERS_EXT,
                                                 mState.getDefaultLayers());
                break;
            case Framebuffer::DIRTY_BIT_FLIP_Y:
                ASSERT(functions->framebufferParameteri || functions->framebufferParameteriMESA);
                if (functions->framebufferParameteri)
                {
                    functions->framebufferParameteri(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
                                                     gl::ConvertToGLBoolean(mState.getFlipY()));
                }
                else
                {
                    functions->framebufferParameteriMESA(GL_FRAMEBUFFER, GL_FRAMEBUFFER_FLIP_Y_MESA,
                                                         gl::ConvertToGLBoolean(mState.getFlipY()));
                }
                break;
            default:
            {
                static_assert(Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0 == 0, "FB dirty bits");
                if (dirtyBit < Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX)
                {
                    size_t index =
                        static_cast<size_t>(dirtyBit - Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_0);
                    const FramebufferAttachment *newAttachment = mState.getColorAttachment(index);
                    BindFramebufferAttachment(functions,
                                              static_cast<GLenum>(GL_COLOR_ATTACHMENT0 + index),
                                              newAttachment, GetFeaturesGL(context));
                    if (newAttachment)
                    {
                        attachment = newAttachment;
                    }

                    // Hiding an alpha channel is only supported when it's the first attachment
                    // currently. Assert that these emulated textures are not bound to a framebuffer
                    // using MRT.
                    if (index == 0)
                    {
                        mHasEmulatedAlphaAttachment =
                            IsEmulatedAlphaChannelTextureAttachment(attachment);
                    }
                    ASSERT(index == 0 || !IsEmulatedAlphaChannelTextureAttachment(attachment));
                }
                break;
            }
        }
    }

    if (attachment && mState.id() == context->getState().getDrawFramebuffer()->id())
    {
        stateManager->updateMultiviewBaseViewLayerIndexUniform(context->getState().getProgram(),
                                                               getState());
    }

    return angle::Result::Continue;
}

void FramebufferGL::updateDefaultFramebufferID(GLuint framebufferID)
{
    // We only update framebufferID for a default frambuffer, and the framebufferID is created
    // externally. ANGLE doesn't owne it.
    ASSERT(isDefault());
    mFramebufferID = framebufferID;
}

bool FramebufferGL::hasEmulatedAlphaChannelTextureAttachment() const
{
    return mHasEmulatedAlphaAttachment;
}

void FramebufferGL::syncClearState(const gl::Context *context, GLbitfield mask)
{
    StateManagerGL *stateManager      = GetStateManagerGL(context);
    const angle::FeaturesGL &features = GetFeaturesGL(context);

    // Clip origin must not affect scissor box but some drivers flip it for clear ops.
    if (context->getState().isScissorTestEnabled())
    {
        stateManager->setClipControl(ClipOrigin::LowerLeft, ClipDepthMode::NegativeOneToOne);
    }

    if (features.doesSRGBClearsOnLinearFramebufferAttachments.enabled &&
        (mask & GL_COLOR_BUFFER_BIT) != 0 && !isDefault())
    {
        bool hasSRGBAttachment = false;
        for (const auto &attachment : mState.getColorAttachments())
        {
            if (attachment.isAttached() && attachment.getColorEncoding() == GL_SRGB)
            {
                hasSRGBAttachment = true;
                break;
            }
        }

        stateManager->setFramebufferSRGBEnabled(context, hasSRGBAttachment);
    }
    else
    {
        stateManager->setFramebufferSRGBEnabled(context, !isDefault());
    }
}

void FramebufferGL::syncClearBufferState(const gl::Context *context,
                                         GLenum buffer,
                                         GLint drawBuffer)
{
    StateManagerGL *stateManager      = GetStateManagerGL(context);
    const angle::FeaturesGL &features = GetFeaturesGL(context);

    // Clip origin must not affect scissor box but some drivers flip it for clear ops.
    if (context->getState().isScissorTestEnabled())
    {
        stateManager->setClipControl(ClipOrigin::LowerLeft, ClipDepthMode::NegativeOneToOne);
    }

    if (features.doesSRGBClearsOnLinearFramebufferAttachments.enabled && buffer == GL_COLOR &&
        !isDefault())
    {
        // If doing a clear on a color buffer, set SRGB blend enabled only if the color buffer
        // is an SRGB format.
        const auto &drawbufferState  = mState.getDrawBufferStates();
        const auto &colorAttachments = mState.getColorAttachments();

        const FramebufferAttachment *attachment = nullptr;
        if (drawbufferState[drawBuffer] >= GL_COLOR_ATTACHMENT0 &&
            drawbufferState[drawBuffer] < GL_COLOR_ATTACHMENT0 + colorAttachments.size())
        {
            size_t attachmentIdx =
                static_cast<size_t>(drawbufferState[drawBuffer] - GL_COLOR_ATTACHMENT0);
            attachment = &colorAttachments[attachmentIdx];
        }

        if (attachment != nullptr)
        {
            stateManager->setFramebufferSRGBEnabled(context,
                                                    attachment->getColorEncoding() == GL_SRGB);
        }
    }
    else
    {
        stateManager->setFramebufferSRGBEnabled(context, !isDefault());
    }
}

bool FramebufferGL::modifyInvalidateAttachmentsForEmulatedDefaultFBO(
    size_t count,
    const GLenum *attachments,
    std::vector<GLenum> *modifiedAttachments) const
{
    bool needsModification = isDefault() && mFramebufferID != 0;
    if (!needsModification)
    {
        return false;
    }

    modifiedAttachments->resize(count);
    for (size_t i = 0; i < count; i++)
    {
        switch (attachments[i])
        {
            case GL_COLOR:
                (*modifiedAttachments)[i] = GL_COLOR_ATTACHMENT0;
                break;

            case GL_DEPTH:
                (*modifiedAttachments)[i] = GL_DEPTH_ATTACHMENT;
                break;

            case GL_STENCIL:
                (*modifiedAttachments)[i] = GL_STENCIL_ATTACHMENT;
                break;

            default:
                UNREACHABLE();
                break;
        }
    }

    return true;
}

angle::Result FramebufferGL::readPixelsRowByRow(const gl::Context *context,
                                                const gl::Rectangle &area,
                                                GLenum originalReadFormat,
                                                GLenum format,
                                                GLenum type,
                                                const gl::PixelPackState &pack,
                                                GLubyte *pixels) const
{
    ContextGL *contextGL              = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions      = GetFunctionsGL(context);
    StateManagerGL *stateManager      = GetStateManagerGL(context);
    GLubyte *originalReadFormatPixels = pixels;

    const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(format, type);

    GLuint rowBytes = 0;
    ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeRowPitch(type, area.width, pack.alignment,
                                                            pack.rowLength, &rowBytes));
    GLuint skipBytes = 0;
    ANGLE_CHECK_GL_MATH(contextGL,
                        glFormat.computeSkipBytes(type, rowBytes, 0, pack, false, &skipBytes));

    ScopedEXTTextureNorm16ReadbackWorkaround workaround;
    angle::Result result =
        workaround.Initialize(context, area, originalReadFormat, format, type, skipBytes, rowBytes,
                              glFormat.computePixelBytes(type), pixels);
    if (result != angle::Result::Continue)
    {
        return result;
    }

    gl::PixelPackState directPack;
    directPack.alignment = 1;
    ANGLE_TRY(stateManager->setPixelPackState(context, directPack));

    GLubyte *readbackPixels = workaround.Pixels();
    readbackPixels += skipBytes;
    for (GLint y = area.y; y < area.y + area.height; ++y)
    {
        ANGLE_GL_TRY(context,
                     functions->readPixels(area.x, y, area.width, 1, format, type, readbackPixels));
        readbackPixels += rowBytes;
    }

    if (workaround.IsEnabled())
    {
        return RearrangeEXTTextureNorm16Pixels(
            context, area, originalReadFormat, format, type, skipBytes, rowBytes,
            glFormat.computePixelBytes(type), pack, originalReadFormatPixels, workaround.Pixels());
    }

    return angle::Result::Continue;
}

angle::Result FramebufferGL::readPixelsAllAtOnce(const gl::Context *context,
                                                 const gl::Rectangle &area,
                                                 GLenum originalReadFormat,
                                                 GLenum format,
                                                 GLenum type,
                                                 const gl::PixelPackState &pack,
                                                 GLubyte *pixels,
                                                 bool readLastRowSeparately) const
{
    ContextGL *contextGL              = GetImplAs<ContextGL>(context);
    const FunctionsGL *functions      = GetFunctionsGL(context);
    StateManagerGL *stateManager      = GetStateManagerGL(context);
    GLubyte *originalReadFormatPixels = pixels;

    const gl::InternalFormat &glFormat = gl::GetInternalFormatInfo(format, type);

    GLuint rowBytes = 0;
    ANGLE_CHECK_GL_MATH(contextGL, glFormat.computeRowPitch(type, area.width, pack.alignment,
                                                            pack.rowLength, &rowBytes));
    GLuint skipBytes = 0;
    ANGLE_CHECK_GL_MATH(contextGL,
                        glFormat.computeSkipBytes(type, rowBytes, 0, pack, false, &skipBytes));

    ScopedEXTTextureNorm16ReadbackWorkaround workaround;
    angle::Result result =
        workaround.Initialize(context, area, originalReadFormat, format, type, skipBytes, rowBytes,
                              glFormat.computePixelBytes(type), pixels);
    if (result != angle::Result::Continue)
    {
        return result;
    }

    GLint height = area.height - readLastRowSeparately;
    if (height > 0)
    {
        ANGLE_TRY(stateManager->setPixelPackState(context, pack));
        ANGLE_GL_TRY(context, functions->readPixels(area.x, area.y, area.width, height, format,
                                                    type, workaround.Pixels()));
    }

    if (readLastRowSeparately)
    {
        gl::PixelPackState directPack;
        directPack.alignment = 1;
        ANGLE_TRY(stateManager->setPixelPackState(context, directPack));

        GLubyte *readbackPixels = workaround.Pixels();
        readbackPixels += skipBytes + (area.height - 1) * rowBytes;
        ANGLE_GL_TRY(context, functions->readPixels(area.x, area.y + area.height - 1, area.width, 1,
                                                    format, type, readbackPixels));
    }

    if (workaround.IsEnabled())
    {
        return RearrangeEXTTextureNorm16Pixels(
            context, area, originalReadFormat, format, type, skipBytes, rowBytes,
            glFormat.computePixelBytes(type), pack, originalReadFormatPixels, workaround.Pixels());
    }

    return angle::Result::Continue;
}
}  // namespace rx
