Back out f7657171e034 (bug 1017865) for relanding after already being backed out because it causes orange
authorPhil Ringnalda <philringnalda@gmail.com>
Mon, 16 Mar 2015 21:32:52 -0700
changeset 264304 c39e4d4e5ba1c7f003a502579bb7a73ca927f0c3
parent 264303 6bcba8fcdafd76c535cb520588d95b31da0928a4
child 264305 fce881b139dc83758ec00ffb0a3383b775a2aa9a
push id830
push userraliiev@mozilla.com
push dateFri, 19 Jun 2015 19:24:37 +0000
treeherdermozilla-release@932614382a68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1017865
milestone39.0a1
backs outf7657171e034c69479e918b82afa1e0a63f84c7d
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Back out f7657171e034 (bug 1017865) for relanding after already being backed out because it causes orange CLOSED TREE
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextUtils.cpp
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLFramebufferAttachable.cpp
dom/canvas/WebGLFramebufferAttachable.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLTexture.cpp
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -709,20 +709,22 @@ WebGLContext::DeleteRenderbuffer(WebGLRe
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachRenderbuffer(rbuf);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachRenderbuffer(rbuf);
 
-    rbuf->InvalidateStatusOfAttachedFBs();
+    // Invalidate framebuffer status cache
+    rbuf->NotifyFBsStatusChanged();
 
     if (mBoundRenderbuffer == rbuf)
-        BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
+        BindRenderbuffer(LOCAL_GL_RENDERBUFFER,
+                         static_cast<WebGLRenderbuffer*>(nullptr));
 
     rbuf->RequestDelete();
 }
 
 void
 WebGLContext::DeleteTexture(WebGLTexture* tex)
 {
     if (IsContextLost())
@@ -735,26 +737,27 @@ WebGLContext::DeleteTexture(WebGLTexture
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachTexture(tex);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachTexture(tex);
 
-    tex->InvalidateStatusOfAttachedFBs();
+    // Invalidate framebuffer status cache
+    tex->NotifyFBsStatusChanged();
 
     GLuint activeTexture = mActiveTexture;
     for (int32_t i = 0; i < mGLMaxTextureUnits; i++) {
         if ((mBound2DTextures[i] == tex && tex->Target() == LOCAL_GL_TEXTURE_2D) ||
             (mBoundCubeMapTextures[i] == tex && tex->Target() == LOCAL_GL_TEXTURE_CUBE_MAP) ||
             (mBound3DTextures[i] == tex && tex->Target() == LOCAL_GL_TEXTURE_3D))
         {
             ActiveTexture(LOCAL_GL_TEXTURE0 + i);
-            BindTexture(tex->Target().get(), nullptr);
+            BindTexture(tex->Target().get(), static_cast<WebGLTexture*>(nullptr));
         }
     }
     ActiveTexture(LOCAL_GL_TEXTURE0 + activeTexture);
 
     tex->RequestDelete();
 }
 
 void
@@ -860,18 +863,21 @@ WebGLContext::FramebufferRenderbuffer(GL
                                      " framebuffer 0.");
     }
 
     if (rbtarget != LOCAL_GL_RENDERBUFFER) {
         return ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:",
                                     rbtarget);
     }
 
