summaryrefslogtreecommitdiffstats
path: root/dom/canvas/WebGLFramebuffer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/canvas/WebGLFramebuffer.cpp')
-rw-r--r--dom/canvas/WebGLFramebuffer.cpp1952
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