/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* 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 "GLContext.h" #include "GLScreenBuffer.h" #include "mozilla/dom/WebGLRenderingContextBinding.h" #include "nsPrintfCString.h" #include "WebGLContext.h" #include "WebGLContextUtils.h" #include "WebGLExtensions.h" #include "WebGLRenderbuffer.h" #include "WebGLTexture.h" namespace mozilla { WebGLFBAttachPoint::WebGLFBAttachPoint() : mFB(nullptr) , mAttachmentPoint(0) , mTexImageTarget(LOCAL_GL_NONE) , mTexImageLayer(0) , mTexImageLevel(0) { } WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint) : mFB(fb) , mAttachmentPoint(attachmentPoint) , mTexImageTarget(LOCAL_GL_NONE) , mTexImageLayer(0) , mTexImageLevel(0) { } WebGLFBAttachPoint::~WebGLFBAttachPoint() { MOZ_ASSERT(mFB, "Should have been Init'd."); MOZ_ASSERT(!mRenderbufferPtr); MOZ_ASSERT(!mTexturePtr); } void WebGLFBAttachPoint::Unlink() { const char funcName[] = "WebGLFramebuffer::GC"; Clear(funcName); } bool WebGLFBAttachPoint::IsDeleteRequested() const { return Texture() ? Texture()->IsDeleteRequested() : Renderbuffer() ? Renderbuffer()->IsDeleteRequested() : false; } bool WebGLFBAttachPoint::IsDefined() const { /* return (Renderbuffer() && Renderbuffer()->IsDefined()) || (Texture() && Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).IsDefined()); */ return (Renderbuffer() || Texture()); } const webgl::FormatUsageInfo* WebGLFBAttachPoint::Format() const { MOZ_ASSERT(IsDefined()); if (Texture()) return Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).mFormat; if (Renderbuffer()) return Renderbuffer()->Format(); return nullptr; } uint32_t WebGLFBAttachPoint::Samples() const { MOZ_ASSERT(IsDefined()); if (mRenderbufferPtr) return mRenderbufferPtr->Samples(); return 0; } bool WebGLFBAttachPoint::HasAlpha() const { return Format()->format->a; } bool WebGLFBAttachPoint::IsReadableFloat() const { auto formatUsage = Format(); MOZ_ASSERT(formatUsage); auto format = formatUsage->format; if (!format->IsColorFormat()) return false; return format->componentType == webgl::ComponentType::Float; } void WebGLFBAttachPoint::Clear(const char* funcName) { if (mRenderbufferPtr) { MOZ_ASSERT(!mTexturePtr); mRenderbufferPtr->UnmarkAttachment(*this); } else if (mTexturePtr) { mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).RemoveAttachPoint(this); } mTexturePtr = nullptr; mRenderbufferPtr = nullptr; OnBackingStoreRespecified(funcName); } void WebGLFBAttachPoint::SetTexImage(const char* funcName, WebGLTexture* tex, TexImageTarget target, GLint level, GLint layer) { Clear(funcName); mTexturePtr = tex; mTexImageTarget = target; mTexImageLevel = level; mTexImageLayer = layer; if (mTexturePtr) { mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).AddAttachPoint(this); } } void WebGLFBAttachPoint::SetRenderbuffer(const char* funcName, WebGLRenderbuffer* rb) { Clear(funcName); mRenderbufferPtr = rb; if (mRenderbufferPtr) { mRenderbufferPtr->MarkAttachment(*this); } } bool WebGLFBAttachPoint::HasUninitializedImageData() const { if (!HasImage()) return false; if (mRenderbufferPtr) return mRenderbufferPtr->HasUninitializedImageData(); MOZ_ASSERT(mTexturePtr); auto& imageInfo = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel); MOZ_ASSERT(imageInfo.IsDefined()); return !imageInfo.IsDataInitialized(); } void WebGLFBAttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus) const { if (!HasImage()) return; if (mRenderbufferPtr) { mRenderbufferPtr->mImageDataStatus = newStatus; return; } MOZ_ASSERT(mTexturePtr); auto& imageInfo = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel); MOZ_ASSERT(imageInfo.IsDefined()); const bool isDataInitialized = (newStatus == WebGLImageDataStatus::InitializedImageData); imageInfo.SetIsDataInitialized(isDataInitialized, mTexturePtr); } bool WebGLFBAttachPoint::HasImage() const { if (Texture() && Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).IsDefined()) return true; if (Renderbuffer() && Renderbuffer()->IsDefined()) return true; return false; } void WebGLFBAttachPoint::Size(uint32_t* const out_width, uint32_t* const out_height) const { MOZ_ASSERT(HasImage()); if (Renderbuffer()) { *out_width = Renderbuffer()->Width(); *out_height = Renderbuffer()->Height(); return; } MOZ_ASSERT(Texture()); MOZ_ASSERT(Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).IsDefined()); const auto& imageInfo = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel); *out_width = imageInfo.mWidth; *out_height = imageInfo.mHeight; } void WebGLFBAttachPoint::OnBackingStoreRespecified(const char* funcName) const { mFB->InvalidateFramebufferStatus(funcName); } void WebGLFBAttachPoint::AttachmentName(nsCString* out) const { switch (mAttachmentPoint) { case LOCAL_GL_DEPTH_ATTACHMENT: out->AssignLiteral("DEPTH_ATTACHMENT"); return; case LOCAL_GL_STENCIL_ATTACHMENT: out->AssignLiteral("STENCIL_ATTACHMENT"); return; case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT: out->AssignLiteral("DEPTH_STENCIL_ATTACHMENT"); return; default: MOZ_ASSERT(mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0); out->AssignLiteral("COLOR_ATTACHMENT"); const uint32_t n = mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; out->AppendInt(n); return; } } bool WebGLFBAttachPoint::IsComplete(WebGLContext* webgl, nsCString* const out_info) const { MOZ_ASSERT(IsDefined()); if (!HasImage()) { AttachmentName(out_info); out_info->AppendLiteral("'s image is not defined"); return false; } uint32_t width; uint32_t height; Size(&width, &height); if (!width || !height) { AttachmentName(out_info); out_info->AppendLiteral(" has no width or height"); return false; } const auto formatUsage = Format(); if (!formatUsage->IsRenderable()) { nsAutoCString attachName; AttachmentName(&attachName); *out_info = nsPrintfCString("%s has an effective format of %s, which is not" " renderable", attachName.BeginReading(), formatUsage->format->name); return false; } if (webgl->IsWebGL2() && Texture() && Texture()->IsCubeMap() && !Texture()->IsCubeComplete()) { AttachmentName(out_info); out_info->AppendLiteral(" is not cube complete"); return false; } 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) { AttachmentName(out_info); out_info->AppendLiteral("'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) { AttachmentName(out_info); out_info->AppendLiteral("'s format has depth or stencil bits when it" " shouldn't"); return false; } } return true; } void WebGLFBAttachPoint::Resolve(gl::GLContext* gl) const { if (!HasImage()) return; if (Renderbuffer()) { Renderbuffer()->DoFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint); return; } MOZ_ASSERT(Texture()); MOZ_ASSERT(gl == Texture()->mContext->GL()); const auto& texName = Texture()->mGLName; //// const auto fnAttach2D = [&](GLenum attachmentPoint) { gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachmentPoint, mTexImageTarget.get(), texName, mTexImageLevel); }; //// switch (mTexImageTarget.get()) { case LOCAL_GL_TEXTURE_2D: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y: case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z: case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z: if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { fnAttach2D(LOCAL_GL_DEPTH_ATTACHMENT); fnAttach2D(LOCAL_GL_STENCIL_ATTACHMENT); } else { fnAttach2D(mAttachmentPoint); } break; case LOCAL_GL_TEXTURE_2D_ARRAY: case LOCAL_GL_TEXTURE_3D: // If we have fFramebufferTextureLayer, we can rely on having // DEPTH_STENCIL_ATTACHMENT. gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, texName, mTexImageLevel, mTexImageLayer); break; } } JS::Value WebGLFBAttachPoint::GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx, GLenum target, GLenum attachment, GLenum pname, ErrorResult* const out_error) const { const bool hasAttachment = (mTexturePtr || mRenderbufferPtr); 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 JS::Int32Value(LOCAL_GL_NONE); case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: if (webgl->IsWebGL2()) return JS::NullValue(); break; default: break; } nsCString attachmentName; WebGLContext::EnumName(attachment, &attachmentName); if (webgl->IsWebGL2()) { webgl->ErrorInvalidOperation("%s: No attachment at %s.", funcName, attachmentName.BeginReading()); } else { webgl->ErrorInvalidEnum("%s: No attachment at %s.", funcName, attachmentName.BeginReading()); } return JS::NullValue(); } bool isPNameValid = false; switch (pname) { case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE: return JS::Int32Value(mTexturePtr ? LOCAL_GL_TEXTURE : LOCAL_GL_RENDERBUFFER); case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME: return (mTexturePtr ? webgl->WebGLObjectAsJSValue(cx, mTexturePtr.get(), *out_error) : webgl->WebGLObjectAsJSValue(cx, mRenderbufferPtr.get(), *out_error)); ////// case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL: if (mTexturePtr) return JS::Int32Value(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 = ImageTarget().get(); } return JS::Int32Value(face); } break; ////// case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER: if (webgl->IsWebGL2() && mTexturePtr) { int32_t layer = 0; if (ImageTarget() == LOCAL_GL_TEXTURE_2D_ARRAY || ImageTarget() == LOCAL_GL_TEXTURE_3D) { layer = Layer(); } return JS::Int32Value(layer); } 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("%s: Invalid pname: 0x%04x", funcName, pname); return JS::NullValue(); } const auto usage = Format(); if (!usage) { if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING) return JS::NumberValue(LOCAL_GL_LINEAR); return JS::NullValue(); } 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->componentType == webgl::ComponentType::Special) { // Special format is used for DS mixed format(e.g. D24S8 and D32FS8). MOZ_ASSERT(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::None: ret = LOCAL_GL_NONE; break; 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; default: MOZ_ASSERT(false, "No matched component type"); break; } break; default: MOZ_ASSERT(false, "Missing case."); break; } return JS::Int32Value(ret); } //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // WebGLFramebuffer WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo) : WebGLRefCountedObject(webgl) , mGLName(fbo) , mNumFBStatusInvals(0) #ifdef ANDROID , mIsFB(false) #endif , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT) , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT) , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { mContext->mFramebuffers.insertBack(this); size_t i = 0; for (auto& cur : mColorAttachments) { new (&cur) WebGLFBAttachPoint(this, LOCAL_GL_COLOR_ATTACHMENT0 + i); i++; } mColorDrawBuffers.push_back(&mColorAttachments[0]); mColorReadBuffer = &mColorAttachments[0]; } void WebGLFramebuffer::Delete() { const char funcName[] = "WebGLFramebuffer::Delete"; InvalidateFramebufferStatus(funcName); mDepthAttachment.Clear(funcName); mStencilAttachment.Clear(funcName); mDepthStencilAttachment.Clear(funcName); for (auto& cur : mColorAttachments) { cur.Clear(funcName); } mContext->gl->fDeleteFramebuffers(1, &mGLName); LinkedListElement::removeFrom(mContext->mFramebuffers); #ifdef ANDROID mIsFB = false; #endif } //// 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->mGLMaxColorAttachments <= kMaxColorAttachments); if (colorId >= mContext->mGLMaxColorAttachments) 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); } } #define FOR_EACH_ATTACHMENT(X) \ X(mDepthAttachment); \ X(mStencilAttachment); \ X(mDepthStencilAttachment); \ \ for (auto& cur : mColorAttachments) { \ X(cur); \ } void WebGLFramebuffer::DetachTexture(const char* funcName, const WebGLTexture* tex) { const auto fnDetach = [&](WebGLFBAttachPoint& attach) { if (attach.Texture() == tex) { attach.Clear(funcName); } }; FOR_EACH_ATTACHMENT(fnDetach) } void WebGLFramebuffer::DetachRenderbuffer(const char* funcName, const WebGLRenderbuffer* rb) { const auto fnDetach = [&](WebGLFBAttachPoint& attach) { if (attach.Renderbuffer() == rb) { attach.Clear(funcName); } }; FOR_EACH_ATTACHMENT(fnDetach) } //////////////////////////////////////////////////////////////////////////////// // Completeness bool WebGLFramebuffer::HasDuplicateAttachments() const { std::set uniqueAttachSet; for (const auto& attach : mColorAttachments) { if (!attach.IsDefined()) 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; const auto func = [&](const WebGLFBAttachPoint& attach) { hasAttachments |= attach.IsDefined(); }; FOR_EACH_ATTACHMENT(func) return hasAttachments; } bool WebGLFramebuffer::HasIncompleteAttachments(nsCString* const out_info) const { bool hasIncomplete = false; const auto func = [&](const WebGLFBAttachPoint& cur) { if (!cur.IsDefined()) return; // Not defined, so can't count as incomplete. hasIncomplete |= !cur.IsComplete(mContext, out_info); }; FOR_EACH_ATTACHMENT(func) 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; const auto func = [&](const WebGLFBAttachPoint& attach) { if (!attach.HasImage()) return; uint32_t curWidth; uint32_t curHeight; attach.Size(&curWidth, &curHeight); if (needsInit) { needsInit = false; width = curWidth; height = curHeight; return; } hasMismatch |= (curWidth != width || curHeight != height); }; FOR_EACH_ATTACHMENT(func) 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; const auto func = [&](const WebGLFBAttachPoint& attach) { if (!attach.HasImage()) return; const uint32_t curSamples = attach.Samples(); if (needsInit) { needsInit = false; samples = curSamples; return; } hasMismatch |= (curSamples != samples); }; FOR_EACH_ATTACHMENT(func) return !hasMismatch; } #undef FOR_EACH_ATTACHMENT 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.IsDefined()); } else { const auto depthOrStencilCount = int(mDepthAttachment.IsDefined()) + int(mStencilAttachment.IsDefined()) + int(mDepthStencilAttachment.IsDefined()); if (depthOrStencilCount > 1) return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; } return LOCAL_GL_FRAMEBUFFER_COMPLETE; } //////////////////////////////////////// // Validation bool WebGLFramebuffer::ValidateAndInitAttachments(const char* funcName) const { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); const auto fbStatus = CheckFramebufferStatus(funcName); if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE) return true; mContext->ErrorInvalidFramebufferOperation("%s: Framebuffer must be" " complete.", funcName); return false; } bool WebGLFramebuffer::ValidateClearBufferType(const char* funcName, GLenum buffer, uint32_t drawBuffer, GLenum funcType) const { if (buffer != LOCAL_GL_COLOR) return true; const auto& attach = mColorAttachments[drawBuffer]; if (!attach.IsDefined()) return true; if (!count(mColorDrawBuffers.begin(), mColorDrawBuffers.end(), &attach)) return true; // DRAW_BUFFERi set to NONE. GLenum attachType; switch (attach.Format()->format->componentType) { case webgl::ComponentType::Int: attachType = LOCAL_GL_INT; break; case webgl::ComponentType::UInt: attachType = LOCAL_GL_UNSIGNED_INT; break; default: attachType = LOCAL_GL_FLOAT; break; } if (attachType != funcType) { mContext->ErrorInvalidOperation("%s: This attachment is of type 0x%04x, but" " this function is of type 0x%04x.", funcName, attachType, funcType); return false; } return true; } bool WebGLFramebuffer::ValidateForColorRead(const char* funcName, const webgl::FormatUsageInfo** const out_format, uint32_t* const out_width, uint32_t* const out_height) const { if (!mColorReadBuffer) { mContext->ErrorInvalidOperation("%s: READ_BUFFER must not be NONE.", funcName); return false; } if (!mColorReadBuffer->IsDefined()) { mContext->ErrorInvalidOperation("%s: The READ_BUFFER attachment is not defined.", funcName); return false; } if (mColorReadBuffer->Samples()) { mContext->ErrorInvalidOperation("%s: The READ_BUFFER attachment is multisampled.", funcName); return false; } *out_format = mColorReadBuffer->Format(); mColorReadBuffer->Size(out_width, out_height); return true; } //////////////////////////////////////////////////////////////////////////////// // Resolution and caching void WebGLFramebuffer::ResolveAttachments() const { const auto& gl = mContext->gl; //// // Nuke attachment points. for (uint32_t i = 0; i < mContext->mGLMaxColorAttachments; i++) { const GLenum attachEnum = LOCAL_GL_COLOR_ATTACHMENT0 + i; gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachEnum, LOCAL_GL_RENDERBUFFER, 0); } 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); //// for (const auto& attach : mColorAttachments) { attach.Resolve(gl); } mDepthAttachment.Resolve(gl); mStencilAttachment.Resolve(gl); mDepthStencilAttachment.Resolve(gl); } bool WebGLFramebuffer::ResolveAttachmentData(const char* funcName) const { ////// // Check if we need to initialize anything const auto fnIs3D = [&](const WebGLFBAttachPoint& attach) { const auto& tex = attach.Texture(); if (!tex) return false; const auto& info = tex->ImageInfoAt(attach.ImageTarget(), attach.MipLevel()); if (info.mDepth == 1) return false; return true; }; uint32_t clearBits = 0; std::vector attachmentsToClear; std::vector colorAttachmentsToClear; std::vector tex3DAttachmentsToInit; const auto fnGather = [&](const WebGLFBAttachPoint& attach, GLenum attachClearBits) { if (!attach.HasUninitializedImageData()) return false; if (fnIs3D(attach)) { tex3DAttachmentsToInit.push_back(&attach); return false; } clearBits |= attachClearBits; attachmentsToClear.push_back(&attach); return true; }; ////// for (auto& cur : mColorDrawBuffers) { if (fnGather(*cur, LOCAL_GL_COLOR_BUFFER_BIT)) { colorAttachmentsToClear.push_back(cur); } } fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT); fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT); fnGather(mDepthStencilAttachment, LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT); ////// for (const auto& attach : tex3DAttachmentsToInit) { const auto& tex = attach->Texture(); if (!tex->InitializeImageData(funcName, attach->ImageTarget(), attach->MipLevel())) { return false; } } if (clearBits) { const auto fnDrawBuffers = [&](const std::vector& src) { std::vector enumList; for (const auto& cur : src) { const auto& attachEnum = cur->mAttachmentPoint; const GLenum attachId = attachEnum - LOCAL_GL_COLOR_ATTACHMENT0; while (enumList.size() < attachId) { enumList.push_back(LOCAL_GL_NONE); } enumList.push_back(attachEnum); } mContext->gl->fDrawBuffers(enumList.size(), enumList.data()); }; //// // Clear const bool hasDrawBuffers = mContext->HasDrawBuffers(); if (hasDrawBuffers) { fnDrawBuffers(colorAttachmentsToClear); } { gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName); mContext->ForceClearFramebufferWithDefaultValues(clearBits, false); } if (hasDrawBuffers) { RefreshDrawBuffers(); } // Mark initialized. for (const auto& cur : attachmentsToClear) { cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData); } } return true; } WebGLFramebuffer::ResolvedData::ResolvedData(const WebGLFramebuffer& parent) { texDrawBuffers.reserve(parent.mColorDrawBuffers.size() + 2); // +2 for depth+stencil. const auto fnCommon = [&](const WebGLFBAttachPoint& attach) { if (!attach.IsDefined()) return false; if (attach.Texture()) { texDrawBuffers.push_back(&attach); } return true; }; //// const auto fnDepthStencil = [&](const WebGLFBAttachPoint& attach) { if (!fnCommon(attach)) return; drawSet.insert(WebGLFBAttachPoint::Ordered(attach)); readSet.insert(WebGLFBAttachPoint::Ordered(attach)); }; fnDepthStencil(parent.mDepthAttachment); fnDepthStencil(parent.mStencilAttachment); fnDepthStencil(parent.mDepthStencilAttachment); //// for (const auto& pAttach : parent.mColorDrawBuffers) { const auto& attach = *pAttach; if (!fnCommon(attach)) return; drawSet.insert(WebGLFBAttachPoint::Ordered(attach)); } if (parent.mColorReadBuffer) { const auto& attach = *parent.mColorReadBuffer; if (!fnCommon(attach)) return; readSet.insert(WebGLFBAttachPoint::Ordered(attach)); } } void WebGLFramebuffer::InvalidateFramebufferStatus(const char* funcName) { if (mResolvedCompleteData) { mNumFBStatusInvals++; if (mNumFBStatusInvals > mContext->mMaxAcceptableFBStatusInvals) { mContext->GeneratePerfWarning("%s: FB was invalidated after being complete %u" " times.", funcName, uint32_t(mNumFBStatusInvals)); } } mResolvedCompleteData = nullptr; } void WebGLFramebuffer::RefreshResolvedData() { if (mResolvedCompleteData) { mResolvedCompleteData.reset(new ResolvedData(*this)); } } //////////////////////////////////////////////////////////////////////////////// // Entrypoints FBStatus WebGLFramebuffer::CheckFramebufferStatus(const char* const funcName) const { if (IsResolvedComplete()) 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); //// ResolveAttachments(); // OK, attach everything properly! 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; } if (!ResolveAttachmentData(funcName)) { ret = LOCAL_GL_FRAMEBUFFER_UNSUPPORTED; statusInfo.AssignLiteral("Failed to lazily-initialize attachment data."); break; } mResolvedCompleteData.reset(new ResolvedData(*this)); return LOCAL_GL_FRAMEBUFFER_COMPLETE; } while (false); MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE); mContext->GenerateWarning("%s: Framebuffer not complete. (status: 0x%04x) %s", funcName, 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->mGLMaxDrawBuffers, LOCAL_GL_NONE); for (const auto& attach : mColorDrawBuffers) { if (attach->HasImage()) { const uint32_t index = attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0; driverBuffers[index] = attach->mAttachmentPoint; } } 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->HasImage()) { driverBuffer = mColorReadBuffer->mAttachmentPoint; } gl->fReadBuffer(driverBuffer); } //// void WebGLFramebuffer::DrawBuffers(const char* funcName, const dom::Sequence& buffers) { if (buffers.Length() > mContext->mGLMaxDrawBuffers) { // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS." mContext->ErrorInvalidValue("%s: `buffers` must have a length <=" " MAX_DRAW_BUFFERS.", funcName); return; } std::vector newColorDrawBuffers; newColorDrawBuffers.reserve(buffers.Length()); for (size_t i = 0; i < buffers.Length(); i++) { // "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); } 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("%s: Unexpected enum in buffers.", funcName); return; } mContext->ErrorInvalidOperation("%s: `buffers[i]` must be NONE or" " COLOR_ATTACHMENTi.", funcName); return; } } //// mColorDrawBuffers.swap(newColorDrawBuffers); RefreshDrawBuffers(); // Calls glDrawBuffers. RefreshResolvedData(); } void WebGLFramebuffer::ReadBuffer(const char* funcName, GLenum attachPoint) { const auto& maybeAttach = GetColorAttachPoint(attachPoint); if (!maybeAttach) { const char text[] = "%s: `mode` must be a COLOR_ATTACHMENTi, for 0 <= i <" " MAX_DRAW_BUFFERS."; if (attachPoint == LOCAL_GL_BACK) { mContext->ErrorInvalidOperation(text, funcName); } else { mContext->ErrorInvalidEnum(text, funcName); } return; } const auto& attach = maybeAttach.value(); // Might be nullptr. //// mColorReadBuffer = attach; RefreshReadBuffer(); // Calls glReadBuffer. RefreshResolvedData(); } //// void WebGLFramebuffer::FramebufferRenderbuffer(const char* funcName, GLenum attachEnum, GLenum rbtarget, WebGLRenderbuffer* rb) { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); // `attachment` const auto maybeAttach = GetAttachPoint(attachEnum); if (!maybeAttach || !maybeAttach.value()) { mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum); return; } const auto& attach = maybeAttach.value(); // `rbTarget` if (rbtarget != LOCAL_GL_RENDERBUFFER) { mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:", rbtarget); return; } // `rb` if (rb) { if (!mContext->ValidateObject("framebufferRenderbuffer: rb", *rb)) return; if (!rb->mHasBeenBound) { mContext->ErrorInvalidOperation("%s: bindRenderbuffer must be called before" " attachment to %04x", funcName, attachEnum); return; } } // End of validation. if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { mDepthAttachment.SetRenderbuffer(funcName, rb); mStencilAttachment.SetRenderbuffer(funcName, rb); } else { attach->SetRenderbuffer(funcName, rb); } InvalidateFramebufferStatus(funcName); } void WebGLFramebuffer::FramebufferTexture2D(const char* funcName, GLenum attachEnum, GLenum texImageTarget, WebGLTexture* tex, GLint level) { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); // `attachment` const auto maybeAttach = GetAttachPoint(attachEnum); if (!maybeAttach || !maybeAttach.value()) { mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum); return; } const auto& attach = maybeAttach.value(); // `texImageTarget` if (texImageTarget != LOCAL_GL_TEXTURE_2D && (texImageTarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X || texImageTarget > LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z)) { mContext->ErrorInvalidEnumInfo("framebufferTexture2D: texImageTarget:", texImageTarget); return; } // `texture` if (tex) { if (!mContext->ValidateObject("framebufferTexture2D: texture", *tex)) return; if (!tex->HasEverBeenBound()) { mContext->ErrorInvalidOperation("%s: `texture` has never been bound.", funcName); return; } const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget); if (tex->Target() != destTexTarget) { mContext->ErrorInvalidOperation("%s: Mismatched texture and texture target.", funcName); return; } } // `level` if (level < 0) return mContext->ErrorInvalidValue("%s: `level` must not be negative.", funcName); if (mContext->IsWebGL2()) { /* GLES 3.0.4 p208: * If textarget is one of TEXTURE_CUBE_MAP_POSITIVE_X, * TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP_POSITIVE_Z, * TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP_NEGATIVE_Y, * or TEXTURE_CUBE_MAP_NEGATIVE_Z, then level must be greater * than or equal to zero and less than or equal to log2 of the * value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D, * level must be greater than or equal to zero and no larger than * log2 of the value of MAX_TEXTURE_SIZE. Otherwise, an * INVALID_VALUE error is generated. */ if (texImageTarget == LOCAL_GL_TEXTURE_2D) { if (uint32_t(level) > FloorLog2(mContext->mGLMaxTextureSize)) return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName); } else { MOZ_ASSERT(texImageTarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X && texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z); if (uint32_t(level) > FloorLog2(mContext->mGLMaxCubeMapTextureSize)) return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName); } } else if (level != 0) { return mContext->ErrorInvalidValue("%s: `level` must be 0.", funcName); } // End of validation. if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { mDepthAttachment.SetTexImage(funcName, tex, texImageTarget, level); mStencilAttachment.SetTexImage(funcName, tex, texImageTarget, level); } else { attach->SetTexImage(funcName, tex, texImageTarget, level); } InvalidateFramebufferStatus(funcName); } void WebGLFramebuffer::FramebufferTextureLayer(const char* funcName, GLenum attachEnum, WebGLTexture* tex, GLint level, GLint layer) { MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this || mContext->mBoundReadFramebuffer == this); // `attachment` const auto maybeAttach = GetAttachPoint(attachEnum); if (!maybeAttach || !maybeAttach.value()) { mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum); return; } const auto& attach = maybeAttach.value(); // `level`, `layer` if (layer < 0) return mContext->ErrorInvalidValue("%s: `layer` must be >= 0.", funcName); if (level < 0) return mContext->ErrorInvalidValue("%s: `level` must be >= 0.", funcName); // `texture` GLenum texImageTarget = LOCAL_GL_TEXTURE_3D; if (tex) { if (!mContext->ValidateObject("framebufferTextureLayer: texture", *tex)) return; if (!tex->HasEverBeenBound()) { mContext->ErrorInvalidOperation("%s: `texture` has never been bound.", funcName); return; } texImageTarget = tex->Target().get(); switch (texImageTarget) { case LOCAL_GL_TEXTURE_3D: if (uint32_t(layer) >= mContext->mGLMax3DTextureSize) { mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName, "MAX_3D_TEXTURE_SIZE"); return; } if (uint32_t(level) > FloorLog2(mContext->mGLMax3DTextureSize)) { mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName, "MAX_3D_TEXTURE_SIZE"); return; } break; case LOCAL_GL_TEXTURE_2D_ARRAY: if (uint32_t(layer) >= mContext->mGLMaxArrayTextureLayers) { mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName, "MAX_ARRAY_TEXTURE_LAYERS"); return; } if (uint32_t(level) > FloorLog2(mContext->mGLMaxTextureSize)) { mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName, "MAX_TEXTURE_SIZE"); return; } break; default: mContext->ErrorInvalidOperation("%s: `texture` must be a TEXTURE_3D or" " TEXTURE_2D_ARRAY.", funcName); return; } } // End of validation. if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { mDepthAttachment.SetTexImage(funcName, tex, texImageTarget, level, layer); mStencilAttachment.SetTexImage(funcName, tex, texImageTarget, level, layer); } else { attach->SetTexImage(funcName, tex, texImageTarget, level, layer); } InvalidateFramebufferStatus(funcName); } JS::Value WebGLFramebuffer::GetAttachmentParameter(const char* funcName, JSContext* cx, GLenum target, GLenum attachEnum, GLenum pname, ErrorResult* const out_error) { const auto maybeAttach = GetAttachPoint(attachEnum); if (!maybeAttach || attachEnum == LOCAL_GL_NONE) { mContext->ErrorInvalidEnum("%s: Can only query COLOR_ATTACHMENTi," " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or" " STENCIL_ATTACHMENT for a framebuffer.", funcName); return JS::NullValue(); } 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("%s: Querying" " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE" " against DEPTH_STENCIL_ATTACHMENT is an" " error.", funcName); return JS::NullValue(); } if (mDepthAttachment.Renderbuffer() != mStencilAttachment.Renderbuffer() || mDepthAttachment.Texture() != mStencilAttachment.Texture()) { mContext->ErrorInvalidOperation("%s: DEPTH_ATTACHMENT and STENCIL_ATTACHMENT" " have different objects bound.", funcName); return JS::NullValue(); } attach = &mDepthAttachment; } return attach->GetParameter(funcName, mContext, cx, target, attachEnum, pname, out_error); } //////////////////// 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) { const char funcName[] = "blitFramebuffer"; const auto& gl = webgl->gl; const auto& srcFB = webgl->mBoundReadFramebuffer; const auto& dstFB = webgl->mBoundDrawFramebuffer; //// // Collect data const auto fnGetDepthAndStencilAttach = [](const WebGLFramebuffer* fb, const WebGLFBAttachPoint** const out_depth, const WebGLFBAttachPoint** const out_stencil) { *out_depth = nullptr; *out_stencil = nullptr; if (!fb) return; if (fb->mDepthStencilAttachment.IsDefined()) { *out_depth = *out_stencil = &fb->mDepthStencilAttachment; return; } if (fb->mDepthAttachment.IsDefined()) { *out_depth = &fb->mDepthAttachment; } if (fb->mStencilAttachment.IsDefined()) { *out_stencil = &fb->mStencilAttachment; } }; const WebGLFBAttachPoint* srcDepthAttach; const WebGLFBAttachPoint* srcStencilAttach; fnGetDepthAndStencilAttach(srcFB, &srcDepthAttach, &srcStencilAttach); const WebGLFBAttachPoint* dstDepthAttach; const WebGLFBAttachPoint* dstStencilAttach; fnGetDepthAndStencilAttach(dstFB, &dstDepthAttach, &dstStencilAttach); //// const auto fnGetFormat = [](const WebGLFBAttachPoint* cur, bool* const out_hasSamples) -> const webgl::FormatInfo* { if (!cur || !cur->IsDefined()) return nullptr; *out_hasSamples |= bool(cur->Samples()); return cur->Format()->format; }; const auto fnNarrowComponentType = [&](const webgl::FormatInfo* format) { switch (format->componentType) { case webgl::ComponentType::NormInt: case webgl::ComponentType::NormUInt: return webgl::ComponentType::Float; default: return format->componentType; } }; bool srcHasSamples; const webgl::FormatInfo* srcColorFormat; webgl::ComponentType srcColorType = webgl::ComponentType::None; const webgl::FormatInfo* srcDepthFormat; const webgl::FormatInfo* srcStencilFormat; if (srcFB) { srcHasSamples = false; srcColorFormat = fnGetFormat(srcFB->mColorReadBuffer, &srcHasSamples); srcDepthFormat = fnGetFormat(srcDepthAttach, &srcHasSamples); srcStencilFormat = fnGetFormat(srcStencilAttach, &srcHasSamples); } else { srcHasSamples = false; // Always false. GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, &srcStencilFormat); } if (srcColorFormat) { srcColorType = fnNarrowComponentType(srcColorFormat); } //// bool dstHasSamples; const webgl::FormatInfo* dstDepthFormat; const webgl::FormatInfo* dstStencilFormat; bool dstHasColor = false; bool colorFormatsMatch = true; bool colorTypesMatch = true; const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) { MOZ_ASSERT(dstFormat->r || dstFormat->g || dstFormat->b || dstFormat->a); dstHasColor = true; colorFormatsMatch &= (dstFormat == srcColorFormat); colorTypesMatch &= ( fnNarrowComponentType(dstFormat) == srcColorType ); }; if (dstFB) { dstHasSamples = false; for (const auto& cur : dstFB->mColorDrawBuffers) { const auto& format = fnGetFormat(cur, &dstHasSamples); if (!format) continue; fnCheckColorFormat(format); } dstDepthFormat = fnGetFormat(dstDepthAttach, &dstHasSamples); dstStencilFormat = fnGetFormat(dstStencilAttach, &dstHasSamples); } else { dstHasSamples = bool(gl->Screen()->Samples()); const webgl::FormatInfo* dstColorFormat; GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat, &dstStencilFormat); fnCheckColorFormat(dstColorFormat); } //// // 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 (mask & LOCAL_GL_COLOR_BUFFER_BIT) { if (srcColorFormat && filter == LOCAL_GL_LINEAR) { const auto& type = srcColorFormat->componentType; if (type == webgl::ComponentType::Int || type == webgl::ComponentType::UInt) { webgl->ErrorInvalidOperation("%s: `filter` is LINEAR and READ_BUFFER" " contains integer data.", funcName); return; } } if (!colorTypesMatch) { webgl->ErrorInvalidOperation("%s: Color component types (fixed/float/uint/" "int) must match.", funcName); return; } } const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT; if (bool(mask & depthAndStencilBits) && filter != LOCAL_GL_NEAREST) { webgl->ErrorInvalidOperation("%s: DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can" " only be used with NEAREST filtering.", funcName); 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("%s: Depth buffer formats must match if selected.", funcName); return; } if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && dstStencilFormat && dstStencilFormat != srcStencilFormat) { webgl->ErrorInvalidOperation("%s: Stencil buffer formats must match if selected.", funcName); return; } //// if (dstHasSamples) { webgl->ErrorInvalidOperation("%s: DRAW_FRAMEBUFFER may not have multiple" " samples.", funcName); return; } if (srcHasSamples) { if (mask & LOCAL_GL_COLOR_BUFFER_BIT && dstHasColor && !colorFormatsMatch) { webgl->ErrorInvalidOperation("%s: Color buffer formats must match if" " selected, when reading from a multisampled" " source.", funcName); return; } if (dstX0 != srcX0 || dstX1 != srcX1 || dstY0 != srcY0 || dstY1 != srcY1) { webgl->ErrorInvalidOperation("%s: If the source is multisampled, then the" " source and dest regions must match exactly.", funcName); return; } } //// // Check for feedback if (srcFB && dstFB) { const WebGLFBAttachPoint* feedback = nullptr; if (mask & LOCAL_GL_COLOR_BUFFER_BIT) { MOZ_ASSERT(srcFB->mColorReadBuffer->IsDefined()); for (const auto& cur : dstFB->mColorDrawBuffers) { if (srcFB->mColorReadBuffer->IsEquivalentForFeedback(*cur)) { feedback = cur; break; } } } if (mask & LOCAL_GL_DEPTH_BUFFER_BIT && srcDepthAttach->IsEquivalentForFeedback(*dstDepthAttach)) { feedback = dstDepthAttach; } if (mask & LOCAL_GL_STENCIL_BUFFER_BIT && srcStencilAttach->IsEquivalentForFeedback(*dstStencilAttach)) { feedback = dstStencilAttach; } if (feedback) { webgl->ErrorInvalidOperation("%s: Feedback detected into DRAW_FRAMEBUFFER's" " 0x%04x attachment.", funcName, feedback->mAttachmentPoint); return; } } else if (!srcFB && !dstFB) { webgl->ErrorInvalidOperation("%s: Feedback with default framebuffer.", funcName); return; } //// const ScopedDrawCallWrapper wrapper(*webgl); gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } //////////////////////////////////////////////////////////////////////////////// // Goop. JSObject* WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle givenProto) { return dom::WebGLFramebufferBinding::Wrap(cx, this, givenProto); } inline void ImplCycleCollectionUnlink(mozilla::WebGLFBAttachPoint& field) { field.Unlink(); } inline void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, const mozilla::WebGLFBAttachPoint& field, const char* name, uint32_t flags = 0) { CycleCollectionNoteChild(callback, field.Texture(), name, flags); CycleCollectionNoteChild(callback, field.Renderbuffer(), name, flags); } template inline void ImplCycleCollectionUnlink(C& field) { for (auto& cur : field) { cur.Unlink(); } } template inline void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, const C& field, const char* name, uint32_t flags = 0) { for (auto& cur : field) { ImplCycleCollectionTraverse(callback, cur, name, flags); } } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebuffer, mDepthAttachment, mStencilAttachment, mDepthStencilAttachment, mColorAttachments) NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLFramebuffer, AddRef) NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLFramebuffer, Release) } // namespace mozilla