-    if (!ValidateFramebufferAttachment(fb, attachment, "framebufferRenderbuffer"))
+    if (!ValidateFramebufferAttachment(fb, attachment,
+                                       "framebufferRenderbuffer"))
+    {
         return;
+    }
 
     fb->FramebufferRenderbuffer(attachment, rbtarget, wrb);
 }
 
 void
 WebGLContext::FramebufferTexture2D(GLenum target,
                                    GLenum attachment,
                                    GLenum textarget,
@@ -1130,21 +1136,21 @@ WebGLContext::GetFramebufferAttachmentPa
 
     if (!ValidateFramebufferAttachment(fb, attachment,
                                        "getFramebufferAttachmentParameter"))
     {
         return JS::NullValue();
     }
 
     if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
-        fb->EnsureColorAttachPoints(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
+        fb->EnsureColorAttachments(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
 
     MakeContextCurrent();
 
-    const WebGLFramebuffer::AttachPoint& fba = fb->GetAttachPoint(attachment);
+    const WebGLFramebuffer::Attachment& fba = fb->GetAttachment(attachment);
 
     if (fba.Renderbuffer()) {
         switch (pname) {
             case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
                 if (IsExtensionEnabled(WebGLExtensionID::EXT_sRGB)) {
                     const GLenum internalFormat = fba.Renderbuffer()->InternalFormat();
                     return (internalFormat == LOCAL_GL_SRGB_EXT ||
                             internalFormat == LOCAL_GL_SRGB_ALPHA_EXT ||
@@ -2313,16 +2319,18 @@ WebGLContext::RenderbufferStorage_base(c
     MakeContextCurrent();
 
     bool willRealloc = samples != mBoundRenderbuffer->Samples() ||
                        internalFormat != mBoundRenderbuffer->InternalFormat() ||
                        width != mBoundRenderbuffer->Width() ||
                        height != mBoundRenderbuffer->Height();
 
     if (willRealloc) {
+        // Invalidate framebuffer status cache
+        mBoundRenderbuffer->NotifyFBsStatusChanged();
         GetAndFlushUnderlyingGLErrors();
         mBoundRenderbuffer->RenderbufferStorage(samples, internalFormatForGL,
                                                 width, height);
         GLenum error = GetAndFlushUnderlyingGLErrors();
         if (error) {
             GenerateWarning("%s generated error %s", funcName,
                             ErrorName(error));
             return;
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -599,17 +599,16 @@ WebGLContext::EnumName(GLenum glenum)
         XX(COMPRESSED_RGBA_PVRTC_2BPPV1);
         XX(COMPRESSED_RGBA_PVRTC_4BPPV1);
         XX(COMPRESSED_RGBA_S3TC_DXT1_EXT);
         XX(COMPRESSED_RGBA_S3TC_DXT3_EXT);
         XX(COMPRESSED_RGBA_S3TC_DXT5_EXT);
         XX(COMPRESSED_RGB_PVRTC_2BPPV1);
         XX(COMPRESSED_RGB_PVRTC_4BPPV1);
         XX(COMPRESSED_RGB_S3TC_DXT1_EXT);
-        XX(DEPTH_ATTACHMENT);
         XX(DEPTH_COMPONENT);
         XX(DEPTH_COMPONENT16);
         XX(DEPTH_COMPONENT32);
         XX(DEPTH_STENCIL);
         XX(DEPTH24_STENCIL8);
         XX(DRAW_FRAMEBUFFER);
         XX(ETC1_RGB8_OES);
         XX(FLOAT);
@@ -785,17 +784,16 @@ WebGLContext::EnumName(GLenum glenum)
         XX(FRAMEBUFFER_DEFAULT);
         XX(DEPTH_STENCIL_ATTACHMENT);
         XX(UNSIGNED_NORMALIZED);
         XX(DRAW_FRAMEBUFFER_BINDING);
         XX(READ_FRAMEBUFFER_BINDING);
         XX(RENDERBUFFER_SAMPLES);
         XX(FRAMEBUFFER_ATTACHMENT_TEXTURE_LAYER);
         XX(MAX_COLOR_ATTACHMENTS);
-        XX(COLOR_ATTACHMENT0);
         XX(COLOR_ATTACHMENT1);
         XX(COLOR_ATTACHMENT2);
         XX(COLOR_ATTACHMENT3);
         XX(COLOR_ATTACHMENT4);
         XX(COLOR_ATTACHMENT5);
         XX(COLOR_ATTACHMENT6);
         XX(COLOR_ATTACHMENT7);
         XX(COLOR_ATTACHMENT8);
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -10,46 +10,70 @@
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
-WebGLFramebuffer::AttachPoint::AttachPoint(WebGLFramebuffer* fb,
-                                           FBAttachment attachmentPoint)
-    : mFB(fb)
-    , mAttachmentPoint(attachmentPoint)
+JSObject*
+WebGLFramebuffer::WrapObject(JSContext* cx)
+{
+    return dom::WebGLFramebufferBinding::Wrap(cx, this);
+}
+
+WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
+    : WebGLBindableName<FBTarget>(fbo)
+    , WebGLContextBoundObject(webgl)
+    , mStatus(0)
+    , mDepthAttachment(LOCAL_GL_DEPTH_ATTACHMENT)
+    , mStencilAttachment(LOCAL_GL_STENCIL_ATTACHMENT)
+    , mDepthStencilAttachment(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
+    , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
+{
+    mContext->mFramebuffers.insertBack(this);
+
+    mColorAttachments.SetLength(1);
+    mColorAttachments[0].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0;
+}
+
+WebGLFramebuffer::Attachment::Attachment(FBAttachment attachmentPoint)
+    : mAttachmentPoint(attachmentPoint)
     , mTexImageTarget(LOCAL_GL_NONE)
+    , mNeedsFinalize(false)
 {}
 
-WebGLFramebuffer::AttachPoint::~AttachPoint()
+WebGLFramebuffer::Attachment::~Attachment()
+{}
+
+void
+WebGLFramebuffer::Attachment::Reset()
 {
-    MOZ_ASSERT(!mRenderbufferPtr);
-    MOZ_ASSERT(!mTexturePtr);
+    mTexturePtr = nullptr;
+    mRenderbufferPtr = nullptr;
 }
 
 bool
-WebGLFramebuffer::AttachPoint::IsDeleteRequested() const
+WebGLFramebuffer::Attachment::IsDeleteRequested() const
 {
     return Texture() ? Texture()->IsDeleteRequested()
          : Renderbuffer() ? Renderbuffer()->IsDeleteRequested()
          : false;
 }
 
 bool
-WebGLFramebuffer::AttachPoint::IsDefined() const
+WebGLFramebuffer::Attachment::IsDefined() const
 {
     return Renderbuffer() ||
            (Texture() && Texture()->HasImageInfoAt(ImageTarget(), 0));
 }
 
 bool
-WebGLFramebuffer::AttachPoint::HasAlpha() const
+WebGLFramebuffer::Attachment::HasAlpha() const
 {
     MOZ_ASSERT(HasImage());
 
     if (Texture() &&
         Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
     {
         return FormatHasAlpha(Texture()->ImageInfoAt(mTexImageTarget,
                                                      mTexImageLevel).EffectiveInternalFormat());
@@ -57,17 +81,17 @@ WebGLFramebuffer::AttachPoint::HasAlpha(
 
     if (Renderbuffer())
         return FormatHasAlpha(Renderbuffer()->InternalFormat());
 
     return false;
 }
 
 GLenum
-WebGLFramebuffer::GetFormatForAttachment(const WebGLFramebuffer::AttachPoint& attachment) const
+WebGLFramebuffer::GetFormatForAttachment(const WebGLFramebuffer::Attachment& attachment) const
 {
     MOZ_ASSERT(attachment.IsDefined());
     MOZ_ASSERT(attachment.Texture() || attachment.Renderbuffer());
 
     if (attachment.Texture()) {
         const WebGLTexture& tex = *attachment.Texture();
         MOZ_ASSERT(tex.HasImageInfoAt(attachment.ImageTarget(), 0));
 
@@ -78,86 +102,65 @@ WebGLFramebuffer::GetFormatForAttachment
 
     if (attachment.Renderbuffer())
         return attachment.Renderbuffer()->InternalFormat();
 
     return LOCAL_GL_NONE;
 }
 
 TexInternalFormat
-WebGLFramebuffer::AttachPoint::EffectiveInternalFormat() const
+WebGLFramebuffer::Attachment::EffectiveInternalFormat() const
 {
     const WebGLTexture* tex = Texture();
     if (tex && tex->HasImageInfoAt(mTexImageTarget, mTexImageLevel)) {
         return tex->ImageInfoAt(mTexImageTarget,
                                 mTexImageLevel).EffectiveInternalFormat();
     }
 
     const WebGLRenderbuffer* rb = Renderbuffer();
     if (rb)
         return rb->InternalFormat();
 
     return LOCAL_GL_NONE;
 }
 
 bool
-WebGLFramebuffer::AttachPoint::IsReadableFloat() const
+WebGLFramebuffer::Attachment::IsReadableFloat() const
 {
     TexInternalFormat internalformat = EffectiveInternalFormat();
     MOZ_ASSERT(internalformat != LOCAL_GL_NONE);
     TexType type = TypeFromInternalFormat(internalformat);
     return type == LOCAL_GL_FLOAT ||
            type == LOCAL_GL_HALF_FLOAT_OES ||
            type == LOCAL_GL_HALF_FLOAT;
 }
 
-static void
-UnmarkAttachment(WebGLFramebuffer::AttachPoint& attachment)
+void
+WebGLFramebuffer::Attachment::SetTexImage(WebGLTexture* tex,
+                                          TexImageTarget target, GLint level)
 {
-    WebGLFramebufferAttachable* maybe = attachment.Texture();
-    if (!maybe)
-        maybe = attachment.Renderbuffer();
-
-    if (maybe)
-        maybe->UnmarkAttachment(attachment);
-}
-
-void
-WebGLFramebuffer::AttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target,
-                                          GLint level)
-{
-    mFB->InvalidateFramebufferStatus();
-
-    UnmarkAttachment(*this);
-
     mTexturePtr = tex;
     mRenderbufferPtr = nullptr;
     mTexImageTarget = target;
     mTexImageLevel = level;
 
-    if (tex)
-        tex->MarkAttachment(*this);
+    mNeedsFinalize = true;
 }
 
 void
-WebGLFramebuffer::AttachPoint::SetRenderbuffer(WebGLRenderbuffer* rb)
+WebGLFramebuffer::Attachment::SetRenderbuffer(WebGLRenderbuffer* rb)
 {
-    mFB->InvalidateFramebufferStatus();
-
-    UnmarkAttachment(*this);
-
     mTexturePtr = nullptr;
     mRenderbufferPtr = rb;
 
-    if (rb)
-        rb->MarkAttachment(*this);
+    mNeedsFinalize = true;
 }
 
 bool
-WebGLFramebuffer::AttachPoint::HasUninitializedImageData() const
+WebGLFramebuffer::Attachment::HasUninitializedImageData() const
 {
     if (!HasImage())
         return false;
 
     if (Renderbuffer())
         return Renderbuffer()->HasUninitializedImageData();
 
     if (Texture()) {
@@ -166,17 +169,17 @@ WebGLFramebuffer::AttachPoint::HasUninit
                                       mTexImageLevel).HasUninitializedImageData();
     }
 
     MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
 
 void
-WebGLFramebuffer::AttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus)
+WebGLFramebuffer::Attachment::SetImageDataStatus(WebGLImageDataStatus newStatus)
 {
     if (!HasImage())
         return;
 
     if (Renderbuffer()) {
         Renderbuffer()->SetImageDataStatus(newStatus);
         return;
     }
@@ -186,29 +189,29 @@ WebGLFramebuffer::AttachPoint::SetImageD
                                       newStatus);
         return;
     }
 
     MOZ_ASSERT(false, "Should not get here.");
 }
 
 bool
-WebGLFramebuffer::AttachPoint::HasImage() const
+WebGLFramebuffer::Attachment::HasImage() const
 {
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
         return true;
 
     if (Renderbuffer())
         return true;
 
     return false;
 }
 
 const WebGLRectangleObject&
-WebGLFramebuffer::AttachPoint::RectangleObject() const
+WebGLFramebuffer::Attachment::RectangleObject() const
 {
     MOZ_ASSERT(HasImage(),
                "Make sure it has an image before requesting the rectangle.");
 
     if (Texture()) {
         MOZ_ASSERT(Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel));
         return Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
     }
@@ -280,17 +283,17 @@ WebGLContext::IsFormatValidForFB(GLenum 
     case LOCAL_GL_RGBA16F:
         return IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float);
     }
 
     return false;
 }
 
 bool
-WebGLFramebuffer::AttachPoint::IsComplete() const
+WebGLFramebuffer::Attachment::IsComplete() const
 {
     if (!HasImage())
         return false;
 
     const WebGLRectangleObject& rect = RectangleObject();
 
     if (!rect.Width() ||
         !rect.Height())
@@ -348,326 +351,412 @@ WebGLFramebuffer::AttachPoint::IsComplet
         MOZ_ASSERT(false, "Invalid WebGL attachment point?");
         return false;
     }
     MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
 
 void
-WebGLFramebuffer::AttachPoint::FinalizeAttachment(gl::GLContext* gl,
+WebGLFramebuffer::Attachment::FinalizeAttachment(gl::GLContext* gl,
                                                  FBAttachment attachmentLoc) const
 {
+    if (!mNeedsFinalize)
+        return;
+
+    mNeedsFinalize = false;
+
     if (!HasImage()) {
-        switch (attachmentLoc.get()) {
-        case LOCAL_GL_DEPTH_ATTACHMENT:
-        case LOCAL_GL_STENCIL_ATTACHMENT:
-        case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-            break;
-
-        default:
-            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(),
+        if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
+                                         LOCAL_GL_DEPTH_ATTACHMENT,
                                          LOCAL_GL_RENDERBUFFER, 0);
-            break;
+            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
+                                         LOCAL_GL_STENCIL_ATTACHMENT,
+                                         LOCAL_GL_RENDERBUFFER, 0);
+        } else {
+            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
+                                         attachmentLoc.get(),
+                                         LOCAL_GL_RENDERBUFFER, 0);
         }
 
         return;
     }
     MOZ_ASSERT(HasImage());
 
     if (Texture()) {
         MOZ_ASSERT(gl == Texture()->Context()->GL());
 
         const GLenum imageTarget = ImageTarget().get();
         const GLint mipLevel = MipLevel();
         const GLuint glName = Texture()->GLName();
 
         if (attachmentLoc == 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);
+            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, attachmentLoc.get(),
                                       imageTarget, glName, mipLevel);
         }
         return;
     }
 
     if (Renderbuffer()) {
         Renderbuffer()->FramebufferRenderbuffer(attachmentLoc);
         return;
     }
 
-    MOZ_CRASH();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// WebGLFramebuffer
-
-WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
-    : WebGLBindableName<FBTarget>(fbo)
-    , WebGLContextBoundObject(webgl)
-    , mStatus(0)
-    , 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)
-{
-    mContext->mFramebuffers.insertBack(this);
+    MOZ_ASSERT(false, "Should not get here.");
 }
 
 void
 WebGLFramebuffer::Delete()
 {
-    mColorAttachment0.Clear();
-    mDepthAttachment.Clear();
-    mStencilAttachment.Clear();
-    mDepthStencilAttachment.Clear();
-
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        mMoreColorAttachments[i].Clear();
-    }
+    DetachAllAttachments();
+    mColorAttachments.Clear();
+    mDepthAttachment.Reset();
+    mStencilAttachment.Reset();
+    mDepthStencilAttachment.Reset();
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 }
 
 void
-WebGLFramebuffer::FramebufferRenderbuffer(FBAttachment attachPointEnum,
+WebGLFramebuffer::DetachAttachment(WebGLFramebuffer::Attachment& attachment)
+{
+    if (attachment.Texture())
+        attachment.Texture()->DetachFrom(this, attachment.mAttachmentPoint);
+
+    if (attachment.Renderbuffer()) {
+        attachment.Renderbuffer()->DetachFrom(this,
+                                              attachment.mAttachmentPoint);
+    }
+}
+
+void
+WebGLFramebuffer::DetachAllAttachments()
+{
+    for (size_t i = 0; i < mColorAttachments.Length(); i++) {
+        DetachAttachment(mColorAttachments[i]);
+    }
+
+    DetachAttachment(mDepthAttachment);
+    DetachAttachment(mStencilAttachment);
+    DetachAttachment(mDepthStencilAttachment);
+}
+
+void
+WebGLFramebuffer::FramebufferRenderbuffer(FBAttachment attachPoint,
                                           RBTarget rbtarget,
                                           WebGLRenderbuffer* rb)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
-    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", rb))
+    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer",
+                                           rb))
+    {
         return;
+    }
 
-    // `attachPoint` is validated by ValidateFramebufferAttachment().
-    AttachPoint& attachPoint = GetAttachPoint(attachPointEnum);
-    attachPoint.SetRenderbuffer(rb);
+    /* Get the requested attachment. If result is NULL, attachment is invalid
+     * and an error is generated.
+     *
+     * Don't use GetAttachment(...) here because it opt builds it returns
+     * mColorAttachment[0] for invalid attachment, which we really don't want to
+     * mess with.
+     */
+    Attachment* attachment = GetAttachmentOrNull(attachPoint);
+    if (!attachment)
+        return; // Error generated internally to GetAttachmentOrNull.
 
-    InvalidateFramebufferStatus();
+    // Invalidate cached framebuffer status and inform texture of its new
+    // attachment.
+    mStatus = 0;
+
+    // Detach current:
+    if (attachment->Texture())
+        attachment->Texture()->DetachFrom(this, attachPoint);
+    else if (attachment->Renderbuffer())
+        attachment->Renderbuffer()->DetachFrom(this, attachPoint);
+
+    // Attach new:
+    if (rb)
+        rb->AttachTo(this, attachPoint);
+
+    attachment->SetRenderbuffer(rb);
 }
 
 void
-WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPointEnum,
+WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPoint,
                                        TexImageTarget texImageTarget,
                                        WebGLTexture* tex, GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
-    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
+    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture",
+                                           tex))
+    {
         return;
+    }
 
     if (tex) {
         bool isTexture2D = tex->Target() == LOCAL_GL_TEXTURE_2D;
         bool isTexTarget2D = texImageTarget == LOCAL_GL_TEXTURE_2D;
         if (isTexture2D != isTexTarget2D) {
             mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
                                             " texture and texture target.");
             return;
         }
     }
 
     if (level != 0) {
         mContext->ErrorInvalidValue("framebufferTexture2D: Level must be 0.");
         return;
     }
 
