/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "WebGLFramebuffer.h" // You know it's going to be fun when these two show up: #include #include #include "GLBlitHelper.h" #include "GLContext.h" #include "GLScreenBuffer.h" #include "MozFramebuffer.h" #include "mozilla/dom/WebGLRenderingContextBinding.h" #include "mozilla/IntegerRange.h" #include "nsPrintfCString.h" #include "WebGLContext.h" #include "WebGLContextUtils.h" #include "WebGLExtensions.h" #include "WebGLFormats.h" #include "WebGLRenderbuffer.h" #include "WebGLTexture.h" namespace mozilla { static bool ShouldDeferAttachment(const WebGLContext* const webgl, const GLenum attachPoint) { if (webgl->IsWebGL2()) return false; switch (attachPoint) { case LOCAL_GL_DEPTH_ATTACHMENT: case LOCAL_GL_STENCIL_ATTACHMENT: case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: return true; default: return false; } } WebGLFBAttachPoint::WebGLFBAttachPoint() = default; WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFBAttachPoint&) = default; WebGLFBAttachPoint::WebGLFBAttachPoint(const WebGLContext* const webgl, const GLenum attachmentPoint) : mAttachmentPoint(attachmentPoint), mDeferAttachment(ShouldDeferAttachment(webgl, mAttachmentPoint)) {} WebGLFBAttachPoint::~WebGLFBAttachPoint() { MOZ_ASSERT(!mRenderbufferPtr); MOZ_ASSERT(!mTexturePtr); } void WebGLFBAttachPoint::Clear() { Set(nullptr, {}); } void WebGLFBAttachPoint::Set(gl::GLContext* const gl, const webgl::FbAttachInfo& toAttach) { mRenderbufferPtr = toAttach.rb; mTexturePtr = toAttach.tex; mTexImageLayer = AssertedCast(toAttach.zLayer); mTexImageZLayerCount = AssertedCast(toAttach.zLayerCount); mTexImageLevel = AssertedCast(toAttach.mipLevel); mIsMultiview = toAttach.isMultiview; if (gl && !mDeferAttachment) { DoAttachment(gl); } } const webgl::ImageInfo* WebGLFBAttachPoint::GetImageInfo() const { if (mTexturePtr) { const auto target = Texture()->Target(); uint8_t face = 0; if (target == LOCAL_GL_TEXTURE_CUBE_MAP) { face = Layer() % 6; } return &mTexturePtr->ImageInfoAtFace(face, mTexImageLevel); } if (mRenderbufferPtr) return &mRenderbufferPtr->ImageInfo(); return nullptr; } bool WebGLFBAttachPoint::IsComplete(WebGLContext* webgl, nsCString* const out_info) const { MOZ_ASSERT(HasAttachment()); const auto fnWriteErrorInfo = [&](const char* const text) { WebGLContext::EnumName(mAttachmentPoint, out_info); out_info->AppendLiteral(": "); out_info->AppendASCII(text); }; const auto& imageInfo = *GetImageInfo(); if (!imageInfo.mWidth || !imageInfo.mHeight) { fnWriteErrorInfo("Attachment has no width or height."); return false; } MOZ_ASSERT(imageInfo.IsDefined()); const auto& tex = Texture(); if (tex) { // ES 3.0 spec, pg 213 has giant blocks of text that bake down to requiring // that attached *non-immutable* tex images are within the valid mip-levels // of the texture. We still need to check immutable textures though, because // checking completeness is also when we zero invalidated/no-data tex // images. const auto attachedMipLevel = MipLevel(); const bool withinValidMipLevels = [&]() { const bool ensureInit = false; const auto texCompleteness = tex->CalcCompletenessInfo(ensureInit); if (!texCompleteness) return false; // OOM if (tex->Immutable()) { // Immutable textures can attach a level that's not valid for sampling. // It still has to exist though! return attachedMipLevel < tex->ImmutableLevelCount(); } // Base level must be complete. if (!texCompleteness->levels) return false; const auto baseLevel = tex->Es3_level_base(); if (attachedMipLevel == baseLevel) return true; // If not base level, must be mip-complete and within mips. if (!texCompleteness->mipmapComplete) return false; const auto maxLevel = baseLevel + texCompleteness->levels - 1; return baseLevel <= attachedMipLevel && attachedMipLevel <= maxLevel; }(); if (!withinValidMipLevels) { fnWriteErrorInfo("Attached mip level is invalid for texture."); return false; } const auto& levelInfo = tex->ImageInfoAtFace(0, attachedMipLevel); const auto faceDepth = levelInfo.mDepth * tex->FaceCount(); const bool withinValidZLayers = Layer() + ZLayerCount() - 1 < faceDepth; if (!withinValidZLayers) { fnWriteErrorInfo("Attached z layer is invalid for texture."); return false; } } const auto& formatUsage = imageInfo.mFormat; if (!formatUsage->IsRenderable()) { const auto info = nsPrintfCString( "Attachment has an effective format of %s," " which is not renderable.", formatUsage->format->name); fnWriteErrorInfo(info.BeginReading()); return false; } if (!formatUsage->IsExplicitlyRenderable()) { webgl->WarnIfImplicit(formatUsage->GetExtensionID()); } const auto format = formatUsage->format; bool hasRequiredBits; switch (mAttachmentPoint) { case LOCAL_GL_DEPTH_ATTACHMENT: hasRequiredBits = format->d; break; case LOCAL_GL_STENCIL_ATTACHMENT: hasRequiredBits = format->s; break; case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: MOZ_ASSERT(!webgl->IsWebGL2()); hasRequiredBits = (format->d && format->s); break; default: MOZ_ASSERT(mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0); hasRequiredBits = format->IsColorFormat(); break; } if (!hasRequiredBits) { fnWriteErrorInfo( "Attachment's format is missing required color/depth/stencil" " bits."); return false; } if (!webgl->IsWebGL2()) { bool hasSurplusPlanes = false; switch (mAttachmentPoint) { case LOCAL_GL_DEPTH_ATTACHMENT: hasSurplusPlanes = format->s; break; case LOCAL_GL_STENCIL_ATTACHMENT: hasSurplusPlanes = format->d; break; } if (hasSurplusPlanes) { fnWriteErrorInfo( "Attachment has depth or stencil bits when it shouldn't."); return false; } } return true; } void WebGLFBAttachPoint::DoAttachment(gl::GLContext* const gl) const { if (Renderbuffer()) { Renderbuffer()->DoFramebufferRenderbuffer(mAttachmentPoint); return; } if (!Texture()) { MOZ_ASSERT(mAttachmentPoint != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); // WebGL 2 doesn't have a real attachment for this, and WebGL 1 is defered // and only DoAttachment if HasAttachment. gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, LOCAL_GL_RENDERBUFFER, 0); return; } const auto& texName = Texture()->mGLName; switch (Texture()->Target().get()) { case LOCAL_GL_TEXTURE_2D: case LOCAL_GL_TEXTURE_CUBE_MAP: { TexImageTarget imageTarget = LOCAL_GL_TEXTURE_2D; if (Texture()->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) { imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + Layer(); } if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, imageTarget.get(), texName, MipLevel()); gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, imageTarget.get(), texName, MipLevel()); } else { gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, imageTarget.get(), texName, MipLevel()); } break; } case LOCAL_GL_TEXTURE_2D_ARRAY: case LOCAL_GL_TEXTURE_3D: if (ZLayerCount() != 1) { gl->fFramebufferTextureMultiview(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, texName, MipLevel(), Layer(), ZLayerCount()); } else { gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, texName, MipLevel(), Layer()); } break; } } Maybe WebGLFBAttachPoint::GetParameter(WebGLContext* webgl, GLenum attachment, GLenum pname) const { if (!HasAttachment()) { // Divergent between GLES 3 and 2. // GLES 2.0.25 p127: // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, then // querying any other pname will generate INVALID_ENUM." // GLES 3.0.4 p240: // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, no // framebuffer is bound to target. In this case querying pname // FRAMEBUFFER_ATTACHMENT_OBJECT_NAME will return zero, and all other // queries will generate an INVALID_OPERATION error." switch (pname) { case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: return Some(LOCAL_GL_NONE); case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: if (webgl->IsWebGL2()) return Nothing(); break; default: break; } nsCString attachmentName; WebGLContext::EnumName(attachment, &attachmentName); if (webgl->IsWebGL2()) { webgl->ErrorInvalidOperation("No attachment at %s.", attachmentName.BeginReading()); } else { webgl->ErrorInvalidEnum("No attachment at %s.", attachmentName.BeginReading()); } return Nothing(); } bool isPNameValid = false; switch (pname) { case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: return Some(mTexturePtr ? LOCAL_GL_TEXTURE : LOCAL_GL_RENDERBUFFER); ////// case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: if (mTexturePtr) return Some(AssertedCast(MipLevel())); break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE: if (mTexturePtr) { GLenum face = 0; if (mTexturePtr->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) { face = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + Layer(); } return Some(face); } break; ////// case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: if (webgl->IsWebGL2()) { return Some(AssertedCast(Layer())); } break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_BASE_VIEW_INDEX_OVR: if (webgl->IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) { return Some(AssertedCast(Layer())); } break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_NUM_VIEWS_OVR: if (webgl->IsExtensionEnabled(WebGLExtensionID::OVR_multiview2)) { return Some(AssertedCast(ZLayerCount())); } break; ////// case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: isPNameValid = webgl->IsWebGL2(); break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: isPNameValid = (webgl->IsWebGL2() || webgl->IsExtensionEnabled(WebGLExtensionID::EXT_sRGB)); break; } if (!isPNameValid) { webgl->ErrorInvalidEnum("Invalid pname: 0x%04x", pname); return Nothing(); } const auto& imageInfo = *GetImageInfo(); const auto& usage = imageInfo.mFormat; if (!usage) { if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) return Some(LOCAL_GL_LINEAR); return Nothing(); } auto format = usage->format; GLint ret = 0; switch (pname) { case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE: ret = format->r; break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE: ret = format->g; break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE: ret = format->b; break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE: ret = format->a; break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE: ret = format->d; break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE: ret = format->s; break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING: ret = (format->isSRGB ? LOCAL_GL_SRGB : LOCAL_GL_LINEAR); break; case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE: MOZ_ASSERT(attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); if (format->unsizedFormat == webgl::UnsizedFormat::DEPTH_STENCIL) { MOZ_ASSERT(attachment == LOCAL_GL_DEPTH_ATTACHMENT || attachment == LOCAL_GL_STENCIL_ATTACHMENT); if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) { switch (format->effectiveFormat) { case webgl::EffectiveFormat::DEPTH24_STENCIL8: format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT24); break; case webgl::EffectiveFormat::DEPTH32F_STENCIL8: format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT32F); break; default: MOZ_ASSERT(false, "no matched DS format"); break; } } else if (attachment == LOCAL_GL_STENCIL_ATTACHMENT) { switch (format->effectiveFormat) { case webgl::EffectiveFormat::DEPTH24_STENCIL8: case webgl::EffectiveFormat::DEPTH32F_STENCIL8: format = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8); break; default: MOZ_ASSERT(false, "no matched DS format"); break; } } } switch (format->componentType) { case webgl::ComponentType::Int: ret = LOCAL_GL_INT; break; case webgl::ComponentType::UInt: ret = LOCAL_GL_UNSIGNED_INT; break; case webgl::ComponentType::NormInt: ret = LOCAL_GL_SIGNED_NORMALIZED; break; case webgl::ComponentType::NormUInt: ret = LOCAL_GL_UNSIGNED_NORMALIZED; break; case webgl::ComponentType::Float: ret = LOCAL_GL_FLOAT; break; } break; default: MOZ_ASSERT(false, "Missing case."); break; } return Some(ret); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // WebGLFramebuffer WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo) : WebGLContextBoundObject(webgl), mGLName(fbo), mDepthAttachment(webgl, LOCAL_GL_DEPTH_ATTACHMENT), mStencilAttachment(webgl, LOCAL_GL_STENCIL_ATTACHMENT), mDepthStencilAttachment(webgl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { mAttachments.push_back(&mDepthAttachment); mAttachments.push_back(&mStencilAttachment); if (!webgl->IsWebGL2()) { // Only WebGL1 has a separate depth+stencil attachment point. mAttachments.push_back(&mDepthStencilAttachment); } size_t i = 0; for (auto& cur : mColorAttachments) { new (&cur) WebGLFBAttachPoint(webgl, LOCAL_GL_COLOR_ATTACHMENT0 + i); i++; mAttachments.push_back(&cur); } mColorDrawBuffers.push_back(&mColorAttachments[0]); mColorReadBuffer = &mColorAttachments[0]; } WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, UniquePtr fbo) : WebGLContextBoundObject(webgl), mGLName(fbo->mFB), mOpaque(std::move(fbo)), mColorReadBuffer(nullptr) { // Opaque Framebuffer is guaranteed to be complete at this point. // Cache the Completeness info. CompletenessInfo info; info.width = mOpaque->mSize.width; info.height = mOpaque->mSize.height; info.zLayerCount = 1; info.isMultiview = false; mCompletenessInfo = Some(std::move(info)); } WebGLFramebuffer::~WebGLFramebuffer() { InvalidateCaches(); mDepthAttachment.Clear(); mStencilAttachment.Clear(); mDepthStencilAttachment.Clear(); for (auto& cur : mColorAttachments) { cur.Clear(); } if (!mContext) return; // If opaque, fDeleteFramebuffers is called in the destructor of // MozFramebuffer. if (!mOpaque) { mContext->gl->fDeleteFramebuffers(1, &mGLName); } } //// Maybe WebGLFramebuffer::GetColorAttachPoint( GLenum attachPoint) { if (attachPoint == LOCAL_GL_NONE) return Some(nullptr); if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0) return Nothing(); const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0; MOZ_ASSERT(mContext->Limits().maxColorDrawBuffers <= webgl::kMaxDrawBuffers); if (colorId >= mContext->MaxValidDrawBuffers()) return Nothing(); return Some(&mColorAttachments[colorId]); } Maybe WebGLFramebuffer::GetAttachPoint( GLenum attachPoint) { switch (attachPoint) { case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: return Some(&mDepthStencilAttachment); case LOCAL_GL_DEPTH_ATTACHMENT: return Some(&mDepthAttachment); case LOCAL_GL_STENCIL_ATTACHMENT: return Some(&mStencilAttachment); default: return GetColorAttachPoint(attachPoint); } } void WebGLFramebuffer::DetachTexture(const WebGLTexture* tex) { for (const auto& attach : mAttachments) { if (attach->Texture() == tex) { attach->Clear(); } } InvalidateCaches(); } void WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb) { for (const auto& attach : mAttachments) { if (attach->Renderbuffer() == rb) { attach->Clear(); } } InvalidateCaches(); } //////////////////////////////////////////////////////////////////////////////// // Completeness bool WebGLFramebuffer::HasDuplicateAttachments() const { std::set uniqueAttachSet; for (const auto& attach : mColorAttachments) { if (!attach.HasAttachment()) continue; const WebGLFBAttachPoint::Ordered ordered(attach); const bool didInsert = uniqueAttachSet.insert(ordered).second; if (!didInsert) return true; } return false; } bool WebGLFramebuffer::HasDefinedAttachments() const { bool hasAttachments = false; for (const auto& attach : mAttachments) { hasAttachments |= attach->HasAttachment(); } return hasAttachments; } bool WebGLFramebuffer::HasIncompleteAttachments( nsCString* const out_info) const { bool hasIncomplete = false; for (const auto& cur : mAttachments) { if (!cur->HasAttachment()) continue; // Not defined, so can't count as incomplete. hasIncomplete |= !cur->IsComplete(mContext, out_info); } return hasIncomplete; } bool WebGLFramebuffer::AllImageRectsMatch() const { MOZ_ASSERT(HasDefinedAttachments()); DebugOnly fbStatusInfo; MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo)); bool needsInit = true; uint32_t width = 0; uint32_t height = 0; bool hasMismatch = false; for (const auto& attach : mAttachments) { const auto& imageInfo = attach->GetImageInfo(); if (!imageInfo) continue; const auto& curWidth = imageInfo->mWidth; const auto& curHeight = imageInfo->mHeight; if (needsInit) { needsInit = false; width = curWidth; height = curHeight; continue; } hasMismatch |= (curWidth != width || curHeight != height); } return !hasMismatch; } bool WebGLFramebuffer::AllImageSamplesMatch() const { MOZ_ASSERT(HasDefinedAttachments()); DebugOnly fbStatusInfo; MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo)); bool needsInit = true; uint32_t samples = 0; bool hasMismatch = false; for (const auto& attach : mAttachments) { const auto& imageInfo = attach->GetImageInfo(); if (!imageInfo) continue; const auto& curSamples = imageInfo->mSamples; if (needsInit) { needsInit = false; samples = curSamples; continue; } hasMismatch |= (curSamples != samples); }; return !hasMismatch; } FBStatus WebGLFramebuffer::PrecheckFramebufferStatus( nsCString* const out_info) const { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); if (!HasDefinedAttachments()) return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // No // attachments if (HasIncompleteAttachments(out_info)) return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT; if (!AllImageRectsMatch()) return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; // Inconsistent sizes if (!AllImageSamplesMatch()) return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; // Inconsistent samples if (HasDuplicateAttachments()) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; if (mContext->IsWebGL2()) { MOZ_ASSERT(!mDepthStencilAttachment.HasAttachment()); if (mDepthAttachment.HasAttachment() && mStencilAttachment.HasAttachment()) { if (!mDepthAttachment.IsEquivalentForFeedback(mStencilAttachment)) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; } } else { const auto depthOrStencilCount = int(mDepthAttachment.HasAttachment()) + int(mStencilAttachment.HasAttachment()) + int(mDepthStencilAttachment.HasAttachment()); if (depthOrStencilCount > 1) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; } { const WebGLFBAttachPoint* example = nullptr; for (const auto& x : mAttachments) { if (!x->HasAttachment()) continue; if (!example) { example = x; continue; } if (x->ZLayerCount() != example->ZLayerCount()) { return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_VIEW_TARGETS_OVR; } } } return LOCAL_GL_FRAMEBUFFER_COMPLETE; } //////////////////////////////////////// // Validation bool WebGLFramebuffer::ValidateAndInitAttachments( const GLenum incompleteFbError) const { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); const auto fbStatus = CheckFramebufferStatus(); if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE) return true; mContext->GenerateError(incompleteFbError, "Framebuffer must be complete."); return false; } bool WebGLFramebuffer::ValidateClearBufferType( GLenum buffer, uint32_t drawBuffer, const webgl::AttribBaseType funcType) const { if (buffer != LOCAL_GL_COLOR) return true; const auto& attach = mColorAttachments[drawBuffer]; const auto& imageInfo = attach.GetImageInfo(); if (!imageInfo) return true; if (!count(mColorDrawBuffers.begin(), mColorDrawBuffers.end(), &attach)) return true; // DRAW_BUFFERi set to NONE. auto attachType = webgl::AttribBaseType::Float; switch (imageInfo->mFormat->format->componentType) { case webgl::ComponentType::Int: attachType = webgl::AttribBaseType::Int; break; case webgl::ComponentType::UInt: attachType = webgl::AttribBaseType::Uint; break; default: break; } if (attachType != funcType) { mContext->ErrorInvalidOperation( "This attachment is of type %s, but" " this function is of type %s.", ToString(attachType), ToString(funcType)); return false; } return true; } bool WebGLFramebuffer::ValidateForColorRead( const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width, uint32_t* const out_height) const { if (!mColorReadBuffer) { mContext->ErrorInvalidOperation("READ_BUFFER must not be NONE."); return false; } if (mColorReadBuffer->ZLayerCount() > 1) { mContext->GenerateError(LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION, "The READ_BUFFER attachment has multiple views."); return false; } const auto& imageInfo = mColorReadBuffer->GetImageInfo(); if (!imageInfo) { mContext->ErrorInvalidOperation( "The READ_BUFFER attachment is not defined."); return false; } if (imageInfo->mSamples) { mContext->ErrorInvalidOperation( "The READ_BUFFER attachment is multisampled."); return false; } *out_format = imageInfo->mFormat; *out_width = imageInfo->mWidth; *out_height = imageInfo->mHeight; return true; } //////////////////////////////////////////////////////////////////////////////// // Resolution and caching void WebGLFramebuffer::DoDeferredAttachments() const { if (mContext->IsWebGL2()) return; const auto& gl = mContext->gl; gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_RENDERBUFFER, 0); gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_RENDERBUFFER, 0); const auto fn = [&](const WebGLFBAttachPoint& attach) { MOZ_ASSERT(attach.mDeferAttachment); if (attach.HasAttachment()) { attach.DoAttachment(gl); } }; // Only one of these will have an attachment. fn(mDepthAttachment); fn(mStencilAttachment); fn(mDepthStencilAttachment); } void WebGLFramebuffer::ResolveAttachmentData() const { // GLES 3.0.5 p188: // The result of clearing integer color buffers with `Clear` is undefined. // Two different approaches: // On WebGL 2, we have glClearBuffer, and *must* use it for integer buffers, // so let's just use it for all the buffers. One WebGL 1, we might not have // glClearBuffer, // WebGL 1 is easier, because we can just call glClear, possibly with // glDrawBuffers. const auto& gl = mContext->gl; const webgl::ScopedPrepForResourceClear scopedPrep(*mContext); if (mContext->IsWebGL2()) { const uint32_t uiZeros[4] = {}; const int32_t iZeros[4] = {}; const float fZeros[4] = {}; const float fOne[] = {1.0f}; for (const auto& cur : mAttachments) { const auto& imageInfo = cur->GetImageInfo(); if (!imageInfo || !imageInfo->mUninitializedSlices) continue; // Nothing attached, or already has data. const auto fnClearBuffer = [&]() { const auto& format = imageInfo->mFormat->format; MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(uiZeros)); MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(iZeros)); MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(fZeros)); switch (cur->mAttachmentPoint) { case LOCAL_GL_DEPTH_ATTACHMENT: gl->fClearBufferfv(LOCAL_GL_DEPTH, 0, fOne); break; case LOCAL_GL_STENCIL_ATTACHMENT: gl->fClearBufferiv(LOCAL_GL_STENCIL, 0, iZeros); break; default: MOZ_ASSERT(cur->mAttachmentPoint != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT); const uint32_t drawBuffer = cur->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; MOZ_ASSERT(drawBuffer <= 100); switch (format->componentType) { case webgl::ComponentType::Int: gl->fClearBufferiv(LOCAL_GL_COLOR, drawBuffer, iZeros); break; case webgl::ComponentType::UInt: gl->fClearBufferuiv(LOCAL_GL_COLOR, drawBuffer, uiZeros); break; default: gl->fClearBufferfv(LOCAL_GL_COLOR, drawBuffer, fZeros); break; } } }; if (imageInfo->mDepth > 1) { const auto& tex = cur->Texture(); const gl::ScopedFramebuffer scopedFB(gl); const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB()); for (const auto z : IntegerRange(imageInfo->mDepth)) { if ((*imageInfo->mUninitializedSlices)[z]) { gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, cur->mAttachmentPoint, tex->mGLName, cur->MipLevel(), z); fnClearBuffer(); } } } else { fnClearBuffer(); } imageInfo->mUninitializedSlices = Nothing(); } return; } uint32_t clearBits = 0; std::vector drawBufferForClear; const auto fnGather = [&](const WebGLFBAttachPoint& attach, const uint32_t attachClearBits) { const auto& imageInfo = attach.GetImageInfo(); if (!imageInfo || !imageInfo->mUninitializedSlices) return false; clearBits |= attachClearBits; imageInfo->mUninitializedSlices = Nothing(); // Just mark it now. return true; }; ////// for (const auto& cur : mColorAttachments) { if (fnGather(cur, LOCAL_GL_COLOR_BUFFER_BIT)) { const uint32_t id = cur.mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; MOZ_ASSERT(id <= 100); drawBufferForClear.resize(id + 1); // Pads with zeros! drawBufferForClear[id] = cur.mAttachmentPoint; } } (void)fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT); (void)fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT); (void)fnGather(mDepthStencilAttachment, LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT); ////// if (!clearBits) return; if (gl->IsSupported(gl::GLFeature::draw_buffers)) { gl->fDrawBuffers(drawBufferForClear.size(), drawBufferForClear.data()); } gl->fClear(clearBits); RefreshDrawBuffers(); } WebGLFramebuffer::CompletenessInfo::~CompletenessInfo() { if (!this->fb) return; const auto& fb = *this->fb; const auto& webgl = fb.mContext; fb.mNumFBStatusInvals++; if (fb.mNumFBStatusInvals > webgl->mMaxAcceptableFBStatusInvals) { webgl->GeneratePerfWarning( "FB was invalidated after being complete %u" " times. [webgl.perf.max-acceptable-fb-status-invals]", uint32_t(fb.mNumFBStatusInvals)); } } //////////////////////////////////////////////////////////////////////////////// // Entrypoints FBStatus WebGLFramebuffer::CheckFramebufferStatus() const { if (MOZ_UNLIKELY(mOpaque && !mInOpaqueRAF)) { // Opaque Framebuffers are considered incomplete outside of a RAF. return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; } if (mCompletenessInfo) return LOCAL_GL_FRAMEBUFFER_COMPLETE; // Ok, let's try to resolve it! nsCString statusInfo; FBStatus ret = PrecheckFramebufferStatus(&statusInfo); do { if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) break; // Looks good on our end. Let's ask the driver. gl::GLContext* const gl = mContext->gl; const ScopedFBRebinder autoFB(mContext); gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName); //// DoDeferredAttachments(); RefreshDrawBuffers(); RefreshReadBuffer(); ret = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER); //// if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) { const nsPrintfCString text("Bad status according to the driver: 0x%04x", ret.get()); statusInfo = text; break; } ResolveAttachmentData(); // Sweet, let's cache that. auto info = CompletenessInfo{this}; mCompletenessInfo.ResetInvalidators({}); mCompletenessInfo.AddInvalidator(*this); const auto fnIsFloat32 = [](const webgl::FormatInfo& info) { if (info.componentType != webgl::ComponentType::Float) return false; return info.r == 32; }; for (const auto& cur : mAttachments) { const auto& tex = cur->Texture(); const auto& rb = cur->Renderbuffer(); if (tex) { mCompletenessInfo.AddInvalidator(*tex); info.texAttachments.push_back(cur); } else if (rb) { mCompletenessInfo.AddInvalidator(*rb); } else { continue; } const auto& imageInfo = cur->GetImageInfo(); MOZ_ASSERT(imageInfo); const auto maybeColorId = cur->ColorAttachmentId(); if (maybeColorId) { const auto id = *maybeColorId; info.hasAttachment[id] = true; info.isAttachmentF32[id] = fnIsFloat32(*imageInfo->mFormat->format); } info.width = imageInfo->mWidth; info.height = imageInfo->mHeight; info.zLayerCount = cur->ZLayerCount(); info.isMultiview = cur->IsMultiview(); } MOZ_ASSERT(info.width && info.height); mCompletenessInfo = Some(std::move(info)); info.fb = nullptr; // Don't trigger the invalidation warning. return LOCAL_GL_FRAMEBUFFER_COMPLETE; } while (false); MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE); mContext->GenerateWarning("Framebuffer not complete. (status: 0x%04x) %s", ret.get(), statusInfo.BeginReading()); return ret; } //// void WebGLFramebuffer::RefreshDrawBuffers() const { const auto& gl = mContext->gl; if (!gl->IsSupported(gl::GLFeature::draw_buffers)) return; // Prior to GL4.1, having a no-image FB attachment that's selected by // DrawBuffers yields a framebuffer status of // FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER. We could workaround this only on // affected versions, but it's easier be unconditional. std::vector driverBuffers(mContext->Limits().maxColorDrawBuffers, LOCAL_GL_NONE); for (const auto& attach : mColorDrawBuffers) { if (attach->HasAttachment()) { const uint32_t index = attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; driverBuffers[index] = attach->mAttachmentPoint; } } gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, mGLName); gl->fDrawBuffers(driverBuffers.size(), driverBuffers.data()); } void WebGLFramebuffer::RefreshReadBuffer() const { const auto& gl = mContext->gl; if (!gl->IsSupported(gl::GLFeature::read_buffer)) return; // Prior to GL4.1, having a no-image FB attachment that's selected by // ReadBuffer yields a framebuffer status of // FRAMEBUFFER_INCOMPLETE_READ_BUFFER. We could workaround this only on // affected versions, but it's easier be unconditional. GLenum driverBuffer = LOCAL_GL_NONE; if (mColorReadBuffer && mColorReadBuffer->HasAttachment()) { driverBuffer = mColorReadBuffer->mAttachmentPoint; } gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, mGLName); gl->fReadBuffer(driverBuffer); } //// void WebGLFramebuffer::DrawBuffers(const std::vector& buffers) { if (buffers.size() > mContext->MaxValidDrawBuffers()) { // "An INVALID_VALUE error is generated if `n` is greater than // MAX_DRAW_BUFFERS." mContext->ErrorInvalidValue( "`buffers` must have a length <=" " MAX_DRAW_BUFFERS."); return; } std::vector newColorDrawBuffers; newColorDrawBuffers.reserve(buffers.size()); mDrawBufferEnabled.reset(); for (const auto i : IntegerRange(buffers.size())) { // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed // in bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of // order, BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to // the value of MAX_COLOR_ATTACHMENTS, will generate the error // INVALID_OPERATION. // WEBGL_draw_buffers: // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater // than or equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter." This // means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't // be larger than MaxColorAttachments. const auto& cur = buffers[i]; if (cur == LOCAL_GL_COLOR_ATTACHMENT0 + i) { const auto& attach = mColorAttachments[i]; newColorDrawBuffers.push_back(&attach); mDrawBufferEnabled[i] = true; } else if (cur != LOCAL_GL_NONE) { const bool isColorEnum = (cur >= LOCAL_GL_COLOR_ATTACHMENT0 && cur < mContext->LastColorAttachmentEnum()); if (cur != LOCAL_GL_BACK && !isColorEnum) { mContext->ErrorInvalidEnum("Unexpected enum in buffers."); return; } mContext->ErrorInvalidOperation( "`buffers[i]` must be NONE or" " COLOR_ATTACHMENTi."); return; } } //// mColorDrawBuffers = std::move(newColorDrawBuffers); RefreshDrawBuffers(); // Calls glDrawBuffers. } void WebGLFramebuffer::ReadBuffer(GLenum attachPoint) { const auto& maybeAttach = GetColorAttachPoint(attachPoint); if (!maybeAttach) { const char text[] = "`mode` must be a COLOR_ATTACHMENTi, for 0 <= i <" " MAX_DRAW_BUFFERS."; if (attachPoint == LOCAL_GL_BACK) { mContext->ErrorInvalidOperation(text); } else { mContext->ErrorInvalidEnum(text); } return; } const auto& attach = maybeAttach.value(); // Might be nullptr. //// mColorReadBuffer = attach; RefreshReadBuffer(); // Calls glReadBuffer. } //// bool WebGLFramebuffer::FramebufferAttach(const GLenum attachEnum, const webgl::FbAttachInfo& toAttach) { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); if (MOZ_UNLIKELY(mOpaque)) { // An opaque framebuffer's attachments cannot be inspected or changed. return false; } // `attachment` const auto maybeAttach = GetAttachPoint(attachEnum); if (!maybeAttach || !maybeAttach.value()) return false; const auto& attach = maybeAttach.value(); const auto& gl = mContext->gl; gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName); if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { mDepthAttachment.Set(gl, toAttach); mStencilAttachment.Set(gl, toAttach); } else { attach->Set(gl, toAttach); } InvalidateCaches(); return true; } Maybe WebGLFramebuffer::GetAttachmentParameter(GLenum attachEnum, GLenum pname) { const auto maybeAttach = GetAttachPoint(attachEnum); if (!maybeAttach || attachEnum == LOCAL_GL_NONE) { mContext->ErrorInvalidEnum( "Can only query COLOR_ATTACHMENTi," " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or" " STENCIL_ATTACHMENT for a framebuffer."); return Nothing(); } if (MOZ_UNLIKELY(mOpaque)) { mContext->ErrorInvalidOperation( "An opaque framebuffer's attachments cannot be inspected or changed."); return Nothing(); } auto attach = maybeAttach.value(); if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { // There are a couple special rules for this one. if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE) { mContext->ErrorInvalidOperation( "Querying" " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE" " against DEPTH_STENCIL_ATTACHMENT is an" " error."); return Nothing(); } if (mDepthAttachment.Renderbuffer() != mStencilAttachment.Renderbuffer() || mDepthAttachment.Texture() != mStencilAttachment.Texture()) { mContext->ErrorInvalidOperation( "DEPTH_ATTACHMENT and STENCIL_ATTACHMENT" " have different objects bound."); return Nothing(); } attach = &mDepthAttachment; } return attach->GetParameter(mContext, attachEnum, pname); } //////////////////// static void GetBackbufferFormats(const WebGLContext* webgl, const webgl::FormatInfo** const out_color, const webgl::FormatInfo** const out_depth, const webgl::FormatInfo** const out_stencil) { const auto& options = webgl->Options(); const auto effFormat = (options.alpha ? webgl::EffectiveFormat::RGBA8 : webgl::EffectiveFormat::RGB8); *out_color = webgl::GetFormat(effFormat); *out_depth = nullptr; *out_stencil = nullptr; if (options.depth && options.stencil) { *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8); *out_stencil = *out_depth; } else { if (options.depth) { *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16); } if (options.stencil) { *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8); } } } /*static*/ void WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl, GLint _srcX0, GLint _srcY0, GLint _srcX1, GLint _srcY1, GLint _dstX0, GLint _dstY0, GLint _dstX1, GLint _dstY1, GLbitfield mask, GLenum filter) { auto srcP0 = ivec2{_srcX0, _srcY0}; auto srcP1 = ivec2{_srcX1, _srcY1}; auto dstP0 = ivec2{_dstX0, _dstY0}; auto dstP1 = ivec2{_dstX1, _dstY1}; const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; if (bool(mask & depthAndStencilBits) && filter == LOCAL_GL_LINEAR) { webgl->ErrorInvalidOperation( "DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can" " only be used with NEAREST filtering."); return; } const auto& srcFB = webgl->mBoundReadFramebuffer; const auto& dstFB = webgl->mBoundDrawFramebuffer; //// // Collect data const auto fnGetFormat = [](const WebGLFBAttachPoint& cur, bool* const out_hasSamples) -> const webgl::FormatInfo* { const auto& imageInfo = cur.GetImageInfo(); if (!imageInfo) return nullptr; // No attachment. *out_hasSamples = bool(imageInfo->mSamples); return imageInfo->mFormat->format; }; bool srcHasSamples = false; bool srcIsFilterable = true; const webgl::FormatInfo* srcColorFormat; const webgl::FormatInfo* srcDepthFormat; const webgl::FormatInfo* srcStencilFormat; gfx::IntSize srcSize; if (srcFB) { const auto& info = *srcFB->GetCompletenessInfo(); if (info.zLayerCount != 1) { webgl->GenerateError( LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION, "Source framebuffer cannot have more than one multiview layer."); return; } srcColorFormat = nullptr; if (srcFB->mColorReadBuffer) { const auto& imageInfo = srcFB->mColorReadBuffer->GetImageInfo(); if (imageInfo) { srcIsFilterable &= imageInfo->mFormat->isFilterable; } srcColorFormat = fnGetFormat(*(srcFB->mColorReadBuffer), &srcHasSamples); } srcDepthFormat = fnGetFormat(srcFB->DepthAttachment(), &srcHasSamples); srcStencilFormat = fnGetFormat(srcFB->StencilAttachment(), &srcHasSamples); MOZ_ASSERT(!srcFB->DepthStencilAttachment().HasAttachment()); srcSize = {info.width, info.height}; } else { srcHasSamples = false; // Always false. GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, &srcStencilFormat); const auto& size = webgl->DrawingBufferSize(); srcSize = {size.x, size.y}; } //// bool dstHasSamples = false; const webgl::FormatInfo* dstDepthFormat; const webgl::FormatInfo* dstStencilFormat; bool dstHasColor = false; bool colorFormatsMatch = true; bool colorTypesMatch = true; bool colorSrgbMatches = true; gfx::IntSize dstSize; const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) { MOZ_ASSERT(dstFormat->r || dstFormat->g || dstFormat->b || dstFormat->a); dstHasColor = true; colorFormatsMatch &= (dstFormat == srcColorFormat); colorTypesMatch &= srcColorFormat && (dstFormat->baseType == srcColorFormat->baseType); colorSrgbMatches &= srcColorFormat && (dstFormat->isSRGB == srcColorFormat->isSRGB); }; if (dstFB) { for (const auto& cur : dstFB->mColorDrawBuffers) { const auto& format = fnGetFormat(*cur, &dstHasSamples); if (!format) continue; fnCheckColorFormat(format); } dstDepthFormat = fnGetFormat(dstFB->DepthAttachment(), &dstHasSamples); dstStencilFormat = fnGetFormat(dstFB->StencilAttachment(), &dstHasSamples); MOZ_ASSERT(!dstFB->DepthStencilAttachment().HasAttachment()); const auto& info = *dstFB->GetCompletenessInfo(); if (info.isMultiview) { webgl->GenerateError( LOCAL_GL_INVALID_FRAMEBUFFER_OPERATION, "Destination framebuffer cannot have multiview attachments."); return; } dstSize = {info.width, info.height}; } else { dstHasSamples = webgl->Options().antialias; const webgl::FormatInfo* dstColorFormat; GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat, &dstStencilFormat); fnCheckColorFormat(dstColorFormat); const auto& size = webgl->DrawingBufferSize(); dstSize = {size.x, size.y}; } //// // Clear unused buffer bits if (mask & LOCAL_GL_COLOR_BUFFER_BIT && !srcColorFormat && !dstHasColor) { mask ^= LOCAL_GL_COLOR_BUFFER_BIT; } if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && !srcDepthFormat && !dstDepthFormat) { mask ^= LOCAL_GL_DEPTH_BUFFER_BIT; } if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && !srcStencilFormat && !dstStencilFormat) { mask ^= LOCAL_GL_STENCIL_BUFFER_BIT; } //// // Validation if (dstHasSamples) { webgl->ErrorInvalidOperation( "DRAW_FRAMEBUFFER may not have multiple" " samples."); return; } bool requireFilterable = (filter == LOCAL_GL_LINEAR); if (srcHasSamples) { requireFilterable = false; // It picks one. if (mask & LOCAL_GL_COLOR_BUFFER_BIT && dstHasColor && !colorFormatsMatch) { webgl->ErrorInvalidOperation( "Color buffer formats must match if" " selected, when reading from a multisampled" " source."); return; } if (srcP0 != dstP0 || srcP1 != dstP1) { webgl->ErrorInvalidOperation( "If the source is multisampled, then the" " source and dest regions must match exactly."); return; } } // - if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { if (requireFilterable && !srcIsFilterable) { webgl->ErrorInvalidOperation( "`filter` is LINEAR and READ_BUFFER" " contains integer data."); return; } if (!colorTypesMatch) { webgl->ErrorInvalidOperation( "Color component types (float/uint/" "int) must match."); return; } } /* GLES 3.0.4, p199: * Calling BlitFramebuffer will result in an INVALID_OPERATION error if * mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source * and destination depth and stencil buffer formats do not match. * * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified, * the stencil formats must match. This seems wrong. It could be a spec bug, * or I could be missing an interaction in one of the earlier paragraphs. */ if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && dstDepthFormat && dstDepthFormat != srcDepthFormat) { webgl->ErrorInvalidOperation( "Depth buffer formats must match if selected."); return; } if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && dstStencilFormat && dstStencilFormat != srcStencilFormat) { webgl->ErrorInvalidOperation( "Stencil buffer formats must match if selected."); return; } //// // Check for feedback if (srcFB && dstFB) { const WebGLFBAttachPoint* feedback = nullptr; if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { MOZ_ASSERT(srcFB->mColorReadBuffer->HasAttachment()); for (const auto& cur : dstFB->mColorDrawBuffers) { if (srcFB->mColorReadBuffer->IsEquivalentForFeedback(*cur)) { feedback = cur; break; } } } if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && srcFB->DepthAttachment().IsEquivalentForFeedback( dstFB->DepthAttachment())) { feedback = &dstFB->DepthAttachment(); } if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && srcFB->StencilAttachment().IsEquivalentForFeedback( dstFB->StencilAttachment())) { feedback = &dstFB->StencilAttachment(); } if (feedback) { webgl->ErrorInvalidOperation( "Feedback detected into DRAW_FRAMEBUFFER's" " 0x%04x attachment.", feedback->mAttachmentPoint); return; } } else if (!srcFB && !dstFB) { webgl->ErrorInvalidOperation("Feedback with default framebuffer."); return; } // - // Mutually constrain src and dst rects for eldritch blits. [&] { using fvec2 = avec2; // Switch to float, because there's no perfect // solution anyway. const auto zero2f = fvec2{0, 0}; const auto srcSizef = AsVec(srcSize).StaticCast(); const auto dstSizef = AsVec(dstSize).StaticCast(); const auto srcP0f = srcP0.StaticCast(); const auto srcP1f = srcP1.StaticCast(); const auto dstP0f = dstP0.StaticCast(); const auto dstP1f = dstP1.StaticCast(); const auto srcRectDiff = srcP1f - srcP0f; const auto dstRectDiff = dstP1f - dstP0f; // Skip if zero-sized. if (!srcRectDiff.x || !srcRectDiff.y || !dstRectDiff.x || !dstRectDiff.y) { srcP0 = srcP1 = dstP0 = dstP1 = {0, 0}; return; } // Clamp the rect points const auto srcQ0 = srcP0f.ClampMinMax(zero2f, srcSizef); const auto srcQ1 = srcP1f.ClampMinMax(zero2f, srcSizef); // Normalized to the [0,1] abstact copy rect const auto srcQ0Norm = (srcQ0 - srcP0f) / srcRectDiff; const auto srcQ1Norm = (srcQ1 - srcP0f) / srcRectDiff; // Map into dst const auto srcQ0InDst = dstP0f + srcQ0Norm * dstRectDiff; const auto srcQ1InDst = dstP0f + srcQ1Norm * dstRectDiff; // Clamp the rect points const auto dstQ0 = srcQ0InDst.ClampMinMax(zero2f, dstSizef); const auto dstQ1 = srcQ1InDst.ClampMinMax(zero2f, dstSizef); // Alright, time to go back to src! // Normalized to the [0,1] abstact copy rect const auto dstQ0Norm = (dstQ0 - dstP0f) / dstRectDiff; const auto dstQ1Norm = (dstQ1 - dstP0f) / dstRectDiff; // Map into src const auto dstQ0InSrc = srcP0f + dstQ0Norm * srcRectDiff; const auto dstQ1InSrc = srcP0f + dstQ1Norm * srcRectDiff; const auto srcQ0Constrained = dstQ0InSrc.ClampMinMax(zero2f, srcSizef); const auto srcQ1Constrained = dstQ1InSrc.ClampMinMax(zero2f, srcSizef); // Round, don't floor: srcP0 = (srcQ0Constrained + 0.5).StaticCast(); srcP1 = (srcQ1Constrained + 0.5).StaticCast(); dstP0 = (dstQ0 + 0.5).StaticCast(); dstP1 = (dstQ1 + 0.5).StaticCast(); }(); bool inBounds = true; inBounds &= (srcP0 == srcP0.Clamp({0, 0}, AsVec(srcSize))); inBounds &= (srcP1 == srcP1.Clamp({0, 0}, AsVec(srcSize))); inBounds &= (dstP0 == dstP0.Clamp({0, 0}, AsVec(dstSize))); inBounds &= (dstP1 == dstP1.Clamp({0, 0}, AsVec(dstSize))); if (!inBounds) { webgl->ErrorImplementationBug( "Subrects still not within src and dst after constraining."); return; } // - // Execute as constrained const auto& gl = webgl->gl; const ScopedDrawCallWrapper wrapper(*webgl); gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, dstP0.y, dstP1.x, dstP1.y, mask, filter); // - if (mask & LOCAL_GL_COLOR_BUFFER_BIT && !colorSrgbMatches && !gl->IsGLES() && gl->Version() < 440) { // Mostly for Mac. // Remember, we have to filter in the *linear* format blit. // src -Blit-> fbB -DrawBlit-> fbC -Blit-> dst const auto fbB = gl::MozFramebuffer::Create(gl, {1, 1}, 0, false); const auto fbC = gl::MozFramebuffer::Create(gl, {1, 1}, 0, false); // - auto sizeBC = srcSize; GLenum formatC = LOCAL_GL_RGBA8; if (srcColorFormat->isSRGB) { // srgb -> linear } else { // linear -> srgb sizeBC = dstSize; formatC = LOCAL_GL_SRGB8_ALPHA8; } const auto fnSetTex = [&](const gl::MozFramebuffer& fb, const GLenum format) { const gl::ScopedBindTexture bindTex(gl, fb.ColorTex()); gl->fTexStorage2D(LOCAL_GL_TEXTURE_2D, 1, format, sizeBC.width, sizeBC.height); }; fnSetTex(*fbB, srcColorFormat->sizedFormat); fnSetTex(*fbC, formatC); // - { const gl::ScopedBindFramebuffer bindFb(gl); gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fbB->mFB); if (srcColorFormat->isSRGB) { // srgb -> linear gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, srcP0.x, srcP0.y, srcP1.x, srcP1.y, LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST); } else { // linear -> srgb gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, dstP0.y, dstP1.x, dstP1.y, LOCAL_GL_COLOR_BUFFER_BIT, filter); } gl->fBindFramebuffer(LOCAL_GL_DRAW_FRAMEBUFFER, fbC->mFB); gl->BlitHelper()->DrawBlitTextureToFramebuffer(fbB->ColorTex(), sizeBC, sizeBC); } { const gl::ScopedBindFramebuffer bindFb(gl); gl->fBindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER, fbC->mFB); if (srcColorFormat->isSRGB) { // srgb -> linear gl->fBlitFramebuffer(srcP0.x, srcP0.y, srcP1.x, srcP1.y, dstP0.x, dstP0.y, dstP1.x, dstP1.y, LOCAL_GL_COLOR_BUFFER_BIT, filter); } else { // linear -> srgb gl->fBlitFramebuffer(dstP0.x, dstP0.y, dstP1.x, dstP1.y, dstP0.x, dstP0.y, dstP1.x, dstP1.y, LOCAL_GL_COLOR_BUFFER_BIT, LOCAL_GL_NEAREST); } } } // - // glBlitFramebuffer ignores glColorMask! if (!webgl->mBoundDrawFramebuffer && webgl->mNeedsFakeNoAlpha) { const auto dstRectMin = MinExtents(dstP0, dstP1); const auto dstRectMax = MaxExtents(dstP0, dstP1); const auto dstRectSize = dstRectMax - dstRectMin; const WebGLContext::ScissorRect dstRect = {dstRectMin.x, dstRectMin.y, dstRectSize.x, dstRectSize.y}; dstRect.Apply(*gl); const auto forClear = webgl::ScopedPrepForResourceClear{*webgl}; gl->fClearColor(0, 0, 0, 1); webgl->DoColorMask(Some(0), 0b1000); // Only alpha. gl->fClear(LOCAL_GL_COLOR_BUFFER_BIT); webgl->mScissorRect.Apply(*gl); } } } // namespace mozilla