Bug 1303879 - Refactor framebuffer funcs and completeness caching. - r=mtseng a=ritu
authorJeff Gilbert <jgilbert@mozilla.com>
Tue, 19 Jul 2016 00:36:54 -0700
changeset 355834 ec6e14759d101deeb915901d4a3328a4059870e5
parent 355833 5e28a73451c94094e933fce97bdc309296a34b91
child 355835 2b689ba2cc20fb04dd01b4cc8acb17b6d835f4c0
push id6570
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:26:13 +0000
treeherdermozilla-beta@f455459b2ae5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmtseng, ritu
bugs1303879
milestone51.0a2
Bug 1303879 - Refactor framebuffer funcs and completeness caching. - r=mtseng a=ritu MozReview-Commit-ID: Hi3uEwpmWF4
dom/canvas/WebGL2ContextFramebuffers.cpp
dom/canvas/WebGL2ContextState.cpp
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLContextFramebufferOperations.cpp
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLTextureUpload.cpp
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -8,81 +8,16 @@
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFormats.h"
 #include "WebGLFramebuffer.h"
 
 namespace mozilla {
 
-static bool
-GetFBInfoForBlit(const WebGLFramebuffer* fb, const char* const fbInfo,
-                 GLsizei* const out_samples,
-                 const webgl::FormatInfo** const out_colorFormat,
-                 const webgl::FormatInfo** const out_depthFormat,
-                 const webgl::FormatInfo** const out_stencilFormat)
-{
-    *out_samples = 0;
-    *out_colorFormat = nullptr;
-    *out_depthFormat = nullptr;
-    *out_stencilFormat = nullptr;
-
-    if (fb->ColorAttachment(0).IsDefined()) {
-        const auto& attachment = fb->ColorAttachment(0);
-        *out_samples = attachment.Samples();
-        *out_colorFormat = attachment.Format()->format;
-    }
-
-    if (fb->DepthStencilAttachment().IsDefined()) {
-        const auto& attachment = fb->DepthStencilAttachment();
-        *out_samples = attachment.Samples();
-
-        *out_depthFormat = attachment.Format()->format;
-        *out_stencilFormat = *out_depthFormat;
-    } else {
-        if (fb->DepthAttachment().IsDefined()) {
-            const auto& attachment = fb->DepthAttachment();
-            *out_samples = attachment.Samples();
-            *out_depthFormat = attachment.Format()->format;
-        }
-
-        if (fb->StencilAttachment().IsDefined()) {
-            const auto& attachment = fb->StencilAttachment();
-            *out_samples = attachment.Samples();
-            *out_stencilFormat = attachment.Format()->format;
-        }
-    }
-    return true;
-}
-
-static void
-GetBackbufferFormats(const WebGLContextOptions& options,
-                     const webgl::FormatInfo** const out_color,
-                     const webgl::FormatInfo** const out_depth,
-                     const webgl::FormatInfo** const out_stencil)
-{
-    const auto effFormat = options.alpha ? webgl::EffectiveFormat::RGBA8
-                                          : webgl::EffectiveFormat::RGB8;
-    *out_color = webgl::GetFormat(effFormat);
-
-    *out_depth = nullptr;
-    *out_stencil = nullptr;
-    if (options.depth && options.stencil) {
-        *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8);
-        *out_stencil = *out_depth;
-    } else {
-        if (options.depth) {
-            *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16);
-        }
-        if (options.stencil) {
-            *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
-        }
-    }
-}
-
 void
 WebGL2Context::BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                                GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                                GLbitfield mask, GLenum filter)
 {
     if (IsContextLost())
         return;
 
@@ -98,269 +33,70 @@ WebGL2Context::BlitFramebuffer(GLint src
     case LOCAL_GL_NEAREST:
     case LOCAL_GL_LINEAR:
         break;
     default:
         ErrorInvalidEnumInfo("blitFramebuffer: Bad `filter`:", filter);
         return;
     }
 
-    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
-                                           LOCAL_GL_STENCIL_BUFFER_BIT;
-    if (mask & depthAndStencilBits &&
-        filter != LOCAL_GL_NEAREST)
+    ////
+
+    const auto& readFB = mBoundReadFramebuffer;
+    if (readFB &&
+        !readFB->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER"))
     {
-        ErrorInvalidOperation("blitFramebuffer: DEPTH_BUFFER_BIT and"
-                              " STENCIL_BUFFER_BIT can only be used with"
-                              " NEAREST filtering.");
-        return;
-    }
-
-    if (mBoundReadFramebuffer == mBoundDrawFramebuffer) {
-        // TODO: It's actually more complicated than this. We need to check that
-        // the underlying buffers are not the same, not the framebuffers
-        // themselves.
-        ErrorInvalidOperation("blitFramebuffer: Source and destination must"
-                              " differ.");
         return;
     }
 
-    GLsizei srcSamples;
-    const webgl::FormatInfo* srcColorFormat = nullptr;
-    const webgl::FormatInfo* srcDepthFormat = nullptr;
-    const webgl::FormatInfo* srcStencilFormat = nullptr;
-
-    if (mBoundReadFramebuffer) {
-        if (!mBoundReadFramebuffer->ValidateAndInitAttachments("blitFramebuffer's READ_FRAMEBUFFER"))
-            return;
-
-        if (!GetFBInfoForBlit(mBoundReadFramebuffer, "READ_FRAMEBUFFER", &srcSamples,
-                              &srcColorFormat, &srcDepthFormat, &srcStencilFormat))
-        {
-            return;
-        }
-    } else {
-        srcSamples = 0; // Always 0.
-
-        GetBackbufferFormats(mOptions, &srcColorFormat, &srcDepthFormat,
-                             &srcStencilFormat);
-    }
-
-    GLsizei dstSamples;
-    const webgl::FormatInfo* dstColorFormat = nullptr;
-    const webgl::FormatInfo* dstDepthFormat = nullptr;
-    const webgl::FormatInfo* dstStencilFormat = nullptr;
-
-    if (mBoundDrawFramebuffer) {
-        if (!mBoundDrawFramebuffer->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER"))
-            return;
-
-        if (!GetFBInfoForBlit(mBoundDrawFramebuffer, "DRAW_FRAMEBUFFER", &dstSamples,
-                              &dstColorFormat, &dstDepthFormat, &dstStencilFormat))
-        {
-            return;
-        }
-    } else {
-        dstSamples = gl->Screen()->Samples();
-
-        GetBackbufferFormats(mOptions, &dstColorFormat, &dstDepthFormat,
-                             &dstStencilFormat);
-    }
-
-    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
-        const auto fnSignlessType = [](const webgl::FormatInfo* format)
-                                    -> webgl::ComponentType
-        {
-            if (!format)
-                return webgl::ComponentType::None;
-
-            switch (format->componentType) {
-            case webgl::ComponentType::UInt:
-                return webgl::ComponentType::Int;
-
-            case webgl::ComponentType::NormUInt:
-                return webgl::ComponentType::NormInt;
-
-            default:
-                return format->componentType;
-            }
-        };
-
-        const auto srcType = fnSignlessType(srcColorFormat);
-        const auto dstType = fnSignlessType(dstColorFormat);
-
-        if (srcType != dstType) {
-            ErrorInvalidOperation("blitFramebuffer: Color buffer format component type"
-                                  " mismatch.");
-            return;
-        }
-
-        const bool srcIsInt = (srcType == webgl::ComponentType::Int);
-        if (srcIsInt && filter != LOCAL_GL_NEAREST) {
-            ErrorInvalidOperation("blitFramebuffer: Integer read buffers can only"
-                                  " be filtered with NEAREST.");
-            return;
-        }
-    }
-
-    /* GLES 3.0.4, p199:
-     *   Calling BlitFramebuffer will result in an INVALID_OPERATION error if
-     *   mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
-     *   and destination depth and stencil buffer formats do not match.
-     *
-     * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
-     * the stencil formats must match. This seems wrong. It could be a spec bug,
-     * or I could be missing an interaction in one of the earlier paragraphs.
-     */
-    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
-        dstDepthFormat != srcDepthFormat)
+    const auto& drawFB = mBoundDrawFramebuffer;
+    if (drawFB &&
+        !drawFB->ValidateAndInitAttachments("blitFramebuffer's DRAW_FRAMEBUFFER"))
     {
-        ErrorInvalidOperation("blitFramebuffer: Depth buffer formats must match"
-                              " if selected.");
         return;
     }
 
-    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
-        dstStencilFormat != srcStencilFormat)
-    {
-        ErrorInvalidOperation("blitFramebuffer: Stencil buffer formats must"
-                              " match if selected.");
-        return;
-    }
-
-    if (dstSamples != 0) {
-        ErrorInvalidOperation("blitFramebuffer: DRAW_FRAMEBUFFER may not have"
-                              " multiple samples.");
-        return;
-    }
-
-    if (srcSamples != 0) {
-        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
-            dstColorFormat != srcColorFormat)
-        {
-            ErrorInvalidOperation("blitFramebuffer: Color buffer formats must"
-                                  " match if selected, when reading from a"
-                                  " multisampled source.");
-            return;
-        }
+    ////
 
-        if (dstX0 != srcX0 ||
-            dstX1 != srcX1 ||
-            dstY0 != srcY0 ||
-            dstY1 != srcY1)
-        {
-            ErrorInvalidOperation("blitFramebuffer: If the source is"
-                                  " multisampled, then the source and dest"
-                                  " regions must match exactly.");
-            return;
-        }
-    }
-
-    MakeContextCurrent();
-    gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
-                         dstX0, dstY0, dstX1, dstY1,
-                         mask, filter);
-}
-
-static bool
-ValidateTextureLayerAttachment(GLenum attachment)
-{
-    if (LOCAL_GL_COLOR_ATTACHMENT0 <= attachment &&
-        attachment <= LOCAL_GL_COLOR_ATTACHMENT15)
-    {
-        return true;
-    }
-
-    switch (attachment) {
-    case LOCAL_GL_DEPTH_ATTACHMENT:
-    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-    case LOCAL_GL_STENCIL_ATTACHMENT:
-        return true;
-    }
-
-    return false;
+    WebGLFramebuffer::BlitFramebuffer(this,
+                                      readFB, srcX0, srcY0, srcX1, srcY1,
+                                      drawFB, dstX0, dstY0, dstX1, dstY1,
+                                      mask, filter);
 }
 
 void
 WebGL2Context::FramebufferTextureLayer(GLenum target, GLenum attachment,
                                        WebGLTexture* texture, GLint level, GLint layer)
 {
+    const char funcName[] = "framebufferTextureLayer";
     if (IsContextLost())
         return;
 
-    if (!ValidateFramebufferTarget(target, "framebufferTextureLayer"))
+    if (!ValidateFramebufferTarget(target, funcName))
         return;
 
-    if (!ValidateTextureLayerAttachment(attachment))
-        return ErrorInvalidEnumInfo("framebufferTextureLayer: attachment:", attachment);
-
-    if (texture) {
-        if (texture->IsDeleted()) {
-            return ErrorInvalidValue("framebufferTextureLayer: texture must be a valid "
-                                     "texture object.");
-        }
-
-        if (layer < 0)
-            return ErrorInvalidValue("framebufferTextureLayer: layer must be >= 0.");
-
-        if (level < 0)
-            return ErrorInvalidValue("framebufferTextureLayer: level must be >= 0.");
-
-        switch (texture->Target().get()) {
-        case LOCAL_GL_TEXTURE_3D:
-            if (uint32_t(layer) >= mImplMax3DTextureSize) {
-                return ErrorInvalidValue("framebufferTextureLayer: layer must be < "
-                                         "MAX_3D_TEXTURE_SIZE");
-            }
-
-            if (uint32_t(level) > FloorLog2(mImplMax3DTextureSize)) {
-                return ErrorInvalidValue("framebufferTextureLayer: layer mube be <= "
-                                         "log2(MAX_3D_TEXTURE_SIZE");
-            }
-            break;
-
-        case LOCAL_GL_TEXTURE_2D_ARRAY:
-            if (uint32_t(layer) >= mImplMaxArrayTextureLayers) {
-                return ErrorInvalidValue("framebufferTextureLayer: layer must be < "
-                                         "MAX_ARRAY_TEXTURE_LAYERS");
-            }
-
-            if (uint32_t(level) > FloorLog2(mImplMaxTextureSize)) {
-                return ErrorInvalidValue("framebufferTextureLayer: layer mube be <= "
-                                         "log2(MAX_TEXTURE_SIZE");
-            }
-            break;
-
-        default:
-            return ErrorInvalidOperation("framebufferTextureLayer: texture must be an "
-                                         "existing 3D texture, or a 2D texture array.");
-        }
-    }
-
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
 
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    if (!fb) {
-        return ErrorInvalidOperation("framebufferTextureLayer: cannot modify"
-                                     " framebuffer 0.");
-    }
+    if (!fb)
+        return ErrorInvalidOperation("%a: Xannot modify framebuffer 0.");
 
-    fb->FramebufferTextureLayer(attachment, texture, level, layer);
+    fb->FramebufferTextureLayer(funcName, attachment, texture, level, layer);
 }
 
 JS::Value
 WebGL2Context::GetFramebufferAttachmentParameter(JSContext* cx,
                                                  GLenum target,
                                                  GLenum attachment,
                                                  GLenum pname,
                                                  ErrorResult& out_error)