-    AttachPoint& attachPoint = GetAttachPoint(attachPointEnum);
-    attachPoint.SetTexImage(tex, texImageTarget, level);
+    /* Get the requested attachment. If result is NULL, attachment is invalid
+     * and an error is generated.
+     *
+     * Don't use GetAttachment(...) here because it opt builds it returns
+     * mColorAttachment[0] for invalid attachment, which we really don't want to
+     * mess with.
+     */
+    Attachment* attachment = GetAttachmentOrNull(attachPoint);
+    if (!attachment)
+        return; // Error generated internally to GetAttachmentOrNull.
 
-    InvalidateFramebufferStatus();
+    // Invalidate cached framebuffer status and inform texture of its new
+    // attachment.
+    mStatus = 0;
+
+    // Detach current:
+    if (attachment->Texture())
+        attachment->Texture()->DetachFrom(this, attachPoint);
+    else if (attachment->Renderbuffer())
+        attachment->Renderbuffer()->DetachFrom(this, attachPoint);
+
+    // Attach new:
+    if (tex)
+        tex->AttachTo(this, attachPoint);
+
+    attachment->SetTexImage(tex, texImageTarget, level);
 }
 
-WebGLFramebuffer::AttachPoint&
-WebGLFramebuffer::GetAttachPoint(FBAttachment attachPoint)
+WebGLFramebuffer::Attachment*
+WebGLFramebuffer::GetAttachmentOrNull(FBAttachment attachPoint)
 {
     switch (attachPoint.get()) {
-    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;
+    }
 
+    if (!mContext->ValidateFramebufferAttachment(this, attachPoint.get(),
+                                                 "getAttachmentOrNull"))
+    {
+        return nullptr;
+    }
+
+    size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0;
+    EnsureColorAttachments(colorAttachmentId);
+
+    return &mColorAttachments[colorAttachmentId];
+}
+
+const WebGLFramebuffer::Attachment&
+WebGLFramebuffer::GetAttachment(FBAttachment attachPoint) const
+{
+    switch (attachPoint.get()) {
     case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
         return mDepthStencilAttachment;
 
     case LOCAL_GL_DEPTH_ATTACHMENT:
         return mDepthAttachment;
 
     case LOCAL_GL_STENCIL_ATTACHMENT:
         return mStencilAttachment;
 
     default:
         break;
     }
 
-    if (attachPoint >= LOCAL_GL_COLOR_ATTACHMENT1) {
-        size_t moreColorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT1;
-        if (1 + moreColorAttachmentId < WebGLContext::kMaxColorAttachments) {
-            EnsureColorAttachPoints(1 + moreColorAttachmentId);
-            return mMoreColorAttachments[moreColorAttachmentId];
-        }
+    if (!mContext->ValidateFramebufferAttachment(this, attachPoint.get(),
+                                                 "getAttachment"))
+    {
+        MOZ_ASSERT(false);
+        return mColorAttachments[0];
     }
 
-    MOZ_CRASH("bad `attachPoint` validation");
+    size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0;
+    if (colorAttachmentId >= mColorAttachments.Length()) {
+        MOZ_ASSERT(false);
+        return mColorAttachments[0];
+    }
+
+    return mColorAttachments[colorAttachmentId];
 }
 
 void
 WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
 {
-    if (mColorAttachment0.Texture() == tex)
-        mColorAttachment0.Clear();
-
-    if (mDepthAttachment.Texture() == tex)
-        mDepthAttachment.Clear();
+    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
+        if (mColorAttachments[i].Texture() == tex) {
+            FramebufferTexture2D(LOCAL_GL_COLOR_ATTACHMENT0+i,
+                                 LOCAL_GL_TEXTURE_2D, nullptr, 0);
+            // It might be attached in multiple places, so don't break.
+        }
+    }
 
-    if (mStencilAttachment.Texture() == tex)
-        mStencilAttachment.Clear();
-
-    if (mDepthStencilAttachment.Texture() == tex)
-        mDepthStencilAttachment.Clear();
-
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].Texture() == tex)
-            mMoreColorAttachments[i].Clear();
+    if (mDepthAttachment.Texture() == tex) {
+        FramebufferTexture2D(LOCAL_GL_DEPTH_ATTACHMENT, LOCAL_GL_TEXTURE_2D,
+                             nullptr, 0);
+    }
+    if (mStencilAttachment.Texture() == tex) {
+        FramebufferTexture2D(LOCAL_GL_STENCIL_ATTACHMENT, LOCAL_GL_TEXTURE_2D,
+                             nullptr, 0);
+    }
+    if (mDepthStencilAttachment.Texture() == tex) {
+        FramebufferTexture2D(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT,
+                             LOCAL_GL_TEXTURE_2D, nullptr, 0);
     }
 }
 
 void
 WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
 {
-    if (mColorAttachment0.Renderbuffer() == rb)
-        mColorAttachment0.Clear();
-
-    if (mDepthAttachment.Renderbuffer() == rb)
-        mDepthAttachment.Clear();
+    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
+        if (mColorAttachments[i].Renderbuffer() == rb) {
+            FramebufferRenderbuffer(LOCAL_GL_COLOR_ATTACHMENT0+i,
+                                    LOCAL_GL_RENDERBUFFER, nullptr);
+            // It might be attached in multiple places, so don't break.
+        }
+    }
 
