diff options
Diffstat (limited to '')
-rw-r--r-- | gfx/angle/checkout/src/libANGLE/Framebuffer.cpp | 2383 |
1 files changed, 2383 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/libANGLE/Framebuffer.cpp b/gfx/angle/checkout/src/libANGLE/Framebuffer.cpp new file mode 100644 index 0000000000..64acc8a4cd --- /dev/null +++ b/gfx/angle/checkout/src/libANGLE/Framebuffer.cpp @@ -0,0 +1,2383 @@ +// +// 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/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 (static_cast<GLuint>(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 CheckAttachmentSampleCompleteness(const Context *context, + const FramebufferAttachment &attachment, + bool colorAttachment, + Optional<int> *samples, + Optional<bool> *fixedSampleLocations) +{ + ASSERT(attachment.isAttached()); + + if (attachment.type() == GL_TEXTURE) + { + const Texture *texture = attachment.getTexture(); + ASSERT(texture); + + const ImageIndex &attachmentImageIndex = attachment.getTextureImageIndex(); + bool fixedSampleloc = texture->getAttachmentFixedSampleLocations(attachmentImageIndex); + if (fixedSampleLocations->valid() && fixedSampleloc != fixedSampleLocations->value()) + { + return false; + } + else + { + *fixedSampleLocations = fixedSampleloc; + } + } + + if (samples->valid()) + { + if (attachment.getSamples() != samples->value()) + { + 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 ((attachment.getSamples() % std::max(samples->value(), 1)) != 0) + { + return false; + } + } + } + } + else + { + *samples = attachment.getSamples(); + } + + return true; +} + +// Needed to index into the attachment arrays/bitsets. +static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) == + Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX, + "Framebuffer Dirty bit mismatch"); +static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) == + Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT, + "Framebuffer Dirty bit mismatch"); +static_assert(static_cast<size_t>(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS + 1) == + Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT, + "Framebuffer Dirty bit mismatch"); + +angle::Result InitAttachment(const Context *context, FramebufferAttachment *attachment) +{ + ASSERT(attachment->isAttached()); + if (attachment->initState() == InitState::MayNeedInit) + { + ANGLE_TRY(attachment->initializeContents(context)); + } + return angle::Result::Continue; +} + +bool IsColorMaskedOut(const BlendState &blend) +{ + return (!blend.colorMaskRed && !blend.colorMaskGreen && !blend.colorMaskBlue && + !blend.colorMaskAlpha); +} + +bool IsDepthMaskedOut(const DepthStencilState &depthStencil) +{ + return !depthStencil.depthMask; +} + +bool IsStencilMaskedOut(const DepthStencilState &depthStencil) +{ + return ((depthStencil.stencilMask & depthStencil.stencilWritemask) == 0); +} + +bool IsClearBufferMaskedOut(const Context *context, GLenum buffer) +{ + switch (buffer) + { + case GL_COLOR: + return IsColorMaskedOut(context->getState().getBlendState()); + case GL_DEPTH: + return IsDepthMaskedOut(context->getState().getDepthStencilState()); + case GL_STENCIL: + return IsStencilMaskedOut(context->getState().getDepthStencilState()); + case GL_DEPTH_STENCIL: + return IsDepthMaskedOut(context->getState().getDepthStencilState()) && + IsStencilMaskedOut(context->getState().getDepthStencilState()); + default: + UNREACHABLE(); + return true; + } +} + +} // anonymous namespace + +// This constructor is only used for default framebuffers. +FramebufferState::FramebufferState() + : mId(0), + mLabel(), + mColorAttachments(1), + mDrawBufferStates(1, GL_BACK), + mReadBufferState(GL_BACK), + mDrawBufferTypeMask(), + mDefaultWidth(0), + mDefaultHeight(0), + mDefaultSamples(0), + mDefaultFixedSampleLocations(GL_FALSE), + mDefaultLayers(0), + mWebGLDepthStencilConsistent(true) +{ + ASSERT(mDrawBufferStates.size() > 0); + mEnabledDrawBuffers.set(0); +} + +FramebufferState::FramebufferState(const Caps &caps, GLuint id) + : mId(id), + mLabel(), + mColorAttachments(caps.maxColorAttachments), + mDrawBufferStates(caps.maxDrawBuffers, GL_NONE), + mReadBufferState(GL_COLOR_ATTACHMENT0_EXT), + mDrawBufferTypeMask(), + mDefaultWidth(0), + mDefaultHeight(0), + mDefaultSamples(0), + mDefaultFixedSampleLocations(GL_FALSE), + mDefaultLayers(0), + mWebGLDepthStencilConsistent(true) +{ + ASSERT(mId != 0); + ASSERT(mDrawBufferStates.size() > 0); + mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT; +} + +FramebufferState::~FramebufferState() {} + +const std::string &FramebufferState::getLabel() +{ + return mLabel; +} + +const FramebufferAttachment *FramebufferState::getAttachment(const Context *context, + GLenum attachment) const +{ + if (attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15) + { + return getColorAttachment(attachment - GL_COLOR_ATTACHMENT0); + } + + // WebGL1 allows a developer to query for attachment parameters even when "inconsistant" (i.e. + // multiple conflicting attachment points) and requires us to return the framebuffer attachment + // associated with WebGL. + switch (attachment) + { + case GL_COLOR: + case GL_BACK: + return getColorAttachment(0); + case GL_DEPTH: + case GL_DEPTH_ATTACHMENT: + if (context->isWebGL1()) + { + return getWebGLDepthAttachment(); + } + else + { + return getDepthAttachment(); + } + case GL_STENCIL: + case GL_STENCIL_ATTACHMENT: + if (context->isWebGL1()) + { + return getWebGLStencilAttachment(); + } + else + { + return getStencilAttachment(); + } + case GL_DEPTH_STENCIL: + case GL_DEPTH_STENCIL_ATTACHMENT: + if (context->isWebGL1()) + { + return getWebGLDepthStencilAttachment(); + } + else + { + return getDepthStencilAttachment(); + } + default: + UNREACHABLE(); + return nullptr; + } +} + +size_t FramebufferState::getReadIndex() const +{ + ASSERT(mReadBufferState == GL_BACK || + (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15)); + size_t readIndex = (mReadBufferState == GL_BACK + ? 0 + : static_cast<size_t>(mReadBufferState - GL_COLOR_ATTACHMENT0)); + ASSERT(readIndex < mColorAttachments.size()); + return readIndex; +} + +const FramebufferAttachment *FramebufferState::getReadAttachment() const +{ + if (mReadBufferState == GL_NONE) + { + return nullptr; + } + size_t readIndex = getReadIndex(); + return mColorAttachments[readIndex].isAttached() ? &mColorAttachments[readIndex] : nullptr; +} + +const FramebufferAttachment *FramebufferState::getFirstNonNullAttachment() const +{ + auto *colorAttachment = getFirstColorAttachment(); + if (colorAttachment) + { + return colorAttachment; + } + return getDepthOrStencilAttachment(); +} + +const FramebufferAttachment *FramebufferState::getFirstColorAttachment() const +{ + for (const FramebufferAttachment &colorAttachment : mColorAttachments) + { + if (colorAttachment.isAttached()) + { + return &colorAttachment; + } + } + + return nullptr; +} + +const FramebufferAttachment *FramebufferState::getDepthOrStencilAttachment() const +{ + if (mDepthAttachment.isAttached()) + { + return &mDepthAttachment; + } + if (mStencilAttachment.isAttached()) + { + return &mStencilAttachment; + } + return nullptr; +} + +const FramebufferAttachment *FramebufferState::getStencilOrDepthStencilAttachment() const +{ + if (mStencilAttachment.isAttached()) + { + return &mStencilAttachment; + } + return getDepthStencilAttachment(); +} + +const FramebufferAttachment *FramebufferState::getColorAttachment(size_t colorAttachment) const +{ + ASSERT(colorAttachment < mColorAttachments.size()); + return mColorAttachments[colorAttachment].isAttached() ? &mColorAttachments[colorAttachment] + : nullptr; +} + +const FramebufferAttachment *FramebufferState::getDepthAttachment() const +{ + return mDepthAttachment.isAttached() ? &mDepthAttachment : nullptr; +} + +const FramebufferAttachment *FramebufferState::getWebGLDepthAttachment() const +{ + return mWebGLDepthAttachment.isAttached() ? &mWebGLDepthAttachment : nullptr; +} + +const FramebufferAttachment *FramebufferState::getWebGLDepthStencilAttachment() const +{ + return mWebGLDepthStencilAttachment.isAttached() ? &mWebGLDepthStencilAttachment : nullptr; +} + +const FramebufferAttachment *FramebufferState::getStencilAttachment() const +{ + return mStencilAttachment.isAttached() ? &mStencilAttachment : nullptr; +} + +const FramebufferAttachment *FramebufferState::getWebGLStencilAttachment() const +{ + return mWebGLStencilAttachment.isAttached() ? &mWebGLStencilAttachment : nullptr; +} + +const FramebufferAttachment *FramebufferState::getDepthStencilAttachment() const +{ + // A valid depth-stencil attachment has the same resource bound to both the + // depth and stencil attachment points. + if (mDepthAttachment.isAttached() && mStencilAttachment.isAttached() && + mDepthAttachment == mStencilAttachment) + { + return &mDepthAttachment; + } + + return nullptr; +} + +bool FramebufferState::attachmentsHaveSameDimensions() const +{ + Optional<Extents> attachmentSize; + + auto hasMismatchedSize = [&attachmentSize](const FramebufferAttachment &attachment) { + if (!attachment.isAttached()) + { + return false; + } + + if (!attachmentSize.valid()) + { + attachmentSize = attachment.getSize(); + return false; + } + + const auto &prevSize = attachmentSize.value(); + const auto &curSize = attachment.getSize(); + return (curSize.width != prevSize.width || curSize.height != prevSize.height); + }; + + for (const auto &attachment : mColorAttachments) + { + if (hasMismatchedSize(attachment)) + { + return false; + } + } + + if (hasMismatchedSize(mDepthAttachment)) + { + return false; + } + + return !hasMismatchedSize(mStencilAttachment); +} + +bool FramebufferState::hasSeparateDepthAndStencilAttachments() const +{ + // if we have both a depth and stencil buffer, they must refer to the same object + // since we only support packed_depth_stencil and not separate depth and stencil + return (getDepthAttachment() != nullptr && getStencilAttachment() != nullptr && + getDepthStencilAttachment() == nullptr); +} + +const FramebufferAttachment *FramebufferState::getDrawBuffer(size_t drawBufferIdx) const +{ + ASSERT(drawBufferIdx < mDrawBufferStates.size()); + if (mDrawBufferStates[drawBufferIdx] != GL_NONE) + { + // ES3 spec: "If the GL is bound to a draw framebuffer object, the ith buffer listed in bufs + // must be COLOR_ATTACHMENTi or NONE" + ASSERT(mDrawBufferStates[drawBufferIdx] == GL_COLOR_ATTACHMENT0 + drawBufferIdx || + (drawBufferIdx == 0 && mDrawBufferStates[drawBufferIdx] == GL_BACK)); + + if (mDrawBufferStates[drawBufferIdx] == GL_BACK) + { + return getColorAttachment(0); + } + else + { + return getColorAttachment(mDrawBufferStates[drawBufferIdx] - GL_COLOR_ATTACHMENT0); + } + } + else + { + return nullptr; + } +} + +size_t FramebufferState::getDrawBufferCount() const +{ + return mDrawBufferStates.size(); +} + +bool FramebufferState::colorAttachmentsAreUniqueImages() const +{ + for (size_t firstAttachmentIdx = 0; firstAttachmentIdx < mColorAttachments.size(); + firstAttachmentIdx++) + { + const FramebufferAttachment &firstAttachment = mColorAttachments[firstAttachmentIdx]; + if (!firstAttachment.isAttached()) + { + continue; + } + + for (size_t secondAttachmentIdx = firstAttachmentIdx + 1; + secondAttachmentIdx < mColorAttachments.size(); secondAttachmentIdx++) + { + const FramebufferAttachment &secondAttachment = mColorAttachments[secondAttachmentIdx]; + if (!secondAttachment.isAttached()) + { + continue; + } + + if (firstAttachment == secondAttachment) + { + return false; + } + } + } + + return true; +} + +bool FramebufferState::hasDepth() const +{ + return (mDepthAttachment.isAttached() && mDepthAttachment.getDepthSize() > 0); +} + +bool FramebufferState::hasStencil() const +{ + return (mStencilAttachment.isAttached() && mStencilAttachment.getStencilSize() > 0); +} + +bool FramebufferState::isMultiview() const +{ + const FramebufferAttachment *attachment = getFirstNonNullAttachment(); + if (attachment == nullptr) + { + return false; + } + return attachment->isMultiview(); +} + +int FramebufferState::getBaseViewIndex() const +{ + const FramebufferAttachment *attachment = getFirstNonNullAttachment(); + if (attachment == nullptr) + { + return GL_NONE; + } + return attachment->getBaseViewIndex(); +} + +Box FramebufferState::getDimensions() const +{ + Extents extents = getExtents(); + return Box(0, 0, 0, extents.width, extents.height, extents.depth); +} + +Extents FramebufferState::getExtents() const +{ + ASSERT(attachmentsHaveSameDimensions()); + ASSERT(getFirstNonNullAttachment() != nullptr); + return getFirstNonNullAttachment()->getSize(); +} + +Framebuffer::Framebuffer(const Caps &caps, rx::GLImplFactory *factory, GLuint id) + : mState(caps, id), + 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); + } +} + +Framebuffer::Framebuffer(const Context *context, egl::Surface *surface) + : mState(), + 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); + + if (surface->getConfig()->depthSize > 0) + { + setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, ImageIndex(), surface, + FramebufferAttachment::kDefaultNumViews, + FramebufferAttachment::kDefaultBaseViewIndex, false); + } + + if (surface->getConfig()->stencilSize > 0) + { + setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL, ImageIndex(), surface, + FramebufferAttachment::kDefaultNumViews, + FramebufferAttachment::kDefaultBaseViewIndex, false); + } + SetComponentTypeMask(getDrawbufferWriteType(0), 0, &mState.mDrawBufferTypeMask); + + // Ensure the backend has a chance to synchronize its content for a new backbuffer. + mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0); +} + +Framebuffer::Framebuffer(rx::GLImplFactory *factory) + : mState(), + 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); +} + +Framebuffer::~Framebuffer() +{ + SafeDelete(mImpl); +} + +void Framebuffer::onDestroy(const Context *context) +{ + for (auto &attachment : mState.mColorAttachments) + { + attachment.detach(context); + } + mState.mDepthAttachment.detach(context); + mState.mStencilAttachment.detach(context); + mState.mWebGLDepthAttachment.detach(context); + mState.mWebGLStencilAttachment.detach(context); + mState.mWebGLDepthStencilAttachment.detach(context); + + mImpl->destroy(context); +} + +void Framebuffer::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, GLuint textureId) +{ + return detachResourceById(context, GL_TEXTURE, textureId); +} + +bool Framebuffer::detachRenderbuffer(const Context *context, GLuint renderbufferId) +{ + return detachResourceById(context, GL_RENDERBUFFER, renderbufferId); +} + +bool Framebuffer::detachResourceById(const Context *context, GLenum resourceType, GLuint resourceId) +{ + bool found = false; + + for (size_t colorIndex = 0; colorIndex < mState.mColorAttachments.size(); ++colorIndex) + { + if (detachMatchingAttachment(context, &mState.mColorAttachments[colorIndex], resourceType, + resourceId)) + { + found = true; + } + } + + if (context->isWebGL1()) + { + const std::array<FramebufferAttachment *, 3> attachments = { + {&mState.mWebGLDepthStencilAttachment, &mState.mWebGLDepthAttachment, + &mState.mWebGLStencilAttachment}}; + for (FramebufferAttachment *attachment : attachments) + { + if (detachMatchingAttachment(context, attachment, resourceType, resourceId)) + { + found = true; + } + } + } + else + { + if (detachMatchingAttachment(context, &mState.mDepthAttachment, resourceType, resourceId)) + { + found = true; + } + if (detachMatchingAttachment(context, &mState.mStencilAttachment, resourceType, resourceId)) + { + found = true; + } + } + + return found; +} + +bool Framebuffer::detachMatchingAttachment(const Context *context, + FramebufferAttachment *attachment, + GLenum matchType, + GLuint matchId) +{ + if (attachment->isAttached() && attachment->type() == matchType && attachment->id() == matchId) + { + // We go through resetAttachment to make sure that all the required bookkeeping will be done + // such as updating enabled draw buffer state. + resetAttachment(context, attachment->getBinding()); + return true; + } + + return false; +} + +const FramebufferAttachment *Framebuffer::getColorAttachment(size_t colorAttachment) const +{ + return mState.getColorAttachment(colorAttachment); +} + +const FramebufferAttachment *Framebuffer::getDepthAttachment() const +{ + return mState.getDepthAttachment(); +} + +const FramebufferAttachment *Framebuffer::getStencilAttachment() const +{ + return mState.getStencilAttachment(); +} + +const FramebufferAttachment *Framebuffer::getDepthStencilAttachment() const +{ + return mState.getDepthStencilAttachment(); +} + +const FramebufferAttachment *Framebuffer::getDepthOrStencilAttachment() const +{ + return mState.getDepthOrStencilAttachment(); +} + +const FramebufferAttachment *Framebuffer::getStencilOrDepthStencilAttachment() const +{ + return mState.getStencilOrDepthStencilAttachment(); +} + +const FramebufferAttachment *Framebuffer::getReadColorAttachment() const +{ + return mState.getReadAttachment(); +} + +GLenum Framebuffer::getReadColorAttachmentType() const +{ + const FramebufferAttachment *readAttachment = mState.getReadAttachment(); + return (readAttachment != nullptr ? readAttachment->type() : GL_NONE); +} + +const FramebufferAttachment *Framebuffer::getFirstColorAttachment() const +{ + return mState.getFirstColorAttachment(); +} + +const FramebufferAttachment *Framebuffer::getFirstNonNullAttachment() const +{ + return mState.getFirstNonNullAttachment(); +} + +const FramebufferAttachment *Framebuffer::getAttachment(const Context *context, + GLenum attachment) const +{ + return mState.getAttachment(context, attachment); +} + +size_t Framebuffer::getDrawbufferStateCount() const +{ + return mState.mDrawBufferStates.size(); +} + +GLenum Framebuffer::getDrawBufferState(size_t drawBuffer) const +{ + ASSERT(drawBuffer < mState.mDrawBufferStates.size()); + return mState.mDrawBufferStates[drawBuffer]; +} + +const std::vector<GLenum> &Framebuffer::getDrawBufferStates() const +{ + return mState.getDrawBufferStates(); +} + +void Framebuffer::setDrawBuffers(size_t count, const GLenum *buffers) +{ + auto &drawStates = mState.mDrawBufferStates; + + ASSERT(count <= drawStates.size()); + std::copy(buffers, buffers + count, drawStates.begin()); + std::fill(drawStates.begin() + count, drawStates.end(), GL_NONE); + mDirtyBits.set(DIRTY_BIT_DRAW_BUFFERS); + + mState.mEnabledDrawBuffers.reset(); + mState.mDrawBufferTypeMask.reset(); + + for (size_t index = 0; index < count; ++index) + { + SetComponentTypeMask(getDrawbufferWriteType(index), index, &mState.mDrawBufferTypeMask); + + if (drawStates[index] != GL_NONE && mState.mColorAttachments[index].isAttached()) + { + mState.mEnabledDrawBuffers.set(index); + } + } +} + +const FramebufferAttachment *Framebuffer::getDrawBuffer(size_t drawBuffer) const +{ + return mState.getDrawBuffer(drawBuffer); +} + +ComponentType Framebuffer::getDrawbufferWriteType(size_t drawBuffer) const +{ + const FramebufferAttachment *attachment = mState.getDrawBuffer(drawBuffer); + if (attachment == nullptr) + { + return ComponentType::NoType; + } + + GLenum componentType = attachment->getFormat().info->componentType; + switch (componentType) + { + case GL_INT: + return ComponentType::Int; + case GL_UNSIGNED_INT: + return ComponentType::UnsignedInt; + + default: + return ComponentType::Float; + } +} + +ComponentTypeMask Framebuffer::getDrawBufferTypeMask() const +{ + return mState.mDrawBufferTypeMask; +} + +DrawBufferMask Framebuffer::getDrawBufferMask() const +{ + return mState.mEnabledDrawBuffers; +} + +bool Framebuffer::hasEnabledDrawBuffer() const +{ + for (size_t drawbufferIdx = 0; drawbufferIdx < mState.mDrawBufferStates.size(); ++drawbufferIdx) + { + if (getDrawBuffer(drawbufferIdx) != nullptr) + { + return true; + } + } + + return false; +} + +GLenum Framebuffer::getReadBufferState() const +{ + return mState.mReadBufferState; +} + +void Framebuffer::setReadBuffer(GLenum buffer) +{ + ASSERT(buffer == GL_BACK || buffer == GL_NONE || + (buffer >= GL_COLOR_ATTACHMENT0 && + (buffer - GL_COLOR_ATTACHMENT0) < mState.mColorAttachments.size())); + mState.mReadBufferState = buffer; + mDirtyBits.set(DIRTY_BIT_READ_BUFFER); +} + +size_t Framebuffer::getNumColorAttachments() const +{ + return mState.mColorAttachments.size(); +} + +bool Framebuffer::hasDepth() const +{ + return mState.hasDepth(); +} + +bool Framebuffer::hasStencil() const +{ + return mState.hasStencil(); +} + +bool Framebuffer::usingExtendedDrawBuffers() const +{ + for (size_t drawbufferIdx = 1; drawbufferIdx < mState.mDrawBufferStates.size(); ++drawbufferIdx) + { + if (getDrawBuffer(drawbufferIdx) != nullptr) + { + return true; + } + } + + return false; +} + +void Framebuffer::invalidateCompletenessCache() +{ + if (mState.mId != 0) + { + mCachedStatus.reset(); + } + onStateChange(angle::SubjectMessage::DirtyBitsFlagged); +} + +GLenum Framebuffer::checkStatusImpl(const Context *context) +{ + 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()) + { + angle::Result err = syncState(context); + 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 State &state = context->getState(); + + ASSERT(mState.mId != 0); + + bool hasAttachments = false; + Optional<unsigned int> colorbufferSize; + Optional<int> samples; + Optional<bool> fixedSampleLocations; + bool hasRenderbuffer = false; + + 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)) + { + 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)) + { + 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)) + { + 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) + { + const auto &blend = glState.getBlendState(); + if (!(blend.colorMaskRed && blend.colorMaskGreen && blend.colorMaskBlue && + blend.colorMaskAlpha)) + { + return true; + } + } + + const auto &depthStencil = glState.getDepthStencilState(); + if (stencil && (depthStencil.stencilMask != depthStencil.stencilWritemask || + depthStencil.stencilBackMask != depthStencil.stencilBackWritemask)) + { + return true; + } + + return false; +} + +angle::Result Framebuffer::invalidateSub(const Context *context, + size_t count, + const GLenum *attachments, + const Rectangle &area) +{ + // Back-ends might make the contents of the FBO undefined. In WebGL 2.0, invalidate operations + // can be no-ops, so we should probably do that to ensure consistency. + // TODO(jmadill): Make a invalidate no-op in WebGL 2.0. + + return mImpl->invalidateSub(context, count, attachments, area); +} + +angle::Result Framebuffer::clear(const Context *context, GLbitfield mask) +{ + const auto &glState = context->getState(); + if (glState.isRasterizerDiscardEnabled()) + { + return angle::Result::Continue; + } + + // Remove clear bits that are ineffective. An effective clear changes at least one fragment. If + // color/depth/stencil masks make the clear ineffective we skip it altogether. + + // If all color channels are masked, don't attempt to clear color. + if (context->getState().getBlendState().allChannelsMasked()) + { + mask &= ~GL_COLOR_BUFFER_BIT; + } + + // If depth write is disabled, don't attempt to clear depth. + if (!context->getState().getDepthStencilState().depthMask) + { + mask &= ~GL_DEPTH_BUFFER_BIT; + } + + // If all stencil bits are masked, don't attempt to clear stencil. + if (context->getState().getDepthStencilState().stencilWritemask == 0) + { + mask &= ~GL_STENCIL_BUFFER_BIT; + } + + if (mask != 0) + { + ANGLE_TRY(mImpl->clear(context, mask)); + } + + return angle::Result::Continue; +} + +angle::Result Framebuffer::clearBufferfv(const Context *context, + GLenum buffer, + GLint drawbuffer, + const GLfloat *values) +{ + if (context->getState().isRasterizerDiscardEnabled() || IsClearBufferMaskedOut(context, buffer)) + { + return angle::Result::Continue; + } + + if (buffer == GL_DEPTH) + { + // If depth write is disabled, don't attempt to clear depth. + if (!context->getState().getDepthStencilState().depthMask) + { + return angle::Result::Continue; + } + } + else + { + // If all color channels are masked, don't attempt to clear color. + if (context->getState().getBlendState().allChannelsMasked()) + { + return angle::Result::Continue; + } + } + + ANGLE_TRY(mImpl->clearBufferfv(context, buffer, drawbuffer, values)); + + return angle::Result::Continue; +} + +angle::Result Framebuffer::clearBufferuiv(const Context *context, + GLenum buffer, + GLint drawbuffer, + const GLuint *values) +{ + if (context->getState().isRasterizerDiscardEnabled() || IsClearBufferMaskedOut(context, buffer)) + { + return angle::Result::Continue; + } + + // If all color channels are masked, don't attempt to clear color. + if (context->getState().getBlendState().allChannelsMasked()) + { + return angle::Result::Continue; + } + + ANGLE_TRY(mImpl->clearBufferuiv(context, buffer, drawbuffer, values)); + + return angle::Result::Continue; +} + +angle::Result Framebuffer::clearBufferiv(const Context *context, + GLenum buffer, + GLint drawbuffer, + const GLint *values) +{ + if (context->getState().isRasterizerDiscardEnabled() || IsClearBufferMaskedOut(context, buffer)) + { + return angle::Result::Continue; + } + + if (buffer == GL_STENCIL) + { + // If all stencil bits are masked, don't attempt to clear stencil. + if (context->getState().getDepthStencilState().stencilWritemask == 0) + { + return angle::Result::Continue; + } + } + else + { + // If all color channels are masked, don't attempt to clear color. + if (context->getState().getBlendState().allChannelsMasked()) + { + return angle::Result::Continue; + } + } + + ANGLE_TRY(mImpl->clearBufferiv(context, buffer, drawbuffer, values)); + + return angle::Result::Continue; +} + +angle::Result Framebuffer::clearBufferfi(const Context *context, + GLenum buffer, + GLint drawbuffer, + GLfloat depth, + GLint stencil) +{ + if (context->getState().isRasterizerDiscardEnabled() || IsClearBufferMaskedOut(context, buffer)) + { + return angle::Result::Continue; + } + + bool clearDepth = context->getState().getDepthStencilState().depthMask; + bool clearStencil = context->getState().getDepthStencilState().stencilWritemask != 0; + + if (clearDepth && clearStencil) + { + ASSERT(buffer == GL_DEPTH_STENCIL); + ANGLE_TRY(mImpl->clearBufferfi(context, GL_DEPTH_STENCIL, drawbuffer, depth, stencil)); + } + else if (clearDepth && !clearStencil) + { + ANGLE_TRY(mImpl->clearBufferfv(context, GL_DEPTH, drawbuffer, &depth)); + } + else if (!clearDepth && clearStencil) + { + ANGLE_TRY(mImpl->clearBufferiv(context, GL_STENCIL, drawbuffer, &stencil)); + } + + return angle::Result::Continue; +} + +angle::Result Framebuffer::getImplementationColorReadFormat(const Context *context, + GLenum *formatOut) +{ + ANGLE_TRY(syncState(context)); + *formatOut = mImpl->getImplementationColorReadFormat(context); + return angle::Result::Continue; +} + +angle::Result Framebuffer::getImplementationColorReadType(const Context *context, GLenum *typeOut) +{ + ANGLE_TRY(syncState(context)); + *typeOut = mImpl->getImplementationColorReadType(context); + return angle::Result::Continue; +} + +angle::Result Framebuffer::readPixels(const Context *context, + const Rectangle &area, + GLenum format, + GLenum type, + void *pixels) +{ + ANGLE_TRY(mImpl->readPixels(context, area, format, type, pixels)); + + Buffer *unpackBuffer = context->getState().getTargetBuffer(BufferBinding::PixelUnpack); + if (unpackBuffer) + { + unpackBuffer->onPixelPack(); + } + + return angle::Result::Continue; +} + +angle::Result Framebuffer::blit(const Context *context, + const Rectangle &sourceArea, + const Rectangle &destArea, + GLbitfield mask, + GLenum filter) +{ + GLbitfield blitMask = mask; + + // Note that blitting is called against draw framebuffer. + // See the code in gl::Context::blitFramebuffer. + if ((mask & GL_COLOR_BUFFER_BIT) && !hasEnabledDrawBuffer()) + { + blitMask &= ~GL_COLOR_BUFFER_BIT; + } + + if ((mask & GL_STENCIL_BUFFER_BIT) && mState.getStencilAttachment() == nullptr) + { + blitMask &= ~GL_STENCIL_BUFFER_BIT; + } + + if ((mask & GL_DEPTH_BUFFER_BIT) && mState.getDepthAttachment() == nullptr) + { + blitMask &= ~GL_DEPTH_BUFFER_BIT; + } + + if (!blitMask) + { + return angle::Result::Continue; + } + + return mImpl->blit(context, sourceArea, destArea, blitMask, filter); +} + +bool Framebuffer::isDefault() const +{ + return id() == 0; +} + +int Framebuffer::getSamples(const Context *context) +{ + return (isComplete(context) ? getCachedSamples(context) : 0); +} + +int Framebuffer::getCachedSamples(const Context *context) const +{ + ASSERT(mCachedStatus.valid() && mCachedStatus.value() == GL_FRAMEBUFFER_COMPLETE); + + // For a complete framebuffer, all attachments must have the same sample count. + // In this case return the first nonzero sample size. + const auto *firstNonNullAttachment = mState.getFirstNonNullAttachment(); + if (firstNonNullAttachment) + { + ASSERT(firstNonNullAttachment->isAttached()); + return firstNonNullAttachment->getSamples(); + } + + // No attachments found. + return 0; +} + +angle::Result Framebuffer::getSamplePosition(const Context *context, + size_t index, + GLfloat *xy) const +{ + ANGLE_TRY(mImpl->getSamplePosition(context, index, xy)); + return angle::Result::Continue; +} + +bool Framebuffer::hasValidDepthStencil() const +{ + return mState.getDepthStencilAttachment() != nullptr; +} + +void Framebuffer::setAttachment(const Context *context, + GLenum type, + GLenum binding, + const ImageIndex &textureIndex, + FramebufferAttachmentObject *resource) +{ + setAttachment(context, type, binding, textureIndex, resource, + FramebufferAttachment::kDefaultNumViews, + FramebufferAttachment::kDefaultBaseViewIndex, false); +} + +void Framebuffer::setAttachment(const Context *context, + GLenum type, + GLenum binding, + const ImageIndex &textureIndex, + FramebufferAttachmentObject *resource, + GLsizei numViews, + GLuint baseViewIndex, + bool isMultiview) +{ + // Context may be null in unit tests. + if (!context || !context->isWebGL1()) + { + setAttachmentImpl(context, type, binding, textureIndex, resource, numViews, baseViewIndex, + isMultiview); + return; + } + + switch (binding) + { + case GL_DEPTH_STENCIL: + case GL_DEPTH_STENCIL_ATTACHMENT: + mState.mWebGLDepthStencilAttachment.attach(context, type, binding, textureIndex, + resource, numViews, baseViewIndex, + isMultiview); + break; + case GL_DEPTH: + case GL_DEPTH_ATTACHMENT: + mState.mWebGLDepthAttachment.attach(context, type, binding, textureIndex, resource, + numViews, baseViewIndex, isMultiview); + break; + case GL_STENCIL: + case GL_STENCIL_ATTACHMENT: + mState.mWebGLStencilAttachment.attach(context, type, binding, textureIndex, resource, + numViews, baseViewIndex, isMultiview); + break; + default: + setAttachmentImpl(context, type, binding, textureIndex, resource, numViews, + baseViewIndex, isMultiview); + return; + } + + commitWebGL1DepthStencilIfConsistent(context, numViews, baseViewIndex, isMultiview); +} + +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); +} + +void Framebuffer::commitWebGL1DepthStencilIfConsistent(const Context *context, + GLsizei numViews, + GLuint baseViewIndex, + bool isMultiview) +{ + 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); + setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews, + baseViewIndex, isMultiview); + } + else if (mState.mWebGLStencilAttachment.isAttached()) + { + const auto &stencil = mState.mWebGLStencilAttachment; + setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews, + baseViewIndex, isMultiview); + setAttachmentImpl(context, stencil.type(), GL_STENCIL_ATTACHMENT, + getImageIndexIfTextureAttachment(stencil), stencil.getResource(), + numViews, baseViewIndex, isMultiview); + } + else if (mState.mWebGLDepthStencilAttachment.isAttached()) + { + const auto &depthStencil = mState.mWebGLDepthStencilAttachment; + setAttachmentImpl(context, depthStencil.type(), GL_DEPTH_ATTACHMENT, + getImageIndexIfTextureAttachment(depthStencil), + depthStencil.getResource(), numViews, baseViewIndex, isMultiview); + setAttachmentImpl(context, depthStencil.type(), GL_STENCIL_ATTACHMENT, + getImageIndexIfTextureAttachment(depthStencil), + depthStencil.getResource(), numViews, baseViewIndex, isMultiview); + } + else + { + setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews, + baseViewIndex, isMultiview); + setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews, + baseViewIndex, isMultiview); + } +} + +void Framebuffer::setAttachmentImpl(const Context *context, + GLenum type, + GLenum binding, + const ImageIndex &textureIndex, + FramebufferAttachmentObject *resource, + GLsizei numViews, + GLuint baseViewIndex, + bool isMultiview) +{ + switch (binding) + { + case GL_DEPTH_STENCIL: + case GL_DEPTH_STENCIL_ATTACHMENT: + { + // ensure this is a legitimate depth+stencil format + FramebufferAttachmentObject *attachmentObj = resource; + if (resource) + { + const Format &format = resource->getAttachmentFormat(binding, textureIndex); + if (format.info->depthBits == 0 || format.info->stencilBits == 0) + { + // Attaching nullptr detaches the current attachment. + attachmentObj = nullptr; + } + } + + updateAttachment(context, &mState.mDepthAttachment, DIRTY_BIT_DEPTH_ATTACHMENT, + &mDirtyDepthAttachmentBinding, type, binding, textureIndex, + attachmentObj, numViews, baseViewIndex, isMultiview); + updateAttachment(context, &mState.mStencilAttachment, DIRTY_BIT_STENCIL_ATTACHMENT, + &mDirtyStencilAttachmentBinding, type, binding, textureIndex, + attachmentObj, numViews, baseViewIndex, isMultiview); + break; + } + + case GL_DEPTH: + case GL_DEPTH_ATTACHMENT: + updateAttachment(context, &mState.mDepthAttachment, DIRTY_BIT_DEPTH_ATTACHMENT, + &mDirtyDepthAttachmentBinding, type, binding, textureIndex, resource, + numViews, baseViewIndex, isMultiview); + break; + + case GL_STENCIL: + case GL_STENCIL_ATTACHMENT: + updateAttachment(context, &mState.mStencilAttachment, DIRTY_BIT_STENCIL_ATTACHMENT, + &mDirtyStencilAttachmentBinding, type, binding, textureIndex, resource, + numViews, baseViewIndex, isMultiview); + break; + + case GL_BACK: + updateAttachment(context, &mState.mColorAttachments[0], DIRTY_BIT_COLOR_ATTACHMENT_0, + &mDirtyColorAttachmentBindings[0], type, binding, textureIndex, + resource, numViews, baseViewIndex, isMultiview); + 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); + + if (!resource) + { + mColorAttachmentBits.reset(colorIndex); + mFloat32ColorAttachmentBits.reset(colorIndex); + } + else + { + mColorAttachmentBits.set(colorIndex); + updateFloat32ColorAttachmentBits( + colorIndex, resource->getAttachmentFormat(binding, textureIndex).info); + } + + // TODO(jmadill): ASSERT instead of checking the attachment exists in + // formsRenderingFeedbackLoopWith + 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) +{ + attachment->attach(context, type, binding, textureIndex, resource, numViews, baseViewIndex, + isMultiview); + 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); +} + +angle::Result Framebuffer::syncState(const Context *context) +{ + if (mDirtyBits.any()) + { + mDirtyBitsGuard = mDirtyBits; + ANGLE_TRY(mImpl->syncState(context, mDirtyBits)); + mDirtyBits.reset(); + mDirtyBitsGuard.reset(); + } + return angle::Result::Continue; +} + +void Framebuffer::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) +{ + if (message != angle::SubjectMessage::SubjectChanged) + { + // This can be triggered by SubImage calls for Textures. + if (message == angle::SubjectMessage::ContentsChanged) + { + mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + index); + onStateChange(angle::SubjectMessage::DirtyBitsFlagged); + return; + } + + // 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 &state = context->getState(); + const Program *program = state.getProgram(); + + // TODO(jmadill): Default framebuffer feedback loops. + if (mState.mId == 0) + { + return false; + } + + const FramebufferAttachment *depth = getDepthAttachment(); + const FramebufferAttachment *stencil = getStencilAttachment(); + + const bool checkDepth = depth && depth->type() == GL_TEXTURE; + // Skip the feedback loop check for stencil if depth/stencil point to the same resource. + const bool checkStencil = + (stencil && stencil->type() == GL_TEXTURE) && (!depth || *stencil != *depth); + + const gl::ActiveTextureMask &activeTextures = program->getActiveSamplersMask(); + const gl::ActiveTexturePointerArray &textures = state.getActiveTexturesCache(); + + for (size_t textureUnit : activeTextures) + { + Texture *texture = textures[textureUnit]; + + if (texture == nullptr) + { + continue; + } + + // Depth and stencil attachment form feedback loops + // Regardless of if enabled or masked. + if (checkDepth) + { + if (texture->id() == depth->id()) + { + return true; + } + } + + if (checkStencil) + { + if (texture->id() == stencil->id()) + { + return true; + } + } + + // Check if any color attachment forms a feedback loop. + for (size_t drawIndex : mColorAttachmentBits) + { + const FramebufferAttachment &attachment = mState.mColorAttachments[drawIndex]; + ASSERT(attachment.isAttached()); + + if (attachment.isTextureWithId(texture->id())) + { + // TODO(jmadill): Check for appropriate overlap. + return true; + } + } + } + + return false; +} + +bool Framebuffer::formsCopyingFeedbackLoopWith(GLuint copyTextureID, + GLint copyTextureLevel, + GLint copyTextureLayer) const +{ + if (mState.mId == 0) + { + // 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 BlendState &blend = glState.getBlendState(); + const DepthStencilState &depthStencil = glState.getDepthStencilState(); + + bool color = (mask & GL_COLOR_BUFFER_BIT) != 0 && !IsColorMaskedOut(blend); + bool depth = (mask & GL_DEPTH_BUFFER_BIT) != 0 && !IsDepthMaskedOut(depthStencil); + bool stencil = (mask & GL_STENCIL_BUFFER_BIT) != 0 && !IsStencilMaskedOut(depthStencil); + + if (!color && !depth && !stencil) + { + return angle::Result::Continue; + } + + if (partialClearNeedsInit(context, color, depth, stencil)) + { + ANGLE_TRY(ensureDrawAttachmentsInitialized(context)); + } + + // If the impl encounters an error during a a full (non-partial) clear, the attachments will + // still be marked initialized. This simplifies design, allowing this method to be called before + // the clear. + markDrawAttachmentsInitialized(color, depth, stencil); + + return angle::Result::Continue; +} + +angle::Result Framebuffer::ensureClearBufferAttachmentsInitialized(const Context *context, + GLenum buffer, + GLint drawbuffer) +{ + if (!context->isRobustResourceInitEnabled() || + context->getState().isRasterizerDiscardEnabled() || IsClearBufferMaskedOut(context, buffer)) + { + 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) + { + 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 |