@@ -527,51 +263,31 @@ WebGL2Context::InvalidateSubFramebuffer(
         gl->fInvalidateSubFramebuffer(target, attachments.Length(),
                                       attachments.Elements(), x, y, width, height);
     }
 }
 
 void
 WebGL2Context::ReadBuffer(GLenum mode)
 {
+    const char funcName[] = "readBuffer";
     if (IsContextLost())
         return;
 
-    const bool isColorAttachment = (mode >= LOCAL_GL_COLOR_ATTACHMENT0 &&
-                                    mode <= LastColorAttachmentEnum());
-
-    if (mode != LOCAL_GL_NONE && mode != LOCAL_GL_BACK && !isColorAttachment) {
-        ErrorInvalidEnum("readBuffer: `mode` must be one of NONE, BACK, or "
-                         "COLOR_ATTACHMENTi. Was %s",
-                         EnumName(mode));
-        return;
-    }
-
     if (mBoundReadFramebuffer) {
-        if (mode != LOCAL_GL_NONE &&
-            !isColorAttachment)
-        {
-            ErrorInvalidOperation("readBuffer: If READ_FRAMEBUFFER is non-null, `mode` "
-                                  "must be COLOR_ATTACHMENTi or NONE. Was %s",
-                                  EnumName(mode));
-            return;
-        }
-
-        MakeContextCurrent();
-        mBoundReadFramebuffer->SetReadBufferMode(mode);
-        gl->fReadBuffer(mode);
+        mBoundReadFramebuffer->ReadBuffer(funcName, mode);
         return;
     }
 
     // Operating on the default framebuffer.
     if (mode != LOCAL_GL_NONE &&
         mode != LOCAL_GL_BACK)
     {
-        ErrorInvalidOperation("readBuffer: If READ_FRAMEBUFFER is null, `mode`"
-                              " must be BACK or NONE. Was %s",
-                              EnumName(mode));
+        ErrorInvalidOperation("%s: If READ_FRAMEBUFFER is null, `mode` must be BACK or"
+                              " NONE. Was %s",
+                              funcName, EnumName(mode));
         return;
     }
 
     gl->Screen()->SetReadBuffer(mode);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextState.cpp
+++ b/dom/canvas/WebGL2ContextState.cpp
@@ -38,20 +38,23 @@ WebGL2Context::GetParameter(JSContext* c
     case LOCAL_GL_TRANSFORM_FEEDBACK_ACTIVE: {
       realGLboolean b = 0;
       gl->fGetBooleanv(pname, &b);
       return JS::BooleanValue(bool(b));
     }
 
     /* GLenum */
     case LOCAL_GL_READ_BUFFER: {
-      if (mBoundReadFramebuffer)
-        return JS::Int32Value(mBoundReadFramebuffer->ReadBufferMode());
+      if (!mBoundReadFramebuffer)
+        return JS::Int32Value(gl->Screen()->GetReadBufferMode());
 
-      return JS::Int32Value(LOCAL_GL_BACK);
+      if (!mBoundReadFramebuffer->ColorReadBuffer())
+        return JS::Int32Value(LOCAL_GL_NONE);
+
+      return JS::Int32Value(mBoundReadFramebuffer->ColorReadBuffer()->mAttachmentPoint);
     }
 
     case LOCAL_GL_FRAGMENT_SHADER_DERIVATIVE_HINT:
       /* fall through */
 
     /* GLint */
     case LOCAL_GL_MAX_COMBINED_UNIFORM_BLOCKS:
     case LOCAL_GL_MAX_ELEMENTS_INDICES:
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1632,31 +1632,21 @@ WebGLContext::EndComposition()
 }
 
 void
 WebGLContext::DummyReadFramebufferOperation(const char* funcName)
 {
     if (!mBoundReadFramebuffer)
         return; // Infallible.
 
-    const auto target = (IsWebGL2() ? LOCAL_GL_READ_FRAMEBUFFER
-                                    : LOCAL_GL_FRAMEBUFFER);
-    nsCString fbStatusInfo;
-    const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(target,
-                                                                      &fbStatusInfo);
+    const auto status = mBoundReadFramebuffer->CheckFramebufferStatus(funcName);
 
     if (status != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-        nsCString errorText("Incomplete framebuffer");
-
-        if (fbStatusInfo.Length()) {
-            errorText += ": ";
-            errorText += fbStatusInfo;
-        }
-
-        ErrorInvalidFramebufferOperation("%s: %s.", funcName, errorText.BeginReading());
+        ErrorInvalidFramebufferOperation("%s: Framebuffer must be complete.",
+                                         funcName);
     }
 }
 
 bool
 WebGLContext::HasTimestampBits() const
 {
     // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or GLES3+.
     return gl->IsSupported(GLFeature::sync);
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1133,16 +1133,18 @@ protected:
     uint32_t mImplMaxColorAttachments;
     uint32_t mImplMaxDrawBuffers;
 
 public:
     GLenum LastColorAttachmentEnum() const {
         return LOCAL_GL_COLOR_ATTACHMENT0 + mImplMaxColorAttachments - 1;
     }
 
+    const decltype(mOptions)& Options() const { return mOptions; }
+
 protected:
 
     // Texture sizes are often not actually the GL values. Let's be explicit that these
     // are implementation limits.
     uint32_t mImplMaxTextureSize;
     uint32_t mImplMaxCubeMapTextureSize;
     uint32_t mImplMax3DTextureSize;
     uint32_t mImplMaxArrayTextureLayers;
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -96,36 +96,38 @@ ScopedResolveTexturesForDraw::ScopedReso
     MOZ_ASSERT(mWebGL->gl->IsCurrent());
 
     if (!mWebGL->mActiveProgramLinkInfo) {
         mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
         *out_error = true;
         return;
     }
 
-    std::vector<const WebGLFBAttachPoint*> fbAttachments;
-    if (mWebGL->mBoundDrawFramebuffer) {
-        const auto& fb = mWebGL->mBoundDrawFramebuffer;
-        fb->GatherAttachments(&fbAttachments);
+    const std::vector<const WebGLFBAttachPoint*>* attachList = nullptr;
+    const auto& fb = mWebGL->mBoundDrawFramebuffer;
+    if (fb) {
+        attachList = &(fb->ResolvedCompleteData()->texDrawBuffers);
     }
 
     MOZ_ASSERT(mWebGL->mActiveProgramLinkInfo);
     const auto& uniformSamplers = mWebGL->mActiveProgramLinkInfo->uniformSamplers;
     for (const auto& uniform : uniformSamplers) {
         const auto& texList = *(uniform->mSamplerTexList);
 
         for (const auto& texUnit : uniform->mSamplerValues) {
             if (texUnit >= texList.Length())
                 continue;
 
             const auto& tex = texList[texUnit];
             if (!tex)
                 continue;
 
-            if (tex->IsFeedback(mWebGL, funcName, texUnit, fbAttachments)) {
+            if (attachList &&
+                tex->IsFeedback(mWebGL, funcName, texUnit, *attachList))
+            {
                 *out_error = true;
                 return;
             }
 
             FakeBlackType fakeBlack;
             if (!tex->ResolveForDraw(funcName, texUnit, &fakeBlack)) {
                 mWebGL->ErrorOutOfMemory("%s: Failed to resolve textures for draw.",
                                          funcName);
@@ -582,17 +584,18 @@ WebGLContext::DrawElementsInstanced(GLen
         gl->fDrawElementsInstanced(mode, count, type,
                                    reinterpret_cast<GLvoid*>(byteOffset),
                                    primcount);
     }
 
     Draw_cleanup(funcName);
 }
 
-void WebGLContext::Draw_cleanup(const char* funcName)
+void
+WebGLContext::Draw_cleanup(const char* funcName)
 {
     UndoFakeVertexAttrib0();
 
     if (!mBoundDrawFramebuffer) {
         Invalidate();
         mShouldPresent = true;
         MOZ_ASSERT(!mBackbufferNeedsClear);
     }
@@ -609,19 +612,22 @@ void WebGLContext::Draw_cleanup(const ch
     }
 
     // Let's check for a really common error: Viewport is larger than the actual
     // destination framebuffer.
     uint32_t destWidth = mViewportWidth;
     uint32_t destHeight = mViewportHeight;
 
     if (mBoundDrawFramebuffer) {
-        const auto& fba = mBoundDrawFramebuffer->ColorAttachment(0);
-        if (fba.IsDefined()) {
-            fba.Size(&destWidth, &destHeight);
+        const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
+        for (const auto& cur : drawBuffers) {
+            if (!cur->IsDefined())
+                continue;
+            cur->Size(&destWidth, &destHeight);
+            break;
         }
     } else {
         destWidth = mWidth;
         destHeight = mHeight;
     }
 
     if (mViewportWidth > int32_t(destWidth) ||
         mViewportHeight > int32_t(destHeight))
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -139,86 +139,47 @@ WebGLContext::DepthMask(WebGLboolean b)
 
 void
 WebGLContext::DrawBuffers(const dom::Sequence<GLenum>& buffers)
 {
     const char funcName[] = "drawBuffers";
     if (IsContextLost())
         return;
 
-    if (!mBoundDrawFramebuffer) {
-        // GLES 3.0.4 p186:
-        // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
-        //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
-        //  constant other than BACK and NONE, or with a value of `n` other than 1, the
-        //  error INVALID_OPERATION is generated."
-        if (buffers.Length() != 1) {
-            ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
-                                  " length of 1.",
-                                  funcName);
-            return;
-        }
-
-        switch (buffers[0]) {
-        case LOCAL_GL_NONE:
-        case LOCAL_GL_BACK:
-            break;
-
-        default:
-            ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
-                                  " BACK or NONE.",
-                                  funcName);
-            return;
-        }
-
-        mDefaultFB_DrawBuffer0 = buffers[0];
-        gl->Screen()->SetDrawBuffer(buffers[0]);
+    if (mBoundDrawFramebuffer) {
+        mBoundDrawFramebuffer->DrawBuffers(funcName, buffers);
         return;
     }
 
-    // Framebuffer object (not default framebuffer)
-
-    if (buffers.Length() > mImplMaxDrawBuffers) {
-        // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
-        ErrorInvalidValue("%s: `buffers` must have a length <= MAX_DRAW_BUFFERS.",
-                          funcName);
+    // GLES 3.0.4 p186:
+    // "If the GL is bound to the default framebuffer, then `n` must be 1 and the
+    //  constant must be BACK or NONE. [...] If DrawBuffers is supplied with a
+    //  constant other than BACK and NONE, or with a value of `n` other than 1, the
+    //  error INVALID_OPERATION is generated."
+    if (buffers.Length() != 1) {
+        ErrorInvalidOperation("%s: For the default framebuffer, `buffers` must have a"
+                              " length of 1.",
+                              funcName);
         return;
     }
 
-    for (size_t i = 0; i < buffers.Length(); i++) {
-        // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed in
-        //  bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of order,
-        //  BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to the value of
-        // MAX_COLOR_ATTACHMENTS, will generate the error INVALID_OPERATION.
+    switch (buffers[0]) {
+    case LOCAL_GL_NONE:
+    case LOCAL_GL_BACK:
+        break;
 
-        // WEBGL_draw_buffers:
-        // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
-        //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
-        // This means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
-        // be larger than MaxColorAttachments.
-        if (buffers[i] != LOCAL_GL_NONE &&
-            buffers[i] != LOCAL_GL_COLOR_ATTACHMENT0 + i)
-        {
-            ErrorInvalidOperation("%s: `buffers[i]` must be NONE or COLOR_ATTACHMENTi.",
-                                  funcName);
-            return;
-        }
+    default:
+        ErrorInvalidOperation("%s: For the default framebuffer, `buffers[0]` must be"
+                              " BACK or NONE.",
+                              funcName);
+        return;
     }
 
-    MakeContextCurrent();
-
-    const GLenum* ptr = nullptr;
-    if (buffers.Length()) {
-        ptr = buffers.Elements();
-    }
-
-    gl->fDrawBuffers(buffers.Length(), ptr);
-
-    const auto end = ptr + buffers.Length();
-    mBoundDrawFramebuffer->mDrawBuffers.assign(ptr, end);
+    mDefaultFB_DrawBuffer0 = buffers[0];
+    gl->Screen()->SetDrawBuffer(buffers[0]);
 }
 
 void
 WebGLContext::StencilMask(GLuint mask)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -247,20 +247,21 @@ WebGLContext::BlendFuncSeparate(GLenum s
 
     MakeContextCurrent();
     gl->fBlendFuncSeparate(srcRGB, dstRGB, srcAlpha, dstAlpha);
 }
 
 GLenum
 WebGLContext::CheckFramebufferStatus(GLenum target)
 {
+    const char funcName[] = "checkFramebufferStatus";
     if (IsContextLost())
         return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
 
-    if (!ValidateFramebufferTarget(target, "invalidateFramebuffer"))
+    if (!ValidateFramebufferTarget(target, funcName))
         return 0;
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
@@ -271,18 +272,17 @@ WebGLContext::CheckFramebufferStatus(GLe
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
     if (!fb)
         return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 
-    nsCString fbErrorInfo;
-    return fb->CheckFramebufferStatus(target, &fbErrorInfo).get();
+    return fb->CheckFramebufferStatus(funcName).get();
 }
 
 already_AddRefed<WebGLProgram>
 WebGLContext::CreateProgram()
 {
     if (IsContextLost())
         return nullptr;
     RefPtr<WebGLProgram> globj = new WebGLProgram(this);
@@ -479,20 +479,21 @@ WebGLContext::DepthRange(GLfloat zNear, 
     MakeContextCurrent();
     gl->fDepthRange(zNear, zFar);
 }
 
 void
 WebGLContext::FramebufferRenderbuffer(GLenum target, GLenum attachment,
                                       GLenum rbtarget, WebGLRenderbuffer* wrb)
 {
+    const char funcName[] = "framebufferRenderbuffer";
     if (IsContextLost())
         return;
 
-    if (!ValidateFramebufferTarget(target, "framebufferRenderbuffer"))
+    if (!ValidateFramebufferTarget(target, funcName))
         return;
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
@@ -500,114 +501,55 @@ WebGLContext::FramebufferRenderbuffer(GL
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    if (!fb) {
-        return ErrorInvalidOperation("framebufferRenderbuffer: cannot modify"
-                                     " framebuffer 0.");
-    }
-
-    if (rbtarget != LOCAL_GL_RENDERBUFFER) {
-        return ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:",
-                                    rbtarget);
-    }
-
-    if (!ValidateFramebufferAttachment(fb, attachment, "framebufferRenderbuffer"))
-        return;
-
-    fb->FramebufferRenderbuffer(attachment, rbtarget, wrb);
+    if (!fb)
+        return ErrorInvalidOperation("%s: Cannot modify framebuffer 0.", funcName);
+
+    fb->FramebufferRenderbuffer(funcName, attachment, rbtarget, wrb);
 }
 
 void
 WebGLContext::FramebufferTexture2D(GLenum target,
                                    GLenum attachment,
                                    GLenum textarget,
                                    WebGLTexture* tobj,
                                    GLint level)
 {
+    const char funcName[] = "framebufferTexture2D";
     if (IsContextLost())
         return;
 
-    if (!ValidateFramebufferTarget(target, "framebufferTexture2D"))
-        return;
-
-    if (level < 0) {
-        ErrorInvalidValue("framebufferTexture2D: level must not be negative.");
+    if (!ValidateFramebufferTarget(target, funcName))
         return;
-    }
-
-    if (textarget != LOCAL_GL_TEXTURE_2D &&
-        (textarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
-         textarget > LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z))
-    {
-        return ErrorInvalidEnumInfo("framebufferTexture2D: textarget:",
-                                    textarget);
-    }
-
-    if (IsWebGL2()) {
-        /* GLES 3.0.4 p208:
-         *   If textarget is one of TEXTURE_CUBE_MAP_POSITIVE_X,
-         *   TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP_POSITIVE_Z,
-         *   TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP_NEGATIVE_Y,
-         *   or TEXTURE_CUBE_MAP_NEGATIVE_Z, then level must be greater
-         *   than or equal to zero and less than or equal to log2 of the
-         *   value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D,
-         *   level must be greater than or equal to zero and no larger than
-         *   log2 of the value of MAX_TEXTURE_SIZE. Otherwise, an
-         *   INVALID_VALUE error is generated.
-         */
-
-        if (textarget == LOCAL_GL_TEXTURE_2D) {
-            if (uint32_t(level) > FloorLog2(mImplMaxTextureSize)) {
-                ErrorInvalidValue("framebufferTexture2D: level is too large.");
-                return;
-            }
-        } else {
-            MOZ_ASSERT(textarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
-                       textarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
-
-            if (uint32_t(level) > FloorLog2(mImplMaxCubeMapTextureSize)) {
-                ErrorInvalidValue("framebufferTexture2D: level is too large.");
-                return;
-            }
-        }
-    } else if (level != 0) {
-        ErrorInvalidValue("framebufferTexture2D: level must be 0.");
-        return;
-    }
 
     WebGLFramebuffer* fb;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         break;
 
     case LOCAL_GL_READ_FRAMEBUFFER:
         fb = mBoundReadFramebuffer;
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    if (!fb) {
-        return ErrorInvalidOperation("framebufferTexture2D: cannot modify"
-                                     " framebuffer 0.");
-    }
-
-    if (!ValidateFramebufferAttachment(fb, attachment, "framebufferTexture2D"))
-        return;
-
-    fb->FramebufferTexture2D(attachment, textarget, tobj, level);
+    if (!fb)
+        return ErrorInvalidOperation("%s: Cannot modify framebuffer 0.", funcName);
+
+    fb->FramebufferTexture2D(funcName, attachment, textarget, tobj, level);
 }
 
 void
 WebGLContext::FrontFace(GLenum mode)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -1,36 +1,50 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLFramebuffer.h"
 
+// You know it's going to be fun when these two show up:
+#include <algorithm>
+#include <iterator>
+
 #include "GLContext.h"
+#include "GLScreenBuffer.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "nsPrintfCString.h"
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
+WebGLFBAttachPoint::WebGLFBAttachPoint()
+    : mFB(nullptr)
+    , mAttachmentPoint(0)
+    , mTexImageTarget(LOCAL_GL_NONE)
+    , mTexImageLayer(0)
+    , mTexImageLevel(0)
+{ }
+
 WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint)
     : mFB(fb)
     , mAttachmentPoint(attachmentPoint)
     , mTexImageTarget(LOCAL_GL_NONE)
     , mTexImageLayer(0)
     , mTexImageLevel(0)
 { }
 
 WebGLFBAttachPoint::~WebGLFBAttachPoint()
 {
+    MOZ_ASSERT(mFB, "Should have been Init'd.");
     MOZ_ASSERT(!mRenderbufferPtr);
     MOZ_ASSERT(!mTexturePtr);
 }
 
 void
 WebGLFBAttachPoint::Unlink()
 {
     Clear();
@@ -81,25 +95,16 @@ WebGLFBAttachPoint::Samples() const
 }
 
 bool
 WebGLFBAttachPoint::HasAlpha() const
 {
     return Format()->format->a;
 }
 
-const webgl::FormatUsageInfo*
-WebGLFramebuffer::GetFormatForAttachment(const WebGLFBAttachPoint& attachment) const
-{
-    MOZ_ASSERT(attachment.IsDefined());
-    MOZ_ASSERT(attachment.Texture() || attachment.Renderbuffer());
-
-    return attachment.Format();
-}
-
 bool
 WebGLFBAttachPoint::IsReadableFloat() const
 {
     auto formatUsage = Format();
     MOZ_ASSERT(formatUsage);
 
     auto format = formatUsage->format;
     if (!format->IsColorFormat())
@@ -120,24 +125,18 @@ WebGLFBAttachPoint::Clear()
 
     mTexturePtr = nullptr;
     mRenderbufferPtr = nullptr;
 
     OnBackingStoreRespecified();
 }
 
 void
-WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level)
-{
-    SetTexImageLayer(tex, target, level, 0);
-}
-
-void
-WebGLFBAttachPoint::SetTexImageLayer(WebGLTexture* tex, TexImageTarget target,
-                                     GLint level, GLint layer)
+WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level,
+                                GLint layer)
 {
     Clear();
 
     mTexturePtr = tex;
     mTexImageTarget = target;
     mTexImageLevel = level;
     mTexImageLayer = layer;
 
@@ -171,17 +170,17 @@ WebGLFBAttachPoint::HasUninitializedImag
 
     auto& imageInfo = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel);
     MOZ_ASSERT(imageInfo.IsDefined());
 
     return !imageInfo.IsDataInitialized();
 }
 
 void
-WebGLFBAttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus)
+WebGLFBAttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus) const
 {
     if (!HasImage())
         return;
 
     if (mRenderbufferPtr) {
         mRenderbufferPtr->mImageDataStatus = newStatus;
         return;
     }
@@ -338,94 +337,70 @@ WebGLFBAttachPoint::IsComplete(WebGLCont
             return false;
         }
     }
 
     return true;
 }
 
 void
-WebGLFBAttachPoint::FinalizeAttachment(gl::GLContext* gl, FBTarget target,
-                                       GLenum attachment) const
+WebGLFBAttachPoint::Resolve(gl::GLContext* gl, FBTarget target) const
 {
     if (!HasImage()) {
-        switch (attachment) {
+        switch (mAttachmentPoint) {
         case LOCAL_GL_DEPTH_ATTACHMENT:
         case LOCAL_GL_STENCIL_ATTACHMENT:
         case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
             break;
 
         default:
-            gl->fFramebufferRenderbuffer(target, attachment,
+            gl->fFramebufferRenderbuffer(target.get(), mAttachmentPoint,
                                          LOCAL_GL_RENDERBUFFER, 0);
             break;
         }
 
         return;
     }
-    MOZ_ASSERT(HasImage());
 
-    if (Texture()) {
-        MOZ_ASSERT(gl == Texture()->mContext->GL());
-
-        const GLenum imageTarget = ImageTarget().get();
-        const GLint mipLevel = MipLevel();
-        const GLint layer = Layer();
-        const GLuint glName = Texture()->mGLName;
-
-        switch (imageTarget) {
-        case LOCAL_GL_TEXTURE_2D:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
-        case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
-            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-                gl->fFramebufferTexture2D(target, LOCAL_GL_DEPTH_ATTACHMENT,
-                                          imageTarget, glName, mipLevel);
-                gl->fFramebufferTexture2D(target,
-                                          LOCAL_GL_STENCIL_ATTACHMENT, imageTarget,
-                                          glName, mipLevel);
-            } else {
-                gl->fFramebufferTexture2D(target, attachment, imageTarget,
-                                          glName, mipLevel);
-            }
-            break;
-
-        case LOCAL_GL_TEXTURE_2D_ARRAY:
-        case LOCAL_GL_TEXTURE_3D:
-            if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-                gl->fFramebufferTextureLayer(target,
-                                             LOCAL_GL_DEPTH_ATTACHMENT, glName, mipLevel,
-                                             layer);
-                gl->fFramebufferTextureLayer(target,
-                                             LOCAL_GL_STENCIL_ATTACHMENT, glName,
-                                             mipLevel, layer);
-            } else {
-                gl->fFramebufferTextureLayer(target, attachment, glName,
-                                             mipLevel, layer);
-            }
-            break;
-        }
+    if (Renderbuffer()) {
+        Renderbuffer()->DoFramebufferRenderbuffer(target, mAttachmentPoint);
         return;
     }
+    MOZ_ASSERT(Texture());
 
-    if (Renderbuffer()) {
-        Renderbuffer()->DoFramebufferRenderbuffer(target, attachment);
-        return;
+    MOZ_ASSERT(gl == Texture()->mContext->GL());
+
+    const auto& texName = Texture()->mGLName;
+
+    switch (mTexImageTarget.get()) {
+    case LOCAL_GL_TEXTURE_2D:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
+    case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
+        gl->fFramebufferTexture2D(target.get(), mAttachmentPoint, mTexImageTarget.get(),
+                                  texName, mTexImageLevel);
+        break;
+
+    case LOCAL_GL_TEXTURE_2D_ARRAY:
+    case LOCAL_GL_TEXTURE_3D:
+        // If we have fFramebufferTextureLayer, we can rely on having
+        // DEPTH_STENCIL_ATTACHMENT.
+        gl->fFramebufferTextureLayer(target.get(), mAttachmentPoint,
+                                     texName, mTexImageLevel, mTexImageLayer);
+        break;
     }
-
-    MOZ_CRASH("GFX: invalid render buffer");
 }
 
 JS::Value
 WebGLFBAttachPoint::GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
                                  GLenum target, GLenum attachment, GLenum pname,
-                                 ErrorResult* const out_error)
+                                 ErrorResult* const out_error) const
 {
     const bool hasAttachment = (mTexturePtr || mRenderbufferPtr);
     if (!hasAttachment) {
         // Divergent between GLES 3 and 2.
 
         // GLES 2.0.25 p127:
         // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, then querying any
         //  other pname will generate INVALID_ENUM."
@@ -620,367 +595,231 @@ WebGLFBAttachPoint::GetParameter(const c
         MOZ_ASSERT(false, "Missing case.");
         break;
     }
 
     return JS::Int32Value(ret);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////////////
 // WebGLFramebuffer
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
     : WebGLContextBoundObject(webgl)
     , mGLName(fbo)
-    , mIsKnownFBComplete(false)
-    , mReadBufferMode(LOCAL_GL_COLOR_ATTACHMENT0)
-    , mColorAttachment0(this, LOCAL_GL_COLOR_ATTACHMENT0)
+#ifdef ANDROID
+    , mIsFB(false)
+#endif
     , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT)
     , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT)
     , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
-    , mMoreColorAttachments(webgl->mGLMaxColorAttachments)
-    , mDrawBuffers(1, LOCAL_GL_COLOR_ATTACHMENT0)
-#ifdef ANDROID
-    , mIsFB(false)
-#endif
 {
     mContext->mFramebuffers.insertBack(this);
+
+    size_t i = 0;
+    for (auto& cur : mColorAttachments) {
+        new (&cur) WebGLFBAttachPoint(this, LOCAL_GL_COLOR_ATTACHMENT0 + i);
+        i++;
+    }
+
+    mColorDrawBuffers.push_back(&mColorAttachments[0]);
+    mColorReadBuffer = &mColorAttachments[0];
 }
 
 void
 WebGLFramebuffer::Delete()
 {
-    mColorAttachment0.Clear();
+    InvalidateFramebufferStatus();
+
     mDepthAttachment.Clear();
     mStencilAttachment.Clear();
     mDepthStencilAttachment.Clear();
 
-    for (auto& cur : mMoreColorAttachments) {
+    for (auto& cur : mColorAttachments) {
         cur.Clear();
     }
 
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteFramebuffers(1, &mGLName);
 
     LinkedListElement<WebGLFramebuffer>::removeFrom(mContext->mFramebuffers);
 
 #ifdef ANDROID
     mIsFB = false;
 #endif
 }
 
-void
-WebGLFramebuffer::FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
-                                          WebGLRenderbuffer* rb)
+////
+
+Maybe<WebGLFBAttachPoint*>
+WebGLFramebuffer::GetColorAttachPoint(GLenum attachPoint)
 {
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: renderbuffer", rb))
-        return;
-
-    // `attachPointEnum` is validated by ValidateFramebufferAttachment().
+    if (attachPoint == LOCAL_GL_NONE)
+        return Some<WebGLFBAttachPoint*>(nullptr);
 
-    RefPtr<WebGLRenderbuffer> rb_ = rb; // Bug 1201275
-    const auto fnAttach = [this, &rb_](GLenum attachment) {
-        const auto attachPoint = this->GetAttachPoint(attachment);
-        MOZ_ASSERT(attachPoint);
+    if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT0)
+        return Nothing();
 
-        attachPoint->SetRenderbuffer(rb_);
-    };
+    const size_t colorId = attachPoint - LOCAL_GL_COLOR_ATTACHMENT0;
 
-    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
-        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
-    } else {
-        fnAttach(attachment);
-    }
+    MOZ_ASSERT(mContext->mImplMaxColorAttachments <= kMaxColorAttachments);
+    if (colorId >= mContext->mImplMaxColorAttachments)
+        return Nothing();
 
-    InvalidateFramebufferStatus();
+    return Some(&mColorAttachments[colorId]);
 }
 
-void
-WebGLFramebuffer::FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
-                                       WebGLTexture* tex, GLint level)
-{
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
-        return;
-
-    if (tex) {
-        if (!tex->HasEverBeenBound()) {
-            mContext->ErrorInvalidOperation("framebufferTexture2D: the texture"
-                                            " is not the name of a texture.");
-            return;
-        }
-
-        const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget);
-        if (tex->Target() != destTexTarget) {
-            mContext->ErrorInvalidOperation("framebufferTexture2D: Mismatched"
-                                            " texture and texture target.");
-            return;
-        }
-    }
-
-    RefPtr<WebGLTexture> tex_ = tex; // Bug 1201275
-    const auto fnAttach = [this, &tex_, texImageTarget, level](GLenum attachment) {
-        const auto attachPoint = this->GetAttachPoint(attachment);
-        MOZ_ASSERT(attachPoint);
-
-        attachPoint->SetTexImage(tex_, texImageTarget, level);
-    };
-
-    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
-        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
-    } else {
-        fnAttach(attachment);
-    }
-
-    InvalidateFramebufferStatus();
-}
-
-void
-WebGLFramebuffer::FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex,
-                                          GLint level, GLint layer)
-{
-    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
-               mContext->mBoundReadFramebuffer == this);
-
-    const TexImageTarget texImageTarget = (tex ? tex->Target().get()
-                                               : LOCAL_GL_TEXTURE_2D);
-
-    RefPtr<WebGLTexture> tex_ = tex; // Bug 1201275
-    const auto fnAttach = [this, &tex_, texImageTarget, level, layer](GLenum attachment) {
-        const auto attachPoint = this->GetAttachPoint(attachment);
-        MOZ_ASSERT(attachPoint);
-
-        attachPoint->SetTexImageLayer(tex_, texImageTarget, level, layer);
-    };
-
-    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        fnAttach(LOCAL_GL_DEPTH_ATTACHMENT);
-        fnAttach(LOCAL_GL_STENCIL_ATTACHMENT);
-    } else {
-        fnAttach(attachment);
-    }
-
-    InvalidateFramebufferStatus();
-}
-
-WebGLFBAttachPoint*
+Maybe<WebGLFBAttachPoint*>
 WebGLFramebuffer::GetAttachPoint(GLenum attachPoint)
 {
     switch (attachPoint) {
-    case LOCAL_GL_COLOR_ATTACHMENT0:
-        return &mColorAttachment0;
-
     case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-        return &mDepthStencilAttachment;
+        return Some(&mDepthStencilAttachment);
 
     case LOCAL_GL_DEPTH_ATTACHMENT:
-        return &mDepthAttachment;
+        return Some(&mDepthAttachment);
 
     case LOCAL_GL_STENCIL_ATTACHMENT:
-        return &mStencilAttachment;
+        return Some(&mStencilAttachment);
 
     default:
-        break;
+        return GetColorAttachPoint(attachPoint);
     }
-
-    const auto lastCAEnum = mContext->LastColorAttachmentEnum();
-    if (attachPoint < LOCAL_GL_COLOR_ATTACHMENT1 ||
-        attachPoint > lastCAEnum)
-    {
-        return nullptr;
-    }
+}
 