-    if (mStencilAttachment.Renderbuffer() == rb)
-        mStencilAttachment.Clear();
-
-    if (mDepthStencilAttachment.Renderbuffer() == rb)
-        mDepthStencilAttachment.Clear();
-
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].Renderbuffer() == rb)
-            mMoreColorAttachments[i].Clear();
+    if (mDepthAttachment.Renderbuffer() == rb) {
+        FramebufferRenderbuffer(LOCAL_GL_DEPTH_ATTACHMENT,
+                                LOCAL_GL_RENDERBUFFER, nullptr);
+    }
+    if (mStencilAttachment.Renderbuffer() == rb) {
+        FramebufferRenderbuffer(LOCAL_GL_STENCIL_ATTACHMENT,
+                                LOCAL_GL_RENDERBUFFER, nullptr);
+    }
+    if (mDepthStencilAttachment.Renderbuffer() == rb) {
+        FramebufferRenderbuffer(LOCAL_GL_DEPTH_STENCIL_ATTACHMENT,
+                                LOCAL_GL_RENDERBUFFER, nullptr);
     }
 }
 
 bool
 WebGLFramebuffer::HasDefinedAttachments() const
 {
     bool hasAttachments = false;
 
-    hasAttachments |= mColorAttachment0.IsDefined();
+    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
+        hasAttachments |= mColorAttachments[i].IsDefined();
+    }
+
     hasAttachments |= mDepthAttachment.IsDefined();
     hasAttachments |= mStencilAttachment.IsDefined();
     hasAttachments |= mDepthStencilAttachment.IsDefined();
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        hasAttachments |= mMoreColorAttachments[i].IsDefined();
-    }
-
     return hasAttachments;
 }
 
 static bool
