gecko-dev/dom/canvas/WebGLFramebuffer.cpp
2016-12-16 18:46:18 -08:00

1883 lines
58 KiB
C++

/* -*- 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;
}
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;
}
if (webgl->IsWebGL2()) {
webgl->ErrorInvalidOperation("%s: No attachment at %s.", funcName,
webgl->EnumName(attachment));
} else {
webgl->ErrorInvalidEnum("%s: No attachment at %s.", funcName,
webgl->EnumName(attachment));
}
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)
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::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;
}
*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)
: hasSampleBuffers(false)
, depthBuffer(nullptr)
, stencilBuffer(nullptr)
{
if (parent.mDepthAttachment.IsDefined()) {
depthBuffer = &parent.mDepthAttachment;
}
if (parent.mStencilAttachment.IsDefined()) {
stencilBuffer = &parent.mStencilAttachment;
}
if (parent.mDepthStencilAttachment.IsDefined()) {
depthBuffer = &parent.mDepthStencilAttachment;
stencilBuffer = &parent.mDepthStencilAttachment;
}
////
colorDrawBuffers.reserve(parent.mColorDrawBuffers.size());
texDrawBuffers.reserve(parent.mColorDrawBuffers.size() + 2); // +2 for depth+stencil.
const auto fnCommon = [&](const WebGLFBAttachPoint& attach) {
if (!attach.IsDefined())
return false;
hasSampleBuffers |= bool(attach.Samples());
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));
colorDrawBuffers.push_back(&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) {
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";
auto& gl = webgl->gl;
////
// Collect data
const auto fnGetFormat = [](const WebGLFBAttachPoint* cur) -> const webgl::FormatInfo*
{
if (!cur)
return nullptr;
MOZ_ASSERT(cur->IsDefined());
return cur->Format()->format;
};
bool srcSampleBuffers;
const webgl::FormatInfo* srcColorFormat;
const webgl::FormatInfo* srcDepthFormat;
const webgl::FormatInfo* srcStencilFormat;
if (srcFB) {
srcSampleBuffers = srcFB->mResolvedCompleteData->hasSampleBuffers;
srcColorFormat = fnGetFormat(srcFB->mColorReadBuffer);
srcDepthFormat = fnGetFormat(srcFB->mResolvedCompleteData->depthBuffer);
srcStencilFormat = fnGetFormat(srcFB->mResolvedCompleteData->stencilBuffer);
} else {
srcSampleBuffers = false; // Always false.
GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, &srcStencilFormat);
}
////
bool dstSampleBuffers;
const webgl::FormatInfo* dstDepthFormat;
const webgl::FormatInfo* dstStencilFormat;
bool dstHasColor = false;
bool colorFormatsMatch = true;
bool colorTypesMatch = true;
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;
}
};
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) ==
fnNarrowComponentType(srcColorFormat) );
};
if (dstFB) {
dstSampleBuffers = dstFB->mResolvedCompleteData->hasSampleBuffers;
dstDepthFormat = fnGetFormat(dstFB->mResolvedCompleteData->depthBuffer);
dstStencilFormat = fnGetFormat(dstFB->mResolvedCompleteData->stencilBuffer);
for (const auto& cur : dstFB->mResolvedCompleteData->colorDrawBuffers) {
fnCheckColorFormat(cur->Format()->format);
}
} else {
dstSampleBuffers = 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 != srcDepthFormat)
{
webgl->ErrorInvalidOperation("%s: Depth buffer formats must match if selected.",
funcName);
return;
}
if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
dstStencilFormat != srcStencilFormat)
{
webgl->ErrorInvalidOperation("%s: Stencil buffer formats must match if selected.",
funcName);
return;
}
////
if (dstSampleBuffers) {
webgl->ErrorInvalidOperation("%s: DRAW_FRAMEBUFFER may not have multiple"
" samples.",
funcName);
return;
}
if (srcSampleBuffers) {
if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
!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 auto fnValidateBuffers = [&](GLenum bufferBit, const char* bufferBitName,
bool srcHas, bool dstHas)
{
if (mask & bufferBit &&
srcHas != dstHas)
{
webgl->ErrorInvalidOperation("%s: With %s, must have a corresponding draw"
" buffer iff there's a relevent read"
" buffer.",
funcName, bufferBitName);
return false;
}
return true;
};
if (!fnValidateBuffers( LOCAL_GL_COLOR_BUFFER_BIT, "COLOR_BUFFER_BIT",
bool(srcFB->mColorReadBuffer),
bool(dstFB->mColorDrawBuffers.size()) ) ||
!fnValidateBuffers( LOCAL_GL_DEPTH_BUFFER_BIT, "DEPTH_BUFFER_BIT",
bool(srcFB->mResolvedCompleteData->depthBuffer),
bool(dstFB->mResolvedCompleteData->depthBuffer) ) ||
!fnValidateBuffers( LOCAL_GL_STENCIL_BUFFER_BIT, "STENCIL_BUFFER_BIT",
bool(srcFB->mResolvedCompleteData->stencilBuffer),
bool(dstFB->mResolvedCompleteData->stencilBuffer) ))
{
return;
}
const auto& readSet = srcFB->mResolvedCompleteData->readSet;
const auto& drawSet = dstFB->mResolvedCompleteData->drawSet;
std::vector<WebGLFBAttachPoint::Ordered> intersection;
std::set_intersection(drawSet.begin(), drawSet.end(),
readSet.begin(), readSet.end(),
std::back_inserter(intersection));
if (intersection.size()) {
// set_intersection pulls from the first range, so it records conflicts on the
// DRAW_FRAMEBUFFER.
const auto& example = intersection.cbegin()->mRef;
webgl->ErrorInvalidOperation("%s: Feedback detected into DRAW_FRAMEBUFFER's"
" 0x%04x attachment.",
funcName, example.mAttachmentPoint);
return;
}
} else if (!srcFB && !dstFB) {
webgl->ErrorInvalidOperation("%s: Feedback with default framebuffer.", funcName);
return;
}
////
gl->MakeCurrent();
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