Edit

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

Branch :

  • Show log

    Commit

  • Author : James Darpinian
    Date : 2021-03-17 10:17:31
    Hash : bb7c1442
    Message : Fix immutable texture base level validation Jeff's new rendering feedback loop test (https://github.com/KhronosGroup/WebGL/pull/3221) found a couple of issues with texture base level validation. For immutable textures the base level is clamped to the valid range, so setting an out of range base level does not make an immutable texture sampler incomplete, and we need to use the clamped value when checking for rendering feedback loops. Bug: angleproject:5768 Change-Id: Ie065709efd736f2cf75d4f980e8ac27999f80142 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2767901 Commit-Queue: James Darpinian <jdarpinian@chromium.org> Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Kenneth Russell <kbr@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_DRAW_BUFFERS) ==
                      Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX,
                  "Framebuffer Dirty bit mismatch");
    static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_DRAW_BUFFERS) ==
                      Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT,
                  "Framebuffer Dirty bit mismatch");
    static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_DRAW_BUFFERS + 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 AttachmentOverlapsWithTexture(const FramebufferAttachment &attachment,
                                       const Texture *texture,
                                       const Sampler *sampler)
    {
        if (!attachment.isTextureWithId(texture->id()))
        {
            return false;
        }
    
        const gl::ImageIndex &index      = attachment.getTextureImageIndex();
        GLuint attachmentLevel           = static_cast<GLuint>(index.getLevelIndex());
        GLuint textureEffectiveBaseLevel = texture->getTextureState().getEffectiveBaseLevel();
        GLuint textureMaxLevel           = textureEffectiveBaseLevel;
        if ((sampler && IsMipmapFiltered(sampler->getSamplerState().getMinFilter())) ||
            IsMipmapFiltered(texture->getSamplerState().getMinFilter()))
        {
            textureMaxLevel = texture->getMipmapMaxLevel();
        }
    
        return attachmentLevel >= textureEffectiveBaseLevel && attachmentLevel <= textureMaxLevel;
    }
    }  // anonymous namespace
    
    // This constructor is only used for default framebuffers.
    FramebufferState::FramebufferState(rx::Serial serial)
        : mId(Framebuffer::kDefaultDrawFramebufferHandle),
          mFramebufferSerial(serial),
          mLabel(),
          mColorAttachments(1),
          mColorAttachmentsMask(0),
          mDrawBufferStates(1, GL_BACK),
          mReadBufferState(GL_BACK),
          mDrawBufferTypeMask(),
          mDefaultWidth(0),
          mDefaultHeight(0),
          mDefaultSamples(0),
          mDefaultFixedSampleLocations(GL_FALSE),
          mDefaultLayers(0),
          mWebGLDepthStencilConsistent(true),
          mDefaultFramebufferReadAttachmentInitialized(false),
          mSrgbWriteControlMode(SrgbWriteControlMode::Default)
    {
        ASSERT(mDrawBufferStates.size() > 0);
        mEnabledDrawBuffers.set(0);
    }
    
    FramebufferState::FramebufferState(const Caps &caps, FramebufferID id, rx::Serial serial)
        : mId(id),
          mFramebufferSerial(serial),
          mLabel(),
          mColorAttachments(caps.maxColorAttachments),
          mColorAttachmentsMask(0),
          mDrawBufferStates(caps.maxDrawBuffers, GL_NONE),
          mReadBufferState(GL_COLOR_ATTACHMENT0_EXT),
          mDrawBufferTypeMask(),
          mDefaultWidth(0),
          mDefaultHeight(0),
          mDefaultSamples(0),
          mDefaultFixedSampleLocations(GL_FALSE),
          mDefaultLayers(0),
          mWebGLDepthStencilConsistent(true),
          mDefaultFramebufferReadAttachmentInitialized(false),
          mSrgbWriteControlMode(SrgbWriteControlMode::Default)
    {
        ASSERT(mId != Framebuffer::kDefaultDrawFramebufferHandle);
        ASSERT(mDrawBufferStates.size() > 0);
        mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT;
    }
    
    FramebufferState::~FramebufferState() {}
    
    const std::string &FramebufferState::getLabel() const
    {
        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;
        }
    }
    
    uint32_t FramebufferState::getReadIndex() const
    {
        ASSERT(mReadBufferState == GL_BACK ||
               (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15));
        uint32_t readIndex = mReadBufferState == GL_BACK ? 0 : mReadBufferState - GL_COLOR_ATTACHMENT0;
        ASSERT(readIndex < mColorAttachments.size());
        return readIndex;
    }
    
    const FramebufferAttachment *FramebufferState::getReadAttachment() const
    {
        if (mReadBufferState == GL_NONE)
        {
            return nullptr;
        }
    
        uint32_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::hasExternalTextureAttachment() const
    {
        // External textures can only be bound to color attachment 0
        return (mColorAttachments[0].isAttached() && mColorAttachments[0].isExternalTexture());
    }
    
    bool FramebufferState::hasYUVAttachment() const
    {
        // The only attachments that can be YUV are external textures and surfaces, both are attached at
        // color attachment 0.
        return (mColorAttachments[0].isAttached() && mColorAttachments[0].isYUV());
    }
    
    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;
    }
    
    const FramebufferID Framebuffer::kDefaultDrawFramebufferHandle = {0};
    
    Framebuffer::Framebuffer(const Caps &caps,
                             rx::GLImplFactory *factory,
                             FramebufferID id,
                             egl::ShareGroup *shareGroup)
        : mState(caps, id, shareGroup->generateFramebufferSerial()),
          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);
        }
        if (caps.maxDrawBuffers > 1)
        {
            mDirtyBits.set(DIRTY_BIT_READ_BUFFER);
        }
    }
    
    Framebuffer::Framebuffer(const Context *context, egl::Surface *surface, egl::Surface *readSurface)
        : mState(context->getShareGroup()->generateFramebufferSerial()),
          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);
    
        mState.mSurfaceTextureOffset = surface->getTextureOffset();
    
        // 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->getShareGroup()->generateFramebufferSerial()),
          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.mFramebufferSerial);
            mState.mDefaultFramebufferReadAttachmentInitialized = false;
        }
    
        for (auto &attachment : mState.mColorAttachments)
        {
            attachment.detach(context, mState.mFramebufferSerial);
        }
        mState.mDepthAttachment.detach(context, mState.mFramebufferSerial);
        mState.mStencilAttachment.detach(context, mState.mFramebufferSerial);
        mState.mWebGLDepthAttachment.detach(context, mState.mFramebufferSerial);
        mState.mWebGLStencilAttachment.detach(context, mState.mFramebufferSerial);
        mState.mWebGLDepthStencilAttachment.detach(context, mState.mFramebufferSerial);
    
        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, mState.mFramebufferSerial);
        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::hasExternalTextureAttachment() const
    {
        return mState.hasExternalTextureAttachment();
    }
    
    bool Framebuffer::hasYUVAttachment() const
    {
        return mState.hasYUVAttachment();
    }
    
    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, Command::Other);
                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)
    {
        ASSERT(mask && !context->getState().isRasterizerDiscardEnabled());
    
        return mImpl->clear(context, mask);
    }
    
    angle::Result Framebuffer::clearBufferfv(const Context *context,
                                             GLenum buffer,
                                             GLint drawbuffer,
                                             const GLfloat *values)
    {
        return mImpl->clearBufferfv(context, buffer, drawbuffer, values);
    }
    
    angle::Result Framebuffer::clearBufferuiv(const Context *context,
                                              GLenum buffer,
                                              GLint drawbuffer,
                                              const GLuint *values)
    {
        return mImpl->clearBufferuiv(context, buffer, drawbuffer, values);
    }
    
    angle::Result Framebuffer::clearBufferiv(const Context *context,
                                             GLenum buffer,
                                             GLint drawbuffer,
                                             const GLint *values)
    {
        return mImpl->clearBufferiv(context, buffer, drawbuffer, values);
    }
    
    angle::Result Framebuffer::clearBufferfi(const Context *context,
                                             GLenum buffer,
                                             GLint drawbuffer,
                                             GLfloat depth,
                                             GLint stencil)
    {
        const bool clearDepth =
            getDepthAttachment() != nullptr && context->getState().getDepthStencilState().depthMask;
        const bool clearStencil = getStencilAttachment() != nullptr &&
                                  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,
                                          const PixelPackState &pack,
                                          Buffer *packBuffer,
                                          void *pixels)
    {
        ANGLE_TRY(mImpl->readPixels(context, area, format, type, pack, packBuffer, pixels));
    
        if (packBuffer)
        {
            packBuffer->onDataChanged();
        }
    
        return angle::Result::Continue;
    }
    
    angle::Result Framebuffer::blit(const Context *context,
                                    const Rectangle &sourceArea,
                                    const Rectangle &destArea,
                                    GLbitfield mask,
                                    GLenum filter)
    {
        ASSERT(mask != 0);
    
        ANGLE_TRY(mImpl->blit(context, sourceArea, destArea, mask, filter));
    
        // Mark the contents of the attachments dirty
        if ((mask & GL_COLOR_BUFFER_BIT) != 0)
        {
            for (size_t colorIndex : mState.mEnabledDrawBuffers)
            {
                mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + colorIndex);
            }
        }
        if ((mask & GL_DEPTH_BUFFER_BIT) != 0)
        {
            mDirtyBits.set(DIRTY_BIT_DEPTH_BUFFER_CONTENTS);
        }
        if ((mask & GL_STENCIL_BUFFER_BIT) != 0)
        {
            mDirtyBits.set(DIRTY_BIT_STENCIL_BUFFER_CONTENTS);
        }
        onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
    
        return angle::Result::Continue;
    }
    
    int Framebuffer::getSamples(const Context *context) const
    {
        if (!isComplete(context))
        {
            return 0;
        }
    
        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 FramebufferAttachment *firstNonNullAttachment = mState.getFirstNonNullAttachment();
        ASSERT(firstNonNullAttachment == nullptr || firstNonNullAttachment->isAttached());
    
        return firstNonNullAttachment ? firstNonNullAttachment->getSamples() : 0;
    }
    
    int Framebuffer::getReadBufferResourceSamples(const Context *context) const
    {
        if (!isComplete(context))
        {
            return 0;
        }
    
        ASSERT(mCachedStatus.valid() && mCachedStatus.value() == GL_FRAMEBUFFER_COMPLETE);
    
        const FramebufferAttachment *readAttachment = mState.getReadAttachment();
        ASSERT(readAttachment == nullptr || readAttachment->isAttached());
    
        return readAttachment ? readAttachment->getResourceSamples() : 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;
    }
    
    const gl::Offset &Framebuffer::getSurfaceTextureOffset() const
    {
        return mState.getSurfaceTextureOffset();
    }
    
    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, mState.mFramebufferSerial);
                break;
            case GL_DEPTH:
            case GL_DEPTH_ATTACHMENT:
                mState.mWebGLDepthAttachment.attach(context, type, binding, textureIndex, resource,
                                                    numViews, baseViewIndex, isMultiview, samples,
                                                    mState.mFramebufferSerial);
                break;
            case GL_STENCIL:
            case GL_STENCIL_ATTACHMENT:
                mState.mWebGLStencilAttachment.attach(context, type, binding, textureIndex, resource,
                                                      numViews, baseViewIndex, isMultiview, samples,
                                                      mState.mFramebufferSerial);
                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);
                mState.mColorAttachmentsMask.set(0);
    
                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);
                    mState.mColorAttachmentsMask.reset(colorIndex);
                }
                else
                {
                    updateFloat32ColorAttachmentBits(
                        colorIndex, resource->getAttachmentFormat(binding, textureIndex).info);
                    mState.mColorAttachmentsMask.set(colorIndex);
                }
    
                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, mState.mFramebufferSerial);
        mDirtyBits.set(dirtyBit);
        mState.mResourceNeedsInit.set(dirtyBit, attachment->initState() == InitState::MayNeedInit);
        onDirtyBinding->bind(resource);
    
        invalidateCompletenessCache();
    }
    
    void Framebuffer::resetAttachment(const Context *context, GLenum binding)
    {
        setAttachment(context, GL_NONE, binding, ImageIndex(), nullptr);
    }
    
    void Framebuffer::setWriteControlMode(SrgbWriteControlMode srgbWriteControlMode)
    {
        if (srgbWriteControlMode != mState.getWriteControlMode())
        {
            mState.mSrgbWriteControlMode = srgbWriteControlMode;
            mDirtyBits.set(DIRTY_BIT_FRAMEBUFFER_SRGB_WRITE_CONTROL_MODE);
        }
    }
    
    angle::Result Framebuffer::syncState(const Context *context,
                                         GLenum framebufferBinding,
                                         Command command) const
    {
        if (mDirtyBits.any())
        {
            mDirtyBitsGuard = mDirtyBits;
            ANGLE_TRY(mImpl->syncState(context, framebufferBinding, mDirtyBits, command));
            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;
            }
    
            ASSERT(message != angle::SubjectMessage::BindingChanged);
    
            // This can be triggered by external changes to the default framebuffer.
            if (message == angle::SubjectMessage::SurfaceChanged)
            {
                onStateChange(angle::SubjectMessage::SurfaceChanged);
                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::formsRenderingFeedbackLoopWith(const Context *context) const
    {
        const State &glState                = context->getState();
        const ProgramExecutable *executable = glState.getProgramExecutable();
    
        // In some error cases there may be no bound program or executable.
        if (!executable)
            return false;
    
        const ActiveTextureMask &activeTextures    = executable->getActiveSamplersMask();
        const ActiveTextureTypeArray &textureTypes = executable->getActiveSamplerTypes();
    
        for (size_t textureIndex : activeTextures)
        {
            unsigned int uintIndex = static_cast<unsigned int>(textureIndex);
            Texture *texture       = glState.getSamplerTexture(uintIndex, textureTypes[textureIndex]);
            const Sampler *sampler = glState.getSampler(uintIndex);
            if (texture && texture->isSamplerComplete(context, sampler) &&
                texture->isBoundToFramebuffer(mState.mFramebufferSerial))
            {
                // Check for level overlap.
                for (const FramebufferAttachment &attachment : mState.mColorAttachments)
                {
                    if (AttachmentOverlapsWithTexture(attachment, texture, sampler))
                    {
                        return true;
                    }
                }
    
                if (AttachmentOverlapsWithTexture(mState.mDepthAttachment, texture, sampler))
                {
                    return true;
                }
    
                if (AttachmentOverlapsWithTexture(mState.mStencilAttachment, texture, sampler))
                {
                    return true;
                }
            }
        }
    
        return false;
    }
    
    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 && !depthStencil.isDepthMaskedOut();
        bool stencil = (mask & GL_STENCIL_BUFFER_BIT) != 0 && !depthStencil.isStencilMaskedOut();
    
        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() ||
            context->isClearBufferMaskedOut(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