-IsIncomplete(const WebGLFramebuffer::AttachPoint& cur)
+IsIncomplete(const WebGLFramebuffer::Attachment& cur)
 {
     return cur.IsDefined() && !cur.IsComplete();
 }
 
 bool
 WebGLFramebuffer::HasIncompleteAttachments() const
 {
     bool hasIncomplete = false;
 
-    hasIncomplete |= IsIncomplete(mColorAttachment0);
+    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
+        hasIncomplete |= IsIncomplete(mColorAttachments[i]);
+    }
+
     hasIncomplete |= IsIncomplete(mDepthAttachment);
     hasIncomplete |= IsIncomplete(mStencilAttachment);
     hasIncomplete |= IsIncomplete(mDepthStencilAttachment);
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        hasIncomplete |= IsIncomplete(mMoreColorAttachments[i]);
-    }
-
     return hasIncomplete;
 }
 
 const WebGLRectangleObject&
 WebGLFramebuffer::GetAnyRectObject() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
 
-    if (mColorAttachment0.HasImage())
-        return mColorAttachment0.RectangleObject();
+    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
+        if (mColorAttachments[i].HasImage())
+            return mColorAttachments[i].RectangleObject();
+    }
 
     if (mDepthAttachment.HasImage())
         return mDepthAttachment.RectangleObject();
 
     if (mStencilAttachment.HasImage())
         return mStencilAttachment.RectangleObject();
 
     if (mDepthStencilAttachment.HasImage())
         return mDepthStencilAttachment.RectangleObject();
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].HasImage())
-            return mMoreColorAttachments[i].RectangleObject();
-    }
-
     MOZ_CRASH("Should not get here.");
 }
 
 static bool
-RectsMatch(const WebGLFramebuffer::AttachPoint& attachment,
+RectsMatch(const WebGLFramebuffer::Attachment& attachment,
            const WebGLRectangleObject& rect)
 {
     return attachment.RectangleObject().HasSameDimensionsAs(rect);
 }
 
 bool
 WebGLFramebuffer::AllImageRectsMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     MOZ_ASSERT(!HasIncompleteAttachments());
 
     const WebGLRectangleObject& rect = GetAnyRectObject();
 
     // Alright, we have *a* rect, let's check all the others.
     bool imageRectsMatch = true;
 
-    if (mColorAttachment0.HasImage())
-        imageRectsMatch &= RectsMatch(mColorAttachment0, rect);
+    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
+        if (mColorAttachments[i].HasImage())
+            imageRectsMatch &= RectsMatch(mColorAttachments[i], rect);
+    }
 
     if (mDepthAttachment.HasImage())
         imageRectsMatch &= RectsMatch(mDepthAttachment, rect);
 
     if (mStencilAttachment.HasImage())
         imageRectsMatch &= RectsMatch(mStencilAttachment, rect);
 
     if (mDepthStencilAttachment.HasImage())
         imageRectsMatch &= RectsMatch(mDepthStencilAttachment, rect);
 
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].HasImage())
-            imageRectsMatch &= RectsMatch(mMoreColorAttachments[i], rect);
-    }
-
     return imageRectsMatch;
 }
 
 const WebGLRectangleObject&
 WebGLFramebuffer::RectangleObject() const
 {
     // If we're using this as the RectObj of an FB, we need to be sure the FB
     // has a consistent rect.
@@ -722,130 +811,129 @@ WebGLFramebuffer::HasCompletePlanes(GLbi
 {
     if (CheckFramebufferStatus() != LOCAL_GL_FRAMEBUFFER_COMPLETE)
         return false;
 
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     bool hasPlanes = true;
-    if (mask & LOCAL_GL_COLOR_BUFFER_BIT)
-        hasPlanes &= mColorAttachment0.IsDefined();
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
+        hasPlanes &= ColorAttachmentCount() &&
+                     ColorAttachment(0).IsDefined();
+    }
 
     if (mask & LOCAL_GL_DEPTH_BUFFER_BIT) {
-        hasPlanes &= mDepthAttachment.IsDefined() ||
-                     mDepthStencilAttachment.IsDefined();
+        hasPlanes &= DepthAttachment().IsDefined() ||
+                     DepthStencilAttachment().IsDefined();
     }
 
     if (mask & LOCAL_GL_STENCIL_BUFFER_BIT) {
-        hasPlanes &= mStencilAttachment.IsDefined() ||
-                     mDepthStencilAttachment.IsDefined();
+        hasPlanes &= StencilAttachment().IsDefined() ||
+                     DepthStencilAttachment().IsDefined();
     }
 
     return hasPlanes;
 }
 
 bool
 WebGLFramebuffer::CheckAndInitializeAttachments()
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (CheckFramebufferStatus() != LOCAL_GL_FRAMEBUFFER_COMPLETE)
         return false;
 
     // Cool! We've checked out ok. Just need to initialize.
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
+    const size_t colorAttachmentCount = mColorAttachments.Length();
 
     // Check if we need to initialize anything
     {
         bool hasUninitializedAttachments = false;
 
-        if (mColorAttachment0.HasImage())
-            hasUninitializedAttachments |= mColorAttachment0.HasUninitializedImageData();
+        for (size_t i = 0; i < colorAttachmentCount; i++) {
+            if (mColorAttachments[i].HasImage())
+                hasUninitializedAttachments |= mColorAttachments[i].HasUninitializedImageData();
+        }
+
         if (mDepthAttachment.HasImage())
             hasUninitializedAttachments |= mDepthAttachment.HasUninitializedImageData();
         if (mStencilAttachment.HasImage())
             hasUninitializedAttachments |= mStencilAttachment.HasUninitializedImageData();
         if (mDepthStencilAttachment.HasImage())
             hasUninitializedAttachments |= mDepthStencilAttachment.HasUninitializedImageData();
 
-        for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-            if (mMoreColorAttachments[i].HasImage())
-                hasUninitializedAttachments |= mMoreColorAttachments[i].HasUninitializedImageData();
-        }
-
         if (!hasUninitializedAttachments)
             return true;
     }
 
     // Get buffer-bit-mask and color-attachment-mask-list
     uint32_t mask = 0;
     bool colorAttachmentsMask[WebGLContext::kMaxColorAttachments] = { false };
