Bug 1017865 - Refactor attach/detach for FB attachments. - r=kamidphish
authorJeff Gilbert <jgilbert@mozilla.com>
Tue, 24 Mar 2015 16:00:28 -0700
changeset 264312 319a40f0fce42c89b21518db7530b29ad359ac51
parent 264311 04e9afe5d6d81b0c2eee5fd337dadaac83e86a9f
child 264313 cd28897da642500d6c644a886e2c27cba0fb2f3d
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskamidphish
bugs1017865
milestone39.0a1
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
Bug 1017865 - Refactor attach/detach for FB attachments. - r=kamidphish
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
dom/canvas/test/_webgl-conformance.ini
dom/canvas/test/webgl-conformance/mochitest-errata.ini
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -709,22 +709,20 @@ WebGLContext::DeleteRenderbuffer(WebGLRe
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachRenderbuffer(rbuf);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachRenderbuffer(rbuf);
 
-    // Invalidate framebuffer status cache
-    rbuf->NotifyFBsStatusChanged();
+    rbuf->InvalidateStatusOfAttachedFBs();
 
     if (mBoundRenderbuffer == rbuf)
-        BindRenderbuffer(LOCAL_GL_RENDERBUFFER,
-                         static_cast<WebGLRenderbuffer*>(nullptr));
+        BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
 
     rbuf->RequestDelete();
 }
 
 void
 WebGLContext::DeleteTexture(WebGLTexture* tex)
 {
     if (IsContextLost())
@@ -737,27 +735,26 @@ WebGLContext::DeleteTexture(WebGLTexture
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachTexture(tex);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachTexture(tex);
 
-    // Invalidate framebuffer status cache
-    tex->NotifyFBsStatusChanged();
+    tex->InvalidateStatusOfAttachedFBs();
 
     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(), static_cast<WebGLTexture*>(nullptr));
+            BindTexture(tex->Target().get(), nullptr);
         }
     }
     ActiveTexture(LOCAL_GL_TEXTURE0 + activeTexture);
 
     tex->RequestDelete();
 }
 
 void
@@ -863,21 +860,18 @@ 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,
@@ -1141,21 +1135,21 @@ WebGLContext::GetFramebufferAttachmentPa
 
     if (!ValidateFramebufferAttachment(fb, attachment,
                                        "getFramebufferAttachmentParameter"))
     {
         return JS::NullValue();
     }
 
     if (IsExtensionEnabled(WebGLExtensionID::WEBGL_draw_buffers))
-        fb->EnsureColorAttachments(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
+        fb->EnsureColorAttachPoints(attachment - LOCAL_GL_COLOR_ATTACHMENT0);
 
     MakeContextCurrent();
 
-    const WebGLFramebuffer::Attachment& fba = fb->GetAttachment(attachment);
+    const WebGLFramebuffer::AttachPoint& fba = fb->GetAttachPoint(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 ||
@@ -2324,18 +2318,16 @@ 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,16 +599,17 @@ 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);
@@ -784,16 +785,17 @@ 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,70 +10,46 @@
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
-JSObject*
-WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
-{
-    return dom::WebGLFramebufferBinding::Wrap(cx, this, aGivenProto);
-}
-
-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);
+WebGLFramebuffer::AttachPoint::AttachPoint(WebGLFramebuffer* fb,
+                                           FBAttachment attachmentPoint)
+    : mFB(fb)
+    , mAttachmentPoint(attachmentPoint)
+    , mTexImageTarget(LOCAL_GL_NONE)
+{ }
 
-    mColorAttachments.SetLength(1);
-    mColorAttachments[0].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0;
-}
-
-WebGLFramebuffer::Attachment::Attachment(FBAttachment attachmentPoint)
-    : mAttachmentPoint(attachmentPoint)
-    , mTexImageTarget(LOCAL_GL_NONE)
-    , mNeedsFinalize(false)
-{}
-
-WebGLFramebuffer::Attachment::~Attachment()
-{}
-
-void
-WebGLFramebuffer::Attachment::Reset()
+WebGLFramebuffer::AttachPoint::~AttachPoint()
 {
-    mTexturePtr = nullptr;
-    mRenderbufferPtr = nullptr;
+    MOZ_ASSERT(!mRenderbufferPtr);
+    MOZ_ASSERT(!mTexturePtr);
 }
 
 bool