-    if (!mMoreColorAttachments.Size()) {
-        for (GLenum cur = LOCAL_GL_COLOR_ATTACHMENT1; cur <= lastCAEnum; cur++) {
-            mMoreColorAttachments.AppendNew(this, cur);
-        }
+#define FOR_EACH_ATTACHMENT(X)            \
+    X(mDepthAttachment);                  \
+    X(mStencilAttachment);                \
+    X(mDepthStencilAttachment);           \
+                                          \
+    for (auto& cur : mColorAttachments) { \
+        X(cur);                           \
     }
-    MOZ_ASSERT(LOCAL_GL_COLOR_ATTACHMENT0 + mMoreColorAttachments.Size() == lastCAEnum);
-
-    const size_t offset = attachPoint - LOCAL_GL_COLOR_ATTACHMENT1;
-    MOZ_ASSERT(offset <= mMoreColorAttachments.Size());
-    return &mMoreColorAttachments[offset];
-}
 
 void
 WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
 {
-    if (mColorAttachment0.Texture() == tex)
-        mColorAttachment0.Clear();
-
-    if (mDepthAttachment.Texture() == tex)
-        mDepthAttachment.Clear();
+    const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
+        if (attach.Texture() == tex) {
+            attach.Clear();
+        }
+    };
 