-    MOZ_ASSERT(1 + moreColorAttachmentCount <= WebGLContext::kMaxColorAttachments);
+    MOZ_ASSERT(colorAttachmentCount <= WebGLContext::kMaxColorAttachments);
 
-    if (mColorAttachment0.HasUninitializedImageData()) {
-        colorAttachmentsMask[0] = true;
-        mask |= LOCAL_GL_COLOR_BUFFER_BIT;
+    for (size_t i = 0; i < colorAttachmentCount; i++) {
+        if (mColorAttachments[i].HasUninitializedImageData()) {
+          colorAttachmentsMask[i] = true;
+          mask |= LOCAL_GL_COLOR_BUFFER_BIT;
+        }
     }
 
     if (mDepthAttachment.HasUninitializedImageData() ||
         mDepthStencilAttachment.HasUninitializedImageData())
     {
         mask |= LOCAL_GL_DEPTH_BUFFER_BIT;
     }
 
     if (mStencilAttachment.HasUninitializedImageData() ||
         mDepthStencilAttachment.HasUninitializedImageData())
     {
         mask |= LOCAL_GL_STENCIL_BUFFER_BIT;
     }
 
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].HasUninitializedImageData()) {
-          colorAttachmentsMask[1 + i] = true;
-          mask |= LOCAL_GL_COLOR_BUFFER_BIT;
-        }
-    }
-
     // Clear!
     mContext->ForceClearFramebufferWithDefaultValues(mask, colorAttachmentsMask);
 
     // Mark all the uninitialized images as initialized.
-    if (mColorAttachment0.HasUninitializedImageData())
-        mColorAttachment0.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    for (size_t i = 0; i < colorAttachmentCount; i++) {
+        if (mColorAttachments[i].HasUninitializedImageData())
+            mColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    }
+
     if (mDepthAttachment.HasUninitializedImageData())
         mDepthAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mStencilAttachment.HasUninitializedImageData())
         mStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
     if (mDepthStencilAttachment.HasUninitializedImageData())
         mDepthStencilAttachment.SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
 
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        if (mMoreColorAttachments[i].HasUninitializedImageData())
-            mMoreColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
-    }
-
     return true;
 }
 
