Edit

kc3-lang/angle/src/libANGLE/Framebuffer.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2020-04-07 14:01:24
    Hash : 1033d55d
    Message : Pass binding enum to Framebuffer::syncState. Will allow us to determine if we're clearing the read or draw FBO. Then we can stash clears for the draw FBO only and issue them immediately for the read FBO in the Vulkan back-end. Bug: angleproject:4517 Change-Id: Ifc043317d6156a75749b13f9d2c44a17e14ee378 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2139997 Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Cody Northrop <cnorthrop@google.com> Reviewed-by: Tim Van Patten <timvp@google.com> Commit-Queue: Jamie Madill <jmadill@chromium.org>

  • src/libANGLE/Framebuffer.cpp
  • //
    // Copyright 2002 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.
    //
    
    // Framebuffer.cpp: Implements the gl::Framebuffer class. Implements GL framebuffer
    // objects and related functionality. [OpenGL ES 2.0.24] section 4.4 page 105.
    
    #include "libANGLE/Framebuffer.h"
    
    #include "common/Optional.h"
    #include "common/bitset_utils.h"
    #include "common/utilities.h"
    #include "libANGLE/Config.h"
    #include "libANGLE/Context.h"
    #include "libANGLE/Display.h"
    #include "libANGLE/FramebufferAttachment.h"
    #include "libANGLE/Renderbuffer.h"
    #include "libANGLE/Surface.h"
    #include "libANGLE/Texture.h"
    #include "libANGLE/angletypes.h"
    #include "libANGLE/formatutils.h"
    #include "libANGLE/renderer/ContextImpl.h"
    #include "libANGLE/renderer/FramebufferImpl.h"
    #include "libANGLE/renderer/GLImplFactory.h"
    #include "libANGLE/renderer/RenderbufferImpl.h"
    #include "libANGLE/renderer/SurfaceImpl.h"
    
    using namespace angle;
    
    namespace gl
    {
    
    namespace
    {
    
    bool CheckMultiviewStateMatchesForCompleteness(const FramebufferAttachment *firstAttachment,
                                                   const FramebufferAttachment *secondAttachment)
    {
        ASSERT(firstAttachment && secondAttachment);
        ASSERT(firstAttachment->isAttached() && secondAttachment->isAttached());
    
        if (firstAttachment->getNumViews() != secondAttachment->getNumViews())
        {
            return false;
        }
        if (firstAttachment->getBaseViewIndex() != secondAttachment->getBaseViewIndex())
        {
            return false;
        }
        if (firstAttachment->isMultiview() != secondAttachment->isMultiview())
        {
            return false;
        }
        return true;
    }
    
    bool CheckAttachmentCompleteness(const Context *context, const FramebufferAttachment &attachment)
    {
        ASSERT(attachment.isAttached());
    
        const Extents &size = attachment.getSize();
        if (size.width == 0 || size.height == 0)
        {
            return false;
        }
    
        if (!attachment.isRenderable(context))
        {
            return false;
        }
    
        if (attachment.type() == GL_TEXTURE)
        {
            // [EXT_geometry_shader] Section 9.4.1, "Framebuffer Completeness"
            // If <image> is a three-dimensional texture or a two-dimensional array texture and the
            // attachment is not layered, the selected layer is less than the depth or layer count,
            // respectively, of the texture.
            if (!attachment.isLayered())
            {
                if (attachment.layer() >= size.depth)
                {
                    return false;
                }
            }
            // If <image> is a three-dimensional texture or a two-dimensional array texture and the
            // attachment is layered, the depth or layer count, respectively, of the texture is less
            // than or equal to the value of MAX_FRAMEBUFFER_LAYERS_EXT.
            else
            {
                if (size.depth >= context->getCaps().maxFramebufferLayers)
                {
                    return false;
                }
            }
    
            // ES3 specifies that cube map texture attachments must be cube complete.
            // This language is missing from the ES2 spec, but we enforce it here because some
            // desktop OpenGL drivers also enforce this validation.
            // TODO(jmadill): Check if OpenGL ES2 drivers enforce cube completeness.
            const Texture *texture = attachment.getTexture();
            ASSERT(texture);
            if (texture->getType() == TextureType::CubeMap &&
                !texture->getTextureState().isCubeComplete())
            {
                return false;
            }
    
            if (!texture->getImmutableFormat())
            {
                GLuint attachmentMipLevel = static_cast<GLuint>(attachment.mipLevel());
    
                // From the ES 3.0 spec, pg 213:
                // If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is TEXTURE and the value of
                // FRAMEBUFFER_ATTACHMENT_OBJECT_NAME does not name an immutable-format texture,
                // then the value of FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL must be in the
                // range[levelbase, q], where levelbase is the value of TEXTURE_BASE_LEVEL and q is
                // the effective maximum texture level defined in the Mipmapping discussion of
                // section 3.8.10.4.
                if (attachmentMipLevel < texture->getBaseLevel() ||
                    attachmentMipLevel > texture->getMipmapMaxLevel())
                {
                    return false;
                }
    
                // Form the ES 3.0 spec, pg 213/214:
                // If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is TEXTURE and the value of
                // FRAMEBUFFER_ATTACHMENT_OBJECT_NAME does not name an immutable-format texture and
                // the value of FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL is not levelbase, then the
                // texture must be mipmap complete, and if FRAMEBUFFER_ATTACHMENT_OBJECT_NAME names
                // a cubemap texture, the texture must also be cube complete.
                if (attachmentMipLevel != texture->getBaseLevel() && !texture->isMipmapComplete())
                {
                    return false;
                }
            }
        }
    
        return true;
    }
    
    bool CheckAttachmentSampleCounts(const Context *context,
                                     GLsizei currAttachmentSamples,
                                     GLsizei samples,
                                     bool colorAttachment)
    {
        if (currAttachmentSamples != samples)
        {
            if (colorAttachment)
            {
                // APPLE_framebuffer_multisample, which EXT_draw_buffers refers to, requires that
                // all color attachments have the same number of samples for the FBO to be complete.
                return false;
            }
            else
            {
                // CHROMIUM_framebuffer_mixed_samples allows a framebuffer to be considered complete
                // when its depth or stencil samples are a multiple of the number of color samples.
                if (!context->getExtensions().framebufferMixedSamples)
                {
                    return false;
                }
    
                if ((currAttachmentSamples % std::max(samples, 1)) != 0)
                {
                    return false;
                }
            }
        }
        return true;
    }
    
    bool CheckAttachmentSampleCompleteness(const Context *context,
                                           const FramebufferAttachment &attachment,
                                           bool colorAttachment,
                                           Optional<int> *samples,
                                           Optional<bool> *fixedSampleLocations,
                                           Optional<int> *renderToTextureSamples)
    {
        ASSERT(attachment.isAttached());
    
        if (attachment.type() == GL_TEXTURE)
        {
            const Texture *texture = attachment.getTexture();
            ASSERT(texture);
            GLenum internalFormat         = attachment.getFormat().info->internalFormat;
            const TextureCaps &formatCaps = context->getTextureCaps().get(internalFormat);
            if (static_cast<GLuint>(attachment.getSamples()) > formatCaps.getMaxSamples())
            {
                return false;
            }
    
            const ImageIndex &attachmentImageIndex = attachment.getTextureImageIndex();
            bool fixedSampleloc = texture->getAttachmentFixedSampleLocations(attachmentImageIndex);
            if (fixedSampleLocations->valid() && fixedSampleloc != fixedSampleLocations->value())
            {
                return false;
            }
            else
            {
                *fixedSampleLocations = fixedSampleloc;
            }
        }
    
        if (renderToTextureSamples->valid())
        {
            // Only check against RenderToTextureSamples if they actually exist.
            if (renderToTextureSamples->value() !=
                FramebufferAttachment::kDefaultRenderToTextureSamples)
            {
                if (!CheckAttachmentSampleCounts(context, attachment.getRenderToTextureSamples(),
                                                 renderToTextureSamples->value(), colorAttachment))
                {
                    return false;
                }
            }
        }
        else
        {
            *renderToTextureSamples = attachment.getRenderToTextureSamples();
        }
    
        if (samples->valid())
        {
            // RenderToTextureSamples takes precedence if they exist.
            if (renderToTextureSamples->value() ==
                FramebufferAttachment::kDefaultRenderToTextureSamples)
            {
                if (!CheckAttachmentSampleCounts(context, attachment.getSamples(), samples->value(),
                                                 colorAttachment))
                {
                    return false;
                }
            }
        }
        else
        {
            *samples = attachment.getSamples();
        }
    
        return true;
    }
    
    // Needed to index into the attachment arrays/bitsets.
    static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) ==
                      Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX,
                  "Framebuffer Dirty bit mismatch");
    static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) ==
                      Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT,
                  "Framebuffer Dirty bit mismatch");
    static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS + 1) ==
                      Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT,
                  "Framebuffer Dirty bit mismatch");
    
    angle::Result InitAttachment(const Context *context, FramebufferAttachment *attachment)
    {
        ASSERT(attachment->isAttached());
        if (attachment->initState() == InitState::MayNeedInit)
        {
            ANGLE_TRY(attachment->initializeContents(context));
        }
        return angle::Result::Continue;
    }
    
    bool IsColorMaskedOut(const BlendState &blend)
    {
        return (!blend.colorMaskRed && !blend.colorMaskGreen && !blend.colorMaskBlue &&
                !blend.colorMaskAlpha);
    }
    
    bool IsDepthMaskedOut(const DepthStencilState &depthStencil)
    {
        return !depthStencil.depthMask;
    }
    
    bool IsStencilMaskedOut(const DepthStencilState &depthStencil)
    {
        return ((depthStencil.stencilMask & depthStencil.stencilWritemask) == 0);
    }
    
    bool IsClearBufferMaskedOut(const Context *context, GLenum buffer, GLint drawbuffer)
    {
        switch (buffer)
        {
            case GL_COLOR:
                ASSERT(static_cast<size_t>(drawbuffer) <
                       context->getState().getBlendStateArray().size());
                return IsColorMaskedOut(context->getState().getBlendStateArray()[drawbuffer]);
            case GL_DEPTH:
                return IsDepthMaskedOut(context->getState().getDepthStencilState());
            case GL_STENCIL:
                return IsStencilMaskedOut(context->getState().getDepthStencilState());
            case GL_DEPTH_STENCIL:
                return IsDepthMaskedOut(context->getState().getDepthStencilState()) &&
                       IsStencilMaskedOut(context->getState().getDepthStencilState());
            default:
                UNREACHABLE();
                return true;
        }
    }
    
    }  // anonymous namespace
    
    // This constructor is only used for default framebuffers.
    FramebufferState::FramebufferState(ContextID owningContextID)
        : mId(Framebuffer::kDefaultDrawFramebufferHandle),
          mOwningContextID(owningContextID),
          mLabel(),
          mColorAttachments(1),
          mDrawBufferStates(1, GL_BACK),
          mReadBufferState(GL_BACK),
          mDrawBufferTypeMask(),
          mDefaultWidth(0),
          mDefaultHeight(0),
          mDefaultSamples(0),
          mDefaultFixedSampleLocations(GL_FALSE),
          mDefaultLayers(0),
          mWebGLDepthStencilConsistent(true),
          mDepthBufferFeedbackLoop(false),
          mStencilBufferFeedbackLoop(false),
          mHasRenderingFeedbackLoop(false),
          mDefaultFramebufferReadAttachmentInitialized(false)
    {
        ASSERT(mDrawBufferStates.size() > 0);
        mEnabledDrawBuffers.set(0);
    }
    
    FramebufferState::FramebufferState(const Caps &caps, FramebufferID id, ContextID owningContextID)
        : mId(id),
          mOwningContextID(owningContextID),
          mLabel(),
          mColorAttachments(caps.maxColorAttachments),
          mDrawBufferStates(caps.maxDrawBuffers, GL_NONE),
          mReadBufferState(GL_COLOR_ATTACHMENT0_EXT),
          mDrawBufferTypeMask(),
          mDefaultWidth(0),
          mDefaultHeight(0),
          mDefaultSamples(0),
          mDefaultFixedSampleLocations(GL_FALSE),
          mDefaultLayers(0),
          mWebGLDepthStencilConsistent(true),
          mDepthBufferFeedbackLoop(false),
          mStencilBufferFeedbackLoop(false),
          mHasRenderingFeedbackLoop(false),
          mDefaultFramebufferReadAttachmentInitialized(false)
    {
        ASSERT(mId != Framebuffer::kDefaultDrawFramebufferHandle);
        ASSERT(mDrawBufferStates.size() > 0);
        mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT;
    }
    
    FramebufferState::~FramebufferState() {}
    
    const std::string &FramebufferState::getLabel()
    {
        return mLabel;
    }
    
    const FramebufferAttachment *FramebufferState::getAttachment(const Context *context,
                                                                 GLenum attachment) const
    {
        if (attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15)
        {
            return getColorAttachment(attachment - GL_COLOR_ATTACHMENT0);
        }
    
        // WebGL1 allows a developer to query for attachment parameters even when "inconsistant" (i.e.
        // multiple conflicting attachment points) and requires us to return the framebuffer attachment
        // associated with WebGL.
        switch (attachment)
        {
            case GL_COLOR:
            case GL_BACK:
                return getColorAttachment(0);
            case GL_DEPTH:
            case GL_DEPTH_ATTACHMENT:
                if (context->isWebGL1())
                {
                    return getWebGLDepthAttachment();
                }
                else
                {
                    return getDepthAttachment();
                }
            case GL_STENCIL:
            case GL_STENCIL_ATTACHMENT:
                if (context->isWebGL1())
                {
                    return getWebGLStencilAttachment();
                }
                else
                {
                    return getStencilAttachment();
                }
            case GL_DEPTH_STENCIL:
            case GL_DEPTH_STENCIL_ATTACHMENT:
                if (context->isWebGL1())
                {
                    return getWebGLDepthStencilAttachment();
                }
                else
                {
                    return getDepthStencilAttachment();
                }
            default:
                UNREACHABLE();
                return nullptr;
        }
    }
    
    size_t FramebufferState::getReadIndex() const
    {
        ASSERT(mReadBufferState == GL_BACK ||
               (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15));
        size_t readIndex = (mReadBufferState == GL_BACK
                                ? 0
                                : static_cast<size_t>(mReadBufferState - GL_COLOR_ATTACHMENT0));
        ASSERT(readIndex < mColorAttachments.size());
        return readIndex;
    }
    
    const FramebufferAttachment *FramebufferState::getReadAttachment() const
    {
        if (mReadBufferState == GL_NONE)
        {
            return nullptr;
        }
    
        size_t readIndex = getReadIndex();
        const gl::FramebufferAttachment &framebufferAttachment =
            isDefault() ? mDefaultFramebufferReadAttachment : mColorAttachments[readIndex];
    
        return framebufferAttachment.isAttached() ? &framebufferAttachment : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getReadPixelsAttachment(GLenum readFormat) const
    {
        switch (readFormat)
        {
            case GL_DEPTH_COMPONENT:
                return getDepthAttachment();
            case GL_STENCIL_INDEX_OES:
                return getStencilOrDepthStencilAttachment();
            default:
                return getReadAttachment();
        }
    }
    
    const FramebufferAttachment *FramebufferState::getFirstNonNullAttachment() const
    {
        auto *colorAttachment = getFirstColorAttachment();
        if (colorAttachment)
        {
            return colorAttachment;
        }
        return getDepthOrStencilAttachment();
    }
    
    const FramebufferAttachment *FramebufferState::getFirstColorAttachment() const
    {
        for (const FramebufferAttachment &colorAttachment : mColorAttachments)
        {
            if (colorAttachment.isAttached())
            {
                return &colorAttachment;
            }
        }
    
        return nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getDepthOrStencilAttachment() const
    {
        if (mDepthAttachment.isAttached())
        {
            return &mDepthAttachment;
        }
        if (mStencilAttachment.isAttached())
        {
            return &mStencilAttachment;
        }
        return nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getStencilOrDepthStencilAttachment() const
    {
        if (mStencilAttachment.isAttached())
        {
            return &mStencilAttachment;
        }
        return getDepthStencilAttachment();
    }
    
    const FramebufferAttachment *FramebufferState::getColorAttachment(size_t colorAttachment) const
    {
        ASSERT(colorAttachment < mColorAttachments.size());
        return mColorAttachments[colorAttachment].isAttached() ? &mColorAttachments[colorAttachment]
                                                               : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getDepthAttachment() const
    {
        return mDepthAttachment.isAttached() ? &mDepthAttachment : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getWebGLDepthAttachment() const
    {
        return mWebGLDepthAttachment.isAttached() ? &mWebGLDepthAttachment : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getWebGLDepthStencilAttachment() const
    {
        return mWebGLDepthStencilAttachment.isAttached() ? &mWebGLDepthStencilAttachment : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getStencilAttachment() const
    {
        return mStencilAttachment.isAttached() ? &mStencilAttachment : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getWebGLStencilAttachment() const
    {
        return mWebGLStencilAttachment.isAttached() ? &mWebGLStencilAttachment : nullptr;
    }
    
    const FramebufferAttachment *FramebufferState::getDepthStencilAttachment() const
    {
        // A valid depth-stencil attachment has the same resource bound to both the
        // depth and stencil attachment points.
        if (mDepthAttachment.isAttached() && mStencilAttachment.isAttached() &&
            mDepthAttachment == mStencilAttachment)
        {
            return &mDepthAttachment;
        }
    
        return nullptr;
    }
    
    bool FramebufferState::attachmentsHaveSameDimensions() const
    {
        Optional<Extents> attachmentSize;
    
        auto hasMismatchedSize = [&attachmentSize](const FramebufferAttachment &attachment) {
            if (!attachment.isAttached())
            {
                return false;
            }
    
            if (!attachmentSize.valid())
            {
                attachmentSize = attachment.getSize();
                return false;
            }
    
            const auto &prevSize = attachmentSize.value();
            const auto &curSize  = attachment.getSize();
            return (curSize.width != prevSize.width || curSize.height != prevSize.height);
        };
    
        for (const auto &attachment : mColorAttachments)
        {
            if (hasMismatchedSize(attachment))
            {
                return false;
            }
        }
    
        if (hasMismatchedSize(mDepthAttachment))
        {
            return false;
        }
    
        return !hasMismatchedSize(mStencilAttachment);
    }
    
    bool FramebufferState::hasSeparateDepthAndStencilAttachments() const
    {
        // if we have both a depth and stencil buffer, they must refer to the same object
        // since we only support packed_depth_stencil and not separate depth and stencil
        return (getDepthAttachment() != nullptr && getStencilAttachment() != nullptr &&
                getDepthStencilAttachment() == nullptr);
    }
    
    const FramebufferAttachment *FramebufferState::getDrawBuffer(size_t drawBufferIdx) const
    {
        ASSERT(drawBufferIdx < mDrawBufferStates.size());
        if (mDrawBufferStates[drawBufferIdx] != GL_NONE)
        {
            // ES3 spec: "If the GL is bound to a draw framebuffer object, the ith buffer listed in bufs
            // must be COLOR_ATTACHMENTi or NONE"
            ASSERT(mDrawBufferStates[drawBufferIdx] == GL_COLOR_ATTACHMENT0 + drawBufferIdx ||
                   (drawBufferIdx == 0 && mDrawBufferStates[drawBufferIdx] == GL_BACK));
    
            if (mDrawBufferStates[drawBufferIdx] == GL_BACK)
            {
                return getColorAttachment(0);
            }
            else
            {
                return getColorAttachment(mDrawBufferStates[drawBufferIdx] - GL_COLOR_ATTACHMENT0);
            }
        }
        else
        {
            return nullptr;
        }
    }
    
    size_t FramebufferState::getDrawBufferCount() const
    {
        return mDrawBufferStates.size();
    }
    
    bool FramebufferState::colorAttachmentsAreUniqueImages() const
    {
        for (size_t firstAttachmentIdx = 0; firstAttachmentIdx < mColorAttachments.size();
             firstAttachmentIdx++)
        {
            const FramebufferAttachment &firstAttachment = mColorAttachments[firstAttachmentIdx];
            if (!firstAttachment.isAttached())
            {
                continue;
            }
    
            for (size_t secondAttachmentIdx = firstAttachmentIdx + 1;
                 secondAttachmentIdx < mColorAttachments.size(); secondAttachmentIdx++)
            {
                const FramebufferAttachment &secondAttachment = mColorAttachments[secondAttachmentIdx];
                if (!secondAttachment.isAttached())
                {
                    continue;
                }
    
                if (firstAttachment == secondAttachment)
                {
                    return false;
                }
            }
        }
    
        return true;
    }
    
    bool FramebufferState::hasDepth() const
    {
        return (mDepthAttachment.isAttached() && mDepthAttachment.getDepthSize() > 0);
    }
    
    bool FramebufferState::hasStencil() const
    {
        return (mStencilAttachment.isAttached() && mStencilAttachment.getStencilSize() > 0);
    }
    
    bool FramebufferState::isMultiview() const
    {
        const FramebufferAttachment *attachment = getFirstNonNullAttachment();
        if (attachment == nullptr)
        {
            return false;
        }
        return attachment->isMultiview();
    }
    
    int FramebufferState::getBaseViewIndex() const
    {
        const FramebufferAttachment *attachment = getFirstNonNullAttachment();
        if (attachment == nullptr)
        {
            return GL_NONE;
        }
        return attachment->getBaseViewIndex();
    }
    
    Box FramebufferState::getDimensions() const
    {
        Extents extents = getExtents();
        return Box(0, 0, 0, extents.width, extents.height, extents.depth);
    }
    
    Extents FramebufferState::getExtents() const
    {
        ASSERT(attachmentsHaveSameDimensions());
        const FramebufferAttachment *first = getFirstNonNullAttachment();
        if (first)
        {
            return first->getSize();
        }
        return Extents(getDefaultWidth(), getDefaultHeight(), 0);
    }
    
    bool FramebufferState::isDefault() const
    {
        return mId == Framebuffer::kDefaultDrawFramebufferHandle;
    }
    
    bool FramebufferState::updateAttachmentFeedbackLoopAndReturnIfChanged(size_t dirtyBit)
    {
        bool previous;
        bool loop;
    
        switch (dirtyBit)
        {
            case Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT:
                previous                 = mDepthBufferFeedbackLoop;
                loop                     = mDepthAttachment.isBoundAsSamplerOrImage(mOwningContextID);
                mDepthBufferFeedbackLoop = loop;
                break;
    
            case Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT:
                previous = mStencilBufferFeedbackLoop;
                loop     = mStencilAttachment.isBoundAsSamplerOrImage(mOwningContextID);
                mStencilBufferFeedbackLoop = loop;
                break;
    
            default:
            {
                ASSERT(dirtyBit <= Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX);
                previous = mDrawBufferFeedbackLoops.test(dirtyBit);
                loop     = mColorAttachments[dirtyBit].isBoundAsSamplerOrImage(mOwningContextID);
                mDrawBufferFeedbackLoops[dirtyBit] = loop;
                break;
            }
        }
    
        updateHasRenderingFeedbackLoop();
        return previous != loop;
    }
    
    void FramebufferState::updateHasRenderingFeedbackLoop()
    {
        // We don't handle tricky cases where the default FBO is bound as a sampler.
        // We also don't handle tricky cases with EGLImages and mipmap selection.
        // TODO(http://anglebug.com/4500): Tricky rendering feedback loop cases.
        if (isDefault())
        {
            return;
        }
    
        mHasRenderingFeedbackLoop =
            mDrawBufferFeedbackLoops.any() || mDepthBufferFeedbackLoop || mStencilBufferFeedbackLoop;
    }
    
    const FramebufferID Framebuffer::kDefaultDrawFramebufferHandle = {0};
    
    Framebuffer::Framebuffer(const Caps &caps,
                             rx::GLImplFactory *factory,
                             FramebufferID id,
                             ContextID owningContextID)
        : mSerial(factory->generateSerial()),
          mState(caps, id, owningContextID),
          mImpl(factory->createFramebuffer(mState)),
          mCachedStatus(),
          mDirtyDepthAttachmentBinding(this, DIRTY_BIT_DEPTH_ATTACHMENT),
          mDirtyStencilAttachmentBinding(this, DIRTY_BIT_STENCIL_ATTACHMENT)
    {
        ASSERT(mImpl != nullptr);
        ASSERT(mState.mColorAttachments.size() == static_cast<size_t>(caps.maxColorAttachments));
    
        for (uint32_t colorIndex = 0;
             colorIndex < static_cast<uint32_t>(mState.mColorAttachments.size()); ++colorIndex)
        {
            mDirtyColorAttachmentBindings.emplace_back(this, DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex);
        }
        mDirtyBits.set(DIRTY_BIT_READ_BUFFER);
    }
    
    Framebuffer::Framebuffer(const Context *context, egl::Surface *surface, egl::Surface *readSurface)
        : mSerial(context->getImplementation()->generateSerial()),
          mState(context->id()),
          mImpl(surface->getImplementation()->createDefaultFramebuffer(context, mState)),
          mCachedStatus(GL_FRAMEBUFFER_COMPLETE),
          mDirtyDepthAttachmentBinding(this, DIRTY_BIT_DEPTH_ATTACHMENT),
          mDirtyStencilAttachmentBinding(this, DIRTY_BIT_STENCIL_ATTACHMENT)
    {
        ASSERT(mImpl != nullptr);
    
        mDirtyColorAttachmentBindings.emplace_back(this, DIRTY_BIT_COLOR_ATTACHMENT_0);
        setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex(), surface,
                          FramebufferAttachment::kDefaultNumViews,
                          FramebufferAttachment::kDefaultBaseViewIndex, false,
                          FramebufferAttachment::kDefaultRenderToTextureSamples);
    
        setReadSurface(context, readSurface);
    
        if (surface->getConfig()->depthSize > 0)
        {
            setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, ImageIndex(), surface,
                              FramebufferAttachment::kDefaultNumViews,
                              FramebufferAttachment::kDefaultBaseViewIndex, false,
                              FramebufferAttachment::kDefaultRenderToTextureSamples);
        }
    
        if (surface->getConfig()->stencilSize > 0)
        {
            setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL, ImageIndex(), surface,
                              FramebufferAttachment::kDefaultNumViews,
                              FramebufferAttachment::kDefaultBaseViewIndex, false,
                              FramebufferAttachment::kDefaultRenderToTextureSamples);
        }
        SetComponentTypeMask(getDrawbufferWriteType(0), 0, &mState.mDrawBufferTypeMask);
    
        // Ensure the backend has a chance to synchronize its content for a new backbuffer.
        mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0);
    }
    
    Framebuffer::Framebuffer(const Context *context,
                             rx::GLImplFactory *factory,
                             egl::Surface *readSurface)
        : mState(context->id()),
          mImpl(factory->createFramebuffer(mState)),
          mCachedStatus(GL_FRAMEBUFFER_UNDEFINED_OES),
          mDirtyDepthAttachmentBinding(this, DIRTY_BIT_DEPTH_ATTACHMENT),
          mDirtyStencilAttachmentBinding(this, DIRTY_BIT_STENCIL_ATTACHMENT)
    {
        mDirtyColorAttachmentBindings.emplace_back(this, DIRTY_BIT_COLOR_ATTACHMENT_0);
        SetComponentTypeMask(getDrawbufferWriteType(0), 0, &mState.mDrawBufferTypeMask);
    
        setReadSurface(context, readSurface);
    }
    
    Framebuffer::~Framebuffer()
    {
        SafeDelete(mImpl);
    }
    
    void Framebuffer::onDestroy(const Context *context)
    {
        if (isDefault())
        {
            mState.mDefaultFramebufferReadAttachment.detach(context);
            mState.mDefaultFramebufferReadAttachmentInitialized = false;
        }
    
        for (auto &attachment : mState.mColorAttachments)
        {
            attachment.detach(context);
        }
        mState.mDepthAttachment.detach(context);
        mState.mStencilAttachment.detach(context);
        mState.mWebGLDepthAttachment.detach(context);
        mState.mWebGLStencilAttachment.detach(context);
        mState.mWebGLDepthStencilAttachment.detach(context);
    
        mImpl->destroy(context);
    }
    
    void Framebuffer::setReadSurface(const Context *context, egl::Surface *readSurface)
    {
        // updateAttachment() without mState.mResourceNeedsInit.set()
        mState.mDefaultFramebufferReadAttachment.attach(
            context, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex(), readSurface,
            FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex,
            false, FramebufferAttachment::kDefaultRenderToTextureSamples);
        mDirtyBits.set(DIRTY_BIT_READ_BUFFER);
    }
    
    void Framebuffer::setLabel(const Context *context, const std::string &label)
    {
        mState.mLabel = label;
    }
    
    const std::string &Framebuffer::getLabel() const
    {
        return mState.mLabel;
    }
    
    bool Framebuffer::detachTexture(const Context *context, TextureID textureId)
    {
        return detachResourceById(context, GL_TEXTURE, textureId.value);
    }
    
    bool Framebuffer::detachRenderbuffer(const Context *context, RenderbufferID renderbufferId)
    {
        return detachResourceById(context, GL_RENDERBUFFER, renderbufferId.value);
    }
    
    bool Framebuffer::detachResourceById(const Context *context, GLenum resourceType, GLuint resourceId)
    {
        bool found = false;
    
        for (size_t colorIndex = 0; colorIndex < mState.mColorAttachments.size(); ++colorIndex)
        {
            if (detachMatchingAttachment(context, &mState.mColorAttachments[colorIndex], resourceType,
                                         resourceId))
            {
                found = true;
            }
        }
    
        if (context->isWebGL1())
        {
            const std::array<FramebufferAttachment *, 3> attachments = {
                {&mState.mWebGLDepthStencilAttachment, &mState.mWebGLDepthAttachment,
                 &mState.mWebGLStencilAttachment}};
            for (FramebufferAttachment *attachment : attachments)
            {
                if (detachMatchingAttachment(context, attachment, resourceType, resourceId))
                {
                    found = true;
                }
            }
        }
        else
        {
            if (detachMatchingAttachment(context, &mState.mDepthAttachment, resourceType, resourceId))
            {
                found = true;
            }
            if (detachMatchingAttachment(context, &mState.mStencilAttachment, resourceType, resourceId))
            {
                found = true;
            }
        }
    
        return found;
    }
    
    bool Framebuffer::detachMatchingAttachment(const Context *context,
                                               FramebufferAttachment *attachment,
                                               GLenum matchType,
                                               GLuint matchId)
    {
        if (attachment->isAttached() && attachment->type() == matchType && attachment->id() == matchId)
        {
            // We go through resetAttachment to make sure that all the required bookkeeping will be done
            // such as updating enabled draw buffer state.
            resetAttachment(context, attachment->getBinding());
            return true;
        }
    
        return false;
    }
    
    const FramebufferAttachment *Framebuffer::getColorAttachment(size_t colorAttachment) const
    {
        return mState.getColorAttachment(colorAttachment);
    }
    
    const FramebufferAttachment *Framebuffer::getDepthAttachment() const
    {
        return mState.getDepthAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getStencilAttachment() const
    {
        return mState.getStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getDepthStencilAttachment() const
    {
        return mState.getDepthStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getDepthOrStencilAttachment() const
    {
        return mState.getDepthOrStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getStencilOrDepthStencilAttachment() const
    {
        return mState.getStencilOrDepthStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getReadColorAttachment() const
    {
        return mState.getReadAttachment();
    }
    
    GLenum Framebuffer::getReadColorAttachmentType() const
    {
        const FramebufferAttachment *readAttachment = mState.getReadAttachment();
        return (readAttachment != nullptr ? readAttachment->type() : GL_NONE);
    }
    
    const FramebufferAttachment *Framebuffer::getFirstColorAttachment() const
    {
        return mState.getFirstColorAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getFirstNonNullAttachment() const
    {
        return mState.getFirstNonNullAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getAttachment(const Context *context,
                                                            GLenum attachment) const
    {
        return mState.getAttachment(context, attachment);
    }
    
    size_t Framebuffer::getDrawbufferStateCount() const
    {
        return mState.mDrawBufferStates.size();
    }
    
    GLenum Framebuffer::getDrawBufferState(size_t drawBuffer) const
    {
        ASSERT(drawBuffer < mState.mDrawBufferStates.size());
        return mState.mDrawBufferStates[drawBuffer];
    }
    
    const std::vector<GLenum> &Framebuffer::getDrawBufferStates() const
    {
        return mState.getDrawBufferStates();
    }
    
    void Framebuffer::setDrawBuffers(size_t count, const GLenum *buffers)
    {
        auto &drawStates = mState.mDrawBufferStates;
    
        ASSERT(count <= drawStates.size());
        std::copy(buffers, buffers + count, drawStates.begin());
        std::fill(drawStates.begin() + count, drawStates.end(), GL_NONE);
        mDirtyBits.set(DIRTY_BIT_DRAW_BUFFERS);
    
        mState.mEnabledDrawBuffers.reset();
        mState.mDrawBufferTypeMask.reset();
    
        for (size_t index = 0; index < count; ++index)
        {
            SetComponentTypeMask(getDrawbufferWriteType(index), index, &mState.mDrawBufferTypeMask);
    
            if (drawStates[index] != GL_NONE && mState.mColorAttachments[index].isAttached())
            {
                mState.mEnabledDrawBuffers.set(index);
            }
        }
    }
    
    const FramebufferAttachment *Framebuffer::getDrawBuffer(size_t drawBuffer) const
    {
        return mState.getDrawBuffer(drawBuffer);
    }
    
    ComponentType Framebuffer::getDrawbufferWriteType(size_t drawBuffer) const
    {
        const FramebufferAttachment *attachment = mState.getDrawBuffer(drawBuffer);
        if (attachment == nullptr)
        {
            return ComponentType::NoType;
        }
    
        GLenum componentType = attachment->getFormat().info->componentType;
        switch (componentType)
        {
            case GL_INT:
                return ComponentType::Int;
            case GL_UNSIGNED_INT:
                return ComponentType::UnsignedInt;
    
            default:
                return ComponentType::Float;
        }
    }
    
    ComponentTypeMask Framebuffer::getDrawBufferTypeMask() const
    {
        return mState.mDrawBufferTypeMask;
    }
    
    DrawBufferMask Framebuffer::getDrawBufferMask() const
    {
        return mState.mEnabledDrawBuffers;
    }
    
    bool Framebuffer::hasEnabledDrawBuffer() const
    {
        for (size_t drawbufferIdx = 0; drawbufferIdx < mState.mDrawBufferStates.size(); ++drawbufferIdx)
        {
            if (getDrawBuffer(drawbufferIdx) != nullptr)
            {
                return true;
            }
        }
    
        return false;
    }
    
    GLenum Framebuffer::getReadBufferState() const
    {
        return mState.mReadBufferState;
    }
    
    void Framebuffer::setReadBuffer(GLenum buffer)
    {
        ASSERT(buffer == GL_BACK || buffer == GL_NONE ||
               (buffer >= GL_COLOR_ATTACHMENT0 &&
                (buffer - GL_COLOR_ATTACHMENT0) < mState.mColorAttachments.size()));
        mState.mReadBufferState = buffer;
        mDirtyBits.set(DIRTY_BIT_READ_BUFFER);
    }
    
    size_t Framebuffer::getNumColorAttachments() const
    {
        return mState.mColorAttachments.size();
    }
    
    bool Framebuffer::hasDepth() const
    {
        return mState.hasDepth();
    }
    
    bool Framebuffer::hasStencil() const
    {
        return mState.hasStencil();
    }
    
    bool Framebuffer::usingExtendedDrawBuffers() const
    {
        for (size_t drawbufferIdx = 1; drawbufferIdx < mState.mDrawBufferStates.size(); ++drawbufferIdx)
        {
            if (getDrawBuffer(drawbufferIdx) != nullptr)
            {
                return true;
            }
        }
    
        return false;
    }
    
    void Framebuffer::invalidateCompletenessCache()
    {
        if (!isDefault())
        {
            mCachedStatus.reset();
        }
        onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
    }
    
    GLenum Framebuffer::checkStatusImpl(const Context *context) const
    {
        ASSERT(!isDefault());
        ASSERT(hasAnyDirtyBit() || !mCachedStatus.valid());
    
        mCachedStatus = checkStatusWithGLFrontEnd(context);
    
        if (mCachedStatus.value() == GL_FRAMEBUFFER_COMPLETE)
        {
            // We can skip syncState on several back-ends.
            if (mImpl->shouldSyncStateBeforeCheckStatus())
            {
                // This binding is not totally correct. It is ok because the parameter isn't used in
                // the GL back-end and the GL back-end is the only user of syncStateBeforeCheckStatus.
                angle::Result err = syncState(context, GL_FRAMEBUFFER);
                if (err != angle::Result::Continue)
                {
                    return 0;
                }
            }
    
            if (!mImpl->checkStatus(context))
            {
                mCachedStatus = GL_FRAMEBUFFER_UNSUPPORTED;
            }
        }
    
        return mCachedStatus.value();
    }
    
    GLenum Framebuffer::checkStatusWithGLFrontEnd(const Context *context) const
    {
        const State &state = context->getState();
    
        ASSERT(!isDefault());
    
        bool hasAttachments = false;
        Optional<unsigned int> colorbufferSize;
        Optional<int> samples;
        Optional<bool> fixedSampleLocations;
        bool hasRenderbuffer = false;
        Optional<int> renderToTextureSamples;
    
        const FramebufferAttachment *firstAttachment = getFirstNonNullAttachment();
    
        Optional<bool> isLayered;
        Optional<TextureType> colorAttachmentsTextureType;
    
        for (const FramebufferAttachment &colorAttachment : mState.mColorAttachments)
        {
            if (colorAttachment.isAttached())
            {
                if (!CheckAttachmentCompleteness(context, colorAttachment))
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                const InternalFormat &format = *colorAttachment.getFormat().info;
                if (format.depthBits > 0 || format.stencilBits > 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                if (!CheckAttachmentSampleCompleteness(context, colorAttachment, true, &samples,
                                                       &fixedSampleLocations, &renderToTextureSamples))
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
                }
    
                // in GLES 2.0, all color attachments attachments must have the same number of bitplanes
                // in GLES 3.0, there is no such restriction
                if (state.getClientMajorVersion() < 3)
                {
                    if (colorbufferSize.valid())
                    {
                        if (format.pixelBytes != colorbufferSize.value())
                        {
                            return GL_FRAMEBUFFER_UNSUPPORTED;
                        }
                    }
                    else
                    {
                        colorbufferSize = format.pixelBytes;
                    }
                }
    
                if (!CheckMultiviewStateMatchesForCompleteness(firstAttachment, &colorAttachment))
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR;
                }
    
                hasRenderbuffer = hasRenderbuffer || (colorAttachment.type() == GL_RENDERBUFFER);
    
                if (!hasAttachments)
                {
                    isLayered = colorAttachment.isLayered();
                    if (isLayered.value())
                    {
                        colorAttachmentsTextureType = colorAttachment.getTextureImageIndex().getType();
                    }
                    hasAttachments = true;
                }
                else
                {
                    // [EXT_geometry_shader] section 9.4.1, "Framebuffer Completeness"
                    // If any framebuffer attachment is layered, all populated attachments
                    // must be layered. Additionally, all populated color attachments must
                    // be from textures of the same target. {FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT }
                    ASSERT(isLayered.valid());
                    if (isLayered.value() != colorAttachment.isLayered())
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT;
                    }
                    else if (isLayered.value())
                    {
                        ASSERT(colorAttachmentsTextureType.valid());
                        if (colorAttachmentsTextureType.value() !=
                            colorAttachment.getTextureImageIndex().getType())
                        {
                            return GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT;
                        }
                    }
                }
            }
        }
    
        const FramebufferAttachment &depthAttachment = mState.mDepthAttachment;
        if (depthAttachment.isAttached())
        {
            if (!CheckAttachmentCompleteness(context, depthAttachment))
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
    
            const InternalFormat &format = *depthAttachment.getFormat().info;
            if (format.depthBits == 0)
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
    
            if (!CheckAttachmentSampleCompleteness(context, depthAttachment, false, &samples,
                                                   &fixedSampleLocations, &renderToTextureSamples))
            {
                return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
            }
    
            if (!CheckMultiviewStateMatchesForCompleteness(firstAttachment, &depthAttachment))
            {
                return GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR;
            }
    
            hasRenderbuffer = hasRenderbuffer || (depthAttachment.type() == GL_RENDERBUFFER);
    
            if (!hasAttachments)
            {
                isLayered      = depthAttachment.isLayered();
                hasAttachments = true;
            }
            else
            {
                // [EXT_geometry_shader] section 9.4.1, "Framebuffer Completeness"
                // If any framebuffer attachment is layered, all populated attachments
                // must be layered. {FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT }
                ASSERT(isLayered.valid());
                if (isLayered.value() != depthAttachment.isLayered())
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT;
                }
            }
        }
    
        const FramebufferAttachment &stencilAttachment = mState.mStencilAttachment;
        if (stencilAttachment.isAttached())
        {
            if (!CheckAttachmentCompleteness(context, stencilAttachment))
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
    
            const InternalFormat &format = *stencilAttachment.getFormat().info;
            if (format.stencilBits == 0)
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
    
            if (!CheckAttachmentSampleCompleteness(context, stencilAttachment, false, &samples,
                                                   &fixedSampleLocations, &renderToTextureSamples))
            {
                return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
            }
    
            if (!CheckMultiviewStateMatchesForCompleteness(firstAttachment, &stencilAttachment))
            {
                return GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR;
            }
    
            hasRenderbuffer = hasRenderbuffer || (stencilAttachment.type() == GL_RENDERBUFFER);
    
            if (!hasAttachments)
            {
                hasAttachments = true;
            }
            else
            {
                // [EXT_geometry_shader] section 9.4.1, "Framebuffer Completeness"
                // If any framebuffer attachment is layered, all populated attachments
                // must be layered.
                // {FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT }
                ASSERT(isLayered.valid());
                if (isLayered.value() != stencilAttachment.isLayered())
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT;
                }
            }
        }
    
        // Starting from ES 3.0 stencil and depth, if present, should be the same image
        if (state.getClientMajorVersion() >= 3 && depthAttachment.isAttached() &&
            stencilAttachment.isAttached() && stencilAttachment != depthAttachment)
        {
            return GL_FRAMEBUFFER_UNSUPPORTED;
        }
    
        // Special additional validation for WebGL 1 DEPTH/STENCIL/DEPTH_STENCIL.
        if (state.isWebGL1())
        {
            if (!mState.mWebGLDepthStencilConsistent)
            {
                return GL_FRAMEBUFFER_UNSUPPORTED;
            }
    
            if (mState.mWebGLDepthStencilAttachment.isAttached())
            {
                if (mState.mWebGLDepthStencilAttachment.getDepthSize() == 0 ||
                    mState.mWebGLDepthStencilAttachment.getStencilSize() == 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                if (!CheckMultiviewStateMatchesForCompleteness(firstAttachment,
                                                               &mState.mWebGLDepthStencilAttachment))
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR;
                }
            }
            else if (mState.mStencilAttachment.isAttached() &&
                     mState.mStencilAttachment.getDepthSize() > 0)
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
            else if (mState.mDepthAttachment.isAttached() &&
                     mState.mDepthAttachment.getStencilSize() > 0)
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
        }
    
        // ES3.1(section 9.4) requires that if no image is attached to the framebuffer, and either the
        // value of the framebuffer's FRAMEBUFFER_DEFAULT_WIDTH or FRAMEBUFFER_DEFAULT_HEIGHT parameters
        // is zero, the framebuffer is considered incomplete.
        GLint defaultWidth  = mState.getDefaultWidth();
        GLint defaultHeight = mState.getDefaultHeight();
        if (!hasAttachments && (defaultWidth == 0 || defaultHeight == 0))
        {
            return GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
        }
    
        // In ES 2.0 and WebGL, all color attachments must have the same width and height.
        // In ES 3.0, there is no such restriction.
        if ((state.getClientMajorVersion() < 3 || state.getExtensions().webglCompatibility) &&
            !mState.attachmentsHaveSameDimensions())
        {
            return GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
        }
    
        // ES3.1(section 9.4) requires that if the attached images are a mix of renderbuffers and
        // textures, the value of TEXTURE_FIXED_SAMPLE_LOCATIONS must be TRUE for all attached textures.
        if (fixedSampleLocations.valid() && hasRenderbuffer && !fixedSampleLocations.value())
        {
            return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE;
        }
    
        // The WebGL conformance tests implicitly define that all framebuffer
        // attachments must be unique. For example, the same level of a texture can
        // not be attached to two different color attachments.
        if (state.getExtensions().webglCompatibility)
        {
            if (!mState.colorAttachmentsAreUniqueImages())
            {
                return GL_FRAMEBUFFER_UNSUPPORTED;
            }
        }
    
        return GL_FRAMEBUFFER_COMPLETE;
    }
    
    angle::Result Framebuffer::discard(const Context *context, size_t count, const GLenum *attachments)
    {
        // Back-ends might make the contents of the FBO undefined. In WebGL 2.0, invalidate operations
        // can be no-ops, so we should probably do that to ensure consistency.
        // TODO(jmadill): WebGL behaviour, and robust resource init behaviour without WebGL.
    
        return mImpl->discard(context, count, attachments);
    }
    
    angle::Result Framebuffer::invalidate(const Context *context,
                                          size_t count,
                                          const GLenum *attachments)
    {
        // Back-ends might make the contents of the FBO undefined. In WebGL 2.0, invalidate operations
        // can be no-ops, so we should probably do that to ensure consistency.
        // TODO(jmadill): WebGL behaviour, and robust resource init behaviour without WebGL.
    
        return mImpl->invalidate(context, count, attachments);
    }
    
    bool Framebuffer::partialClearNeedsInit(const Context *context,
                                            bool color,
                                            bool depth,
                                            bool stencil)
    {
        const auto &glState = context->getState();
    
        if (!glState.isRobustResourceInitEnabled())
        {
            return false;
        }
    
        // Scissors can affect clearing.
        // TODO(jmadill): Check for complete scissor overlap.
        if (glState.isScissorTestEnabled())
        {
            return true;
        }
    
        // If colors masked, we must clear before we clear. Do a simple check.
        // TODO(jmadill): Filter out unused color channels from the test.
        if (color && glState.anyActiveDrawBufferChannelMasked())
        {
            return true;
        }
    
        const auto &depthStencil = glState.getDepthStencilState();
        if (stencil && (depthStencil.stencilMask != depthStencil.stencilWritemask ||
                        depthStencil.stencilBackMask != depthStencil.stencilBackWritemask))
        {
            return true;
        }
    
        return false;
    }
    
    angle::Result Framebuffer::invalidateSub(const Context *context,
                                             size_t count,
                                             const GLenum *attachments,
                                             const Rectangle &area)
    {
        // Back-ends might make the contents of the FBO undefined. In WebGL 2.0, invalidate operations
        // can be no-ops, so we should probably do that to ensure consistency.
        // TODO(jmadill): Make a invalidate no-op in WebGL 2.0.
    
        return mImpl->invalidateSub(context, count, attachments, area);
    }
    
    angle::Result Framebuffer::clear(const Context *context, GLbitfield mask)
    {
        const auto &glState = context->getState();
        if (glState.isRasterizerDiscardEnabled())
        {
            return angle::Result::Continue;
        }
    
        // Remove clear bits that are ineffective. An effective clear changes at least one fragment. If
        // color/depth/stencil masks make the clear ineffective we skip it altogether.
    
        // If all color channels in all draw buffers are masked, don't attempt to clear color.
        if (context->getState().allActiveDrawBufferChannelsMasked())
        {
            mask &= ~GL_COLOR_BUFFER_BIT;
        }
    
        // If depth write is disabled, don't attempt to clear depth.
        if (!context->getState().getDepthStencilState().depthMask)
        {
            mask &= ~GL_DEPTH_BUFFER_BIT;
        }
    
        // If all stencil bits are masked, don't attempt to clear stencil.
        if (context->getState().getDepthStencilState().stencilWritemask == 0)
        {
            mask &= ~GL_STENCIL_BUFFER_BIT;
        }
    
        if (mask != 0)
        {
            ANGLE_TRY(mImpl->clear(context, mask));
        }
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::clearBufferfv(const Context *context,
                                             GLenum buffer,
                                             GLint drawbuffer,
                                             const GLfloat *values)
    {
        if (context->getState().isRasterizerDiscardEnabled() ||
            IsClearBufferMaskedOut(context, buffer, drawbuffer))
        {
            return angle::Result::Continue;
        }
    
        ANGLE_TRY(mImpl->clearBufferfv(context, buffer, drawbuffer, values));
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::clearBufferuiv(const Context *context,
                                              GLenum buffer,
                                              GLint drawbuffer,
                                              const GLuint *values)
    {
        if (context->getState().isRasterizerDiscardEnabled() ||
            IsClearBufferMaskedOut(context, buffer, drawbuffer))
        {
            return angle::Result::Continue;
        }
    
        ANGLE_TRY(mImpl->clearBufferuiv(context, buffer, drawbuffer, values));
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::clearBufferiv(const Context *context,
                                             GLenum buffer,
                                             GLint drawbuffer,
                                             const GLint *values)
    {
        if (context->getState().isRasterizerDiscardEnabled() ||
            IsClearBufferMaskedOut(context, buffer, drawbuffer))
        {
            return angle::Result::Continue;
        }
    
        ANGLE_TRY(mImpl->clearBufferiv(context, buffer, drawbuffer, values));
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::clearBufferfi(const Context *context,
                                             GLenum buffer,
                                             GLint drawbuffer,
                                             GLfloat depth,
                                             GLint stencil)
    {
        if (context->getState().isRasterizerDiscardEnabled() ||
            IsClearBufferMaskedOut(context, buffer, drawbuffer))
        {
            return angle::Result::Continue;
        }
    
        bool clearDepth   = context->getState().getDepthStencilState().depthMask;
        bool clearStencil = context->getState().getDepthStencilState().stencilWritemask != 0;
    
        if (clearDepth && clearStencil)
        {
            ASSERT(buffer == GL_DEPTH_STENCIL);
            ANGLE_TRY(mImpl->clearBufferfi(context, GL_DEPTH_STENCIL, drawbuffer, depth, stencil));
        }
        else if (clearDepth && !clearStencil)
        {
            ANGLE_TRY(mImpl->clearBufferfv(context, GL_DEPTH, drawbuffer, &depth));
        }
        else if (!clearDepth && clearStencil)
        {
            ANGLE_TRY(mImpl->clearBufferiv(context, GL_STENCIL, drawbuffer, &stencil));
        }
    
        return angle::Result::Continue;
    }
    
    GLenum Framebuffer::getImplementationColorReadFormat(const Context *context)
    {
        const gl::InternalFormat &format = mImpl->getImplementationColorReadFormat(context);
        return format.getReadPixelsFormat(context->getExtensions());
    }
    
    GLenum Framebuffer::getImplementationColorReadType(const Context *context)
    {
        const gl::InternalFormat &format = mImpl->getImplementationColorReadFormat(context);
        return format.getReadPixelsType(context->getClientVersion());
    }
    
    angle::Result Framebuffer::readPixels(const Context *context,
                                          const Rectangle &area,
                                          GLenum format,
                                          GLenum type,
                                          void *pixels)
    {
        ANGLE_TRY(mImpl->readPixels(context, area, format, type, pixels));
    
        Buffer *unpackBuffer = context->getState().getTargetBuffer(BufferBinding::PixelUnpack);
        if (unpackBuffer)
        {
            unpackBuffer->onDataChanged();
        }
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::blit(const Context *context,
                                    const Rectangle &sourceArea,
                                    const Rectangle &destArea,
                                    GLbitfield mask,
                                    GLenum filter)
    {
        GLbitfield blitMask = mask;
    
        // Note that blitting is called against draw framebuffer.
        // See the code in gl::Context::blitFramebuffer.
        if ((mask & GL_COLOR_BUFFER_BIT) && !hasEnabledDrawBuffer())
        {
            blitMask &= ~GL_COLOR_BUFFER_BIT;
        }
    
        if ((mask & GL_STENCIL_BUFFER_BIT) && mState.getStencilAttachment() == nullptr)
        {
            blitMask &= ~GL_STENCIL_BUFFER_BIT;
        }
    
        if ((mask & GL_DEPTH_BUFFER_BIT) && mState.getDepthAttachment() == nullptr)
        {
            blitMask &= ~GL_DEPTH_BUFFER_BIT;
        }
    
        if (!blitMask)
        {
            return angle::Result::Continue;
        }
    
        return mImpl->blit(context, sourceArea, destArea, blitMask, filter);
    }
    
    int Framebuffer::getSamples(const Context *context) const
    {
        return (isComplete(context) ? getCachedSamples(context, AttachmentSampleType::Emulated) : 0);
    }
    
    int Framebuffer::getResourceSamples(const Context *context) const
    {
        return (isComplete(context) ? getCachedSamples(context, AttachmentSampleType::Resource) : 0);
    }
    
    int Framebuffer::getCachedSamples(const Context *context, AttachmentSampleType sampleType) const
    {
        ASSERT(mCachedStatus.valid() && mCachedStatus.value() == GL_FRAMEBUFFER_COMPLETE);
    
        // For a complete framebuffer, all attachments must have the same sample count.
        // In this case return the first nonzero sample size.
        const auto *firstNonNullAttachment = mState.getFirstNonNullAttachment();
        if (firstNonNullAttachment)
        {
            ASSERT(firstNonNullAttachment->isAttached());
            if (sampleType == AttachmentSampleType::Resource)
            {
                return firstNonNullAttachment->getResourceSamples();
            }
            else
            {
                ASSERT(sampleType == AttachmentSampleType::Emulated);
                return firstNonNullAttachment->getSamples();
            }
        }
    
        // No attachments found.
        return 0;
    }
    
    angle::Result Framebuffer::getSamplePosition(const Context *context,
                                                 size_t index,
                                                 GLfloat *xy) const
    {
        ANGLE_TRY(mImpl->getSamplePosition(context, index, xy));
        return angle::Result::Continue;
    }
    
    bool Framebuffer::hasValidDepthStencil() const
    {
        return mState.getDepthStencilAttachment() != nullptr;
    }
    
    void Framebuffer::setAttachment(const Context *context,
                                    GLenum type,
                                    GLenum binding,
                                    const ImageIndex &textureIndex,
                                    FramebufferAttachmentObject *resource)
    {
        setAttachment(context, type, binding, textureIndex, resource,
                      FramebufferAttachment::kDefaultNumViews,
                      FramebufferAttachment::kDefaultBaseViewIndex, false,
                      FramebufferAttachment::kDefaultRenderToTextureSamples);
    }
    
    void Framebuffer::setAttachmentMultisample(const Context *context,
                                               GLenum type,
                                               GLenum binding,
                                               const ImageIndex &textureIndex,
                                               FramebufferAttachmentObject *resource,
                                               GLsizei samples)
    {
        setAttachment(context, type, binding, textureIndex, resource,
                      FramebufferAttachment::kDefaultNumViews,
                      FramebufferAttachment::kDefaultBaseViewIndex, false, samples);
    }
    
    void Framebuffer::setAttachment(const Context *context,
                                    GLenum type,
                                    GLenum binding,
                                    const ImageIndex &textureIndex,
                                    FramebufferAttachmentObject *resource,
                                    GLsizei numViews,
                                    GLuint baseViewIndex,
                                    bool isMultiview,
                                    GLsizei samples)
    {
        // Context may be null in unit tests.
        if (!context || !context->isWebGL1())
        {
            setAttachmentImpl(context, type, binding, textureIndex, resource, numViews, baseViewIndex,
                              isMultiview, samples);
            return;
        }
    
        switch (binding)
        {
            case GL_DEPTH_STENCIL:
            case GL_DEPTH_STENCIL_ATTACHMENT:
                mState.mWebGLDepthStencilAttachment.attach(context, type, binding, textureIndex,
                                                           resource, numViews, baseViewIndex,
                                                           isMultiview, samples);
                break;
            case GL_DEPTH:
            case GL_DEPTH_ATTACHMENT:
                mState.mWebGLDepthAttachment.attach(context, type, binding, textureIndex, resource,
                                                    numViews, baseViewIndex, isMultiview, samples);
                break;
            case GL_STENCIL:
            case GL_STENCIL_ATTACHMENT:
                mState.mWebGLStencilAttachment.attach(context, type, binding, textureIndex, resource,
                                                      numViews, baseViewIndex, isMultiview, samples);
                break;
            default:
                setAttachmentImpl(context, type, binding, textureIndex, resource, numViews,
                                  baseViewIndex, isMultiview, samples);
                return;
        }
    
        commitWebGL1DepthStencilIfConsistent(context, numViews, baseViewIndex, isMultiview, samples);
    }
    
    void Framebuffer::setAttachmentMultiview(const Context *context,
                                             GLenum type,
                                             GLenum binding,
                                             const ImageIndex &textureIndex,
                                             FramebufferAttachmentObject *resource,
                                             GLsizei numViews,
                                             GLint baseViewIndex)
    {
        setAttachment(context, type, binding, textureIndex, resource, numViews, baseViewIndex, true,
                      FramebufferAttachment::kDefaultRenderToTextureSamples);
    }
    
    void Framebuffer::commitWebGL1DepthStencilIfConsistent(const Context *context,
                                                           GLsizei numViews,
                                                           GLuint baseViewIndex,
                                                           bool isMultiview,
                                                           GLsizei samples)
    {
        int count = 0;
    
        std::array<FramebufferAttachment *, 3> attachments = {{&mState.mWebGLDepthStencilAttachment,
                                                               &mState.mWebGLDepthAttachment,
                                                               &mState.mWebGLStencilAttachment}};
        for (FramebufferAttachment *attachment : attachments)
        {
            if (attachment->isAttached())
            {
                count++;
            }
        }
    
        mState.mWebGLDepthStencilConsistent = (count <= 1);
        if (!mState.mWebGLDepthStencilConsistent)
        {
            // Inconsistent.
            return;
        }
    
        auto getImageIndexIfTextureAttachment = [](const FramebufferAttachment &attachment) {
            if (attachment.type() == GL_TEXTURE)
            {
                return attachment.getTextureImageIndex();
            }
            else
            {
                return ImageIndex();
            }
        };
    
        if (mState.mWebGLDepthAttachment.isAttached())
        {
            const auto &depth = mState.mWebGLDepthAttachment;
            setAttachmentImpl(context, depth.type(), GL_DEPTH_ATTACHMENT,
                              getImageIndexIfTextureAttachment(depth), depth.getResource(), numViews,
                              baseViewIndex, isMultiview, samples);
            setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews,
                              baseViewIndex, isMultiview, samples);
        }
        else if (mState.mWebGLStencilAttachment.isAttached())
        {
            const auto &stencil = mState.mWebGLStencilAttachment;
            setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews,
                              baseViewIndex, isMultiview, samples);
            setAttachmentImpl(context, stencil.type(), GL_STENCIL_ATTACHMENT,
                              getImageIndexIfTextureAttachment(stencil), stencil.getResource(),
                              numViews, baseViewIndex, isMultiview, samples);
        }
        else if (mState.mWebGLDepthStencilAttachment.isAttached())
        {
            const auto &depthStencil = mState.mWebGLDepthStencilAttachment;
            setAttachmentImpl(context, depthStencil.type(), GL_DEPTH_ATTACHMENT,
                              getImageIndexIfTextureAttachment(depthStencil),
                              depthStencil.getResource(), numViews, baseViewIndex, isMultiview,
                              samples);
            setAttachmentImpl(context, depthStencil.type(), GL_STENCIL_ATTACHMENT,
                              getImageIndexIfTextureAttachment(depthStencil),
                              depthStencil.getResource(), numViews, baseViewIndex, isMultiview,
                              samples);
        }
        else
        {
            setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews,
                              baseViewIndex, isMultiview, samples);
            setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews,
                              baseViewIndex, isMultiview, samples);
        }
    }
    
    void Framebuffer::setAttachmentImpl(const Context *context,
                                        GLenum type,
                                        GLenum binding,
                                        const ImageIndex &textureIndex,
                                        FramebufferAttachmentObject *resource,
                                        GLsizei numViews,
                                        GLuint baseViewIndex,
                                        bool isMultiview,
                                        GLsizei samples)
    {
        switch (binding)
        {
            case GL_DEPTH_STENCIL:
            case GL_DEPTH_STENCIL_ATTACHMENT:
                updateAttachment(context, &mState.mDepthAttachment, DIRTY_BIT_DEPTH_ATTACHMENT,
                                 &mDirtyDepthAttachmentBinding, type, binding, textureIndex, resource,
                                 numViews, baseViewIndex, isMultiview, samples);
                updateAttachment(context, &mState.mStencilAttachment, DIRTY_BIT_STENCIL_ATTACHMENT,
                                 &mDirtyStencilAttachmentBinding, type, binding, textureIndex, resource,
                                 numViews, baseViewIndex, isMultiview, samples);
                break;
    
            case GL_DEPTH:
            case GL_DEPTH_ATTACHMENT:
                updateAttachment(context, &mState.mDepthAttachment, DIRTY_BIT_DEPTH_ATTACHMENT,
                                 &mDirtyDepthAttachmentBinding, type, binding, textureIndex, resource,
                                 numViews, baseViewIndex, isMultiview, samples);
                break;
    
            case GL_STENCIL:
            case GL_STENCIL_ATTACHMENT:
                updateAttachment(context, &mState.mStencilAttachment, DIRTY_BIT_STENCIL_ATTACHMENT,
                                 &mDirtyStencilAttachmentBinding, type, binding, textureIndex, resource,
                                 numViews, baseViewIndex, isMultiview, samples);
                break;
    
            case GL_BACK:
                updateAttachment(context, &mState.mColorAttachments[0], DIRTY_BIT_COLOR_ATTACHMENT_0,
                                 &mDirtyColorAttachmentBindings[0], type, binding, textureIndex,
                                 resource, numViews, baseViewIndex, isMultiview, samples);
                break;
    
            default:
            {
                size_t colorIndex = binding - GL_COLOR_ATTACHMENT0;
                ASSERT(colorIndex < mState.mColorAttachments.size());
                size_t dirtyBit = DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex;
                updateAttachment(context, &mState.mColorAttachments[colorIndex], dirtyBit,
                                 &mDirtyColorAttachmentBindings[colorIndex], type, binding,
                                 textureIndex, resource, numViews, baseViewIndex, isMultiview, samples);
    
                if (!resource)
                {
                    mFloat32ColorAttachmentBits.reset(colorIndex);
                }
                else
                {
                    updateFloat32ColorAttachmentBits(
                        colorIndex, resource->getAttachmentFormat(binding, textureIndex).info);
                }
    
                bool enabled = (type != GL_NONE && getDrawBufferState(colorIndex) != GL_NONE);
                mState.mEnabledDrawBuffers.set(colorIndex, enabled);
                SetComponentTypeMask(getDrawbufferWriteType(colorIndex), colorIndex,
                                     &mState.mDrawBufferTypeMask);
            }
            break;
        }
    }
    
    void Framebuffer::updateAttachment(const Context *context,
                                       FramebufferAttachment *attachment,
                                       size_t dirtyBit,
                                       angle::ObserverBinding *onDirtyBinding,
                                       GLenum type,
                                       GLenum binding,
                                       const ImageIndex &textureIndex,
                                       FramebufferAttachmentObject *resource,
                                       GLsizei numViews,
                                       GLuint baseViewIndex,
                                       bool isMultiview,
                                       GLsizei samples)
    {
        attachment->attach(context, type, binding, textureIndex, resource, numViews, baseViewIndex,
                           isMultiview, samples);
        mDirtyBits.set(dirtyBit);
        mState.mResourceNeedsInit.set(dirtyBit, attachment->initState() == InitState::MayNeedInit);
        onDirtyBinding->bind(resource);
    
        mState.updateAttachmentFeedbackLoopAndReturnIfChanged(dirtyBit);
        invalidateCompletenessCache();
    }
    
    void Framebuffer::resetAttachment(const Context *context, GLenum binding)
    {
        setAttachment(context, GL_NONE, binding, ImageIndex(), nullptr);
    }
    
    angle::Result Framebuffer::syncState(const Context *context, GLenum framebufferBinding) const
    {
        if (mDirtyBits.any())
        {
            mDirtyBitsGuard = mDirtyBits;
            ANGLE_TRY(mImpl->syncState(context, framebufferBinding, mDirtyBits));
            mDirtyBits.reset();
            mDirtyBitsGuard.reset();
        }
        return angle::Result::Continue;
    }
    
    void Framebuffer::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message)
    {
        if (message != angle::SubjectMessage::SubjectChanged)
        {
            // This can be triggered by SubImage calls for Textures.
            if (message == angle::SubjectMessage::ContentsChanged)
            {
                mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + index);
                onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
                return;
            }
    
            // Triggered by changes to Texture feedback loops.
            if (message == angle::SubjectMessage::BindingChanged)
            {
                if (mState.updateAttachmentFeedbackLoopAndReturnIfChanged(index))
                {
                    mDirtyBits.set(index);
                    onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
                }
                return;
            }
    
            // This can be triggered by the GL back-end TextureGL class.
            ASSERT(message == angle::SubjectMessage::DirtyBitsFlagged);
            return;
        }
    
        ASSERT(!mDirtyBitsGuard.valid() || mDirtyBitsGuard.value().test(index));
        mDirtyBits.set(index);
    
        invalidateCompletenessCache();
    
        FramebufferAttachment *attachment = getAttachmentFromSubjectIndex(index);
    
        // Mark the appropriate init flag.
        mState.mResourceNeedsInit.set(index, attachment->initState() == InitState::MayNeedInit);
    
        // Update mFloat32ColorAttachmentBits Cache
        if (index < DIRTY_BIT_COLOR_ATTACHMENT_MAX)
        {
            ASSERT(index != DIRTY_BIT_DEPTH_ATTACHMENT);
            ASSERT(index != DIRTY_BIT_STENCIL_ATTACHMENT);
            updateFloat32ColorAttachmentBits(index - DIRTY_BIT_COLOR_ATTACHMENT_0,
                                             attachment->getFormat().info);
        }
    }
    
    FramebufferAttachment *Framebuffer::getAttachmentFromSubjectIndex(angle::SubjectIndex index)
    {
        switch (index)
        {
            case DIRTY_BIT_DEPTH_ATTACHMENT:
                return &mState.mDepthAttachment;
            case DIRTY_BIT_STENCIL_ATTACHMENT:
                return &mState.mStencilAttachment;
            default:
                size_t colorIndex = (index - DIRTY_BIT_COLOR_ATTACHMENT_0);
                ASSERT(colorIndex < mState.mColorAttachments.size());
                return &mState.mColorAttachments[colorIndex];
        }
    }
    
    bool Framebuffer::formsCopyingFeedbackLoopWith(TextureID copyTextureID,
                                                   GLint copyTextureLevel,
                                                   GLint copyTextureLayer) const
    {
        if (mState.isDefault())
        {
            // It seems impossible to form a texture copying feedback loop with the default FBO.
            return false;
        }
    
        const FramebufferAttachment *readAttachment = getReadColorAttachment();
        ASSERT(readAttachment);
    
        if (readAttachment->isTextureWithId(copyTextureID))
        {
            const auto &imageIndex = readAttachment->getTextureImageIndex();
            if (imageIndex.getLevelIndex() == copyTextureLevel)
            {
                // Check 3D/Array texture layers.
                return !imageIndex.hasLayer() || copyTextureLayer == ImageIndex::kEntireLevel ||
                       imageIndex.getLayerIndex() == copyTextureLayer;
            }
        }
        return false;
    }
    
    GLint Framebuffer::getDefaultWidth() const
    {
        return mState.getDefaultWidth();
    }
    
    GLint Framebuffer::getDefaultHeight() const
    {
        return mState.getDefaultHeight();
    }
    
    GLint Framebuffer::getDefaultSamples() const
    {
        return mState.getDefaultSamples();
    }
    
    bool Framebuffer::getDefaultFixedSampleLocations() const
    {
        return mState.getDefaultFixedSampleLocations();
    }
    
    GLint Framebuffer::getDefaultLayers() const
    {
        return mState.getDefaultLayers();
    }
    
    void Framebuffer::setDefaultWidth(const Context *context, GLint defaultWidth)
    {
        mState.mDefaultWidth = defaultWidth;
        mDirtyBits.set(DIRTY_BIT_DEFAULT_WIDTH);
        invalidateCompletenessCache();
    }
    
    void Framebuffer::setDefaultHeight(const Context *context, GLint defaultHeight)
    {
        mState.mDefaultHeight = defaultHeight;
        mDirtyBits.set(DIRTY_BIT_DEFAULT_HEIGHT);
        invalidateCompletenessCache();
    }
    
    void Framebuffer::setDefaultSamples(const Context *context, GLint defaultSamples)
    {
        mState.mDefaultSamples = defaultSamples;
        mDirtyBits.set(DIRTY_BIT_DEFAULT_SAMPLES);
        invalidateCompletenessCache();
    }
    
    void Framebuffer::setDefaultFixedSampleLocations(const Context *context,
                                                     bool defaultFixedSampleLocations)
    {
        mState.mDefaultFixedSampleLocations = defaultFixedSampleLocations;
        mDirtyBits.set(DIRTY_BIT_DEFAULT_FIXED_SAMPLE_LOCATIONS);
        invalidateCompletenessCache();
    }
    
    void Framebuffer::setDefaultLayers(GLint defaultLayers)
    {
        mState.mDefaultLayers = defaultLayers;
        mDirtyBits.set(DIRTY_BIT_DEFAULT_LAYERS);
    }
    
    GLsizei Framebuffer::getNumViews() const
    {
        return mState.getNumViews();
    }
    
    GLint Framebuffer::getBaseViewIndex() const
    {
        return mState.getBaseViewIndex();
    }
    
    bool Framebuffer::isMultiview() const
    {
        return mState.isMultiview();
    }
    
    bool Framebuffer::readDisallowedByMultiview() const
    {
        return (mState.isMultiview() && mState.getNumViews() > 1);
    }
    
    angle::Result Framebuffer::ensureClearAttachmentsInitialized(const Context *context,
                                                                 GLbitfield mask)
    {
        const auto &glState = context->getState();
        if (!context->isRobustResourceInitEnabled() || glState.isRasterizerDiscardEnabled())
        {
            return angle::Result::Continue;
        }
    
        const DepthStencilState &depthStencil = glState.getDepthStencilState();
    
        bool color = (mask & GL_COLOR_BUFFER_BIT) != 0 && !glState.allActiveDrawBufferChannelsMasked();
        bool depth = (mask & GL_DEPTH_BUFFER_BIT) != 0 && !IsDepthMaskedOut(depthStencil);
        bool stencil = (mask & GL_STENCIL_BUFFER_BIT) != 0 && !IsStencilMaskedOut(depthStencil);
    
        if (!color && !depth && !stencil)
        {
            return angle::Result::Continue;
        }
    
        if (partialClearNeedsInit(context, color, depth, stencil))
        {
            ANGLE_TRY(ensureDrawAttachmentsInitialized(context));
        }
    
        // If the impl encounters an error during a a full (non-partial) clear, the attachments will
        // still be marked initialized. This simplifies design, allowing this method to be called before
        // the clear.
        markDrawAttachmentsInitialized(color, depth, stencil);
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::ensureClearBufferAttachmentsInitialized(const Context *context,
                                                                       GLenum buffer,
                                                                       GLint drawbuffer)
    {
        if (!context->isRobustResourceInitEnabled() ||
            context->getState().isRasterizerDiscardEnabled() ||
            IsClearBufferMaskedOut(context, buffer, drawbuffer))
        {
            return angle::Result::Continue;
        }
    
        if (partialBufferClearNeedsInit(context, buffer))
        {
            ANGLE_TRY(ensureBufferInitialized(context, buffer, drawbuffer));
        }
    
        // If the impl encounters an error during a a full (non-partial) clear, the attachments will
        // still be marked initialized. This simplifies design, allowing this method to be called before
        // the clear.
        markBufferInitialized(buffer, drawbuffer);
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::ensureDrawAttachmentsInitialized(const Context *context)
    {
        if (!context->isRobustResourceInitEnabled())
        {
            return angle::Result::Continue;
        }
    
        // Note: we don't actually filter by the draw attachment enum. Just init everything.
        for (size_t bit : mState.mResourceNeedsInit)
        {
            switch (bit)
            {
                case DIRTY_BIT_DEPTH_ATTACHMENT:
                    ANGLE_TRY(InitAttachment(context, &mState.mDepthAttachment));
                    break;
                case DIRTY_BIT_STENCIL_ATTACHMENT:
                    ANGLE_TRY(InitAttachment(context, &mState.mStencilAttachment));
                    break;
                default:
                    ANGLE_TRY(InitAttachment(context, &mState.mColorAttachments[bit]));
                    break;
            }
        }
    
        mState.mResourceNeedsInit.reset();
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::ensureReadAttachmentsInitialized(const Context *context)
    {
        ASSERT(context->isRobustResourceInitEnabled());
    
        if (mState.mResourceNeedsInit.none())
        {
            return angle::Result::Continue;
        }
    
        if (mState.mReadBufferState != GL_NONE)
        {
            if (isDefault())
            {
                if (!mState.mDefaultFramebufferReadAttachmentInitialized)
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mDefaultFramebufferReadAttachment));
                    mState.mDefaultFramebufferReadAttachmentInitialized = true;
                }
            }
            else
            {
                size_t readIndex = mState.getReadIndex();
                if (mState.mResourceNeedsInit[readIndex])
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mColorAttachments[readIndex]));
                    mState.mResourceNeedsInit.reset(readIndex);
                }
            }
        }
    
        // Conservatively init depth since it can be read by BlitFramebuffer.
        if (hasDepth())
        {
            if (mState.mResourceNeedsInit[DIRTY_BIT_DEPTH_ATTACHMENT])
            {
                ANGLE_TRY(InitAttachment(context, &mState.mDepthAttachment));
                mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT);
            }
        }
    
        // Conservatively init stencil since it can be read by BlitFramebuffer.
        if (hasStencil())
        {
            if (mState.mResourceNeedsInit[DIRTY_BIT_STENCIL_ATTACHMENT])
            {
                ANGLE_TRY(InitAttachment(context, &mState.mStencilAttachment));
                mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT);
            }
        }
    
        return angle::Result::Continue;
    }
    
    void Framebuffer::markDrawAttachmentsInitialized(bool color, bool depth, bool stencil)
    {
        // Mark attachments as initialized.
        if (color)
        {
            for (auto colorIndex : mState.mEnabledDrawBuffers)
            {
                auto &colorAttachment = mState.mColorAttachments[colorIndex];
                ASSERT(colorAttachment.isAttached());
                colorAttachment.setInitState(InitState::Initialized);
                mState.mResourceNeedsInit.reset(colorIndex);
            }
        }
    
        if (depth && mState.mDepthAttachment.isAttached())
        {
            mState.mDepthAttachment.setInitState(InitState::Initialized);
            mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT);
        }
    
        if (stencil && mState.mStencilAttachment.isAttached())
        {
            mState.mStencilAttachment.setInitState(InitState::Initialized);
            mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT);
        }
    }
    
    void Framebuffer::markBufferInitialized(GLenum bufferType, GLint bufferIndex)
    {
        switch (bufferType)
        {
            case GL_COLOR:
            {
                ASSERT(bufferIndex < static_cast<GLint>(mState.mColorAttachments.size()));
                if (mState.mColorAttachments[bufferIndex].isAttached())
                {
                    mState.mColorAttachments[bufferIndex].setInitState(InitState::Initialized);
                    mState.mResourceNeedsInit.reset(bufferIndex);
                }
                break;
            }
            case GL_DEPTH:
            {
                if (mState.mDepthAttachment.isAttached())
                {
                    mState.mDepthAttachment.setInitState(InitState::Initialized);
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT);
                }
                break;
            }
            case GL_STENCIL:
            {
                if (mState.mStencilAttachment.isAttached())
                {
                    mState.mStencilAttachment.setInitState(InitState::Initialized);
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT);
                }
                break;
            }
            case GL_DEPTH_STENCIL:
            {
                if (mState.mDepthAttachment.isAttached())
                {
                    mState.mDepthAttachment.setInitState(InitState::Initialized);
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT);
                }
                if (mState.mStencilAttachment.isAttached())
                {
                    mState.mStencilAttachment.setInitState(InitState::Initialized);
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT);
                }
                break;
            }
            default:
                UNREACHABLE();
                break;
        }
    }
    
    Box Framebuffer::getDimensions() const
    {
        return mState.getDimensions();
    }
    
    Extents Framebuffer::getExtents() const
    {
        return mState.getExtents();
    }
    
    angle::Result Framebuffer::ensureBufferInitialized(const Context *context,
                                                       GLenum bufferType,
                                                       GLint bufferIndex)
    {
        ASSERT(context->isRobustResourceInitEnabled());
    
        if (mState.mResourceNeedsInit.none())
        {
            return angle::Result::Continue;
        }
    
        switch (bufferType)
        {
            case GL_COLOR:
            {
                ASSERT(bufferIndex < static_cast<GLint>(mState.mColorAttachments.size()));
                if (mState.mResourceNeedsInit[bufferIndex])
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mColorAttachments[bufferIndex]));
                    mState.mResourceNeedsInit.reset(bufferIndex);
                }
                break;
            }
            case GL_DEPTH:
            {
                if (mState.mResourceNeedsInit[DIRTY_BIT_DEPTH_ATTACHMENT])
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mDepthAttachment));
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT);
                }
                break;
            }
            case GL_STENCIL:
            {
                if (mState.mResourceNeedsInit[DIRTY_BIT_STENCIL_ATTACHMENT])
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mStencilAttachment));
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT);
                }
                break;
            }
            case GL_DEPTH_STENCIL:
            {
                if (mState.mResourceNeedsInit[DIRTY_BIT_DEPTH_ATTACHMENT])
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mDepthAttachment));
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT);
                }
                if (mState.mResourceNeedsInit[DIRTY_BIT_STENCIL_ATTACHMENT])
                {
                    ANGLE_TRY(InitAttachment(context, &mState.mStencilAttachment));
                    mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT);
                }
                break;
            }
            default:
                UNREACHABLE();
                break;
        }
    
        return angle::Result::Continue;
    }
    
    bool Framebuffer::partialBufferClearNeedsInit(const Context *context, GLenum bufferType)
    {
        if (!context->isRobustResourceInitEnabled() || mState.mResourceNeedsInit.none())
        {
            return false;
        }
    
        switch (bufferType)
        {
            case GL_COLOR:
                return partialClearNeedsInit(context, true, false, false);
            case GL_DEPTH:
                return partialClearNeedsInit(context, false, true, false);
            case GL_STENCIL:
                return partialClearNeedsInit(context, false, false, true);
            case GL_DEPTH_STENCIL:
                return partialClearNeedsInit(context, false, true, true);
            default:
                UNREACHABLE();
                return false;
        }
    }
    }  // namespace gl