-    if (mStencilAttachment.Texture() == tex)
-        mStencilAttachment.Clear();
-
-    if (mDepthStencilAttachment.Texture() == tex)
-        mDepthStencilAttachment.Clear();
-
-    for (auto& cur : mMoreColorAttachments) {
-        if (cur.Texture() == tex)
-            cur.Clear();
-    }
+    FOR_EACH_ATTACHMENT(fnDetach)
 }
 
 void
 WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
 {
-    if (mColorAttachment0.Renderbuffer() == rb)
-        mColorAttachment0.Clear();
-
-    if (mDepthAttachment.Renderbuffer() == rb)
-        mDepthAttachment.Clear();
-
-    if (mStencilAttachment.Renderbuffer() == rb)
-        mStencilAttachment.Clear();
+    const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
+        if (attach.Renderbuffer() == rb) {
+            attach.Clear();
+        }
+    };
 
-    if (mDepthStencilAttachment.Renderbuffer() == rb)
-        mDepthStencilAttachment.Clear();
+    FOR_EACH_ATTACHMENT(fnDetach)
+}
 
-    for (auto& cur : mMoreColorAttachments) {
-        if (cur.Renderbuffer() == rb)
-            cur.Clear();
-    }
-}
+////////////////////////////////////////////////////////////////////////////////
+// Completeness
 
 bool
 WebGLFramebuffer::HasDefinedAttachments() const
 {
     bool hasAttachments = false;
+    const auto func = [&](const WebGLFBAttachPoint& attach) {
+        hasAttachments |= attach.IsDefined();
+    };
 
-    hasAttachments |= mColorAttachment0.IsDefined();
-    hasAttachments |= mDepthAttachment.IsDefined();
-    hasAttachments |= mStencilAttachment.IsDefined();
-    hasAttachments |= mDepthStencilAttachment.IsDefined();
-
-    for (const auto& cur : mMoreColorAttachments) {
-        hasAttachments |= cur.IsDefined();
-    }
-
+    FOR_EACH_ATTACHMENT(func)
     return hasAttachments;
 }
 
 bool
 WebGLFramebuffer::HasIncompleteAttachments(nsCString* const out_info) const
 {
-    const auto fnIsIncomplete = [this, out_info](const WebGLFBAttachPoint& cur) {
+    bool hasIncomplete = false;
+    const auto func = [&](const WebGLFBAttachPoint& cur) {
         if (!cur.IsDefined())
-            return false; // Not defined, so can't count as incomplete.
+            return; // Not defined, so can't count as incomplete.
 
-        return !cur.IsComplete(this->mContext, out_info);
+        hasIncomplete |= !cur.IsComplete(mContext, out_info);
     };
 
-    bool hasIncomplete = false;
-
-    hasIncomplete |= fnIsIncomplete(mColorAttachment0);
-    hasIncomplete |= fnIsIncomplete(mDepthAttachment);
-    hasIncomplete |= fnIsIncomplete(mStencilAttachment);
-    hasIncomplete |= fnIsIncomplete(mDepthStencilAttachment);
-
-    for (const auto& cur : mMoreColorAttachments) {
-        hasIncomplete |= fnIsIncomplete(cur);
-    }
-
+    FOR_EACH_ATTACHMENT(func)
     return hasIncomplete;
 }
 
 bool
 WebGLFramebuffer::AllImageRectsMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     DebugOnly<nsCString> fbStatusInfo;
     MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
 
     bool needsInit = true;
     uint32_t width = 0;
     uint32_t height = 0;
 
-    const auto fnInitializeOrMatch = [&needsInit, &width,
-                                      &height](const WebGLFBAttachPoint& attach)
-    {
+    bool hasMismatch = false;
+    const auto func = [&](const WebGLFBAttachPoint& attach) {
         if (!attach.HasImage())
-            return true;
+            return;
 
         uint32_t curWidth;
         uint32_t curHeight;
         attach.Size(&curWidth, &curHeight);
 
         if (needsInit) {
             needsInit = false;
             width = curWidth;
             height = curHeight;
-            return true;
+            return;
         }
 
-        return (curWidth == width &&
-                curHeight == height);
+        hasMismatch |= (curWidth != width ||
+                        curHeight != height);
     };
 
-    bool matches = true;
-
-    matches &= fnInitializeOrMatch(mColorAttachment0      );
-    matches &= fnInitializeOrMatch(mDepthAttachment       );
-    matches &= fnInitializeOrMatch(mStencilAttachment     );
-    matches &= fnInitializeOrMatch(mDepthStencilAttachment);
-
-    for (const auto& cur : mMoreColorAttachments) {
-        matches &= fnInitializeOrMatch(cur);
-    }
-
-    return matches;
+    FOR_EACH_ATTACHMENT(func)
+    return !hasMismatch;
 }
 
 bool
 WebGLFramebuffer::AllImageSamplesMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     DebugOnly<nsCString> fbStatusInfo;
     MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
 
     bool needsInit = true;
     uint32_t samples = 0;
 
-    const auto fnInitializeOrMatch = [&needsInit,
-                                      &samples](const WebGLFBAttachPoint& attach)
-    {
+    bool hasMismatch = false;
+    const auto func = [&](const WebGLFBAttachPoint& attach) {
         if (!attach.HasImage())
-          return true;
+          return;
 
         const uint32_t curSamples = attach.Samples();
 
         if (needsInit) {
             needsInit = false;
             samples = curSamples;
-            return true;
+            return;
         }
 
-        return (curSamples == samples);
+        hasMismatch |= (curSamples != samples);
     };
 
-    bool matches = true;
-
-    matches &= fnInitializeOrMatch(mColorAttachment0      );
-    matches &= fnInitializeOrMatch(mDepthAttachment       );
-    matches &= fnInitializeOrMatch(mStencilAttachment     );
-    matches &= fnInitializeOrMatch(mDepthStencilAttachment);
+    FOR_EACH_ATTACHMENT(func)
+    return !hasMismatch;
+}
 
-    for (const auto& cur : mMoreColorAttachments) {
-        matches &= fnInitializeOrMatch(cur);
-    }
-
-    return matches;
-}
+#undef FOR_EACH_ATTACHMENT
 
 FBStatus
 WebGLFramebuffer::PrecheckFramebufferStatus(nsCString* const out_info) const
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (!HasDefinedAttachments())
@@ -990,365 +829,948 @@ WebGLFramebuffer::PrecheckFramebufferSta
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
 
     if (!AllImageRectsMatch())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS; // Inconsistent sizes
 
     if (!AllImageSamplesMatch())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; // Inconsistent samples
 
-    if (!mContext->IsWebGL2()) {
-        // INCOMPLETE_DIMENSIONS doesn't exist in GLES3.
+    if (mContext->IsWebGL2()) {
+        MOZ_ASSERT(!mDepthStencilAttachment.IsDefined());
+    } else {
         const auto depthOrStencilCount = int(mDepthAttachment.IsDefined()) +
                                          int(mStencilAttachment.IsDefined()) +
                                          int(mDepthStencilAttachment.IsDefined());
         if (depthOrStencilCount > 1)
             return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
     }
 
     return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 }
 
-FBStatus
-WebGLFramebuffer::CheckFramebufferStatus(FBTarget target, nsCString* const out_info) const
-{
-    if (mIsKnownFBComplete)
-        return LOCAL_GL_FRAMEBUFFER_COMPLETE;
-
-    FBStatus ret = PrecheckFramebufferStatus(out_info);
-    if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE)
-        return ret;
-
-    // Looks good on our end. Let's ask the driver.
-    mContext->MakeContextCurrent();
-
-    // Ok, attach our chosen flavor of {DEPTH, STENCIL, DEPTH_STENCIL}.
-    FinalizeAttachments();
-
-    ret = mContext->gl->fCheckFramebufferStatus(target);
-
-    if (ret == LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-        mIsKnownFBComplete = true;
-    } else {
-        out_info->AssignLiteral("Bad status according to the driver");
-    }
-
-    return ret;
-}
+////////////////////////////////////////
+// Validation
 
 bool
 WebGLFramebuffer::ValidateAndInitAttachments(const char* funcName)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