-void WebGLFramebuffer::EnsureColorAttachPoints(size_t colorAttachmentId)
+void WebGLFramebuffer::EnsureColorAttachments(size_t colorAttachmentId)
 {
     MOZ_ASSERT(colorAttachmentId < WebGLContext::kMaxColorAttachments);
 
-    if (colorAttachmentId < ColorAttachmentCount())
+    size_t currentAttachmentCount = mColorAttachments.Length();
+    if (colorAttachmentId < currentAttachmentCount)
         return;
 
-    size_t colorAttachmentCount = ColorAttachmentCount();
-    while (colorAttachmentCount < WebGLContext::kMaxColorAttachments) {
-        GLenum nextAttachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + colorAttachmentCount;
-        mMoreColorAttachments.AppendElement(AttachPoint(this, nextAttachPoint));
+    mColorAttachments.SetLength(colorAttachmentId + 1);
+
+    for (size_t i = colorAttachmentId; i >= currentAttachmentCount; i--) {
+        mColorAttachments[i].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0 + i;
     }
+}
 
-    MOZ_ASSERT(colorAttachmentCount == ColorAttachmentCount());
+void
+WebGLFramebuffer::NotifyAttachableChanged() const
+{
+    // Attachment has changed, so invalidate cached status
+    mStatus = 0;
 }
 
 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
@@ -870,104 +958,82 @@ FinalizeDrawAndReadBuffers(gl::GLContext
                                                     : LOCAL_GL_NONE;
     gl->fDrawBuffer(colorBufferSource);
     gl->fReadBuffer(colorBufferSource);
 }
 
 void
 WebGLFramebuffer::FinalizeAttachments() const
 {
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    MOZ_ASSERT(mStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE);
-
     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);
-
-    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
-    for (size_t i = 0; i < moreColorAttachmentCount; i++) {
-        GLenum attachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + 1 + i;
-        mMoreColorAttachments[i].FinalizeAttachment(gl, attachPoint);
+    for (size_t i = 0; i < ColorAttachmentCount(); i++) {
+        ColorAttachment(i).FinalizeAttachment(gl, LOCAL_GL_COLOR_ATTACHMENT0+i);
     }
 
-    FinalizeDrawAndReadBuffers(gl, mColorAttachment0.IsDefined());
+    DepthAttachment().FinalizeAttachment(gl, LOCAL_GL_DEPTH_ATTACHMENT);
+    StencilAttachment().FinalizeAttachment(gl, LOCAL_GL_STENCIL_ATTACHMENT);
+    DepthStencilAttachment().FinalizeAttachment(gl,
+                                                LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
+
+    FinalizeDrawAndReadBuffers(gl, ColorAttachment(0).IsDefined());
 }
 
 bool
 WebGLFramebuffer::ValidateForRead(const char* info, TexInternalFormat* const out_format)
 {
     if (mReadBufferMode == LOCAL_GL_NONE) {
         mContext->ErrorInvalidOperation("%s: Read buffer mode must not be"
                                         " NONE.", info);
         return false;
     }
 
-    const auto& attachPoint = GetAttachPoint(mReadBufferMode);
+    const auto& attachment = GetAttachment(mReadBufferMode);
 
     if (!CheckAndInitializeAttachments()) {
         mContext->ErrorInvalidFramebufferOperation("readPixels: incomplete framebuffer");
         return false;
     }
 
     GLenum readPlaneBits = LOCAL_GL_COLOR_BUFFER_BIT;
     if (!HasCompletePlanes(readPlaneBits)) {
         mContext->ErrorInvalidOperation("readPixels: Read source attachment doesn't have the"
                                         " correct color/depth/stencil type.");
         return false;
     }
 
-    if (!attachPoint.IsDefined()) {
+    if (!attachment.IsDefined()) {
         mContext->ErrorInvalidOperation("readPixels: ");
         return false;
     }
 
-    *out_format = attachPoint.EffectiveInternalFormat();
+    *out_format = attachment.EffectiveInternalFormat();
     return true;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// Goop.
-
-JSObject*
-WebGLFramebuffer::WrapObject(JSContext* cx)
+inline void
+ImplCycleCollectionUnlink(mozilla::WebGLFramebuffer::Attachment& field)
 {
-    return dom::WebGLFramebufferBinding::Wrap(cx, this);
-}
-
-inline void
-ImplCycleCollectionUnlink(mozilla::WebGLFramebuffer::AttachPoint& field)
-{
-    field.Unlink();
+    field.mTexturePtr = nullptr;
+    field.mRenderbufferPtr = nullptr;
 }
 
 inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
-                            mozilla::WebGLFramebuffer::AttachPoint& field,
+                            mozilla::WebGLFramebuffer::Attachment& field,
                             const char* name,
                             uint32_t flags = 0)
 {
-    CycleCollectionNoteChild(callback, field.Texture(), name, flags);
-    CycleCollectionNoteChild(callback, field.Renderbuffer(), name, flags);
+    CycleCollectionNoteChild(callback, field.mTexturePtr.get(), name, flags);
+    CycleCollectionNoteChild(callback, field.mRenderbufferPtr.get(), name,
+                             flags);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebuffer,
-                                      mColorAttachment0,
+                                      mColorAttachments,
                                       mDepthAttachment,
                                       mStencilAttachment,
-                                      mDepthStencilAttachment,
-                                      mMoreColorAttachments)
+                                      mDepthStencilAttachment)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLFramebuffer, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLFramebuffer, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -28,49 +28,40 @@ class WebGLFramebuffer MOZ_FINAL
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public WebGLContextBoundObject
     , public SupportsWeakPtr<WebGLFramebuffer>
 {
 public:
     MOZ_DECLARE_REFCOUNTED_TYPENAME(WebGLFramebuffer)
 
-    class AttachPoint
+    explicit WebGLFramebuffer(WebGLContext* webgl, GLuint fbo);
+
+    struct Attachment
     {
-    public:
-        WebGLFramebuffer* const mFB;
-    private:
+        // deleting a texture or renderbuffer immediately detaches it
         WebGLRefPtr<WebGLTexture> mTexturePtr;
         WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
         FBAttachment mAttachmentPoint;
         TexImageTarget mTexImageTarget;
         GLint mTexImageLevel;
-
-    public:
-        AttachPoint(WebGLFramebuffer* fb, FBAttachment attachmentPoint);
-        ~AttachPoint();
+        mutable bool mNeedsFinalize;
 
-        void Unlink() {
-            mRenderbufferPtr = nullptr;
-            mTexturePtr = nullptr;
-        }
+        explicit Attachment(FBAttachment attachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0);
+        ~Attachment();
 
         bool IsDefined() const;
 
         bool IsDeleteRequested() const;
 
         TexInternalFormat EffectiveInternalFormat() const;
 
         bool HasAlpha() const;
         bool IsReadableFloat() const;
 
-        void Clear() {
-            SetRenderbuffer(nullptr);
-        }
-
         void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level);
         void SetRenderbuffer(WebGLRenderbuffer* rb);
 
         const WebGLTexture* Texture() const {
             return mTexturePtr;
         }
         WebGLTexture* Texture() {
             return mTexturePtr;
@@ -86,95 +77,78 @@ public:
         }
         GLint MipLevel() const {
             return mTexImageLevel;
         }
 
         bool HasUninitializedImageData() const;
         void SetImageDataStatus(WebGLImageDataStatus x);
 
+        void Reset();
+
         const WebGLRectangleObject& RectangleObject() const;
 
         bool HasImage() const;
         bool IsComplete() const;
 
         void FinalizeAttachment(gl::GLContext* gl,
                                 FBAttachment attachmentLoc) const;
     };
 
-private:
-    mutable GLenum mStatus;
-
-    GLenum mReadBufferMode;
-
-    // No need to chase pointers for the oft-used color0.
-    AttachPoint mColorAttachment0;
-    AttachPoint mDepthAttachment;
-    AttachPoint mStencilAttachment;
-    AttachPoint mDepthStencilAttachment;
-    nsTArray<AttachPoint> mMoreColorAttachments;
-
-public:
-    WebGLFramebuffer(WebGLContext* webgl, GLuint fbo);
-
-private:
-    ~WebGLFramebuffer() {
-        DeleteOnce();
-    }
-
-    const WebGLRectangleObject& GetAnyRectObject() const;
-
-public:
     void Delete();
 
     void FramebufferRenderbuffer(FBAttachment attachment, RBTarget rbtarget,
                                  WebGLRenderbuffer* rb);
 
     void FramebufferTexture2D(FBAttachment attachment,
                               TexImageTarget texImageTarget, WebGLTexture* tex,
                               GLint level);
 
+private:
+    void DetachAttachment(WebGLFramebuffer::Attachment& attachment);
+    void DetachAllAttachments();
+    const WebGLRectangleObject& GetAnyRectObject() const;
+    Attachment* GetAttachmentOrNull(FBAttachment attachment);
+
+public:
     bool HasDefinedAttachments() const;
     bool HasIncompleteAttachments() const;
     bool AllImageRectsMatch() const;
     FBStatus PrecheckFramebufferStatus() const;
     FBStatus CheckFramebufferStatus() const;
 
     GLenum
-    GetFormatForAttachment(const WebGLFramebuffer::AttachPoint& attachment) const;
+    GetFormatForAttachment(const WebGLFramebuffer::Attachment& attachment) const;
 
     bool HasDepthStencilConflict() const {
         return int(mDepthAttachment.IsDefined()) +
                int(mStencilAttachment.IsDefined()) +
                int(mDepthStencilAttachment.IsDefined()) >= 2;
     }
 
     size_t ColorAttachmentCount() const {
-        return 1 + mMoreColorAttachments.Length();
+        return mColorAttachments.Length();
+    }
+    const Attachment& ColorAttachment(size_t colorAttachmentId) const {
+        return mColorAttachments[colorAttachmentId];
     }
 
-    const AttachPoint& ColorAttachment(size_t colorAttachmentId) const {
-        MOZ_ASSERT(colorAttachmentId < ColorAttachmentCount());
-        return colorAttachmentId ? mMoreColorAttachments[colorAttachmentId - 1]
-                                 : mColorAttachment0;
-    }
-
-    const AttachPoint& DepthAttachment() const {
+    const Attachment& DepthAttachment() const {
         return mDepthAttachment;
     }
 
-    const AttachPoint& StencilAttachment() const {
+    const Attachment& StencilAttachment() const {
         return mStencilAttachment;
     }
 
-    const AttachPoint& DepthStencilAttachment() const {
+    const Attachment& DepthStencilAttachment() const {
         return mDepthStencilAttachment;
     }
 
-    AttachPoint& GetAttachPoint(FBAttachment attachPointEnum);
+    const Attachment& GetAttachment(FBAttachment attachment) const;
 
     void DetachTexture(const WebGLTexture* tex);
 
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
 
     const WebGLRectangleObject& RectangleObject() const;
 
     WebGLContext* GetParentObject() const {
@@ -191,20 +165,33 @@ public:
     // mask mirrors glClear.
     bool HasCompletePlanes(GLbitfield mask);
 
     bool CheckAndInitializeAttachments();
 
     bool CheckColorAttachmentNumber(FBAttachment attachment,
                                     const char* funcName) const;
 
-    void EnsureColorAttachPoints(size_t colorAttachmentId);
+    void EnsureColorAttachments(size_t colorAttachmentId);
+
+    void NotifyAttachableChanged() const;
 
-    void InvalidateFramebufferStatus() const {
-        mStatus = 0;
+    bool ValidateForRead(const char* info, TexInternalFormat* const out_format);
+
+private:
+    ~WebGLFramebuffer() {
+        DeleteOnce();
     }
 
-    bool ValidateForRead(const char* info, TexInternalFormat* const out_format);
+    mutable GLenum mStatus;
+
+    // we only store pointers to attached renderbuffers, not to attached textures, because
+    // we will only need to initialize renderbuffers. Textures are already initialized.
+    nsTArray<Attachment> mColorAttachments;
+    Attachment mDepthAttachment;
+    Attachment mStencilAttachment;
+    Attachment mDepthStencilAttachment;
+    GLenum mReadBufferMode;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLFramebufferAttachable.cpp
+++ b/dom/canvas/WebGLFramebufferAttachable.cpp
@@ -1,46 +1,51 @@
 /* -*- 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 "WebGLContext.h"
 #include "WebGLFramebufferAttachable.h"
-
-#include "WebGLContext.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
-namespace mozilla {
+using namespace mozilla;
 
 void
-WebGLFramebufferAttachable::MarkAttachment(const WebGLFramebuffer::AttachPoint& attachment)
+WebGLFramebufferAttachable::AttachTo(WebGLFramebuffer* fb, FBAttachment attachment)
 {
-    if (mAttachmentPoints.Contains(&attachment))
+    MOZ_ASSERT(fb);
+    if (!fb)
+        return;
+
+    if (mAttachmentPoints.Contains(AttachmentPoint(fb, attachment)))
         return; // Already attached. Ignore.
 
-    mAttachmentPoints.AppendElement(&attachment);
+    mAttachmentPoints.AppendElement(AttachmentPoint(fb, attachment));
 }
 
 void
-WebGLFramebufferAttachable::UnmarkAttachment(const WebGLFramebuffer::AttachPoint& attachment)
+WebGLFramebufferAttachable::DetachFrom(WebGLFramebuffer* fb, FBAttachment attachment)
 {
-    const size_t i = mAttachmentPoints.IndexOf(&attachment);
+    MOZ_ASSERT(fb);
+    if (!fb)
+        return;
+
+    const size_t i = mAttachmentPoints.IndexOf(AttachmentPoint(fb, attachment));
     if (i == mAttachmentPoints.NoIndex) {
         MOZ_ASSERT(false, "Is not attached to FB");
         return;
     }
 
     mAttachmentPoints.RemoveElementAt(i);
 }
 
 void
-WebGLFramebufferAttachable::InvalidateStatusOfAttachedFBs() const
+WebGLFramebufferAttachable::NotifyFBsStatusChanged()
 {
-    const size_t count = mAttachmentPoints.Length();
-    for (size_t i = 0; i < count; ++i) {
-        MOZ_ASSERT(mAttachmentPoints[i]->mFB);
-        mAttachmentPoints[i]->mFB->InvalidateFramebufferStatus();
+    for (size_t i = 0; i < mAttachmentPoints.Length(); ++i) {
+        MOZ_ASSERT(mAttachmentPoints[i].mFB,
+                   "Unexpected null pointer; seems that a WebGLFramebuffer forgot to call DetachFrom before dying");
+        mAttachmentPoints[i].mFB->NotifyAttachableChanged();
     }
 }
-
-} // namespace mozilla
--- a/dom/canvas/WebGLFramebufferAttachable.h
+++ b/dom/canvas/WebGLFramebufferAttachable.h
@@ -1,30 +1,46 @@
 /* -*- Mode: C++; tab-width: 4; 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/. */
 
-#ifndef WEBGL_FRAMEBUFFER_ATTACHABLE_H_
-#define WEBGL_FRAMEBUFFER_ATTACHABLE_H_
+#ifndef WEBGLFRAMEBUFFERATTACHABLE_H_
+#define WEBGLFRAMEBUFFERATTACHABLE_H_
 
 #include "GLDefs.h"
+#include "nsTArray.h"
 #include "mozilla/WeakPtr.h"
-#include "nsTArray.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLStrongTypes.h"
 
 namespace mozilla {
 
 class WebGLFramebufferAttachable
 {
-    nsTArray<const WebGLFramebuffer::AttachPoint*> mAttachmentPoints;
+    struct AttachmentPoint
+    {
+        AttachmentPoint(const WebGLFramebuffer* fb, FBAttachment attachment)
+            : mFB(fb)
+            , mAttachment(attachment)
+        {}
+
+        WeakPtr<const WebGLFramebuffer> mFB;
+        FBAttachment mAttachment;
+
+        bool operator==(const AttachmentPoint& o) const {
+          return mFB == o.mFB && mAttachment == o.mAttachment;
+        }
+    };
+
+    nsTArray<AttachmentPoint> mAttachmentPoints;
 
 public:
+
     // Track FBO/Attachment combinations
-    void MarkAttachment(const WebGLFramebuffer::AttachPoint& attachment);
-    void UnmarkAttachment(const WebGLFramebuffer::AttachPoint& attachment);
-    void InvalidateStatusOfAttachedFBs() const;
+    void AttachTo(WebGLFramebuffer* fb, FBAttachment attachment);
+    void DetachFrom(WebGLFramebuffer* fb, FBAttachment attachment);
+    void NotifyFBsStatusChanged();
 };
 
 } // namespace mozilla
 
 #endif // !WEBGLFRAMEBUFFERATTACHABLE_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -174,18 +174,16 @@ RenderbufferStorageMaybeMultisample(gl::
                                  height);
     }
 }
 
 void
 WebGLRenderbuffer::RenderbufferStorage(GLsizei samples, GLenum internalFormat,
                                        GLsizei width, GLsizei height) const
 {
-    InvalidateStatusOfAttachedFBs();
-
     gl::GLContext* gl = mContext->gl;
     MOZ_ASSERT(samples >= 0 && samples <= 256); // Sanity check.
 
     GLenum primaryFormat = internalFormat;
     GLenum secondaryFormat = 0;
 
     if (NeedsDepthStencilEmu(mContext->gl, primaryFormat)) {
         primaryFormat = DepthStencilDepthFormat(gl);
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -176,27 +176,28 @@ void
 WebGLTexture::SetImageInfo(TexImageTarget texImageTarget, GLint level,
                            GLsizei width, GLsizei height, GLsizei depth,
                            TexInternalFormat effectiveInternalFormat,
                            WebGLImageDataStatus status)
 {
     MOZ_ASSERT(depth == 1 || texImageTarget == LOCAL_GL_TEXTURE_3D);
     MOZ_ASSERT(TexImageTargetToTexTarget(texImageTarget) == mTarget);
 
-    InvalidateStatusOfAttachedFBs();
-
     EnsureMaxLevelWithCustomImagesAtLeast(level);
 
     ImageInfoAt(texImageTarget, level) = ImageInfo(width, height, depth,
                                                    effectiveInternalFormat,
                                                    status);
 
     if (level > 0)
         SetCustomMipmap();
 
+    // Invalidate framebuffer status cache.
+    NotifyFBsStatusChanged();
+
     SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
 }
 
 void
 WebGLTexture::SetGeneratedMipmap()
 {
     if (!mHaveGeneratedMipmap) {
         mHaveGeneratedMipmap = true;