-WebGLFramebuffer::Attachment::IsDeleteRequested() const
+WebGLFramebuffer::AttachPoint::IsDeleteRequested() const
 {
     return Texture() ? Texture()->IsDeleteRequested()
          : Renderbuffer() ? Renderbuffer()->IsDeleteRequested()
          : false;
 }
 
 bool
-WebGLFramebuffer::Attachment::IsDefined() const
+WebGLFramebuffer::AttachPoint::IsDefined() const
 {
     return Renderbuffer() ||
            (Texture() && Texture()->HasImageInfoAt(ImageTarget(), 0));
 }
 
 bool
-WebGLFramebuffer::Attachment::HasAlpha() const
+WebGLFramebuffer::AttachPoint::HasAlpha() const
 {
     MOZ_ASSERT(HasImage());
 
     if (Texture() &&
         Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
     {
         return FormatHasAlpha(Texture()->ImageInfoAt(mTexImageTarget,
                                                      mTexImageLevel).EffectiveInternalFormat());
@@ -81,17 +57,17 @@ WebGLFramebuffer::Attachment::HasAlpha()
 
     if (Renderbuffer())
         return FormatHasAlpha(Renderbuffer()->InternalFormat());
 
     return false;
 }
 
 GLenum
-WebGLFramebuffer::GetFormatForAttachment(const WebGLFramebuffer::Attachment& attachment) const
+WebGLFramebuffer::GetFormatForAttachment(const WebGLFramebuffer::AttachPoint& 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));
 
@@ -102,65 +78,86 @@ WebGLFramebuffer::GetFormatForAttachment
 
     if (attachment.Renderbuffer())
         return attachment.Renderbuffer()->InternalFormat();
 
     return LOCAL_GL_NONE;
 }
 
 TexInternalFormat
-WebGLFramebuffer::Attachment::EffectiveInternalFormat() const
+WebGLFramebuffer::AttachPoint::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::Attachment::IsReadableFloat() const
+WebGLFramebuffer::AttachPoint::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)
+{
+    WebGLFramebufferAttachable* maybe = attachment.Texture();
+    if (!maybe)
+        maybe = attachment.Renderbuffer();
+
+    if (maybe)
+        maybe->UnmarkAttachment(attachment);
+}
+
 void
-WebGLFramebuffer::Attachment::SetTexImage(WebGLTexture* tex,
-                                          TexImageTarget target, GLint level)
+WebGLFramebuffer::AttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target,
+                                          GLint level)
 {
+    mFB->InvalidateFramebufferStatus();
+
+    UnmarkAttachment(*this);
+
     mTexturePtr = tex;
     mRenderbufferPtr = nullptr;
     mTexImageTarget = target;
     mTexImageLevel = level;
 
-    mNeedsFinalize = true;
+    if (tex)
+        tex->MarkAttachment(*this);
 }
 
 void
-WebGLFramebuffer::Attachment::SetRenderbuffer(WebGLRenderbuffer* rb)
+WebGLFramebuffer::AttachPoint::SetRenderbuffer(WebGLRenderbuffer* rb)
 {
+    mFB->InvalidateFramebufferStatus();
+
+    UnmarkAttachment(*this);
+
     mTexturePtr = nullptr;
     mRenderbufferPtr = rb;
 
-    mNeedsFinalize = true;
+    if (rb)
+        rb->MarkAttachment(*this);
 }
 
 bool