-    nsCString fbStatusInfo;
-    const auto fbStatus = CheckFramebufferStatus(&fbStatusInfo);
-    if (fbStatus != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
-        nsCString errorText = nsPrintfCString("Incomplete framebuffer: Status 0x%04x",
-                                              fbStatus.get());
-        if (fbStatusInfo.Length()) {
-            errorText += ": ";
-            errorText += fbStatusInfo;
-        }
-
-        mContext->ErrorInvalidFramebufferOperation("%s: %s.", funcName,
-                                                   errorText.BeginReading());
-        return false;
-    }
-
-    // Cool! We've checked out ok. Just need to initialize.
-
-    //////
-    // Check if we need to initialize anything
-
-    std::vector<WebGLFBAttachPoint*> tex3DToClear;
-
-    const auto fnGatherIf3D = [&](WebGLFBAttachPoint& attach) {
-        if (!attach.Texture())
-            return false;
-
-        const auto& info = attach.Texture()->ImageInfoAt(attach.ImageTarget(),
-                                                         attach.MipLevel());
-        if (info.mDepth == 1)
-            return false;
-
-        tex3DToClear.push_back(&attach);
+    const auto fbStatus = CheckFramebufferStatus(funcName);
+    if (fbStatus == LOCAL_GL_FRAMEBUFFER_COMPLETE)
         return true;
-    };
-
-    //////
-
-    uint32_t clearBits = 0;
-    std::vector<GLenum> drawBuffersForClear(1 + mMoreColorAttachments.Size(),
-                                            LOCAL_GL_NONE);
-
-    std::vector<WebGLFBAttachPoint*> attachmentsToClear;
-    attachmentsToClear.reserve(1 + mMoreColorAttachments.Size() + 3);
-
-    const auto fnGatherColor = [&](WebGLFBAttachPoint& attach, uint32_t colorAttachNum) {
-        if (!IsDrawBuffer(colorAttachNum) || !attach.HasUninitializedImageData())
-            return;
-
-        if (fnGatherIf3D(attach))
-            return;
-
-        attachmentsToClear.push_back(&attach);
-
-        clearBits |= LOCAL_GL_COLOR_BUFFER_BIT;
-        drawBuffersForClear[colorAttachNum] = LOCAL_GL_COLOR_ATTACHMENT0 + colorAttachNum;
-    };
-
-    const auto fnGatherOther = [&](WebGLFBAttachPoint& attach, GLenum attachClearBits) {
-        if (!attach.HasUninitializedImageData())
-            return;
-
-        if (fnGatherIf3D(attach))
-            return;
-
-        attachmentsToClear.push_back(&attach);
-
-        clearBits |= attachClearBits;
-    };
-
-    //////
-
-    fnGatherColor(mColorAttachment0, 0);
-
-    size_t colorAttachNum = 1;
-    for (auto& cur : mMoreColorAttachments) {
-        fnGatherColor(cur, colorAttachNum);
-        ++colorAttachNum;
-    }
-
-    fnGatherOther(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
-    fnGatherOther(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
-    fnGatherOther(mDepthStencilAttachment,
-                  LOCAL_GL_DEPTH_BUFFER_BIT | LOCAL_GL_STENCIL_BUFFER_BIT);
-
-    //////
-
-    mContext->MakeContextCurrent();
-
-    if (clearBits) {
-        const auto fnDrawBuffers = [this](const std::vector<GLenum>& list) {
-            this->mContext->gl->fDrawBuffers(list.size(), list.data());
-        };
-
-        const auto drawBufferExt = WebGLExtensionID::WEBGL_draw_buffers;
-        const bool hasDrawBuffers = (mContext->IsWebGL2() ||
-                                     mContext->IsExtensionEnabled(drawBufferExt));
 
-        if (hasDrawBuffers) {
-            fnDrawBuffers(drawBuffersForClear);
-        }
-
-        ////////////
-
-        // Clear!
-        {
-            gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName);
-
-            mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
-        }
-
-        if (hasDrawBuffers) {
-            fnDrawBuffers(mDrawBuffers);
-        }
-
-        for (auto* cur : attachmentsToClear) {
-            cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
-        }
-    }
-
-    //////
-
-    for (auto* attach : tex3DToClear) {
-        auto* tex = attach->Texture();
-        if (!tex->InitializeImageData(funcName, attach->ImageTarget(),
-                                      attach->MipLevel()))
-        {
-            return false;
-        }
-    }
-
-    return true;
-}
-
-static void
-FinalizeDrawAndReadBuffers(gl::GLContext* gl, bool isColorBufferDefined)
-{
-    MOZ_ASSERT(gl, "Expected a valid GLContext ptr.");
-    // GLES don't support DrawBuffer()/ReadBuffer.
-    // According to http://www.opengl.org/wiki/Framebuffer_Object
-    //
-    // Each draw buffers must either specify color attachment points that have images
-    // attached or must be GL_NONE​. (GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER​ when false).
-    //
-    // If the read buffer is set, then it must specify an attachment point that has an
-    // image attached. (GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER​ when false).
-    //
-    // Note that this test is not performed if OpenGL 4.2 or ARB_ES2_compatibility is
-    // available.
-    if (gl->IsGLES() ||
-        gl->IsSupported(gl::GLFeature::ES2_compatibility) ||
-        gl->IsAtLeast(gl::ContextProfile::OpenGL, 420))
-    {
-        return;
-    }
-
-    // TODO(djg): Assert that fDrawBuffer/fReadBuffer is not NULL.
-    GLenum colorBufferSource = isColorBufferDefined ? LOCAL_GL_COLOR_ATTACHMENT0
-                                                    : LOCAL_GL_NONE;
-    gl->fDrawBuffer(colorBufferSource);
-    gl->fReadBuffer(colorBufferSource);
-}
-
-void
-WebGLFramebuffer::FinalizeAttachments(FBTarget target) const
-{
-    MOZ_ASSERT_IF(target == LOCAL_GL_READ_FRAMEBUFFER,
-                  mContext->mBoundReadFramebuffer == this);
-    MOZ_ASSERT_IF(target != LOCAL_GL_READ_FRAMEBUFFER,
-                  mContext->mBoundDrawFramebuffer == this);
-
-    gl::GLContext* gl = mContext->gl;
-
-    ////
-
-    mColorAttachment0.FinalizeAttachment(gl, target, LOCAL_GL_COLOR_ATTACHMENT0);
-
-    for (size_t i = 0; i < mMoreColorAttachments.Size(); i++) {
-        GLenum attachPoint = LOCAL_GL_COLOR_ATTACHMENT1 + i;
-        mMoreColorAttachments[i].FinalizeAttachment(gl, target, attachPoint);
-    }
-
-    ////
-
-    // Nuke the depth and stencil attachment points.
-    gl->fFramebufferRenderbuffer(target, LOCAL_GL_DEPTH_ATTACHMENT,
-                                 LOCAL_GL_RENDERBUFFER, 0);
-    gl->fFramebufferRenderbuffer(target, LOCAL_GL_STENCIL_ATTACHMENT,
-                                 LOCAL_GL_RENDERBUFFER, 0);
-
-    mDepthAttachment.FinalizeAttachment(gl, target, LOCAL_GL_DEPTH_ATTACHMENT);
-    mStencilAttachment.FinalizeAttachment(gl, target, LOCAL_GL_STENCIL_ATTACHMENT);
-    mDepthStencilAttachment.FinalizeAttachment(gl, target, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
-
-    ////
-
-    FinalizeDrawAndReadBuffers(gl, mColorAttachment0.IsDefined());
+    mContext->ErrorInvalidFramebufferOperation("%s: Framebuffer must be"
+                                               " complete.",
+                                               funcName);
+    return false;
 }
 
 bool
 WebGLFramebuffer::ValidateForRead(const char* funcName,
                                   const webgl::FormatUsageInfo** const out_format,
                                   uint32_t* const out_width, uint32_t* const out_height)
 {
     if (!ValidateAndInitAttachments(funcName))
         return false;
 
-    if (mReadBufferMode == LOCAL_GL_NONE) {
-        mContext->ErrorInvalidOperation("%s: Read buffer mode must not be NONE.",
+    if (!mColorReadBuffer) {
+        mContext->ErrorInvalidOperation("%s: READ_BUFFER must not be NONE.", funcName);
+        return false;
+    }
+
+    if (!mColorReadBuffer->IsDefined()) {
+        mContext->ErrorInvalidOperation("%s: The READ_BUFFER attachment is not defined.",
                                         funcName);
         return false;
     }
 
-    const auto attachPoint = GetAttachPoint(mReadBufferMode);
-    if (!attachPoint || !attachPoint->IsDefined()) {
-        mContext->ErrorInvalidOperation("%s: The attachment specified for reading is"
-                                        " null.", funcName);
-        return false;
+    *out_format = mColorReadBuffer->Format();
+    mColorReadBuffer->Size(out_width, out_height);
+    return true;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Resolution and caching
+
+void
+WebGLFramebuffer::ResolveAttachments(FBTarget target) const
+{
+    const auto& gl = mContext->gl;
+
+    ////
+
+    for (const auto& attach : mColorAttachments) {
+        attach.Resolve(gl, target);
     }
 
-    *out_format = attachPoint->Format();
-    attachPoint->Size(out_width, out_height);
+    ////
+
+    // Nuke the depth and stencil attachment points.
+    gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_DEPTH_ATTACHMENT,
+                                 LOCAL_GL_RENDERBUFFER, 0);
+    gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_STENCIL_ATTACHMENT,
+                                 LOCAL_GL_RENDERBUFFER, 0);
+
+    mDepthAttachment.Resolve(gl, target);
+    mStencilAttachment.Resolve(gl, target);
+    mDepthStencilAttachment.Resolve(gl, target);
+}
+
+bool
+WebGLFramebuffer::ResolveAttachmentData(const char* funcName) const
+{
+    //////
+    // Check if we need to initialize anything
+
+    const auto fnIs3D = [&](const WebGLFBAttachPoint& attach) {
+        const auto& tex = attach.Texture();
+        if (!tex)
+            return false;
+
+        const auto& info = tex->ImageInfoAt(attach.ImageTarget(), attach.MipLevel());
+        if (info.mDepth == 1)
+            return false;
+
+        return true;
+    };
+
+    uint32_t clearBits = 0;
+    std::vector<const WebGLFBAttachPoint*> attachmentsToClear;
+    std::vector<const WebGLFBAttachPoint*> colorAttachmentsToClear;
+    std::vector<const WebGLFBAttachPoint*> tex3DAttachmentsToInit;
+
+    const auto fnGather = [&](const WebGLFBAttachPoint& attach, GLenum attachClearBits) {
+        if (!attach.HasUninitializedImageData())
+            return false;
+
+        if (fnIs3D(attach)) {
+            tex3DAttachmentsToInit.push_back(&attach);
+            return false;
+        }
+
+        clearBits |= attachClearBits;
+        attachmentsToClear.push_back(&attach);
+        return true;
+    };
+
+    //////
+
+    for (auto& cur : mColorDrawBuffers) {
+        if (fnGather(*cur, LOCAL_GL_COLOR_BUFFER_BIT)) {
+            colorAttachmentsToClear.push_back(cur);
+        }
+    }
+
+    fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
+    fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
+    fnGather(mDepthStencilAttachment, LOCAL_GL_DEPTH_BUFFER_BIT |
+                                      LOCAL_GL_STENCIL_BUFFER_BIT);
+
+    //////
+
+    for (const auto& attach : tex3DAttachmentsToInit) {
+        const auto& tex = attach->Texture();
+        if (!tex->InitializeImageData(funcName, attach->ImageTarget(),
+                                      attach->MipLevel()))
+        {
+            return false;
+        }
+    }
+
+    if (clearBits) {
+        const auto drawBufferExt = WebGLExtensionID::WEBGL_draw_buffers;
+        const bool hasDrawBuffers = (mContext->IsWebGL2() ||
+                                     mContext->IsExtensionEnabled(drawBufferExt));
+
+        const auto fnDrawBuffers = [&](const std::vector<const WebGLFBAttachPoint*>& src)
+        {
+            if (!hasDrawBuffers)
+                return;
+
+            std::vector<GLenum> enumList;
+
+            for (const auto& cur : src) {
+                const auto& attachEnum = cur->mAttachmentPoint;
+                const GLenum attachId = attachEnum - LOCAL_GL_COLOR_ATTACHMENT0;
+
+                while (enumList.size() < attachId) {
+                    enumList.push_back(LOCAL_GL_NONE);
+                }
+                enumList.push_back(attachEnum);
+            }
+
+            mContext->gl->fDrawBuffers(enumList.size(), enumList.data());
+        };
+
+        ////
+        // Clear
+
+        mContext->MakeContextCurrent();
+
+        fnDrawBuffers(colorAttachmentsToClear);
+
+        {
+            gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName);
+
+            mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
+        }
+
+        fnDrawBuffers(mColorDrawBuffers);
+
+        // Mark initialized.
+        for (const auto& cur : attachmentsToClear) {
+            cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+        }
+    }
+
     return true;
 }
 
-static bool
-AttachmentsDontMatch(const WebGLFBAttachPoint& a, const WebGLFBAttachPoint& b)
+WebGLFramebuffer::ResolvedData::ResolvedData(const WebGLFramebuffer& parent)
+    : hasSampleBuffers(false)
+    , depthBuffer(nullptr)
+    , stencilBuffer(nullptr)
+{
+    if (parent.mDepthAttachment.IsDefined()) {
+        depthBuffer = &parent.mDepthAttachment;
+    }
+    if (parent.mStencilAttachment.IsDefined()) {
+        stencilBuffer = &parent.mStencilAttachment;
+    }
+    if (parent.mDepthStencilAttachment.IsDefined()) {
+        depthBuffer = &parent.mDepthStencilAttachment;
+        stencilBuffer = &parent.mDepthStencilAttachment;
+    }
+
+    ////
+
+    texDrawBuffers.reserve(parent.mColorDrawBuffers.size() + 2); // +2 for depth+stencil.
+
+    const auto fnCommon = [&](const WebGLFBAttachPoint& attach) {
+        if (!attach.IsDefined())
+            return false;
+
+        hasSampleBuffers |= bool(attach.Samples());
+
+        if (attach.Texture()) {
+            texDrawBuffers.push_back(&attach);
+        }
+        return true;
+    };
+
+    ////
+
+    const auto fnColor = [&](const WebGLFBAttachPoint& attach,
+                             decltype(drawSet)* const out_destSet)
+    {
+        if (!fnCommon(attach))
+            return;
+
+        out_destSet->insert(attach);
+    };
+
+    const auto fnDepthStencil = [&](const WebGLFBAttachPoint& attach) {
+        if (!fnCommon(attach))
+            return;
+
+        drawSet.insert(attach);
+        readSet.insert(attach);
+    };
+
+    ////
+
+    fnDepthStencil(parent.mDepthAttachment);
+    fnDepthStencil(parent.mStencilAttachment);
+    fnDepthStencil(parent.mDepthStencilAttachment);
+
+    for (const auto& attach : parent.mColorDrawBuffers) {
+        fnColor(*attach, &drawSet);
+    }
+
+    if (parent.mColorReadBuffer) {
+        fnColor(*(parent.mColorReadBuffer), &readSet);
+    }
+}
+
+void
+WebGLFramebuffer::RecacheResolvedData()
+{
+    if (mResolvedCompleteData) {
+        mResolvedCompleteData.reset(new ResolvedData(*this));
+    }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Entrypoints
+
+FBStatus
+WebGLFramebuffer::CheckFramebufferStatus(const char* funcName)
 {
-    if (a.Texture()) {
-        return (a.Texture() != b.Texture());
+    if (IsResolvedComplete())
+        return LOCAL_GL_FRAMEBUFFER_COMPLETE;
+
+    // Ok, let's try to resolve it!
+
+    nsCString statusInfo;
+    FBStatus ret = PrecheckFramebufferStatus(&statusInfo);
+    do {
+        if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE)
+            break;
+
+        // Looks good on our end. Let's ask the driver.
+        gl::GLContext* const gl = mContext->gl;
+        gl->MakeCurrent();
+
+        ////
+
+        const FBTarget fbTarget = (mContext->IsWebGL2() ? LOCAL_GL_DRAW_FRAMEBUFFER
+                                                        : LOCAL_GL_FRAMEBUFFER);
+        const bool needsFBRebind = (mContext->mBoundDrawFramebuffer != this);
+        if (needsFBRebind) {
+            gl->fBindFramebuffer(fbTarget.get(), mGLName);
+        }
+
+        ResolveAttachments(fbTarget); // OK, attach everything properly!
+        ret = gl->fCheckFramebufferStatus(fbTarget.get());
+
+        if (needsFBRebind) {
+            gl->fBindFramebuffer(fbTarget.get(),
+                                 mContext->mBoundDrawFramebuffer->mGLName);
+        }
+
+        ////
+
+        if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
+            statusInfo.AssignLiteral("Bad status according to the driver:");
+            break;
+        }
+
+        if (!ResolveAttachmentData(funcName)) {
+            ret = LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
+            statusInfo.AssignLiteral("Failed to lazily-initialize attachment data.");
+            break;
+        }
+
+        mResolvedCompleteData.reset(new ResolvedData(*this));
+        return LOCAL_GL_FRAMEBUFFER_COMPLETE;
+    } while (false);
+
+    MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE);
+    mContext->GenerateWarning("%s: Framebuffer not complete. (status: 0x%04x) %s",
+                              funcName, ret.get(), statusInfo.BeginReading());
+    return ret;
+}
+
+////
+
+void
+WebGLFramebuffer::DrawBuffers(const char* funcName, const dom::Sequence<GLenum>& buffers)
+{
+    if (buffers.Length() > mContext->mImplMaxDrawBuffers) {
+        // "An INVALID_VALUE error is generated if `n` is greater than MAX_DRAW_BUFFERS."
+        mContext->ErrorInvalidValue("%s: `buffers` must have a length <="
+                                    " MAX_DRAW_BUFFERS.", funcName);
+        return;
+    }
+
+    for (size_t i = 0; i < buffers.Length(); i++) {
+        // "If the GL is bound to a draw framebuffer object, the `i`th buffer listed in
+        //  bufs must be COLOR_ATTACHMENTi or NONE. Specifying a buffer out of order,
+        //  BACK, or COLOR_ATTACHMENTm where `m` is greater than or equal to the value of
+        // MAX_COLOR_ATTACHMENTS, will generate the error INVALID_OPERATION.
+
+        // WEBGL_draw_buffers:
+        // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
+        //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
+        // This means that if buffers.Length() isn't larger than MaxDrawBuffers, it won't
+        // be larger than MaxColorAttachments.
+        if (buffers[i] != LOCAL_GL_NONE &&
+            buffers[i] != LOCAL_GL_COLOR_ATTACHMENT0 + i)
+        {
+            mContext->ErrorInvalidOperation("%s: `buffers[i]` must be NONE or"
+                                            " COLOR_ATTACHMENTi.",
+                                            funcName);
+            return;
+        }
     }
 
-    if (a.Renderbuffer()) {
-        return (a.Renderbuffer() != b.Renderbuffer());
+    ////
+    // Record it.
+
+    mColorDrawBuffers.clear();
+    for (size_t i = 0; i < buffers.Length(); i++) {
+        const auto& attachEnum = buffers[i];
+        if (attachEnum == LOCAL_GL_NONE)
+            continue;
+
+        const auto& attach = mColorAttachments[i];
+        MOZ_ASSERT(attach.mAttachmentPoint == attachEnum);
+
+        mColorDrawBuffers.push_back(&attach);
+    }
+    RecacheResolvedData();
+
+    ////
+
+    mContext->MakeContextCurrent();
+    mContext->gl->fDrawBuffers(buffers.Length(), buffers.Elements());
+}
+
+void
+WebGLFramebuffer::ReadBuffer(const char* funcName, GLenum attachPoint)
+{
+    const auto& maybeAttach = GetColorAttachPoint(attachPoint);
+    if (!maybeAttach) {
+        mContext->ErrorInvalidValue("%s: `mode` must be a COLOR_ATTACHMENTi, for "
+                                    " 0 <= i < MAX_DRAW_BUFFERS.", funcName);
+        return;
+    }
+    const auto& attach = maybeAttach.value();
+
+    // Record it.
+    mColorReadBuffer = attach;
+    RecacheResolvedData();
+
+    mContext->MakeContextCurrent();
+    mContext->gl->fReadBuffer(attachPoint);
+}
+
+////
+
+void
+WebGLFramebuffer::FramebufferRenderbuffer(const char* funcName, GLenum attachEnum,
+                                          GLenum rbtarget, WebGLRenderbuffer* rb)
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
+
+    // `attachment`
+    const auto maybeAttach = GetAttachPoint(attachEnum);
+    if (!maybeAttach) {
+        mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
+        return;
+    }
+    const auto& attach = maybeAttach.value();
+
+    // `rbTarget`
+    if (rbtarget != LOCAL_GL_RENDERBUFFER) {
+        mContext->ErrorInvalidEnumInfo("framebufferRenderbuffer: rbtarget:", rbtarget);
+        return;
+    }
+
+    // `rb`
+    if (!mContext->ValidateObjectAllowNull("framebufferRenderbuffer: rb", rb))
+        return;
+
+    // End of validation.
+
+    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        mDepthAttachment.SetRenderbuffer(rb);
+        mStencilAttachment.SetRenderbuffer(rb);
+    } else {
+        attach->SetRenderbuffer(rb);
+    }
+
+    InvalidateFramebufferStatus();
+}
+
+void
+WebGLFramebuffer::FramebufferTexture2D(const char* funcName, GLenum attachEnum,
+                                       GLenum texImageTarget, WebGLTexture* tex,
+                                       GLint level)
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
+
+    // `attachment`
+    const auto maybeAttach = GetAttachPoint(attachEnum);
+    if (!maybeAttach) {
+        mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
+        return;
+    }
+    const auto& attach = maybeAttach.value();
+
+    // `texImageTarget`
+    if (texImageTarget != LOCAL_GL_TEXTURE_2D &&
+        (texImageTarget < LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X ||
+         texImageTarget > LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z))
+    {
+        mContext->ErrorInvalidEnumInfo("framebufferTexture2D: texImageTarget:",
+                                       texImageTarget);
+        return;
+    }
+
+    // `texture`
+    if (!mContext->ValidateObjectAllowNull("framebufferTexture2D: texture", tex))
+        return;
+
+    if (tex) {
+        if (!tex->HasEverBeenBound()) {
+            mContext->ErrorInvalidOperation("%s: `texture` has never been bound.",
+                                            funcName);
+            return;
+        }
+
+        const TexTarget destTexTarget = TexImageTargetToTexTarget(texImageTarget);
+        if (tex->Target() != destTexTarget) {
+            mContext->ErrorInvalidOperation("%s: Mismatched texture and texture target.",
+                                            funcName);
+            return;
+        }
     }
 
