Edit

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

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2017-01-11 17:36:34
    Hash : a4595b80
    Message : WebGL: Validate simple rendering feedback loops. This adds the most basic form of rendering feedback loop detection: when we're rendering to a texture that's also bound as an input. It doesn't filter by selected mipmap level or 3D texture slice, or do depth attachment validation. It also is missing checks for feedback loops against the default Framebuffer. BUG=angleproject:1685 Change-Id: Idb0ee2bfe1c35611544d132204c0da832c0f1c48 Reviewed-on: https://chromium-review.googlesource.com/425489 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org>

  • src/libANGLE/Framebuffer.cpp
  • //
    // Copyright (c) 2002-2014 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/BitSetIterator.h"
    #include "common/Optional.h"
    #include "common/utilities.h"
    #include "libANGLE/Config.h"
    #include "libANGLE/Context.h"
    #include "libANGLE/FramebufferAttachment.h"
    #include "libANGLE/Renderbuffer.h"
    #include "libANGLE/Surface.h"
    #include "libANGLE/Texture.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
    {
    
    void BindResourceChannel(ChannelBinding *binding, FramebufferAttachmentObject *resource)
    {
        binding->bind(resource ? resource->getDirtyChannel() : nullptr);
    }
    
    }  // anonymous namespace
    
    FramebufferState::FramebufferState()
        : mLabel(),
          mColorAttachments(1),
          mDrawBufferStates(1, GL_NONE),
          mReadBufferState(GL_COLOR_ATTACHMENT0_EXT)
    {
        mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT;
        mEnabledDrawBuffers.set(0);
    }
    
    FramebufferState::FramebufferState(const Caps &caps)
        : mLabel(),
          mColorAttachments(caps.maxColorAttachments),
          mDrawBufferStates(caps.maxDrawBuffers, GL_NONE),
          mReadBufferState(GL_COLOR_ATTACHMENT0_EXT)
    {
        ASSERT(mDrawBufferStates.size() > 0);
        mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT;
    }
    
    FramebufferState::~FramebufferState()
    {
    }
    
    const std::string &FramebufferState::getLabel()
    {
        return mLabel;
    }
    
    const FramebufferAttachment *FramebufferState::getAttachment(GLenum attachment) const
    {
        if (attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15)
        {
            return getColorAttachment(attachment - GL_COLOR_ATTACHMENT0);
        }
    
        switch (attachment)
        {
            case GL_COLOR:
            case GL_BACK:
                return getColorAttachment(0);
            case GL_DEPTH:
            case GL_DEPTH_ATTACHMENT:
                return getDepthAttachment();
            case GL_STENCIL:
            case GL_STENCIL_ATTACHMENT:
                return getStencilAttachment();
            case GL_DEPTH_STENCIL:
            case GL_DEPTH_STENCIL_ATTACHMENT:
                return getDepthStencilAttachment();
            default:
                UNREACHABLE();
                return nullptr;
        }
    }
    
    const FramebufferAttachment *FramebufferState::getReadAttachment() const
    {
        if (mReadBufferState == GL_NONE)
        {
            return nullptr;
        }
        ASSERT(mReadBufferState == GL_BACK || (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15));
        size_t readIndex = (mReadBufferState == GL_BACK ? 0 : static_cast<size_t>(mReadBufferState - GL_COLOR_ATTACHMENT0));
        ASSERT(readIndex < mColorAttachments.size());
        return mColorAttachments[readIndex].isAttached() ? &mColorAttachments[readIndex] : nullptr;
    }
    
    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::getStencilAttachment() const
    {
        return mStencilAttachment.isAttached() ? &mStencilAttachment : 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;
            }
    
            return (attachment.getSize() != attachmentSize.value());
        };
    
        for (const auto &attachment : mColorAttachments)
        {
            if (hasMismatchedSize(attachment))
            {
                return false;
            }
        }
    
        if (hasMismatchedSize(mDepthAttachment))
        {
            return false;
        }
    
        return !hasMismatchedSize(mStencilAttachment);
    }
    
    const gl::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));
            return getAttachment(mDrawBufferStates[drawBufferIdx]);
        }
        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 gl::FramebufferAttachment &firstAttachment = mColorAttachments[firstAttachmentIdx];
            if (!firstAttachment.isAttached())
            {
                continue;
            }
    
            for (size_t secondAttachmentIdx = firstAttachmentIdx + 1;
                 secondAttachmentIdx < mColorAttachments.size(); secondAttachmentIdx++)
            {
                const gl::FramebufferAttachment &secondAttachment =
                    mColorAttachments[secondAttachmentIdx];
                if (!secondAttachment.isAttached())
                {
                    continue;
                }
    
                if (firstAttachment == secondAttachment)
                {
                    return false;
                }
            }
        }
    
        return true;
    }
    
    Framebuffer::Framebuffer(const Caps &caps, rx::GLImplFactory *factory, GLuint id)
        : mState(caps),
          mImpl(factory->createFramebuffer(mState)),
          mId(id),
          mCachedStatus(),
          mDirtyDepthAttachmentBinding(this, DIRTY_BIT_DEPTH_ATTACHMENT),
          mDirtyStencilAttachmentBinding(this, DIRTY_BIT_STENCIL_ATTACHMENT)
    {
        ASSERT(mId != 0);
        ASSERT(mImpl != nullptr);
        ASSERT(mState.mColorAttachments.size() == static_cast<size_t>(caps.maxColorAttachments));
    
        for (size_t colorIndex = 0; colorIndex < mState.mColorAttachments.size(); ++colorIndex)
        {
            mDirtyColorAttachmentBindings.push_back(ChannelBinding(
                this, static_cast<SignalToken>(DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex)));
        }
    }
    
    Framebuffer::Framebuffer(rx::SurfaceImpl *surface)
        : mState(),
          mImpl(surface->createDefaultFramebuffer(mState)),
          mId(0),
          mCachedStatus(GL_FRAMEBUFFER_COMPLETE),
          mDirtyDepthAttachmentBinding(this, DIRTY_BIT_DEPTH_ATTACHMENT),
          mDirtyStencilAttachmentBinding(this, DIRTY_BIT_STENCIL_ATTACHMENT)
    {
        ASSERT(mImpl != nullptr);
        mDirtyColorAttachmentBindings.push_back(
            ChannelBinding(this, static_cast<SignalToken>(DIRTY_BIT_COLOR_ATTACHMENT_0)));
    }
    
    Framebuffer::~Framebuffer()
    {
        SafeDelete(mImpl);
    }
    
    void Framebuffer::setLabel(const std::string &label)
    {
        mState.mLabel = label;
    }
    
    const std::string &Framebuffer::getLabel() const
    {
        return mState.mLabel;
    }
    
    void Framebuffer::detachTexture(GLuint textureId)
    {
        detachResourceById(GL_TEXTURE, textureId);
    }
    
    void Framebuffer::detachRenderbuffer(GLuint renderbufferId)
    {
        detachResourceById(GL_RENDERBUFFER, renderbufferId);
    }
    
    void Framebuffer::detachResourceById(GLenum resourceType, GLuint resourceId)
    {
        for (size_t colorIndex = 0; colorIndex < mState.mColorAttachments.size(); ++colorIndex)
        {
            detachMatchingAttachment(&mState.mColorAttachments[colorIndex], resourceType, resourceId,
                                     DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex);
        }
    
        detachMatchingAttachment(&mState.mDepthAttachment, resourceType, resourceId,
                                 DIRTY_BIT_DEPTH_ATTACHMENT);
        detachMatchingAttachment(&mState.mStencilAttachment, resourceType, resourceId,
                                 DIRTY_BIT_STENCIL_ATTACHMENT);
    }
    
    void Framebuffer::detachMatchingAttachment(FramebufferAttachment *attachment,
                                               GLenum matchType,
                                               GLuint matchId,
                                               size_t dirtyBit)
    {
        if (attachment->isAttached() && attachment->type() == matchType && attachment->id() == matchId)
        {
            attachment->detach();
            mDirtyBits.set(dirtyBit);
        }
    }
    
    const FramebufferAttachment *Framebuffer::getColorbuffer(size_t colorAttachment) const
    {
        return mState.getColorAttachment(colorAttachment);
    }
    
    const FramebufferAttachment *Framebuffer::getDepthbuffer() const
    {
        return mState.getDepthAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getStencilbuffer() const
    {
        return mState.getStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getDepthStencilBuffer() const
    {
        return mState.getDepthStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getDepthOrStencilbuffer() const
    {
        return mState.getDepthOrStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getStencilOrDepthStencilAttachment() const
    {
        return mState.getStencilOrDepthStencilAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getReadColorbuffer() const
    {
        return mState.getReadAttachment();
    }
    
    GLenum Framebuffer::getReadColorbufferType() const
    {
        const FramebufferAttachment *readAttachment = mState.getReadAttachment();
        return (readAttachment != nullptr ? readAttachment->type() : GL_NONE);
    }
    
    const FramebufferAttachment *Framebuffer::getFirstColorbuffer() const
    {
        return mState.getFirstColorAttachment();
    }
    
    const FramebufferAttachment *Framebuffer::getAttachment(GLenum attachment) const
    {
        return mState.getAttachment(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();
        for (size_t index = 0; index < count; ++index)
        {
            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);
    }
    
    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::getNumColorBuffers() const
    {
        return mState.mColorAttachments.size();
    }
    
    bool Framebuffer::hasDepth() const
    {
        return (mState.mDepthAttachment.isAttached() && mState.mDepthAttachment.getDepthSize() > 0);
    }
    
    bool Framebuffer::hasStencil() const
    {
        return (mState.mStencilAttachment.isAttached() &&
                mState.mStencilAttachment.getStencilSize() > 0);
    }
    
    bool Framebuffer::usingExtendedDrawBuffers() const
    {
        for (size_t drawbufferIdx = 1; drawbufferIdx < mState.mDrawBufferStates.size(); ++drawbufferIdx)
        {
            if (getDrawBuffer(drawbufferIdx) != nullptr)
            {
                return true;
            }
        }
    
        return false;
    }
    
    GLenum Framebuffer::checkStatus(const ContextState &state)
    {
        // The default framebuffer *must* always be complete, though it may not be
        // subject to the same rules as application FBOs. ie, it could have 0x0 size.
        if (mId == 0)
        {
            return GL_FRAMEBUFFER_COMPLETE;
        }
    
        if (hasAnyDirtyBit() || !mCachedStatus.valid())
        {
            mCachedStatus = checkStatusImpl(state);
        }
    
        return mCachedStatus.value();
    }
    
    GLenum Framebuffer::checkStatusImpl(const ContextState &state)
    {
        ASSERT(mId != 0);
    
        unsigned int colorbufferSize = 0;
        int samples = -1;
        bool missingAttachment = true;
    
        for (const FramebufferAttachment &colorAttachment : mState.mColorAttachments)
        {
            if (colorAttachment.isAttached())
            {
                const Extents &size = colorAttachment.getSize();
                if (size.width == 0 || size.height == 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                const Format &format          = colorAttachment.getFormat();
                const TextureCaps &formatCaps = state.getTextureCap(format.asSized());
                if (colorAttachment.type() == GL_TEXTURE)
                {
                    if (!formatCaps.renderable)
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                    }
    
                    if (format.info->depthBits > 0 || format.info->stencilBits > 0)
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                    }
    
                    if (colorAttachment.layer() >= size.depth)
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                    }
    
                    // 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 = colorAttachment.getTexture();
                    ASSERT(texture);
                    if (texture->getTarget() == GL_TEXTURE_CUBE_MAP &&
                        !texture->getTextureState().isCubeComplete())
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                    }
                }
                else if (colorAttachment.type() == GL_RENDERBUFFER)
                {
                    if (!formatCaps.renderable || format.info->depthBits > 0 ||
                        format.info->stencilBits > 0)
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                    }
                }
    
                if (!missingAttachment)
                {
                    // 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.
                    if (colorAttachment.getSamples() != samples)
                    {
                        return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_EXT;
                    }
    
                    // 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 (format.info->pixelBytes != colorbufferSize)
                        {
                            return GL_FRAMEBUFFER_UNSUPPORTED;
                        }
                    }
                }
                else
                {
                    samples = colorAttachment.getSamples();
                    colorbufferSize   = format.info->pixelBytes;
                    missingAttachment = false;
                }
            }
        }
    
        const FramebufferAttachment &depthAttachment = mState.mDepthAttachment;
        if (depthAttachment.isAttached())
        {
            const Extents &size = depthAttachment.getSize();
            if (size.width == 0 || size.height == 0)
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
    
            const Format &format          = depthAttachment.getFormat();
            const TextureCaps &formatCaps = state.getTextureCap(format.asSized());
            if (depthAttachment.type() == GL_TEXTURE)
            {
                // depth texture attachments require OES/ANGLE_depth_texture
                if (!state.getExtensions().depthTextures)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                if (!formatCaps.renderable)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                if (format.info->depthBits == 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
            }
            else if (depthAttachment.type() == GL_RENDERBUFFER)
            {
                if (!formatCaps.renderable || format.info->depthBits == 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
            }
    
            if (missingAttachment)
            {
                samples = depthAttachment.getSamples();
                missingAttachment = false;
            }
            else if (samples != depthAttachment.getSamples())
            {
                // 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.
                const bool mixedSamples = state.getExtensions().framebufferMixedSamples;
                if (!mixedSamples)
                    return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_ANGLE;
    
                const int colorSamples = samples ? samples : 1;
                const int depthSamples = depthAttachment.getSamples();
                if ((depthSamples % colorSamples) != 0)
                    return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_ANGLE;
            }
        }
    
        const FramebufferAttachment &stencilAttachment = mState.mStencilAttachment;
        if (stencilAttachment.isAttached())
        {
            const Extents &size = stencilAttachment.getSize();
            if (size.width == 0 || size.height == 0)
            {
                return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
            }
    
            const Format &format          = stencilAttachment.getFormat();
            const TextureCaps &formatCaps = state.getTextureCap(format.asSized());
            if (stencilAttachment.type() == GL_TEXTURE)
            {
                // texture stencil attachments come along as part
                // of OES_packed_depth_stencil + OES/ANGLE_depth_texture
                if (!state.getExtensions().depthTextures)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                if (!formatCaps.renderable)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
    
                if (format.info->stencilBits == 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
            }
            else if (stencilAttachment.type() == GL_RENDERBUFFER)
            {
                if (!formatCaps.renderable || format.info->stencilBits == 0)
                {
                    return GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
                }
            }
    
            if (missingAttachment)
            {
                samples = stencilAttachment.getSamples();
                missingAttachment = false;
            }
            else if (samples != stencilAttachment.getSamples())
            {
                // see the comments in depth attachment check.
                const bool mixedSamples = state.getExtensions().framebufferMixedSamples;
                if (!mixedSamples)
                    return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_ANGLE;
    
                const int colorSamples   = samples ? samples : 1;
                const int stencilSamples = stencilAttachment.getSamples();
                if ((stencilSamples % colorSamples) != 0)
                    return GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE_ANGLE;
            }
    
            // Starting from ES 3.0 stencil and depth, if present, should be the same image
            if (state.getClientMajorVersion() >= 3 && depthAttachment.isAttached() &&
                stencilAttachment != depthAttachment)
            {
                return GL_FRAMEBUFFER_UNSUPPORTED;
            }
        }
    
        // we need to have at least one attachment to be complete
        if (missingAttachment)
        {
            return GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT;
        }
    
        // In ES 2.0, all color attachments must have the same width and height.
        // In ES 3.0, there is no such restriction.
        if (state.getClientMajorVersion() < 3 && !mState.attachmentsHaveSameDimensions())
        {
            return GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS;
        }
    
        syncState();
        if (!mImpl->checkStatus())
        {
            return GL_FRAMEBUFFER_UNSUPPORTED;
        }
    
        return GL_FRAMEBUFFER_COMPLETE;
    }
    
    Error Framebuffer::discard(size_t count, const GLenum *attachments)
    {
        return mImpl->discard(count, attachments);
    }
    
    Error Framebuffer::invalidate(size_t count, const GLenum *attachments)
    {
        return mImpl->invalidate(count, attachments);
    }
    
    Error Framebuffer::invalidateSub(size_t count, const GLenum *attachments, const gl::Rectangle &area)
    {
        return mImpl->invalidateSub(count, attachments, area);
    }
    
    Error Framebuffer::clear(rx::ContextImpl *context, GLbitfield mask)
    {
        if (context->getGLState().isRasterizerDiscardEnabled())
        {
            return gl::NoError();
        }
    
        return mImpl->clear(context, mask);
    }
    
    Error Framebuffer::clearBufferfv(rx::ContextImpl *context,
                                     GLenum buffer,
                                     GLint drawbuffer,
                                     const GLfloat *values)
    {
        if (context->getGLState().isRasterizerDiscardEnabled())
        {
            return gl::NoError();
        }
    
        return mImpl->clearBufferfv(context, buffer, drawbuffer, values);
    }
    
    Error Framebuffer::clearBufferuiv(rx::ContextImpl *context,
                                      GLenum buffer,
                                      GLint drawbuffer,
                                      const GLuint *values)
    {
        if (context->getGLState().isRasterizerDiscardEnabled())
        {
            return gl::NoError();
        }
    
        return mImpl->clearBufferuiv(context, buffer, drawbuffer, values);
    }
    
    Error Framebuffer::clearBufferiv(rx::ContextImpl *context,
                                     GLenum buffer,
                                     GLint drawbuffer,
                                     const GLint *values)
    {
        if (context->getGLState().isRasterizerDiscardEnabled())
        {
            return gl::NoError();
        }
    
        return mImpl->clearBufferiv(context, buffer, drawbuffer, values);
    }
    
    Error Framebuffer::clearBufferfi(rx::ContextImpl *context,
                                     GLenum buffer,
                                     GLint drawbuffer,
                                     GLfloat depth,
                                     GLint stencil)
    {
        if (context->getGLState().isRasterizerDiscardEnabled())
        {
            return gl::NoError();
        }
    
        return mImpl->clearBufferfi(context, buffer, drawbuffer, depth, stencil);
    }
    
    GLenum Framebuffer::getImplementationColorReadFormat() const
    {
        return mImpl->getImplementationColorReadFormat();
    }
    
    GLenum Framebuffer::getImplementationColorReadType() const
    {
        return mImpl->getImplementationColorReadType();
    }
    
    Error Framebuffer::readPixels(rx::ContextImpl *context,
                                  const Rectangle &area,
                                  GLenum format,
                                  GLenum type,
                                  GLvoid *pixels) const
    {
        ANGLE_TRY(mImpl->readPixels(context, area, format, type, pixels));
    
        Buffer *unpackBuffer = context->getGLState().getUnpackState().pixelBuffer.get();
        if (unpackBuffer)
        {
            unpackBuffer->onPixelUnpack();
        }
    
        return NoError();
    }
    
    Error Framebuffer::blit(rx::ContextImpl *context,
                            const Rectangle &sourceArea,
                            const Rectangle &destArea,
                            GLbitfield mask,
                            GLenum filter)
    {
        GLbitfield blitMask = mask;
    
        // Note that blitting is called against draw framebuffer.
        // See the code in gl::Context::blitFramebuffer.
        if ((mask & GL_COLOR_BUFFER_BIT) && !hasEnabledDrawBuffer())
        {
            blitMask &= ~GL_COLOR_BUFFER_BIT;
        }
    
        if ((mask & GL_STENCIL_BUFFER_BIT) && mState.getStencilAttachment() == nullptr)
        {
            blitMask &= ~GL_STENCIL_BUFFER_BIT;
        }
    
        if ((mask & GL_DEPTH_BUFFER_BIT) && mState.getDepthAttachment() == nullptr)
        {
            blitMask &= ~GL_DEPTH_BUFFER_BIT;
        }
    
        if (!blitMask)
        {
            return NoError();
        }
    
        return mImpl->blit(context, sourceArea, destArea, blitMask, filter);
    }
    
    int Framebuffer::getSamples(const ContextState &state)
    {
        if (complete(state))
        {
            // For a complete framebuffer, all attachments must have the same sample count.
            // In this case return the first nonzero sample size.
            const auto *firstColorAttachment = mState.getFirstColorAttachment();
            if (firstColorAttachment)
            {
                ASSERT(firstColorAttachment->isAttached());
                return firstColorAttachment->getSamples();
            }
        }
    
        return 0;
    }
    
    bool Framebuffer::hasValidDepthStencil() const
    {
        return mState.getDepthStencilAttachment() != nullptr;
    }
    
    void Framebuffer::setAttachment(GLenum type,
                                    GLenum binding,
                                    const ImageIndex &textureIndex,
                                    FramebufferAttachmentObject *resource)
    {
        if (binding == GL_DEPTH_STENCIL || binding == GL_DEPTH_STENCIL_ATTACHMENT)
        {
            // ensure this is a legitimate depth+stencil format
            FramebufferAttachmentObject *attachmentObj = resource;
            if (resource)
            {
                FramebufferAttachment::Target target(binding, textureIndex);
                const Format &format = resource->getAttachmentFormat(target);
                if (format.info->depthBits == 0 || format.info->stencilBits == 0)
                {
                    // Attaching nullptr detaches the current attachment.
                    attachmentObj = nullptr;
                }
            }
    
            mState.mDepthAttachment.attach(type, binding, textureIndex, attachmentObj);
            mState.mStencilAttachment.attach(type, binding, textureIndex, attachmentObj);
            mDirtyBits.set(DIRTY_BIT_DEPTH_ATTACHMENT);
            mDirtyBits.set(DIRTY_BIT_STENCIL_ATTACHMENT);
            BindResourceChannel(&mDirtyDepthAttachmentBinding, resource);
            BindResourceChannel(&mDirtyStencilAttachmentBinding, resource);
        }
        else
        {
            switch (binding)
            {
                case GL_DEPTH:
                case GL_DEPTH_ATTACHMENT:
                    mState.mDepthAttachment.attach(type, binding, textureIndex, resource);
                    mDirtyBits.set(DIRTY_BIT_DEPTH_ATTACHMENT);
                    BindResourceChannel(&mDirtyDepthAttachmentBinding, resource);
                    break;
                case GL_STENCIL:
                case GL_STENCIL_ATTACHMENT:
                    mState.mStencilAttachment.attach(type, binding, textureIndex, resource);
                    mDirtyBits.set(DIRTY_BIT_STENCIL_ATTACHMENT);
                    BindResourceChannel(&mDirtyStencilAttachmentBinding, resource);
                    break;
                case GL_BACK:
                    mState.mColorAttachments[0].attach(type, binding, textureIndex, resource);
                    mDirtyBits.set(DIRTY_BIT_COLOR_ATTACHMENT_0);
                    // No need for a resource binding for the default FBO, it's always complete.
                    break;
                default:
                {
                    size_t colorIndex = binding - GL_COLOR_ATTACHMENT0;
                    ASSERT(colorIndex < mState.mColorAttachments.size());
                    mState.mColorAttachments[colorIndex].attach(type, binding, textureIndex, resource);
                    mDirtyBits.set(DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex);
                    BindResourceChannel(&mDirtyColorAttachmentBindings[colorIndex], resource);
    
                    bool enabled = (type != GL_NONE && getDrawBufferState(colorIndex) != GL_NONE);
                    mState.mEnabledDrawBuffers.set(colorIndex, enabled);
                }
                break;
            }
        }
    }
    
    void Framebuffer::resetAttachment(GLenum binding)
    {
        setAttachment(GL_NONE, binding, ImageIndex::MakeInvalid(), nullptr);
    }
    
    void Framebuffer::syncState()
    {
        if (mDirtyBits.any())
        {
            mImpl->syncState(mDirtyBits);
            mDirtyBits.reset();
            mCachedStatus.reset();
        }
    }
    
    void Framebuffer::signal(SignalToken token)
    {
        // TOOD(jmadill): Make this only update individual attachments to do less work.
        mCachedStatus.reset();
    }
    
    bool Framebuffer::complete(const ContextState &state)
    {
        return (checkStatus(state) == GL_FRAMEBUFFER_COMPLETE);
    }
    
    bool Framebuffer::formsRenderingFeedbackLoopWith(const State &state) const
    {
        const Program *program = state.getProgram();
    
        // TODO(jmadill): Default framebuffer feedback loops.
        if (mId == 0)
        {
            return false;
        }
    
        // The bitset will skip inactive draw buffers.
        for (GLuint drawIndex : angle::IterateBitSet(mState.mEnabledDrawBuffers))
        {
            const FramebufferAttachment *attachment = getDrawBuffer(drawIndex);
            if (attachment && attachment->type() == GL_TEXTURE)
            {
                // Validate the feedback loop.
                if (program->samplesFromTexture(state, attachment->id()))
                {
                    return true;
                }
            }
        }
    
        // TODO(jmadill): Validate depth-stencil feedback loop.
        return false;
    }
    
    }  // namespace gl