-WebGLFramebuffer::Attachment::HasUninitializedImageData() const
+WebGLFramebuffer::AttachPoint::HasUninitializedImageData() const
 {
     if (!HasImage())
         return false;
 
     if (Renderbuffer())
         return Renderbuffer()->HasUninitializedImageData();
 
     if (Texture()) {
@@ -169,17 +166,17 @@ WebGLFramebuffer::Attachment::HasUniniti
                                       mTexImageLevel).HasUninitializedImageData();
     }
 
     MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
 
 void
-WebGLFramebuffer::Attachment::SetImageDataStatus(WebGLImageDataStatus newStatus)
+WebGLFramebuffer::AttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus)
 {
     if (!HasImage())
         return;
 
     if (Renderbuffer()) {
         Renderbuffer()->SetImageDataStatus(newStatus);
         return;
     }
@@ -189,29 +186,29 @@ WebGLFramebuffer::Attachment::SetImageDa
                                       newStatus);
         return;
     }
 
     MOZ_ASSERT(false, "Should not get here.");
 }
 
 bool
-WebGLFramebuffer::Attachment::HasImage() const
+WebGLFramebuffer::AttachPoint::HasImage() const
 {
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
         return true;
 
     if (Renderbuffer())
         return true;
 
     return false;
 }
 
 const WebGLRectangleObject&
-WebGLFramebuffer::Attachment::RectangleObject() const
+WebGLFramebuffer::AttachPoint::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);
     }
@@ -283,17 +280,17 @@ WebGLContext::IsFormatValidForFB(GLenum 
     case LOCAL_GL_RGBA16F:
         return IsExtensionEnabled(WebGLExtensionID::EXT_color_buffer_half_float);
     }
 
     return false;
 }
 
 bool
-WebGLFramebuffer::Attachment::IsComplete() const
+WebGLFramebuffer::AttachPoint::IsComplete() const
 {
     if (!HasImage())
         return false;
 
     const WebGLRectangleObject& rect = RectangleObject();
 
     if (!rect.Width() ||
         !rect.Height())
@@ -351,153 +348,117 @@ WebGLFramebuffer::Attachment::IsComplete
         MOZ_ASSERT(false, "Invalid WebGL attachment point?");
         return false;
     }
     MOZ_ASSERT(false, "Should not get here.");
     return false;
 }
 
 void
-WebGLFramebuffer::Attachment::FinalizeAttachment(gl::GLContext* gl,
+WebGLFramebuffer::AttachPoint::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;
 
-    if (!HasImage()) {
-        if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER,
-                                         LOCAL_GL_DEPTH_ATTACHMENT,
+        default:
+            gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachmentLoc.get(),
                                          LOCAL_GL_RENDERBUFFER, 0);
-            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);
+            break;
         }
 
         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_ASSERT(false, "Should not get here.");
+    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);
 }
 
 void
 WebGLFramebuffer::Delete()
 {
-    DetachAllAttachments();
-    mColorAttachments.Clear();
-    mDepthAttachment.Reset();
-    mStencilAttachment.Reset();
-    mDepthStencilAttachment.Reset();
+    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();
+    }
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 }
 
 void
-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,
+WebGLFramebuffer::FramebufferRenderbuffer(FBAttachment attachPointEnum,
                                           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;
-    }
 