-    return false;
+    // `level`
+    if (level < 0)
+        return mContext->ErrorInvalidValue("%s: `level` must not be negative.", funcName);
+
+    if (mContext->IsWebGL2()) {
+        /* GLES 3.0.4 p208:
+         *   If textarget is one of TEXTURE_CUBE_MAP_POSITIVE_X,
+         *   TEXTURE_CUBE_MAP_POSITIVE_Y, TEXTURE_CUBE_MAP_POSITIVE_Z,
+         *   TEXTURE_CUBE_MAP_NEGATIVE_X, TEXTURE_CUBE_MAP_NEGATIVE_Y,
+         *   or TEXTURE_CUBE_MAP_NEGATIVE_Z, then level must be greater
+         *   than or equal to zero and less than or equal to log2 of the
+         *   value of MAX_CUBE_MAP_TEXTURE_SIZE. If textarget is TEXTURE_2D,
+         *   level must be greater than or equal to zero and no larger than
+         *   log2 of the value of MAX_TEXTURE_SIZE. Otherwise, an
+         *   INVALID_VALUE error is generated.
+         */
+
+        if (texImageTarget == LOCAL_GL_TEXTURE_2D) {
+            if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize))
+                return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName);
+        } else {
+            MOZ_ASSERT(texImageTarget >= LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X &&
+                       texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
+
+            if (uint32_t(level) > FloorLog2(mContext->mImplMaxCubeMapTextureSize))
+                return mContext->ErrorInvalidValue("%s: `level` is too large.", funcName);
+        }
+    } else if (level != 0) {
+        return mContext->ErrorInvalidValue("%s: `level` must be 0.", funcName);
+    }
+
+    // End of validation.
+
+    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        mDepthAttachment.SetTexImage(tex, texImageTarget, level);
+        mStencilAttachment.SetTexImage(tex, texImageTarget, level);
+    } else {
+        attach->SetTexImage(tex, texImageTarget, level);
+    }
+
+    InvalidateFramebufferStatus();
+}
+
+void
+WebGLFramebuffer::FramebufferTextureLayer(const char* funcName, GLenum attachEnum,
+                                          WebGLTexture* tex, GLint level, GLint layer)
+{
+    MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
+               mContext->mBoundReadFramebuffer == this);
+
+    // `attachment`
+    const auto maybeAttach = GetAttachPoint(attachEnum);
+    if (!maybeAttach) {
+        mContext->ErrorInvalidEnum("%s: Bad `attachment`: 0x%x.", funcName, attachEnum);
+        return;
+    }
+    const auto& attach = maybeAttach.value();
+
+    // `texture`
+    if (!mContext->ValidateObjectAllowNull("framebufferTextureLayer: texture", tex))
+        return;
+
+    if (tex && !tex->HasEverBeenBound()) {
+        mContext->ErrorInvalidOperation("%s: `texture` has never been bound.", funcName);
+        return;
+    }
+
+    // `level`, `layer`
+    if (layer < 0)
+        return mContext->ErrorInvalidValue("%s: `layer` must be >= 0.", funcName);
+
+    if (level < 0)
+        return mContext->ErrorInvalidValue("%s: `level` must be >= 0.", funcName);
+
+    TexImageTarget texImageTarget = LOCAL_GL_TEXTURE_3D;
+    if (tex) {
+        texImageTarget = tex->Target().get();
+        switch (texImageTarget.get()) {
+        case LOCAL_GL_TEXTURE_3D:
+            if (uint32_t(layer) >= mContext->mImplMax3DTextureSize) {
+                mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName,
+                                            "MAX_3D_TEXTURE_SIZE");
+                return;
+            }
+
+            if (uint32_t(level) > FloorLog2(mContext->mImplMax3DTextureSize)) {
+                mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName,
+                                            "MAX_3D_TEXTURE_SIZE");
+                return;
+            }
+            break;
+
+        case LOCAL_GL_TEXTURE_2D_ARRAY:
+            if (uint32_t(layer) >= mContext->mImplMaxArrayTextureLayers) {
+                mContext->ErrorInvalidValue("%s: `layer` must be < %s.", funcName,
+                                            "MAX_ARRAY_TEXTURE_LAYERS");
+                return;
+            }
+
+            if (uint32_t(level) > FloorLog2(mContext->mImplMaxTextureSize)) {
+                mContext->ErrorInvalidValue("%s: `level` must be <= log2(%s).", funcName,
+                                            "MAX_TEXTURE_SIZE");
+                return;
+            }
+            break;
+
+        default:
+            mContext->ErrorInvalidOperation("%s: `texture` must be a TEXTURE_3D or"
+                                            " TEXTURE_2D_ARRAY.",
+                                            funcName);
+            return;
+        }
+    }
+
+    // End of validation.
+
+    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+        mDepthAttachment.SetTexImage(tex, texImageTarget, level, layer);
+        mStencilAttachment.SetTexImage(tex, texImageTarget, level, layer);
+    } else {
+        attach->SetTexImage(tex, texImageTarget, level, layer);
+    }
+
+    InvalidateFramebufferStatus();
 }
 
 JS::Value
 WebGLFramebuffer::GetAttachmentParameter(const char* funcName, JSContext* cx,
-                                         GLenum target, GLenum attachment,
-                                         GLenum pname, ErrorResult* const out_error)
+                                         GLenum target, GLenum attachEnum, GLenum pname,
+                                         ErrorResult* const out_error)
 {
-    auto attachPoint = GetAttachPoint(attachment);
-    if (!attachPoint) {
+    const auto maybeAttach = GetAttachPoint(attachEnum);
+    if (!maybeAttach) {
         mContext->ErrorInvalidEnum("%s: Can only query COLOR_ATTACHMENTi,"
                                    " DEPTH_ATTACHMENT, DEPTH_STENCIL_ATTACHMENT, or"
                                    " STENCIL_ATTACHMENT for a framebuffer.",
                                    funcName);
         return JS::NullValue();
     }
+    auto attach = maybeAttach.value();
 
-    if (mContext->IsWebGL2() && attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+    if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
         // There are a couple special rules for this one.
 
         if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE) {
             mContext->ErrorInvalidOperation("%s: Querying"
                                             " FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE"
                                             " against DEPTH_STENCIL_ATTACHMENT is an"
                                             " error.",
                                             funcName);
             return JS::NullValue();
         }
 
-        if (AttachmentsDontMatch(DepthAttachment(), StencilAttachment())) {
+        if (mDepthAttachment.Renderbuffer() != mStencilAttachment.Renderbuffer() ||
+            mDepthAttachment.Texture() != mStencilAttachment.Texture())
+        {
             mContext->ErrorInvalidOperation("%s: DEPTH_ATTACHMENT and STENCIL_ATTACHMENT"
                                             " have different objects bound.",
                                             funcName);
             return JS::NullValue();
         }
 
-        attachPoint = GetAttachPoint(LOCAL_GL_DEPTH_ATTACHMENT);
+        attach = &mDepthAttachment;
     }
 
-    return attachPoint->GetParameter(funcName, mContext, cx, target, attachment, pname,
-                                     out_error);
+    return attach->GetParameter(funcName, mContext, cx, target, attachEnum, pname,
+                                out_error);
 }
 
+////////////////////
+
+static void
+GetBackbufferFormats(const WebGLContext* webgl,
+                     const webgl::FormatInfo** const out_color,
+                     const webgl::FormatInfo** const out_depth,
+                     const webgl::FormatInfo** const out_stencil)
+{
+    const auto& options = webgl->Options();
+
+    const auto effFormat = (options.alpha ? webgl::EffectiveFormat::RGBA8
+                                          : webgl::EffectiveFormat::RGB8);
+    *out_color = webgl::GetFormat(effFormat);
+
+    *out_depth = nullptr;
+    *out_stencil = nullptr;
+    if (options.depth && options.stencil) {
+        *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH24_STENCIL8);
+        *out_stencil = *out_depth;
+    } else {
+        if (options.depth) {
+            *out_depth = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT16);
+        }
+        if (options.stencil) {
+            *out_stencil = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
+        }
+    }
+}
+
+/*static*/ void
+WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl,
+                                  const WebGLFramebuffer* srcFB, GLint srcX0, GLint srcY0,
+                                  GLint srcX1, GLint srcY1,
+                                  const WebGLFramebuffer* dstFB, GLint dstX0, GLint dstY0,
+                                  GLint dstX1, GLint dstY1,
+                                  GLbitfield mask, GLenum filter)
+{
+    const char funcName[] = "blitFramebuffer";
+    auto& gl = webgl->gl;
+
 
-void
-WebGLFramebuffer::GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const
-{
-    auto itr = mDrawBuffers.cbegin();
-    if (itr != mDrawBuffers.cend() &&
-        *itr != LOCAL_GL_NONE)
+    ////
+    // Collect data
+
+    const auto fnGetFormat = [](const WebGLFBAttachPoint* cur) -> const webgl::FormatInfo*
     {
-        out->push_back(&mColorAttachment0);
-        ++itr;
+        if (!cur)
+            return nullptr;
+
+        MOZ_ASSERT(cur->IsDefined());
+        return cur->Format()->format;
+    };
+
+    bool srcSampleBuffers;
+    const webgl::FormatInfo* srcColorFormat;
+    const webgl::FormatInfo* srcDepthFormat;
+    const webgl::FormatInfo* srcStencilFormat;
+
+    if (srcFB) {
+        srcSampleBuffers = srcFB->mResolvedCompleteData->hasSampleBuffers;
+
+        srcColorFormat = fnGetFormat(srcFB->mColorReadBuffer);
+        srcDepthFormat = fnGetFormat(srcFB->mResolvedCompleteData->depthBuffer);
+        srcStencilFormat = fnGetFormat(srcFB->mResolvedCompleteData->stencilBuffer);
+    } else {
+        srcSampleBuffers = false; // Always false.
+
+        GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, &srcStencilFormat);
+    }
+
+    ////
+
+    bool dstSampleBuffers;
+    const webgl::FormatInfo* dstDepthFormat;
+    const webgl::FormatInfo* dstStencilFormat;
+    bool dstHasColor = false;
+    bool colorFormatsMatch = true;
+    bool colorTypesMatch = true;
+
+    const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) {
+        dstHasColor = true;
+        colorFormatsMatch &= (dstFormat == srcColorFormat);
+        colorTypesMatch &= (dstFormat->componentType == srcColorFormat->componentType);
+    };
+
+    if (dstFB) {
+        dstSampleBuffers = dstFB->mResolvedCompleteData->hasSampleBuffers;
+
+        dstDepthFormat = fnGetFormat(dstFB->mResolvedCompleteData->depthBuffer);
+        dstStencilFormat = fnGetFormat(dstFB->mResolvedCompleteData->stencilBuffer);
+
+        for (const auto& drawBuffer : dstFB->mColorDrawBuffers) {
+            fnCheckColorFormat(drawBuffer->Format()->format);
+        }
+    } else {
+        dstSampleBuffers = bool(gl->Screen()->Samples());
+
+        const webgl::FormatInfo* dstColorFormat;
+        GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat, &dstStencilFormat);
+
+        fnCheckColorFormat(dstColorFormat);
+    }
+
+    ////
+    // Clear unused buffer bits
+
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
+        !srcColorFormat && !dstHasColor)
+    {
+
+        mask ^= LOCAL_GL_COLOR_BUFFER_BIT;
+    }
+
+    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
+        !srcDepthFormat && !dstDepthFormat)
+    {
+        mask ^= LOCAL_GL_DEPTH_BUFFER_BIT;
+    }
+
+    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
+        !srcStencilFormat && !dstStencilFormat)
+    {
+        mask ^= LOCAL_GL_STENCIL_BUFFER_BIT;
+    }
+
+    ////
+    // Validation
+
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
+        if (srcColorFormat && filter == LOCAL_GL_LINEAR) {
+            const auto& type = srcColorFormat->componentType;
+            if (type == webgl::ComponentType::Int ||
+                type == webgl::ComponentType::UInt)
+            {
+                webgl->ErrorInvalidOperation("%s: `filter` is LINEAR and READ_BUFFER"
+                                             " contains integer data.",
+                                             funcName);
+                return;
+            }
+        }
+
+        if (!colorTypesMatch) {
+            webgl->ErrorInvalidOperation("%s: Color component types (fixed/float/uint/"
+                                         "int) must match.",
+                                         funcName);
+            return;
+        }
     }
 
