dom/canvas/WebGLFramebuffer.cpp
author Jeff Gilbert <jgilbert@mozilla.com>
Thu, 21 Jul 2016 23:36:14 -0700
changeset 391214 2daec738be0ec28df1503a2b637606f9ebb76dcd
parent 388912 be7d2dd9f84fd2b05d862d5715621189f9ccf6d3
child 391216 17a88ce695956d75c290afa35f6e30c37fbfd491
permissions -rw-r--r--
Bug 1288643 - mContext is mutable, so is now protected. - r=jerry MozReview-Commit-ID: 1R2qZVngSYI

/* -*- 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"

#include "GLContext.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(WebGLFramebuffer* fb, GLenum attachmentPoint)
    : mFB(fb)
    , mAttachmentPoint(attachmentPoint)
    , mTexImageTarget(LOCAL_GL_NONE)
    , mTexImageLayer(0)
    , mTexImageLevel(0)
{ }

WebGLFBAttachPoint::~WebGLFBAttachPoint()
{
    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;
}

const webgl::FormatUsageInfo*
WebGLFramebuffer::GetFormatForAttachment(const WebGLFBAttachPoint& attachment) const
{
    MOZ_ASSERT(attachment.IsDefined());
    MOZ_ASSERT(attachment.Texture() || attachment.Renderbuffer());

    return attachment.Format();
}

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)
{
    SetTexImageLayer(tex, target, level, 0);
}

void
WebGLFBAttachPoint::SetTexImageLayer(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)
{
    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::FinalizeAttachment(gl::GLContext* gl, GLenum attachment) const
{
    if (!HasImage()) {
        switch (attachment) {
        case LOCAL_GL_DEPTH_ATTACHMENT:
        case LOCAL_GL_STENCIL_ATTACHMENT:
        case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
            break;

        default:
            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment,
                                         LOCAL_GL_RENDERBUFFER, 0);
            break;
        }

        return;
    }
    MOZ_ASSERT(HasImage());

    if (Texture()) {
        MOZ_ASSERT(gl == Texture()->Context()->GL());

        const GLenum imageTarget = ImageTarget().get();
        const GLint mipLevel = MipLevel();
        const GLint layer = Layer();
        const GLuint glName = Texture()->mGLName;

        switch (imageTarget) {
        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 (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                          imageTarget, glName, mipLevel);
                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER,
                                          LOCAL_GL_STENCIL_ATTACHMENT, imageTarget,
                                          glName, mipLevel);
            } else {
                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachment, imageTarget,
                                          glName, mipLevel);
            }
            break;

        case LOCAL_GL_TEXTURE_2D_ARRAY:
        case LOCAL_GL_TEXTURE_3D:
            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
                                             LOCAL_GL_DEPTH_ATTACHMENT, glName, mipLevel,
                                             layer);
                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER,
                                             LOCAL_GL_STENCIL_ATTACHMENT, glName,
                                             mipLevel, layer);
            } else {
                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachment, glName,
                                             mipLevel, layer);
            }
            break;
        }
        return;
    }

    if (Renderbuffer()) {
        Renderbuffer()->DoFramebufferRenderbuffer(attachment);
        return;
    }

    MOZ_CRASH("GFX: invalid render buffer");
}

JS::Value
WebGLFBAttachPoint::GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
                                 GLenum target, GLenum attachment, GLenum pname,
                                 ErrorResult* const out_error)
{
    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();

    const 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);

        switch (format->componentType) {
        case webgl::ComponentType::Special:
            MOZ_ASSERT(false, "Should never happen.");
            break;
        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;
        }
        break;

    default:
        MOZ_ASSERT(false, "Missing case.");
        break;
    }

    return JS::Int32Value(ret);
}

////////////////////////////////////////////////////////////////////////////////
// WebGLFramebuffer

WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
    : WebGLContextBoundObject(webgl)
    , mGLName(fbo)
    , mIsKnownFBComplete(false)
    , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
    , mColorAttachment0(this, LOCAL_GL_COLOR_ATTACHMENT0)
    , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT)
    , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT)
    , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
    , mMoreColorAttachments(webgl->mGLMaxColorAttachments)
    , mDrawBuffers(1, LOCAL_GL_COLOR_ATTACHMENT0)
#ifdef ANDROID
    , mIsFB(false)
#endif
{
    mContext->mFramebuffers.insertBack(this);
}

void
WebGLFramebuffer::Delete()
{
    mColorAttachment0.Clear();
    mDepthAttachment.Clear();
    mStencilAttachment.Clear();
    mDepthStencilAttachment.Clear();

    for (auto& cur : mMoreColorAttachments) {
        cur.Clear();
    }

    mContext->MakeContextCurrent();
    mContext->gl->fDeleteFramebuffers(1, &mGLName);

    LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);

#ifdef ANDROID
    mIsFB = false;
#endif
}

void
WebGLFramebuffer::FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
                                          WebGLRenderbuffer* rb)
{
    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
               mContext->mBoundReadFramebuffer == this);

    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", rb))
        return;

    // `attachPointEnum` is validated by ValidateFramebufferAttachment().

    RefPtr<WebGLRenderbuffer> rb_ = rb; // Bug 1201275
    const auto fnAttach = [this, &rb_](GLenum attachment) {
        const auto attachPoint = this->GetAttachPoint(attachment);
        MOZ_ASSERT(attachPoint);

        attachPoint->SetRenderbuffer(rb_);
    };

    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
    } else {
        fnAttach(attachment);
    }

    InvalidateFramebufferStatus();
}

void
WebGLFramebuffer::FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
                                       WebGLTexture* tex, GLint level)
{
    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
               mContext->mBoundReadFramebuffer == this);

    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
        return;

    if (tex) {
        if (!tex->HasEverBeenBound()) {
            mContext->ErrorInvalidOperation("framebufferTexture2D: the texture"
                                            " is not the name of a texture.");
            return;
        }

        const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget);
        if (tex->Target() != destTexTarget) {
            mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
                                            " texture and texture target.");
            return;
        }
    }

    RefPtr<WebGLTexture> tex_ = tex; // Bug 1201275
    const auto fnAttach = [this, &tex_, texImageTarget, level](GLenum attachment) {
        const auto attachPoint = this->GetAttachPoint(attachment);
        MOZ_ASSERT(attachPoint);

        attachPoint->SetTexImage(tex_, texImageTarget, level);
    };

    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
    } else {
        fnAttach(attachment);
    }

    InvalidateFramebufferStatus();
}

void
WebGLFramebuffer::FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex,
                                          GLint level, GLint layer)
{
    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
               mContext->mBoundReadFramebuffer == this);

    const TexImageTarget texImageTarget = (tex ? tex->Target().get()
                                               : LOCAL_GL_TEXTURE_2D);

    RefPtr<WebGLTexture> tex_ = tex; // Bug 1201275
    const auto fnAttach = [this, &tex_, texImageTarget, level, layer](GLenum attachment) {
        const auto attachPoint = this->GetAttachPoint(attachment);
        MOZ_ASSERT(attachPoint);

        attachPoint->SetTexImageLayer(tex_, texImageTarget, level, layer);
    };

    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
    } else {
        fnAttach(attachment);
    }

    InvalidateFramebufferStatus();
}

WebGLFBAttachPoint*
WebGLFramebuffer::GetAttachPoint(GLenum attachPoint)
{
    switch (attachPoint) {
    case LOCAL_GL_COLOR_ATTACHMENT0:
        return &mColorAttachment0;

    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
        return &mDepthStencilAttachment;

    case LOCAL_GL_DEPTH_ATTACHMENT:
        return &mDepthAttachment;

    case LOCAL_GL_STENCIL_ATTACHMENT:
        return &mStencilAttachment;

    default:
        break;
    }

    const auto lastCAEnum = mContext->LastColorAttachmentEnum();
    if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT1 ||
        attachPoint > lastCAEnum)
    {
        return nullptr;
    }

    if (!mMoreColorAttachments.Size()) {
        for (GLenum cur = LOCAL_GL_COLOR_ATTACHMENT1; cur <= lastCAEnum; cur++) {
            mMoreColorAttachments.AppendNew(this, cur);
        }
    }
    MOZ_ASSERT(LOCAL_GL_COLOR_ATTACHMENT0 + mMoreColorAttachments.Size() == lastCAEnum);

    const size_t offset = attachPoint - LOCAL_GL_COLOR_ATTACHMENT1;
    MOZ_ASSERT(offset <= mMoreColorAttachments.Size());
    return &mMoreColorAttachments[offset];
}

void
WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
{
    if (mColorAttachment0.Texture() == tex)
        mColorAttachment0.Clear();

    if (mDepthAttachment.Texture() == tex)
        mDepthAttachment.Clear();

    if (mStencilAttachment.Texture() == tex)
        mStencilAttachment.Clear();

    if (mDepthStencilAttachment.Texture() == tex)
        mDepthStencilAttachment.Clear();

    for (auto& cur : mMoreColorAttachments) {
        if (cur.Texture() == tex)
            cur.Clear();
    }
}

void
WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
{
    if (mColorAttachment0.Renderbuffer() == rb)
        mColorAttachment0.Clear();

    if (mDepthAttachment.Renderbuffer() == rb)
        mDepthAttachment.Clear();

    if (mStencilAttachment.Renderbuffer() == rb)
        mStencilAttachment.Clear();

    if (mDepthStencilAttachment.Renderbuffer() == rb)
        mDepthStencilAttachment.Clear();

    for (auto& cur : mMoreColorAttachments) {
        if (cur.Renderbuffer() == rb)
            cur.Clear();
    }
}

bool
WebGLFramebuffer::HasDefinedAttachments() const
{
    bool hasAttachments = false;

    hasAttachments |= mColorAttachment0.IsDefined();
    hasAttachments |= mDepthAttachment.IsDefined();
    hasAttachments |= mStencilAttachment.IsDefined();
    hasAttachments |= mDepthStencilAttachment.IsDefined();

    for (const auto& cur : mMoreColorAttachments) {
        hasAttachments |= cur.IsDefined();
    }

    return hasAttachments;
}

bool
WebGLFramebuffer::HasIncompleteAttachments(nsCString* const out_info) const
{
    const auto fnIsIncomplete = [this, out_info](const WebGLFBAttachPoint& cur) {
        if (!cur.IsDefined())
            return false; // Not defined, so can't count as incomplete.

        return !cur.IsComplete(this->mContext, out_info);
    };

    bool hasIncomplete = false;

    hasIncomplete |= fnIsIncomplete(mColorAttachment0);
    hasIncomplete |= fnIsIncomplete(mDepthAttachment);
    hasIncomplete |= fnIsIncomplete(mStencilAttachment);
    hasIncomplete |= fnIsIncomplete(mDepthStencilAttachment);

    for (const auto& cur : mMoreColorAttachments) {
        hasIncomplete |= fnIsIncomplete(cur);
    }

    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;

    const auto fnInitializeOrMatch = [&needsInit, &width,
                                      &height](const WebGLFBAttachPoint& attach)
    {
        if (!attach.HasImage())
            return true;

        uint32_t curWidth;
        uint32_t curHeight;
        attach.Size(&curWidth, &curHeight);

        if (needsInit) {
            needsInit = false;
            width = curWidth;
            height = curHeight;
            return true;
        }

        return (curWidth == width &&
                curHeight == height);
    };

    bool matches = true;

    matches &= fnInitializeOrMatch(mColorAttachment0      );
    matches &= fnInitializeOrMatch(mDepthAttachment       );
    matches &= fnInitializeOrMatch(mStencilAttachment     );
    matches &= fnInitializeOrMatch(mDepthStencilAttachment);

    for (const auto& cur : mMoreColorAttachments) {
        matches &= fnInitializeOrMatch(cur);
    }

    return matches;
}

bool
WebGLFramebuffer::AllImageSamplesMatch() const
{
    MOZ_ASSERT(HasDefinedAttachments());
    DebugOnly<nsCString> fbStatusInfo;
    MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));

    bool needsInit = true;
    uint32_t samples = 0;

    const auto fnInitializeOrMatch = [&needsInit,
                                      &samples](const WebGLFBAttachPoint& attach)
    {
        if (!attach.HasImage())
          return true;

        const uint32_t curSamples = attach.Samples();

        if (needsInit) {
            needsInit = false;
            samples = curSamples;
            return true;
        }

        return (curSamples == samples);
    };

    bool matches = true;

    matches &= fnInitializeOrMatch(mColorAttachment0      );
    matches &= fnInitializeOrMatch(mDepthAttachment       );
    matches &= fnInitializeOrMatch(mStencilAttachment     );
    matches &= fnInitializeOrMatch(mDepthStencilAttachment);

    for (const auto& cur : mMoreColorAttachments) {
        matches &= fnInitializeOrMatch(cur);
    }

    return matches;
}

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()) {
        // INCOMPLETE_DIMENSIONS doesn't exist in GLES3.
        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;
}

FBStatus
WebGLFramebuffer::CheckFramebufferStatus(nsCString* const out_info) const
{
    if (mIsKnownFBComplete)
        return LOCAL_GL_FRAMEBUFFER_COMPLETE;

    FBStatus ret = PrecheckFramebufferStatus(out_info);
    if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE)
        return ret;

    // Looks good on our end. Let's ask the driver.
    mContext->MakeContextCurrent();

    // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}.
    FinalizeAttachments();

    // TODO: This should not be unconditionally GL_FRAMEBUFFER.
    ret = mContext->gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);

    if (ret == LOCAL_GL_FRAMEBUFFER_COMPLETE) {
        mIsKnownFBComplete = true;
    } else {
        out_info->AssignLiteral("Bad status according to the driver");
    }

    return ret;
}

bool
WebGLFramebuffer::ValidateAndInitAttachments(const char* funcName)
{
    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
               mContext->mBoundReadFramebuffer == this);

    nsCString fbStatusInfo;
    const auto fbStatus = CheckFramebufferStatus(&fbStatusInfo);
    if (fbStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
        nsCString errorText = nsPrintfCString("Incomplete framebuffer: Status 0x%04x",
                                              fbStatus.get());
        if (fbStatusInfo.Length()) {
            errorText += ": ";
            errorText += fbStatusInfo;
        }

        mContext->ErrorInvalidFramebufferOperation("%s: %s.", funcName,
                                                   errorText.BeginReading());
        return false;
    }

    // Cool! We've checked out ok. Just need to initialize.

    //////
    // Check if we need to initialize anything

    std::vector<WebGLFBAttachPoint*> tex3DToClear;

    const auto fnGatherIf3D = [&](WebGLFBAttachPoint& attach) {
        if (!attach.Texture())
            return false;

        const auto& info = attach.Texture()->ImageInfoAt(attach.ImageTarget(),
                                                         attach.MipLevel());
        if (info.mDepth == 1)
            return false;

        tex3DToClear.push_back(&attach);
        return true;
    };

    //////

    uint32_t clearBits = 0;
    std::vector<GLenum> drawBuffersForClear(1 + mMoreColorAttachments.Size(),
                                            LOCAL_GL_NONE);

    std::vector<WebGLFBAttachPoint*> attachmentsToClear;
    attachmentsToClear.reserve(1 + mMoreColorAttachments.Size() + 3);

    const auto fnGatherColor = [&](WebGLFBAttachPoint& attach, uint32_t colorAttachNum) {
        if (!IsDrawBuffer(colorAttachNum) || !attach.HasUninitializedImageData())
            return;

        if (fnGatherIf3D(attach))
            return;

        attachmentsToClear.push_back(&attach);

        clearBits |= LOCAL_GL_COLOR_BUFFER_BIT;
        drawBuffersForClear[colorAttachNum] = LOCAL_GL_COLOR_ATTACHMENT0 + colorAttachNum;
    };

    const auto fnGatherOther = [&](WebGLFBAttachPoint& attach, GLenum attachClearBits) {
        if (!attach.HasUninitializedImageData())
            return;

        if (fnGatherIf3D(attach))
            return;

        attachmentsToClear.push_back(&attach);

        clearBits |= attachClearBits;
    };

    //////

    fnGatherColor(mColorAttachment0, 0);

    size_t colorAttachNum = 1;
    for (auto& cur : mMoreColorAttachments) {
        fnGatherColor(cur, colorAttachNum);
        ++colorAttachNum;
    }

    fnGatherOther(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
    fnGatherOther(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
    fnGatherOther(mDepthStencilAttachment,
                  LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT);

    //////

    mContext->MakeContextCurrent();

    if (clearBits) {
        const auto fnDrawBuffers = [this](const std::vector<GLenum>& list) {
            this->mContext->gl->fDrawBuffers(list.size(), list.data());
        };

        const auto drawBufferExt = WebGLExtensionID::WEBGL_draw_buffers;
        const bool hasDrawBuffers = (mContext->IsWebGL2() ||
                                     mContext->IsExtensionEnabled(drawBufferExt));

        if (hasDrawBuffers) {
            fnDrawBuffers(drawBuffersForClear);
        }

        ////////////

        // Clear!
        {
            gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName);

            mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
        }

        if (hasDrawBuffers) {
            fnDrawBuffers(mDrawBuffers);
        }

        for (auto* cur : attachmentsToClear) {
            cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
        }
    }

    //////

    for (auto* attach : tex3DToClear) {
        auto* tex = attach->Texture();
        if (!tex->InitializeImageData(funcName, attach->ImageTarget(),
                                      attach->MipLevel()))
        {
            return false;
        }
    }

    return true;
}

static void
FinalizeDrawAndReadBuffers(gl::GLContext* gl, bool isColorBufferDefined)
{
    MOZ_ASSERT(gl, "Expected a valid GLContext ptr.");
    // GLES don't support DrawBuffer()/ReadBuffer.
    // According to http://www.opengl.org/wiki/Framebuffer_Object
    //
    // Each draw buffers must either specify color attachment points that have images
    // attached or must be GL_NONE​. (GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER​ when false).
    //
    // If the read buffer is set, then it must specify an attachment point that has an
    // image attached. (GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER​ when false).
    //
    // Note that this test is not performed if OpenGL 4.2 or ARB_ES2_compatibility is
    // available.
    if (gl->IsGLES() ||
        gl->IsSupported(gl::GLFeature::ES2_compatibility) ||
        gl->IsAtLeast(gl::ContextProfile::OpenGL, 420))
    {
        return;
    }

    // TODO(djg): Assert that fDrawBuffer/fReadBuffer is not NULL.
    GLenum colorBufferSource = isColorBufferDefined ? LOCAL_GL_COLOR_ATTACHMENT0
                                                    : LOCAL_GL_NONE;
    gl->fDrawBuffer(colorBufferSource);
    gl->fReadBuffer(colorBufferSource);
}

void
WebGLFramebuffer::FinalizeAttachments() const
{
    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
               mContext->mBoundReadFramebuffer == this);

    gl::GLContext* gl = mContext->gl;

    // Nuke the depth and stencil attachment points.
    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);

    // Call finalize.
    mColorAttachment0.FinalizeAttachment(gl, LOCAL_GL_COLOR_ATTACHMENT0);
    mDepthAttachment.FinalizeAttachment(gl, LOCAL_GL_DEPTH_ATTACHMENT);
    mStencilAttachment.FinalizeAttachment(gl, LOCAL_GL_STENCIL_ATTACHMENT);
    mDepthStencilAttachment.FinalizeAttachment(gl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);

    for (size_t i = 0; i < mMoreColorAttachments.Size(); i++) {
        GLenum attachPoint = LOCAL_GL_COLOR_ATTACHMENT1 + i;
        mMoreColorAttachments[i].FinalizeAttachment(gl, attachPoint);
    }

    FinalizeDrawAndReadBuffers(gl, mColorAttachment0.IsDefined());
}

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 (mReadBufferMode == LOCAL_GL_NONE) {
        mContext->ErrorInvalidOperation("%s: Read buffer mode must not be NONE.",
                                        funcName);
        return false;
    }

    const auto attachPoint = GetAttachPoint(mReadBufferMode);
    if (!attachPoint || !attachPoint->IsDefined()) {
        mContext->ErrorInvalidOperation("%s: The attachment specified for reading is"
                                        " null.", funcName);
        return false;
    }

    *out_format = attachPoint->Format();
    attachPoint->Size(out_width, out_height);
    return true;
}

static bool
AttachmentsDontMatch(const WebGLFBAttachPoint& a, const WebGLFBAttachPoint& b)
{
    if (a.Texture()) {
        return (a.Texture() != b.Texture());
    }

    if (a.Renderbuffer()) {
        return (a.Renderbuffer() != b.Renderbuffer());
    }

    return false;
}

JS::Value
WebGLFramebuffer::GetAttachmentParameter(const char* funcName, JSContext* cx,
                                         GLenum target, GLenum attachment,
                                         GLenum pname, ErrorResult* const out_error)
{
    auto attachPoint = GetAttachPoint(attachment);
    if (!attachPoint) {
        mContext->ErrorInvalidEnum("%s: Can only query COLOR_ATTACHMENTi,"
                                   " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or"
                                   " STENCIL_ATTACHMENT for a framebuffer.",
                                   funcName);
        return JS::NullValue();
    }

    if (mContext->IsWebGL2() && attachment == 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 (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
            mContext->ErrorInvalidOperation("%s: DEPTH_ATTACHMENT and STENCIL_ATTACHMENT"
                                            " have different objects bound.",
                                            funcName);
            return JS::NullValue();
        }

        attachPoint = GetAttachPoint(LOCAL_GL_DEPTH_ATTACHMENT);
    }

    FinalizeAttachments();

    return attachPoint->GetParameter(funcName, mContext, cx, target, attachment, pname,
                                     out_error);
}


void
WebGLFramebuffer::GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const
{
    auto itr = mDrawBuffers.cbegin();
    if (itr != mDrawBuffers.cend() &&
        *itr != LOCAL_GL_NONE)
    {
        out->push_back(&mColorAttachment0);
        ++itr;
    }

    size_t i = 0;
    for (; itr != mDrawBuffers.cend(); ++itr) {
        if (i >= mMoreColorAttachments.Size())
            break;

        if (*itr != LOCAL_GL_NONE) {
            out->push_back(&mMoreColorAttachments[i]);
        }
        ++i;
    }

    out->push_back(&mDepthAttachment);
    out->push_back(&mStencilAttachment);
    out->push_back(&mDepthStencilAttachment);
}

////////////////////////////////////////////////////////////////////////////////
// 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,
                                      mColorAttachment0,
                                      mDepthAttachment,
                                      mStencilAttachment,
                                      mDepthStencilAttachment,
                                      mMoreColorAttachments)

NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLFramebuffer, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLFramebuffer, Release)

} // namespace mozilla