diff options
Diffstat (limited to 'dom/canvas/WebGLFramebuffer.cpp')
-rw-r--r-- | dom/canvas/WebGLFramebuffer.cpp | 1952 |
1 files changed, 1952 insertions, 0 deletions
diff --git a/dom/canvas/WebGLFramebuffer.cpp b/dom/canvas/WebGLFramebuffer.cpp new file mode 100644 index 000000000..35efa4f16 --- /dev/null +++ b/dom/canvas/WebGLFramebuffer.cpp @@ -0,0 +1,1952 @@ +/* -*- 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 <algorithm> +#include <iterator> + +#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() +{ + Clear(); +} + +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() +{ + if (mRenderbufferPtr) { + MOZ_ASSERT(!mTexturePtr); + mRenderbufferPtr->UnmarkAttachment(*this); + } else if (mTexturePtr) { + mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).RemoveAttachPoint(this); + } + + mTexturePtr = nullptr; + mRenderbufferPtr = nullptr; + + OnBackingStoreRespecified(); +} + +void +WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level, + GLint layer) +{ + Clear(); + + mTexturePtr = tex; + mTexImageTarget = target; + mTexImageLevel = level; + mTexImageLayer = layer; + + if (mTexturePtr) { + mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).AddAttachPoint(this); + } +} + +void +WebGLFBAttachPoint::SetRenderbuffer(WebGLRenderbuffer* rb) +{ + Clear(); + + 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 +{ + mFB->InvalidateFramebufferStatus(); +} + +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) +#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() +{ + InvalidateFramebufferStatus(); + + mDepthAttachment.Clear(); + mStencilAttachment.Clear(); + mDepthStencilAttachment.Clear(); + + for (auto& cur : mColorAttachments) { + cur.Clear(); + } + + mContext->MakeContextCurrent(); + mContext->gl->fDeleteFramebuffers(1, &mGLName); + + LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers); + +#ifdef ANDROID + mIsFB = false; +#endif +} + +//// + +Maybe<WebGLFBAttachPoint*> +WebGLFramebuffer::GetColorAttachPoint(GLenum attachPoint) +{ + if (attachPoint == LOCAL_GL_NONE) + return Some<WebGLFBAttachPoint*>(nullptr); + + if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0) + return Nothing(); + + const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0; + + MOZ_ASSERT(mContext->mImplMaxColorAttachments <= kMaxColorAttachments); + if (colorId >= mContext->mImplMaxColorAttachments) + return Nothing(); + + return Some(&mColorAttachments[colorId]); +} + +Maybe<WebGLFBAttachPoint*> +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 WebGLTexture* tex) +{ + const auto fnDetach = [&](WebGLFBAttachPoint& attach) { + if (attach.Texture() == tex) { + attach.Clear(); + } + }; + + FOR_EACH_ATTACHMENT(fnDetach) +} + +void +WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb) +{ + const auto fnDetach = [&](WebGLFBAttachPoint& attach) { + if (attach.Renderbuffer() == rb) { + attach.Clear(); + } + }; + + FOR_EACH_ATTACHMENT(fnDetach) +} + +//////////////////////////////////////////////////////////////////////////////// +// Completeness + +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<nsCString> 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<nsCString> 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 (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) +{ + 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::ValidateForRead(const char* funcName, + const webgl::FormatUsageInfo** const out_format, + uint32_t* const out_width, uint32_t* const out_height) +{ + if (!ValidateAndInitAttachments(funcName)) + return false; + + 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->mImplMaxColorAttachments; 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<const WebGLFBAttachPoint*> attachmentsToClear; + std::vector<const WebGLFBAttachPoint*> colorAttachmentsToClear; + std::vector<const WebGLFBAttachPoint*> 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<const WebGLFBAttachPoint*>& src) + { + std::vector<GLenum> 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 + + mContext->MakeContextCurrent(); + + 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::RefreshResolvedData() +{ + if (mResolvedCompleteData) { + mResolvedCompleteData.reset(new ResolvedData(*this)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// Entrypoints + +FBStatus +WebGLFramebuffer::CheckFramebufferStatus(const char* funcName) +{ + 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; + gl->MakeCurrent(); + + 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<GLenum> driverBuffers(mContext->mImplMaxDrawBuffers, 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<GLenum>& buffers) +{ + if (buffers.Length() > mContext->mImplMaxDrawBuffers) { + // "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<const WebGLFBAttachPoint*> 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; + } + } + + //// + + mContext->MakeContextCurrent(); + + 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. + + //// + + mContext->MakeContextCurrent(); + + 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 && !mContext->ValidateObject("framebufferRenderbuffer: rb", *rb)) + return; + + // End of validation. + + if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) { + mDepthAttachment.SetRenderbuffer(rb); + mStencilAttachment.SetRenderbuffer(rb); + } else { + attach->SetRenderbuffer(rb); + } + + InvalidateFramebufferStatus(); +} + +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->mImplMaxTextureSize)) + 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->mImplMaxCubeMapTextureSize)) + 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(tex, texImageTarget, level); + mStencilAttachment.SetTexImage(tex, texImageTarget, level); + } else { + attach->SetTexImage(tex, texImageTarget, level); + } + + InvalidateFramebufferStatus(); +} + +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` + TexImageTarget 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.get()) { + case LOCAL_GL_TEXTURE_3D: + if (uint32_t(layer) >= mContext->mImplMax3DTextureSize) { + mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName, + "MAX_3D_TEXTURE_SIZE"); + return; + } + + if (uint32_t(level) > FloorLog2(mContext->mImplMax3DTextureSize)) { + 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->mImplMaxArrayTextureLayers) { + mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName, + "MAX_ARRAY_TEXTURE_LAYERS"); + return; + } + + if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize)) { + 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(tex, texImageTarget, level, layer); + mStencilAttachment.SetTexImage(tex, texImageTarget, level, layer); + } else { + attach->SetTexImage(tex, texImageTarget, level, layer); + } + + InvalidateFramebufferStatus(); +} + +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, + const WebGLFramebuffer* srcFB, GLint srcX0, GLint srcY0, + GLint srcX1, GLint srcY1, + const WebGLFramebuffer* dstFB, GLint dstX0, GLint dstY0, + GLint dstX1, GLint dstY1, + GLbitfield mask, GLenum filter) +{ + const char funcName[] = "blitFramebuffer"; + const auto& gl = webgl->gl; + + //// + // 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; + } + + //// + + gl->MakeCurrent(); + webgl->OnBeforeReadCall(); + WebGLContext::ScopedDrawCallWrapper wrapper(*webgl); + gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, + dstX0, dstY0, dstX1, dstY1, + mask, filter); +} + +//////////////////////////////////////////////////////////////////////////////// +// Goop. + +JSObject* +WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) +{ + return dom::WebGLFramebufferBinding::Wrap(cx, this, givenProto); +} + +inline void +ImplCycleCollectionUnlink(mozilla::WebGLFBAttachPoint& field) +{ + field.Unlink(); +} + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, + mozilla::WebGLFBAttachPoint& field, + const char* name, + uint32_t flags = 0) +{ + CycleCollectionNoteChild(callback, field.Texture(), name, flags); + CycleCollectionNoteChild(callback, field.Renderbuffer(), name, flags); +} + +template<typename C> +inline void +ImplCycleCollectionUnlink(C& field) +{ + for (auto& cur : field) { + cur.Unlink(); + } +} + +template<typename C> +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback, + 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 |