-    size_t i = 0;
-    for (; itr != mDrawBuffers.cend(); ++itr) {
-        if (i >= mMoreColorAttachments.Size())
-            break;
+    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
+                                           LOCAL_GL_STENCIL_BUFFER_BIT;
+    if (bool(mask & depthAndStencilBits) &&
+        filter != LOCAL_GL_NEAREST)
+    {
+        webgl->ErrorInvalidOperation("%s: DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can"
+                                     " only be used with NEAREST filtering.",
+                                     funcName);
+        return;
+    }
 
-        if (*itr != LOCAL_GL_NONE) {
-            out->push_back(&mMoreColorAttachments[i]);
-        }
-        ++i;
+    /* GLES 3.0.4, p199:
+     *   Calling BlitFramebuffer will result in an INVALID_OPERATION error if
+     *   mask includes DEPTH_BUFFER_BIT or STENCIL_BUFFER_BIT, and the source
+     *   and destination depth and stencil buffer formats do not match.
+     *
+     * jgilbert: The wording is such that if only DEPTH_BUFFER_BIT is specified,
+     * the stencil formats must match. This seems wrong. It could be a spec bug,
+     * or I could be missing an interaction in one of the earlier paragraphs.
+     */
+    if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
+        dstDepthFormat != srcDepthFormat)
+    {
+        webgl->ErrorInvalidOperation("%s: Depth buffer formats must match if selected.",
+                                     funcName);
+        return;
+    }
+
+    if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
+        dstStencilFormat != srcStencilFormat)
+    {
+        webgl->ErrorInvalidOperation("%s: Stencil buffer formats must match if selected.",
+                                     funcName);
+        return;
+    }
+
+    ////
+
+    if (dstSampleBuffers) {
+        webgl->ErrorInvalidOperation("%s: DRAW_FRAMEBUFFER may not have multiple"
+                                     " samples.",
+                                     funcName);
+        return;
     }
 
-    out->push_back(&mDepthAttachment);
-    out->push_back(&mStencilAttachment);
-    out->push_back(&mDepthStencilAttachment);
+    if (srcSampleBuffers) {
+        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
+            !colorFormatsMatch)
+        {
+            webgl->ErrorInvalidOperation("%s: Color buffer formats must match if"
+                                         " selected, when reading from a multisampled"
+                                         " source.",
+                                         funcName);
+            return;
+        }
+
+        if (dstX0 != srcX0 ||
+            dstX1 != srcX1 ||
+            dstY0 != srcY0 ||
+            dstY1 != srcY1)
+        {
+            webgl->ErrorInvalidOperation("%s: If the source is multisampled, then the"
+                                         " source and dest regions must match exactly.",
+                                         funcName);
+            return;
+        }
+    }
+
+    ////
+    // Check for feedback
+
+    if (srcFB && dstFB) {
+        const auto fnValidateBuffers = [&](GLenum bufferBit, const char* bufferBitName,
+                                           bool srcHas, bool dstHas)
+        {
+            if (mask & bufferBit &&
+                srcHas != dstHas)
+            {
+                webgl->ErrorInvalidOperation("%s: With %s, must have a corresponding draw"
+                                             " buffer iff there's a relevent read"
+                                             " buffer.",
+                                             funcName, bufferBitName);
+                return false;
+            }
+            return true;
+        };
+
+        if (!fnValidateBuffers( LOCAL_GL_COLOR_BUFFER_BIT, "COLOR_BUFFER_BIT",
+                                bool(srcFB->mColorReadBuffer),
+                                bool(dstFB->mColorDrawBuffers.size()) ) ||
+            !fnValidateBuffers( LOCAL_GL_DEPTH_BUFFER_BIT, "DEPTH_BUFFER_BIT",
+                                bool(srcFB->mResolvedCompleteData->depthBuffer),
+                                bool(dstFB->mResolvedCompleteData->depthBuffer) ) ||
+            !fnValidateBuffers( LOCAL_GL_STENCIL_BUFFER_BIT, "STENCIL_BUFFER_BIT",
+                                bool(srcFB->mResolvedCompleteData->stencilBuffer),
+                                bool(dstFB->mResolvedCompleteData->stencilBuffer) ))
+        {
+            return;
+        }
+
+        const auto& readSet = srcFB->mResolvedCompleteData->readSet;
+        const auto& drawSet = dstFB->mResolvedCompleteData->drawSet;
+
+        std::vector<WebGLFBAttachPoint::Ordered> intersection;
+        std::set_intersection(drawSet.begin(), drawSet.end(),
+                              readSet.begin(), readSet.end(),
+                              std::back_inserter(intersection));
+
+        if (intersection.size()) {
+            // set_intersection pulls from the first range, so it records conflicts on the
+            // DRAW_FRAMEBUFFER.
+            const auto& example = intersection.cbegin()->mRef;
+            webgl->ErrorInvalidOperation("%s: Feedback detected into DRAW_FRAMEBUFFER's"
+                                         " 0x%04x attachment.",
+                                         funcName, example.mAttachmentPoint);
+            return;
+        }
+    } else if (!srcFB && !dstFB) {
+        webgl->ErrorInvalidOperation("%s: Feedback with default framebuffer.", funcName);
+        return;
+    }
+
+    ////
+
+    gl->MakeCurrent();
+    gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
+                         dstX0, dstY0, dstX1, dstY1,
+                         mask, filter);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
 
 JSObject*
 WebGLFramebuffer::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
 {
@@ -1388,18 +1810,17 @@ ImplCycleCollectionTraverse(nsCycleColle
                             uint32_t flags = 0)
 {
     for (auto& cur : field) {
         ImplCycleCollectionTraverse(callback, cur, name, flags);
     }
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebGLFramebuffer,
-                                      mColorAttachment0,
                                       mDepthAttachment,
                                       mStencilAttachment,
                                       mDepthStencilAttachment,
-                                      mMoreColorAttachments)
+                                      mColorAttachments)
 
 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
@@ -22,279 +22,263 @@ namespace mozilla {
 
 class WebGLFramebuffer;
 class WebGLRenderbuffer;
 class WebGLTexture;
 
 template<typename T>
 class PlacementArray;
 
-class WebGLFBAttachPoint
+namespace gl {
+    class GLContext;
+} // namespace gl
+
+class WebGLFBAttachPoint final
 {
+    friend class WebGLFramebuffer;
 public:
     WebGLFramebuffer* const mFB;
     const GLenum mAttachmentPoint;
-private:
+
+protected:
     WebGLRefPtr<WebGLTexture> mTexturePtr;
     WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
     TexImageTarget mTexImageTarget;
     GLint mTexImageLayer;
     uint32_t mTexImageLevel;
 
-    // PlacementArray needs a default constructor.
-    template<typename T>
-    friend class PlacementArray;
+    ////
 
-    WebGLFBAttachPoint()
-        : mFB(nullptr)
-        , mAttachmentPoint(0)
-    { }
+    WebGLFBAttachPoint();
+    WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint);
 
 public:
-    WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint);
     ~WebGLFBAttachPoint();
 
+    ////
+
     void Unlink();
 
     bool IsDefined() const;
     bool IsDeleteRequested() const;
 
     const webgl::FormatUsageInfo* Format() const;
     uint32_t Samples() const;
 
     bool HasAlpha() const;
     bool IsReadableFloat() const;
 
     void Clear();
 
-    void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level);
-    void SetTexImageLayer(WebGLTexture* tex, TexImageTarget target, GLint level,
-                          GLint layer);
+    void SetTexImage(WebGLTexture* tex, TexImageTarget target, GLint level,
+                     GLint layer = 0);
     void SetRenderbuffer(WebGLRenderbuffer* rb);
 
-    const WebGLTexture* Texture() const {
-        return mTexturePtr;
-    }
-    WebGLTexture* Texture() {
-        return mTexturePtr;
-    }
-    const WebGLRenderbuffer* Renderbuffer() const {
-        return mRenderbufferPtr;
-    }
-    WebGLRenderbuffer* Renderbuffer() {
-        return mRenderbufferPtr;
-    }
+    WebGLTexture* Texture() const { return mTexturePtr; }
+    WebGLRenderbuffer* Renderbuffer() const { return mRenderbufferPtr; }
+
     TexImageTarget ImageTarget() const {
         return mTexImageTarget;
     }
     GLint Layer() const {
         return mTexImageLayer;
     }
     uint32_t MipLevel() const {
         return mTexImageLevel;
     }
     void AttachmentName(nsCString* out) const;
 
     bool HasUninitializedImageData() const;
