// // Copyright 2002 The ANGLE Project Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // // Framebuffer.cpp: Implements the gl::Framebuffer class. Implements GL framebuffer // objects and related functionality. [OpenGL ES 2.0.24] section 4.4 page 105. #include "libANGLE/Framebuffer.h" #include "common/Optional.h" #include "common/bitset_utils.h" #include "common/utilities.h" #include "libANGLE/Config.h" #include "libANGLE/Context.h" #include "libANGLE/Display.h" #include "libANGLE/ErrorStrings.h" #include "libANGLE/FramebufferAttachment.h" #include "libANGLE/PixelLocalStorage.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 { // Check the |checkAttachment| in reference to |firstAttachment| for the sake of multiview // framebuffer completeness. FramebufferStatus CheckMultiviewStateMatchesForCompleteness( const FramebufferAttachment *firstAttachment, const FramebufferAttachment *checkAttachment) { ASSERT(firstAttachment && checkAttachment); ASSERT(firstAttachment->isAttached() && checkAttachment->isAttached()); if (firstAttachment->isMultiview() != checkAttachment->isMultiview()) { return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR, err::kFramebufferIncompleteMultiviewMismatch); } if (firstAttachment->getNumViews() != checkAttachment->getNumViews()) { return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR, err::kFramebufferIncompleteMultiviewViewsMismatch); } if (checkAttachment->getBaseViewIndex() + checkAttachment->getNumViews() > checkAttachment->getSize().depth) { return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR, err::kFramebufferIncompleteMultiviewBaseViewMismatch); } return FramebufferStatus::Complete(); } FramebufferStatus CheckAttachmentCompleteness(const Context *context, const FramebufferAttachment &attachment) { ASSERT(attachment.isAttached()); const Extents &size = attachment.getSize(); if (size.width == 0 || size.height == 0) { return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentZeroSize); } if (!attachment.isRenderable(context)) { return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentNotRenderable); } 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentLayerGreaterThanDepth); } } // 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 (size.depth >= context->getCaps().maxFramebufferLayers) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentDepthGreaterThanMaxLayers); } } // 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentNotCubeComplete); } 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentLevelOutOfBaseMaxLevelRange); } // 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentLevelNotBaseLevelForIncompleteMipTexture); } } } return FramebufferStatus::Complete(); } FramebufferStatus CheckAttachmentSampleCounts(const Context *context, GLsizei currAttachmentSamples, GLsizei samples, bool colorAttachment) { if (currAttachmentSamples != samples) { if (colorAttachment) { // APPLE_framebuffer_multisample, which EXT_draw_buffers refers to, requires that // all color attachments have the same number of samples for the FBO to be complete. return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, err::kFramebufferIncompleteMultisampleInconsistentSampleCounts); } 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().framebufferMixedSamplesCHROMIUM) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, err::kFramebufferIncompleteMultisampleInconsistentSampleCounts); } if ((currAttachmentSamples % std::max(samples, 1)) != 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, err:: kFramebufferIncompleteMultisampleDepthStencilSampleCountDivisibleByColorSampleCount); } } } return FramebufferStatus::Complete(); } FramebufferStatus CheckAttachmentSampleCompleteness(const Context *context, const FramebufferAttachment &attachment, bool colorAttachment, Optional *samples, Optional *fixedSampleLocations, Optional *renderToTextureSamples) { ASSERT(attachment.isAttached()); if (attachment.type() == GL_TEXTURE) { const Texture *texture = attachment.getTexture(); ASSERT(texture); GLenum sizedInternalFormat = attachment.getFormat().info->sizedInternalFormat; const TextureCaps &formatCaps = context->getTextureCaps().get(sizedInternalFormat); if (static_cast(attachment.getSamples()) > formatCaps.getMaxSamples()) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, err::kFramebufferIncompleteAttachmentSamplesGreaterThanMaxSupportedSamples); } const ImageIndex &attachmentImageIndex = attachment.getTextureImageIndex(); bool fixedSampleloc = texture->getAttachmentFixedSampleLocations(attachmentImageIndex); if (fixedSampleLocations->valid() && fixedSampleloc != fixedSampleLocations->value()) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, err::kFramebufferIncompleteMultisampleInconsistentFixedSampleLocations); } else { *fixedSampleLocations = fixedSampleloc; } } if (renderToTextureSamples->valid()) { // Only check against RenderToTextureSamples if they actually exist. if (renderToTextureSamples->value() != FramebufferAttachment::kDefaultRenderToTextureSamples) { FramebufferStatus sampleCountStatus = CheckAttachmentSampleCounts(context, attachment.getRenderToTextureSamples(), renderToTextureSamples->value(), colorAttachment); if (!sampleCountStatus.isComplete()) { return sampleCountStatus; } } } else { *renderToTextureSamples = attachment.getRenderToTextureSamples(); } if (samples->valid()) { // RenderToTextureSamples takes precedence if they exist. if (renderToTextureSamples->value() == FramebufferAttachment::kDefaultRenderToTextureSamples) { FramebufferStatus sampleCountStatus = CheckAttachmentSampleCounts( context, attachment.getSamples(), samples->value(), colorAttachment); if (!sampleCountStatus.isComplete()) { return sampleCountStatus; } } } else { *samples = attachment.getSamples(); } return FramebufferStatus::Complete(); } // Needed to index into the attachment arrays/bitsets. static_assert(static_cast(IMPLEMENTATION_MAX_DRAW_BUFFERS) == Framebuffer::DIRTY_BIT_COLOR_ATTACHMENT_MAX, "Framebuffer Dirty bit mismatch"); static_assert(static_cast(IMPLEMENTATION_MAX_DRAW_BUFFERS) == Framebuffer::DIRTY_BIT_DEPTH_ATTACHMENT, "Framebuffer Dirty bit mismatch"); static_assert(static_cast(IMPLEMENTATION_MAX_DRAW_BUFFERS + 1) == Framebuffer::DIRTY_BIT_STENCIL_ATTACHMENT, "Framebuffer Dirty bit mismatch"); angle::Result InitAttachment(const Context *context, FramebufferAttachment *attachment) { ASSERT(attachment->isAttached()); if (attachment->initState() == InitState::MayNeedInit) { ANGLE_TRY(attachment->initializeContents(context)); } return angle::Result::Continue; } bool AttachmentOverlapsWithTexture(const FramebufferAttachment &attachment, const Texture *texture, const Sampler *sampler) { if (!attachment.isTextureWithId(texture->id())) { return false; } const gl::ImageIndex &index = attachment.getTextureImageIndex(); GLuint attachmentLevel = static_cast(index.getLevelIndex()); GLuint textureEffectiveBaseLevel = texture->getTextureState().getEffectiveBaseLevel(); GLuint textureMaxLevel = textureEffectiveBaseLevel; if ((sampler && IsMipmapFiltered(sampler->getSamplerState().getMinFilter())) || IsMipmapFiltered(texture->getSamplerState().getMinFilter())) { textureMaxLevel = texture->getMipmapMaxLevel(); } return attachmentLevel >= textureEffectiveBaseLevel && attachmentLevel <= textureMaxLevel; } } // anonymous namespace bool FramebufferStatus::isComplete() const { return status == GL_FRAMEBUFFER_COMPLETE; } FramebufferStatus FramebufferStatus::Complete() { FramebufferStatus result; result.status = GL_FRAMEBUFFER_COMPLETE; result.reason = nullptr; return result; } FramebufferStatus FramebufferStatus::Incomplete(GLenum status, const char *reason) { ASSERT(status != GL_FRAMEBUFFER_COMPLETE); FramebufferStatus result; result.status = status; result.reason = reason; return result; } // This constructor is only used for default framebuffers. FramebufferState::FramebufferState(rx::Serial serial) : mId(Framebuffer::kDefaultDrawFramebufferHandle), mFramebufferSerial(serial), mLabel(), mColorAttachments(1), mColorAttachmentsMask(0), mDrawBufferStates(1, GL_BACK), mReadBufferState(GL_BACK), mDrawBufferTypeMask(), mDefaultWidth(0), mDefaultHeight(0), mDefaultSamples(0), mDefaultFixedSampleLocations(GL_FALSE), mDefaultLayers(0), mFlipY(GL_FALSE), mWebGLDepthStencilConsistent(true), mDefaultFramebufferReadAttachmentInitialized(false), mSrgbWriteControlMode(SrgbWriteControlMode::Default) { ASSERT(mDrawBufferStates.size() > 0); mEnabledDrawBuffers.set(0); } FramebufferState::FramebufferState(const Caps &caps, FramebufferID id, rx::Serial serial) : mId(id), mFramebufferSerial(serial), mLabel(), mColorAttachments(caps.maxColorAttachments), mColorAttachmentsMask(0), mDrawBufferStates(caps.maxDrawBuffers, GL_NONE), mReadBufferState(GL_COLOR_ATTACHMENT0_EXT), mDrawBufferTypeMask(), mDefaultWidth(0), mDefaultHeight(0), mDefaultSamples(0), mDefaultFixedSampleLocations(GL_FALSE), mDefaultLayers(0), mFlipY(GL_FALSE), mWebGLDepthStencilConsistent(true), mDefaultFramebufferReadAttachmentInitialized(false), mSrgbWriteControlMode(SrgbWriteControlMode::Default) { ASSERT(mId != Framebuffer::kDefaultDrawFramebufferHandle); ASSERT(mDrawBufferStates.size() > 0); mDrawBufferStates[0] = GL_COLOR_ATTACHMENT0_EXT; } FramebufferState::~FramebufferState() {} const std::string &FramebufferState::getLabel() const { return mLabel; } const FramebufferAttachment *FramebufferState::getAttachment(const Context *context, GLenum attachment) const { if (attachment >= GL_COLOR_ATTACHMENT0 && attachment <= GL_COLOR_ATTACHMENT15) { return getColorAttachment(attachment - GL_COLOR_ATTACHMENT0); } // WebGL1 allows a developer to query for attachment parameters even when "inconsistant" (i.e. // multiple conflicting attachment points) and requires us to return the framebuffer attachment // associated with WebGL. switch (attachment) { case GL_COLOR: case GL_BACK: return getColorAttachment(0); case GL_DEPTH: case GL_DEPTH_ATTACHMENT: if (context->isWebGL1()) { return getWebGLDepthAttachment(); } else { return getDepthAttachment(); } case GL_STENCIL: case GL_STENCIL_ATTACHMENT: if (context->isWebGL1()) { return getWebGLStencilAttachment(); } else { return getStencilAttachment(); } case GL_DEPTH_STENCIL: case GL_DEPTH_STENCIL_ATTACHMENT: if (context->isWebGL1()) { return getWebGLDepthStencilAttachment(); } else { return getDepthStencilAttachment(); } default: UNREACHABLE(); return nullptr; } } uint32_t FramebufferState::getReadIndex() const { ASSERT(mReadBufferState == GL_BACK || (mReadBufferState >= GL_COLOR_ATTACHMENT0 && mReadBufferState <= GL_COLOR_ATTACHMENT15)); uint32_t readIndex = mReadBufferState == GL_BACK ? 0 : mReadBufferState - GL_COLOR_ATTACHMENT0; ASSERT(readIndex < mColorAttachments.size()); return readIndex; } const FramebufferAttachment *FramebufferState::getReadAttachment() const { if (mReadBufferState == GL_NONE) { return nullptr; } uint32_t readIndex = getReadIndex(); const gl::FramebufferAttachment &framebufferAttachment = isDefault() ? mDefaultFramebufferReadAttachment : mColorAttachments[readIndex]; return framebufferAttachment.isAttached() ? &framebufferAttachment : nullptr; } const FramebufferAttachment *FramebufferState::getReadPixelsAttachment(GLenum readFormat) const { switch (readFormat) { case GL_DEPTH_COMPONENT: return getDepthAttachment(); case GL_STENCIL_INDEX_OES: return getStencilOrDepthStencilAttachment(); case GL_DEPTH_STENCIL_OES: return getDepthStencilAttachment(); default: return getReadAttachment(); } } const FramebufferAttachment *FramebufferState::getFirstNonNullAttachment() const { auto *colorAttachment = getFirstColorAttachment(); if (colorAttachment) { return colorAttachment; } return getDepthOrStencilAttachment(); } const FramebufferAttachment *FramebufferState::getFirstColorAttachment() const { for (const FramebufferAttachment &colorAttachment : mColorAttachments) { if (colorAttachment.isAttached()) { return &colorAttachment; } } return nullptr; } const FramebufferAttachment *FramebufferState::getDepthOrStencilAttachment() const { if (mDepthAttachment.isAttached()) { return &mDepthAttachment; } if (mStencilAttachment.isAttached()) { return &mStencilAttachment; } return nullptr; } const FramebufferAttachment *FramebufferState::getStencilOrDepthStencilAttachment() const { if (mStencilAttachment.isAttached()) { return &mStencilAttachment; } return getDepthStencilAttachment(); } const FramebufferAttachment *FramebufferState::getColorAttachment(size_t colorAttachment) const { ASSERT(colorAttachment < mColorAttachments.size()); return mColorAttachments[colorAttachment].isAttached() ? &mColorAttachments[colorAttachment] : nullptr; } const FramebufferAttachment *FramebufferState::getDepthAttachment() const { return mDepthAttachment.isAttached() ? &mDepthAttachment : nullptr; } const FramebufferAttachment *FramebufferState::getWebGLDepthAttachment() const { return mWebGLDepthAttachment.isAttached() ? &mWebGLDepthAttachment : nullptr; } const FramebufferAttachment *FramebufferState::getWebGLDepthStencilAttachment() const { return mWebGLDepthStencilAttachment.isAttached() ? &mWebGLDepthStencilAttachment : nullptr; } const FramebufferAttachment *FramebufferState::getStencilAttachment() const { return mStencilAttachment.isAttached() ? &mStencilAttachment : nullptr; } const FramebufferAttachment *FramebufferState::getWebGLStencilAttachment() const { return mWebGLStencilAttachment.isAttached() ? &mWebGLStencilAttachment : nullptr; } const FramebufferAttachment *FramebufferState::getDepthStencilAttachment() const { // A valid depth-stencil attachment has the same resource bound to both the // depth and stencil attachment points. if (mDepthAttachment.isAttached() && mStencilAttachment.isAttached() && mDepthAttachment == mStencilAttachment) { return &mDepthAttachment; } return nullptr; } const Extents FramebufferState::getAttachmentExtentsIntersection() const { int32_t width = std::numeric_limits::max(); int32_t height = std::numeric_limits::max(); for (const FramebufferAttachment &attachment : mColorAttachments) { if (attachment.isAttached()) { width = std::min(width, attachment.getSize().width); height = std::min(height, attachment.getSize().height); } } if (mDepthAttachment.isAttached()) { width = std::min(width, mDepthAttachment.getSize().width); height = std::min(height, mDepthAttachment.getSize().height); } if (mStencilAttachment.isAttached()) { width = std::min(width, mStencilAttachment.getSize().width); height = std::min(height, mStencilAttachment.getSize().height); } return Extents(width, height, 0); } 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::hasExternalTextureAttachment() const { // External textures can only be bound to color attachment 0 return (mColorAttachments[0].isAttached() && mColorAttachments[0].isExternalTexture()); } bool FramebufferState::hasYUVAttachment() const { // The only attachments that can be YUV are external textures and surfaces, both are attached at // color attachment 0. return (mColorAttachments[0].isAttached() && mColorAttachments[0].isYUV()); } bool FramebufferState::isMultiview() const { const FramebufferAttachment *attachment = getFirstNonNullAttachment(); if (attachment == nullptr) { return false; } return attachment->isMultiview(); } int FramebufferState::getBaseViewIndex() const { const FramebufferAttachment *attachment = getFirstNonNullAttachment(); if (attachment == nullptr) { return GL_NONE; } return attachment->getBaseViewIndex(); } Box FramebufferState::getDimensions() const { Extents extents = getExtents(); return Box(0, 0, 0, extents.width, extents.height, extents.depth); } Extents FramebufferState::getExtents() const { // OpenGLES3.0 (https://www.khronos.org/registry/OpenGL/specs/es/3.0/es_spec_3.0.pdf // section 4.4.4.2) allows attachments have unequal size. const FramebufferAttachment *first = getFirstNonNullAttachment(); if (first) { return getAttachmentExtentsIntersection(); } return Extents(getDefaultWidth(), getDefaultHeight(), 0); } bool FramebufferState::isDefault() const { return mId == Framebuffer::kDefaultDrawFramebufferHandle; } bool FramebufferState::isBoundAsDrawFramebuffer(const Context *context) const { return context->getState().getDrawFramebuffer()->id() == mId; } const FramebufferID Framebuffer::kDefaultDrawFramebufferHandle = {0}; Framebuffer::Framebuffer(const Context *context, rx::GLImplFactory *factory) : mState(context->getShareGroup()->generateFramebufferSerial()), mImpl(factory->createFramebuffer(mState)), mCachedStatus(FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNDEFINED_OES, err::kFramebufferIncompleteSurfaceless)), 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(const Context *context, rx::GLImplFactory *factory, FramebufferID id) : mState(context->getCaps(), id, context->getShareGroup()->generateFramebufferSerial()), mImpl(factory->createFramebuffer(mState)), mCachedStatus(), mDirtyDepthAttachmentBinding(this, DIRTY_BIT_DEPTH_ATTACHMENT), mDirtyStencilAttachmentBinding(this, DIRTY_BIT_STENCIL_ATTACHMENT) { ASSERT(mImpl != nullptr); ASSERT(mState.mColorAttachments.size() == static_cast(context->getCaps().maxColorAttachments)); for (uint32_t colorIndex = 0; colorIndex < static_cast(mState.mColorAttachments.size()); ++colorIndex) { mDirtyColorAttachmentBindings.emplace_back(this, DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex); } if (context->getClientVersion() >= ES_3_0) { mDirtyBits.set(DIRTY_BIT_READ_BUFFER); } } Framebuffer::~Framebuffer() { SafeDelete(mImpl); } void Framebuffer::onDestroy(const Context *context) { if (isDefault()) { std::ignore = unsetSurfaces(context); } for (auto &attachment : mState.mColorAttachments) { attachment.detach(context, mState.mFramebufferSerial); } mState.mDepthAttachment.detach(context, mState.mFramebufferSerial); mState.mStencilAttachment.detach(context, mState.mFramebufferSerial); mState.mWebGLDepthAttachment.detach(context, mState.mFramebufferSerial); mState.mWebGLStencilAttachment.detach(context, mState.mFramebufferSerial); mState.mWebGLDepthStencilAttachment.detach(context, mState.mFramebufferSerial); if (mPixelLocalStorage) { mPixelLocalStorage->onFramebufferDestroyed(context); } mImpl->destroy(context); } egl::Error Framebuffer::setSurfaces(const Context *context, egl::Surface *surface, egl::Surface *readSurface) { // This has to be a default framebuffer. ASSERT(isDefault()); ASSERT(mDirtyColorAttachmentBindings.size() == 1); ASSERT(mDirtyColorAttachmentBindings[0].getSubjectIndex() == DIRTY_BIT_COLOR_ATTACHMENT_0); ASSERT(!mState.mColorAttachments[0].isAttached()); ASSERT(!mState.mDepthAttachment.isAttached()); ASSERT(!mState.mStencilAttachment.isAttached()); if (surface) { setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex(), surface, FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex, false, FramebufferAttachment::kDefaultRenderToTextureSamples); mDirtyBits.set(DIRTY_BIT_COLOR_ATTACHMENT_0); if (surface->getConfig()->depthSize > 0) { setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_DEPTH, ImageIndex(), surface, FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex, false, FramebufferAttachment::kDefaultRenderToTextureSamples); mDirtyBits.set(DIRTY_BIT_DEPTH_ATTACHMENT); } if (surface->getConfig()->stencilSize > 0) { setAttachmentImpl(context, GL_FRAMEBUFFER_DEFAULT, GL_STENCIL, ImageIndex(), surface, FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex, false, FramebufferAttachment::kDefaultRenderToTextureSamples); mDirtyBits.set(DIRTY_BIT_STENCIL_ATTACHMENT); } mState.mSurfaceTextureOffset = surface->getTextureOffset(); // Ensure the backend has a chance to synchronize its content for a new backbuffer. mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0); } setReadSurface(context, readSurface); SetComponentTypeMask(getDrawbufferWriteType(0), 0, &mState.mDrawBufferTypeMask); ASSERT(mCachedStatus.value().status == GL_FRAMEBUFFER_UNDEFINED_OES); ASSERT(mCachedStatus.value().reason == err::kFramebufferIncompleteSurfaceless); if (surface) { mCachedStatus = FramebufferStatus::Complete(); ANGLE_TRY(surface->getImplementation()->attachToFramebuffer(context, this)); } return egl::NoError(); } void Framebuffer::setReadSurface(const Context *context, egl::Surface *readSurface) { // This has to be a default framebuffer. ASSERT(isDefault()); ASSERT(mDirtyColorAttachmentBindings.size() == 1); ASSERT(mDirtyColorAttachmentBindings[0].getSubjectIndex() == DIRTY_BIT_COLOR_ATTACHMENT_0); // Read surface is not attached. ASSERT(!mState.mDefaultFramebufferReadAttachment.isAttached()); // updateAttachment() without mState.mResourceNeedsInit.set() mState.mDefaultFramebufferReadAttachment.attach( context, GL_FRAMEBUFFER_DEFAULT, GL_BACK, ImageIndex(), readSurface, FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex, false, FramebufferAttachment::kDefaultRenderToTextureSamples, mState.mFramebufferSerial); if (context->getClientVersion() >= ES_3_0) { mDirtyBits.set(DIRTY_BIT_READ_BUFFER); } } egl::Error Framebuffer::unsetSurfaces(const Context *context) { // This has to be a default framebuffer. ASSERT(isDefault()); ASSERT(mDirtyColorAttachmentBindings.size() == 1); ASSERT(mDirtyColorAttachmentBindings[0].getSubjectIndex() == DIRTY_BIT_COLOR_ATTACHMENT_0); if (mState.mColorAttachments[0].isAttached()) { const egl::Surface *surface = mState.mColorAttachments[0].getSurface(); mState.mColorAttachments[0].detach(context, mState.mFramebufferSerial); mDirtyBits.set(DIRTY_BIT_COLOR_ATTACHMENT_0); if (mState.mDepthAttachment.isAttached()) { mState.mDepthAttachment.detach(context, mState.mFramebufferSerial); mDirtyBits.set(DIRTY_BIT_DEPTH_ATTACHMENT); } if (mState.mStencilAttachment.isAttached()) { mState.mStencilAttachment.detach(context, mState.mFramebufferSerial); mDirtyBits.set(DIRTY_BIT_STENCIL_ATTACHMENT); } ANGLE_TRY(surface->getImplementation()->detachFromFramebuffer(context, this)); ASSERT(mCachedStatus.value().status == GL_FRAMEBUFFER_COMPLETE); mCachedStatus = FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNDEFINED_OES, err::kFramebufferIncompleteSurfaceless); } else { ASSERT(!mState.mDepthAttachment.isAttached()); ASSERT(!mState.mStencilAttachment.isAttached()); ASSERT(mCachedStatus.value().status == GL_FRAMEBUFFER_UNDEFINED_OES); ASSERT(mCachedStatus.value().reason == err::kFramebufferIncompleteSurfaceless); } mState.mDefaultFramebufferReadAttachment.detach(context, mState.mFramebufferSerial); mState.mDefaultFramebufferReadAttachmentInitialized = false; return egl::NoError(); } angle::Result Framebuffer::setLabel(const Context *context, const std::string &label) { mState.mLabel = label; if (mImpl) { return mImpl->onLabelUpdate(context); } return angle::Result::Continue; } const std::string &Framebuffer::getLabel() const { return mState.mLabel; } bool Framebuffer::detachTexture(const Context *context, TextureID textureId) { return detachResourceById(context, GL_TEXTURE, textureId.value); } bool Framebuffer::detachRenderbuffer(const Context *context, RenderbufferID renderbufferId) { return detachResourceById(context, GL_RENDERBUFFER, renderbufferId.value); } bool Framebuffer::detachResourceById(const Context *context, GLenum resourceType, GLuint resourceId) { bool found = false; for (size_t colorIndex = 0; colorIndex < mState.mColorAttachments.size(); ++colorIndex) { if (detachMatchingAttachment(context, &mState.mColorAttachments[colorIndex], resourceType, resourceId)) { found = true; } } if (context->isWebGL1()) { const std::array 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 DrawBuffersVector &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())); if (mState.mReadBufferState != buffer) { mState.mReadBufferState = buffer; mDirtyBits.set(DIRTY_BIT_READ_BUFFER); } } size_t Framebuffer::getNumColorAttachments() const { return mState.mColorAttachments.size(); } bool Framebuffer::hasDepth() const { return mState.hasDepth(); } bool Framebuffer::hasStencil() const { return mState.hasStencil(); } bool Framebuffer::hasExternalTextureAttachment() const { return mState.hasExternalTextureAttachment(); } bool Framebuffer::hasYUVAttachment() const { return mState.hasYUVAttachment(); } bool Framebuffer::usingExtendedDrawBuffers() const { for (size_t drawbufferIdx = 1; drawbufferIdx < mState.mDrawBufferStates.size(); ++drawbufferIdx) { if (getDrawBuffer(drawbufferIdx) != nullptr) { return true; } } return false; } void Framebuffer::invalidateCompletenessCache() { if (!isDefault()) { mCachedStatus.reset(); } onStateChange(angle::SubjectMessage::DirtyBitsFlagged); } const FramebufferStatus &Framebuffer::checkStatusImpl(const Context *context) const { ASSERT(!isDefault()); ASSERT(hasAnyDirtyBit() || !mCachedStatus.valid()); mCachedStatus = checkStatusWithGLFrontEnd(context); if (mCachedStatus.value().isComplete()) { // We can skip syncState on several back-ends. if (mImpl->shouldSyncStateBeforeCheckStatus()) { // This binding is not totally correct. It is ok because the parameter isn't used in // the GL back-end and the GL back-end is the only user of syncStateBeforeCheckStatus. angle::Result err = syncState(context, GL_FRAMEBUFFER, Command::Other); if (err != angle::Result::Continue) { mCachedStatus = FramebufferStatus::Incomplete(0, err::kFramebufferIncompleteInternalError); return mCachedStatus.value(); } } mCachedStatus = mImpl->checkStatus(context); } return mCachedStatus.value(); } FramebufferStatus Framebuffer::checkStatusWithGLFrontEnd(const Context *context) const { const State &state = context->getState(); ASSERT(!isDefault()); bool hasAttachments = false; Optional colorbufferSize; Optional samples; Optional fixedSampleLocations; bool hasRenderbuffer = false; Optional renderToTextureSamples; const FramebufferAttachment *firstAttachment = getFirstNonNullAttachment(); Optional isLayered; Optional colorAttachmentsTextureType; for (const FramebufferAttachment &colorAttachment : mState.mColorAttachments) { if (colorAttachment.isAttached()) { FramebufferStatus attachmentCompleteness = CheckAttachmentCompleteness(context, colorAttachment); if (!attachmentCompleteness.isComplete()) { return attachmentCompleteness; } const InternalFormat &format = *colorAttachment.getFormat().info; if (format.depthBits > 0 || format.stencilBits > 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteDepthStencilInColorBuffer); } FramebufferStatus attachmentSampleCompleteness = CheckAttachmentSampleCompleteness(context, colorAttachment, true, &samples, &fixedSampleLocations, &renderToTextureSamples); if (!attachmentSampleCompleteness.isComplete()) { return attachmentSampleCompleteness; } // 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_UNSUPPORTED, err::kFramebufferIncompleteAttachmentInconsistantBitPlanes); } } else { colorbufferSize = format.pixelBytes; } } FramebufferStatus attachmentMultiviewCompleteness = CheckMultiviewStateMatchesForCompleteness(firstAttachment, &colorAttachment); if (!attachmentMultiviewCompleteness.isComplete()) { return attachmentMultiviewCompleteness; } 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, err::kFramebufferIncompleteMismatchedLayeredAttachments); } else if (isLayered.value()) { ASSERT(colorAttachmentsTextureType.valid()); if (colorAttachmentsTextureType.value() != colorAttachment.getTextureImageIndex().getType()) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, err::kFramebufferIncompleteMismatchedLayeredTexturetypes); } } } } } const FramebufferAttachment &depthAttachment = mState.mDepthAttachment; if (depthAttachment.isAttached()) { FramebufferStatus attachmentCompleteness = CheckAttachmentCompleteness(context, depthAttachment); if (!attachmentCompleteness.isComplete()) { return attachmentCompleteness; } const InternalFormat &format = *depthAttachment.getFormat().info; if (format.depthBits == 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentNoDepthBitsInDepthBuffer); } FramebufferStatus attachmentSampleCompleteness = CheckAttachmentSampleCompleteness(context, depthAttachment, false, &samples, &fixedSampleLocations, &renderToTextureSamples); if (!attachmentSampleCompleteness.isComplete()) { return attachmentSampleCompleteness; } FramebufferStatus attachmentMultiviewCompleteness = CheckMultiviewStateMatchesForCompleteness(firstAttachment, &depthAttachment); if (!attachmentMultiviewCompleteness.isComplete()) { return attachmentMultiviewCompleteness; } 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, err::kFramebufferIncompleteMismatchedLayeredAttachments); } } } const FramebufferAttachment &stencilAttachment = mState.mStencilAttachment; if (stencilAttachment.isAttached()) { FramebufferStatus attachmentCompleteness = CheckAttachmentCompleteness(context, stencilAttachment); if (!attachmentCompleteness.isComplete()) { return attachmentCompleteness; } const InternalFormat &format = *stencilAttachment.getFormat().info; if (format.stencilBits == 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentNoStencilBitsInStencilBuffer); } FramebufferStatus attachmentSampleCompleteness = CheckAttachmentSampleCompleteness(context, stencilAttachment, false, &samples, &fixedSampleLocations, &renderToTextureSamples); if (!attachmentSampleCompleteness.isComplete()) { return attachmentSampleCompleteness; } FramebufferStatus attachmentMultiviewCompleteness = CheckMultiviewStateMatchesForCompleteness(firstAttachment, &stencilAttachment); if (!attachmentMultiviewCompleteness.isComplete()) { return attachmentMultiviewCompleteness; } 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS_EXT, err::kFramebufferIncompleteMismatchedLayeredAttachments); } } } // 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_UNSUPPORTED, err::kFramebufferIncompleteDepthAndStencilBuffersNotTheSame); } // Special additional validation for WebGL 1 DEPTH/STENCIL/DEPTH_STENCIL. if (state.isWebGL1()) { if (!mState.mWebGLDepthStencilConsistent) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_UNSUPPORTED, err::kFramebufferIncompleteWebGLDepthStencilInconsistant); } if (mState.mWebGLDepthStencilAttachment.isAttached()) { if (mState.mWebGLDepthStencilAttachment.getDepthSize() == 0 || mState.mWebGLDepthStencilAttachment.getStencilSize() == 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentWebGLDepthStencilNoDepthOrStencilBits); } FramebufferStatus attachmentMultiviewCompleteness = CheckMultiviewStateMatchesForCompleteness(firstAttachment, &mState.mWebGLDepthStencilAttachment); if (!attachmentMultiviewCompleteness.isComplete()) { return attachmentMultiviewCompleteness; } } else if (mState.mStencilAttachment.isAttached() && mState.mStencilAttachment.getDepthSize() > 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentWebGLStencilBufferHasDepthBits); } else if (mState.mDepthAttachment.isAttached() && mState.mDepthAttachment.getStencilSize() > 0) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT, err::kFramebufferIncompleteAttachmentWebGLDepthBufferHasStencilBits); } } // 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 FramebufferStatus::Incomplete(GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT, err::kFramebufferIncompleteDefaultZeroSize); } // 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().webglCompatibilityANGLE) && !mState.attachmentsHaveSameDimensions()) { return FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS, err::kFramebufferIncompleteInconsistantAttachmentSizes); } // 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 FramebufferStatus::Incomplete( GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE, err::kFramebufferIncompleteMultisampleNonFixedSamplesWithRenderbuffers); } // 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().webglCompatibilityANGLE) { if (!mState.colorAttachmentsAreUniqueImages()) { return FramebufferStatus::Incomplete(GL_FRAMEBUFFER_UNSUPPORTED, err::kFramebufferIncompleteAttachmentsNotUnique); } } return FramebufferStatus::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; } if (depth && context->getFrontendFeatures().forceDepthAttachmentInitOnClear.enabled) { return true; } // Scissors can affect clearing. // TODO(jmadill): Check for complete scissor overlap. if (glState.isScissorTestEnabled()) { return true; } // If colors masked, we must clear before we clear. Do a simple check. // TODO(jmadill): Filter out unused color channels from the test. if (color && glState.anyActiveDrawBufferChannelMasked()) { return true; } const auto &depthStencil = glState.getDepthStencilState(); if (stencil && (depthStencil.stencilMask != depthStencil.stencilWritemask || depthStencil.stencilBackMask != depthStencil.stencilBackWritemask)) { return true; } return false; } angle::Result Framebuffer::invalidateSub(const Context *context, size_t count, const GLenum *attachments, const Rectangle &area) { // Back-ends might make the contents of the FBO undefined. In WebGL 2.0, invalidate operations // can be no-ops, so we should probably do that to ensure consistency. // TODO(jmadill): Make a invalidate no-op in WebGL 2.0. return mImpl->invalidateSub(context, count, attachments, area); } angle::Result Framebuffer::clear(const Context *context, GLbitfield mask) { ASSERT(mask && !context->getState().isRasterizerDiscardEnabled()); return mImpl->clear(context, mask); } angle::Result Framebuffer::clearBufferfv(const Context *context, GLenum buffer, GLint drawbuffer, const GLfloat *values) { return mImpl->clearBufferfv(context, buffer, drawbuffer, values); } angle::Result Framebuffer::clearBufferuiv(const Context *context, GLenum buffer, GLint drawbuffer, const GLuint *values) { return mImpl->clearBufferuiv(context, buffer, drawbuffer, values); } angle::Result Framebuffer::clearBufferiv(const Context *context, GLenum buffer, GLint drawbuffer, const GLint *values) { return mImpl->clearBufferiv(context, buffer, drawbuffer, values); } angle::Result Framebuffer::clearBufferfi(const Context *context, GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) { const bool clearDepth = getDepthAttachment() != nullptr && context->getState().getDepthStencilState().depthMask; const bool clearStencil = getStencilAttachment() != nullptr && context->getState().getDepthStencilState().stencilWritemask != 0; if (clearDepth && clearStencil) { ASSERT(buffer == GL_DEPTH_STENCIL); ANGLE_TRY(mImpl->clearBufferfi(context, GL_DEPTH_STENCIL, drawbuffer, depth, stencil)); } else if (clearDepth && !clearStencil) { ANGLE_TRY(mImpl->clearBufferfv(context, GL_DEPTH, drawbuffer, &depth)); } else if (!clearDepth && clearStencil) { ANGLE_TRY(mImpl->clearBufferiv(context, GL_STENCIL, drawbuffer, &stencil)); } return angle::Result::Continue; } GLenum Framebuffer::getImplementationColorReadFormat(const Context *context) { const gl::InternalFormat &format = mImpl->getImplementationColorReadFormat(context); return format.getReadPixelsFormat(context->getExtensions()); } GLenum Framebuffer::getImplementationColorReadType(const Context *context) { const gl::InternalFormat &format = mImpl->getImplementationColorReadFormat(context); return format.getReadPixelsType(context->getClientVersion()); } angle::Result Framebuffer::readPixels(const Context *context, const Rectangle &area, GLenum format, GLenum type, const PixelPackState &pack, Buffer *packBuffer, void *pixels) { ANGLE_TRY(mImpl->readPixels(context, area, format, type, pack, packBuffer, pixels)); if (packBuffer) { packBuffer->onDataChanged(); } return angle::Result::Continue; } angle::Result Framebuffer::blit(const Context *context, const Rectangle &sourceArea, const Rectangle &destArea, GLbitfield mask, GLenum filter) { ASSERT(mask != 0); ANGLE_TRY(mImpl->blit(context, sourceArea, destArea, mask, filter)); // Mark the contents of the attachments dirty if ((mask & GL_COLOR_BUFFER_BIT) != 0) { for (size_t colorIndex : mState.mEnabledDrawBuffers) { mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + colorIndex); } } if ((mask & GL_DEPTH_BUFFER_BIT) != 0) { mDirtyBits.set(DIRTY_BIT_DEPTH_BUFFER_CONTENTS); } if ((mask & GL_STENCIL_BUFFER_BIT) != 0) { mDirtyBits.set(DIRTY_BIT_STENCIL_BUFFER_CONTENTS); } onStateChange(angle::SubjectMessage::DirtyBitsFlagged); return angle::Result::Continue; } int Framebuffer::getSamples(const Context *context) const { if (!isComplete(context)) { return 0; } ASSERT(mCachedStatus.valid() && mCachedStatus.value().isComplete()); // For a complete framebuffer, all attachments must have the same sample count. // In this case return the first nonzero sample size. const FramebufferAttachment *firstNonNullAttachment = mState.getFirstNonNullAttachment(); ASSERT(firstNonNullAttachment == nullptr || firstNonNullAttachment->isAttached()); return firstNonNullAttachment ? firstNonNullAttachment->getSamples() : 0; } int Framebuffer::getReadBufferResourceSamples(const Context *context) const { if (!isComplete(context)) { return 0; } ASSERT(mCachedStatus.valid() && mCachedStatus.value().isComplete()); const FramebufferAttachment *readAttachment = mState.getReadAttachment(); ASSERT(readAttachment == nullptr || readAttachment->isAttached()); return readAttachment ? readAttachment->getResourceSamples() : 0; } angle::Result Framebuffer::getSamplePosition(const Context *context, size_t index, GLfloat *xy) const { ANGLE_TRY(mImpl->getSamplePosition(context, index, xy)); return angle::Result::Continue; } bool Framebuffer::hasValidDepthStencil() const { return mState.getDepthStencilAttachment() != nullptr; } const gl::Offset &Framebuffer::getSurfaceTextureOffset() const { return mState.getSurfaceTextureOffset(); } void Framebuffer::setAttachment(const Context *context, GLenum type, GLenum binding, const ImageIndex &textureIndex, FramebufferAttachmentObject *resource) { setAttachment(context, type, binding, textureIndex, resource, FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex, false, FramebufferAttachment::kDefaultRenderToTextureSamples); } void Framebuffer::setAttachmentMultisample(const Context *context, GLenum type, GLenum binding, const ImageIndex &textureIndex, FramebufferAttachmentObject *resource, GLsizei samples) { setAttachment(context, type, binding, textureIndex, resource, FramebufferAttachment::kDefaultNumViews, FramebufferAttachment::kDefaultBaseViewIndex, false, samples); } void Framebuffer::setAttachment(const Context *context, GLenum type, GLenum binding, const ImageIndex &textureIndex, FramebufferAttachmentObject *resource, GLsizei numViews, GLuint baseViewIndex, bool isMultiview, GLsizei samplesIn) { GLsizei samples = samplesIn; // Match the sample count to the attachment's sample count. if (resource) { const InternalFormat *info = resource->getAttachmentFormat(binding, textureIndex).info; ASSERT(info); GLenum sizedInternalFormat = info->sizedInternalFormat; const TextureCaps &formatCaps = context->getTextureCaps().get(sizedInternalFormat); samples = formatCaps.getNearestSamples(samples); } // Context may be null in unit tests. if (!context || !context->isWebGL1()) { setAttachmentImpl(context, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); return; } switch (binding) { case GL_DEPTH_STENCIL: case GL_DEPTH_STENCIL_ATTACHMENT: mState.mWebGLDepthStencilAttachment.attach( context, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples, mState.mFramebufferSerial); break; case GL_DEPTH: case GL_DEPTH_ATTACHMENT: mState.mWebGLDepthAttachment.attach(context, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples, mState.mFramebufferSerial); break; case GL_STENCIL: case GL_STENCIL_ATTACHMENT: mState.mWebGLStencilAttachment.attach(context, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples, mState.mFramebufferSerial); break; default: setAttachmentImpl(context, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); return; } commitWebGL1DepthStencilIfConsistent(context, numViews, baseViewIndex, isMultiview, samples); } void Framebuffer::setAttachmentMultiview(const Context *context, GLenum type, GLenum binding, const ImageIndex &textureIndex, FramebufferAttachmentObject *resource, GLsizei numViews, GLint baseViewIndex) { setAttachment(context, type, binding, textureIndex, resource, numViews, baseViewIndex, true, FramebufferAttachment::kDefaultRenderToTextureSamples); } void Framebuffer::commitWebGL1DepthStencilIfConsistent(const Context *context, GLsizei numViews, GLuint baseViewIndex, bool isMultiview, GLsizei samples) { int count = 0; std::array attachments = {{&mState.mWebGLDepthStencilAttachment, &mState.mWebGLDepthAttachment, &mState.mWebGLStencilAttachment}}; for (FramebufferAttachment *attachment : attachments) { if (attachment->isAttached()) { count++; } } mState.mWebGLDepthStencilConsistent = (count <= 1); if (!mState.mWebGLDepthStencilConsistent) { // Inconsistent. return; } auto getImageIndexIfTextureAttachment = [](const FramebufferAttachment &attachment) { if (attachment.type() == GL_TEXTURE) { return attachment.getTextureImageIndex(); } else { return ImageIndex(); } }; if (mState.mWebGLDepthAttachment.isAttached()) { const auto &depth = mState.mWebGLDepthAttachment; setAttachmentImpl(context, depth.type(), GL_DEPTH_ATTACHMENT, getImageIndexIfTextureAttachment(depth), depth.getResource(), numViews, baseViewIndex, isMultiview, samples); setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews, baseViewIndex, isMultiview, samples); } else if (mState.mWebGLStencilAttachment.isAttached()) { const auto &stencil = mState.mWebGLStencilAttachment; setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews, baseViewIndex, isMultiview, samples); setAttachmentImpl(context, stencil.type(), GL_STENCIL_ATTACHMENT, getImageIndexIfTextureAttachment(stencil), stencil.getResource(), numViews, baseViewIndex, isMultiview, samples); } else if (mState.mWebGLDepthStencilAttachment.isAttached()) { const auto &depthStencil = mState.mWebGLDepthStencilAttachment; setAttachmentImpl(context, depthStencil.type(), GL_DEPTH_ATTACHMENT, getImageIndexIfTextureAttachment(depthStencil), depthStencil.getResource(), numViews, baseViewIndex, isMultiview, samples); setAttachmentImpl(context, depthStencil.type(), GL_STENCIL_ATTACHMENT, getImageIndexIfTextureAttachment(depthStencil), depthStencil.getResource(), numViews, baseViewIndex, isMultiview, samples); } else { setAttachmentImpl(context, GL_NONE, GL_DEPTH_ATTACHMENT, ImageIndex(), nullptr, numViews, baseViewIndex, isMultiview, samples); setAttachmentImpl(context, GL_NONE, GL_STENCIL_ATTACHMENT, ImageIndex(), nullptr, numViews, baseViewIndex, isMultiview, samples); } } void Framebuffer::setAttachmentImpl(const Context *context, GLenum type, GLenum binding, const ImageIndex &textureIndex, FramebufferAttachmentObject *resource, GLsizei numViews, GLuint baseViewIndex, bool isMultiview, GLsizei samples) { switch (binding) { case GL_DEPTH_STENCIL: case GL_DEPTH_STENCIL_ATTACHMENT: updateAttachment(context, &mState.mDepthAttachment, DIRTY_BIT_DEPTH_ATTACHMENT, &mDirtyDepthAttachmentBinding, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); updateAttachment(context, &mState.mStencilAttachment, DIRTY_BIT_STENCIL_ATTACHMENT, &mDirtyStencilAttachmentBinding, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); break; case GL_DEPTH: case GL_DEPTH_ATTACHMENT: updateAttachment(context, &mState.mDepthAttachment, DIRTY_BIT_DEPTH_ATTACHMENT, &mDirtyDepthAttachmentBinding, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); break; case GL_STENCIL: case GL_STENCIL_ATTACHMENT: updateAttachment(context, &mState.mStencilAttachment, DIRTY_BIT_STENCIL_ATTACHMENT, &mDirtyStencilAttachmentBinding, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); break; case GL_BACK: updateAttachment(context, &mState.mColorAttachments[0], DIRTY_BIT_COLOR_ATTACHMENT_0, &mDirtyColorAttachmentBindings[0], type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); mState.mColorAttachmentsMask.set(0); break; default: { size_t colorIndex = binding - GL_COLOR_ATTACHMENT0; ASSERT(colorIndex < mState.mColorAttachments.size()); size_t dirtyBit = DIRTY_BIT_COLOR_ATTACHMENT_0 + colorIndex; updateAttachment(context, &mState.mColorAttachments[colorIndex], dirtyBit, &mDirtyColorAttachmentBindings[colorIndex], type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples); if (!resource) { mFloat32ColorAttachmentBits.reset(colorIndex); mState.mColorAttachmentsMask.reset(colorIndex); } else { updateFloat32ColorAttachmentBits( colorIndex, resource->getAttachmentFormat(binding, textureIndex).info); mState.mColorAttachmentsMask.set(colorIndex); } bool enabled = (type != GL_NONE && getDrawBufferState(colorIndex) != GL_NONE); mState.mEnabledDrawBuffers.set(colorIndex, enabled); SetComponentTypeMask(getDrawbufferWriteType(colorIndex), colorIndex, &mState.mDrawBufferTypeMask); } break; } } void Framebuffer::updateAttachment(const Context *context, FramebufferAttachment *attachment, size_t dirtyBit, angle::ObserverBinding *onDirtyBinding, GLenum type, GLenum binding, const ImageIndex &textureIndex, FramebufferAttachmentObject *resource, GLsizei numViews, GLuint baseViewIndex, bool isMultiview, GLsizei samples) { attachment->attach(context, type, binding, textureIndex, resource, numViews, baseViewIndex, isMultiview, samples, mState.mFramebufferSerial); mDirtyBits.set(dirtyBit); mState.mResourceNeedsInit.set(dirtyBit, attachment->initState() == InitState::MayNeedInit); onDirtyBinding->bind(resource); invalidateCompletenessCache(); } void Framebuffer::resetAttachment(const Context *context, GLenum binding) { setAttachment(context, GL_NONE, binding, ImageIndex(), nullptr); } void Framebuffer::setWriteControlMode(SrgbWriteControlMode srgbWriteControlMode) { if (srgbWriteControlMode != mState.getWriteControlMode()) { mState.mSrgbWriteControlMode = srgbWriteControlMode; mDirtyBits.set(DIRTY_BIT_FRAMEBUFFER_SRGB_WRITE_CONTROL_MODE); } } angle::Result Framebuffer::syncState(const Context *context, GLenum framebufferBinding, Command command) const { if (mDirtyBits.any()) { mDirtyBitsGuard = mDirtyBits; ANGLE_TRY(mImpl->syncState(context, framebufferBinding, mDirtyBits, command)); mDirtyBits.reset(); mDirtyBitsGuard.reset(); } return angle::Result::Continue; } void Framebuffer::onSubjectStateChange(angle::SubjectIndex index, angle::SubjectMessage message) { if (message != angle::SubjectMessage::SubjectChanged) { // This can be triggered by SubImage calls for Textures. if (message == angle::SubjectMessage::ContentsChanged) { mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + index); onStateChange(angle::SubjectMessage::DirtyBitsFlagged); return; } // Swapchain changes should only result in color buffer changes. if (message == angle::SubjectMessage::SwapchainImageChanged) { if (index < DIRTY_BIT_COLOR_ATTACHMENT_MAX) { mDirtyBits.set(DIRTY_BIT_COLOR_BUFFER_CONTENTS_0 + index); onStateChange(angle::SubjectMessage::DirtyBitsFlagged); } return; } ASSERT(message != angle::SubjectMessage::BindingChanged); // This can be triggered by external changes to the default framebuffer. if (message == angle::SubjectMessage::SurfaceChanged) { onStateChange(angle::SubjectMessage::SurfaceChanged); return; } // This can be triggered by freeing TextureStorage in D3D back-end. if (message == angle::SubjectMessage::StorageReleased) { mDirtyBits.set(index); invalidateCompletenessCache(); return; } // This can be triggered by the GL back-end TextureGL class. ASSERT(message == angle::SubjectMessage::DirtyBitsFlagged); return; } ASSERT(!mDirtyBitsGuard.valid() || mDirtyBitsGuard.value().test(index)); mDirtyBits.set(index); invalidateCompletenessCache(); FramebufferAttachment *attachment = getAttachmentFromSubjectIndex(index); // Mark the appropriate init flag. mState.mResourceNeedsInit.set(index, attachment->initState() == InitState::MayNeedInit); // Update mFloat32ColorAttachmentBits Cache if (index < DIRTY_BIT_COLOR_ATTACHMENT_MAX) { ASSERT(index != DIRTY_BIT_DEPTH_ATTACHMENT); ASSERT(index != DIRTY_BIT_STENCIL_ATTACHMENT); updateFloat32ColorAttachmentBits(index - DIRTY_BIT_COLOR_ATTACHMENT_0, attachment->getFormat().info); } } FramebufferAttachment *Framebuffer::getAttachmentFromSubjectIndex(angle::SubjectIndex index) { switch (index) { case DIRTY_BIT_DEPTH_ATTACHMENT: return &mState.mDepthAttachment; case DIRTY_BIT_STENCIL_ATTACHMENT: return &mState.mStencilAttachment; default: size_t colorIndex = (index - DIRTY_BIT_COLOR_ATTACHMENT_0); ASSERT(colorIndex < mState.mColorAttachments.size()); return &mState.mColorAttachments[colorIndex]; } } bool Framebuffer::formsRenderingFeedbackLoopWith(const Context *context) const { const State &glState = context->getState(); const ProgramExecutable *executable = glState.getLinkedProgramExecutable(context); // In some error cases there may be no bound program or executable. if (!executable) return false; const ActiveTextureMask &activeTextures = executable->getActiveSamplersMask(); const ActiveTextureTypeArray &textureTypes = executable->getActiveSamplerTypes(); for (size_t textureIndex : activeTextures) { unsigned int uintIndex = static_cast(textureIndex); Texture *texture = glState.getSamplerTexture(uintIndex, textureTypes[textureIndex]); const Sampler *sampler = glState.getSampler(uintIndex); if (texture && texture->isSamplerComplete(context, sampler) && texture->isBoundToFramebuffer(mState.mFramebufferSerial)) { // Check for level overlap. for (const FramebufferAttachment &attachment : mState.mColorAttachments) { if (AttachmentOverlapsWithTexture(attachment, texture, sampler)) { return true; } } if (AttachmentOverlapsWithTexture(mState.mDepthAttachment, texture, sampler)) { return true; } if (AttachmentOverlapsWithTexture(mState.mStencilAttachment, texture, sampler)) { return true; } } } return false; } bool Framebuffer::formsCopyingFeedbackLoopWith(TextureID copyTextureID, GLint copyTextureLevel, GLint copyTextureLayer) const { if (mState.isDefault()) { // It seems impossible to form a texture copying feedback loop with the default FBO. return false; } const FramebufferAttachment *readAttachment = getReadColorAttachment(); ASSERT(readAttachment); if (readAttachment->isTextureWithId(copyTextureID)) { const auto &imageIndex = readAttachment->getTextureImageIndex(); if (imageIndex.getLevelIndex() == copyTextureLevel) { // Check 3D/Array texture layers. return !imageIndex.hasLayer() || copyTextureLayer == ImageIndex::kEntireLevel || imageIndex.getLayerIndex() == copyTextureLayer; } } return false; } GLint Framebuffer::getDefaultWidth() const { return mState.getDefaultWidth(); } GLint Framebuffer::getDefaultHeight() const { return mState.getDefaultHeight(); } GLint Framebuffer::getDefaultSamples() const { return mState.getDefaultSamples(); } bool Framebuffer::getDefaultFixedSampleLocations() const { return mState.getDefaultFixedSampleLocations(); } GLint Framebuffer::getDefaultLayers() const { return mState.getDefaultLayers(); } bool Framebuffer::getFlipY() const { return mState.getFlipY(); } 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); } void Framebuffer::setFlipY(bool flipY) { mState.mFlipY = flipY; mDirtyBits.set(DIRTY_BIT_FLIP_Y); invalidateCompletenessCache(); } GLsizei Framebuffer::getNumViews() const { return mState.getNumViews(); } GLint Framebuffer::getBaseViewIndex() const { return mState.getBaseViewIndex(); } bool Framebuffer::isMultiview() const { return mState.isMultiview(); } bool Framebuffer::readDisallowedByMultiview() const { return (mState.isMultiview() && mState.getNumViews() > 1); } angle::Result Framebuffer::ensureClearAttachmentsInitialized(const Context *context, GLbitfield mask) { const auto &glState = context->getState(); if (!context->isRobustResourceInitEnabled() || glState.isRasterizerDiscardEnabled()) { return angle::Result::Continue; } const DepthStencilState &depthStencil = glState.getDepthStencilState(); bool color = (mask & GL_COLOR_BUFFER_BIT) != 0 && !glState.allActiveDrawBufferChannelsMasked(); bool depth = (mask & GL_DEPTH_BUFFER_BIT) != 0 && !depthStencil.isDepthMaskedOut(); bool stencil = (mask & GL_STENCIL_BUFFER_BIT) != 0 && !depthStencil.isStencilMaskedOut(); if (!color && !depth && !stencil) { return angle::Result::Continue; } if (partialClearNeedsInit(context, color, depth, stencil)) { ANGLE_TRY(ensureDrawAttachmentsInitialized(context)); } // If the impl encounters an error during a a full (non-partial) clear, the attachments will // still be marked initialized. This simplifies design, allowing this method to be called before // the clear. markDrawAttachmentsInitialized(color, depth, stencil); return angle::Result::Continue; } angle::Result Framebuffer::ensureClearBufferAttachmentsInitialized(const Context *context, GLenum buffer, GLint drawbuffer) { if (!context->isRobustResourceInitEnabled() || context->getState().isRasterizerDiscardEnabled() || context->isClearBufferMaskedOut(buffer, drawbuffer)) { return angle::Result::Continue; } if (partialBufferClearNeedsInit(context, buffer)) { ANGLE_TRY(ensureBufferInitialized(context, buffer, drawbuffer)); } // If the impl encounters an error during a a full (non-partial) clear, the attachments will // still be marked initialized. This simplifies design, allowing this method to be called before // the clear. markBufferInitialized(buffer, drawbuffer); return angle::Result::Continue; } angle::Result Framebuffer::ensureDrawAttachmentsInitialized(const Context *context) { if (!context->isRobustResourceInitEnabled()) { return angle::Result::Continue; } // Note: we don't actually filter by the draw attachment enum. Just init everything. for (size_t bit : mState.mResourceNeedsInit) { switch (bit) { case DIRTY_BIT_DEPTH_ATTACHMENT: ANGLE_TRY(InitAttachment(context, &mState.mDepthAttachment)); break; case DIRTY_BIT_STENCIL_ATTACHMENT: ANGLE_TRY(InitAttachment(context, &mState.mStencilAttachment)); break; default: ANGLE_TRY(InitAttachment(context, &mState.mColorAttachments[bit])); break; } } mState.mResourceNeedsInit.reset(); return angle::Result::Continue; } angle::Result Framebuffer::ensureReadAttachmentsInitialized(const Context *context) { ASSERT(context->isRobustResourceInitEnabled()); if (mState.mResourceNeedsInit.none()) { return angle::Result::Continue; } if (mState.mReadBufferState != GL_NONE) { if (isDefault()) { if (!mState.mDefaultFramebufferReadAttachmentInitialized) { ANGLE_TRY(InitAttachment(context, &mState.mDefaultFramebufferReadAttachment)); mState.mDefaultFramebufferReadAttachmentInitialized = true; } } else { size_t readIndex = mState.getReadIndex(); if (mState.mResourceNeedsInit[readIndex]) { ANGLE_TRY(InitAttachment(context, &mState.mColorAttachments[readIndex])); mState.mResourceNeedsInit.reset(readIndex); } } } // Conservatively init depth since it can be read by BlitFramebuffer. if (hasDepth()) { if (mState.mResourceNeedsInit[DIRTY_BIT_DEPTH_ATTACHMENT]) { ANGLE_TRY(InitAttachment(context, &mState.mDepthAttachment)); mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT); } } // Conservatively init stencil since it can be read by BlitFramebuffer. if (hasStencil()) { if (mState.mResourceNeedsInit[DIRTY_BIT_STENCIL_ATTACHMENT]) { ANGLE_TRY(InitAttachment(context, &mState.mStencilAttachment)); mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT); } } return angle::Result::Continue; } void Framebuffer::markDrawAttachmentsInitialized(bool color, bool depth, bool stencil) { // Mark attachments as initialized. if (color) { for (auto colorIndex : mState.mEnabledDrawBuffers) { auto &colorAttachment = mState.mColorAttachments[colorIndex]; ASSERT(colorAttachment.isAttached()); colorAttachment.setInitState(InitState::Initialized); mState.mResourceNeedsInit.reset(colorIndex); } } if (depth && mState.mDepthAttachment.isAttached()) { mState.mDepthAttachment.setInitState(InitState::Initialized); mState.mResourceNeedsInit.reset(DIRTY_BIT_DEPTH_ATTACHMENT); } if (stencil && mState.mStencilAttachment.isAttached()) { mState.mStencilAttachment.setInitState(InitState::Initialized); mState.mResourceNeedsInit.reset(DIRTY_BIT_STENCIL_ATTACHMENT); } } void Framebuffer::markBufferInitialized(GLenum bufferType, GLint bufferIndex) { switch (bufferType) { case GL_COLOR: { ASSERT(bufferIndex < static_cast(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; } } PixelLocalStorage &Framebuffer::getPixelLocalStorage(const Context *context) { if (!mPixelLocalStorage) { mPixelLocalStorage = PixelLocalStorage::Make(context); } return *mPixelLocalStorage.get(); } std::unique_ptr Framebuffer::detachPixelLocalStorage() { return std::move(mPixelLocalStorage); } } // namespace gl