-    /* 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.
+    // `attachPoint` is validated by ValidateFramebufferAttachment().
+    AttachPoint& attachPoint = GetAttachPoint(attachPointEnum);
+    attachPoint.SetRenderbuffer(rb);
 
-    // 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);
+    InvalidateFramebufferStatus();
 }
 
 void
-WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPoint,
+WebGLFramebuffer::FramebufferTexture2D(FBAttachment attachPointEnum,
                                        TexImageTarget texImageTarget,
                                        WebGLTexture* tex, GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
         return;
@@ -507,248 +468,201 @@ WebGLFramebuffer::FramebufferTexture2D(F
         bool isTexTarget2D = texImageTarget == LOCAL_GL_TEXTURE_2D;
         if (isTexture2D != isTexTarget2D) {
             mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
                                             " texture and texture target.");
             return;
         }
     }
 
-    /* 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.
+    AttachPoint& attachPoint = GetAttachPoint(attachPointEnum);
+    attachPoint.SetTexImage(tex, texImageTarget, level);
 
-    // 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);
+    InvalidateFramebufferStatus();
 }
 
-WebGLFramebuffer::Attachment*
-WebGLFramebuffer::GetAttachmentOrNull(FBAttachment attachPoint)
+WebGLFramebuffer::AttachPoint&
+WebGLFramebuffer::GetAttachPoint(FBAttachment attachPoint)
 {
     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;
-    }
+    case LOCAL_GL_COLOR_ATTACHMENT0:
+        return mColorAttachment0;
 
-    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 (!mContext->ValidateFramebufferAttachment(this, attachPoint.get(),
-                                                 "getAttachment"))
-    {
-        MOZ_ASSERT(false);
-        return mColorAttachments[0];
+    if (attachPoint >= LOCAL_GL_COLOR_ATTACHMENT1) {
+        size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0;
+        if (colorAttachmentId < WebGLContext::kMaxColorAttachments) {
+            EnsureColorAttachPoints(colorAttachmentId);
+            return mMoreColorAttachments[colorAttachmentId - 1];
+        }
     }
 
-    size_t colorAttachmentId = attachPoint.get() - LOCAL_GL_COLOR_ATTACHMENT0;
-    if (colorAttachmentId >= mColorAttachments.Length()) {
-        MOZ_ASSERT(false);
-        return mColorAttachments[0];
-    }
-
-    return mColorAttachments[colorAttachmentId];
+    MOZ_CRASH("bad `attachPoint` validation");
 }
 
 void
 WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
 {
-    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 (mColorAttachment0.Texture() == tex)
+        mColorAttachment0.Clear();
+
+    if (mDepthAttachment.Texture() == tex)
+        mDepthAttachment.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);
+    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();
     }
 }
 
 void
 WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
 {
-    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 (mColorAttachment0.Renderbuffer() == rb)
+        mColorAttachment0.Clear();
+
+    if (mDepthAttachment.Renderbuffer() == rb)
+        mDepthAttachment.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);
+    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();
     }
 }
 
 bool
 WebGLFramebuffer::HasDefinedAttachments() const
 {
     bool hasAttachments = false;
 
-    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
-        hasAttachments |= mColorAttachments[i].IsDefined();
-    }
-
+    hasAttachments |= mColorAttachment0.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::Attachment& cur)
+IsIncomplete(const WebGLFramebuffer::AttachPoint& cur)
 {
     return cur.IsDefined() && !cur.IsComplete();
 }
 
 bool
 WebGLFramebuffer::HasIncompleteAttachments() const
 {
     bool hasIncomplete = false;
 
-    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
-        hasIncomplete |= IsIncomplete(mColorAttachments[i]);
-    }
-
+    hasIncomplete |= IsIncomplete(mColorAttachment0);
     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());
 
-    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
-        if (mColorAttachments[i].HasImage())
-            return mColorAttachments[i].RectangleObject();
-    }
+    if (mColorAttachment0.HasImage())
+        return mColorAttachment0.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::Attachment& attachment,
+RectsMatch(const WebGLFramebuffer::AttachPoint& 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;
 
-    for (size_t i = 0; i < (size_t)mColorAttachments.Length(); i++) {
-        if (mColorAttachments[i].HasImage())
-            imageRectsMatch &= RectsMatch(mColorAttachments[i], rect);
-    }
+    if (mColorAttachment0.HasImage())
+        imageRectsMatch &= RectsMatch(mColorAttachment0, 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.
@@ -803,129 +717,130 @@ 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 &= ColorAttachmentCount() &&
-                     ColorAttachment(0).IsDefined();
-    }
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT)
+        hasPlanes &= mColorAttachment0.IsDefined();
 
     if (mask & LOCAL_GL_DEPTH_BUFFER_BIT) {
-        hasPlanes &= DepthAttachment().IsDefined() ||
-                     DepthStencilAttachment().IsDefined();
+        hasPlanes &= mDepthAttachment.IsDefined() ||
+                     mDepthStencilAttachment.IsDefined();
     }
 
     if (mask & LOCAL_GL_STENCIL_BUFFER_BIT) {
-        hasPlanes &= StencilAttachment().IsDefined() ||
-                     DepthStencilAttachment().IsDefined();
+        hasPlanes &= mStencilAttachment.IsDefined() ||
+                     mDepthStencilAttachment.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 colorAttachmentCount = mColorAttachments.Length();
+    const size_t moreColorAttachmentCount = mMoreColorAttachments.Length();
 
     // Check if we need to initialize anything
     {
         bool hasUninitializedAttachments = false;
 
-        for (size_t i = 0; i < colorAttachmentCount; i++) {
-            if (mColorAttachments[i].HasImage())
-                hasUninitializedAttachments |= mColorAttachments[i].HasUninitializedImageData();
-        }
-
+        if (mColorAttachment0.HasImage())
+            hasUninitializedAttachments |= mColorAttachment0.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(colorAttachmentCount <= WebGLContext::kMaxColorAttachments);
+    MOZ_ASSERT(1 + moreColorAttachmentCount <= WebGLContext::kMaxColorAttachments);
 
-    for (size_t i = 0; i < colorAttachmentCount; i++) {
-        if (mColorAttachments[i].HasUninitializedImageData()) {
-          colorAttachmentsMask[i] = true;
-          mask |= LOCAL_GL_COLOR_BUFFER_BIT;
-        }
+    if (mColorAttachment0.HasUninitializedImageData()) {
+        colorAttachmentsMask[0] = 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.
-    for (size_t i = 0; i < colorAttachmentCount; i++) {
-        if (mColorAttachments[i].HasUninitializedImageData())
-            mColorAttachments[i].SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
-    }
-
+    if (mColorAttachment0.HasUninitializedImageData())
+        mColorAttachment0.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::EnsureColorAttachments(size_t colorAttachmentId)
+void WebGLFramebuffer::EnsureColorAttachPoints(size_t colorAttachmentId)
 {
     MOZ_ASSERT(colorAttachmentId < WebGLContext::kMaxColorAttachments);
 
-    size_t currentAttachmentCount = mColorAttachments.Length();
-    if (colorAttachmentId < currentAttachmentCount)
+    if (colorAttachmentId < ColorAttachmentCount())
         return;
 
-    mColorAttachments.SetLength(colorAttachmentId + 1);
-
-    for (size_t i = colorAttachmentId; i >= currentAttachmentCount; i--) {
-        mColorAttachments[i].mAttachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0 + i;
+    size_t colorAttachmentCount = ColorAttachmentCount();
+    while (colorAttachmentCount < WebGLContext::kMaxColorAttachments) {
+        GLenum nextAttachPoint = LOCAL_GL_COLOR_ATTACHMENT0 + colorAttachmentCount;
+        mMoreColorAttachments.AppendElement(AttachPoint(this, nextAttachPoint));
     }
-}
 
-void
-WebGLFramebuffer::NotifyAttachableChanged() const
-{
-    // Attachment has changed, so invalidate cached status
-    mStatus = 0;
+    MOZ_ASSERT(colorAttachmentCount == ColorAttachmentCount());
 }
 
 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
@@ -950,82 +865,104 @@ 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;
 
-    for (size_t i = 0; i < ColorAttachmentCount(); i++) {
-        ColorAttachment(i).FinalizeAttachment(gl, LOCAL_GL_COLOR_ATTACHMENT0+i);
+    // 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);
     }
 
-    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());
+    FinalizeDrawAndReadBuffers(gl, mColorAttachment0.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& attachment = GetAttachment(mReadBufferMode);
+    const auto& attachPoint = GetAttachPoint(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 (!attachment.IsDefined()) {
+    if (!attachPoint.IsDefined()) {
         mContext->ErrorInvalidOperation("readPixels: ");
         return false;
     }
 
-    *out_format = attachment.EffectiveInternalFormat();
+    *out_format = attachPoint.EffectiveInternalFormat();
     return true;
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Goop.
+
+JSObject*
+WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
+{
+    return dom::WebGLFramebufferBinding::Wrap(cx, this, aGivenProto);
+}
+
 inline void
-ImplCycleCollectionUnlink(mozilla::WebGLFramebuffer::Attachment& field)
+ImplCycleCollectionUnlink(mozilla::WebGLFramebuffer::AttachPoint& field)
 {
-    field.mTexturePtr = nullptr;
-    field.mRenderbufferPtr = nullptr;
+    field.Unlink();
 }
 
 inline void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
-                            mozilla::WebGLFramebuffer::Attachment& field,
+                            mozilla::WebGLFramebuffer::AttachPoint& field,
                             const char* name,
                             uint32_t flags = 0)
 {
-    CycleCollectionNoteChild(callback, field.mTexturePtr.get(), name, flags);
-    CycleCollectionNoteChild(callback, field.mRenderbufferPtr.get(), name,
-                             flags);
+    CycleCollectionNoteChild(callback, field.Texture(), name, flags);
+    CycleCollectionNoteChild(callback, field.Renderbuffer(), name, flags);
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebuffer,
-                                      mColorAttachments,
+                                      mColorAttachment0,
                                       mDepthAttachment,
                                       mStencilAttachment,
-                                      mDepthStencilAttachment)
+                                      mDepthStencilAttachment,
+                                      mMoreColorAttachments)
 
 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,40 +28,49 @@ class WebGLFramebuffer final
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public WebGLContextBoundObject
     , public SupportsWeakPtr<WebGLFramebuffer>
 {
 public:
     MOZ_DECLARE_REFCOUNTED_TYPENAME(WebGLFramebuffer)
 
-    explicit WebGLFramebuffer(WebGLContext* webgl, GLuint fbo);
-
-    struct Attachment
+    class AttachPoint
     {
-        // deleting a texture or renderbuffer immediately detaches it
+    public:
+        WebGLFramebuffer* const mFB;
+    private:
         WebGLRefPtr<WebGLTexture> mTexturePtr;
         WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
         FBAttachment mAttachmentPoint;
         TexImageTarget mTexImageTarget;
         GLint mTexImageLevel;
-        mutable bool mNeedsFinalize;
+
+    public:
+        AttachPoint(WebGLFramebuffer* fb, FBAttachment attachmentPoint);
+        ~AttachPoint();
 
-        explicit Attachment(FBAttachment attachmentPoint = LOCAL_GL_COLOR_ATTACHMENT0);
-        ~Attachment();
+        void Unlink() {
+            mRenderbufferPtr = nullptr;
+            mTexturePtr = nullptr;
+        }
 
         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;
@@ -77,78 +86,94 @@ 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::Attachment& attachment) const;
+    GetFormatForAttachment(const WebGLFramebuffer::AttachPoint& attachment) const;
 
     bool HasDepthStencilConflict() const {
         return int(mDepthAttachment.IsDefined()) +
                int(mStencilAttachment.IsDefined()) +
                int(mDepthStencilAttachment.IsDefined()) >= 2;
     }
 
     size_t ColorAttachmentCount() const {
-        return mColorAttachments.Length();
+        return 1 + mMoreColorAttachments.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 Attachment& DepthAttachment() const {
+    const AttachPoint& DepthAttachment() const {
         return mDepthAttachment;
     }
 
-    const Attachment& StencilAttachment() const {
+    const AttachPoint& StencilAttachment() const {
         return mStencilAttachment;
     }
 
-    const Attachment& DepthStencilAttachment() const {
+    const AttachPoint& DepthStencilAttachment() const {
         return mDepthStencilAttachment;
     }
 
-    const Attachment& GetAttachment(FBAttachment attachment) const;
+    AttachPoint& GetAttachPoint(FBAttachment attachPointEnum);
 
     void DetachTexture(const WebGLTexture* tex);
 
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
 
     const WebGLRectangleObject& RectangleObject() const;
 
     WebGLContext* GetParentObject() const {
@@ -165,33 +190,20 @@ public:
     // mask mirrors glClear.
     bool HasCompletePlanes(GLbitfield mask);
 
     bool CheckAndInitializeAttachments();
 
     bool CheckColorAttachmentNumber(FBAttachment attachment,
                                     const char* funcName) const;
 
-    void EnsureColorAttachments(size_t colorAttachmentId);
+    void EnsureColorAttachPoints(size_t colorAttachmentId);
 
-    void NotifyAttachableChanged() const;
+    void InvalidateFramebufferStatus() const {
+        mStatus = 0;
+    }
 
     bool ValidateForRead(const char* info, TexInternalFormat* const out_format);
-
-private:
-    ~WebGLFramebuffer() {
-        DeleteOnce();
-    }
-
-    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,51 +1,46 @@
 /* -*- 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 "WebGLFramebufferAttachable.h"
+
 #include "WebGLContext.h"
-#include "WebGLFramebufferAttachable.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
-using namespace mozilla;
+namespace mozilla {
 
 void
-WebGLFramebufferAttachable::AttachTo(WebGLFramebuffer* fb, FBAttachment attachment)
+WebGLFramebufferAttachable::MarkAttachment(const WebGLFramebuffer::AttachPoint& attachment)
 {
-    MOZ_ASSERT(fb);
-    if (!fb)
-        return;
-
-    if (mAttachmentPoints.Contains(AttachmentPoint(fb, attachment)))
+    if (mAttachmentPoints.Contains(&attachment))
         return; // Already attached. Ignore.
 
-    mAttachmentPoints.AppendElement(AttachmentPoint(fb, attachment));
+    mAttachmentPoints.AppendElement(&attachment);
 }
 
 void
-WebGLFramebufferAttachable::DetachFrom(WebGLFramebuffer* fb, FBAttachment attachment)
+WebGLFramebufferAttachable::UnmarkAttachment(const WebGLFramebuffer::AttachPoint& attachment)
 {
-    MOZ_ASSERT(fb);
-    if (!fb)
-        return;
-
-    const size_t i = mAttachmentPoints.IndexOf(AttachmentPoint(fb, attachment));
+    const size_t i = mAttachmentPoints.IndexOf(&attachment);
     if (i == mAttachmentPoints.NoIndex) {
         MOZ_ASSERT(false, "Is not attached to FB");
         return;
     }
 
     mAttachmentPoints.RemoveElementAt(i);
 }
 
 void
-WebGLFramebufferAttachable::NotifyFBsStatusChanged()
+WebGLFramebufferAttachable::InvalidateStatusOfAttachedFBs() const
 {
-    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();
+    const size_t count = mAttachmentPoints.Length();
+    for (size_t i = 0; i < count; ++i) {
+        MOZ_ASSERT(mAttachmentPoints[i]->mFB);
+        mAttachmentPoints[i]->mFB->InvalidateFramebufferStatus();
     }
 }
+
+} // namespace mozilla
--- a/dom/canvas/WebGLFramebufferAttachable.h
+++ b/dom/canvas/WebGLFramebufferAttachable.h
@@ -1,46 +1,30 @@
 /* -*- 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 WEBGLFRAMEBUFFERATTACHABLE_H_
-#define WEBGLFRAMEBUFFERATTACHABLE_H_
+#ifndef WEBGL_FRAMEBUFFER_ATTACHABLE_H_
+#define WEBGL_FRAMEBUFFER_ATTACHABLE_H_
 
 #include "GLDefs.h"
+#include "mozilla/WeakPtr.h"
 #include "nsTArray.h"
-#include "mozilla/WeakPtr.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLStrongTypes.h"
 
 namespace mozilla {
 
 class WebGLFramebufferAttachable
 {
-    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;
+    nsTArray<const WebGLFramebuffer::AttachPoint*> mAttachmentPoints;
 
 public:
-
     // Track FBO/Attachment combinations
-    void AttachTo(WebGLFramebuffer* fb, FBAttachment attachment);
-    void DetachFrom(WebGLFramebuffer* fb, FBAttachment attachment);
-    void NotifyFBsStatusChanged();
+    void MarkAttachment(const WebGLFramebuffer::AttachPoint& attachment);
+    void UnmarkAttachment(const WebGLFramebuffer::AttachPoint& attachment);
+    void InvalidateStatusOfAttachedFBs() const;
 };
 
 } // namespace mozilla
 
 #endif // !WEBGLFRAMEBUFFERATTACHABLE_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -176,16 +176,18 @@ RenderbufferStorageMaybeMultisample(gl::
 }
 
 void
 WebGLRenderbuffer::RenderbufferStorage(GLsizei samples, GLenum internalFormat,
                                        GLsizei width, GLsizei height) const
 {
     MOZ_ASSERT(mContext->mBoundRenderbuffer == this);
 
+    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,28 +176,27 @@ 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;
--- a/dom/canvas/test/_webgl-conformance.ini
+++ b/dom/canvas/test/_webgl-conformance.ini
@@ -701,17 +701,16 @@ fail-if = (os == 'linux')
 [webgl-conformance/_wrappers/test_conformance__misc__error-reporting.html]
 [webgl-conformance/_wrappers/test_conformance__misc__instanceof-test.html]
 [webgl-conformance/_wrappers/test_conformance__misc__invalid-passed-params.html]
 skip-if = (os == 'android') || (os == 'b2g') || (os == 'linux')
 [webgl-conformance/_wrappers/test_conformance__misc__is-object.html]
 [webgl-conformance/_wrappers/test_conformance__misc__null-object-behaviour.html]
 [webgl-conformance/_wrappers/test_conformance__misc__functions-returning-strings.html]
 [webgl-conformance/_wrappers/test_conformance__misc__object-deletion-behaviour.html]
-fail-if = (os == 'mac' && os_version == '10.10')
 [webgl-conformance/_wrappers/test_conformance__misc__shader-precision-format.html]
 [webgl-conformance/_wrappers/test_conformance__misc__type-conversion-test.html]
 skip-if = (os == 'android') || (os == 'b2g') || (os == 'linux')
 [webgl-conformance/_wrappers/test_conformance__misc__uninitialized-test.html]
 skip-if = os == 'android'
 [webgl-conformance/_wrappers/test_conformance__misc__webgl-specific.html]
 [webgl-conformance/_wrappers/test_conformance__programs__get-active-test.html]
 [webgl-conformance/_wrappers/test_conformance__programs__gl-bind-attrib-location-test.html]
--- a/dom/canvas/test/webgl-conformance/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conformance/mochitest-errata.ini
@@ -96,19 +96,16 @@ skip-if = (os == 'b2g')
 # Failures after enabling color_buffer_[half_]float.
 fail-if = (os == 'linux')
 
 ########################################################################
 # Mac
 [_wrappers/test_conformance__canvas__drawingbuffer-static-canvas-test.html]
 # Intermittent crash on OSX.
 skip-if = os == 'mac'
-[_wrappers/test_conformance__misc__object-deletion-behaviour.html]
-# Fails on OS X 10.10
-fail-if = (os == 'mac' && os_version == '10.10')
 [_wrappers/test_conformance__rendering__line-loop-tri-fan.html]
 # Fails on OS X 10.10
 fail-if = (os == 'mac' && os_version == '10.10')
 [_wrappers/test_conformance__rendering__point-size.html]
 # Fails on OS X 10.10
 fail-if = (os == 'mac' && os_version == '10.10')
 [_wrappers/test_conformance__textures__copy-tex-image-and-sub-image-2d.html]
 # Fails on OS X 10.10