-    void SetImageDataStatus(WebGLImageDataStatus x);
+    void SetImageDataStatus(WebGLImageDataStatus x) const;
 
     void Size(uint32_t* const out_width, uint32_t* const out_height) const;
 
     bool HasImage() const;
     bool IsComplete(WebGLContext* webgl, nsCString* const out_info) const;
 
-    void FinalizeAttachment(gl::GLContext* gl, FBTarget target,
-                            GLenum attachmentLoc) const;
+    void Resolve(gl::GLContext* gl, FBTarget target) const;
 
     JS::Value GetParameter(const char* funcName, WebGLContext* webgl, JSContext* cx,
                            GLenum target, GLenum attachment, GLenum pname,
-                           ErrorResult* const out_error);
+                           ErrorResult* const out_error) const;
 
     void OnBackingStoreRespecified() const;
-};
+
+    ////
 
-template<typename T>
-class PlacementArray
-{
-public:
-    const size_t mCapacity;
-protected:
-    size_t mSize;
-    T* const mArray;
+    struct Ordered {
+        const WebGLFBAttachPoint& mRef;
 
-public:
-    explicit PlacementArray(size_t capacity)
-        : mCapacity(capacity)
-        , mSize(0)
-        , mArray((T*)moz_xmalloc(sizeof(T) * capacity))
-    { }
+        Ordered(const WebGLFBAttachPoint& ref)
+            : mRef(ref)
+        { }
 
-    ~PlacementArray() {
-        for (auto& cur : *this) {
-            cur.~T();
-        }
-        free(mArray);
-    }
+        bool operator<(const Ordered& other) const {
+            MOZ_ASSERT(mRef.IsDefined() && other.mRef.IsDefined());
 
-    T* begin() const {
-        return mArray;
-    }
-
-    T* end() const {
-        return mArray + mSize;
-    }
+#define ORDER_BY(X) if (X != other.X) return X < other.X;
 
-    T& operator [](size_t offset) const {
-        MOZ_ASSERT(offset < mSize);
-        return mArray[offset];
-    }
-
-    const size_t& Size() const { return mSize; }
+            ORDER_BY(mRef.mRenderbufferPtr)
+            ORDER_BY(mRef.mTexturePtr)
+            ORDER_BY(mRef.mTexImageTarget.get())
+            ORDER_BY(mRef.mTexImageLevel)
+            ORDER_BY(mRef.mTexImageLayer)
 
-    template<typename A, typename B>
-    void AppendNew(A a, B b) {
-        if (mSize == mCapacity)
-            MOZ_CRASH("GFX: Bad EmplaceAppend.");
-
-        // Placement `new`:
-        new (&(mArray[mSize])) T(a, b);
-        ++mSize;
-    }
+#undef ORDER_BY
+            return false;
+        }
+    };
 };
 
 class WebGLFramebuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public WebGLContextBoundObject
     , public SupportsWeakPtr<WebGLFramebuffer>
 {
     friend class WebGLContext;
 
 public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLFramebuffer)
 
     const GLuint mGLName;
 
-private:
-    mutable bool mIsKnownFBComplete;
-
-    GLenum mReadBufferMode;
-
-    // No need to chase pointers for the oft-used color0.
-    WebGLFBAttachPoint mColorAttachment0;
-    WebGLFBAttachPoint mDepthAttachment;
-    WebGLFBAttachPoint mStencilAttachment;
-    WebGLFBAttachPoint mDepthStencilAttachment;
-
-    PlacementArray<WebGLFBAttachPoint> mMoreColorAttachments;
-
-    std::vector<GLenum> mDrawBuffers;
-
-    bool IsDrawBuffer(size_t n) const {
-        if (n < mDrawBuffers.size())
-            return bool(mDrawBuffers[n]);
-
-        return false;
-    }
-
+protected:
 #ifdef ANDROID
     // Bug 1140459: Some drivers (including our test slaves!) don't
     // give reasonable answers for IsRenderbuffer, maybe others.
     // This shows up on Android 2.3 emulator.
     //
     // So we track the `is a Framebuffer` state ourselves.
     bool mIsFB;
 #endif
 
+    ////
+
+    WebGLFBAttachPoint mDepthAttachment;
+    WebGLFBAttachPoint mStencilAttachment;
+    WebGLFBAttachPoint mDepthStencilAttachment;
+
+    // In theory, this number can be unbounded based on the driver. However, no driver
+    // appears to expose more than 8. We might as well stop there too, for now.
+    // (http://opengl.gpuinfo.org/gl_stats_caps_single.php?listreportsbycap=GL_MAX_COLOR_ATTACHMENTS)
+    static const size_t kMaxColorAttachments = 8; // jgilbert's MacBook Pro exposes 8.
+    WebGLFBAttachPoint mColorAttachments[kMaxColorAttachments];
+
+    ////
+
+    std::vector<const WebGLFBAttachPoint*> mColorDrawBuffers; // Non-null
+    const WebGLFBAttachPoint* mColorReadBuffer; // Null if NONE
+
+    ////
+
+    struct ResolvedData {
+        // BlitFramebuffer
+        bool hasSampleBuffers;
+        const WebGLFBAttachPoint* depthBuffer;
+        const WebGLFBAttachPoint* stencilBuffer;
+
+        // IsFeedback
+        std::vector<const WebGLFBAttachPoint*> texDrawBuffers; // Non-null
+        std::set<WebGLFBAttachPoint::Ordered> drawSet;
+        std::set<WebGLFBAttachPoint::Ordered> readSet;
+
+        ResolvedData(const WebGLFramebuffer& parent);
+    };
+
+    UniquePtr<const ResolvedData> mResolvedCompleteData;
+
+    ////
+
 public:
+    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
+    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
+
     WebGLFramebuffer(WebGLContext* webgl, GLuint fbo);
 
+    WebGLContext* GetParentObject() const { return mContext; }
+    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
+
 private:
     ~WebGLFramebuffer() {
         DeleteOnce();
     }
 
-    const WebGLRectangleObject& GetAnyRectObject() const;
-
 public:
     void Delete();
 
-    void FramebufferRenderbuffer(GLenum attachment, RBTarget rbtarget,
-                                 WebGLRenderbuffer* rb);
-    void FramebufferTexture2D(GLenum attachment, TexImageTarget texImageTarget,
-                              WebGLTexture* tex, GLint level);
-    void FramebufferTextureLayer(GLenum attachment, WebGLTexture* tex, GLint level,
-                                 GLint layer);
+    ////
 
     bool HasDefinedAttachments() const;
     bool HasIncompleteAttachments(nsCString* const out_info) const;
     bool AllImageRectsMatch() const;
     bool AllImageSamplesMatch() const;
     FBStatus PrecheckFramebufferStatus(nsCString* const out_info) const;
-    FBStatus CheckFramebufferStatus(FBTarget target, nsCString* const out_info) const;
-
-    const webgl::FormatUsageInfo*
-    GetFormatForAttachment(const WebGLFBAttachPoint& attachment) const;
-
-    const WebGLFBAttachPoint& ColorAttachment(size_t colorAttachmentId) const {
-        MOZ_ASSERT(colorAttachmentId < 1 + mMoreColorAttachments.Size());
-        return colorAttachmentId ? mMoreColorAttachments[colorAttachmentId - 1]
-                                 : mColorAttachment0;
-    }
-
-    const WebGLFBAttachPoint& DepthAttachment() const {
-        return mDepthAttachment;
-    }
-
-    const WebGLFBAttachPoint& StencilAttachment() const {
-        return mStencilAttachment;
-    }
-
-    const WebGLFBAttachPoint& DepthStencilAttachment() const {
-        return mDepthStencilAttachment;
-    }
-
-    void SetReadBufferMode(GLenum readBufferMode) {
-        mReadBufferMode = readBufferMode;
-    }
-
-    GLenum ReadBufferMode() const { return mReadBufferMode; }
-
-    void GatherAttachments(std::vector<const WebGLFBAttachPoint*>* const out) const;
 
 protected:
-    WebGLFBAttachPoint* GetAttachPoint(GLenum attachment); // Fallible
+    Maybe<WebGLFBAttachPoint*> GetAttachPoint(GLenum attachment); // Fallible
+    Maybe<WebGLFBAttachPoint*> GetColorAttachPoint(GLenum attachment); // Fallible
+    void ResolveAttachments(FBTarget target) const;
+    bool ResolveAttachmentData(const char* funcName) const;
 
 public:
     void DetachTexture(const WebGLTexture* tex);
-
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
-
-    WebGLContext* GetParentObject() const {
-        return mContext;
-    }
-
-    void FinalizeAttachments(FBTarget target) const;
-
-    virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
-
-    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLFramebuffer)
-    NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLFramebuffer)
-
     bool ValidateAndInitAttachments(const char* funcName);
 
-    void InvalidateFramebufferStatus() const {
-        mIsKnownFBComplete = false;
-    }
-
     bool ValidateForRead(const char* info,
                          const webgl::FormatUsageInfo** const out_format,
                          uint32_t* const out_width, uint32_t* const out_height);
 
+    ////////////////
+    // Getters
+
+#define GETTER(X) const decltype(m##X)& X() const { return m##X; }
+
+    GETTER(DepthAttachment)
+    GETTER(StencilAttachment)
+    GETTER(DepthStencilAttachment)
+    GETTER(ColorDrawBuffers)
+    GETTER(ColorReadBuffer)
+    GETTER(ResolvedCompleteData)
+
+#undef GETTER
+
+    ////////////////
+    // Invalidation
+
+    bool IsResolvedComplete() const { return bool(mResolvedCompleteData); }
+
+    void InvalidateFramebufferStatus() {
+        mResolvedCompleteData = nullptr;
+    }
+
+    void RecacheResolvedData();
+
+    ////////////////
+    // WebGL funcs
+
+    FBStatus CheckFramebufferStatus(const char* funcName);
+    void FramebufferRenderbuffer(const char* funcName, GLenum attachment, GLenum rbtarget,
+                                 WebGLRenderbuffer* rb);
+    void FramebufferTexture2D(const char* funcName, GLenum attachment,
+                              GLenum texImageTarget, WebGLTexture* tex, GLint level);
+    void FramebufferTextureLayer(const char* funcName, GLenum attachment,
+                                 WebGLTexture* tex, GLint level, GLint layer);
+    void DrawBuffers(const char* funcName, const dom::Sequence<GLenum>& buffers);
+    void ReadBuffer(const char* funcName, GLenum attachPoint);
+
     JS::Value GetAttachmentParameter(const char* funcName, JSContext* cx, GLenum target,
                                      GLenum attachment, GLenum pname,
                                      ErrorResult* const out_error);
+
+    static void BlitFramebuffer(WebGLContext* webgl,
+                                const WebGLFramebuffer* src, GLint srcX0, GLint srcY0,
+                                GLint srcX1, GLint srcY1,
+                                const WebGLFramebuffer* dst, GLint dstX0, GLint dstY0,
+                                GLint dstX1, GLint dstY1,
+                                GLbitfield mask, GLenum filter);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_FRAMEBUFFER_H_
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -229,24 +229,24 @@ WebGLRenderbuffer::RenderbufferStorage(c
 
 void
 WebGLRenderbuffer::DoFramebufferRenderbuffer(FBTarget target, GLenum attachment) const
 {
     gl::GLContext* gl = mContext->gl;
 
     if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
         const GLuint stencilRB = (mSecondaryRB ? mSecondaryRB : mPrimaryRB);
-        gl->fFramebufferRenderbuffer(target, LOCAL_GL_DEPTH_ATTACHMENT,
+        gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_DEPTH_ATTACHMENT,
                                      LOCAL_GL_RENDERBUFFER, mPrimaryRB);
-        gl->fFramebufferRenderbuffer(target, LOCAL_GL_STENCIL_ATTACHMENT,
+        gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_STENCIL_ATTACHMENT,
                                      LOCAL_GL_RENDERBUFFER, stencilRB);
         return;
     }
 
-    gl->fFramebufferRenderbuffer(target, attachment,
+    gl->fFramebufferRenderbuffer(target.get(), attachment,
                                  LOCAL_GL_RENDERBUFFER, mPrimaryRB);
 }
 
 GLint
 WebGLRenderbuffer::GetRenderbufferParameter(RBTarget target,
                                             RBParam pname) const
 {
     gl::GLContext* gl = mContext->gl;
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -1964,30 +1964,28 @@ ValidateCopyDestUsage(const char* funcNa
     return dstUsage;
 }
 
 bool
 WebGLTexture::ValidateCopyTexImageForFeedback(const char* funcName, uint32_t level) const
 {
     const auto& fb = mContext->mBoundReadFramebuffer;
     if (fb) {
-        const auto readBuffer = fb->ReadBufferMode();
-        MOZ_ASSERT(readBuffer != LOCAL_GL_NONE);
-        const uint32_t colorAttachment = readBuffer - LOCAL_GL_COLOR_ATTACHMENT0;
-        const auto& attach = fb->ColorAttachment(colorAttachment);
+        const auto& attach = fb->ColorReadBuffer();
+        MOZ_ASSERT(attach);
 
-        if (attach.Texture() == this &&
-            uint32_t(attach.MipLevel()) == level)
+        if (attach->Texture() == this &&
+            uint32_t(attach->MipLevel()) == level)
         {
             // Note that the TexImageTargets *don't* have to match for this to be
             // undefined per GLES 3.0.4 p211, thus an INVALID_OP in WebGL.
             mContext->ErrorInvalidOperation("%s: Feedback loop detected, as this texture"
                                             " is already attached to READ_FRAMEBUFFER's"
                                             " READ_BUFFER-selected COLOR_ATTACHMENT%u.",
-                                            funcName, colorAttachment);
+                                            funcName, attach->mAttachmentPoint);
             return false;
         }
     }
     return true;
 }
 
 // There is no CopyTexImage3D.
 void