// // 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 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 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(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(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 *samples, Optional *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(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) == Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX, "Framebuffer Dirty bit mismatch"); static_assert(static_cast(IMPLEMENTATION_MAX_FRAMEBUFFER_ATTACHMENTS) == Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT, "Framebuffer Dirty bit mismatch"); static_assert(static_cast(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(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 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(caps.maxColorAttachments)); for (uint32_t colorIndex = 0; colorIndex < static_cast(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 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 &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 colorbufferSize; Optional samples; Optional fixedSampleLocations; bool hasRenderbuffer = false; const FramebufferAttachment *firstAttachment = getFirstNonNullAttachment(); Optional isLayered; Optional 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 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(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(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