Bug 1498070 - Simplify and cache framebuffer and texture completeness. - r=kvark
authorJeff Gilbert <jgilbert@mozilla.com>
Wed, 17 Oct 2018 04:18:15 +0000
changeset 489958 59befcc4a2d6886d0d642710dd4b0ebc25a62082
parent 489957 daf3e491e6fff54278e1564b7b7c25f115076e1c
child 489959 46c013fd9f6a051e5bc47ddf1405b9564e907f59
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewerskvark
bugs1498070
milestone64.0a1
Bug 1498070 - Simplify and cache framebuffer and texture completeness. - r=kvark Net-removes 700LOC, too! Differential Revision: https://phabricator.services.mozilla.com/D8325
dom/canvas/CacheInvalidator.cpp
dom/canvas/CacheInvalidator.h
dom/canvas/CacheMap.cpp
dom/canvas/CacheMap.h
dom/canvas/WebGL2ContextSamplers.cpp
dom/canvas/WebGL2ContextUniforms.cpp
dom/canvas/WebGLActiveInfo.cpp
dom/canvas/WebGLActiveInfo.h
dom/canvas/WebGLBuffer.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLContextFramebufferOperations.cpp
dom/canvas/WebGLContextGL.cpp
dom/canvas/WebGLContextState.cpp
dom/canvas/WebGLContextTextures.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLContextVertices.cpp
dom/canvas/WebGLFormats.cpp
dom/canvas/WebGLFormats.h
dom/canvas/WebGLFramebuffer.cpp
dom/canvas/WebGLFramebuffer.h
dom/canvas/WebGLFramebufferAttachable.cpp
dom/canvas/WebGLFramebufferAttachable.h
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLProgram.h
dom/canvas/WebGLRenderbuffer.cpp
dom/canvas/WebGLRenderbuffer.h
dom/canvas/WebGLSampler.cpp
dom/canvas/WebGLSampler.h
dom/canvas/WebGLTexture.cpp
dom/canvas/WebGLTexture.h
dom/canvas/WebGLTextureUpload.cpp
dom/canvas/WebGLTypes.h
dom/canvas/WebGLUniformLocation.cpp
dom/canvas/WebGLUniformLocation.h
dom/canvas/WebGLVertexArray.h
dom/canvas/WebGLVertexAttribData.cpp
dom/canvas/WebGLVertexAttribData.h
dom/canvas/moz.build
dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-mipmap-levels.html
dom/canvas/test/webgl-conf/generated-mochitest.ini
dom/canvas/test/webgl-conf/mochitest-errata.ini
new file mode 100644
--- /dev/null
+++ b/dom/canvas/CacheInvalidator.cpp
@@ -0,0 +1,55 @@
+/* -*- Mode: C++; tab-width: 13; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=13 sts=4 et sw=4 tw=90: */
+/* 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 "CacheInvalidator.h"
+
+namespace mozilla {
+
+void
+CacheInvalidator::InvalidateCaches() const
+{
+    // The only sane approach is to require caches to remove invalidators.
+    while (mCaches.size()) {
+        const auto& itr = mCaches.begin();
+        const auto pEntry = *itr;
+        pEntry->OnInvalidate();
+        MOZ_ASSERT(mCaches.find(pEntry) == mCaches.end());
+    }
+}
+
+// -
+
+AbstractCache::InvalidatorListT
+AbstractCache::ResetInvalidators(InvalidatorListT&& newList)
+{
+    for (const auto& cur : mInvalidators) {
+        if (cur) {
+            (void)cur->mCaches.erase(this);
+        }
+    }
+
+    auto ret = std::move(mInvalidators);
+    mInvalidators = std::move(newList);
+
+    for (const auto& cur : mInvalidators) {
+        // Don't assert that we insert, since there may be dupes in `invalidators`.
+        // (and it's not worth removing the dupes)
+        if (cur) {
+            (void)cur->mCaches.insert(this);
+        }
+    }
+
+    return ret;
+}
+
+void
+AbstractCache::AddInvalidator(const CacheInvalidator& x)
+{
+    mInvalidators.push_back(&x);
+    x.mCaches.insert(this);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/canvas/CacheInvalidator.h
@@ -0,0 +1,206 @@
+/* -*- Mode: C++; tab-width: 13; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=13 sts=4 et sw=4 tw=90: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_CACHE_INVALIDATOR_H_
+#define MOZILLA_CACHE_INVALIDATOR_H_
+
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+namespace std { // You know it's going to be good with this at the top of the file.
+
+// The STL is lazy and doesn't provide these:
+template<typename T>
+struct hash<const T*>
+{
+    auto operator()(const T* const x) const {
+        return hash<T*>()(const_cast<T*>(x));
+    }
+};
+
+template<typename T>
+struct hash<const T>
+{
+    auto operator()(const T x) const {
+        return hash<T>()(const_cast<T>(x));
+    }
+};
+
+} // namespace std
+
+// -
+
+namespace mozilla {
+
+class AbstractCache;
+
+// -
+
+class CacheInvalidator
+{
+    friend class AbstractCache;
+private:
+    mutable std::unordered_set<AbstractCache*> mCaches;
+
+public:
+    virtual ~CacheInvalidator() {
+        // It's actually generally unsafe to wait until now to invalidate caches, because
+        // when used as a mixin, this dtor is called after the dtor for the derived class.
+        // This means that if the derived class holds a cache (or is a cache!),
+        // OnInvalidate() will be called on a destroyed object.
+        //MOZ_ASSERT(!mCaches);
+        InvalidateCaches();
+    }
+
+    void InvalidateCaches() const;
+};
+
+// -
+
+class AbstractCache
+{
+    typedef std::vector<const CacheInvalidator*> InvalidatorListT;
+private:
+    InvalidatorListT mInvalidators;
+
+public:
+    AbstractCache() = default;
+
+    explicit AbstractCache(InvalidatorListT&& invalidators) {
+        ResetInvalidators(std::move(invalidators));
+    }
+
+    virtual ~AbstractCache() {
+        ResetInvalidators({});
+    }
+
+public:
+    virtual void OnInvalidate() = 0;
+
+    InvalidatorListT ResetInvalidators(InvalidatorListT&&); // Returns the old list.
+    void AddInvalidator(const CacheInvalidator&);
+};
+
+// -
+
+template<typename T>
+class CacheMaybe : public AbstractCache
+{
+    Maybe<T> mVal;
+
+public:
+    template<typename U>
+    CacheMaybe& operator=(Maybe<U>&& rhs) {
+        mVal.reset();
+        if (rhs) {
+            mVal.emplace(std::move(rhs.ref()));
+        }
+        return *this;
+    }
+
+    CacheMaybe& operator=(Nothing) {
+        return *this = Maybe<T>();
+    }
+
+    void OnInvalidate() override {
+        *this = Nothing();
+        ResetInvalidators({});
+    }
+
+    explicit operator bool() const { return bool(mVal); }
+    T* get() const { return mVal.ptrOr(nullptr); }
+    T* operator->() const { return get(); }
+};
+
+// -
+
+template<typename KeyT, typename ValueT>
+class CacheWeakMap final
+{
+    class Entry final : public AbstractCache {
+    public:
+        CacheWeakMap& mParent;
+        const KeyT mKey;
+        const ValueT mValue;
+
+        Entry(CacheWeakMap& parent, const KeyT& key, ValueT&& value)
+            : mParent(parent)
+            , mKey(key)
+            , mValue(value)
+        { }
+
+        void OnInvalidate() override {
+            const auto erased = mParent.mMap.erase(&mKey);
+            MOZ_ALWAYS_TRUE( erased == 1 );
+        }
+    };
+
+    struct DerefHash final {
+        size_t operator ()(const KeyT* const a) const {
+            return std::hash<const KeyT>()(*a);
+        }
+    };
+    struct DerefEqual final {
+        bool operator ()(const KeyT* const a, const KeyT* const b) const {
+            return *a == *b;
+        }
+    };
+
+    typedef std::unordered_map<const KeyT*, UniquePtr<Entry>, DerefHash,
+                               DerefEqual> MapT;
+    MapT mMap;
+
+public:
+    UniquePtr<Entry> MakeEntry(const KeyT& key, ValueT&& value) {
+        return UniquePtr<Entry>( new Entry(*this, key, std::move(value)) );
+    }
+    UniquePtr<Entry> MakeEntry(const KeyT& key, const ValueT& value) {
+        return MakeEntry(key, ValueT(value));
+    }
+
+    const ValueT* Insert(UniquePtr<Entry>&& entry)
+    {
+        auto insertable = typename MapT::value_type{
+            &entry->mKey,
+            std::move(entry)
+        };
+
+        const auto res = mMap.insert(std::move(insertable));
+        const auto& didInsert = res.second;
+        MOZ_ALWAYS_TRUE( didInsert );
+
+        const auto& itr = res.first;
+        return &itr->second->mValue;
+    }
+
+    const ValueT* Find(const KeyT& key) const {
+        const auto itr = mMap.find(&key);
+        if (itr == mMap.end())
+            return nullptr;
+
+        return &itr->second->mValue;
+    }
+
+    void Clear() const {
+        while (true) {
+            const auto itr = mMap.begin();
+            if (itr == mMap.end())
+                return;
+            itr->second->OnInvalidate();
+        }
+    }
+
+    ~CacheWeakMap() {
+        Clear();
+    }
+};
+
+} // namespace mozilla
+
+#endif // MOZILLA_CACHE_INVALIDATOR_H_
deleted file mode 100644
--- a/dom/canvas/CacheMap.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C++; tab-width: 13; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* vim: set ts=13 sts=4 et sw=4 tw=90: */
-/* 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 "CacheMap.h"
-
-namespace mozilla {
-
-void
-CacheMapInvalidator::InvalidateCaches() const
-{
-    while (mCacheEntries.size()) {
-        const auto pEntry = *(mCacheEntries.begin());
-        pEntry->Invalidate();
-        MOZ_ASSERT(mCacheEntries.find(pEntry) == mCacheEntries.end());
-    }
-}
-
-namespace detail {
-
-CacheMapUntypedEntry::CacheMapUntypedEntry(std::vector<const CacheMapInvalidator*>&& invalidators)
-    : mInvalidators(std::move(invalidators))
-{
-    for (const auto& cur : mInvalidators) {
-        // Don't assert that we insert, since there may be dupes in `invalidators`.
-        // (and it's not worth removing the dupes)
-        (void)cur->mCacheEntries.insert(this);
-    }
-}
-
-CacheMapUntypedEntry::~CacheMapUntypedEntry()
-{
-    for (const auto& cur : mInvalidators) {
-        // There might be dupes, so erase might return >1.
-        (void)cur->mCacheEntries.erase(this);
-    }
-}
-
-} // namespace detail
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/canvas/CacheMap.h
+++ /dev/null
@@ -1,131 +0,0 @@
-/* -*- Mode: C++; tab-width: 13; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* vim: set ts=13 sts=4 et sw=4 tw=90: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef MOZILLA_CACHE_MAP_H_
-#define MOZILLA_CACHE_MAP_H_
-
-#include "mozilla/UniquePtr.h"
-#include <map>
-#include <unordered_set>
-#include <vector>
-
-namespace mozilla {
-
-namespace detail {
-class CacheMapUntypedEntry;
-}
-
-class CacheMapInvalidator
-{
-    friend class detail::CacheMapUntypedEntry;
-
-    mutable std::unordered_set<const detail::CacheMapUntypedEntry*> mCacheEntries;
-
-public:
-    ~CacheMapInvalidator() {
-        InvalidateCaches();
-    }
-
-    void InvalidateCaches() const;
-};
-
-namespace detail {
-
-class CacheMapUntypedEntry
-{
-    template<typename, typename> friend class CacheMap;
-
-private:
-    const std::vector<const CacheMapInvalidator*> mInvalidators;
-
-protected:
-    CacheMapUntypedEntry(std::vector<const CacheMapInvalidator*>&& invalidators);
-    ~CacheMapUntypedEntry();
-
-public:
-    virtual void Invalidate() const = 0;
-};
-
-struct DerefLess final {
-    template<typename T>
-    bool operator ()(const T* const a, const T* const b) const {
-        return *a < *b;
-    }
-};
-
-} // namespace detail
-
-
-template<typename KeyT, typename ValueT>
-class CacheMap final
-{
-    class Entry final : public detail::CacheMapUntypedEntry {
-    public:
-        CacheMap& mParent;
-        const KeyT mKey;
-        const ValueT mValue;
-
-        Entry(std::vector<const CacheMapInvalidator*>&& invalidators, CacheMap& parent,
-              KeyT&& key, ValueT&& value)
-            : detail::CacheMapUntypedEntry(std::move(invalidators))
-            , mParent(parent)
-            , mKey(std::move(key))
-            , mValue(std::move(value))
-        { }
-
-        void Invalidate() const override {
-            const auto erased = mParent.mMap.erase(&mKey);
-            MOZ_ALWAYS_TRUE( erased == 1 );
-        }
-
-        bool operator <(const Entry& x) const {
-            return mKey < x.mKey;
-        }
-    };
-
-    typedef std::map<const KeyT*, UniquePtr<const Entry>, detail::DerefLess> MapT;
-    MapT mMap;
-
-public:
-    const ValueT* Insert(KeyT&& key, ValueT&& value,
-                         std::vector<const CacheMapInvalidator*>&& invalidators)
-    {
-        UniquePtr<const Entry> entry( new Entry(std::move(invalidators), *this, std::move(key),
-                                                std::move(value)) );
-
-        typename MapT::value_type insertable{
-            &entry->mKey,
-            nullptr
-        };
-        insertable.second = std::move(entry);
-
-        const auto res = mMap.insert(std::move(insertable));
-        const auto& didInsert = res.second;
-        MOZ_ALWAYS_TRUE( didInsert );
-
-        const auto& itr = res.first;
-        return &itr->second->mValue;
-    }
-
-    const ValueT* Find(const KeyT& key) const {
-        const auto itr = mMap.find(&key);
-        if (itr == mMap.end())
-            return nullptr;
-
-        return &itr->second->mValue;
-    }
-
-    void Invalidate() {
-        while (mMap.size()) {
-            const auto& itr = mMap.begin();
-            itr->second->Invalidate();
-        }
-    }
-};
-
-} // namespace mozilla
-
-#endif // MOZILLA_CACHE_MAP_H_
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -25,18 +25,16 @@ WebGL2Context::DeleteSampler(WebGLSample
 {
     const FuncScope funcScope(*this, "deleteSampler");
     if (!ValidateDeleteObject(sampler))
         return;
 
     for (uint32_t n = 0; n < mGLMaxTextureUnits; n++) {
         if (mBoundSamplers[n] == sampler) {
             mBoundSamplers[n] = nullptr;
-
-            InvalidateResolveCacheForTextureWithTexUnit(n);
         }
     }
 
     sampler->RequestDelete();
 }
 
 bool
 WebGL2Context::IsSampler(const WebGLSampler* const obj)
@@ -60,17 +58,16 @@ WebGL2Context::BindSampler(GLuint unit, 
 
     if (unit >= mGLMaxTextureUnits)
         return ErrorInvalidValue("unit must be < %u", mGLMaxTextureUnits);
 
     ////
 
     gl->fBindSampler(unit, sampler ? sampler->mGLName : 0);
 
-    InvalidateResolveCacheForTextureWithTexUnit(unit);
     mBoundSamplers[unit] = sampler;
 }
 
 void
 WebGL2Context::SamplerParameteri(WebGLSampler& sampler, GLenum pname, GLint param)
 {
     const FuncScope funcScope(*this, "samplerParameteri");
     if (IsContextLost())
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -19,48 +19,48 @@ namespace mozilla {
 
 // -------------------------------------------------------------------------
 // Uniforms
 
 void
 WebGLContext::Uniform1ui(WebGLUniformLocation* loc, GLuint v0)
 {
     const FuncScope funcScope(*this, "uniform1ui");
-    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_UNSIGNED_INT))
+    if (!ValidateUniformSetter(loc, 1, webgl::AttribBaseType::UInt))
         return;
 
     gl->fUniform1ui(loc->mLoc, v0);
 }
 
 void
 WebGLContext::Uniform2ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1)
 {
     const FuncScope funcScope(*this, "uniform2ui");
-    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_UNSIGNED_INT))
+    if (!ValidateUniformSetter(loc, 2, webgl::AttribBaseType::UInt))
         return;
 
     gl->fUniform2ui(loc->mLoc, v0, v1);
 }
 
 void
 WebGLContext::Uniform3ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2)
 {
     const FuncScope funcScope(*this, "uniform3ui");
-    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_UNSIGNED_INT))
+    if (!ValidateUniformSetter(loc, 3, webgl::AttribBaseType::UInt))
         return;
 
     gl->fUniform3ui(loc->mLoc, v0, v1, v2);
 }
 
 void
 WebGLContext::Uniform4ui(WebGLUniformLocation* loc, GLuint v0, GLuint v1, GLuint v2,
                          GLuint v3)
 {
     const FuncScope funcScope(*this, "uniform4ui");
-    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_UNSIGNED_INT))
+    if (!ValidateUniformSetter(loc, 4, webgl::AttribBaseType::UInt))
         return;
 
     gl->fUniform4ui(loc->mLoc, v0, v1, v2, v3);
 }
 
 // -------------------------------------------------------------------------
 // Uniform Buffer Objects and Transform Feedback Buffers
 
--- a/dom/canvas/WebGLActiveInfo.cpp
+++ b/dom/canvas/WebGLActiveInfo.cpp
@@ -73,26 +73,99 @@ ElemSizeFromType(GLenum elemType)
 
     default:
         MOZ_CRASH("GFX: Bad `elemType`.");
     }
 }
 
 ////////////////////
 
+static webgl::AttribBaseType
+ElemBaseType(const GLenum elemType)
+{
+    switch (elemType) {
+    case LOCAL_GL_FLOAT:
+    case LOCAL_GL_FLOAT_VEC2:
+    case LOCAL_GL_FLOAT_VEC3:
+    case LOCAL_GL_FLOAT_VEC4:
+
+    case LOCAL_GL_FLOAT_MAT2:
+    case LOCAL_GL_FLOAT_MAT2x3:
+    case LOCAL_GL_FLOAT_MAT2x4:
+
+    case LOCAL_GL_FLOAT_MAT3x2:
+    case LOCAL_GL_FLOAT_MAT3:
+    case LOCAL_GL_FLOAT_MAT3x4:
+
+    case LOCAL_GL_FLOAT_MAT4x2:
+    case LOCAL_GL_FLOAT_MAT4x3:
+    case LOCAL_GL_FLOAT_MAT4:
+        return webgl::AttribBaseType::Float;
+
+    // -
+
+    case LOCAL_GL_INT:
+    case LOCAL_GL_INT_VEC2:
+    case LOCAL_GL_INT_VEC3:
+    case LOCAL_GL_INT_VEC4:
+
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_3D:
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+        return webgl::AttribBaseType::Int;
+
+    // -
+
+    case LOCAL_GL_UNSIGNED_INT:
+    case LOCAL_GL_UNSIGNED_INT_VEC2:
+    case LOCAL_GL_UNSIGNED_INT_VEC3:
+    case LOCAL_GL_UNSIGNED_INT_VEC4:
+        return webgl::AttribBaseType::UInt;
+
+    // -
+
+    case LOCAL_GL_BOOL:
+    case LOCAL_GL_BOOL_VEC2:
+    case LOCAL_GL_BOOL_VEC3:
+    case LOCAL_GL_BOOL_VEC4:
+        return webgl::AttribBaseType::Bool;
+
+    // -
+
+    default:
+        MOZ_ASSERT(false, "unexpected attrib elemType");
+        return webgl::AttribBaseType::Float; // Just pick something.
+    }
+}
+
 WebGLActiveInfo::WebGLActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType,
                                  bool isArray, const nsACString& baseUserName,
                                  const nsACString& baseMappedName)
     : mWebGL(webgl)
     , mElemCount(elemCount)
     , mElemType(elemType)
     , mBaseUserName(baseUserName)
     , mIsArray(isArray)
     , mElemSize(ElemSizeFromType(elemType))
     , mBaseMappedName(baseMappedName)
+    , mBaseType(ElemBaseType(mElemType))
 { }
 
 bool
 WebGLActiveInfo::IsSampler() const
 {
     switch (mElemType) {
     case LOCAL_GL_SAMPLER_2D:
     case LOCAL_GL_SAMPLER_3D:
--- a/dom/canvas/WebGLActiveInfo.h
+++ b/dom/canvas/WebGLActiveInfo.h
@@ -36,16 +36,17 @@ public:
     const uint32_t mElemCount; // `size`
     const GLenum mElemType; // `type`
     const nsCString mBaseUserName; // `name`, but ASCII, and without any final "[0]".
 
     // Not actually part of ActiveInfo:
     const bool mIsArray;
     const uint8_t mElemSize;
     const nsCString mBaseMappedName; // Without any final "[0]".
+    const webgl::AttribBaseType mBaseType = webgl::AttribBaseType::Float;
 
     bool IsSampler() const;
 
     WebGLActiveInfo(WebGLContext* webgl, GLint elemCount, GLenum elemType, bool isArray,
                     const nsACString& baseUserName, const nsACString& baseMappedName);
 
     /* GLES 2.0.25, p33:
      *   This command will return as much information about active
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WEBGL_BUFFER_H_
 #define WEBGL_BUFFER_H_
 
 #include <map>
 
-#include "CacheMap.h"
+#include "CacheInvalidator.h"
 #include "GLDefs.h"
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 #include "WebGLObjectModel.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
 
@@ -122,16 +122,16 @@ protected:
             return indexCount < x.indexCount;
         }
     };
 
     UniqueBuffer mIndexCache;
     mutable std::map<IndexRange, Maybe<uint32_t>> mIndexRanges;
 
 public:
-    CacheMapInvalidator mFetchInvalidator;
+    CacheInvalidator mFetchInvalidator;
 
     void ResetLastUpdateFenceId() const;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_BUFFER_H_
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -267,25 +267,16 @@ WebGLContext::DestroyResourcesAndContext
 
     if (mEmptyTFO) {
         gl->fDeleteTransformFeedbacks(1, &mEmptyTFO);
         mEmptyTFO = 0;
     }
 
     //////
 
-    mFakeBlack_2D_0000       = nullptr;
-    mFakeBlack_2D_0001       = nullptr;
-    mFakeBlack_CubeMap_0000  = nullptr;
-    mFakeBlack_CubeMap_0001  = nullptr;
-    mFakeBlack_3D_0000       = nullptr;
-    mFakeBlack_3D_0001       = nullptr;
-    mFakeBlack_2D_Array_0000 = nullptr;
-    mFakeBlack_2D_Array_0001 = nullptr;
-
     if (mFakeVertexAttrib0BufferObject) {
         gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
         mFakeVertexAttrib0BufferObject = 0;
     }
 
     // disable all extensions except "WEBGL_lose_context". see bug #927969
     // spec: http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
     for (size_t i = 0; i < size_t(WebGLExtensionID::Max); ++i) {
@@ -1335,92 +1326,70 @@ WebGLContext::GetContextAttributes(dom::
     result.mStencil = mOptions.stencil;
     result.mAntialias = mOptions.antialias;
     result.mPremultipliedAlpha = mOptions.premultipliedAlpha;
     result.mPreserveDrawingBuffer = mOptions.preserveDrawingBuffer;
     result.mFailIfMajorPerformanceCaveat = mOptions.failIfMajorPerformanceCaveat;
     result.mPowerPreference = mOptions.powerPreference;
 }
 
-void
-WebGLContext::ForceClearFramebufferWithDefaultValues(const GLbitfield clearBits,
-                                                     const bool fakeNoAlpha) const
+// -
+
+namespace webgl {
+
+ScopedPrepForResourceClear::ScopedPrepForResourceClear(const WebGLContext& webgl_)
+    : webgl(webgl_)
 {
-    const bool initializeColorBuffer = bool(clearBits & LOCAL_GL_COLOR_BUFFER_BIT);
-    const bool initializeDepthBuffer = bool(clearBits & LOCAL_GL_DEPTH_BUFFER_BIT);
-    const bool initializeStencilBuffer = bool(clearBits & LOCAL_GL_STENCIL_BUFFER_BIT);
-
-    // Fun GL fact: No need to worry about the viewport here, glViewport is just
-    // setting up a coordinates transformation, it doesn't affect glClear at all.
-    AssertCachedGlobalState();
-
-    // Prepare GL state for clearing.
-    if (mScissorTestEnabled) {
+    const auto& gl = webgl.gl;
+
+    if (webgl.mScissorTestEnabled) {
         gl->fDisable(LOCAL_GL_SCISSOR_TEST);
     }
-
-    if (initializeColorBuffer) {
-        DoColorMask(0x0f);
-
-        if (fakeNoAlpha) {
-            gl->fClearColor(0.0f, 0.0f, 0.0f, 1.0f);
-        } else {
-            gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
-        }
-    }
-
-    if (initializeDepthBuffer) {
-        gl->fDepthMask(1);
-        gl->fClearDepth(1.0f);
-    }
-
-    if (initializeStencilBuffer) {
-        // "The clear operation always uses the front stencil write mask
-        //  when clearing the stencil buffer."
-        gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
-        gl->fStencilMaskSeparate(LOCAL_GL_BACK,  0xffffffff);
-        gl->fClearStencil(0);
-    }
-
-    if (mRasterizerDiscardEnabled) {
+    if (webgl.mRasterizerDiscardEnabled) {
         gl->fDisable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
-    // Do the clear!
-    gl->fClear(clearBits);
-
-    // And reset!
-    if (mScissorTestEnabled) {
+    // "The clear operation always uses the front stencil write mask
+    //  when clearing the stencil buffer."
+    webgl.DoColorMask(0x0f);
+    gl->fDepthMask(true);
+    gl->fStencilMaskSeparate(LOCAL_GL_FRONT, 0xffffffff);
+
+    gl->fClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    gl->fClearDepth(1.0f); // Depth formats are always cleared to 1.0f, not 0.0f.
+    gl->fClearStencil(0);
+}
+
+ScopedPrepForResourceClear::~ScopedPrepForResourceClear()
+{
+    const auto& gl = webgl.gl;
+
+    if (webgl.mScissorTestEnabled) {
         gl->fEnable(LOCAL_GL_SCISSOR_TEST);
     }
-
-    if (mRasterizerDiscardEnabled) {
+    if (webgl.mRasterizerDiscardEnabled) {
         gl->fEnable(LOCAL_GL_RASTERIZER_DISCARD);
     }
 
-    // Restore GL state after clearing.
-    if (initializeColorBuffer) {
-        gl->fClearColor(mColorClearValue[0],
-                        mColorClearValue[1],
-                        mColorClearValue[2],
-                        mColorClearValue[3]);
-    }
-
-    if (initializeDepthBuffer) {
-        gl->fDepthMask(mDepthWriteMask);
-        gl->fClearDepth(mDepthClearValue);
-    }
-
-    if (initializeStencilBuffer) {
-        gl->fStencilMaskSeparate(LOCAL_GL_FRONT, mStencilWriteMaskFront);
-        gl->fStencilMaskSeparate(LOCAL_GL_BACK,  mStencilWriteMaskBack);
-        gl->fClearStencil(mStencilClearValue);
-    }
+    // DoColorMask() is lazy.
+    gl->fDepthMask(webgl.mDepthWriteMask);
+    gl->fStencilMaskSeparate(LOCAL_GL_FRONT, webgl.mStencilWriteMaskFront);
+
+    gl->fClearColor(webgl.mColorClearValue[0],
+                    webgl.mColorClearValue[1],
+                    webgl.mColorClearValue[2],
+                    webgl.mColorClearValue[3]);
+    gl->fClearDepth(webgl.mDepthClearValue);
+    gl->fClearStencil(webgl.mStencilClearValue);
 }
 
+} // namespace webgl
+
+// -
+
 void
 WebGLContext::OnEndOfFrame() const
 {
    if (gfxPrefs::WebGLSpewFrameAllocs()) {
       GeneratePerfWarning("[webgl.perf.spew-frame-allocs] %" PRIu64 " data allocations this frame.",
                            mDataAllocGLCallCount);
    }
    mDataAllocGLCallCount = 0;
@@ -1908,22 +1877,27 @@ WebGLContext::ValidateAndInitFB(const We
 {
     if (fb)
         return fb->ValidateAndInitAttachments();
 
     if (!EnsureDefaultFB())
         return false;
 
     if (mDefaultFB_IsInvalid) {
+        // Clear it!
         gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mDefaultFB->mFB);
+        const webgl::ScopedPrepForResourceClear scopedPrep(*this);
+        if (!mOptions.alpha) {
+            gl->fClearColor(0, 0, 0, 1);
+        }
         const GLbitfield bits = LOCAL_GL_COLOR_BUFFER_BIT |
                                 LOCAL_GL_DEPTH_BUFFER_BIT |
                                 LOCAL_GL_STENCIL_BUFFER_BIT;
-        const bool fakeNoAlpha = !mOptions.alpha;
-        ForceClearFramebufferWithDefaultValues(bits, fakeNoAlpha);
+        gl->fClear(bits);
+
         mDefaultFB_IsInvalid = false;
     }
     return true;
 }
 
 void
 WebGLContext::DoBindFB(const WebGLFramebuffer* const fb, const GLenum target) const
 {
@@ -2031,18 +2005,18 @@ ScopedDrawCallWrapper::ScopedDrawCallWra
                                  // rendering?
         } else {
             driverColorMask &= ~(uint8_t(mWebGL.mNeedsFakeNoAlpha) << 3);
         }
         driverDepthTest   &= !mWebGL.mNeedsFakeNoDepth;
         driverStencilTest &= !mWebGL.mNeedsFakeNoStencil;
     } else {
         if (mWebGL.mNeedsFakeNoStencil_UserFBs &&
-            fb->DepthAttachment().IsDefined() &&
-            !fb->StencilAttachment().IsDefined())
+            fb->DepthAttachment().HasAttachment() &&
+            !fb->StencilAttachment().HasAttachment())
         {
             driverStencilTest = false;
         }
     }
 
     const auto& gl = mWebGL.gl;
     mWebGL.DoColorMask(driverColorMask);
     if (mWebGL.mDriverDepthTest != driverDepthTest) {
@@ -2092,17 +2066,17 @@ IndexedBufferBinding::ByteCount() const
         return 0;
     bufferSize -= mRangeStart;
 
     return std::min(bufferSize, mRangeSize);
 }
 
 ////////////////////////////////////////
 
-ScopedUnpackReset::ScopedUnpackReset(WebGLContext* webgl)
+ScopedUnpackReset::ScopedUnpackReset(const WebGLContext* const webgl)
     : ScopedGLWrapper<ScopedUnpackReset>(webgl->gl)
     , mWebGL(webgl)
 {
     if (mWebGL->mPixelStore_UnpackAlignment != 4) mGL->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
 
     if (mWebGL->IsWebGL2()) {
         if (mWebGL->mPixelStore_UnpackRowLength   != 0) mGL->fPixelStorei(LOCAL_GL_UNPACK_ROW_LENGTH  , 0);
         if (mWebGL->mPixelStore_UnpackImageHeight != 0) mGL->fPixelStorei(LOCAL_GL_UNPACK_IMAGE_HEIGHT, 0);
@@ -2386,17 +2360,17 @@ WebGLContext::EnsureVRReady()
         const auto caps = gl->Screen()->mCaps;
         auto flags = TextureFlags::ORIGIN_BOTTOM_LEFT;
         if (!IsPremultAlpha() && mOptions.alpha) {
             flags |= TextureFlags::NON_PREMULTIPLIED;
         }
         auto factory = gl::GLScreenBuffer::CreateFactory(gl, caps, imageBridge.get(), flags);
         gl->Screen()->Morph(std::move(factory));
 #if defined(MOZ_WIDGET_ANDROID)
-        // On Android we are using a different GLScreenBuffer for WebVR, so we need a resize here because 
+        // On Android we are using a different GLScreenBuffer for WebVR, so we need a resize here because
         // PresentScreenBuffer() may not be called for the gl->Screen() after we set the new factory.
         gl->Screen()->Resize(DrawingBufferSize());
 #endif
         mVRReady = true;
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -26,22 +26,21 @@
 #include "nsLayoutUtils.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "SurfaceTypes.h"
 #include "ScopedGLHelpers.h"
 #include "TexUnpackBlob.h"
 
 // Local
-#include "CacheMap.h"
+#include "CacheInvalidator.h"
 #include "WebGLContextLossHandler.h"
 #include "WebGLContextUnchecked.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
-#include "WebGLTexture.h"
 
 // Generated
 #include "nsIDOMEventListener.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "nsIObserver.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsWrapperCache.h"
 #include "nsLayoutUtils.h"
@@ -99,17 +98,20 @@ class MozFramebuffer;
 } // namespace gl
 
 namespace webgl {
 class AvailabilityRunnable;
 struct CachedDrawFetchLimits;
 struct FormatInfo;
 class FormatUsageAuthority;
 struct FormatUsageInfo;
+struct ImageInfo;
 struct LinkedProgramInfo;
+struct SamplingState;
+class ScopedPrepForResourceClear;
 class ShaderValidator;
 class TexUnpackBlob;
 struct UniformInfo;
 struct UniformBlockInfo;
 } // namespace webgl
 
 WebGLTexelFormat GetWebGLTexelFormat(TexInternalFormat format);
 
@@ -301,16 +303,17 @@ class WebGLContext
     friend class WebGLExtensionDisjointTimerQuery;
     friend class WebGLExtensionDrawBuffers;
     friend class WebGLExtensionLoseContext;
     friend class WebGLExtensionMOZDebug;
     friend class WebGLExtensionVertexArray;
     friend class WebGLMemoryTracker;
     friend class webgl::AvailabilityRunnable;
     friend struct webgl::LinkedProgramInfo;
+    friend class webgl::ScopedPrepForResourceClear;
     friend struct webgl::UniformBlockInfo;
 
     friend const webgl::CachedDrawFetchLimits*
         ValidateDraw(WebGLContext*, GLenum, uint32_t);
 
     enum {
         UNPACK_FLIP_Y_WEBGL = 0x9240,
         UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241,
@@ -469,18 +472,16 @@ public:
      */
     WebGLTexture*
     ActiveBoundTextureForTexImageTarget(const TexImageTarget texImgTarget) const
     {
         const TexTarget texTarget = TexImageTargetToTexTarget(texImgTarget);
         return ActiveBoundTextureForTarget(texTarget);
     }
 
-    void InvalidateResolveCacheForTextureWithTexUnit(const GLuint);
-
     already_AddRefed<Layer>
     GetCanvasLayer(nsDisplayListBuilder* builder, Layer* oldLayer,
                    LayerManager* manager) override;
 
     bool
     UpdateWebRenderCanvasData(nsDisplayListBuilder* aBuilder,
                               WebRenderCanvasData* aCanvasData) override;
 
@@ -508,23 +509,16 @@ public:
     void BeginComposition(gl::GLScreenBuffer* const screen = nullptr);
     // Clean up the context after captured for compositing
     void EndComposition();
 
     // a number that increments every time we have an event that causes
     // all context resources to be lost.
     uint32_t Generation() const { return mGeneration.value(); }
 
-    // This is similar to GLContext::ClearSafely, but tries to minimize the
-    // amount of work it does.
-    // It only clears the buffers we specify, and can reset its state without
-    // first having to query anything, as WebGL knows its state at all times.
-    void ForceClearFramebufferWithDefaultValues(GLbitfield bufferBits,
-                                                bool fakeNoAlpha) const;
-
     void RunContextLossTimer();
     void UpdateContextLossStatus();
     void EnqueueUpdateContextLossStatus();
 
     bool TryToRestoreContext();
 
     void AssertCachedBindings() const;
     void AssertCachedGlobalState() const;
@@ -930,27 +924,28 @@ public:
 
     #undef FOO
 
     ////////////////////////////////////
 
     void UseProgram(WebGLProgram* prog);
 
     bool ValidateAttribArraySetter(uint32_t count, uint32_t arrayLength);
-    bool ValidateUniformLocation(WebGLUniformLocation* loc);
-    bool ValidateUniformSetter(WebGLUniformLocation* loc, uint8_t setterSize,
-                               GLenum setterType);
-    bool ValidateUniformArraySetter(WebGLUniformLocation* loc,
-                                    uint8_t setterElemSize, GLenum setterType,
+    bool ValidateUniformLocation(const WebGLUniformLocation* loc);
+    bool ValidateUniformSetter(const WebGLUniformLocation* loc, uint8_t setterElemSize,
+                               webgl::AttribBaseType setterType);
+    bool ValidateUniformArraySetter(const WebGLUniformLocation* loc,
+                                    uint8_t setterElemSize,
+                                    webgl::AttribBaseType setterType,
                                     uint32_t setterArraySize,
                                     uint32_t* out_numElementsToUpload);
-    bool ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
+    bool ValidateUniformMatrixArraySetter(const WebGLUniformLocation* loc,
                                           uint8_t setterCols,
                                           uint8_t setterRows,
-                                          GLenum setterType,
+                                          webgl::AttribBaseType setterType,
                                           uint32_t setterArraySize,
                                           bool setterTranspose,
                                           uint32_t* out_numElementsToUpload);
     void ValidateProgram(const WebGLProgram& prog);
     bool ValidateUniformLocation(const char* info, WebGLUniformLocation* loc);
     bool ValidateSamplerUniformSetter(const char* info,
                                       WebGLUniformLocation* loc, GLint value);
     void Viewport(GLint x, GLint y, GLsizei width, GLsizei height);
@@ -1314,24 +1309,24 @@ public:
 
 protected:
     bool ValidateTexImageSpecification(uint8_t funcDims,
                                        GLenum texImageTarget, GLint level,
                                        GLsizei width, GLsizei height, GLsizei depth,
                                        GLint border,
                                        TexImageTarget* const out_target,
                                        WebGLTexture** const out_texture,
-                                       WebGLTexture::ImageInfo** const out_imageInfo);
+                                       webgl::ImageInfo** const out_imageInfo);
     bool ValidateTexImageSelection(uint8_t funcDims,
                                    GLenum texImageTarget, GLint level, GLint xOffset,
                                    GLint yOffset, GLint zOffset, GLsizei width,
                                    GLsizei height, GLsizei depth,
                                    TexImageTarget* const out_target,
                                    WebGLTexture** const out_texture,
-                                   WebGLTexture::ImageInfo** const out_imageInfo);
+                                   webgl::ImageInfo** const out_imageInfo);
     bool ValidateUnpackInfo(bool usePBOs, GLenum format,
                             GLenum type, webgl::PackingInfo* const out);
 
     UniquePtr<webgl::TexUnpackBlob>
     FromDomElem(TexImageTarget target, uint32_t width,
                 uint32_t height, uint32_t depth, const dom::Element& elem,
                 ErrorResult* const out_error);
 
@@ -1881,52 +1876,27 @@ protected:
                           uint32_t* const out_endOffset);
 
     GLenum mPixelStore_ColorspaceConversion = 0;
     bool mPixelStore_FlipY = false;
     bool mPixelStore_PremultiplyAlpha = false;
     bool mPixelStore_RequireFastPath = false;
 
     ////////////////////////////////////
-    class FakeBlackTexture {
-    public:
-        static UniquePtr<FakeBlackTexture> Create(gl::GLContext* gl,
-                                                  TexTarget target,
-                                                  FakeBlackType type);
-        gl::GLContext* const mGL;
-        const GLuint mGLName;
-
-        ~FakeBlackTexture();
-    protected:
-        explicit FakeBlackTexture(gl::GLContext* gl);
-    };
-
-    UniquePtr<FakeBlackTexture> mFakeBlack_2D_0000;
-    UniquePtr<FakeBlackTexture> mFakeBlack_2D_0001;
-    UniquePtr<FakeBlackTexture> mFakeBlack_CubeMap_0000;
-    UniquePtr<FakeBlackTexture> mFakeBlack_CubeMap_0001;
-    UniquePtr<FakeBlackTexture> mFakeBlack_3D_0000;
-    UniquePtr<FakeBlackTexture> mFakeBlack_3D_0001;
-    UniquePtr<FakeBlackTexture> mFakeBlack_2D_Array_0000;
-    UniquePtr<FakeBlackTexture> mFakeBlack_2D_Array_0001;
-
-    bool BindFakeBlack(uint32_t texUnit, TexTarget target, FakeBlackType fakeBlack);
-
-    ////////////////////////////////////
 
 protected:
     GLuint mEmptyTFO;
 
     // Generic Vertex Attributes
     // Though CURRENT_VERTEX_ATTRIB is listed under "Vertex Shader State" in the spec
     // state tables, this isn't vertex shader /object/ state. This array is merely state
     // useful to vertex shaders, but is global state.
-    UniquePtr<GLenum[]> mGenericVertexAttribTypes;
+    std::vector<webgl::AttribBaseType> mGenericVertexAttribTypes;
     uint8_t mGenericVertexAttrib0Data[sizeof(float) * 4];
-    CacheMapInvalidator mGenericVertexAttribTypeInvalidator;
+    CacheInvalidator mGenericVertexAttribTypeInvalidator;
 
     GLuint mFakeVertexAttrib0BufferObject = 0;
     size_t mFakeVertexAttrib0BufferObjectSize = 0;
     bool mFakeVertexAttrib0DataDefined = false;
     uint8_t mFakeVertexAttrib0Data[sizeof(float) * 4];
 
     JSObject* GetVertexAttribFloat32Array(JSContext* cx, GLuint index);
     JSObject* GetVertexAttribInt32Array(JSContext* cx, GLuint index);
@@ -2044,16 +2014,18 @@ public:
     void UpdateMaxDrawBuffers();
 
     // --
 private:
     webgl::AvailabilityRunnable* mAvailabilityRunnable = nullptr;
 public:
     webgl::AvailabilityRunnable* EnsureAvailabilityRunnable();
 
+    // -
+
     // Friend list
     friend class ScopedCopyTexImageSource;
     friend class ScopedResolveTexturesForDraw;
     friend class ScopedUnpackReset;
     friend class webgl::TexUnpackBlob;
     friend class webgl::TexUnpackBytes;
     friend class webgl::TexUnpackImage;
     friend class webgl::TexUnpackSurface;
@@ -2104,35 +2076,35 @@ ValidateTexImageTarget(WebGLContext* web
                        WebGLTexture** const out_tex);
 
 class ScopedUnpackReset final
     : public gl::ScopedGLWrapper<ScopedUnpackReset>
 {
     friend struct gl::ScopedGLWrapper<ScopedUnpackReset>;
 
 private:
-    WebGLContext* const mWebGL;
+    const WebGLContext* const mWebGL;
 
 public:
-    explicit ScopedUnpackReset(WebGLContext* webgl);
+    explicit ScopedUnpackReset(const WebGLContext* webgl);
 
 private:
     void UnwrapImpl();
 };
 
 class ScopedFBRebinder final
     : public gl::ScopedGLWrapper<ScopedFBRebinder>
 {
     friend struct gl::ScopedGLWrapper<ScopedFBRebinder>;
 
 private:
-    WebGLContext* const mWebGL;
+    const WebGLContext* const mWebGL;
 
 public:
-    explicit ScopedFBRebinder(WebGLContext* webgl)
+    explicit ScopedFBRebinder(const WebGLContext* const webgl)
         : ScopedGLWrapper<ScopedFBRebinder>(webgl->gl)
         , mWebGL(webgl)
     { }
 
 private:
     void UnwrapImpl();
 };
 
@@ -2167,16 +2139,27 @@ class ScopedDrawCallWrapper final
 {
 public:
     WebGLContext& mWebGL;
 
     explicit ScopedDrawCallWrapper(WebGLContext& webgl);
     ~ScopedDrawCallWrapper();
 };
 
+namespace webgl {
+class ScopedPrepForResourceClear final
+{
+    const WebGLContext& webgl;
+
+public:
+    explicit ScopedPrepForResourceClear(const WebGLContext&);
+    ~ScopedPrepForResourceClear();
+};
+} // namespace webgl
+
 ////
 
 void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags = 0);
 
 void
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -42,114 +42,124 @@ class ScopedResolveTexturesForDraw
     WebGLContext* const mWebGL;
     std::vector<TexRebindRequest> mRebindRequests;
 
 public:
     ScopedResolveTexturesForDraw(WebGLContext* webgl, bool* const out_error);
     ~ScopedResolveTexturesForDraw();
 };
 
-bool
-WebGLTexture::IsFeedback(WebGLContext* webgl, uint32_t texUnit,
-                         const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const
+static bool
+ValidateNoSamplingFeedback(const WebGLTexture& tex, const uint32_t sampledLevels,
+                           const WebGLFramebuffer* const fb, const uint32_t texUnit)
 {
-    auto itr = fbAttachments.cbegin();
-    for (; itr != fbAttachments.cend(); ++itr) {
-        const auto& attach = *itr;
-        if (attach->Texture() == this)
-            break;
-    }
-
-    if (itr == fbAttachments.cend())
-        return false;
+    if (!fb)
+        return true;
 
-    ////
-
-    const auto minLevel = mBaseMipmapLevel;
-    uint32_t maxLevel;
-    if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel)) {
-        // No valid mips. Will need fake-black.
-        return false;
-    }
-
-    ////
-
-    for (; itr != fbAttachments.cend(); ++itr) {
-        const auto& attach = *itr;
-        if (attach->Texture() != this)
+    const auto& texAttachments = fb->GetCompletenessInfo()->texAttachments;
+    for (const auto& attach : texAttachments) {
+        if (attach->Texture() != &tex)
             continue;
 
-        const auto dstLevel = attach->MipLevel();
-
-        if (minLevel <= dstLevel && dstLevel <= maxLevel) {
-            webgl->ErrorInvalidOperation("Feedback loop detected between tex target"
-                                         " 0x%04x, tex unit %u, levels %u-%u; and"
-                                         " framebuffer attachment 0x%04x, level %u.",
-                                         mTarget.get(), texUnit, minLevel,
-                                         maxLevel, attach->mAttachmentPoint, dstLevel);
-            return true;
+        const auto& srcBase = tex.BaseMipmapLevel();
+        const auto srcLast = srcBase + sampledLevels - 1;
+        const auto& dstLevel = attach->MipLevel();
+        if (MOZ_UNLIKELY( srcBase <= dstLevel && dstLevel <= srcLast )) {
+            const auto& webgl = tex.mContext;
+            const auto& texTargetStr = EnumString(tex.Target().get());
+            const auto& attachStr = EnumString(attach->mAttachmentPoint);
+            webgl->ErrorInvalidOperation("Texture level %u would be read by %s unit %u,"
+                                         " but written by framebuffer attachment %s,"
+                                         " which would be illegal feedback.",
+                                         dstLevel, texTargetStr.c_str(), texUnit,
+                                         attachStr.c_str());
+            return false;
         }
     }
-
-    return false;
+    return true;
 }
 
 ScopedResolveTexturesForDraw::ScopedResolveTexturesForDraw(WebGLContext* webgl,
                                                            bool* const out_error)
     : mWebGL(webgl)
 {
-    MOZ_ASSERT(mWebGL->gl->IsCurrent());
-
-    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);
 
+        const auto& uniformBaseType = uniform->mTexBaseType;
         for (const auto& texUnit : uniform->mSamplerValues) {
             if (texUnit >= texList.Length())
                 continue;
 
             const auto& tex = texList[texUnit];
             if (!tex)
                 continue;
 
-            if (attachList &&
-                tex->IsFeedback(mWebGL, texUnit, *attachList))
-            {
+            const auto& sampler = mWebGL->mBoundSamplers[texUnit];
+            const auto& samplingInfo = tex->GetSampleableInfo(sampler.get());
+            if (!samplingInfo) { // There was an error.
                 *out_error = true;
                 return;
             }
+            if (!samplingInfo->IsComplete()) {
+                if (samplingInfo->incompleteReason) {
+                    const auto& targetName = GetEnumName(tex->Target().get());
+                    mWebGL->GenerateWarning("%s at unit %u is incomplete: %s",
+                                            targetName, texUnit,
+                                            samplingInfo->incompleteReason);
+                }
+                mRebindRequests.push_back({texUnit, tex});
+                continue;
+            }
 
-            FakeBlackType fakeBlack;
-            if (!tex->ResolveForDraw(texUnit, &fakeBlack)) {
-                mWebGL->ErrorOutOfMemory("Failed to resolve textures for draw.");
+            // We have more validation to do if we're otherwise complete:
+            const auto& texBaseType = samplingInfo->usage->format->baseType;
+            if (texBaseType != uniformBaseType) {
+                const auto& targetName = GetEnumName(tex->Target().get());
+                const auto& srcType = ToString(texBaseType);
+                const auto& dstType = ToString(uniformBaseType);
+                mWebGL->ErrorInvalidOperation("%s at unit %u is of type %s, but"
+                                              " the shader samples as %s.",
+                                              targetName, texUnit, srcType,
+                                              dstType);
                 *out_error = true;
                 return;
             }
 
-            if (fakeBlack == FakeBlackType::None)
-                continue;
-
-            if (!mWebGL->BindFakeBlack(texUnit, tex->Target(), fakeBlack)) {
-                mWebGL->ErrorOutOfMemory("Failed to create fake black texture.");
+            if (uniform->mIsShadowSampler != samplingInfo->isDepthTexCompare) {
+                const auto& targetName = GetEnumName(tex->Target().get());
+                mWebGL->ErrorInvalidOperation("%s at unit %u is%s a depth texture"
+                                              " with TEXTURE_COMPARE_MODE, but"
+                                              " the shader sampler is%s a shadow"
+                                              " sampler.",
+                                              targetName, texUnit,
+                                              samplingInfo->isDepthTexCompare ? "" : " not",
+                                              uniform->mIsShadowSampler ? "" : " not");
                 *out_error = true;
                 return;
             }
 
-            mRebindRequests.push_back({texUnit, tex});
+            if (!ValidateNoSamplingFeedback(*tex, samplingInfo->levels, fb.get(),
+                                            texUnit))
+            {
+                *out_error = true;
+                return;
+            }
         }
     }
 
-    *out_error = false;
+    const auto& gl = mWebGL->gl;
+    for (const auto& itr : mRebindRequests) {
+        gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
+        gl->fBindTexture(itr.tex->Target().get(), 0); // Tex 0 is always incomplete.
+    }
 }
 
 ScopedResolveTexturesForDraw::~ScopedResolveTexturesForDraw()
 {
     if (mRebindRequests.empty())
         return;
 
     gl::GLContext* gl = mWebGL->gl;
@@ -157,97 +167,32 @@ ScopedResolveTexturesForDraw::~ScopedRes
     for (const auto& itr : mRebindRequests) {
         gl->fActiveTexture(LOCAL_GL_TEXTURE0 + itr.texUnit);
         gl->fBindTexture(itr.tex->Target().get(), itr.tex->mGLName);
     }
 
     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mWebGL->mActiveTexture);
 }
 
-bool
-WebGLContext::BindFakeBlack(uint32_t texUnit, TexTarget target, FakeBlackType fakeBlack)
-{
-    MOZ_ASSERT(fakeBlack == FakeBlackType::RGBA0000 ||
-               fakeBlack == FakeBlackType::RGBA0001);
-
-    const auto fnGetSlot = [this, target, fakeBlack]() -> UniquePtr<FakeBlackTexture>*
-    {
-        switch (fakeBlack) {
-        case FakeBlackType::RGBA0000:
-            switch (target.get()) {
-            case LOCAL_GL_TEXTURE_2D      : return &mFakeBlack_2D_0000;
-            case LOCAL_GL_TEXTURE_CUBE_MAP: return &mFakeBlack_CubeMap_0000;
-            case LOCAL_GL_TEXTURE_3D      : return &mFakeBlack_3D_0000;
-            case LOCAL_GL_TEXTURE_2D_ARRAY: return &mFakeBlack_2D_Array_0000;
-            default: return nullptr;
-            }
-
-        case FakeBlackType::RGBA0001:
-            switch (target.get()) {
-            case LOCAL_GL_TEXTURE_2D      : return &mFakeBlack_2D_0001;
-            case LOCAL_GL_TEXTURE_CUBE_MAP: return &mFakeBlack_CubeMap_0001;
-            case LOCAL_GL_TEXTURE_3D      : return &mFakeBlack_3D_0001;
-            case LOCAL_GL_TEXTURE_2D_ARRAY: return &mFakeBlack_2D_Array_0001;
-            default: return nullptr;
-            }
-
-        default:
-            return nullptr;
-        }
-    };
-
-    UniquePtr<FakeBlackTexture>* slot = fnGetSlot();
-    if (!slot) {
-        MOZ_CRASH("GFX: fnGetSlot failed.");
-    }
-    UniquePtr<FakeBlackTexture>& fakeBlackTex = *slot;
-
-    if (!fakeBlackTex) {
-        gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1);
-        if (IsWebGL2()) {
-            gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, 0);
-            gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, 0);
-            gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, 0);
-        }
-
-        fakeBlackTex = FakeBlackTexture::Create(gl, target, fakeBlack);
-
-        gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, mPixelStore_UnpackAlignment);
-        if (IsWebGL2()) {
-            gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_PIXELS, mPixelStore_UnpackSkipPixels);
-            gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_ROWS, mPixelStore_UnpackSkipRows);
-            gl->fPixelStorei(LOCAL_GL_UNPACK_SKIP_IMAGES, mPixelStore_UnpackSkipImages);
-        }
-        if (!fakeBlackTex) {
-            return false;
-        }
-    }
-
-    gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
-    gl->fBindTexture(target.get(), fakeBlackTex->mGLName);
-    gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
-    return true;
-}
-
 ////////////////////////////////////////
 
 bool
 WebGLContext::ValidateStencilParamsForDrawCall() const
 {
     const auto stencilBits = [&]() -> uint8_t {
         if (!mStencilTestEnabled)
             return 0;
 
         if (!mBoundDrawFramebuffer)
             return mOptions.stencil ? 8 : 0;
 
-        if (mBoundDrawFramebuffer->StencilAttachment().IsDefined())
+        if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment())
             return 8;
 
-        if (mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined())
+        if (mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
             return 8;
 
         return 0;
     }();
     const uint32_t stencilMax = (1 << stencilBits) - 1;
 
     const auto fnMask = [&](const uint32_t x) { return x & stencilMax; };
     const auto fnClamp = [&](const int32_t x) {
@@ -832,27 +777,22 @@ WebGLContext::Draw_cleanup()
                 gl->fFlush();
                 mDrawCallsSinceLastFlush = 0;
             }
         }
     }
 
     // Let's check for a really common error: Viewport is larger than the actual
     // destination framebuffer.
-    uint32_t destWidth = mViewportWidth;
-    uint32_t destHeight = mViewportHeight;
-
+    uint32_t destWidth;
+    uint32_t destHeight;
     if (mBoundDrawFramebuffer) {
-        const auto& drawBuffers = mBoundDrawFramebuffer->ColorDrawBuffers();
-        for (const auto& cur : drawBuffers) {
-            if (!cur->IsDefined())
-                continue;
-            cur->Size(&destWidth, &destHeight);
-            break;
-        }
+        const auto& info = mBoundDrawFramebuffer->GetCompletenessInfo();
+        destWidth = info->width;
+        destHeight = info->height;
     } else {
         destWidth = mDefaultFB->mSize.width;
         destHeight = mDefaultFB->mSize.height;
     }
 
     if (mViewportWidth > int32_t(destWidth) ||
         mViewportHeight > int32_t(destHeight))
     {
@@ -914,30 +854,28 @@ WebGLContext::DoFakeVertexAttrib0(const 
         gl->fGenBuffers(1, &mFakeVertexAttrib0BufferObject);
         mFakeVertexAttrib0BufferObjectSize = 0;
     }
     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mFakeVertexAttrib0BufferObject);
 
     ////
 
     switch (mGenericVertexAttribTypes[0]) {
-    case LOCAL_GL_FLOAT:
+    case webgl::AttribBaseType::Bool:
+    case webgl::AttribBaseType::Float:
         gl->fVertexAttribPointer(0, 4, LOCAL_GL_FLOAT, false, 0, 0);
         break;
 
-    case LOCAL_GL_INT:
+    case webgl::AttribBaseType::Int:
         gl->fVertexAttribIPointer(0, 4, LOCAL_GL_INT, 0, 0);
         break;
 
-    case LOCAL_GL_UNSIGNED_INT:
+    case webgl::AttribBaseType::UInt:
         gl->fVertexAttribIPointer(0, 4, LOCAL_GL_UNSIGNED_INT, 0, 0);
         break;
-
-    default:
-        MOZ_CRASH();
     }
 
     ////
 
     const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
     const auto checked_dataSize = CheckedUint32(vertexCount) * bytesPerVert;
     if (!checked_dataSize.isValid()) {
         ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0"
@@ -1011,78 +949,9 @@ WebGLContext::UndoFakeVertexAttrib0()
         attrib0.DoVertexAttribPointer(gl, 0);
     } else {
         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
     }
 
     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->mGLName : 0);
 }
 
-static GLuint
-CreateGLTexture(gl::GLContext* gl)
-{
-    MOZ_ASSERT(gl->IsCurrent());
-    GLuint ret = 0;
-    gl->fGenTextures(1, &ret);
-    return ret;
-}
-
-UniquePtr<WebGLContext::FakeBlackTexture>
-WebGLContext::FakeBlackTexture::Create(gl::GLContext* gl, TexTarget target,
-                                       FakeBlackType type)
-{
-    GLenum texFormat;
-    switch (type) {
-    case FakeBlackType::RGBA0000:
-        texFormat = LOCAL_GL_RGBA;
-        break;
-
-    case FakeBlackType::RGBA0001:
-        texFormat = LOCAL_GL_RGB;
-        break;
-
-    default:
-        MOZ_CRASH("GFX: bad type");
-    }
-
-    UniquePtr<FakeBlackTexture> result(new FakeBlackTexture(gl));
-    gl::ScopedBindTexture scopedBind(gl, result->mGLName, target.get());
-
-    gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
-    gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
-
-    const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE};
-    UniqueBuffer zeros = moz_xcalloc(1, 4); // Infallible allocation.
-
-    MOZ_ASSERT(gl->IsCurrent());
-
-    if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
-        for (int i = 0; i < 6; ++i) {
-            const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
-            const GLenum error = DoTexImage(gl, curTarget.get(), 0, &dui, 1, 1, 1,
-                                            zeros.get());
-            if (error) {
-                return nullptr;
-            }
-        }
-    } else {
-        const GLenum error = DoTexImage(gl, target.get(), 0, &dui, 1, 1, 1,
-                                        zeros.get());
-        if (error) {
-            return nullptr;
-        }
-    }
-
-    return result;
-}
-
-WebGLContext::FakeBlackTexture::FakeBlackTexture(gl::GLContext* gl)
-    : mGL(gl)
-    , mGLName(CreateGLTexture(gl))
-{
-}
-
-WebGLContext::FakeBlackTexture::~FakeBlackTexture()
-{
-    mGL->fDeleteTextures(1, &mGLName);
-}
-
 } // namespace mozilla
--- a/dom/canvas/WebGLContextFramebufferOperations.cpp
+++ b/dom/canvas/WebGLContextFramebufferOperations.cpp
@@ -27,32 +27,25 @@ WebGLContext::Clear(GLbitfield mask)
 
     if (mask == 0) {
         GenerateWarning("Calling gl.clear(0) has no effect.");
     } else if (mRasterizerDiscardEnabled) {
         GenerateWarning("Calling gl.clear() with RASTERIZER_DISCARD enabled has no effects.");
     }
 
     if (mask & LOCAL_GL_COLOR_BUFFER_BIT && mBoundDrawFramebuffer) {
-        if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
-            for (const auto& cur : mBoundDrawFramebuffer->ColorDrawBuffers()) {
-                if (!cur->HasImage())
-                    continue;
+        for (const auto& cur : mBoundDrawFramebuffer->ColorDrawBuffers()) {
+            const auto imageInfo = cur->GetImageInfo();
+            if (!imageInfo || !imageInfo->mFormat)
+                continue;
 
-                switch (cur->Format()->format->componentType) {
-                case webgl::ComponentType::Float:
-                case webgl::ComponentType::NormInt:
-                case webgl::ComponentType::NormUInt:
-                    break;
-
-                default:
-                    ErrorInvalidOperation("Color draw buffers must be floating-point"
-                                          " or fixed-point. (normalized (u)ints)");
-                    return;
-                }
+            if (imageInfo->mFormat->format->baseType != webgl::TextureBaseType::Float) {
+                ErrorInvalidOperation("Color draw buffers must be floating-point"
+                                      " or fixed-point. (normalized (u)ints)");
+                return;
             }
         }
     }
 
     if (!BindCurFBForDraw())
         return;
 
     auto driverMask = mask;
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -409,18 +409,16 @@ WebGLContext::DeleteRenderbuffer(WebGLRe
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachRenderbuffer(rbuf);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachRenderbuffer(rbuf);
 
-    rbuf->InvalidateStatusOfAttachedFBs();
-
     if (mBoundRenderbuffer == rbuf)
         BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
 
     rbuf->RequestDelete();
 }
 
 void
 WebGLContext::DeleteTexture(WebGLTexture* tex)
@@ -1929,97 +1927,97 @@ public:
 };
 
 ////////////////////
 
 void
 WebGLContext::Uniform1i(WebGLUniformLocation* loc, GLint a1)
 {
     const FuncScope funcScope(*this, "uniform1i");
-    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_INT))
+    if (!ValidateUniformSetter(loc, 1, webgl::AttribBaseType::Int))
         return;
 
     bool error;
     const ValidateIfSampler validate(this, loc, 1, &a1, &error);
     if (error)
         return;
 
     gl->fUniform1i(loc->mLoc, a1);
 }
 
 void
 WebGLContext::Uniform2i(WebGLUniformLocation* loc, GLint a1, GLint a2)
 {
     const FuncScope funcScope(*this, "uniform2i");
-    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_INT))
+    if (!ValidateUniformSetter(loc, 2, webgl::AttribBaseType::Int))
         return;
 
     gl->fUniform2i(loc->mLoc, a1, a2);
 }
 
 void
 WebGLContext::Uniform3i(WebGLUniformLocation* loc, GLint a1, GLint a2, GLint a3)
 {
     const FuncScope funcScope(*this, "uniform3i");
-    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_INT))
+    if (!ValidateUniformSetter(loc, 3, webgl::AttribBaseType::Int))
         return;
 
     gl->fUniform3i(loc->mLoc, a1, a2, a3);
 }
 
 void
 WebGLContext::Uniform4i(WebGLUniformLocation* loc, GLint a1, GLint a2, GLint a3,
                         GLint a4)
 {
     const FuncScope funcScope(*this, "uniform4i");
-    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_INT))
+    if (!ValidateUniformSetter(loc, 4, webgl::AttribBaseType::Int))
         return;
 
     gl->fUniform4i(loc->mLoc, a1, a2, a3, a4);
 }
 
 //////////
 
 void
 WebGLContext::Uniform1f(WebGLUniformLocation* loc, GLfloat a1)
 {
     const FuncScope funcScope(*this, "uniform1f");
-    if (!ValidateUniformSetter(loc, 1, LOCAL_GL_FLOAT))
+    if (!ValidateUniformSetter(loc, 1, webgl::AttribBaseType::Float))
         return;
 
     gl->fUniform1f(loc->mLoc, a1);
 }
 
 void
 WebGLContext::Uniform2f(WebGLUniformLocation* loc, GLfloat a1, GLfloat a2)
 {
     const FuncScope funcScope(*this, "uniform2f");
-    if (!ValidateUniformSetter(loc, 2, LOCAL_GL_FLOAT))
+    if (!ValidateUniformSetter(loc, 2, webgl::AttribBaseType::Float))
         return;
 
     gl->fUniform2f(loc->mLoc, a1, a2);
 }
 
 void
 WebGLContext::Uniform3f(WebGLUniformLocation* loc, GLfloat a1, GLfloat a2,
                         GLfloat a3)
 {
     const FuncScope funcScope(*this, "uniform3f");
-    if (!ValidateUniformSetter(loc, 3, LOCAL_GL_FLOAT))
+    if (!ValidateUniformSetter(loc, 3, webgl::AttribBaseType::Float))
         return;
 
     gl->fUniform3f(loc->mLoc, a1, a2, a3);
 }
 
 void
 WebGLContext::Uniform4f(WebGLUniformLocation* loc, GLfloat a1, GLfloat a2,
                         GLfloat a3, GLfloat a4)
 {
     const FuncScope funcScope(*this, "uniform4f");
-    if (!ValidateUniformSetter(loc, 4, LOCAL_GL_FLOAT))
+    if (!ValidateUniformSetter(loc, 4, webgl::AttribBaseType::Float))
         return;
 
     gl->fUniform4f(loc->mLoc, a1, a2, a3, a4);
 }
 
 ////////////////////////////////////////
 // Array
 
@@ -2059,17 +2057,17 @@ WebGLContext::UniformNiv(const char* fun
     if (!ValidateArrOffsetAndCount(this, arr.elemCount, elemOffset,
                                    elemCountOverride, &elemCount))
     {
         return;
     }
     const auto elemBytes = arr.elemBytes + elemOffset;
 
     uint32_t numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, N, LOCAL_GL_INT, elemCount,
+    if (!ValidateUniformArraySetter(loc, N, webgl::AttribBaseType::Int, elemCount,
                                     &numElementsToUpload))
     {
         return;
     }
 
     bool error;
     const ValidateIfSampler samplerValidator(this, loc, numElementsToUpload,
                                              elemBytes, &error);
@@ -2098,17 +2096,17 @@ WebGLContext::UniformNuiv(const char* fu
     if (!ValidateArrOffsetAndCount(this, arr.elemCount, elemOffset,
                                    elemCountOverride, &elemCount))
     {
         return;
     }
     const auto elemBytes = arr.elemBytes + elemOffset;
 
     uint32_t numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, N, LOCAL_GL_UNSIGNED_INT, elemCount,
+    if (!ValidateUniformArraySetter(loc, N, webgl::AttribBaseType::UInt, elemCount,
                                     &numElementsToUpload))
     {
         return;
     }
     MOZ_ASSERT(!loc->mInfo->mSamplerTexList, "Should not be a sampler.");
 
     static const decltype(&gl::GLContext::fUniform1uiv) kFuncList[] = {
         &gl::GLContext::fUniform1uiv,
@@ -2132,17 +2130,17 @@ WebGLContext::UniformNfv(const char* fun
     if (!ValidateArrOffsetAndCount(this, arr.elemCount, elemOffset,
                                    elemCountOverride, &elemCount))
     {
         return;
     }
     const auto elemBytes = arr.elemBytes + elemOffset;
 
     uint32_t numElementsToUpload;
-    if (!ValidateUniformArraySetter(loc, N, LOCAL_GL_FLOAT, elemCount,
+    if (!ValidateUniformArraySetter(loc, N, webgl::AttribBaseType::Float, elemCount,
                                     &numElementsToUpload))
     {
         return;
     }
     MOZ_ASSERT(!loc->mInfo->mSamplerTexList, "Should not be a sampler.");
 
     static const decltype(&gl::GLContext::fUniform1fv) kFuncList[] = {
         &gl::GLContext::fUniform1fv,
@@ -2179,18 +2177,18 @@ WebGLContext::UniformMatrixAxBfv(const c
     if (!ValidateArrOffsetAndCount(this, arr.elemCount, elemOffset,
                                    elemCountOverride, &elemCount))
     {
         return;
     }
     const auto elemBytes = arr.elemBytes + elemOffset;
 
     uint32_t numMatsToUpload;
-    if (!ValidateUniformMatrixArraySetter(loc, A, B, LOCAL_GL_FLOAT, elemCount,
-                                          transpose, &numMatsToUpload))
+    if (!ValidateUniformMatrixArraySetter(loc, A, B, webgl::AttribBaseType::Float,
+                                          elemCount, transpose, &numMatsToUpload))
     {
         return;
     }
     MOZ_ASSERT(!loc->mInfo->mSamplerTexList, "Should not be a sampler.");
 
     ////
 
     bool uploadTranspose = transpose;
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -50,26 +50,24 @@ WebGLContext::SetEnabled(const char* con
     }
 }
 
 bool
 WebGLContext::GetStencilBits(GLint* const out_stencilBits) const
 {
     *out_stencilBits = 0;
     if (mBoundDrawFramebuffer) {
-        if (mBoundDrawFramebuffer->StencilAttachment().IsDefined() &&
-            mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined())
-        {
+        if (!mBoundDrawFramebuffer->IsCheckFramebufferStatusComplete()) {
             // Error, we don't know which stencil buffer's bits to use
             ErrorInvalidFramebufferOperation("getParameter: framebuffer has two stencil buffers bound");
             return false;
         }
 
-        if (mBoundDrawFramebuffer->StencilAttachment().IsDefined() ||
-            mBoundDrawFramebuffer->DepthStencilAttachment().IsDefined())
+        if (mBoundDrawFramebuffer->StencilAttachment().HasAttachment() ||
+            mBoundDrawFramebuffer->DepthStencilAttachment().HasAttachment())
         {
             *out_stencilBits = 8;
         }
     } else if (mOptions.stencil) {
         *out_stencilBits = 8;
     }
 
     return true;
@@ -313,33 +311,42 @@ WebGLContext::GetParameter(JSContext* cx
 
         case LOCAL_GL_RED_BITS:
         case LOCAL_GL_GREEN_BITS:
         case LOCAL_GL_BLUE_BITS:
         case LOCAL_GL_ALPHA_BITS:
         case LOCAL_GL_DEPTH_BITS:
         case LOCAL_GL_STENCIL_BITS: {
             const auto format = [&]() -> const webgl::FormatInfo* {
-                if (mBoundDrawFramebuffer) {
-                    const auto& fb = *mBoundDrawFramebuffer;
+                const auto& fb = mBoundDrawFramebuffer;
+                if (fb) {
+                    if (!fb->IsCheckFramebufferStatusComplete())
+                        return nullptr;
+
                     const auto& attachment = [&]() -> const auto& {
                         switch (pname) {
                         case LOCAL_GL_DEPTH_BITS:
-                            return fb.AnyDepthAttachment();
+                            if (fb->DepthStencilAttachment().HasAttachment())
+                                return fb->DepthStencilAttachment();
+                            return fb->DepthAttachment();
 
                         case LOCAL_GL_STENCIL_BITS:
-                            return fb.AnyStencilAttachment();
+                            if (fb->DepthStencilAttachment().HasAttachment())
+                                return fb->DepthStencilAttachment();
+                            return fb->StencilAttachment();
 
                         default:
-                            return fb.ColorAttachment0();
+                            return fb->ColorAttachment0();
                         }
                     }();
-                    if (!attachment.HasImage())
+
+                    const auto imageInfo = attachment.GetImageInfo();
+                    if (!imageInfo)
                         return nullptr;
-                    return attachment.Format()->format;
+                    return imageInfo->mFormat->format;
                 }
 
                 auto effFormat = webgl::EffectiveFormat::RGB8;
                 switch (pname) {
                 case LOCAL_GL_DEPTH_BITS:
                     if (mOptions.depth) {
                         effFormat = webgl::EffectiveFormat::DEPTH24_STENCIL8;
                     }
--- a/dom/canvas/WebGLContextTextures.cpp
+++ b/dom/canvas/WebGLContextTextures.cpp
@@ -182,29 +182,16 @@ WebGLContext::IsTexParamValid(GLenum pna
     case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
         return IsExtensionEnabled(WebGLExtensionID::EXT_texture_filter_anisotropic);
 
     default:
         return false;
     }
 }
 
-void
-WebGLContext::InvalidateResolveCacheForTextureWithTexUnit(const GLuint texUnit)
-{
-    if (mBound2DTextures[texUnit])
-        mBound2DTextures[texUnit]->InvalidateResolveCache();
-    if (mBoundCubeMapTextures[texUnit])
-        mBoundCubeMapTextures[texUnit]->InvalidateResolveCache();
-    if (mBound3DTextures[texUnit])
-        mBound3DTextures[texUnit]->InvalidateResolveCache();
-    if (mBound2DArrayTextures[texUnit])
-        mBound2DArrayTextures[texUnit]->InvalidateResolveCache();
-}
-
 //////////////////////////////////////////////////////////////////////////////////////////
 // GL calls
 
 void
 WebGLContext::BindTexture(GLenum rawTarget, WebGLTexture* newTex)
 {
     const FuncScope funcScope(*this, "bindTexture");
     if (IsContextLost())
@@ -258,17 +245,17 @@ WebGLContext::GenerateMipmap(GLenum rawT
     const FuncScope funcScope(*this, "generateMipmap");
     const uint8_t funcDims = 0;
 
     TexTarget texTarget;
     WebGLTexture* tex;
     if (!ValidateTexTarget(this, funcDims, rawTexTarget, &texTarget, &tex))
         return;
 
-    tex->GenerateMipmap(texTarget);
+    tex->GenerateMipmap();
 }
 
 JS::Value
 WebGLContext::GetTexParameter(GLenum rawTexTarget, GLenum pname)
 {
     const FuncScope funcScope(*this, "getTexParameter");
     const uint8_t funcDims = 0;
 
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -178,17 +178,17 @@ WebGLContext::ValidateFaceEnum(const GLe
 
     default:
         ErrorInvalidEnumInfo("face", face);
         return false;
     }
 }
 
 bool
-WebGLContext::ValidateUniformLocation(WebGLUniformLocation* loc)
+WebGLContext::ValidateUniformLocation(const WebGLUniformLocation* const loc)
 {
     /* GLES 2.0.25, p38:
      *   If the value of location is -1, the Uniform* commands will silently
      *   ignore the data passed in, and the current uniform values will not be
      *   changed.
      */
     if (!loc)
         return false;
@@ -214,36 +214,37 @@ WebGLContext::ValidateAttribArraySetter(
         ErrorInvalidValue("Array must have >= %d elements.", setterElemSize);
         return false;
     }
 
     return true;
 }
 
 bool
-WebGLContext::ValidateUniformSetter(WebGLUniformLocation* loc,
-                                    uint8_t setterElemSize, GLenum setterType)
+WebGLContext::ValidateUniformSetter(const WebGLUniformLocation* const loc,
+                                    const uint8_t setterElemSize,
+                                    const webgl::AttribBaseType setterType)
 {
     if (IsContextLost())
         return false;
 
     if (!ValidateUniformLocation(loc))
         return false;
 
     if (!loc->ValidateSizeAndType(setterElemSize, setterType))
         return false;
 
     return true;
 }
 
 bool
-WebGLContext::ValidateUniformArraySetter(WebGLUniformLocation* loc,
-                                         uint8_t setterElemSize,
-                                         GLenum setterType,
-                                         uint32_t setterArraySize,
+WebGLContext::ValidateUniformArraySetter(const WebGLUniformLocation* const loc,
+                                         const uint8_t setterElemSize,
+                                         const webgl::AttribBaseType setterType,
+                                         const uint32_t setterArraySize,
                                          uint32_t* const out_numElementsToUpload)
 {
     if (IsContextLost())
         return false;
 
     if (!ValidateUniformLocation(loc))
         return false;
 
@@ -258,22 +259,22 @@ WebGLContext::ValidateUniformArraySetter
     const uint32_t uniformElemCount = elemCount - loc->mArrayIndex;
 
     *out_numElementsToUpload = std::min(uniformElemCount,
                                         setterArraySize / setterElemSize);
     return true;
 }
 
 bool
-WebGLContext::ValidateUniformMatrixArraySetter(WebGLUniformLocation* loc,
-                                               uint8_t setterCols,
-                                               uint8_t setterRows,
-                                               GLenum setterType,
-                                               uint32_t setterArraySize,
-                                               bool setterTranspose,
+WebGLContext::ValidateUniformMatrixArraySetter(const WebGLUniformLocation* const loc,
+                                               const uint8_t setterCols,
+                                               const uint8_t setterRows,
+                                               const webgl::AttribBaseType setterType,
+                                               const uint32_t setterArraySize,
+                                               const bool setterTranspose,
                                                uint32_t* const out_numElementsToUpload)
 {
     const uint8_t setterElemSize = setterCols * setterRows;
 
     if (IsContextLost())
         return false;
 
     if (!ValidateUniformLocation(loc))
@@ -657,18 +658,18 @@ WebGLContext::InitAndValidateGL(FailureR
     mPixelStore_UnpackAlignment = 4;
     mPixelStore_PackRowLength = 0;
     mPixelStore_PackSkipRows = 0;
     mPixelStore_PackSkipPixels = 0;
     mPixelStore_PackAlignment = 4;
 
     mPrimRestartTypeBytes = 0;
 
-    mGenericVertexAttribTypes.reset(new GLenum[mGLMaxVertexAttribs]);
-    std::fill_n(mGenericVertexAttribTypes.get(), mGLMaxVertexAttribs, LOCAL_GL_FLOAT);
+    mGenericVertexAttribTypes.assign(mGLMaxVertexAttribs,
+                                     webgl::AttribBaseType::Float);
     mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     static const float kDefaultGenericVertexAttribData[4] = { 0, 0, 0, 1 };
     memcpy(mGenericVertexAttrib0Data, kDefaultGenericVertexAttribData,
            sizeof(mGenericVertexAttrib0Data));
 
     mFakeVertexAttrib0BufferObject = 0;
 
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -93,17 +93,17 @@ WebGLContext::VertexAttrib4f(GLuint inde
     ////
 
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fVertexAttrib4f(index, x, y, z, w);
     }
 
     ////
 
-    mGenericVertexAttribTypes[index] = LOCAL_GL_FLOAT;
+    mGenericVertexAttribTypes[index] = webgl::AttribBaseType::Float;
     mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     if (!index) {
         const float data[4] = { x, y, z, w };
         memcpy(mGenericVertexAttrib0Data, data, sizeof(data));
     }
 }
 
@@ -120,17 +120,17 @@ WebGL2Context::VertexAttribI4i(GLuint in
     ////
 
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fVertexAttribI4i(index, x, y, z, w);
     }
 
     ////
 
-    mGenericVertexAttribTypes[index] = LOCAL_GL_INT;
+    mGenericVertexAttribTypes[index] = webgl::AttribBaseType::Int;
     mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     if (!index) {
         const int32_t data[4] = { x, y, z, w };
         memcpy(mGenericVertexAttrib0Data, data, sizeof(data));
     }
 }
 
@@ -147,17 +147,17 @@ WebGL2Context::VertexAttribI4ui(GLuint i
     ////
 
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fVertexAttribI4ui(index, x, y, z, w);
     }
 
     ////
 
-    mGenericVertexAttribTypes[index] = LOCAL_GL_UNSIGNED_INT;
+    mGenericVertexAttribTypes[index] = webgl::AttribBaseType::UInt;
     mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     if (!index) {
         const uint32_t data[4] = { x, y, z, w };
         memcpy(mGenericVertexAttrib0Data, data, sizeof(data));
     }
 }
 
@@ -238,27 +238,30 @@ WebGLContext::GetVertexAttrib(JSContext*
             return JS::Int32Value(mBoundVertexArray->mAttribs[index].mDivisor);
         }
         break;
 
     case LOCAL_GL_CURRENT_VERTEX_ATTRIB:
         {
             JS::RootedObject obj(cx);
             switch (mGenericVertexAttribTypes[index]) {
-            case LOCAL_GL_FLOAT:
+            case webgl::AttribBaseType::Float:
                 obj = GetVertexAttribFloat32Array(cx, index);
                 break;
 
-            case LOCAL_GL_INT:
+            case webgl::AttribBaseType::Int:
                 obj =  GetVertexAttribInt32Array(cx, index);
                 break;
 
-            case LOCAL_GL_UNSIGNED_INT:
+            case webgl::AttribBaseType::UInt:
                 obj = GetVertexAttribUint32Array(cx, index);
                 break;
+
+            case webgl::AttribBaseType::Bool:
+                MOZ_CRASH("impossible");
             }
 
             if (!obj) {
                 rv.Throw(NS_ERROR_OUT_OF_MEMORY);
                 return JS::NullValue();
             }
             return JS::ObjectValue(*obj);
         }
--- a/dom/canvas/WebGLFormats.cpp
+++ b/dom/canvas/WebGLFormats.cpp
@@ -9,16 +9,49 @@
 #include "GLContext.h"
 #include "GLDefs.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/StaticMutex.h"
 
 namespace mozilla {
 namespace webgl {
 
+static TextureBaseType
+ToBaseType(const ComponentType type)
+{
+    switch (type) {
+    case ComponentType::Int:
+        return TextureBaseType::Int;
+    case ComponentType::UInt:
+        return TextureBaseType::UInt;
+    case ComponentType::NormInt:
+    case ComponentType::NormUInt:
+    case ComponentType::Float:
+    //case ComponentType::Depth:
+        return TextureBaseType::Float;
+    }
+    MOZ_CRASH("pacify gcc6 warning");
+}
+
+const char*
+ToString(const TextureBaseType x)
+{
+    switch (x) {
+    case webgl::TextureBaseType::Float:
+        return "FLOAT";
+    case webgl::TextureBaseType::Int:
+        return "INT";
+    case webgl::TextureBaseType::UInt:
+        return "UINT";
+    }
+    MOZ_CRASH("pacify gcc6 warning");
+}
+
+// -
+
 template<typename K, typename V, typename K2, typename V2>
 static inline void
 AlwaysInsert(std::map<K,V>& dest, const K2& key, const V2& val)
 {
     auto res = dest.insert({ key, val });
     bool didInsert = res.second;
     MOZ_ALWAYS_TRUE(didInsert);
 }
@@ -226,17 +259,18 @@ AddFormatInfo(EffectiveFormat format, co
         MOZ_ASSERT(totalBits);
         MOZ_ASSERT(!bytesPerPixel);
     } else {
         MOZ_ASSERT(totalBits == bytesPerPixel*8);
     }
 #endif
 
     const FormatInfo info = { format, name, sizedFormat, unsizedFormat, componentType,
-                              isSRGB, compressedFormatInfo, bytesPerPixel, r,g,b,a,d,s };
+                              ToBaseType(componentType), isSRGB, compressedFormatInfo,
+                              bytesPerPixel, r,g,b,a,d,s };
     AlwaysInsert(gFormatInfoMap, format, info);
 }
 
 static void
 InitFormatInfo()
 {
 #define FOO(x) EffectiveFormat::x, #x, LOCAL_GL_ ## x
 
@@ -297,21 +331,22 @@ InitFormatInfo()
     AddFormatInfo(FOO(RGBA16UI      ),  8, 16,16,16,16,  0,0, UnsizedFormat::RGBA, false, ComponentType::UInt    );
     AddFormatInfo(FOO(RGBA32I       ), 16, 32,32,32,32,  0,0, UnsizedFormat::RGBA, false, ComponentType::Int     );
     AddFormatInfo(FOO(RGBA32UI      ), 16, 32,32,32,32,  0,0, UnsizedFormat::RGBA, false, ComponentType::UInt    );
 
     // GLES 3.0.4, p133, table 3.14
     AddFormatInfo(FOO(DEPTH_COMPONENT16 ), 2, 0,0,0,0, 16,0, UnsizedFormat::D , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(DEPTH_COMPONENT24 ), 3, 0,0,0,0, 24,0, UnsizedFormat::D , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(DEPTH_COMPONENT32F), 4, 0,0,0,0, 32,0, UnsizedFormat::D , false, ComponentType::Float);
-    AddFormatInfo(FOO(DEPTH24_STENCIL8  ), 4, 0,0,0,0, 24,8, UnsizedFormat::DEPTH_STENCIL, false, ComponentType::Special);
-    AddFormatInfo(FOO(DEPTH32F_STENCIL8 ), 5, 0,0,0,0, 32,8, UnsizedFormat::DEPTH_STENCIL, false, ComponentType::Special);
+    // DEPTH_STENCIL types are sampled as their depth component.
+    AddFormatInfo(FOO(DEPTH24_STENCIL8  ), 4, 0,0,0,0, 24,8, UnsizedFormat::DEPTH_STENCIL, false, ComponentType::NormUInt);
+    AddFormatInfo(FOO(DEPTH32F_STENCIL8 ), 5, 0,0,0,0, 32,8, UnsizedFormat::DEPTH_STENCIL, false, ComponentType::Float);
 
     // GLES 3.0.4, p205-206, "Required Renderbuffer Formats"
-    AddFormatInfo(FOO(STENCIL_INDEX8), 1, 0,0,0,0, 0,8, UnsizedFormat::S, false, ComponentType::UInt);
+    AddFormatInfo(FOO(STENCIL_INDEX8), 1, 0,0,0,0, 0,8, UnsizedFormat::S, false, ComponentType::Int);
 
     // GLES 3.0.4, p147, table 3.19
     // GLES 3.0.4  p286+  $C.1 "ETC Compressed Texture Image Formats"
     AddFormatInfo(FOO(COMPRESSED_RGB8_ETC2                     ), 0, 1,1,1,0, 0,0, UnsizedFormat::RGB , false, ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_SRGB8_ETC2                    ), 0, 1,1,1,0, 0,0, UnsizedFormat::RGB , true , ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_RGBA8_ETC2_EAC                ), 0, 1,1,1,1, 0,0, UnsizedFormat::RGBA, false, ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_SRGB8_ALPHA8_ETC2_EAC         ), 0, 1,1,1,1, 0,0, UnsizedFormat::RGBA, true , ComponentType::NormUInt);
     AddFormatInfo(FOO(COMPRESSED_R11_EAC                       ), 0, 1,0,0,0, 0,0, UnsizedFormat::R   , false, ComponentType::NormUInt);
--- a/dom/canvas/WebGLFormats.h
+++ b/dom/canvas/WebGLFormats.h
@@ -202,25 +202,31 @@ enum class UnsizedFormat : uint8_t {
     A,
     D,
     S,
     DEPTH_STENCIL, // `DS` is a macro on Solaris. (regset.h)
 };
 
 // GLES 3.0.4 p114 Table 3.4, p240
 enum class ComponentType : uint8_t {
-    None,
     Int,          // RGBA32I
-    UInt,         // RGBA32UI, STENCIL_INDEX8
+    UInt,         // RGBA32UI
     NormInt,      // RGBA8_SNORM
-    NormUInt,     // RGBA8, DEPTH_COMPONENT16
+    NormUInt,     // RGBA8
     Float,        // RGBA32F
-    Special,      // DEPTH24_STENCIL8
 };
 
+enum class TextureBaseType : uint8_t {
+    Int = uint8_t(ComponentType::Int),
+    UInt = uint8_t(ComponentType::UInt),
+    Float = uint8_t(ComponentType::Float), // Also includes NormU?Int and Depth
+};
+
+const char* ToString(TextureBaseType);
+
 enum class CompressionFamily : uint8_t {
     ASTC,
     BPTC,
     ES3, // ETC2 or EAC
     ETC1,
     PVRTC,
     RGTC,
     S3TC,
@@ -239,16 +245,17 @@ struct CompressedFormatInfo
 
 struct FormatInfo
 {
     const EffectiveFormat effectiveFormat;
     const char* const name;
     const GLenum sizedFormat;
     const UnsizedFormat unsizedFormat;
     const ComponentType componentType;
+    const TextureBaseType baseType;
     const bool isSRGB;
 
     const CompressedFormatInfo* const compression;
 
     const uint8_t estimatedBytesPerPixel; // 0 iff bool(compression).
 
     // In bits. Iff bool(compression), active channels are 1.
     const uint8_t r;
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -16,287 +16,148 @@
 #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)
-{ }
+static bool
+ShouldDeferAttachment(const WebGLContext* const webgl, const GLenum attachPoint)
+{
+    if (webgl->IsWebGL2())
+        return false;
 
-WebGLFBAttachPoint::WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint)
-    : mFB(fb)
-    , mAttachmentPoint(attachmentPoint)
-    , mTexImageTarget(LOCAL_GL_NONE)
-    , mTexImageLayer(0)
-    , mTexImageLevel(0)
+    switch (attachPoint) {
+    case LOCAL_GL_DEPTH_ATTACHMENT:
+    case LOCAL_GL_STENCIL_ATTACHMENT:
+    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
+        return true;
+    default:
+        return false;
+    }
+}
+
+WebGLFBAttachPoint::WebGLFBAttachPoint(const WebGLContext* const webgl,
+                                       const GLenum attachmentPoint)
+    : mAttachmentPoint(attachmentPoint)
+    , mDeferAttachment(ShouldDeferAttachment(webgl, mAttachmentPoint))
 { }
 
 WebGLFBAttachPoint::~WebGLFBAttachPoint()
 {
-    MOZ_ASSERT(mFB, "Should have been Init'd.");
     MOZ_ASSERT(!mRenderbufferPtr);
     MOZ_ASSERT(!mTexturePtr);
 }
 
-void
-WebGLFBAttachPoint::Unlink()
-{
-    Clear();
-}
-
 bool
 WebGLFBAttachPoint::IsDeleteRequested() const
 {
     return Texture() ? Texture()->IsDeleteRequested()
          : Renderbuffer() ? Renderbuffer()->IsDeleteRequested()
          : false;
 }
 
-bool
-WebGLFBAttachPoint::IsDefined() const
-{
-    /*
-    return (Renderbuffer() && Renderbuffer()->IsDefined()) ||
-           (Texture() && Texture()->ImageInfoAt(mTexImageTarget,
-                                                mTexImageLevel).IsDefined());
-    */
-    return (Renderbuffer() || Texture());
-}
-
-const webgl::FormatUsageInfo*
-WebGLFBAttachPoint::Format() const
+void
+WebGLFBAttachPoint::Clear()
 {
-    MOZ_ASSERT(IsDefined());
-
-    if (Texture())
-        return Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).mFormat;
-
-    if (Renderbuffer())
-        return Renderbuffer()->Format();
-
-    return nullptr;
-}
-
-uint32_t
-WebGLFBAttachPoint::Samples() const
-{
-    MOZ_ASSERT(IsDefined());
-
-    if (mRenderbufferPtr)
-        return mRenderbufferPtr->Samples();
-
-    return 0;
-}
-
-bool
-WebGLFBAttachPoint::HasAlpha() const
-{
-    return Format()->format->a;
-}
-
-bool
-WebGLFBAttachPoint::IsReadableFloat() const
-{
-    auto formatUsage = Format();
-    MOZ_ASSERT(formatUsage);
-
-    auto format = formatUsage->format;
-    if (!format->IsColorFormat())
-        return false;
-
-    return format->componentType == webgl::ComponentType::Float;
+    mRenderbufferPtr = nullptr;
+    mTexturePtr = nullptr;
+    mTexImageTarget = 0;
+    mTexImageLevel = 0;
+    mTexImageLayer = 0;
 }
 
 void
-WebGLFBAttachPoint::Clear()
-{
-    if (mRenderbufferPtr) {
-        MOZ_ASSERT(!mTexturePtr);
-        mRenderbufferPtr->UnmarkAttachment(*this);
-    } else if (mTexturePtr) {
-        mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).RemoveAttachPoint(this);
-    }
-
-    mTexturePtr = nullptr;
-    mRenderbufferPtr = nullptr;
-
-    OnBackingStoreRespecified();
-}
-
-void
-WebGLFBAttachPoint::SetTexImage(WebGLTexture* tex,
+WebGLFBAttachPoint::SetTexImage(gl::GLContext* const gl, WebGLTexture* const tex,
                                 TexImageTarget target, GLint level, GLint layer)
 {
     Clear();
 
     mTexturePtr = tex;
     mTexImageTarget = target;
     mTexImageLevel = level;
     mTexImageLayer = layer;
 
-    if (mTexturePtr) {
-        mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel).AddAttachPoint(this);
+    if (!mDeferAttachment) {
+        DoAttachment(gl);
     }
 }
 
 void
-WebGLFBAttachPoint::SetRenderbuffer(WebGLRenderbuffer* rb)
+WebGLFBAttachPoint::SetRenderbuffer(gl::GLContext* const gl, WebGLRenderbuffer* const rb)
 {
     Clear();
 
     mRenderbufferPtr = rb;
 
-    if (mRenderbufferPtr) {
-        mRenderbufferPtr->MarkAttachment(*this);
+    if (!mDeferAttachment) {
+        DoAttachment(gl);
     }
 }
 
-bool
-WebGLFBAttachPoint::HasUninitializedImageData() const
-{
-    if (!HasImage())
-        return false;
-
-    if (mRenderbufferPtr)
-        return mRenderbufferPtr->HasUninitializedImageData();
-
-    MOZ_ASSERT(mTexturePtr);
-
-    auto& imageInfo = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel);
-    MOZ_ASSERT(imageInfo.IsDefined());
-
-    return !imageInfo.IsDataInitialized();
-}
-
-void
-WebGLFBAttachPoint::SetImageDataStatus(WebGLImageDataStatus newStatus) const
-{
-    if (!HasImage())
-        return;
-
-    if (mRenderbufferPtr) {
-        mRenderbufferPtr->mImageDataStatus = newStatus;
-        return;
-    }
-
-    MOZ_ASSERT(mTexturePtr);
-
-    auto& imageInfo = mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel);
-    MOZ_ASSERT(imageInfo.IsDefined());
-
-    const bool isDataInitialized = (newStatus == WebGLImageDataStatus::InitializedImageData);
-    imageInfo.SetIsDataInitialized(isDataInitialized, mTexturePtr);
-}
-
-bool
-WebGLFBAttachPoint::HasImage() const
+const webgl::ImageInfo*
+WebGLFBAttachPoint::GetImageInfo() const
 {
-    if (Texture() && Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).IsDefined())
-        return true;
-
-    if (Renderbuffer() && Renderbuffer()->IsDefined())
-        return true;
-
-    return false;
-}
-
-void
-WebGLFBAttachPoint::Size(uint32_t* const out_width, uint32_t* const out_height) const
-{
-    MOZ_ASSERT(HasImage());
-
-    if (Renderbuffer()) {
-        *out_width = Renderbuffer()->Width();
-        *out_height = Renderbuffer()->Height();
-        return;
-    }
-
-    MOZ_ASSERT(Texture());
-    MOZ_ASSERT(Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).IsDefined());
-    const auto& imageInfo = Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
-
-    *out_width = imageInfo.mWidth;
-    *out_height = imageInfo.mHeight;
-}
-
-void
-WebGLFBAttachPoint::OnBackingStoreRespecified() const
-{
-    mFB->InvalidateFramebufferStatus();
-}
-
-void
-WebGLFBAttachPoint::AttachmentName(nsCString* out) const
-{
-    switch (mAttachmentPoint) {
-    case LOCAL_GL_DEPTH_ATTACHMENT:
-        out->AssignLiteral("DEPTH_ATTACHMENT");
-        return;
-
-    case LOCAL_GL_STENCIL_ATTACHMENT:
-        out->AssignLiteral("STENCIL_ATTACHMENT");
-        return;
-
-    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
-        out->AssignLiteral("DEPTH_STENCIL_ATTACHMENT");
-        return;
-
-    default:
-        MOZ_ASSERT(mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0);
-        out->AssignLiteral("COLOR_ATTACHMENT");
-        const uint32_t n = mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
-        out->AppendInt(n);
-        return;
-    }
+    if (mTexturePtr)
+        return &mTexturePtr->ImageInfoAt(mTexImageTarget, mTexImageLevel);
+    if (mRenderbufferPtr)
+        return &mRenderbufferPtr->ImageInfo();
+    return nullptr;
 }
 
 bool
 WebGLFBAttachPoint::IsComplete(WebGLContext* webgl, nsCString* const out_info) const
 {
-    MOZ_ASSERT(IsDefined());
+    MOZ_ASSERT(HasAttachment());
 
-    if (!HasImage()) {
-        AttachmentName(out_info);
-        out_info->AppendLiteral("'s image is not defined");
+    const auto fnWriteErrorInfo = [&](const char* const text) {
+        WebGLContext::EnumName(mAttachmentPoint, out_info);
+        out_info->AppendASCII(text);
+    };
+
+    const auto& imageInfo = *GetImageInfo();
+    if (!imageInfo.mWidth || !imageInfo.mHeight) {
+        fnWriteErrorInfo("Attachment has no width or height.");
         return false;
     }
+    MOZ_ASSERT(imageInfo.IsDefined());
 
-    uint32_t width;
-    uint32_t height;
-    Size(&width, &height);
-    if (!width || !height) {
-        AttachmentName(out_info);
-        out_info->AppendLiteral(" has no width or height");
-        return false;
+    const auto& tex = Texture();
+    if (tex) {
+        // ES 3.0 spec, pg 213 has giant blocks of text that bake down to requiring that
+        // attached tex images are within the valid mip-levels of the texture.
+        // While it draws distinction to only test non-immutable textures, that's because
+        // immutable textures are *always* texture-complete.
+        // We need to check immutable textures though, because checking completeness is
+        // also when we zero invalidated/no-data tex images.
+        const bool complete = [&]() {
+            const auto texCompleteness = tex->CalcCompletenessInfo();
+            if (!texCompleteness) // OOM
+                return false;
+            if (!texCompleteness->levels)
+                return false;
+
+            const auto baseLevel = tex->BaseMipmapLevel();
+            const auto maxLevel = baseLevel + texCompleteness->levels - 1;
+            return baseLevel <= mTexImageLevel && mTexImageLevel <= maxLevel;
+        }();
+        if (!complete) {
+            fnWriteErrorInfo("Attached texture is not texture-complete.");
+            return false;
+        }
     }
 
-    const auto formatUsage = Format();
+    const auto& formatUsage = imageInfo.mFormat;
     if (!formatUsage->IsRenderable()) {
-        nsAutoCString attachName;
-        AttachmentName(&attachName);
-
-        *out_info = nsPrintfCString("%s has an effective format of %s, which is not"
-                                    " renderable",
-                                    attachName.BeginReading(), formatUsage->format->name);
-        return false;
-    }
-
-    if (webgl->IsWebGL2() && Texture() &&
-        Texture()->IsCubeMap() && !Texture()->IsCubeComplete())
-    {
-        AttachmentName(out_info);
-        out_info->AppendLiteral(" is not cube complete");
+        const auto info = nsPrintfCString("Attachment has an effective format of %s,"
+                                          " which is not renderable.",
+                                          formatUsage->format->name);
+        fnWriteErrorInfo(info.BeginReading());
         return false;
     }
 
     const auto format = formatUsage->format;
 
     bool hasRequiredBits;
 
     switch (mAttachmentPoint) {
@@ -315,18 +176,18 @@ WebGLFBAttachPoint::IsComplete(WebGLCont
 
     default:
         MOZ_ASSERT(mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0);
         hasRequiredBits = format->IsColorFormat();
         break;
     }
 
     if (!hasRequiredBits) {
-        AttachmentName(out_info);
-        out_info->AppendLiteral("'s format is missing required color/depth/stencil bits");
+        fnWriteErrorInfo("Attachment's format is missing required color/depth/stencil"
+                         " bits.");
         return false;
     }
 
     if (!webgl->IsWebGL2()) {
         bool hasSurplusPlanes = false;
 
         switch (mAttachmentPoint) {
         case LOCAL_GL_DEPTH_ATTACHMENT:
@@ -334,84 +195,77 @@ WebGLFBAttachPoint::IsComplete(WebGLCont
             break;
 
         case LOCAL_GL_STENCIL_ATTACHMENT:
             hasSurplusPlanes = format->d;
             break;
         }
 
         if (hasSurplusPlanes) {
-            AttachmentName(out_info);
-            out_info->AppendLiteral("'s format has depth or stencil bits when it"
-                                    " shouldn't");
+            fnWriteErrorInfo("Attachment has depth or stencil bits when it shouldn't.");
             return false;
         }
     }
 
     return true;
 }
 
 void
-WebGLFBAttachPoint::Resolve(gl::GLContext* gl) const
+WebGLFBAttachPoint::DoAttachment(gl::GLContext* const gl) const
 {
-    if (!HasImage())
-        return;
-
     if (Renderbuffer()) {
-        Renderbuffer()->DoFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint);
+        Renderbuffer()->DoFramebufferRenderbuffer(mAttachmentPoint);
         return;
     }
-    MOZ_ASSERT(Texture());
 
-    MOZ_ASSERT(gl == Texture()->mContext->GL());
+    if (!Texture()) {
+        MOZ_ASSERT(mAttachmentPoint != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
+        // WebGL 2 doesn't have a real attachment for this, and WebGL 1 is defered and
+        // only DoAttachment if HasAttachment.
+
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint,
+                                     LOCAL_GL_RENDERBUFFER, 0);
+        return;
+    }
 
     const auto& texName = Texture()->mGLName;
 
-    ////
-
-    const auto fnAttach2D = [&](GLenum attachmentPoint) {
-        gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachmentPoint,
-                                  mTexImageTarget.get(), texName, mTexImageLevel);
-    };
-
-    ////
-
     switch (mTexImageTarget.get()) {
     case LOCAL_GL_TEXTURE_2D:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X:
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
     case LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
         if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            fnAttach2D(LOCAL_GL_DEPTH_ATTACHMENT);
-            fnAttach2D(LOCAL_GL_STENCIL_ATTACHMENT);
+            gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
+                                      mTexImageTarget.get(), texName, mTexImageLevel);
+            gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
+                                      mTexImageTarget.get(), texName, mTexImageLevel);
         } else {
-            fnAttach2D(mAttachmentPoint);
+            gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, 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(LOCAL_GL_FRAMEBUFFER, mAttachmentPoint, texName,
                                      mTexImageLevel, mTexImageLayer);
         break;
     }
 }
 
 JS::Value
 WebGLFBAttachPoint::GetParameter(WebGLContext* webgl, JSContext* cx,
                                  GLenum target, GLenum attachment, GLenum pname,
                                  ErrorResult* const out_error) const
 {
-    const bool hasAttachment = (mTexturePtr || mRenderbufferPtr);
-    if (!hasAttachment) {
+    if (!HasAttachment()) {
         // Divergent between GLES 3 and 2.
 
         // GLES 2.0.25 p127:
         // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, then querying any
         //  other pname will generate INVALID_ENUM."
 
         // GLES 3.0.4 p240:
         // "If the value of FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE is NONE, no framebuffer is
@@ -504,17 +358,18 @@ WebGLFBAttachPoint::GetParameter(WebGLCo
         break;
     }
 
     if (!isPNameValid) {
         webgl->ErrorInvalidEnum("Invalid pname: 0x%04x", pname);
         return JS::NullValue();
     }
 
-    const auto usage = Format();
+    const auto& imageInfo = *GetImageInfo();
+    const auto& usage = imageInfo.mFormat;
     if (!usage) {
         if (pname == LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING)
             return JS::NumberValue(LOCAL_GL_LINEAR);
 
         return JS::NullValue();
     }
 
     auto format = usage->format;
@@ -543,19 +398,17 @@ WebGLFBAttachPoint::GetParameter(WebGLCo
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
         ret = (format->isSRGB ? LOCAL_GL_SRGB
                               : LOCAL_GL_LINEAR);
         break;
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
         MOZ_ASSERT(attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
 
-        if (format->componentType == webgl::ComponentType::Special) {
-            // Special format is used for DS mixed format(e.g. D24S8 and D32FS8).
-            MOZ_ASSERT(format->unsizedFormat == webgl::UnsizedFormat::DEPTH_STENCIL);
+        if (format->unsizedFormat == webgl::UnsizedFormat::DEPTH_STENCIL) {
             MOZ_ASSERT(attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
                        attachment == LOCAL_GL_STENCIL_ATTACHMENT);
 
             if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) {
                 switch (format->effectiveFormat) {
                 case webgl::EffectiveFormat::DEPTH24_STENCIL8:
                     format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT24);
                     break;
@@ -575,37 +428,31 @@ WebGLFBAttachPoint::GetParameter(WebGLCo
                 default:
                     MOZ_ASSERT(false, "no matched DS format");
                     break;
                 }
             }
         }
 
         switch (format->componentType) {
-        case webgl::ComponentType::None:
-            ret = LOCAL_GL_NONE;
-            break;
         case webgl::ComponentType::Int:
             ret = LOCAL_GL_INT;
             break;
         case webgl::ComponentType::UInt:
             ret = LOCAL_GL_UNSIGNED_INT;
             break;
         case webgl::ComponentType::NormInt:
             ret = LOCAL_GL_SIGNED_NORMALIZED;
             break;
         case webgl::ComponentType::NormUInt:
             ret = LOCAL_GL_UNSIGNED_NORMALIZED;
             break;
         case webgl::ComponentType::Float:
             ret = LOCAL_GL_FLOAT;
             break;
-        default:
-            MOZ_ASSERT(false, "No matched component type");
-            break;
         }
         break;
 
     default:
         MOZ_ASSERT(false, "Missing case.");
         break;
     }
 
@@ -614,40 +461,46 @@ WebGLFBAttachPoint::GetParameter(WebGLCo
 
 ////////////////////////////////////////////////////////////////////////////////
 ////////////////////////////////////////////////////////////////////////////////
 // WebGLFramebuffer
 
 WebGLFramebuffer::WebGLFramebuffer(WebGLContext* webgl, GLuint fbo)
     : WebGLRefCountedObject(webgl)
     , mGLName(fbo)
-    , mNumFBStatusInvals(0)
-#ifdef ANDROID
-    , mIsFB(false)
-#endif
-    , mDepthAttachment(this, LOCAL_GL_DEPTH_ATTACHMENT)
-    , mStencilAttachment(this, LOCAL_GL_STENCIL_ATTACHMENT)
-    , mDepthStencilAttachment(this, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
+    , mDepthAttachment(webgl, LOCAL_GL_DEPTH_ATTACHMENT)
+    , mStencilAttachment(webgl, LOCAL_GL_STENCIL_ATTACHMENT)
+    , mDepthStencilAttachment(webgl, LOCAL_GL_DEPTH_STENCIL_ATTACHMENT)
 {
     mContext->mFramebuffers.insertBack(this);
 
+    mAttachments.push_back(&mDepthAttachment);
+    mAttachments.push_back(&mStencilAttachment);
+
+    if (!webgl->IsWebGL2()) {
+        // Only WebGL1 has a separate depth+stencil attachment point.
+        mAttachments.push_back(&mDepthStencilAttachment);
+    }
+
     size_t i = 0;
     for (auto& cur : mColorAttachments) {
-        new (&cur) WebGLFBAttachPoint(this, LOCAL_GL_COLOR_ATTACHMENT0 + i);
+        new (&cur) WebGLFBAttachPoint(webgl, LOCAL_GL_COLOR_ATTACHMENT0 + i);
         i++;
+
+        mAttachments.push_back(&cur);
     }
 
     mColorDrawBuffers.push_back(&mColorAttachments[0]);
     mColorReadBuffer = &mColorAttachments[0];
 }
 
 void
 WebGLFramebuffer::Delete()
 {
-    InvalidateFramebufferStatus();
+    InvalidateCaches();
 
     mDepthAttachment.Clear();
     mStencilAttachment.Clear();
     mDepthStencilAttachment.Clear();
 
     for (auto& cur : mColorAttachments) {
         cur.Clear();
     }
@@ -694,165 +547,145 @@ WebGLFramebuffer::GetAttachPoint(GLenum 
     case LOCAL_GL_STENCIL_ATTACHMENT:
         return Some(&mStencilAttachment);
 
     default:
         return GetColorAttachPoint(attachPoint);
     }
 }
 
-#define FOR_EACH_ATTACHMENT(X)            \
-    X(mDepthAttachment);                  \
-    X(mStencilAttachment);                \
-    X(mDepthStencilAttachment);           \
-                                          \
-    for (auto& cur : mColorAttachments) { \
-        X(cur);                           \
-    }
-
 void
 WebGLFramebuffer::DetachTexture(const WebGLTexture* tex)
 {
-    const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
-        if (attach.Texture() == tex) {
-            attach.Clear();
+    for (const auto& attach : mAttachments) {
+        if (attach->Texture() == tex) {
+            attach->Clear();
         }
-    };
-
-    FOR_EACH_ATTACHMENT(fnDetach)
+    }
+    InvalidateCaches();
 }
 
 void
 WebGLFramebuffer::DetachRenderbuffer(const WebGLRenderbuffer* rb)
 {
-    const auto fnDetach = [&](WebGLFBAttachPoint& attach) {
-        if (attach.Renderbuffer() == rb) {
-            attach.Clear();
+    for (const auto& attach : mAttachments) {
+        if (attach->Renderbuffer() == rb) {
+            attach->Clear();
         }
-    };
-
-    FOR_EACH_ATTACHMENT(fnDetach)
+    }
+    InvalidateCaches();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Completeness
 
 bool
 WebGLFramebuffer::HasDuplicateAttachments() const
 {
-   std::set<WebGLFBAttachPoint::Ordered> uniqueAttachSet;
+    std::set<WebGLFBAttachPoint::Ordered> uniqueAttachSet;
 
-   for (const auto& attach : mColorAttachments) {
-      if (!attach.IsDefined())
-          continue;
+    for (const auto& attach : mColorAttachments) {
+        if (!attach.HasAttachment())
+            continue;
 
-      const WebGLFBAttachPoint::Ordered ordered(attach);
+        const WebGLFBAttachPoint::Ordered ordered(attach);
 
-      const bool didInsert = uniqueAttachSet.insert(ordered).second;
-      if (!didInsert)
-         return true;
-   }
+        const bool didInsert = uniqueAttachSet.insert(ordered).second;
+        if (!didInsert)
+            return true;
+    }
 
-   return false;
+    return false;
 }
 
 bool
 WebGLFramebuffer::HasDefinedAttachments() const
 {
     bool hasAttachments = false;
-    const auto func = [&](const WebGLFBAttachPoint& attach) {
-        hasAttachments |= attach.IsDefined();
-    };
-
-    FOR_EACH_ATTACHMENT(func)
+    for (const auto& attach : mAttachments) {
+        hasAttachments |= attach->HasAttachment();
+    }
     return hasAttachments;
 }
 
 bool
 WebGLFramebuffer::HasIncompleteAttachments(nsCString* const out_info) const
 {
     bool hasIncomplete = false;
-    const auto func = [&](const WebGLFBAttachPoint& cur) {
-        if (!cur.IsDefined())
-            return; // Not defined, so can't count as incomplete.
+    for (const auto& cur : mAttachments) {
+        if (!cur->HasAttachment())
+            continue; // Not defined, so can't count as incomplete.
 
-        hasIncomplete |= !cur.IsComplete(mContext, out_info);
-    };
-
-    FOR_EACH_ATTACHMENT(func)
+        hasIncomplete |= !cur->IsComplete(mContext, out_info);
+    }
     return hasIncomplete;
 }
 
 bool
 WebGLFramebuffer::AllImageRectsMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     DebugOnly<nsCString> fbStatusInfo;
     MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
 
     bool needsInit = true;
     uint32_t width = 0;
     uint32_t height = 0;
 
     bool hasMismatch = false;
-    const auto func = [&](const WebGLFBAttachPoint& attach) {
-        if (!attach.HasImage())
-            return;
+    for (const auto& attach : mAttachments) {
+        const auto& imageInfo = attach->GetImageInfo();
+        if (!imageInfo)
+            continue;
 
-        uint32_t curWidth;
-        uint32_t curHeight;
-        attach.Size(&curWidth, &curHeight);
+        const auto& curWidth  = imageInfo->mWidth;
+        const auto& curHeight = imageInfo->mHeight;
 
         if (needsInit) {
             needsInit = false;
             width = curWidth;
             height = curHeight;
-            return;
+            continue;
         }
 
         hasMismatch |= (curWidth != width ||
                         curHeight != height);
-    };
-
-    FOR_EACH_ATTACHMENT(func)
+    }
     return !hasMismatch;
 }
 
 bool
 WebGLFramebuffer::AllImageSamplesMatch() const
 {
     MOZ_ASSERT(HasDefinedAttachments());
     DebugOnly<nsCString> fbStatusInfo;
     MOZ_ASSERT(!HasIncompleteAttachments(&fbStatusInfo));
 
     bool needsInit = true;
     uint32_t samples = 0;
 
     bool hasMismatch = false;
-    const auto func = [&](const WebGLFBAttachPoint& attach) {
-        if (!attach.HasImage())
-          return;
+    for (const auto& attach : mAttachments) {
+        const auto& imageInfo = attach->GetImageInfo();
+        if (!imageInfo)
+            continue;
 
-        const uint32_t curSamples = attach.Samples();
+        const auto& curSamples = imageInfo->mSamples;
 
         if (needsInit) {
             needsInit = false;
             samples = curSamples;
-            return;
+            continue;
         }
 
         hasMismatch |= (curSamples != samples);
     };
-
-    FOR_EACH_ATTACHMENT(func)
     return !hasMismatch;
 }
 
-#undef FOR_EACH_ATTACHMENT
-
 FBStatus
 WebGLFramebuffer::PrecheckFramebufferStatus(nsCString* const out_info) const
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
 
     if (!HasDefinedAttachments())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT; // No attachments
@@ -865,21 +698,25 @@ WebGLFramebuffer::PrecheckFramebufferSta
 
     if (!AllImageSamplesMatch())
         return LOCAL_GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE; // Inconsistent samples
 
     if (HasDuplicateAttachments())
        return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
 
     if (mContext->IsWebGL2()) {
-        MOZ_ASSERT(!mDepthStencilAttachment.IsDefined());
+        MOZ_ASSERT(!mDepthStencilAttachment.HasAttachment());
+        if (mDepthAttachment.HasAttachment() && mStencilAttachment.HasAttachment()) {
+            if (!mDepthAttachment.IsEquivalentForFeedback(mStencilAttachment))
+                return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
+        }
     } else {
-        const auto depthOrStencilCount = int(mDepthAttachment.IsDefined()) +
-                                         int(mStencilAttachment.IsDefined()) +
-                                         int(mDepthStencilAttachment.IsDefined());
+        const auto depthOrStencilCount = int(mDepthAttachment.HasAttachment()) +
+                                         int(mStencilAttachment.HasAttachment()) +
+                                         int(mDepthStencilAttachment.HasAttachment());
         if (depthOrStencilCount > 1)
             return LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
     }
 
     return LOCAL_GL_FRAMEBUFFER_COMPLETE;
 }
 
 ////////////////////////////////////////
@@ -902,24 +739,25 @@ WebGLFramebuffer::ValidateAndInitAttachm
 bool
 WebGLFramebuffer::ValidateClearBufferType(GLenum buffer,
                                           uint32_t drawBuffer, GLenum funcType) const
 {
     if (buffer != LOCAL_GL_COLOR)
         return true;
 
     const auto& attach = mColorAttachments[drawBuffer];
-    if (!attach.IsDefined())
+    const auto& imageInfo = attach.GetImageInfo();
+    if (!imageInfo)
         return true;
 
     if (!count(mColorDrawBuffers.begin(), mColorDrawBuffers.end(), &attach))
         return true; // DRAW_BUFFERi set to NONE.
 
     GLenum attachType;
-    switch (attach.Format()->format->componentType) {
+    switch (imageInfo->mFormat->format->componentType) {
     case webgl::ComponentType::Int:
         attachType = LOCAL_GL_INT;
         break;
     case webgl::ComponentType::UInt:
         attachType = LOCAL_GL_UNSIGNED_INT;
         break;
     default:
         attachType = LOCAL_GL_FLOAT;
@@ -941,248 +779,202 @@ WebGLFramebuffer::ValidateForColorRead(c
                                        uint32_t* const out_width,
                                        uint32_t* const out_height) const
 {
     if (!mColorReadBuffer) {
         mContext->ErrorInvalidOperation("READ_BUFFER must not be NONE.");
         return false;
     }
 
-    if (!mColorReadBuffer->IsDefined()) {
+    const auto& imageInfo = mColorReadBuffer->GetImageInfo();
+    if (!imageInfo) {
         mContext->ErrorInvalidOperation("The READ_BUFFER attachment is not defined.");
         return false;
     }
 
-    if (mColorReadBuffer->Samples()) {
+    if (imageInfo->mSamples) {
         mContext->ErrorInvalidOperation("The READ_BUFFER attachment is multisampled.");
         return false;
     }
 
-    *out_format = mColorReadBuffer->Format();
-    mColorReadBuffer->Size(out_width, out_height);
+    *out_format = imageInfo->mFormat;
+    *out_width = imageInfo->mWidth;
+    *out_height = imageInfo->mHeight;
     return true;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Resolution and caching
 
 void
-WebGLFramebuffer::ResolveAttachments() const
+WebGLFramebuffer::DoDeferredAttachments() const
 {
-    const auto& gl = mContext->gl;
-
-    ////
-    // Nuke attachment points.
+    if (mContext->IsWebGL2())
+        return;
 
-    for (uint32_t i = 0; i < mContext->mGLMaxColorAttachments; i++) {
-        const GLenum attachEnum = LOCAL_GL_COLOR_ATTACHMENT0 + i;
-        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachEnum,
-                                     LOCAL_GL_RENDERBUFFER, 0);
-    }
-
+    const auto& gl = mContext->gl;
     gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                  LOCAL_GL_RENDERBUFFER, 0);
     gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
                                  LOCAL_GL_RENDERBUFFER, 0);
 
-    ////
+    const auto fn = [&](const WebGLFBAttachPoint& attach) {
+        MOZ_ASSERT(attach.mDeferAttachment);
+        if (attach.HasAttachment()) {
+            attach.DoAttachment(gl);
+        }
+    };
+    // Only one of these will have an attachment.
+    fn(mDepthAttachment);
+    fn(mStencilAttachment);
+    fn(mDepthStencilAttachment);
+}
+
+void
+WebGLFramebuffer::ResolveAttachmentData() const
+{
+    // GLES 3.0.5 p188:
+    //   The result of clearing integer color buffers with `Clear` is undefined.
+
+    // Two different approaches:
+    // On WebGL 2, we have glClearBuffer, and *must* use it for integer buffers, so let's
+    // just use it for all the buffers.
+    // One WebGL 1, we might not have glClearBuffer,
+
+    // WebGL 1 is easier, because we can just call glClear, possibly with glDrawBuffers.
+
+    const auto& gl = mContext->gl;
+
+    const webgl::ScopedPrepForResourceClear scopedPrep(*mContext);
+
+    if (mContext->IsWebGL2()) {
+        const uint32_t uiZeros[4] = {};
+        const int32_t iZeros[4] = {};
+        const float fZeros[4] = {};
+        const float fOne[] = {1.0f};
+
+        for (const auto& cur : mAttachments) {
+            const auto& imageInfo = cur->GetImageInfo();
+            if (!imageInfo || imageInfo->mHasData)
+                continue; // Nothing attached, or already has data.
 
-    for (const auto& attach : mColorAttachments) {
-        attach.Resolve(gl);
+            const auto fnClearBuffer = [&]() {
+                const auto& format = imageInfo->mFormat->format;
+                MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(uiZeros));
+                MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(iZeros));
+                MOZ_ASSERT(format->estimatedBytesPerPixel <= sizeof(fZeros));
+
+                switch (cur->mAttachmentPoint) {
+                case LOCAL_GL_DEPTH_ATTACHMENT:
+                    gl->fClearBufferfv(LOCAL_GL_DEPTH, 0, fOne);
+                    break;
+                case LOCAL_GL_STENCIL_ATTACHMENT:
+                    gl->fClearBufferiv(LOCAL_GL_STENCIL, 0, iZeros);
+                    break;
+                default:
+                    MOZ_ASSERT(cur->mAttachmentPoint != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
+                    const uint32_t drawBuffer = cur->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
+                    MOZ_ASSERT(drawBuffer <= 100);
+                    switch (format->componentType) {
+                    case webgl::ComponentType::Int:
+                        gl->fClearBufferiv(LOCAL_GL_COLOR, drawBuffer, iZeros);
+                        break;
+                    case webgl::ComponentType::UInt:
+                        gl->fClearBufferuiv(LOCAL_GL_COLOR, drawBuffer, uiZeros);
+                        break;
+                    default:
+                        gl->fClearBufferfv(LOCAL_GL_COLOR, drawBuffer, fZeros);
+                        break;
+                    }
+                }
+            };
+
+            if (imageInfo->mDepth > 1) {
+                // Todo: Use glClearTexImage.
+                const auto& tex = cur->Texture();
+                for (uint32_t z = 0; z < imageInfo->mDepth; z++) {
+                    gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, cur->mAttachmentPoint,
+                                                 tex->mGLName, cur->MipLevel(), z);
+                    fnClearBuffer();
+                }
+
+                gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, cur->mAttachmentPoint,
+                                             tex->mGLName, cur->MipLevel(), cur->Layer());
+            } else {
+                fnClearBuffer();
+            }
+            imageInfo->mHasData = true;
+        }
+        return;
     }
 
-    mDepthAttachment.Resolve(gl);
-    mStencilAttachment.Resolve(gl);
-    mDepthStencilAttachment.Resolve(gl);
-}
+    uint32_t clearBits = 0;
+    std::vector<GLenum> drawBufferForClear;
 
-bool
-WebGLFramebuffer::ResolveAttachmentData() const
-{
-    //////
-    // Check if we need to initialize anything
-
-    const auto fnIs3D = [&](const WebGLFBAttachPoint& attach) {
-        const auto& tex = attach.Texture();
-        if (!tex)
+    const auto fnGather = [&](const WebGLFBAttachPoint& attach,
+                              const uint32_t attachClearBits)
+    {
+        const auto& imageInfo = attach.GetImageInfo();
+        if (!imageInfo || imageInfo->mHasData)
             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);
+        imageInfo->mHasData = true; // Just mark it now.
         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(attach->ImageTarget(),
-                                      attach->MipLevel()))
-        {
-            return false;
-        }
-    }
-
-    if (clearBits) {
-        const auto fnDrawBuffers = [&](const std::vector<const WebGLFBAttachPoint*>& src)
-        {
-            std::vector<GLenum> enumList;
-
-            for (const auto& cur : src) {
-                const auto& attachEnum = cur->mAttachmentPoint;
-                const GLenum attachId = attachEnum - LOCAL_GL_COLOR_ATTACHMENT0;
-
-                while (enumList.size() < attachId) {
-                    enumList.push_back(LOCAL_GL_NONE);
-                }
-                enumList.push_back(attachEnum);
-            }
-
-            mContext->gl->fDrawBuffers(enumList.size(), enumList.data());
-        };
-
-        ////
-        // Clear
-
-        const bool hasDrawBuffers = mContext->HasDrawBuffers();
-        if (hasDrawBuffers) {
-            fnDrawBuffers(colorAttachmentsToClear);
-        }
-
-        {
-            gl::ScopedBindFramebuffer autoBind(mContext->gl, mGLName);
-
-            mContext->ForceClearFramebufferWithDefaultValues(clearBits, false);
-        }
-
-        if (hasDrawBuffers) {
-            RefreshDrawBuffers();
-        }
-
-        // Mark initialized.
-        for (const auto& cur : attachmentsToClear) {
-            cur->SetImageDataStatus(WebGLImageDataStatus::InitializedImageData);
+    for (const auto& cur : mColorAttachments) {
+        if (fnGather(cur, LOCAL_GL_COLOR_BUFFER_BIT)) {
+            const uint32_t id = cur.mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
+            MOZ_ASSERT(id <= 100);
+            drawBufferForClear.resize(id + 1); // Pads with zeros!
+            drawBufferForClear[id] = cur.mAttachmentPoint;
         }
     }
 
-    return true;
+    (void)fnGather(mDepthAttachment, LOCAL_GL_DEPTH_BUFFER_BIT);
+    (void)fnGather(mStencilAttachment, LOCAL_GL_STENCIL_BUFFER_BIT);
+    (void)fnGather(mDepthStencilAttachment, LOCAL_GL_DEPTH_BUFFER_BIT |
+                                            LOCAL_GL_STENCIL_BUFFER_BIT);
+
+    //////
+
+    if (!clearBits)
+        return;
+
+    if (gl->IsSupported(gl::GLFeature::draw_buffers)) {
+        gl->fDrawBuffers(drawBufferForClear.size(), drawBufferForClear.data());
+    }
+
+    gl->fClear(clearBits);
+
+    RefreshDrawBuffers();
 }
 
-WebGLFramebuffer::ResolvedData::ResolvedData(const WebGLFramebuffer& parent)
+WebGLFramebuffer::CompletenessInfo::~CompletenessInfo()
 {
-
-    texDrawBuffers.reserve(parent.mColorDrawBuffers.size() + 2); // +2 for depth+stencil.
-
-    const auto fnCommon = [&](const WebGLFBAttachPoint& attach) {
-        if (!attach.IsDefined())
-            return false;
-
-        if (attach.Texture()) {
-            texDrawBuffers.push_back(&attach);
-        }
-        return true;
-    };
-
-    ////
-
-    const auto fnDepthStencil = [&](const WebGLFBAttachPoint& attach) {
-        if (!fnCommon(attach))
-            return;
-
-        drawSet.insert(WebGLFBAttachPoint::Ordered(attach));
-        readSet.insert(WebGLFBAttachPoint::Ordered(attach));
-    };
-
-    fnDepthStencil(parent.mDepthAttachment);
-    fnDepthStencil(parent.mStencilAttachment);
-    fnDepthStencil(parent.mDepthStencilAttachment);
-
-    ////
-
-    for (const auto& pAttach : parent.mColorDrawBuffers) {
-        const auto& attach = *pAttach;
-        if (!fnCommon(attach))
-            return;
-
-        drawSet.insert(WebGLFBAttachPoint::Ordered(attach));
-    }
-
-    if (parent.mColorReadBuffer) {
-        const auto& attach = *parent.mColorReadBuffer;
-        if (!fnCommon(attach))
-            return;
-
-        readSet.insert(WebGLFBAttachPoint::Ordered(attach));
-    }
-}
-
-void
-WebGLFramebuffer::InvalidateFramebufferStatus()
-{
-    if (mResolvedCompleteData) {
-        mNumFBStatusInvals++;
-        if (mNumFBStatusInvals > mContext->mMaxAcceptableFBStatusInvals) {
-            mContext->GeneratePerfWarning("FB was invalidated after being complete %u"
-                                          " times.",
-                                          uint32_t(mNumFBStatusInvals));
-        }
-    }
-
-    mResolvedCompleteData = nullptr;
-}
-
-void
-WebGLFramebuffer::RefreshResolvedData()
-{
-    if (mResolvedCompleteData) {
-        mResolvedCompleteData.reset(new ResolvedData(*this));
+    const auto& fb = this->fb;
+    const auto& webgl = fb.mContext;
+    fb.mNumFBStatusInvals++;
+    if (fb.mNumFBStatusInvals > webgl->mMaxAcceptableFBStatusInvals) {
+        webgl->GeneratePerfWarning("FB was invalidated after being complete %u"
+                                   " times.",
+                                   uint32_t(fb.mNumFBStatusInvals));
     }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Entrypoints
 
 FBStatus
 WebGLFramebuffer::CheckFramebufferStatus() const
 {
-    if (IsResolvedComplete())
+    if (mCompletenessInfo)
         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)
@@ -1191,38 +983,55 @@ WebGLFramebuffer::CheckFramebufferStatus
         // Looks good on our end. Let's ask the driver.
         gl::GLContext* const gl = mContext->gl;
 
         const ScopedFBRebinder autoFB(mContext);
         gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
 
         ////
 
-        ResolveAttachments(); // OK, attach everything properly!
+        DoDeferredAttachments();
         RefreshDrawBuffers();
         RefreshReadBuffer();
 
         ret = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
 
         ////
 
         if (ret != LOCAL_GL_FRAMEBUFFER_COMPLETE) {
             const nsPrintfCString text("Bad status according to the driver: 0x%04x",
                                        ret.get());
             statusInfo = text;
             break;
         }
 
-        if (!ResolveAttachmentData()) {
-            ret = LOCAL_GL_FRAMEBUFFER_UNSUPPORTED;
-            statusInfo.AssignLiteral("Failed to lazily-initialize attachment data.");
-            break;
+        ResolveAttachmentData();
+
+        // Sweet, let's cache that.
+        auto info = CompletenessInfo { *this, UINT32_MAX, UINT32_MAX };
+        mCompletenessInfo.ResetInvalidators({});
+        mCompletenessInfo.AddInvalidator(*this);
+
+        for (const auto& cur : mAttachments) {
+            const auto& tex = cur->Texture();
+            const auto& rb = cur->Renderbuffer();
+            if (tex) {
+                mCompletenessInfo.AddInvalidator(*tex);
+                info.texAttachments.push_back(cur);
+            } else if (rb) {
+                mCompletenessInfo.AddInvalidator(*rb);
+            } else {
+                continue;
+            }
+            const auto& imageInfo = cur->GetImageInfo();
+            MOZ_ASSERT(imageInfo);
+            info.width = std::min(info.width, imageInfo->mWidth);
+            info.height = std::min(info.height, imageInfo->mHeight);
         }
-
-        mResolvedCompleteData.reset(new ResolvedData(*this));
+        mCompletenessInfo = Some(std::move(info));
         return LOCAL_GL_FRAMEBUFFER_COMPLETE;
     } while (false);
 
     MOZ_ASSERT(ret != LOCAL_GL_FRAMEBUFFER_COMPLETE);
     mContext->GenerateWarning("Framebuffer not complete. (status: 0x%04x) %s",
                               ret.get(), statusInfo.BeginReading());
     return ret;
 }
@@ -1237,17 +1046,17 @@ WebGLFramebuffer::RefreshDrawBuffers() c
         return;
 
     // Prior to GL4.1, having a no-image FB attachment that's selected by DrawBuffers
     // yields a framebuffer status of FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER.
     // We could workaround this only on affected versions, but it's easier be
     // unconditional.
     std::vector<GLenum> driverBuffers(mContext->mGLMaxDrawBuffers, LOCAL_GL_NONE);
     for (const auto& attach : mColorDrawBuffers) {
-        if (attach->HasImage()) {
+        if (attach->HasAttachment()) {
             const uint32_t index = attach->mAttachmentPoint - LOCAL_GL_COLOR_ATTACHMENT0;
             driverBuffers[index] = attach->mAttachmentPoint;
         }
     }
 
     gl->fDrawBuffers(driverBuffers.size(), driverBuffers.data());
 }
 
@@ -1258,17 +1067,17 @@ WebGLFramebuffer::RefreshReadBuffer() co
     if (!gl->IsSupported(gl::GLFeature::read_buffer))
         return;
 
     // Prior to GL4.1, having a no-image FB attachment that's selected by ReadBuffer
     // yields a framebuffer status of FRAMEBUFFER_INCOMPLETE_READ_BUFFER.
     // We could workaround this only on affected versions, but it's easier be
     // unconditional.
     GLenum driverBuffer = LOCAL_GL_NONE;
-    if (mColorReadBuffer && mColorReadBuffer->HasImage()) {
+    if (mColorReadBuffer && mColorReadBuffer->HasAttachment()) {
         driverBuffer = mColorReadBuffer->mAttachmentPoint;
     }
 
     gl->fReadBuffer(driverBuffer);
 }
 
 ////
 
@@ -1315,17 +1124,16 @@ WebGLFramebuffer::DrawBuffers(const dom:
             return;
         }
     }
 
     ////
 
     mColorDrawBuffers.swap(newColorDrawBuffers);
     RefreshDrawBuffers(); // Calls glDrawBuffers.
-    RefreshResolvedData();
 }
 
 void
 WebGLFramebuffer::ReadBuffer(GLenum attachPoint)
 {
     const auto& maybeAttach = GetColorAttachPoint(attachPoint);
     if (!maybeAttach) {
         const char text[] = "`mode` must be a COLOR_ATTACHMENTi, for 0 <= i <"
@@ -1338,17 +1146,16 @@ WebGLFramebuffer::ReadBuffer(GLenum atta
         return;
     }
     const auto& attach = maybeAttach.value(); // Might be nullptr.
 
     ////
 
     mColorReadBuffer = attach;
     RefreshReadBuffer(); // Calls glReadBuffer.
-    RefreshResolvedData();
 }
 
 ////
 
 void
 WebGLFramebuffer::FramebufferRenderbuffer(GLenum attachEnum,
                                           GLenum rbtarget, WebGLRenderbuffer* rb)
 {
@@ -1376,27 +1183,27 @@ WebGLFramebuffer::FramebufferRenderbuffe
 
         if (!rb->mHasBeenBound) {
             mContext->ErrorInvalidOperation("bindRenderbuffer must be called before"
                                             " attachment to %04x",
                                             attachEnum);
             return;
       }
     }
-
     // End of validation.
 
+    const auto& gl = mContext->gl;
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
     if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetRenderbuffer(rb);
-        mStencilAttachment.SetRenderbuffer(rb);
+        mDepthAttachment.SetRenderbuffer(gl, rb);
+        mStencilAttachment.SetRenderbuffer(gl, rb);
     } else {
-        attach->SetRenderbuffer(rb);
+        attach->SetRenderbuffer(gl, rb);
     }
-
-    InvalidateFramebufferStatus();
+    InvalidateCaches();
 }
 
 void
 WebGLFramebuffer::FramebufferTexture2D(GLenum attachEnum,
                                        GLenum texImageTarget, WebGLTexture* tex,
                                        GLint level)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
@@ -1465,24 +1272,26 @@ WebGLFramebuffer::FramebufferTexture2D(G
                 return mContext->ErrorInvalidValue("`level` is too large.");
         }
     } else if (level != 0) {
         return mContext->ErrorInvalidValue("`level` must be 0.");
     }
 
     // End of validation.
 
+    const auto& gl = mContext->gl;
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
     if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetTexImage(tex, texImageTarget, level);
-        mStencilAttachment.SetTexImage(tex, texImageTarget, level);
+        mDepthAttachment.SetTexImage(gl, tex, texImageTarget, level);
+        mStencilAttachment.SetTexImage(gl, tex, texImageTarget, level);
     } else {
-        attach->SetTexImage(tex, texImageTarget, level);
+        attach->SetTexImage(gl, tex, texImageTarget, level);
     }
 
-    InvalidateFramebufferStatus();
+    InvalidateCaches();
 }
 
 void
 WebGLFramebuffer::FramebufferTextureLayer(GLenum attachEnum,
                                           WebGLTexture* tex, GLint level, GLint layer)
 {
     MOZ_ASSERT(mContext->mBoundDrawFramebuffer == this ||
                mContext->mBoundReadFramebuffer == this);
@@ -1547,24 +1356,26 @@ WebGLFramebuffer::FramebufferTextureLaye
             mContext->ErrorInvalidOperation("`texture` must be a TEXTURE_3D or"
                                             " TEXTURE_2D_ARRAY.");
             return;
         }
     }
 
     // End of validation.
 
+    const auto& gl = mContext->gl;
+    gl->fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, mGLName);
     if (mContext->IsWebGL2() && attachEnum == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-        mDepthAttachment.SetTexImage(tex, texImageTarget, level, layer);
-        mStencilAttachment.SetTexImage(tex, texImageTarget, level, layer);
+        mDepthAttachment.SetTexImage(gl, tex, texImageTarget, level, layer);
+        mStencilAttachment.SetTexImage(gl, tex, texImageTarget, level, layer);
     } else {
-        attach->SetTexImage(tex, texImageTarget, level, layer);
+        attach->SetTexImage(gl, tex, texImageTarget, level, layer);
     }
 
-    InvalidateFramebufferStatus();
+    InvalidateCaches();
 }
 
 JS::Value
 WebGLFramebuffer::GetAttachmentParameter(JSContext* cx,
                                          GLenum target, GLenum attachEnum, GLenum pname,
                                          ErrorResult* const out_error)
 {
     const auto maybeAttach = GetAttachPoint(attachEnum);
@@ -1632,128 +1443,97 @@ GetBackbufferFormats(const WebGLContext*
 }
 
 /*static*/ void
 WebGLFramebuffer::BlitFramebuffer(WebGLContext* webgl,
                                   GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                                   GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                                   GLbitfield mask, GLenum filter)
 {
-    const auto& gl = webgl->gl;
+    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
+                                           LOCAL_GL_STENCIL_BUFFER_BIT;
+    if (bool(mask & depthAndStencilBits) &&
+        filter == LOCAL_GL_LINEAR)
+    {
+        webgl->ErrorInvalidOperation("DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can"
+                                     " only be used with NEAREST filtering.");
+        return;
+    }
 
     const auto& srcFB = webgl->mBoundReadFramebuffer;
     const auto& dstFB = webgl->mBoundDrawFramebuffer;
 
     ////
     // Collect data
 
-    const auto fnGetDepthAndStencilAttach = [](const WebGLFramebuffer* fb,
-                                               const WebGLFBAttachPoint** const out_depth,
-                                               const WebGLFBAttachPoint** const out_stencil)
+    const auto fnGetFormat = [](const WebGLFBAttachPoint& cur,
+                                bool* const out_hasSamples) -> const webgl::FormatInfo*
     {
-        *out_depth = nullptr;
-        *out_stencil = nullptr;
-
-        if (!fb)
-            return;
-
-        if (fb->mDepthStencilAttachment.IsDefined()) {
-            *out_depth = *out_stencil = &fb->mDepthStencilAttachment;
-            return;
-        }
-        if (fb->mDepthAttachment.IsDefined()) {
-            *out_depth = &fb->mDepthAttachment;
-        }
-        if (fb->mStencilAttachment.IsDefined()) {
-            *out_stencil = &fb->mStencilAttachment;
-        }
+        const auto& imageInfo = cur.GetImageInfo();
+        if (!imageInfo)
+            return nullptr; // No attachment.
+        *out_hasSamples = bool(imageInfo->mSamples);
+        return imageInfo->mFormat->format;
     };
 
-    const WebGLFBAttachPoint* srcDepthAttach;
-    const WebGLFBAttachPoint* srcStencilAttach;
-    fnGetDepthAndStencilAttach(srcFB, &srcDepthAttach, &srcStencilAttach);
-    const WebGLFBAttachPoint* dstDepthAttach;
-    const WebGLFBAttachPoint* dstStencilAttach;
-    fnGetDepthAndStencilAttach(dstFB, &dstDepthAttach, &dstStencilAttach);
-
-    ////
-
-    const auto fnGetFormat = [](const WebGLFBAttachPoint* cur,
-                                bool* const out_hasSamples) -> const webgl::FormatInfo*
-    {
-        if (!cur || !cur->IsDefined())
-            return nullptr;
-
-        *out_hasSamples |= bool(cur->Samples());
-        return cur->Format()->format;
-    };
-
-    const auto fnNarrowComponentType = [&](const webgl::FormatInfo* format) {
-        switch (format->componentType) {
-        case webgl::ComponentType::NormInt:
-        case webgl::ComponentType::NormUInt:
-            return webgl::ComponentType::Float;
-
-        default:
-            return format->componentType;
-        }
-    };
-
-    bool srcHasSamples;
+    bool srcHasSamples = false;
+    bool srcIsFilterable = true;
     const webgl::FormatInfo* srcColorFormat;
-    webgl::ComponentType srcColorType = webgl::ComponentType::None;
     const webgl::FormatInfo* srcDepthFormat;
     const webgl::FormatInfo* srcStencilFormat;
 
     if (srcFB) {
-        srcHasSamples = false;
-        srcColorFormat = fnGetFormat(srcFB->mColorReadBuffer, &srcHasSamples);
-        srcDepthFormat = fnGetFormat(srcDepthAttach, &srcHasSamples);
-        srcStencilFormat = fnGetFormat(srcStencilAttach, &srcHasSamples);
+        srcColorFormat = nullptr;
+        if (srcFB->mColorReadBuffer) {
+            const auto& imageInfo = srcFB->mColorReadBuffer->GetImageInfo();
+            if (imageInfo) {
+                srcIsFilterable &= imageInfo->mFormat->isFilterable;
+            }
+            srcColorFormat = fnGetFormat(*(srcFB->mColorReadBuffer), &srcHasSamples);
+        }
+        srcDepthFormat = fnGetFormat(srcFB->DepthAttachment(), &srcHasSamples);
+        srcStencilFormat = fnGetFormat(srcFB->StencilAttachment(), &srcHasSamples);
+        MOZ_ASSERT(!srcFB->DepthStencilAttachment().HasAttachment());
     } else {
         srcHasSamples = false; // Always false.
 
         GetBackbufferFormats(webgl, &srcColorFormat, &srcDepthFormat, &srcStencilFormat);
     }
 
-    if (srcColorFormat) {
-        srcColorType = fnNarrowComponentType(srcColorFormat);
-    }
-
     ////
 
-    bool dstHasSamples;
+    bool dstHasSamples = false;
     const webgl::FormatInfo* dstDepthFormat;
     const webgl::FormatInfo* dstStencilFormat;
     bool dstHasColor = false;
     bool colorFormatsMatch = true;
     bool colorTypesMatch = true;
 
     const auto fnCheckColorFormat = [&](const webgl::FormatInfo* dstFormat) {
         MOZ_ASSERT(dstFormat->r || dstFormat->g || dstFormat->b || dstFormat->a);
         dstHasColor = true;
         colorFormatsMatch &= (dstFormat == srcColorFormat);
-        colorTypesMatch &= ( fnNarrowComponentType(dstFormat) == srcColorType );
+        colorTypesMatch &= srcColorFormat &&
+                           (dstFormat->baseType == srcColorFormat->baseType);
     };
 
     if (dstFB) {
-        dstHasSamples = false;
-
         for (const auto& cur : dstFB->mColorDrawBuffers) {
-            const auto& format = fnGetFormat(cur, &dstHasSamples);
+            const auto& format = fnGetFormat(*cur, &dstHasSamples);
             if (!format)
                 continue;
 
             fnCheckColorFormat(format);
         }
 
-        dstDepthFormat = fnGetFormat(dstDepthAttach, &dstHasSamples);
-        dstStencilFormat = fnGetFormat(dstStencilAttach, &dstHasSamples);
+        dstDepthFormat = fnGetFormat(dstFB->DepthAttachment(), &dstHasSamples);
+        dstStencilFormat = fnGetFormat(dstFB->StencilAttachment(), &dstHasSamples);
+        MOZ_ASSERT(!dstFB->DepthStencilAttachment().HasAttachment());
     } else {
-        dstHasSamples = bool(gl->Screen()->Samples());
+        dstHasSamples = webgl->Options().antialias;
 
         const webgl::FormatInfo* dstColorFormat;
         GetBackbufferFormats(webgl, &dstColorFormat, &dstDepthFormat, &dstStencilFormat);
 
         fnCheckColorFormat(dstColorFormat);
     }
 
     ////
@@ -1775,43 +1555,60 @@ WebGLFramebuffer::BlitFramebuffer(WebGLC
         !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("`filter` is LINEAR and READ_BUFFER"
-                                             " contains integer data.");
-                return;
-            }
+    if (dstHasSamples) {
+        webgl->ErrorInvalidOperation("DRAW_FRAMEBUFFER may not have multiple"
+                                     " samples.");
+        return;
+    }
+
+    bool requireFilterable = (filter == LOCAL_GL_LINEAR);
+    if (srcHasSamples) {
+        requireFilterable = false; // It picks one.
+
+        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
+            dstHasColor && !colorFormatsMatch)
+        {
+            webgl->ErrorInvalidOperation("Color buffer formats must match if"
+                                         " selected, when reading from a multisampled"
+                                         " source.");
+            return;
         }
 
-        if (!colorTypesMatch) {
-            webgl->ErrorInvalidOperation("Color component types (fixed/float/uint/"
-                                         "int) must match.");
+        if (dstX0 != srcX0 ||
+            dstX1 != srcX1 ||
+            dstY0 != srcY0 ||
+            dstY1 != srcY1)
+        {
+            webgl->ErrorInvalidOperation("If the source is multisampled, then the"
+                                         " source and dest regions must match exactly.");
             return;
         }
     }
 
-    const GLbitfield depthAndStencilBits = LOCAL_GL_DEPTH_BUFFER_BIT |
-                                           LOCAL_GL_STENCIL_BUFFER_BIT;
-    if (bool(mask & depthAndStencilBits) &&
-        filter != LOCAL_GL_NEAREST)
-    {
-        webgl->ErrorInvalidOperation("DEPTH_BUFFER_BIT and STENCIL_BUFFER_BIT can"
-                                     " only be used with NEAREST filtering.");
-        return;
+    // -
+
+    if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
+        if (requireFilterable && !srcIsFilterable) {
+            webgl->ErrorInvalidOperation("`filter` is LINEAR and READ_BUFFER"
+                                         " contains integer data.");
+                return;
+        }
+
+        if (!colorTypesMatch) {
+            webgl->ErrorInvalidOperation("Color component types (float/uint/"
+                                         "int) must match.");
+            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,
@@ -1828,85 +1625,57 @@ WebGLFramebuffer::BlitFramebuffer(WebGLC
     if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
         dstStencilFormat && dstStencilFormat != srcStencilFormat)
     {
         webgl->ErrorInvalidOperation("Stencil buffer formats must match if selected.");
         return;
     }
 
     ////
-
-    if (dstHasSamples) {
-        webgl->ErrorInvalidOperation("DRAW_FRAMEBUFFER may not have multiple"
-                                     " samples.");
-        return;
-    }
-
-    if (srcHasSamples) {
-        if (mask & LOCAL_GL_COLOR_BUFFER_BIT &&
-            dstHasColor && !colorFormatsMatch)
-        {
-            webgl->ErrorInvalidOperation("Color buffer formats must match if"
-                                         " selected, when reading from a multisampled"
-                                         " source.");
-            return;
-        }
-
-        if (dstX0 != srcX0 ||
-            dstX1 != srcX1 ||
-            dstY0 != srcY0 ||
-            dstY1 != srcY1)
-        {
-            webgl->ErrorInvalidOperation("If the source is multisampled, then the"
-                                         " source and dest regions must match exactly.");
-            return;
-        }
-    }
-
-    ////
     // Check for feedback
 
     if (srcFB && dstFB) {
         const WebGLFBAttachPoint* feedback = nullptr;
 
         if (mask & LOCAL_GL_COLOR_BUFFER_BIT) {
-            MOZ_ASSERT(srcFB->mColorReadBuffer->IsDefined());
+            MOZ_ASSERT(srcFB->mColorReadBuffer->HasAttachment());
             for (const auto& cur : dstFB->mColorDrawBuffers) {
                 if (srcFB->mColorReadBuffer->IsEquivalentForFeedback(*cur)) {
                     feedback = cur;
                     break;
                 }
             }
         }
 
         if (mask & LOCAL_GL_DEPTH_BUFFER_BIT &&
-            srcDepthAttach->IsEquivalentForFeedback(*dstDepthAttach))
+            srcFB->DepthAttachment().IsEquivalentForFeedback(dstFB->DepthAttachment()))
         {
-            feedback = dstDepthAttach;
+            feedback = &dstFB->DepthAttachment();
         }
 
         if (mask & LOCAL_GL_STENCIL_BUFFER_BIT &&
-            srcStencilAttach->IsEquivalentForFeedback(*dstStencilAttach))
+            srcFB->StencilAttachment().IsEquivalentForFeedback(dstFB->StencilAttachment()))
         {
-            feedback = dstStencilAttach;
+            feedback = &dstFB->StencilAttachment();
         }
 
         if (feedback) {
             webgl->ErrorInvalidOperation("Feedback detected into DRAW_FRAMEBUFFER's"
                                          " 0x%04x attachment.",
                                          feedback->mAttachmentPoint);
             return;
         }
     } else if (!srcFB && !dstFB) {
         webgl->ErrorInvalidOperation("Feedback with default framebuffer.");
         return;
     }
 
     ////
 
+    const auto& gl = webgl->gl;
     const ScopedDrawCallWrapper wrapper(*webgl);
     gl->fBlitFramebuffer(srcX0, srcY0, srcX1, srcY1,
                          dstX0, dstY0, dstX1, dstY1,
                          mask, filter);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Goop.
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -30,108 +30,99 @@ class PlacementArray;
 namespace gl {
     class GLContext;
 } // namespace gl
 
 class WebGLFBAttachPoint final
 {
     friend class WebGLFramebuffer;
 public:
-    WebGLFramebuffer* const mFB;
-    const GLenum mAttachmentPoint;
+    const GLenum mAttachmentPoint = 0;
+    const bool mDeferAttachment = false;
 
 private:
     WebGLRefPtr<WebGLTexture> mTexturePtr;
     WebGLRefPtr<WebGLRenderbuffer> mRenderbufferPtr;
-    TexImageTarget mTexImageTarget;
-    GLint mTexImageLayer;
-    uint32_t mTexImageLevel;
+    TexImageTarget mTexImageTarget = 0;
+    GLint mTexImageLayer = 0;
+    uint32_t mTexImageLevel = 0;
 
     ////
 
-    WebGLFBAttachPoint();
-    WebGLFBAttachPoint(WebGLFramebuffer* fb, GLenum attachmentPoint);
+    WebGLFBAttachPoint() = default;
+    WebGLFBAttachPoint(const WebGLContext* webgl, GLenum attachmentPoint);
+
     explicit WebGLFBAttachPoint(WebGLFBAttachPoint&) = default; // Make this private.
 
 public:
     ~WebGLFBAttachPoint();
 
     ////
 
-    void Unlink();
-
-    bool IsDefined() const;
-    bool IsDeleteRequested() const;
+    void Unlink() {
+        Clear();
+    }
 
-    const webgl::FormatUsageInfo* Format() const;
-    uint32_t Samples() const;
-
-    bool HasAlpha() const;
-    bool IsReadableFloat() const;
+    bool HasAttachment() const { return bool(mTexturePtr) | bool(mRenderbufferPtr); }
+    bool IsDeleteRequested() const;
 
     void Clear();
 
-    void SetTexImage(WebGLTexture* tex, TexImageTarget target,
+    void SetTexImage(gl::GLContext* gl, WebGLTexture* tex, TexImageTarget target,
                      GLint level, GLint layer = 0);
-    void SetRenderbuffer(WebGLRenderbuffer* rb);
+    void SetRenderbuffer(gl::GLContext* gl, WebGLRenderbuffer* rb);
 
     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) const;
+    const webgl::ImageInfo* GetImageInfo() 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 Resolve(gl::GLContext* gl) const;
+    void DoAttachment(gl::GLContext* gl) const;
 
     JS::Value GetParameter(WebGLContext* webgl, JSContext* cx,
                            GLenum target, GLenum attachment, GLenum pname,
                            ErrorResult* const out_error) const;
 
-    void OnBackingStoreRespecified() const;
-
     bool IsEquivalentForFeedback(const WebGLFBAttachPoint& other) const {
-        if (!IsDefined() || !other.IsDefined())
+        if (!HasAttachment() | !other.HasAttachment())
             return false;
 
-#define _(X) X == other.X
-        return ( _(mRenderbufferPtr) &&
-                 _(mTexturePtr) &&
-                 _(mTexImageTarget.get()) &&
-                 _(mTexImageLevel) &&
+#define _(X) (X == other.X)
+        return ( _(mRenderbufferPtr) &
+                 _(mTexturePtr) &
+                 _(mTexImageTarget.get()) &
+                 _(mTexImageLevel) &
                  _(mTexImageLayer) );
 #undef _
     }
 
     ////
 
     struct Ordered {
         const WebGLFBAttachPoint& mRef;
 
         explicit Ordered(const WebGLFBAttachPoint& ref)
             : mRef(ref)
         { }
 
         bool operator<(const Ordered& other) const {
-            MOZ_ASSERT(mRef.IsDefined() && other.mRef.IsDefined());
+            MOZ_ASSERT(mRef.HasAttachment() && other.mRef.HasAttachment());
 
 #define ORDER_BY(X) if (X != other.X) return X < other.X;
 
             ORDER_BY(mRef.mRenderbufferPtr)
             ORDER_BY(mRef.mTexturePtr)
             ORDER_BY(mRef.mTexImageTarget.get())
             ORDER_BY(mRef.mTexImageLevel)
             ORDER_BY(mRef.mTexImageLayer)
@@ -142,81 +133,89 @@ public:
     };
 };
 
 class WebGLFramebuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLFramebuffer>
     , public LinkedListElement<WebGLFramebuffer>
     , public SupportsWeakPtr<WebGLFramebuffer>
+    , public CacheInvalidator
 {
     friend class WebGLContext;
 
 public:
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(WebGLFramebuffer)
 
     const GLuint mGLName;
 
 private:
-    uint64_t mNumFBStatusInvals;
+    mutable uint64_t mNumFBStatusInvals = 0;
 
 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;
+    bool mIsFB = false;
 #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<WebGLFBAttachPoint*> mAttachments; // Non-null.
+
     std::vector<const WebGLFBAttachPoint*> mColorDrawBuffers; // Non-null
     const WebGLFBAttachPoint* mColorReadBuffer; // Null if NONE
 
     ////
 
-    struct ResolvedData {
+    struct CompletenessInfo final {
+        const WebGLFramebuffer& fb;
+
+        uint32_t width = 0;
+        uint32_t height = 0;
+
         // IsFeedback
-        std::vector<const WebGLFBAttachPoint*> texDrawBuffers; // Non-null
-        std::set<WebGLFBAttachPoint::Ordered> drawSet;
-        std::set<WebGLFBAttachPoint::Ordered> readSet;
+        std::vector<const WebGLFBAttachPoint*> texAttachments; // Non-null
 
-        explicit ResolvedData(const WebGLFramebuffer& parent);
+        ~CompletenessInfo();
     };
+    friend struct CompletenessInfo;
 
-    mutable UniquePtr<const ResolvedData> mResolvedCompleteData;
+    mutable CacheMaybe<const CompletenessInfo> mCompletenessInfo;
 
     ////
 
 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();
+        InvalidateCaches();
     }
 
 public:
     void Delete();
 
     ////
 
     bool HasDuplicateAttachments() const;
@@ -224,20 +223,20 @@ public:
     bool HasIncompleteAttachments(nsCString* const out_info) const;
     bool AllImageRectsMatch() const;
     bool AllImageSamplesMatch() const;
     FBStatus PrecheckFramebufferStatus(nsCString* const out_info) const;
 
 protected:
     Maybe<WebGLFBAttachPoint*> GetAttachPoint(GLenum attachment); // Fallible
     Maybe<WebGLFBAttachPoint*> GetColorAttachPoint(GLenum attachment); // Fallible
-    void ResolveAttachments() const;
+    void DoDeferredAttachments() const;
     void RefreshDrawBuffers() const;
     void RefreshReadBuffer() const;
-    bool ResolveAttachmentData() const;
+    void ResolveAttachmentData() const;
 
 public:
     void DetachTexture(const WebGLTexture* tex);
     void DetachRenderbuffer(const WebGLRenderbuffer* rb);
     bool ValidateAndInitAttachments() const;
     bool ValidateClearBufferType(GLenum buffer, uint32_t drawBuffer,
                                  GLenum funcType) const;
 
@@ -247,44 +246,30 @@ public:
     ////////////////
     // Getters
 
 #define GETTER(X) const decltype(m##X)& X() const { return m##X; }
 
     GETTER(DepthAttachment)
     GETTER(StencilAttachment)
     GETTER(DepthStencilAttachment)
+    GETTER(Attachments)
     GETTER(ColorDrawBuffers)
     GETTER(ColorReadBuffer)
-    GETTER(ResolvedCompleteData)
 
 #undef GETTER
 
     const auto& ColorAttachment0() const {
         return mColorAttachments[0];
     }
 
-    const auto& AnyDepthAttachment() const {
-        if (mDepthStencilAttachment.IsDefined())
-            return mDepthStencilAttachment;
-        return mDepthAttachment;
-    }
-
-    const auto& AnyStencilAttachment() const {
-        if (mDepthStencilAttachment.IsDefined())
-            return mDepthStencilAttachment;
-        return mStencilAttachment;
-    }
-
     ////////////////
     // Invalidation
 
-    bool IsResolvedComplete() const { return bool(mResolvedCompleteData); }
-    void InvalidateFramebufferStatus();
-    void RefreshResolvedData();
+    const auto* GetCompletenessInfo() const { return mCompletenessInfo.get(); }
 
     ////////////////
     // WebGL funcs
 
     bool IsCheckFramebufferStatusComplete() const {
         return CheckFramebufferStatus() == LOCAL_GL_FRAMEBUFFER_COMPLETE;
     }
 
deleted file mode 100644
--- a/dom/canvas/WebGLFramebufferAttachable.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "WebGLFramebufferAttachable.h"
-
-#include "WebGLFramebuffer.h"
-
-namespace mozilla {
-
-void
-WebGLFramebufferAttachable::MarkAttachment(const WebGLFBAttachPoint& attachment)
-{
-    if (mAttachmentPoints.Contains(&attachment))
-        return; // Already attached. Ignore.
-
-    mAttachmentPoints.AppendElement(&attachment);
-}
-
-void
-WebGLFramebufferAttachable::UnmarkAttachment(const WebGLFBAttachPoint& attachment)
-{
-    const size_t i = mAttachmentPoints.IndexOf(&attachment);
-    if (i == mAttachmentPoints.NoIndex) {
-        MOZ_ASSERT(false, "Is not attached to FB");
-        return;
-    }
-
-    mAttachmentPoints.RemoveElementAt(i);
-}
-
-void
-WebGLFramebufferAttachable::InvalidateStatusOfAttachedFBs() const
-{
-    const size_t count = mAttachmentPoints.Length();
-    for (size_t i = 0; i < count; ++i) {
-        MOZ_ASSERT(mAttachmentPoints[i]->mFB);
-        mAttachmentPoints[i]->mFB->InvalidateFramebufferStatus();
-    }
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/canvas/WebGLFramebufferAttachable.h
+++ /dev/null
@@ -1,27 +0,0 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef WEBGL_FRAMEBUFFER_ATTACHABLE_H_
-#define WEBGL_FRAMEBUFFER_ATTACHABLE_H_
-
-#include "nsTArray.h"
-
-namespace mozilla {
-class WebGLFBAttachPoint;
-
-class WebGLFramebufferAttachable
-{
-    nsTArray<const WebGLFBAttachPoint*> mAttachmentPoints;
-
-public:
-    // Track FBO/Attachment combinations
-    void MarkAttachment(const WebGLFBAttachPoint& attachment);
-    void UnmarkAttachment(const WebGLFBAttachPoint& attachment);
-    void InvalidateStatusOfAttachedFBs() const;
-};
-
-} // namespace mozilla
-
-#endif // !WEBGLFRAMEBUFFERATTACHABLE_H_
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -82,58 +82,16 @@ AssembleName(const nsCString& baseName, 
         out_name->Append('[');
         out_name->AppendInt(uint64_t(arrayIndex));
         out_name->Append(']');
     }
 }
 
 ////
 
-static GLenum
-AttribBaseType(GLenum attribType)
-{
-    switch (attribType) {
-    case LOCAL_GL_FLOAT:
-    case LOCAL_GL_FLOAT_VEC2:
-    case LOCAL_GL_FLOAT_VEC3:
-    case LOCAL_GL_FLOAT_VEC4:
-
-    case LOCAL_GL_FLOAT_MAT2:
-    case LOCAL_GL_FLOAT_MAT2x3:
-    case LOCAL_GL_FLOAT_MAT2x4:
-
-    case LOCAL_GL_FLOAT_MAT3x2:
-    case LOCAL_GL_FLOAT_MAT3:
-    case LOCAL_GL_FLOAT_MAT3x4:
-
-    case LOCAL_GL_FLOAT_MAT4x2:
-    case LOCAL_GL_FLOAT_MAT4x3:
-    case LOCAL_GL_FLOAT_MAT4:
-        return LOCAL_GL_FLOAT;
-
-    case LOCAL_GL_INT:
-    case LOCAL_GL_INT_VEC2:
-    case LOCAL_GL_INT_VEC3:
-    case LOCAL_GL_INT_VEC4:
-        return LOCAL_GL_INT;
-
-    case LOCAL_GL_UNSIGNED_INT:
-    case LOCAL_GL_UNSIGNED_INT_VEC2:
-    case LOCAL_GL_UNSIGNED_INT_VEC3:
-    case LOCAL_GL_UNSIGNED_INT_VEC4:
-        return LOCAL_GL_UNSIGNED_INT;
-
-    default:
-        MOZ_ASSERT(false, "unexpected attrib elemType");
-        return 0;
-    }
-}
-
-////
-
 /*static*/ const webgl::UniformInfo::TexListT*
 webgl::UniformInfo::GetTexList(WebGLActiveInfo* activeInfo)
 {
     const auto& webgl = activeInfo->mWebGL;
 
     switch (activeInfo->mElemType) {
     case LOCAL_GL_SAMPLER_2D:
     case LOCAL_GL_SAMPLER_2D_SHADOW:
@@ -158,19 +116,64 @@ webgl::UniformInfo::GetTexList(WebGLActi
     case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
         return &webgl->mBound2DArrayTextures;
 
     default:
         return nullptr;
     }
 }
 
+static bool
+IsShadowSampler(const GLenum elemType)
+{
+    switch (elemType) {
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+        return true;
+    default:
+        return false;
+    }
+}
+
+static webgl::TextureBaseType
+SamplerBaseType(const GLenum elemType)
+{
+    switch (elemType) {
+    case LOCAL_GL_SAMPLER_2D:
+    case LOCAL_GL_SAMPLER_3D:
+    case LOCAL_GL_SAMPLER_CUBE:
+    case LOCAL_GL_SAMPLER_2D_ARRAY:
+    case LOCAL_GL_SAMPLER_2D_SHADOW:
+    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
+    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
+        return webgl::TextureBaseType::Float;
+
+    case LOCAL_GL_INT_SAMPLER_2D:
+    case LOCAL_GL_INT_SAMPLER_3D:
+    case LOCAL_GL_INT_SAMPLER_CUBE:
+    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
+        return webgl::TextureBaseType::Int;
+
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
+    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
+        return webgl::TextureBaseType::UInt;
+
+    default:
+        return webgl::TextureBaseType::Float; // Will be ignored.
+    }
+}
+
 webgl::UniformInfo::UniformInfo(WebGLActiveInfo* activeInfo)
     : mActiveInfo(activeInfo)
     , mSamplerTexList(GetTexList(activeInfo))
+    , mTexBaseType(SamplerBaseType(mActiveInfo->mElemType))
+    , mIsShadowSampler(IsShadowSampler(mActiveInfo->mElemType))
 {
     if (mSamplerTexList) {
         mSamplerValues.assign(mActiveInfo->mElemCount, 0);
     }
 }
 
 //////////
 
@@ -258,18 +261,17 @@ QueryProgramInfo(WebGLProgram* prog, gl:
 
         ///////
 
         const bool isArray = false;
         const RefPtr<WebGLActiveInfo> activeInfo = new WebGLActiveInfo(webgl, elemCount,
                                                                        elemType, isArray,
                                                                        userName,
                                                                        mappedName);
-        const GLenum baseType = AttribBaseType(elemType);
-        const webgl::AttribInfo attrib = {activeInfo, loc, baseType};
+        const webgl::AttribInfo attrib = {activeInfo, loc};
         info->attribs.push_back(attrib);
 
         if (loc == 0) {
             info->attrib0Active = true;
         }
     }
 
     // Uniforms (can be basically anything)
@@ -457,27 +459,43 @@ webgl::LinkedProgramInfo::~LinkedProgram
     for (auto& cur : uniforms) {
         delete cur;
     }
     for (auto& cur : uniformBlocks) {
         delete cur;
     }
 }
 
+const char*
+webgl::ToString(const webgl::AttribBaseType x)
+{
+    switch (x) {
+    case webgl::AttribBaseType::Float:
+        return "FLOAT";
+    case webgl::AttribBaseType::Int:
+        return "INT";
+    case webgl::AttribBaseType::UInt:
+        return "UINT";
+    case webgl::AttribBaseType::Bool:
+        return "BOOL";
+    }
+    MOZ_CRASH("pacify gcc6 warning");
+}
+
 const webgl::CachedDrawFetchLimits*
 webgl::LinkedProgramInfo::GetDrawFetchLimits() const
 {
     const auto& webgl = prog->mContext;
     const auto& vao = webgl->mBoundVertexArray;
 
     const auto found = mDrawFetchCache.Find(vao);
     if (found)
         return found;
 
-    std::vector<const CacheMapInvalidator*> cacheDeps;
+    std::vector<const CacheInvalidator*> cacheDeps;
     cacheDeps.push_back(vao.get());
     cacheDeps.push_back(&webgl->mGenericVertexAttribTypeInvalidator);
 
     {
         // We have to ensure that every enabled attrib array (not just the active ones)
         // has a non-null buffer.
         uint32_t i = 0;
         for (const auto& cur : vao->mAttribs) {
@@ -499,17 +517,17 @@ webgl::LinkedProgramInfo::GetDrawFetchLi
         const auto& loc = progAttrib.mLoc;
         if (loc == -1)
             continue;
         hasActiveAttrib |= true;
 
         const auto& attribData = vao->mAttribs[loc];
         hasActiveDivisor0 |= (attribData.mDivisor == 0);
 
-        GLenum attribDataBaseType;
+        webgl::AttribBaseType attribDataBaseType;
         if (attribData.mEnabled) {
             MOZ_ASSERT(attribData.mBuf);
             if (attribData.mBuf->IsBoundForTF()) {
                 webgl->ErrorInvalidOperation("Vertex attrib %u's buffer is bound for"
                                              " transform feedback.",
                                               loc);
                 return nullptr;
             }
@@ -529,37 +547,40 @@ webgl::LinkedProgramInfo::GetDrawFetchLi
                 } // If not valid, it overflowed too large, so we're super safe.
             } else {
                 fetchLimits.maxVerts = std::min(fetchLimits.maxVerts, availElems);
             }
         } else {
             attribDataBaseType = webgl->mGenericVertexAttribTypes[loc];
         }
 
-        if (attribDataBaseType != progAttrib.mBaseType) {
-            nsCString progType, dataType;
-            WebGLContext::EnumName(progAttrib.mBaseType, &progType);
-            WebGLContext::EnumName(attribDataBaseType, &dataType);
+        const auto& progBaseType = progAttrib.mActiveInfo->mBaseType;
+        if ((attribDataBaseType != progBaseType) &
+            (progBaseType != webgl::AttribBaseType::Bool))
+        {
+            const auto& dataType = ToString(attribDataBaseType);
+            const auto& progType = ToString(progBaseType);
             webgl->ErrorInvalidOperation("Vertex attrib %u requires data of type %s,"
                                          " but is being supplied with type %s.",
-                                         loc, progType.BeginReading(),
-                                         dataType.BeginReading());
+                                         loc, progType, dataType);
             return nullptr;
         }
     }
 
     if (hasActiveAttrib && !hasActiveDivisor0) {
         webgl->ErrorInvalidOperation("One active vertex attrib (if any are active)"
                                      " must have a divisor of 0.");
         return nullptr;
     }
 
     // --
 
-    return mDrawFetchCache.Insert(vao.get(), std::move(fetchLimits), std::move(cacheDeps));
+    auto entry = mDrawFetchCache.MakeEntry(vao.get(), std::move(fetchLimits));
+    entry->ResetInvalidators(std::move(cacheDeps));
+    return mDrawFetchCache.Insert(std::move(entry));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // WebGLProgram
 
 WebGLProgram::WebGLProgram(WebGLContext* webgl)
     : WebGLRefCountedObject(webgl)
     , mGLName(webgl->gl->fCreateProgram())
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -12,17 +12,17 @@
 #include <vector>
 
 #include "mozilla/LinkedList.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/WeakPtr.h"
 #include "nsString.h"
 #include "nsWrapperCache.h"
 
-#include "CacheMap.h"
+#include "CacheInvalidator.h"
 #include "WebGLContext.h"
 #include "WebGLObjectModel.h"
 
 namespace mozilla {
 class ErrorResult;
 class WebGLActiveInfo;
 class WebGLProgram;
 class WebGLShader;
@@ -31,29 +31,33 @@ class WebGLUniformLocation;
 namespace dom {
 template<typename> struct Nullable;
 class OwningUnsignedLongOrUint32ArrayOrBoolean;
 template<typename> class Sequence;
 } // namespace dom
 
 namespace webgl {
 
+enum class TextureBaseType : uint8_t;
+
 struct AttribInfo final
 {
     const RefPtr<WebGLActiveInfo> mActiveInfo;
     const GLint mLoc; // -1 for active built-ins
-    const GLenum mBaseType;
 };
 
 struct UniformInfo final
 {
     typedef decltype(WebGLContext::mBound2DTextures) TexListT;
 
     const RefPtr<WebGLActiveInfo> mActiveInfo;
     const TexListT* const mSamplerTexList;
+    const webgl::TextureBaseType mTexBaseType;
+    const bool mIsShadowSampler;
+
     std::vector<uint32_t> mSamplerValues;
 
 protected:
     static const TexListT*
     GetTexList(WebGLActiveInfo* activeInfo);
 
 public:
     explicit UniformInfo(WebGLActiveInfo* activeInfo);
@@ -79,16 +83,17 @@ struct UniformBlockInfo final
 struct CachedDrawFetchLimits final {
     uint64_t maxVerts;
     uint64_t maxInstances;
 };
 
 struct LinkedProgramInfo final
     : public RefCounted<LinkedProgramInfo>
     , public SupportsWeakPtr<LinkedProgramInfo>
+    , public CacheInvalidator
 {
     friend class mozilla::WebGLProgram;
 
     MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(LinkedProgramInfo)
 
     //////
 
@@ -109,18 +114,18 @@ struct LinkedProgramInfo final
 
     //////
 
     // The maps for the frag data names to the translated names.
     std::map<nsCString, const nsCString> fragDataMap;
 
     //////
 
-    mutable CacheMap<const WebGLVertexArray*,
-                     CachedDrawFetchLimits> mDrawFetchCache;
+    mutable CacheWeakMap<const WebGLVertexArray*,
+                         CachedDrawFetchLimits> mDrawFetchCache;
 
     const CachedDrawFetchLimits* GetDrawFetchLimits() const;
 
     //////
 
     explicit LinkedProgramInfo(WebGLProgram* prog);
     ~LinkedProgramInfo();
 
--- a/dom/canvas/WebGLRenderbuffer.cpp
+++ b/dom/canvas/WebGLRenderbuffer.cpp
@@ -47,48 +47,39 @@ EmulatePackedDepthStencil(gl::GLContext*
     return !gl->IsSupported(gl::GLFeature::packed_depth_stencil);
 }
 
 WebGLRenderbuffer::WebGLRenderbuffer(WebGLContext* webgl)
     : WebGLRefCountedObject(webgl)
     , mPrimaryRB( DoCreateRenderbuffer(webgl->gl) )
     , mEmulatePackedDepthStencil( EmulatePackedDepthStencil(webgl->gl) )
     , mSecondaryRB(0)
-    , mFormat(nullptr)
-    , mSamples(0)
-    , mImageDataStatus(WebGLImageDataStatus::NoImageData)
     , mHasBeenBound(false)
 {
     mContext->mRenderbuffers.insertBack(this);
+
+    // Bind our RB, or we might end up calling FramebufferRenderbuffer before we ever call
+    // BindRenderbuffer, since webgl.bindRenderbuffer doesn't actually call
+    // glBindRenderbuffer anymore.
+    mContext->gl->fBindRenderbuffer(LOCAL_GL_RENDERBUFFER, mPrimaryRB);
 }
 
 void
 WebGLRenderbuffer::Delete()
 {
     mContext->gl->fDeleteRenderbuffers(1, &mPrimaryRB);
-    if (mSecondaryRB)
+    if (mSecondaryRB) {
         mContext->gl->fDeleteRenderbuffers(1, &mSecondaryRB);
+    }
+
+    mImageInfo = webgl::ImageInfo();
 
     LinkedListElement<WebGLRenderbuffer>::removeFrom(mContext->mRenderbuffers);
 }
 
-int64_t
-WebGLRenderbuffer::MemoryUsage() const
-{
-    // If there is no defined format, we're not taking up any memory
-    if (!mFormat)
-        return 0;
-
-    const auto bytesPerPixel = mFormat->format->estimatedBytesPerPixel;
-    const int64_t pixels = int64_t(mWidth) * int64_t(mHeight);
-
-    const int64_t totalSize = pixels * bytesPerPixel;
-    return totalSize;
-}
-
 static GLenum
 DoRenderbufferStorageMaybeMultisample(gl::GLContext* gl, GLsizei samples,
                                       GLenum internalFormat, GLsizei width,
                                       GLsizei height)
 {
     MOZ_ASSERT_IF(samples >= 1, gl->IsSupported(gl::GLFeature::framebuffer_multisample));
 
     // Certain OpenGL ES renderbuffer formats may not exist on desktop OpenGL.
@@ -209,55 +200,52 @@ WebGLRenderbuffer::RenderbufferStorage(u
     const GLenum error = DoRenderbufferStorage(samples, usage, width, height);
     if (error) {
         mContext->GenerateWarning("Unexpected error %s", EnumString(error).c_str());
         return;
     }
 
     mContext->OnDataAllocCall();
 
-    mSamples = samples;
-    mFormat = usage;
-    mWidth = width;
-    mHeight = height;
-    mImageDataStatus = WebGLImageDataStatus::UninitializedImageData;
-
-    InvalidateStatusOfAttachedFBs();
+    const uint32_t depth = 1;
+    const bool hasData = false;
+    mImageInfo = { usage, width, height, depth, hasData, uint8_t(samples) };
+    InvalidateCaches();
 }
 
 void
-WebGLRenderbuffer::DoFramebufferRenderbuffer(FBTarget target, GLenum attachment) const
+WebGLRenderbuffer::DoFramebufferRenderbuffer(const GLenum attachment) const
 {
     gl::GLContext* gl = mContext->gl;
 
     if (attachment == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
         const GLuint stencilRB = (mSecondaryRB ? mSecondaryRB : mPrimaryRB);
-        gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_DEPTH_ATTACHMENT,
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                      LOCAL_GL_RENDERBUFFER, mPrimaryRB);
-        gl->fFramebufferRenderbuffer(target.get(), LOCAL_GL_STENCIL_ATTACHMENT,
+        gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
                                      LOCAL_GL_RENDERBUFFER, stencilRB);
         return;
     }
 
-    gl->fFramebufferRenderbuffer(target.get(), attachment,
+    gl->fFramebufferRenderbuffer(LOCAL_GL_FRAMEBUFFER, attachment,
                                  LOCAL_GL_RENDERBUFFER, mPrimaryRB);
 }
 
 GLint
 WebGLRenderbuffer::GetRenderbufferParameter(RBTarget target,
                                             RBParam pname) const
 {
     gl::GLContext* gl = mContext->gl;
 
     switch (pname.get()) {
     case LOCAL_GL_RENDERBUFFER_STENCIL_SIZE:
-        if (!mFormat)
+        if (!mImageInfo.mFormat)
             return 0;
 
-        if (!mFormat->format->s)
+        if (!mImageInfo.mFormat->format->s)
             return 0;
 
         return 8;
 
     case LOCAL_GL_RENDERBUFFER_SAMPLES:
     case LOCAL_GL_RENDERBUFFER_WIDTH:
     case LOCAL_GL_RENDERBUFFER_HEIGHT:
     case LOCAL_GL_RENDERBUFFER_RED_SIZE:
@@ -270,18 +258,18 @@ WebGLRenderbuffer::GetRenderbufferParame
             GLint i = 0;
             gl->fGetRenderbufferParameteriv(target.get(), pname.get(), &i);
             return i;
         }
 
     case LOCAL_GL_RENDERBUFFER_INTERNAL_FORMAT:
         {
             GLenum ret = LOCAL_GL_RGBA4;
-            if (mFormat) {
-                ret = mFormat->format->sizedFormat;
+            if (mImageInfo.mFormat) {
+                ret = mImageInfo.mFormat->format->sizedFormat;
 
                 if (!mContext->IsWebGL2() && ret == LOCAL_GL_DEPTH24_STENCIL8) {
                     ret = LOCAL_GL_DEPTH_STENCIL;
                 }
             }
             return ret;
         }
     }
--- a/dom/canvas/WebGLRenderbuffer.h
+++ b/dom/canvas/WebGLRenderbuffer.h
@@ -4,91 +4,73 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WEBGL_RENDERBUFFER_H_
 #define WEBGL_RENDERBUFFER_H_
 
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 
-#include "WebGLFramebufferAttachable.h"
+#include "CacheInvalidator.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
+#include "WebGLTexture.h"
 
 namespace mozilla {
 namespace webgl {
 struct FormatUsageInfo;
 }
 
 class WebGLRenderbuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLRenderbuffer>
     , public LinkedListElement<WebGLRenderbuffer>
     , public WebGLRectangleObject
-    , public WebGLFramebufferAttachable
+    , public CacheInvalidator
 {
     friend class WebGLContext;
     friend class WebGLFramebuffer;
     friend class WebGLFBAttachPoint;
 
 public:
     const GLuint mPrimaryRB;
 protected:
     const bool mEmulatePackedDepthStencil;
     GLuint mSecondaryRB;
-    const webgl::FormatUsageInfo* mFormat;
-    GLsizei mSamples;
-
-    WebGLImageDataStatus mImageDataStatus;
-
     bool mHasBeenBound;
+    webgl::ImageInfo mImageInfo;
 
 public:
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLRenderbuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLRenderbuffer)
 
     explicit WebGLRenderbuffer(WebGLContext* webgl);
 
     void Delete();
 
-    bool HasUninitializedImageData() const {
-        MOZ_ASSERT(mImageDataStatus != WebGLImageDataStatus::NoImageData);
-        return mImageDataStatus == WebGLImageDataStatus::UninitializedImageData;
-    }
-
-    bool IsDefined() const {
-        if (!mFormat) {
-            MOZ_ASSERT(!mWidth && !mHeight);
-            return false;
-        }
-        return true;
-    }
-
-    GLsizei Samples() const { return mSamples; }
-
-    const webgl::FormatUsageInfo* Format() const { return mFormat; }
-
-    int64_t MemoryUsage() const;
+    const auto& ImageInfo() const { return mImageInfo; }
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     void RenderbufferStorage(uint32_t samples, GLenum internalFormat, uint32_t width,
                              uint32_t height);
     // Only handles a subset of `pname`s.
     GLint GetRenderbufferParameter(RBTarget target, RBParam pname) const;
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
+    auto MemoryUsage() const { return mImageInfo.MemoryUsage(); }
+
 protected:
     ~WebGLRenderbuffer() {
         DeleteOnce();
     }
 
-    void DoFramebufferRenderbuffer(FBTarget target, GLenum attachment) const;
+    void DoFramebufferRenderbuffer(GLenum attachment) const;
     GLenum DoRenderbufferStorage(uint32_t samples, const webgl::FormatUsageInfo* format,
                                  uint32_t width, uint32_t height);
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_RENDERBUFFER_H_
--- a/dom/canvas/WebGLSampler.cpp
+++ b/dom/canvas/WebGLSampler.cpp
@@ -138,16 +138,17 @@ ValidateSamplerParameterParams(WebGLCont
 }
 
 void
 WebGLSampler::SamplerParameter(GLenum pname, const FloatOrInt& param)
 {
     if (!ValidateSamplerParameterParams(mContext, pname, param))
         return;
 
+    bool invalidate = true;
     switch (pname) {
     case LOCAL_GL_TEXTURE_MIN_FILTER:
         mState.minFilter = param.i;
         break;
 
     case LOCAL_GL_TEXTURE_MAG_FILTER:
         mState.magFilter = param.i;
         break;
@@ -160,22 +161,22 @@ WebGLSampler::SamplerParameter(GLenum pn
         mState.wrapT = param.i;
         break;
 
     case LOCAL_GL_TEXTURE_COMPARE_MODE:
         mState.compareMode = param.i;
         break;
 
     default:
+        invalidate = false;
         break;
     }
 
-    for (uint32_t i = 0; i < mContext->mBoundSamplers.Length(); ++i) {
-        if (this == mContext->mBoundSamplers[i])
-            mContext->InvalidateResolveCacheForTextureWithTexUnit(i);
+    if (invalidate) {
+        InvalidateCaches();
     }
 
     ////
 
     if (param.isFloat) {
         mContext->gl->fSamplerParameterf(mGLName, pname, param.f);
     } else {
         mContext->gl->fSamplerParameteri(mGLName, pname, param.i);
--- a/dom/canvas/WebGLSampler.h
+++ b/dom/canvas/WebGLSampler.h
@@ -13,16 +13,17 @@
 #include "WebGLTexture.h"
 
 namespace mozilla {
 
 class WebGLSampler final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLSampler>
     , public LinkedListElement<WebGLSampler>
+    , public CacheInvalidator
 {
     friend class WebGLContext2;
     friend class WebGLTexture;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLSampler)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLSampler)
 
 public:
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -16,142 +16,95 @@
 #include "WebGLContext.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFormats.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLSampler.h"
 #include "WebGLTexelConversions.h"
 
 namespace mozilla {
-
-/*static*/ const WebGLTexture::ImageInfo WebGLTexture::ImageInfo::kUndefined;
-
-////////////////////////////////////////
-
-template <typename T>
-static inline T&
-Mutable(const T& x)
-{
-    return const_cast<T&>(x);
-}
-
-void
-WebGLTexture::ImageInfo::Clear()
-{
-    if (!IsDefined())
-        return;
-
-    OnRespecify();
-
-    Mutable(mFormat) = LOCAL_GL_NONE;
-    Mutable(mWidth) = 0;
-    Mutable(mHeight) = 0;
-    Mutable(mDepth) = 0;
-
-    MOZ_ASSERT(!IsDefined());
-}
-
-void
-WebGLTexture::ImageInfo::Set(const ImageInfo& a)
-{
-    MOZ_ASSERT(a.IsDefined());
-
-    Mutable(mFormat) = a.mFormat;
-    Mutable(mWidth) = a.mWidth;
-    Mutable(mHeight) = a.mHeight;
-    Mutable(mDepth) = a.mDepth;
+namespace webgl {
 
-    mIsDataInitialized = a.mIsDataInitialized;
-
-    // But *don't* transfer mAttachPoints!
-    MOZ_ASSERT(a.mAttachPoints.empty());
-    OnRespecify();
-}
-
-bool
-WebGLTexture::ImageInfo::IsPowerOfTwo() const
-{
-    return mozilla::IsPowerOfTwo(mWidth) &&
-           mozilla::IsPowerOfTwo(mHeight) &&
-           mozilla::IsPowerOfTwo(mDepth);
-}
-
-void
-WebGLTexture::ImageInfo::AddAttachPoint(WebGLFBAttachPoint* attachPoint)
-{
-    const auto pair = mAttachPoints.insert(attachPoint);
-    DebugOnly<bool> didInsert = pair.second;
-    MOZ_ASSERT(didInsert);
-}
-
-void
-WebGLTexture::ImageInfo::RemoveAttachPoint(WebGLFBAttachPoint* attachPoint)
-{
-    DebugOnly<size_t> numElemsErased = mAttachPoints.erase(attachPoint);
-    MOZ_ASSERT_IF(IsDefined(), numElemsErased == 1);
-}
-
-void
-WebGLTexture::ImageInfo::OnRespecify() const
-{
-    for (auto cur : mAttachPoints) {
-        cur->OnBackingStoreRespecified();
-    }
-}
+/*static*/ const ImageInfo ImageInfo::kUndefined;
 
 size_t
-WebGLTexture::ImageInfo::MemoryUsage() const
+ImageInfo::MemoryUsage() const
 {
     if (!IsDefined())
         return 0;
 
-    const auto bytesPerTexel = mFormat->format->estimatedBytesPerPixel;
-    return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * bytesPerTexel;
+    size_t samples = mSamples;
+    if (!samples) {
+        samples = 1;
+    }
+
+    const size_t bytesPerTexel = mFormat->format->estimatedBytesPerPixel;
+    return size_t(mWidth) * size_t(mHeight) * size_t(mDepth) * samples * bytesPerTexel;
 }
 
-void
-WebGLTexture::ImageInfo::SetIsDataInitialized(bool isDataInitialized, WebGLTexture* tex)
+Maybe<ImageInfo>
+ImageInfo::NextMip(const GLenum target) const
 {
-    MOZ_ASSERT(tex);
-    MOZ_ASSERT(this >= &tex->mImageInfoArr[0]);
-    MOZ_ASSERT(this < &tex->mImageInfoArr[kMaxLevelCount * kMaxFaceCount]);
+    MOZ_ASSERT(IsDefined());
+
+    auto next = *this;
+
+    if (target == LOCAL_GL_TEXTURE_3D) {
+        if (mWidth <= 1 &&
+            mHeight <= 1 &&
+            mDepth <= 1)
+        {
+            return {};
+        }
 
-    mIsDataInitialized = isDataInitialized;
-    tex->InvalidateResolveCache();
+        next.mDepth = std::max(uint32_t(1), next.mDepth / 2);
+    } else {
+        // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
+        if (mWidth <= 1 &&
+            mHeight <= 1)
+        {
+            return {};
+        }
+    }
+
+    next.mWidth = std::max(uint32_t(1), next.mWidth / 2);
+    next.mHeight = std::max(uint32_t(1), next.mHeight / 2);
+    return Some(next);
 }
 
+} // namespace webgl
+
 ////////////////////////////////////////
 
 JSObject*
-WebGLTexture::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) {
+WebGLTexture::WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto)
+{
     return dom::WebGLTexture_Binding::Wrap(cx, this, givenProto);
 }
 
 WebGLTexture::WebGLTexture(WebGLContext* webgl, GLuint tex)
     : WebGLRefCountedObject(webgl)
     , mGLName(tex)
     , mTarget(LOCAL_GL_NONE)
     , mFaceCount(0)
     , mImmutable(false)
     , mImmutableLevelCount(0)
     , mBaseMipmapLevel(0)
     , mMaxMipmapLevel(1000)
-    , mIsResolved(false)
-    , mResolved_FakeBlack(FakeBlackType::None)
-    , mResolved_Swizzle(nullptr)
 {
     mContext->mTextures.insertBack(this);
 }
 
 void
 WebGLTexture::Delete()
 {
     for (auto& cur : mImageInfoArr) {
-        cur.Clear();
+        cur = webgl::ImageInfo();
     }
+    InvalidateCaches();
 
     mContext->gl->fDeleteTextures(1, &mGLName);
 
     LinkedListElement<WebGLTexture>::removeFrom(mContext->mTextures);
 }
 
 size_t
 WebGLTexture::MemoryUsage() const
@@ -161,250 +114,266 @@ WebGLTexture::MemoryUsage() const
 
     size_t accum = 0;
     for (const auto& cur : mImageInfoArr) {
         accum += cur.MemoryUsage();
     }
     return accum;
 }
 
-void
-WebGLTexture::SetImageInfo(ImageInfo* target,
-                           const ImageInfo& newInfo)
-{
-    target->Set(newInfo);
-
-    InvalidateResolveCache();
-}
+// ---------------------------
 
 void
-WebGLTexture::SetImageInfosAtLevel(uint32_t level,
-                                   const ImageInfo& newInfo)
+WebGLTexture::PopulateMipChain(const uint32_t maxLevel)
 {
-    for (uint8_t i = 0; i < mFaceCount; i++) {
-        ImageInfoAtFace(i, level).Set(newInfo);
-    }
-
-    InvalidateResolveCache();
-}
-
-bool
-WebGLTexture::IsMipmapComplete(uint32_t texUnit,
-                               bool* const out_initFailed)
-{
-    *out_initFailed = false;
-    // GLES 3.0.4, p161
+    // Used by GenerateMipmap and TexStorage.
+    // Populates based on mBaseMipmapLevel.
 
-    uint32_t maxLevel;
-    if (!MaxEffectiveMipmapLevel(texUnit, &maxLevel))
-        return false;
-
-    // "* `level_base <= level_max`"
-    if (mBaseMipmapLevel > maxLevel)
-        return false;
-
-    // Make a copy so we can modify it.
-    const ImageInfo& baseImageInfo = BaseImageInfo();
+    auto ref = BaseImageInfo();
+    MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
 
-    // Reference dimensions based on the current level.
-    uint32_t refWidth = baseImageInfo.mWidth;
-    uint32_t refHeight = baseImageInfo.mHeight;
-    uint32_t refDepth = baseImageInfo.mDepth;
-    MOZ_ASSERT(refWidth && refHeight && refDepth);
-
-    for (uint32_t level = mBaseMipmapLevel; level <= maxLevel; level++) {
-        if (!EnsureLevelInitialized(level)) {
-            *out_initFailed = true;
-            return false;
-        }
-
+    for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
+        // GLES 3.0.4, p161
         // "A cube map texture is mipmap complete if each of the six texture images,
         //  considered individually, is mipmap complete."
 
         for (uint8_t face = 0; face < mFaceCount; face++) {
-            const ImageInfo& cur = ImageInfoAtFace(face, level);
+            auto& cur = ImageInfoAtFace(face, level);
+            cur = ref;
+        }
+
+        const auto next = ref.NextMip(mTarget.get());
+        if (!next)
+            break;
+        ref = next.ref();
+    }
+    InvalidateCaches();
+}
+
+static bool
+ZeroTextureData(const WebGLContext* webgl, GLuint tex,
+                TexImageTarget target, uint32_t level,
+                const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height,
+                uint32_t depth);
+
+bool
+WebGLTexture::IsMipAndCubeComplete(const uint32_t maxLevel,
+                                   bool* const out_initFailed) const
+{
+    *out_initFailed = false;
+
+    // Reference dimensions based on baseLevel.
+    auto ref = BaseImageInfo();
+    MOZ_ASSERT(ref.mWidth && ref.mHeight && ref.mDepth);
+
+    for (auto level = mBaseMipmapLevel; level <= maxLevel; ++level) {
+        // GLES 3.0.4, p161
+        // "A cube map texture is mipmap complete if each of the six texture images,
+        //  considered individually, is mipmap complete."
+
+        for (uint8_t face = 0; face < mFaceCount; face++) {
+            auto& cur = ImageInfoAtFace(face, level);
 
             // "* The set of mipmap arrays `level_base` through `q` (where `q` is defined
             //    the "Mipmapping" discussion of section 3.8.10) were each specified with
             //    the same effective internal format."
 
             // "* The dimensions of the arrays follow the sequence described in the
             //    "Mipmapping" discussion of section 3.8.10."
 
-            if (cur.mWidth != refWidth ||
-                cur.mHeight != refHeight ||
-                cur.mDepth != refDepth ||
-                cur.mFormat != baseImageInfo.mFormat)
+            if (cur.mWidth != ref.mWidth ||
+                cur.mHeight != ref.mHeight ||
+                cur.mDepth != ref.mDepth ||
+                cur.mFormat != ref.mFormat)
             {
                 return false;
             }
+
+            if (MOZ_UNLIKELY( !cur.mHasData )) {
+                auto imageTarget = mTarget.get();
+                if (imageTarget == LOCAL_GL_TEXTURE_CUBE_MAP) {
+                    imageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + face;
+                }
+                if (!ZeroTextureData(mContext, mGLName, imageTarget, level,
+                                     cur.mFormat, cur.mWidth, cur.mHeight, cur.mDepth))
+                {
+                    mContext->ErrorOutOfMemory("Failed to zero tex image data.");
+                    *out_initFailed = true;
+                    return false;
+                }
+                cur.mHasData = true;
+            }
         }
 
-        // GLES 3.0.4, p158:
-        // "[...] until the last array is reached with dimension 1 x 1 x 1."
-        if (mTarget == LOCAL_GL_TEXTURE_3D) {
-            if (refWidth == 1 &&
-                refHeight == 1 &&
-                refDepth == 1)
-            {
-                break;
-            }
-
-            refDepth = std::max(uint32_t(1), refDepth / 2);
-        } else {
-            // TEXTURE_2D_ARRAY may have depth != 1, but that's normal.
-            if (refWidth == 1 &&
-                refHeight == 1)
-            {
-                break;
-            }
-        }
-
-        refWidth  = std::max(uint32_t(1), refWidth  / 2);
-        refHeight = std::max(uint32_t(1), refHeight / 2);
+        const auto next = ref.NextMip(mTarget.get());
+        if (!next)
+            break;
+        ref = next.ref();
     }
 
     return true;
 }
 
-bool
-WebGLTexture::IsCubeComplete() const
+Maybe<const WebGLTexture::CompletenessInfo>
+WebGLTexture::CalcCompletenessInfo() const
 {
-    // GLES 3.0.4, p161
-    // "[...] a cube map texture is cube complete if the following conditions all hold
-    //  true:
-    //  * The `level_base` arrays of each of the six texture images making up the cube map
-    //    have identical, positive, and square dimensions.
-    //  * The `level_base` arrays were each specified with the same effective internal
-    //    format."
-
-    // Note that "cube complete" does not imply "mipmap complete".
-
-    const ImageInfo& reference = BaseImageInfo();
-    if (!reference.IsDefined())
-        return false;
-
-    auto refWidth = reference.mWidth;
-    auto refFormat = reference.mFormat;
+    Maybe<CompletenessInfo> ret = Some(CompletenessInfo());
 
-    for (uint8_t face = 0; face < mFaceCount; face++) {
-        const ImageInfo& cur = ImageInfoAtFace(face, mBaseMipmapLevel);
-        if (!cur.IsDefined())
-            return false;
-
-        MOZ_ASSERT(cur.mDepth == 1);
-        if (cur.mFormat != refFormat || // Check effective formats.
-            cur.mWidth != refWidth ||   // Check both width and height against refWidth to
-            cur.mHeight != refWidth)    // to enforce positive and square dimensions.
-        {
-            return false;
-        }
-    }
+    // -
 
-    return true;
-}
-
-bool
-WebGLTexture::IsComplete(uint32_t texUnit,
-                         const char** const out_reason, bool* const out_initFailed)
-{
-    *out_initFailed = false;
-
-    const auto maxLevel = kMaxLevelCount - 1;
-    if (mBaseMipmapLevel > maxLevel) {
-        *out_reason = "`level_base` too high.";
-        return false;
+    if (mBaseMipmapLevel > kMaxLevelCount - 1) {
+        ret->incompleteReason = "`level_base` too high.";
+        return ret;
     }
 
     // Texture completeness is established at GLES 3.0.4, p160-161.
     // "[A] texture is complete unless any of the following conditions hold true:"
 
     // "* Any dimension of the `level_base` array is not positive."
-    const ImageInfo& baseImageInfo = BaseImageInfo();
+    const auto& baseImageInfo = ImageInfoAtFace(0, mBaseMipmapLevel);
     if (!baseImageInfo.IsDefined()) {
         // In case of undefined texture image, we don't print any message because this is
         // a very common and often legitimate case (asynchronous texture loading).
-        *out_reason = nullptr;
-        return false;
+        ret->incompleteReason = nullptr;
+        return ret;
     }
 
     if (!baseImageInfo.mWidth || !baseImageInfo.mHeight || !baseImageInfo.mDepth) {
-        *out_reason = "The dimensions of `level_base` are not all positive.";
-        return false;
+        ret->incompleteReason = "The dimensions of `level_base` are not all positive.";
+        return ret;
     }
 
     // "* The texture is a cube map texture, and is not cube complete."
-    if (IsCubeMap() && !IsCubeComplete()) {
-        *out_reason = "Cubemaps must be \"cube complete\".";
-        return false;
+    bool initFailed = false;
+    if (!IsMipAndCubeComplete(mBaseMipmapLevel, &initFailed)) {
+        if (initFailed)
+            return {};
+
+        // Can only fail if not cube-complete.
+        ret->incompleteReason = "Cubemaps must be \"cube complete\".";
+        return ret;
+    }
+    ret->levels = 1;
+    ret->usage = baseImageInfo.mFormat;
+    RefreshSwizzle();
+
+    ret->powerOfTwo = mozilla::IsPowerOfTwo(baseImageInfo.mWidth) &&
+                      mozilla::IsPowerOfTwo(baseImageInfo.mHeight);
+    if (mTarget == LOCAL_GL_TEXTURE_3D) {
+        ret->powerOfTwo &= mozilla::IsPowerOfTwo(baseImageInfo.mDepth);
     }
 
-    const auto* samplingState = &mSamplingState;
-    const auto& sampler = mContext->mBoundSamplers[texUnit];
-    if (sampler) {
-        samplingState = &(sampler->State());
+    // -
+
+    if (!mContext->IsWebGL2() &&
+        !ret->powerOfTwo)
+    {
+        // WebGL 1 mipmaps require POT.
+        ret->incompleteReason = "Mipmapping requires power-of-two sizes.";
+        return ret;
+    }
+
+    // "* `level_base <= level_max`"
+
+    const auto maxLevel = EffectiveMaxLevel();
+    if (mBaseMipmapLevel > maxLevel) {
+        ret->incompleteReason = "`level_base > level_max`.";
+        return ret;
     }
 
-    const auto& minFilter = samplingState->minFilter;
-    const auto& magFilter = samplingState->magFilter;
+    if (!IsMipAndCubeComplete(maxLevel, &initFailed)) {
+        if (initFailed)
+            return {};
+
+        ret->incompleteReason = "Bad mipmap dimension or format.";
+        return ret;
+    }
+    ret->levels = maxLevel - mBaseMipmapLevel + 1;
+    ret->mipmapComplete = true;
+
+    // -
+
+    return ret;
+}
+
+Maybe<const webgl::SampleableInfo>
+WebGLTexture::CalcSampleableInfo(const WebGLSampler* const sampler) const
+{
+    Maybe<webgl::SampleableInfo> ret = Some(webgl::SampleableInfo());
+
+    const auto completeness = CalcCompletenessInfo();
+    if (!completeness)
+        return {};
 
-    // "* The minification filter requires a mipmap (is neither NEAREST nor LINEAR) and
-    //    the texture is not mipmap complete."
-    const bool requiresMipmap = (minFilter != LOCAL_GL_NEAREST &&
-                                 minFilter != LOCAL_GL_LINEAR);
-    if (requiresMipmap && !IsMipmapComplete(texUnit, out_initFailed)) {
-        if (*out_initFailed)
-            return false;
+    ret->incompleteReason = completeness->incompleteReason;
+
+    if (!completeness->levels)
+        return ret;
 
-        *out_reason = "Because the minification filter requires mipmapping, the texture"
-                      " must be \"mipmap complete\".";
-        return false;
+    const auto* sampling = &mSamplingState;
+    if (sampler) {
+        sampling = &sampler->State();
     }
+    const auto isDepthTex = bool(completeness->usage->format->d);
+    ret->isDepthTexCompare = isDepthTex & bool(sampling->compareMode.get());
+    // Because if it's not a depth texture, we always ignore compareMode.
+
+    const auto& minFilter = sampling->minFilter;
+    const auto& magFilter = sampling->magFilter;
+
+    // -
+
+    const bool needsMips = (minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST ||
+                            minFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR ||
+                            minFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
+                            minFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR);
+    if (needsMips & !completeness->mipmapComplete)
+        return ret;
 
     const bool isMinFilteringNearest = (minFilter == LOCAL_GL_NEAREST ||
                                         minFilter == LOCAL_GL_NEAREST_MIPMAP_NEAREST);
     const bool isMagFilteringNearest = (magFilter == LOCAL_GL_NEAREST);
     const bool isFilteringNearestOnly = (isMinFilteringNearest && isMagFilteringNearest);
     if (!isFilteringNearestOnly) {
-        auto formatUsage = baseImageInfo.mFormat;
-        auto format = formatUsage->format;
-
-        bool isFilterable = formatUsage->isFilterable;
+        bool isFilterable = completeness->usage->isFilterable;
 
         // "* The effective internal format specified for the texture arrays is a sized
         //    internal depth or depth and stencil format, the value of
         //    TEXTURE_COMPARE_MODE is NONE[1], and either the magnification filter is not
         //    NEAREST, or the minification filter is neither NEAREST nor
         //    NEAREST_MIPMAP_NEAREST."
         // [1]: This sounds suspect, but is explicitly noted in the change log for GLES
         //      3.0.1:
         //      "* Clarify that a texture is incomplete if it has a depth component, no
         //         shadow comparison, and linear filtering (also Bug 9481)."
-        if (format->d && samplingState->compareMode != LOCAL_GL_NONE) {
+        // In short, depth formats are not filterable, but shadow-samplers are.
+        if (ret->isDepthTexCompare) {
             isFilterable = true;
         }
 
         // "* The effective internal format specified for the texture arrays is a sized
         //    internal color format that is not texture-filterable, and either the
         //    magnification filter is not NEAREST or the minification filter is neither
         //    NEAREST nor NEAREST_MIPMAP_NEAREST."
         // Since all (GLES3) unsized color formats are filterable just like their sized
         // equivalents, we don't have to care whether its sized or not.
         if (!isFilterable) {
-            *out_reason = "Because minification or magnification filtering is not NEAREST"
-                          " or NEAREST_MIPMAP_NEAREST, and the texture's format must be"
-                          " \"texture-filterable\".";
-            return false;
+            ret->incompleteReason = "Minification or magnification filtering is not"
+                                    " NEAREST or NEAREST_MIPMAP_NEAREST, and the"
+                                    " texture's format is not \"texture-filterable\".";
+            return ret;
         }
     }
 
     // Texture completeness is effectively (though not explicitly) amended for GLES2 by
     // the "Texture Access" section under $3.8 "Fragment Shaders". This also applies to
     // vertex shaders, as noted on GLES 2.0.25, p41.
-    if (!mContext->IsWebGL2()) {
+    if (!mContext->IsWebGL2() &&
+        !completeness->powerOfTwo)
+    {
         // GLES 2.0.25, p87-88:
         // "Calling a sampler from a fragment shader will return (R,G,B,A)=(0,0,0,1) if
         //  any of the following conditions are true:"
 
         // "* A two-dimensional sampler is called, the minification filter is one that
         //    requires a mipmap[...], and the sampler's associated texture object is not
         //    complete[.]"
         // (already covered)
@@ -416,232 +385,197 @@ WebGLTexture::IsComplete(uint32_t texUni
 
         // "* A two-dimensional sampler is called, the corresponding texture image is a
         //    non-power-of-two image[...], and either the texture wrap mode is not
         //    CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor LINEAR."
 
         // "* A cube map sampler is called, any of the corresponding texture images are
         //    non-power-of-two images, and either the texture wrap mode is not
         //    CLAMP_TO_EDGE, or the minification filter is neither NEAREST nor LINEAR."
-        if (!baseImageInfo.IsPowerOfTwo()) {
-            // "either the texture wrap mode is not CLAMP_TO_EDGE"
-            if (samplingState->wrapS != LOCAL_GL_CLAMP_TO_EDGE ||
-                samplingState->wrapT != LOCAL_GL_CLAMP_TO_EDGE)
-            {
-                *out_reason = "Non-power-of-two textures must have a wrap mode of"
-                              " CLAMP_TO_EDGE.";
-                return false;
-            }
-
-            // "or the minification filter is neither NEAREST nor LINEAR"
-            if (requiresMipmap) {
-                *out_reason = "Mipmapping requires power-of-two textures.";
-                return false;
-            }
+        // "either the texture wrap mode is not CLAMP_TO_EDGE"
+        if (sampling->wrapS != LOCAL_GL_CLAMP_TO_EDGE ||
+            sampling->wrapT != LOCAL_GL_CLAMP_TO_EDGE)
+        {
+            ret->incompleteReason = "Non-power-of-two textures must have a wrap mode of"
+                                    " CLAMP_TO_EDGE.";
+            return ret;
         }
 
         // "* A cube map sampler is called, and either the corresponding cube map texture
         //    image is not cube complete, or TEXTURE_MIN_FILTER is one that requires a
         //    mipmap and the texture is not mipmap cube complete."
         // (already covered)
     }
 
-    if (!EnsureLevelInitialized(mBaseMipmapLevel)) {
-        *out_initFailed = true;
-        return false;
-    }
-
-    return true;
+    // Mark complete.
+    ret->incompleteReason = nullptr;    // NB: incompleteReason is also null for undefined
+    ret->levels = completeness->levels; //   textures.
+    ret->usage = completeness->usage;
+    return ret;
 }
 
-bool
-WebGLTexture::MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const
+const webgl::SampleableInfo*
+WebGLTexture::GetSampleableInfo(const WebGLSampler* const sampler) const
 {
-    const auto* samplingState = &mSamplingState;
-    const auto& sampler = mContext->mBoundSamplers[texUnit];
-    if (sampler) {
-        samplingState = &(sampler->State());
-    }
+    auto itr = mSamplingCache.Find(sampler);
+    if (!itr) {
+        const auto info = CalcSampleableInfo(sampler);
+        if (!info)
+            return nullptr;
 
-    const auto& minFilter = samplingState->minFilter;
-    if (minFilter == LOCAL_GL_NEAREST ||
-        minFilter == LOCAL_GL_LINEAR)
-    {
-        // No extra mips used.
-        *out = mBaseMipmapLevel;
-        return true;
+        auto entry = mSamplingCache.MakeEntry(sampler, info.value());
+        entry->AddInvalidator(*this);
+        if (sampler) {
+            entry->AddInvalidator(*sampler);
+        }
+        itr = mSamplingCache.Insert(std::move(entry));
     }
+    return itr;
+}
 
+// ---------------------------
+
+uint32_t
+WebGLTexture::EffectiveMaxLevel() const
+{
     const auto& imageInfo = BaseImageInfo();
     if (!imageInfo.IsDefined())
-        return false;
+        return mBaseMipmapLevel;
 
-    uint32_t maxLevelBySize = mBaseMipmapLevel + imageInfo.PossibleMipmapLevels() - 1;
-    *out = std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
-    return true;
+    uint32_t largestDim = std::max(imageInfo.mWidth, imageInfo.mHeight);
+    if (mTarget == LOCAL_GL_TEXTURE_3D) {
+        largestDim = std::max(largestDim, imageInfo.mDepth);
+    }
+    if (!largestDim)
+        return mBaseMipmapLevel;
+
+    // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
+    const auto numLevels = FloorLog2Size(largestDim) + 1;
+
+    const auto maxLevelBySize = mBaseMipmapLevel + numLevels - 1;
+    return std::min<uint32_t>(maxLevelBySize, mMaxMipmapLevel);
 }
 
-bool
-WebGLTexture::GetFakeBlackType(uint32_t texUnit,
-                               FakeBlackType* const out_fakeBlack)
-{
-    const char* incompleteReason;
-    bool initFailed = false;
-    if (!IsComplete(texUnit, &incompleteReason, &initFailed)) {
-        if (initFailed) {
-            mContext->ErrorOutOfMemory("Failed to initialize texture data.");
-            return false; // The world just exploded.
-        }
-
-        if (incompleteReason) {
-            mContext->GenerateWarning("Active texture %u for target 0x%04x is"
-                                      " 'incomplete', and will be rendered as"
-                                      " RGBA(0,0,0,1), as per the GLES 2.0.24 $3.8.2: %s",
-                                      texUnit, mTarget.get(),
-                                      incompleteReason);
-        }
-        *out_fakeBlack = FakeBlackType::RGBA0001;
-        return true;
-    }
-
-
-    *out_fakeBlack = FakeBlackType::None;
-    return true;
-}
+// -
 
 static void
 SetSwizzle(gl::GLContext* gl, TexTarget target, const GLint* swizzle)
 {
     static const GLint kNoSwizzle[4] = { LOCAL_GL_RED, LOCAL_GL_GREEN, LOCAL_GL_BLUE,
                                          LOCAL_GL_ALPHA };
     if (!swizzle) {
         swizzle = kNoSwizzle;
     } else if (!gl->IsSupported(gl::GLFeature::texture_swizzle)) {
         MOZ_CRASH("GFX: Needs swizzle feature to swizzle!");
     }
 
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]);
-    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
+    gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_R, swizzle[0]);
+    gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_G, swizzle[1]);
+    gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_B, swizzle[2]);
+    gl->fTexParameteri(target.get(), LOCAL_GL_TEXTURE_SWIZZLE_A, swizzle[3]);
+}
+
+void
+WebGLTexture::RefreshSwizzle() const
+{
+    const auto& imageInfo = BaseImageInfo();
+    const auto& swizzle = imageInfo.mFormat->textureSwizzleRGBA;
+
+    if (swizzle != mCurSwizzle) {
+        SetSwizzle(mContext->gl, mTarget, swizzle);
+        mCurSwizzle = swizzle;
+    }
 }
 
 bool
-WebGLTexture::ResolveForDraw(uint32_t texUnit,
-                             FakeBlackType* const out_fakeBlack)
-{
-    if (!mIsResolved) {
-        if (!GetFakeBlackType(texUnit, &mResolved_FakeBlack))
-            return false;
-
-        // Check which swizzle we should use. Since the texture must be complete at this
-        // point, just grab the format off any valid image.
-        const GLint* newSwizzle = nullptr;
-        if (mResolved_FakeBlack == FakeBlackType::None) {
-            const auto& cur = ImageInfoAtFace(0, mBaseMipmapLevel);
-            newSwizzle = cur.mFormat->textureSwizzleRGBA;
-        }
-
-        // Only set the swizzle if it changed since last time we did it.
-        if (newSwizzle != mResolved_Swizzle) {
-            mResolved_Swizzle = newSwizzle;
-
-            // Set the new swizzle!
-            mContext->gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
-            SetSwizzle(mContext->gl, mTarget, mResolved_Swizzle);
-            mContext->gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mContext->mActiveTexture);
-        }
-
-        mIsResolved = true;
-    }
-
-    *out_fakeBlack = mResolved_FakeBlack;
-    return true;
-}
-
-bool
-WebGLTexture::EnsureImageDataInitialized(TexImageTarget target,
-                                         uint32_t level)
+WebGLTexture::EnsureImageDataInitialized(const TexImageTarget target,
+                                         const uint32_t level)
 {
     auto& imageInfo = ImageInfoAt(target, level);
     if (!imageInfo.IsDefined())
         return true;
 
-    if (imageInfo.IsDataInitialized())
+    if (imageInfo.mHasData)
         return true;
 
-    return InitializeImageData(target, level);
-}
-
-bool
-WebGLTexture::EnsureLevelInitialized(uint32_t level)
-{
-    if (mTarget != LOCAL_GL_TEXTURE_CUBE_MAP)
-        return EnsureImageDataInitialized(mTarget.get(), level);
-
-    for (GLenum texImageTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
-         texImageTarget <= LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
-         ++texImageTarget)
+    if (!ZeroTextureData(mContext, mGLName, target, level, imageInfo.mFormat,
+                         imageInfo.mWidth, imageInfo.mHeight, imageInfo.mDepth))
     {
-        if (!EnsureImageDataInitialized(texImageTarget, level))
-            return false;
+        return false;
     }
+    imageInfo.mHasData = true;
     return true;
 }
 
-static void
-ZeroANGLEDepthTexture(WebGLContext* webgl, GLuint tex,
-                      const webgl::FormatUsageInfo* usage, uint32_t width,
-                      uint32_t height)
+static bool
+ClearDepthTexture(const WebGLContext& webgl, const GLuint tex,
+                  const TexImageTarget imageTarget, const uint32_t level,
+                  const webgl::FormatUsageInfo* const usage, const uint32_t depth)
 {
+    // Depth resources actually clear to 1.0f, not 0.0f!
+    // They are also always renderable.
+    MOZ_ASSERT(usage->IsRenderable());
+
+    const auto& gl = webgl.gl;
     const auto& format = usage->format;
-    GLenum attachPoint = 0;
-    GLbitfield clearBits = 0;
 
-    if (format->d) {
-        attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
-        clearBits |= LOCAL_GL_DEPTH_BUFFER_BIT;
-    }
+    GLenum attachPoint = LOCAL_GL_DEPTH_ATTACHMENT;
+    GLbitfield clearBits = LOCAL_GL_DEPTH_BUFFER_BIT;
 
     if (format->s) {
-        attachPoint = (format->d ? LOCAL_GL_DEPTH_STENCIL_ATTACHMENT
-                                 : LOCAL_GL_STENCIL_ATTACHMENT);
+        attachPoint = LOCAL_GL_DEPTH_STENCIL_ATTACHMENT;
         clearBits |= LOCAL_GL_STENCIL_BUFFER_BIT;
     }
 
-    MOZ_RELEASE_ASSERT(attachPoint && clearBits, "GFX: No bits cleared.");
-
-    ////
-    const auto& gl = webgl->gl;
-    MOZ_ASSERT(gl->IsCurrent());
+    // -
 
     gl::ScopedFramebuffer scopedFB(gl);
     const gl::ScopedBindFramebuffer scopedBindFB(gl, scopedFB.FB());
+    const webgl::ScopedPrepForResourceClear scopedPrep(webgl);
 
-    gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint, LOCAL_GL_TEXTURE_2D,
-                              tex, 0);
+    const auto fnAttach = [&](const uint32_t z) {
+        switch (imageTarget.get()) {
+        case LOCAL_GL_TEXTURE_3D:
+        case LOCAL_GL_TEXTURE_2D_ARRAY:
+            gl->fFramebufferTextureLayer(LOCAL_GL_FRAMEBUFFER, attachPoint,
+                                         tex, level, z);
+            break;
+        default:
+            if (attachPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
+                                          imageTarget.get(), tex, level);
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_STENCIL_ATTACHMENT,
+                                          imageTarget.get(), tex, level);
+            } else {
+                gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, attachPoint,
+                                          imageTarget.get(), tex, level);
+            }
+            break;
+        }
+    };
 
+    for (uint32_t z = 0; z < depth; ++z) {
+        fnAttach(z);
+        gl->fClear(clearBits);
+    }
     const auto& status = gl->fCheckFramebufferStatus(LOCAL_GL_FRAMEBUFFER);
-    MOZ_RELEASE_ASSERT(status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
-
-    ////
-
-    const bool fakeNoAlpha = false;
-    webgl->ForceClearFramebufferWithDefaultValues(clearBits, fakeNoAlpha);
+    const bool isComplete = (status == LOCAL_GL_FRAMEBUFFER_COMPLETE);
+    MOZ_ASSERT(isComplete);
+    return isComplete;
 }
 
 static bool
-ZeroTextureData(WebGLContext* webgl, GLuint tex,
+ZeroTextureData(const WebGLContext* webgl, GLuint tex,
                 TexImageTarget target, uint32_t level,
                 const webgl::FormatUsageInfo* usage, uint32_t width, uint32_t height,
                 uint32_t depth)
 {
     // This has two usecases:
     // 1. Lazy zeroing of uninitialized textures:
-    //    a. Before draw, when FakeBlack isn't viable. (TexStorage + Draw*)
+    //    a. Before draw.
     //    b. Before partial upload. (TexStorage + TexSubImage)
     // 2. Zero subrects from out-of-bounds blits. (CopyTex(Sub)Image)
 
     // We have no sympathy for any of these cases.
 
     // "Doctor, it hurts when I do this!" "Well don't do that!"
     webgl->GenerateWarning("This operation requires zeroing texture data. This is"
                            " slow.");
@@ -658,17 +592,17 @@ ZeroTextureData(WebGLContext* webgl, GLu
     case LOCAL_GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
         scopeBindTarget = LOCAL_GL_TEXTURE_CUBE_MAP;
         break;
     default:
         scopeBindTarget = target.get();
         break;
     }
     const gl::ScopedBindTexture scopeBindTexture(gl, tex, scopeBindTarget);
-    auto compression = usage->format->compression;
+    const auto& compression = usage->format->compression;
     if (compression) {
         auto sizedFormat = usage->format->sizedFormat;
         MOZ_RELEASE_ASSERT(sizedFormat, "GFX: texture sized format not set");
 
         const auto fnSizeInBlocks = [](CheckedUint32 pixels, uint8_t pixelsPerBlock) {
             return RoundUpToMultipleOf(pixels, pixelsPerBlock) / pixelsPerBlock;
         };
 
@@ -697,26 +631,22 @@ ZeroTextureData(WebGLContext* webgl, GLu
                                                    width, height, depth, sizedFormat,
                                                    byteCount, zeros.get());
         return !error;
     }
 
     const auto driverUnpackInfo = usage->idealUnpack;
     MOZ_RELEASE_ASSERT(driverUnpackInfo, "GFX: ideal unpack info not set.");
 
-    if (webgl->IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) &&
-        gl->IsANGLE() &&
-        usage->format->d)
-    {
+    if (usage->format->d) {
         // ANGLE_depth_texture does not allow uploads, so we have to clear.
         // (Restriction because of D3D9)
-        MOZ_ASSERT(target == LOCAL_GL_TEXTURE_2D);
-        MOZ_ASSERT(level == 0);
-        ZeroANGLEDepthTexture(webgl, tex, usage, width, height);
-        return true;
+        // Also, depth resources are cleared to 1.0f and are always renderable, so just
+        // use FB clears.
+        return ClearDepthTexture(*webgl, tex, target, level, usage, depth);
     }
 
     const webgl::PackingInfo packing = driverUnpackInfo->ToPacking();
 
     const auto bytesPerPixel = webgl::BytesPerPixel(packing);
 
     CheckedUint32 checkedByteCount = bytesPerPixel;
     checkedByteCount *= width;
@@ -734,90 +664,32 @@ ZeroTextureData(WebGLContext* webgl, GLu
 
     ScopedUnpackReset scopedReset(webgl);
     gl->fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 1); // Don't bother with striding it well.
     const auto error = DoTexSubImage(gl, target, level, 0, 0, 0, width, height, depth,
                                      packing, zeros.get());
     return !error;
 }
 
-bool
-WebGLTexture::InitializeImageData(TexImageTarget target,
-                                  uint32_t level)
-{
-    auto& imageInfo = ImageInfoAt(target, level);
-    MOZ_ASSERT(imageInfo.IsDefined());
-    MOZ_ASSERT(!imageInfo.IsDataInitialized());
-
-    const auto& usage = imageInfo.mFormat;
-    const auto& width = imageInfo.mWidth;
-    const auto& height = imageInfo.mHeight;
-    const auto& depth = imageInfo.mDepth;
-
-    if (!ZeroTextureData(mContext, mGLName, target, level, usage, width, height,
-                         depth))
-    {
-        return false;
-    }
-
-    imageInfo.SetIsDataInitialized(true, this);
-    return true;
-}
-
 void
 WebGLTexture::ClampLevelBaseAndMax()
 {
     if (!mImmutable)
         return;
 
     // GLES 3.0.4, p158:
     // "For immutable-format textures, `level_base` is clamped to the range
     //  `[0, levels-1]`, `level_max` is then clamped to the range `
     //  `[level_base, levels-1]`, where `levels` is the parameter passed to
     //   TexStorage* for the texture object."
     mBaseMipmapLevel = Clamp<uint32_t>(mBaseMipmapLevel, 0, mImmutableLevelCount - 1);
     mMaxMipmapLevel = Clamp<uint32_t>(mMaxMipmapLevel, mBaseMipmapLevel,
                                       mImmutableLevelCount - 1);
-}
 
-void
-WebGLTexture::PopulateMipChain(uint32_t firstLevel,
-                               uint32_t lastLevel)
-{
-    const ImageInfo& baseImageInfo = ImageInfoAtFace(0, firstLevel);
-    MOZ_ASSERT(baseImageInfo.IsDefined());
-
-    uint32_t refWidth = baseImageInfo.mWidth;
-    uint32_t refHeight = baseImageInfo.mHeight;
-    uint32_t refDepth = baseImageInfo.mDepth;
-    if (!refWidth || !refHeight || !refDepth)
-        return;
-
-    for (uint32_t level = firstLevel + 1; level <= lastLevel; level++) {
-        bool isMinimal = (refWidth == 1 &&
-                          refHeight == 1);
-        if (mTarget == LOCAL_GL_TEXTURE_3D) {
-            isMinimal &= (refDepth == 1);
-        }
-
-        // Higher levels are unaffected.
-        if (isMinimal)
-            break;
-
-        refWidth = std::max(uint32_t(1), refWidth / 2);
-        refHeight = std::max(uint32_t(1), refHeight / 2);
-        if (mTarget == LOCAL_GL_TEXTURE_3D) { // But not TEXTURE_2D_ARRAY!
-            refDepth = std::max(uint32_t(1), refDepth / 2);
-        }
-
-        const ImageInfo cur(baseImageInfo.mFormat, refWidth, refHeight, refDepth,
-                            baseImageInfo.IsDataInitialized());
-
-        SetImageInfosAtLevel(level, cur);
-    }
+    // Note: This means that immutable textures are *always* texture-complete!
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 // GL calls
 
 bool
 WebGLTexture::BindTexture(TexTarget texTarget)
 {
@@ -852,40 +724,33 @@ WebGLTexture::BindTexture(TexTarget texT
             gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_WRAP_R,
                                LOCAL_GL_CLAMP_TO_EDGE);
         }
     }
 
     return true;
 }
 
-
 void
-WebGLTexture::GenerateMipmap(TexTarget texTarget)
-{\
+WebGLTexture::GenerateMipmap()
+{
     // GLES 3.0.4 p160:
     // "Mipmap generation replaces texel array levels level base + 1 through q with arrays
     //  derived from the level base array, regardless of their previous contents. All
     //  other mipmap arrays, including the level base array, are left unchanged by this
     //  computation."
-    const ImageInfo& baseImageInfo = BaseImageInfo();
-    if (!baseImageInfo.IsDefined()) {
-        mContext->ErrorInvalidOperation("The base level of the texture is not"
-                                        " defined.");
+    const auto completeness = CalcCompletenessInfo();
+    if (!completeness || !completeness->levels) {
+        mContext->ErrorInvalidOperation("The texture's base level must be complete.");
         return;
     }
-
-    if (IsCubeMap() && !IsCubeComplete()) {
-      mContext->ErrorInvalidOperation("Cube maps must be \"cube complete\".");
-      return;
-    }
-
-    const auto format = baseImageInfo.mFormat->format;
+    const auto& usage = completeness->usage;
+    const auto& format = usage->format;
     if (!mContext->IsWebGL2()) {
-        if (!baseImageInfo.IsPowerOfTwo()) {
+        if (!completeness->powerOfTwo) {
             mContext->ErrorInvalidOperation("The base level of the texture does not"
                                             " have power-of-two dimensions.");
             return;
         }
         if (format->isSRGB) {
             mContext->ErrorInvalidOperation("EXT_sRGB forbids GenerateMipmap with"
                                             " sRGB.");
             return;
@@ -902,17 +767,16 @@ WebGLTexture::GenerateMipmap(TexTarget t
         return;
     }
 
     // OpenGL ES 3.0.4 p160:
     // If the level base array was not specified with an unsized internal format from
     // table 3.3 or a sized internal format that is both color-renderable and
     // texture-filterable according to table 3.13, an INVALID_OPERATION error
     // is generated.
-    const auto usage = baseImageInfo.mFormat;
     bool canGenerateMipmap = (usage->IsRenderable() && usage->isFilterable);
     switch (usage->format->effectiveFormat) {
     case webgl::EffectiveFormat::Luminance8:
     case webgl::EffectiveFormat::Alpha8:
     case webgl::EffectiveFormat::Luminance8Alpha8:
         // Non-color-renderable formats from Table 3.3.
         canGenerateMipmap = true;
         break;
@@ -932,30 +796,29 @@ WebGLTexture::GenerateMipmap(TexTarget t
     gl::GLContext* gl = mContext->gl;
 
     if (gl->WorkAroundDriverBugs()) {
         // bug 696495 - to work around failures in the texture-mips.html test on various drivers, we
         // set the minification filter before calling glGenerateMipmap. This should not carry a significant performance
         // overhead so we do it unconditionally.
         //
         // note that the choice of GL_NEAREST_MIPMAP_NEAREST really matters. See Chromium bug 101105.
-        gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
+        gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
                            LOCAL_GL_NEAREST_MIPMAP_NEAREST);
-        gl->fGenerateMipmap(texTarget.get());
-        gl->fTexParameteri(texTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
+        gl->fGenerateMipmap(mTarget.get());
+        gl->fTexParameteri(mTarget.get(), LOCAL_GL_TEXTURE_MIN_FILTER,
                            mSamplingState.minFilter.get());
     } else {
-        gl->fGenerateMipmap(texTarget.get());
+        gl->fGenerateMipmap(mTarget.get());
     }
 
     // Record the results.
-    // Note that we don't use MaxEffectiveMipmapLevel() here, since that returns
-    // mBaseMipmapLevel if the min filter doesn't require mipmaps.
-    const uint32_t maxLevel = mBaseMipmapLevel + baseImageInfo.PossibleMipmapLevels() - 1;
-    PopulateMipChain(mBaseMipmapLevel, maxLevel);
+
+    const auto maxLevel = EffectiveMaxLevel();
+    PopulateMipChain(maxLevel);
 }
 
 JS::Value
 WebGLTexture::GetTexParameter(TexTarget texTarget, GLenum pname)
 {
     GLint i = 0;
     GLfloat f = 0.0f;
 
@@ -1146,17 +1009,17 @@ WebGLTexture::TexParameter(TexTarget tex
         }
         return;
     }
 
     ////////////////
     // Store any needed values
 
     FloatOrInt clamped = param;
-    bool invalidateCaches = true;
+    bool invalidate = true;
     switch (pname) {
     case LOCAL_GL_TEXTURE_BASE_LEVEL:
         mBaseMipmapLevel = clamped.i;
         ClampLevelBaseAndMax();
         clamped = FloatOrInt(GLint(mBaseMipmapLevel));
         break;
 
     case LOCAL_GL_TEXTURE_MAX_LEVEL:
@@ -1180,26 +1043,23 @@ WebGLTexture::TexParameter(TexTarget tex
     case LOCAL_GL_TEXTURE_WRAP_T:
         mSamplingState.wrapT = clamped.i;
         break;
 
     case LOCAL_GL_TEXTURE_COMPARE_MODE:
         mSamplingState.compareMode = clamped.i;
         break;
 
-    // Only a couple of pnames don't need to invalidate our resolve status cache.
-    case LOCAL_GL_TEXTURE_MAX_ANISOTROPY_EXT:
-    case LOCAL_GL_TEXTURE_WRAP_R:
-    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
-        invalidateCaches = false;
+    default:
+        invalidate = false; // Texture completeness will not change.
         break;
     }
 
-    if (invalidateCaches) {
-        InvalidateResolveCache();
+    if (invalidate) {
+        InvalidateCaches();
     }
 
     ////////////////
 
     if (!clamped.isFloat)
         mContext->gl->fTexParameteri(texTarget.get(), pname, clamped.i);
     else
         mContext->gl->fTexParameterf(texTarget.get(), pname, clamped.f);
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -12,24 +12,26 @@
 #include <vector>
 
 #include "mozilla/Assertions.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 
-#include "WebGLFramebufferAttachable.h"
+#include "CacheInvalidator.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
 class ErrorResult;
 class WebGLContext;
+class WebGLFramebuffer;
+class WebGLSampler;
 struct FloatOrInt;
 struct TexImageSource;
 
 namespace dom {
 class Element;
 class HTMLVideoElement;
 class ImageData;
 class ArrayBufferViewOrSharedArrayBufferView;
@@ -61,24 +63,52 @@ struct SamplingState final
     TexWrap wrapT = LOCAL_GL_REPEAT;
     //TexWrap wrapR = LOCAL_GL_REPEAT;
     //GLfloat minLod = -1000;
     //GLfloat maxLod = 1000;
     TexCompareMode compareMode = LOCAL_GL_NONE;
     //TexCompareFunc compareFunc = LOCAL_GL_LEQUAL;
 };
 
+struct ImageInfo final
+{
+    static const ImageInfo kUndefined;
+
+    const webgl::FormatUsageInfo* mFormat = nullptr;
+    uint32_t mWidth = 0;
+    uint32_t mHeight = 0;
+    uint32_t mDepth = 0;
+    mutable bool mHasData = false;
+    uint8_t mSamples = 0;
+
+    // -
+
+    size_t MemoryUsage() const;
+
+    bool IsDefined() const {
+        if (!mFormat) {
+            MOZ_ASSERT(!mWidth && !mHeight && !mDepth);
+            return false;
+        }
+
+        return true;
+    }
+
+    Maybe<ImageInfo> NextMip(GLenum target) const;
+};
+
 } // namespace webgl
 
 // NOTE: When this class is switched to new DOM bindings, update the (then-slow)
 // WrapObject calls in GetParameter and GetFramebufferAttachmentParameter.
 class WebGLTexture final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLTexture>
     , public LinkedListElement<WebGLTexture>
+    , public CacheInvalidator
 {
     // Friends
     friend class WebGLContext;
     friend class WebGLFramebuffer;
 
     ////////////////////////////////////
     // Members
 public:
@@ -95,126 +125,51 @@ protected:
 
     uint32_t mBaseMipmapLevel; // Set by texParameter (defaults to 0)
     uint32_t mMaxMipmapLevel;  // Set by texParameter (defaults to 1000)
     // You almost certainly don't want to query mMaxMipmapLevel.
     // You almost certainly want MaxEffectiveMipmapLevel().
 
     webgl::SamplingState mSamplingState;
 
-    // Resolvable optimizations:
-    bool mIsResolved;
-    FakeBlackType mResolved_FakeBlack;
-    const GLint* mResolved_Swizzle; // nullptr means 'default swizzle'.
+    mutable const GLint* mCurSwizzle = nullptr; // nullptr means 'default swizzle'.
+
+    // -
+
+    struct CompletenessInfo final {
+        uint8_t levels = 0;
+        bool powerOfTwo = false;
+        bool mipmapComplete = false;
+        const webgl::FormatUsageInfo* usage = nullptr;
+        const char* incompleteReason = nullptr;
+    };
+
+    mutable CacheWeakMap<const WebGLSampler*, webgl::SampleableInfo> mSamplingCache;
 
 public:
-    class ImageInfo;
+    Maybe<const CompletenessInfo> CalcCompletenessInfo() const;
+    Maybe<const webgl::SampleableInfo> CalcSampleableInfo(const WebGLSampler*) const;
+
+    const webgl::SampleableInfo* GetSampleableInfo(const WebGLSampler*) const;
+
 
-    // numLevels = log2(size) + 1
-    // numLevels(16k) = log2(16k) + 1 = 14 + 1 = 15
-    // numLevels(1M) = log2(1M) + 1 = 19.9 + 1 ~= 21
-    // Or we can just max this out to 31, which is the number of unsigned bits in GLsizei.
+    // -
+
+    const auto& Immutable() const { return mImmutable; }
+    const auto& BaseMipmapLevel() const { return mBaseMipmapLevel; }
+
+    // We can just max this out to 31, which is the number of unsigned bits in GLsizei.
     static const uint8_t kMaxLevelCount = 31;
 
-    // And in turn, it needs these forwards:
-protected:
-    // We need to forward these.
-    void SetImageInfo(ImageInfo* target, const ImageInfo& newInfo);
-    void SetImageInfosAtLevel(uint32_t level, const ImageInfo& newInfo);
-
-public:
     // We store information about the various images that are part of this
     // texture. (cubemap faces, mipmap levels)
-    class ImageInfo
-    {
-        friend void WebGLTexture::SetImageInfo(ImageInfo* target,
-                                               const ImageInfo& newInfo);
-        friend void WebGLTexture::SetImageInfosAtLevel(uint32_t level,
-                                                       const ImageInfo& newInfo);
-
-    public:
-        static const ImageInfo kUndefined;
-
-        // This is the "effective internal format" of the texture, an official
-        // OpenGL spec concept, see OpenGL ES 3.0.3 spec, section 3.8.3, page
-        // 126 and below.
-        const webgl::FormatUsageInfo* const mFormat;
-
-        const uint32_t mWidth;
-        const uint32_t mHeight;
-        const uint32_t mDepth;
-
-    protected:
-        bool mIsDataInitialized;
-
-        std::set<WebGLFBAttachPoint*> mAttachPoints;
-
-    public:
-        ImageInfo()
-            : mFormat(LOCAL_GL_NONE)
-            , mWidth(0)
-            , mHeight(0)
-            , mDepth(0)
-            , mIsDataInitialized(false)
-        { }
-
-        ImageInfo(const webgl::FormatUsageInfo* format, uint32_t width, uint32_t height,
-                  uint32_t depth, bool isDataInitialized)
-            : mFormat(format)
-            , mWidth(width)
-            , mHeight(height)
-            , mDepth(depth)
-            , mIsDataInitialized(isDataInitialized)
-        {
-            MOZ_ASSERT(mFormat);
-        }
-
-        void Clear();
-
-        ~ImageInfo() {
-            MOZ_ASSERT(!mAttachPoints.size());
-        }
-
-    protected:
-        void Set(const ImageInfo& a);
-
-    public:
-        uint32_t PossibleMipmapLevels() const {
-            // GLES 3.0.4, 3.8 - Mipmapping: `floor(log2(largest_of_dims)) + 1`
-            const uint32_t largest = std::max(std::max(mWidth, mHeight), mDepth);
-            MOZ_ASSERT(largest != 0);
-            return FloorLog2Size(largest) + 1;
-        }
-
-        bool IsPowerOfTwo() const;
-
-        void AddAttachPoint(WebGLFBAttachPoint* attachPoint);
-        void RemoveAttachPoint(WebGLFBAttachPoint* attachPoint);
-        void OnRespecify() const;
-
-        size_t MemoryUsage() const;
-
-        bool IsDefined() const {
-            if (mFormat == LOCAL_GL_NONE) {
-                MOZ_ASSERT(!mWidth && !mHeight && !mDepth);
-                return false;
-            }
-
-            return true;
-        }
-
-        bool IsDataInitialized() const { return mIsDataInitialized; }
-
-        void SetIsDataInitialized(bool isDataInitialized, WebGLTexture* tex);
-    };
-
-    ImageInfo mImageInfoArr[kMaxLevelCount * kMaxFaceCount];
+    webgl::ImageInfo mImageInfoArr[kMaxLevelCount * kMaxFaceCount];
 
     ////////////////////////////////////
-public:
+
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLTexture)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLTexture)
 
     WebGLTexture(WebGLContext* webgl, GLuint tex);
 
     void Delete();
 
     bool HasEverBeenBound() const { return mTarget != LOCAL_GL_NONE; }
@@ -230,17 +185,17 @@ protected:
     ~WebGLTexture() {
         DeleteOnce();
     }
 
 public:
     ////////////////////////////////////
     // GL calls
     bool BindTexture(TexTarget texTarget);
-    void GenerateMipmap(TexTarget texTarget);
+    void GenerateMipmap();
     JS::Value GetTexParameter(TexTarget texTarget, GLenum pname);
     bool IsTexture() const;
     void TexParameter(TexTarget texTarget, GLenum pname, const FloatOrInt& param);
 
     ////////////////////////////////////
     // WebGLTextureUpload.cpp
 
 protected:
@@ -248,22 +203,22 @@ protected:
                            GLint level, GLenum internalFormat, GLint xOffset,
                            GLint yOffset, GLint zOffset,
                            const webgl::PackingInfo& pi,
                            const webgl::TexUnpackBlob* blob);
 
     bool ValidateTexImageSpecification(TexImageTarget target,
                                        GLint level, uint32_t width, uint32_t height,
                                        uint32_t depth,
-                                       WebGLTexture::ImageInfo** const out_imageInfo);
+                                       webgl::ImageInfo** const out_imageInfo);
     bool ValidateTexImageSelection(TexImageTarget target,
                                    GLint level, GLint xOffset, GLint yOffset,
                                    GLint zOffset, uint32_t width, uint32_t height,
                                    uint32_t depth,
-                                   WebGLTexture::ImageInfo** const out_imageInfo);
+                                   webgl::ImageInfo** const out_imageInfo);
     bool ValidateCopyTexImageForFeedback(uint32_t level, GLint layer = 0) const;
 
     bool ValidateUnpack(const webgl::TexUnpackBlob* blob,
                         bool isFunc3D, const webgl::PackingInfo& srcPI) const;
 public:
     void TexStorage(TexTarget target, GLsizei levels,
                     GLenum sizedFormat, GLsizei width, GLsizei height, GLsizei depth);
     void TexImage(TexImageTarget target, GLint level,
@@ -295,111 +250,74 @@ public:
     void CopyTexSubImage(TexImageTarget target, GLint level,
                          GLint xOffset, GLint yOffset, GLint zOffset, GLint x, GLint y,
                          GLsizei width, GLsizei height);
 
     ////////////////////////////////////
 
 protected:
     void ClampLevelBaseAndMax();
-
-    void PopulateMipChain(uint32_t baseLevel, uint32_t maxLevel);
+    void RefreshSwizzle() const;
 
-    bool MaxEffectiveMipmapLevel(uint32_t texUnit, uint32_t* const out) const;
+public:
+    uint32_t EffectiveMaxLevel() const; // GLES 3.0.5 p158: `q`
 
+protected:
     static uint8_t FaceForTarget(TexImageTarget texImageTarget) {
         GLenum rawTexImageTarget = texImageTarget.get();
         switch (rawTexImageTarget) {
         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:
             return rawTexImageTarget - LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X;
 
         default:
             return 0;
         }
     }
 
-    ImageInfo& ImageInfoAtFace(uint8_t face, uint32_t level) {
+    auto& ImageInfoAtFace(uint8_t face, uint32_t level) {
         MOZ_ASSERT(face < mFaceCount);
         MOZ_ASSERT(level < kMaxLevelCount);
         size_t pos = (level * mFaceCount) + face;
         return mImageInfoArr[pos];
     }
 
-    const ImageInfo& ImageInfoAtFace(uint8_t face, uint32_t level) const {
+    const auto& ImageInfoAtFace(uint8_t face, uint32_t level) const {
         return const_cast<WebGLTexture*>(this)->ImageInfoAtFace(face, level);
     }
 
 public:
-    ImageInfo& ImageInfoAt(TexImageTarget texImageTarget, GLint level) {
-        auto face = FaceForTarget(texImageTarget);
+    auto& ImageInfoAt(TexImageTarget texImageTarget, GLint level) {
+        const auto& face = FaceForTarget(texImageTarget);
         return ImageInfoAtFace(face, level);
     }
 
-    const ImageInfo& ImageInfoAt(TexImageTarget texImageTarget, GLint level) const {
+    const auto& ImageInfoAt(TexImageTarget texImageTarget, GLint level) const {
         return const_cast<WebGLTexture*>(this)->ImageInfoAt(texImageTarget, level);
     }
 
-    void SetImageInfoAt(TexImageTarget texImageTarget, GLint level,
-                        const ImageInfo& val)
-    {
-        ImageInfo* target = &ImageInfoAt(texImageTarget, level);
-        SetImageInfo(target, val);
-    }
-
-    const ImageInfo& BaseImageInfo() const {
+    const auto& BaseImageInfo() const {
         if (mBaseMipmapLevel >= kMaxLevelCount)
-            return ImageInfo::kUndefined;
+            return webgl::ImageInfo::kUndefined;
 
         return ImageInfoAtFace(0, mBaseMipmapLevel);
     }
 
     size_t MemoryUsage() const;
 
-    bool InitializeImageData(TexImageTarget target, uint32_t level);
-protected:
     bool EnsureImageDataInitialized(TexImageTarget target,
                                     uint32_t level);
-    bool EnsureLevelInitialized(uint32_t level);
-
-public:
-    void SetGeneratedMipmap();
-
-    void SetCustomMipmap();
-
-    bool AreAllLevel0ImageInfosEqual() const;
-
-    bool IsMipmapComplete(uint32_t texUnit,
-                          bool* const out_initFailed);
-
-    bool IsCubeComplete() const;
-
-    bool IsComplete(uint32_t texUnit, const char** const out_reason,
-                    bool* const out_initFailed);
-
-    bool IsMipmapCubeComplete() const;
+    void PopulateMipChain(uint32_t maxLevel);
+    bool IsMipAndCubeComplete(uint32_t maxLevel, bool* out_initFailed) const;
 
     bool IsCubeMap() const { return (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP); }
-
-    // Resolve cache optimizations
-protected:
-    bool GetFakeBlackType(uint32_t texUnit,
-                          FakeBlackType* const out_fakeBlack);
-public:
-    bool IsFeedback(WebGLContext* webgl, uint32_t texUnit,
-                    const std::vector<const WebGLFBAttachPoint*>& fbAttachments) const;
-
-    bool ResolveForDraw(uint32_t texUnit,
-                        FakeBlackType* const out_fakeBlack);
-
-    void InvalidateResolveCache() { mIsResolved = false; }
 };
 
 inline TexImageTarget
 TexImageTargetForTargetAndFace(TexTarget target, uint8_t face)
 {
     switch (target.get()) {
     case LOCAL_GL_TEXTURE_2D:
     case LOCAL_GL_TEXTURE_3D:
--- a/dom/canvas/WebGLTextureUpload.cpp
+++ b/dom/canvas/WebGLTextureUpload.cpp
@@ -497,49 +497,48 @@ WebGLTexture::TexSubImage(TexImageTarget
 }
 
 //////////////////////////////////////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////////////////////////////////////
 
 static bool
 ValidateTexImage(WebGLContext* webgl, WebGLTexture* texture,
                  TexImageTarget target, GLint level,
-                 WebGLTexture::ImageInfo** const out_imageInfo)
+                 webgl::ImageInfo** const out_imageInfo)
 {
     // Check level
     if (level < 0) {
         webgl->ErrorInvalidValue("`level` must be >= 0.");
         return false;
     }
 
     if (level >= WebGLTexture::kMaxLevelCount) {
         webgl->ErrorInvalidValue("`level` is too large.");
         return false;
     }
 
-    WebGLTexture::ImageInfo& imageInfo = texture->ImageInfoAt(target, level);
-
+    auto& imageInfo = texture->ImageInfoAt(target, level);
     *out_imageInfo = &imageInfo;
     return true;
 }
 
 // For *TexImage*
 bool
 WebGLTexture::ValidateTexImageSpecification(TexImageTarget target,
                                             GLint rawLevel, uint32_t width,
                                             uint32_t height, uint32_t depth,
-                                            WebGLTexture::ImageInfo** const out_imageInfo)
+                                            webgl::ImageInfo** const out_imageInfo)
 {
     if (mImmutable) {
         mContext->ErrorInvalidOperation("Specified texture is immutable.");
         return false;
     }
 
     // Do this early to validate `level`.
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImage(mContext, this, target, rawLevel, &imageInfo))
         return false;
     const uint32_t level(rawLevel);
 
     if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP &&
         width != height)
     {
         mContext->ErrorInvalidValue("Cube map images must be square.");
@@ -631,25 +630,25 @@ WebGLTexture::ValidateTexImageSpecificat
 }
 
 // For *TexSubImage*
 bool
 WebGLTexture::ValidateTexImageSelection(TexImageTarget target,
                                         GLint level, GLint xOffset, GLint yOffset,
                                         GLint zOffset, uint32_t width, uint32_t height,
                                         uint32_t depth,
-                                        WebGLTexture::ImageInfo** const out_imageInfo)
+                                        webgl::ImageInfo** const out_imageInfo)
 {
     // The conformance test wants bad arg checks before imageInfo checks.
     if (xOffset < 0 || yOffset < 0 || zOffset < 0) {
         mContext->ErrorInvalidValue("Offsets must be >=0.");
         return false;
     }
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImage(mContext, this, target, level, &imageInfo))
         return false;
 
     if (!imageInfo->IsDefined()) {
         mContext->ErrorInvalidOperation("The specified TexImage has not yet been"
                                         " specified.");
         return false;
     }
@@ -763,34 +762,29 @@ DoChannelsMatchForCopyTexImage(const web
     }
 }
 
 static bool
 EnsureImageDataInitializedForUpload(WebGLTexture* tex,
                                     TexImageTarget target, GLint level, GLint xOffset,
                                     GLint yOffset, GLint zOffset, uint32_t width,
                                     uint32_t height, uint32_t depth,
-                                    WebGLTexture::ImageInfo* imageInfo,
-                                    bool* const out_uploadWillInitialize)
+                                    webgl::ImageInfo* imageInfo)
 {
-    *out_uploadWillInitialize = false;
-
-    if (!imageInfo->IsDataInitialized()) {
+    if (!imageInfo->mHasData) {
         const bool isFullUpload = (!xOffset && !yOffset && !zOffset &&
                                    width == imageInfo->mWidth &&
                                    height == imageInfo->mHeight &&
                                    depth == imageInfo->mDepth);
-        if (isFullUpload) {
-            *out_uploadWillInitialize = true;
-        } else {
+        if (!isFullUpload) {
             WebGLContext* webgl = tex->mContext;
             webgl->GenerateWarning("Texture has not been initialized prior to a"
                                    " partial upload, forcing the browser to clear it."
                                    " This may be slow.");
-            if (!tex->InitializeImageData(target, level)) {
+            if (!tex->EnsureImageDataInitialized(target, level)) {
                 MOZ_ASSERT(false, "Unexpected failure to init image data.");
                 return false;
             }
         }
     }
 
     return true;
 }
@@ -1066,17 +1060,17 @@ WebGLTexture::TexStorage(TexTarget targe
 
     if (!width || !height || !depth) {
         mContext->ErrorInvalidValue("Dimensions must be non-zero.");
         return;
     }
 
     const TexImageTarget testTarget = IsCubeMap() ? LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X
                                                   : target.get();
-    WebGLTexture::ImageInfo* baseImageInfo;
+    webgl::ImageInfo* baseImageInfo;
     if (!ValidateTexImageSpecification(testTarget, 0, width, height, depth,
                                        &baseImageInfo))
     {
         return;
     }
     MOZ_ALWAYS_TRUE(baseImageInfo);
 
     auto dstUsage = mContext->mFormatUsage->GetSizedTexUsage(sizedFormat);
@@ -1141,38 +1135,46 @@ WebGLTexture::TexStorage(TexTarget targe
         mContext->ErrorInvalidOperation("Unexpected error during texture allocation.");
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = false;
-    const WebGLTexture::ImageInfo newInfo(dstUsage, width, height, depth,
-                                          isDataInitialized);
-    SetImageInfosAtLevel(0, newInfo);
+    const webgl::ImageInfo newInfo { dstUsage, uint32_t(width), uint32_t(height),
+                                     uint32_t(depth), isDataInitialized };
 
-    PopulateMipChain(0, levels-1);
+    {
+        const auto base_level = mBaseMipmapLevel;
+        mBaseMipmapLevel = 0;
+
+        ImageInfoAtFace(0, 0) = newInfo;
+        PopulateMipChain(levels-1);
+
+        mBaseMipmapLevel = base_level;
+    }
 
     mImmutable = true;
     mImmutableLevelCount = levels;
+    ClampLevelBaseAndMax();
 }
 
 ////////////////////////////////////////
 // Tex(Sub)Image
 
 void
 WebGLTexture::TexImage(TexImageTarget target, GLint level,
                        GLenum internalFormat, const webgl::PackingInfo& pi,
                        const webgl::TexUnpackBlob* blob)
 {
     ////////////////////////////////////
     // Get dest info
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImageSpecification(target, level, blob->mWidth,
                                        blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     const auto& fua = mContext->mFormatUsage;
@@ -1234,18 +1236,18 @@ WebGLTexture::TexImage(TexImageTarget ta
     }
 
     ////////////////////////////////////
     // Do the thing!
 
     // It's tempting to do allocation first, and TexSubImage second, but this is generally
     // slower.
 
-    const ImageInfo newImageInfo(dstUsage, blob->mWidth, blob->mHeight, blob->mDepth,
-                                 blob->HasData());
+    const webgl::ImageInfo newImageInfo { dstUsage, blob->mWidth, blob->mHeight,
+                                          blob->mDepth, blob->HasData() };
 
     const bool isSubImage = false;
     const bool needsRespec = (imageInfo->mWidth  != newImageInfo.mWidth ||
                               imageInfo->mHeight != newImageInfo.mHeight ||
                               imageInfo->mDepth  != newImageInfo.mDepth ||
                               imageInfo->mFormat != newImageInfo.mFormat);
     const GLint xOffset = 0;
     const GLint yOffset = 0;
@@ -1274,28 +1276,29 @@ WebGLTexture::TexImage(TexImageTarget ta
         mContext->ErrorInvalidOperation("%s", dui.BeginReading());
         gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
-    SetImageInfo(imageInfo, newImageInfo);
+    *imageInfo = newImageInfo;
+    InvalidateCaches();
 }
 
 void
 WebGLTexture::TexSubImage(TexImageTarget target, GLint level,
                           GLint xOffset, GLint yOffset, GLint zOffset,
                           const webgl::PackingInfo& pi, const webgl::TexUnpackBlob* blob)
 {
     ////////////////////////////////////
     // Get dest info
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(target, level, xOffset, yOffset, zOffset,
                                    blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto dstUsage = imageInfo->mFormat;
@@ -1322,21 +1325,19 @@ WebGLTexture::TexSubImage(TexImageTarget
                                         " %s and 0x%04x/0x%04x",
                                         dstFormat->name, pi.format, pi.type);
         return;
     }
 
     ////////////////////////////////////
     // Do the thing!
 
-    bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, target, level, xOffset,
                                              yOffset, zOffset, blob->mWidth,
-                                             blob->mHeight, blob->mDepth, imageInfo,
-                                             &uploadWillInitialize))
+                                             blob->mHeight, blob->mDepth, imageInfo))
     {
         return;
     }
 
     const bool isSubImage = true;
     const bool needsRespec = false;
 
     GLenum glError;
@@ -1360,19 +1361,17 @@ WebGLTexture::TexSubImage(TexImageTarget
         mContext->ErrorInvalidOperation("%s", dui.BeginReading());
         gfxCriticalError() << mContext->FuncName() << ": " << dui.BeginReading();
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data?
 
-    if (uploadWillInitialize) {
-        imageInfo->SetIsDataInitialized(true, this);
-    }
+    imageInfo->mHasData = true;
 }
 
 ////////////////////////////////////////
 // CompressedTex(Sub)Image
 
 UniquePtr<webgl::TexUnpackBytes>
 WebGLContext::FromCompressed(TexImageTarget target,
                              GLsizei rawWidth, GLsizei rawHeight, GLsizei rawDepth,
@@ -1409,17 +1408,17 @@ WebGLTexture::CompressedTexImage(TexImag
     const auto blob = mContext->FromCompressed(target, rawWidth, rawHeight,
                                                rawDepth, border, src, expectedImageSize);
     if (!blob)
         return;
 
     ////////////////////////////////////
     // Get dest info
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImageSpecification(target, level, blob->mWidth,
                                        blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto usage = mContext->mFormatUsage->GetSizedTexUsage(internalFormat);
@@ -1479,24 +1478,25 @@ WebGLTexture::CompressedTexImage(TexImag
         mContext->ForceLoseContext();
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = true;
-    const ImageInfo newImageInfo(usage, blob->mWidth, blob->mHeight, blob->mDepth,
-                                 isDataInitialized);
-    SetImageInfo(imageInfo, newImageInfo);
+    const webgl::ImageInfo newImageInfo { usage, blob->mWidth, blob->mHeight,
+                                          blob->mDepth, isDataInitialized };
+    *imageInfo = newImageInfo;
+    InvalidateCaches();
 }
 
 static inline bool
 IsSubImageBlockAligned(const webgl::CompressedFormatInfo* compression,
-                       const WebGLTexture::ImageInfo* imageInfo, GLint xOffset,
+                       const webgl::ImageInfo* imageInfo, GLint xOffset,
                        GLint yOffset, uint32_t width, uint32_t height)
 {
     if (xOffset % compression->blockWidth != 0 ||
         yOffset % compression->blockHeight != 0)
     {
         return false;
     }
 
@@ -1520,17 +1520,17 @@ WebGLTexture::CompressedTexSubImage(TexI
     const auto blob = mContext->FromCompressed(target, rawWidth, rawHeight,
                                                rawDepth, border, src, expectedImageSize);
     if (!blob)
         return;
 
     ////////////////////////////////////
     // Get dest info
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(target, level, xOffset, yOffset, zOffset,
                                    blob->mWidth, blob->mHeight, blob->mDepth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto dstUsage = imageInfo->mFormat;
@@ -1594,21 +1594,19 @@ WebGLTexture::CompressedTexSubImage(TexI
             return;
         }
         break;
     }
 
     ////////////////////////////////////
     // Do the thing!
 
-    bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, target, level, xOffset,
                                              yOffset, zOffset, blob->mWidth,
-                                             blob->mHeight, blob->mDepth, imageInfo,
-                                             &uploadWillInitialize))
+                                             blob->mHeight, blob->mDepth, imageInfo))
     {
         return;
     }
 
     const ScopedLazyBind bindPBO(mContext->gl, LOCAL_GL_PIXEL_UNPACK_BUFFER,
                                  mContext->mBoundPixelUnpackBuffer);
 
     // Warning: Possibly shared memory.  See bug 1225033.
@@ -1626,19 +1624,17 @@ WebGLTexture::CompressedTexSubImage(TexI
                                   " lost.");
         mContext->ForceLoseContext();
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data?
 
-    if (uploadWillInitialize) {
-        imageInfo->SetIsDataInitialized(true, this);
-    }
+    imageInfo->mHasData = true;
 }
 
 ////////////////////////////////////////
 // CopyTex(Sub)Image
 
 static bool
 ValidateCopyTexImageFormats(WebGLContext* webgl,
                             const webgl::FormatInfo* srcFormat,
@@ -2058,17 +2054,17 @@ WebGLTexture::CopyTexImage2D(TexImageTar
 
     uint32_t width, height, depth;
     if (!ValidateExtents(mContext, rawWidth, rawHeight, 1, border, &width,
                          &height, &depth))
     {
         return;
     }
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImageSpecification(target, level, width, height, depth, &imageInfo))
         return;
     MOZ_ASSERT(imageInfo);
 
     ////////////////////////////////////
     // Get source info
 
     const webgl::FormatUsageInfo* srcUsage;
@@ -2117,18 +2113,20 @@ WebGLTexture::CopyTexImage2D(TexImageTar
     }
 
     mContext->OnDataAllocCall();
 
     ////////////////////////////////////
     // Update our specification data.
 
     const bool isDataInitialized = true;
-    const ImageInfo newImageInfo(dstUsage, width, height, depth, isDataInitialized);
-    SetImageInfo(imageInfo, newImageInfo);
+    const webgl::ImageInfo newImageInfo { dstUsage, width, height, depth,
+                                          isDataInitialized };
+    *imageInfo = newImageInfo;
+    InvalidateCaches();
 }
 
 void
 WebGLTexture::CopyTexSubImage(TexImageTarget target, GLint level,
                               GLint xOffset, GLint yOffset, GLint zOffset, GLint x,
                               GLint y, GLsizei rawWidth, GLsizei rawHeight)
 {
     uint32_t width, height, depth;
@@ -2136,17 +2134,17 @@ WebGLTexture::CopyTexSubImage(TexImageTa
                          &height, &depth))
     {
         return;
     }
 
     ////////////////////////////////////
     // Get dest info
 
-    WebGLTexture::ImageInfo* imageInfo;
+    webgl::ImageInfo* imageInfo;
     if (!ValidateTexImageSelection(target, level, xOffset, yOffset, zOffset,
                                    width, height, depth, &imageInfo))
     {
         return;
     }
     MOZ_ASSERT(imageInfo);
 
     auto dstUsage = imageInfo->mFormat;
@@ -2180,33 +2178,30 @@ WebGLTexture::CopyTexSubImage(TexImageTa
 
     auto srcFormat = srcUsage->format;
     if (!ValidateCopyTexImageFormats(mContext, srcFormat, dstFormat))
         return;
 
     ////////////////////////////////////
     // Do the thing!
 
-    bool uploadWillInitialize;
     if (!EnsureImageDataInitializedForUpload(this, target, level, xOffset,
                                              yOffset, zOffset, width, height, depth,
-                                             imageInfo, &uploadWillInitialize))
+                                             imageInfo))
     {
         return;
     }
 
     const bool isSubImage = true;
     if (!DoCopyTexOrSubImage(mContext, isSubImage, this, target, level, x, y,
                              srcTotalWidth, srcTotalHeight, srcUsage, xOffset, yOffset,
                              zOffset, width, height, dstUsage))
     {
         return;
     }
 
     ////////////////////////////////////
     // Update our specification data?
 
-    if (uploadWillInitialize) {
-        imageInfo->SetIsDataInitialized(true, this);
-    }
+    imageInfo->mHasData = true;
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLTypes.h
+++ b/dom/canvas/WebGLTypes.h
@@ -16,71 +16,28 @@ typedef int64_t WebGLintptr;
 typedef bool WebGLboolean;
 
 namespace mozilla {
 namespace gl {
 class GLContext; // This is going to be needed a lot.
 } // namespace gl
 
 /*
- * WebGLTextureFakeBlackStatus is an enum to track what needs to use a dummy 1x1 black
- * texture, which we refer to as a 'fake black' texture.
- *
- * There are two things that can cause us to use such 'fake black' textures:
- *
- *   (1) OpenGL ES rules on sampling incomplete textures specify that they
- *       must be sampled as RGBA(0, 0, 0, 1) (opaque black). We have to implement these rules
- *       ourselves, if only because we do not always run on OpenGL ES, and also
- *       because this is dangerously close to the kind of case where we don't
- *       want to trust the driver with corner cases of texture memory accesses.
- *
- *   (2) OpenGL has cases where a renderbuffer, or a texture image, can contain
- *       uninitialized image data. See below the comment about WebGLImageDataStatus.
- *       WebGL must never have access to uninitialized image data. The WebGL 1 spec,
- *       section 4.1 'Resource Restrictions', specifies that in any such case, the
- *       uninitialized image data must be exposed to WebGL as if it were filled
- *       with zero bytes, which means it's either opaque or transparent black
- *       depending on whether the image format has alpha.
- */
-
-enum class FakeBlackType : uint8_t {
-    None,
-    RGBA0001, // Incomplete textures and uninitialized no-alpha color textures.
-    RGBA0000, // Uninitialized with-alpha color textures.
-};
-
-/*
  * Implementing WebGL (or OpenGL ES 2.0) on top of desktop OpenGL requires
  * emulating the vertex attrib 0 array when it's not enabled. Indeed,
  * OpenGL ES 2.0 allows drawing without vertex attrib 0 array enabled, but
  * desktop OpenGL does not allow that.
  */
 enum class WebGLVertexAttrib0Status : uint8_t {
     Default, // default status - no emulation needed
     EmulatedUninitializedArray, // need an artificial attrib 0 array, but contents may be left uninitialized
     EmulatedInitializedArray // need an artificial attrib 0 array, and contents must be initialized
 };
 
 /*
- * Enum to track the status of image data (renderbuffer or texture image) presence
- * and initialization.
- *
- * - NoImageData is the initial state before any image data is allocated.
- * - InitializedImageData is the state after image data is allocated and initialized.
- * - UninitializedImageData is an intermediate state where data is allocated but not
- *   initialized. It is the state that renderbuffers are in after a renderbufferStorage call,
- *   and it is the state that texture images are in after a texImage2D call with null data.
- */
-enum class WebGLImageDataStatus : uint8_t {
-    NoImageData,
-    UninitializedImageData,
-    InitializedImageData
-};
-
-/*
  * The formats that may participate, either as source or destination formats,
  * in WebGL texture conversions. This includes:
  *  - all the formats accepted by WebGL.texImage2D, e.g. RGBA4444
  *  - additional formats provided by extensions, e.g. RGB32F
  *  - additional source formats, depending on browser details, used when uploading
  *    textures from DOM elements. See gfxImageSurface::Format().
  */
 enum class WebGLTexelFormat : uint8_t {
@@ -214,11 +171,34 @@ public:
     explicit operator bool() const { return bool(mBuffer); }
 
     void* get() const { return mBuffer; }
 
     UniqueBuffer(const UniqueBuffer& other) = delete; // construct using std::move()!
     void operator =(const UniqueBuffer& other) = delete; // assign using std::move()!
 };
 
+namespace webgl {
+struct FormatUsageInfo;
+
+struct SampleableInfo final
+{
+    const char* incompleteReason = nullptr;
+    uint32_t levels = 0;
+    const webgl::FormatUsageInfo* usage = nullptr;
+    bool isDepthTexCompare = false;
+
+    bool IsComplete() const { return bool(levels); }
+};
+
+enum class AttribBaseType : uint8_t {
+    Int,
+    UInt,
+    Float, // Also includes NormU?Int
+    Bool, // Can convert from anything.
+};
+const char* ToString(AttribBaseType);
+
+} // namespace webgl
+
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGLUniformLocation.cpp
+++ b/dom/canvas/WebGLUniformLocation.cpp
@@ -42,102 +42,37 @@ WebGLUniformLocation::ValidateForProgram
         mContext->ErrorInvalidOperation("This uniform location corresponds to a"
                                         " different program.");
         return false;
     }
 
     return true;
 }
 
-static bool
-IsUniformSetterTypeValid(GLenum setterType, GLenum uniformType)
-{
-    // The order in this switch matches table 2.10 from OpenGL ES
-    // 3.0.4 (Aug 27, 2014) es_spec_3.0.4.pdf
-    switch (uniformType) {
-    case LOCAL_GL_FLOAT:
-    case LOCAL_GL_FLOAT_VEC2:
-    case LOCAL_GL_FLOAT_VEC3:
-    case LOCAL_GL_FLOAT_VEC4:
-        return setterType == LOCAL_GL_FLOAT;
-
-    case LOCAL_GL_INT:
-    case LOCAL_GL_INT_VEC2:
-    case LOCAL_GL_INT_VEC3:
-    case LOCAL_GL_INT_VEC4:
-        return setterType == LOCAL_GL_INT;
-
-    case LOCAL_GL_UNSIGNED_INT:
-    case LOCAL_GL_UNSIGNED_INT_VEC2:
-    case LOCAL_GL_UNSIGNED_INT_VEC3:
-    case LOCAL_GL_UNSIGNED_INT_VEC4:
-        return setterType == LOCAL_GL_UNSIGNED_INT;
-
-        /* bool can be set via any function: 0, 0.0f -> FALSE, _ -> TRUE */
-    case LOCAL_GL_BOOL:
-    case LOCAL_GL_BOOL_VEC2:
-    case LOCAL_GL_BOOL_VEC3:
-    case LOCAL_GL_BOOL_VEC4:
-        return (setterType == LOCAL_GL_INT   ||
-                setterType == LOCAL_GL_FLOAT ||
-                setterType == LOCAL_GL_UNSIGNED_INT);
-
-    case LOCAL_GL_FLOAT_MAT2:
-    case LOCAL_GL_FLOAT_MAT3:
-    case LOCAL_GL_FLOAT_MAT4:
-    case LOCAL_GL_FLOAT_MAT2x3:
-    case LOCAL_GL_FLOAT_MAT2x4:
-    case LOCAL_GL_FLOAT_MAT3x2:
-    case LOCAL_GL_FLOAT_MAT3x4:
-    case LOCAL_GL_FLOAT_MAT4x2:
-    case LOCAL_GL_FLOAT_MAT4x3:
-        return setterType == LOCAL_GL_FLOAT;
-
-        /* Samplers can only be set via Uniform1i */
-    case LOCAL_GL_SAMPLER_2D:
-    case LOCAL_GL_SAMPLER_3D:
-    case LOCAL_GL_SAMPLER_CUBE:
-    case LOCAL_GL_SAMPLER_2D_SHADOW:
-    case LOCAL_GL_SAMPLER_2D_ARRAY:
-    case LOCAL_GL_SAMPLER_2D_ARRAY_SHADOW:
-    case LOCAL_GL_SAMPLER_CUBE_SHADOW:
-
-    case LOCAL_GL_INT_SAMPLER_2D:
-    case LOCAL_GL_INT_SAMPLER_3D:
-    case LOCAL_GL_INT_SAMPLER_CUBE:
-    case LOCAL_GL_INT_SAMPLER_2D_ARRAY:
-
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_3D:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_CUBE:
-    case LOCAL_GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
-        return setterType == LOCAL_GL_INT;
-
-    default:
-        MOZ_CRASH("GFX: Bad `uniformType`.");
-    }
-}
-
 bool
-WebGLUniformLocation::ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType) const
+WebGLUniformLocation::ValidateSizeAndType(const uint8_t setterElemSize,
+                                          const webgl::AttribBaseType setterType) const
 {
     MOZ_ASSERT(mLinkInfo);
 
     const auto& uniformElemSize = mInfo->mActiveInfo->mElemSize;
     if (setterElemSize != uniformElemSize) {
         mContext->ErrorInvalidOperation("Function used differs from uniform size: %i",
                                         uniformElemSize);
         return false;
     }
 
-    const auto& uniformElemType = mInfo->mActiveInfo->mElemType;
-    if (!IsUniformSetterTypeValid(setterType, uniformElemType)) {
+    const auto& uniformType = mInfo->mActiveInfo->mBaseType;
+    if (setterType != uniformType &&
+        uniformType != webgl::AttribBaseType::Bool)
+    {
+        const auto& uniformStr = EnumString(mInfo->mActiveInfo->mElemType);
         mContext->ErrorInvalidOperation("Function used is incompatible with uniform"
-                                        " type: %i",
-                                        uniformElemType);
+                                        " of type: %s",
+                                        uniformStr.c_str());
         return false;
     }
 
     return true;
 }
 
 bool
 WebGLUniformLocation::ValidateArrayLength(uint8_t setterElemSize,
--- a/dom/canvas/WebGLUniformLocation.h
+++ b/dom/canvas/WebGLUniformLocation.h
@@ -48,17 +48,18 @@ public:
     const size_t mArrayIndex;
 
     //////
 
     WebGLUniformLocation(WebGLContext* webgl, const webgl::LinkedProgramInfo* linkInfo,
                          webgl::UniformInfo* info, GLuint loc, size_t arrayIndex);
 
     bool ValidateForProgram(const WebGLProgram* prog) const;
-    bool ValidateSizeAndType(uint8_t setterElemSize, GLenum setterType) const;
+    bool ValidateSizeAndType(uint8_t setterElemSize,
+                             webgl::AttribBaseType setterType) const;
     bool ValidateArrayLength(uint8_t setterElemSize, size_t setterArraySize) const;
 
     JS::Value GetUniform(JSContext* js) const;
 
     // Needed for certain helper functions like ValidateObject.
     // `WebGLUniformLocation`s can't be 'Deleted' in the WebGL sense.
     bool IsDeleted() const { return false; }
 
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -5,33 +5,33 @@
 
 #ifndef WEBGL_VERTEX_ARRAY_H_
 #define WEBGL_VERTEX_ARRAY_H_
 
 #include "nsTArray.h"
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 
-#include "CacheMap.h"
+#include "CacheInvalidator.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 #include "WebGLVertexAttribData.h"
 
 namespace mozilla {
 
 class WebGLVertexArrayFake;
 namespace webgl {
 struct LinkedProgramInfo;
 }
 
 class WebGLVertexArray
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLVertexArray>
     , public LinkedListElement<WebGLVertexArray>
-    , public CacheMapInvalidator
+    , public CacheInvalidator
 {
 public:
     static WebGLVertexArray* Create(WebGLContext* webgl);
 
     void BindVertexArray() {
         // Bind to dummy value to signal that this vertex array has ever been
         // bound.
         BindVertexArrayImpl();
--- a/dom/canvas/WebGLVertexAttribData.cpp
+++ b/dom/canvas/WebGLVertexAttribData.cpp
@@ -39,32 +39,32 @@ CalcBytesPerVertex(GLenum type, uint8_t 
 
     default:
         MOZ_CRASH("Bad `type`.");
     }
 
     return bytesPerType * size;
 }
 
-static GLenum
+static webgl::AttribBaseType
 AttribPointerBaseType(bool integerFunc, GLenum type)
 {
     if (!integerFunc)
-        return LOCAL_GL_FLOAT;
+        return webgl::AttribBaseType::Float;
 
     switch (type) {
     case LOCAL_GL_BYTE:
     case LOCAL_GL_SHORT:
     case LOCAL_GL_INT:
-        return LOCAL_GL_INT;
+        return webgl::AttribBaseType::Int;
 
     case LOCAL_GL_UNSIGNED_BYTE:
     case LOCAL_GL_UNSIGNED_SHORT:
     case LOCAL_GL_UNSIGNED_INT:
-        return LOCAL_GL_UNSIGNED_INT;
+        return webgl::AttribBaseType::UInt;
 
     default:
         MOZ_CRASH();
     }
 }
 
 void
 WebGLVertexAttribData::VertexAttribPointer(bool integerFunc, WebGLBuffer* buf,
--- a/dom/canvas/WebGLVertexAttribData.h
+++ b/dom/canvas/WebGLVertexAttribData.h
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef WEBGL_VERTEX_ATTRIB_DATA_H_
 #define WEBGL_VERTEX_ATTRIB_DATA_H_
 
 #include "GLDefs.h"
+#include "WebGLFormats.h"
 #include "WebGLObjectModel.h"
 
 namespace mozilla {
 
 class WebGLBuffer;
 
 class WebGLVertexAttribData final
 {
@@ -20,17 +21,17 @@ public:
     bool mEnabled;
 
 private:
     bool mIntegerFunc;
 public:
     WebGLRefPtr<WebGLBuffer> mBuf;
 private:
     GLenum mType;
-    GLenum mBaseType;
+    webgl::AttribBaseType mBaseType = webgl::AttribBaseType::Float;
     uint8_t mSize; // num of mType vals per vert
     uint8_t mBytesPerVertex;
     bool mNormalized;
     uint32_t mStride; // bytes
     uint32_t mExplicitStride;
     uint64_t mByteOffset;
 
 public:
@@ -50,17 +51,16 @@ public:
 #undef GETTER
 
     // note that these initial values are what GL initializes vertex attribs to
     WebGLVertexAttribData()
         : mDivisor(0)
         , mEnabled(false)
         , mIntegerFunc(false)
         , mType(0)
-        , mBaseType(0)
         , mSize(0)
         , mBytesPerVertex(0)
         , mNormalized(false)
         , mStride(0)
         , mExplicitStride(0)
         , mByteOffset(0)
     {
         VertexAttribPointer(false, nullptr, 4, LOCAL_GL_FLOAT, false, 0, 0);
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -82,17 +82,17 @@ UNIFIED_SOURCES += [
 ]
 
 SOURCES += [
     'ImageUtils.cpp',
 ]
 
 # WebGL Sources
 UNIFIED_SOURCES += [
-    'CacheMap.cpp',
+    'CacheInvalidator.cpp',
     'TexUnpackBlob.cpp',
     'WebGL1Context.cpp',
     'WebGL2Context.cpp',
     'WebGL2ContextBuffers.cpp',
     'WebGL2ContextFramebuffers.cpp',
     'WebGL2ContextMRTs.cpp',
     'WebGL2ContextPrograms.cpp',
     'WebGL2ContextQueries.cpp',
@@ -149,17 +149,16 @@ UNIFIED_SOURCES += [
     'WebGLExtensionTextureFilterAnisotropic.cpp',
     'WebGLExtensionTextureFloat.cpp',
     'WebGLExtensionTextureFloatLinear.cpp',
     'WebGLExtensionTextureHalfFloat.cpp',
     'WebGLExtensionTextureHalfFloatLinear.cpp',
     'WebGLExtensionVertexArray.cpp',
     'WebGLFormats.cpp',
     'WebGLFramebuffer.cpp',
-    'WebGLFramebufferAttachable.cpp',
     'WebGLMemoryTracker.cpp',
     'WebGLObjectModel.cpp',
     'WebGLProgram.cpp',
     'WebGLQuery.cpp',
     'WebGLRenderbuffer.cpp',
     'WebGLSampler.cpp',
     'WebGLShader.cpp',
     'WebGLShaderPrecisionFormat.cpp',
--- a/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-mipmap-levels.html
+++ b/dom/canvas/test/webgl-conf/checkout/conformance2/textures/misc/tex-mipmap-levels.html
@@ -185,33 +185,35 @@ wtu.glErrorShouldBe(gl, gl.NO_ERROR, "Sh
   gl.texSubImage3D(gl.TEXTURE_2D_ARRAY, 1, 0, 0, 0, 4, 4, 4, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4 * 4 * 4 * 4));
   wtu.glErrorShouldBe(gl, gl.NO_ERROR, "texSubImage3D should succeed");
   gl.deleteTexture(tex2dArray);
 
   // Test sized internal format should be both color-renderable and texture-filterable
   tex = gl.createTexture();
   gl.bindTexture(gl.TEXTURE_2D, tex);
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
-  gl.generateMipmap(gl.TEXTURE_2D);
-  wtu.glErrorShouldBe(gl, gl.NO_ERROR, "generateMipmap should succeed for zero-size texture");
+  // https://github.com/KhronosGroup/WebGL/pull/2728
+  //gl.generateMipmap(gl.TEXTURE_2D);
+  //wtu.glErrorShouldBe(gl, gl.NO_ERROR, "generateMipmap should succeed for zero-size texture");
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 8, 8, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(8 * 8 * 4));
   gl.generateMipmap(gl.TEXTURE_2D);
   wtu.glErrorShouldBe(gl, gl.NO_ERROR, "generateMipmap should succeed");
   gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8UI, 8, 8, 0, gl.RGBA_INTEGER, gl.UNSIGNED_BYTE, new Uint8Array(8 * 8 * 4));
   gl.generateMipmap(gl.TEXTURE_2D);
   wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "generateMipmap should fail for non-texture-filterable format");
   if (gl.getExtension('EXT_color_buffer_float')) {
       gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, 8, 8, 0, gl.RGBA, gl.FLOAT, new Float32Array(8 * 8 * 4));
       gl.generateMipmap(gl.TEXTURE_2D);
       wtu.glErrorShouldBe(gl, gl.INVALID_OPERATION, "generateMipmap should fail for float texture");
   }
   if (gl.getExtension('EXT_color_buffer_float') && gl.getExtension('OES_texture_float_linear')) {
       gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, 0, 0, 0, gl.RGBA, gl.FLOAT, null);
-      gl.generateMipmap(gl.TEXTURE_2D);
-      wtu.glErrorShouldBe(gl, gl.NO_ERROR, "generateMipmap should succeed for zero-size texture");
+      // https://github.com/KhronosGroup/WebGL/pull/2728
+      //gl.generateMipmap(gl.TEXTURE_2D);
+      //wtu.glErrorShouldBe(gl, gl.NO_ERROR, "generateMipmap should succeed for zero-size texture");
       gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA32F, 8, 8, 0, gl.RGBA, gl.FLOAT, new Float32Array(8 * 8 * 4));
       gl.generateMipmap(gl.TEXTURE_2D);
       wtu.glErrorShouldBe(gl, gl.NO_ERROR, "generateMipmap should succeed");
   }
   gl.deleteTexture(tex);
 
   // Test textureSize should work correctly with non-zero base level for texStorage2D
   var program = wtu.setupProgram(
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5319,17 +5319,16 @@ fail-if = (os == 'win')
 [generated/test_2_conformance2__rendering__element-index-uint.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__framebuffer-completeness-draw-framebuffer.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__framebuffer-completeness-unaffected.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__framebuffer-texture-changing-base-level.html]
 subsuite = webgl2-core
-fail-if = (os == 'win')
 [generated/test_2_conformance2__rendering__framebuffer-texture-level1.html]
 subsuite = webgl2-core
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__rendering__framebuffer-unsupported.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__fs-color-type-mismatch-color-buffer-type.html]
 subsuite = webgl2-core
 fail-if = (os == 'mac') || (os == 'win')
@@ -5343,19 +5342,19 @@ subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__line-rendering-quality.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__multisampling-fragment-evaluation.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__out-of-bounds-index-buffers-after-copying.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__read-draw-when-missing-image.html]
 subsuite = webgl2-core
+[generated/test_2_conformance2__rendering__rendering-sampling-feedback-loop.html]
+subsuite = webgl2-core
 fail-if = 1
-[generated/test_2_conformance2__rendering__rendering-sampling-feedback-loop.html]
-subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__rgb-format-support.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__texture-switch-performance.html]
 subsuite = webgl2-core
 skip-if = 1
 [generated/test_2_conformance2__rendering__uniform-block-buffer-size.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__samplers__multi-context-sampler-test.html]
@@ -6824,28 +6823,26 @@ subsuite = webgl2-ext
 [generated/test_2_conformance2__textures__image_data__tex-3d-srgb8-rgb-unsigned_byte.html]
 subsuite = webgl2-ext
 [generated/test_2_conformance2__textures__image_data__tex-3d-srgb8_alpha8-rgba-unsigned_byte.html]
 subsuite = webgl2-ext
 [generated/test_2_conformance2__textures__misc__active-3d-texture-bug.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__misc__angle-stuck-depth-textures.html]
 subsuite = webgl2-core
-fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__misc__canvas-remains-unchanged-after-used-in-webgl-texture.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__misc__compressed-tex-from-pbo-crash.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__misc__copy-texture-cube-map-AMD-bug.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__misc__copy-texture-cube-map-bug.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__misc__copy-texture-image-luma-format.html]
 subsuite = webgl2-core
-fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__misc__copy-texture-image-same-texture.html]
 subsuite = webgl2-core
 skip-if = (os == 'win')
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__misc__copy-texture-image-webgl-specific.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__textures__misc__copy-texture-image.html]
 subsuite = webgl2-core
@@ -7386,17 +7383,16 @@ subsuite = webgl2-core
 [generated/test_2_conformance2__uniforms__dependent-buffer-change.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__uniforms__draw-with-uniform-blocks.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__uniforms__gl-uniform-arrays-sub-source.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__uniforms__incompatible-texture-type-for-sampler.html]
 subsuite = webgl2-core
-fail-if = 1
 [generated/test_2_conformance2__uniforms__query-uniform-blocks-after-shader-detach.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__uniforms__simple-buffer-change.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__uniforms__uniform-blocks-with-arrays.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__vertex_arrays__vertex-array-object.html]
 subsuite = webgl2-core
@@ -11015,16 +11011,17 @@ subsuite = webgl1-core
 [generated/test_conformance__extensions__webgl-debug-shaders.html]
 subsuite = webgl1-core
 [generated/test_conformance__extensions__webgl-depth-texture.html]
 subsuite = webgl1-core
 [generated/test_conformance__extensions__webgl-draw-buffers-broadcast-return.html]
 subsuite = webgl1-core
 [generated/test_conformance__extensions__webgl-draw-buffers-feedback-loop.html]
 subsuite = webgl1-core
+fail-if = 1
 [generated/test_conformance__extensions__webgl-draw-buffers-framebuffer-unsupported.html]
 subsuite = webgl1-core
 [generated/test_conformance__extensions__webgl-draw-buffers-max-draw-buffers.html]
 subsuite = webgl1-core
 [generated/test_conformance__extensions__webgl-draw-buffers.html]
 subsuite = webgl1-core
 skip-if = (os == 'linux')
 [generated/test_conformance__glsl__bugs__angle-ambiguous-function-call.html]
@@ -12297,23 +12294,20 @@ subsuite = webgl1-core
 [generated/test_conformance__state__gl-initial-state.html]
 subsuite = webgl1-core
 [generated/test_conformance__state__gl-object-get-calls.html]
 subsuite = webgl1-core
 [generated/test_conformance__state__state-uneffected-after-compositing.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__canvas__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__canvas__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__canvas__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
@@ -12332,118 +12326,100 @@ subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas_sub_rectangle__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas_sub_rectangle__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__canvas_sub_rectangle__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
@@ -12462,23 +12438,20 @@ subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_video__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_video__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_bitmap_from_video__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_data__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_data__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_data__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__image_data__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_data__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_data__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__image_data__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
@@ -12495,18 +12468,16 @@ subsuite = webgl1-core
 [generated/test_conformance__textures__misc__copy-tex-image-crash.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__misc__copytexsubimage2d-large-partial-copy-corruption.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__misc__copytexsubimage2d-subrects.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__misc__cube-incomplete-fbo.html]
 subsuite = webgl1-core
-fail-if = (os == 'mac') || (os == 'linux')
-skip-if = (os == 'android')
 [generated/test_conformance__textures__misc__cube-map-uploads-out-of-order.html]
 subsuite = webgl1-core
 skip-if = (os == 'mac')
 [generated/test_conformance__textures__misc__default-texture.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__misc__gl-get-tex-parameter.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__misc__gl-pixelstorei.html]
@@ -12600,23 +12571,20 @@ subsuite = webgl1-core
 subsuite = webgl1-core
 [generated/test_conformance__textures__misc__texture-upload-size.html]
 subsuite = webgl1-core
 skip-if = (os == 'win') || (os == 'android')
 [generated/test_conformance__textures__misc__texture-with-flip-y-and-premultiply-alpha.html]
 subsuite = webgl1-core
 [generated/test_conformance__textures__svg_image__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__svg_image__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__svg_image__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__svg_image__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__svg_image__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__svg_image__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__svg_image__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
@@ -12635,23 +12603,20 @@ subsuite = webgl1-ext
 [generated/test_conformance__textures__video__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__video__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__video__tex-2d-rgba-rgba-unsigned_short_5_5_5_1.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__webgl_canvas__tex-2d-alpha-alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__webgl_canvas__tex-2d-luminance-luminance-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__webgl_canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 subsuite = webgl1-ext
-fail-if = (os == 'mac')
 [generated/test_conformance__textures__webgl_canvas__tex-2d-rgb-rgb-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__webgl_canvas__tex-2d-rgb-rgb-unsigned_short_5_6_5.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__webgl_canvas__tex-2d-rgba-rgba-unsigned_byte.html]
 subsuite = webgl1-ext
 [generated/test_conformance__textures__webgl_canvas__tex-2d-rgba-rgba-unsigned_short_4_4_4_4.html]
 subsuite = webgl1-ext
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -77,23 +77,19 @@ skip-if = (os == 'win')
 
 [generated/test_2_conformance2__rendering__blitframebuffer-resolve-to-back-buffer.html]
 # ABORT_ON_ERROR
 skip-if = 1
 [generated/test_2_conformance2__rendering__depth-stencil-feedback-loop.html]
 fail-if = 1
 [generated/test_2_conformance2__rendering__instanced-arrays.html]
 fail-if = 1
-[generated/test_2_conformance2__rendering__read-draw-when-missing-image.html]
-fail-if = 1
 [generated/test_2_conformance2__transform_feedback__too-small-buffers.html]
 # ABORT_ON_ERROR
 skip-if = 1
-[generated/test_2_conformance2__uniforms__incompatible-texture-type-for-sampler.html]
-fail-if = 1
 [generated/test_2_conformance2__attribs__gl-bindAttribLocation-aliasing-inactive.html]
 fail-if = (os != 'win')
 
 [generated/test_conformance__rendering__texture-switch-performance.html]
 # Frequent orange on linux+asan, but likely intermittant:
 # Texture switching significantly hurt performance - achieved 77 frames in 2.016 seconds (0.79 times baseline performance)
 skip-if = 1
 [generated/test_2_conformance__rendering__texture-switch-performance.html]
@@ -154,16 +150,22 @@ fail-if = 1
 fail-if = 1
 
 [generated/test_2_conformance2__textures__misc__tex-unpack-params-with-flip-y-and-premultiply-alpha.html]
 fail-if = 1
 [generated/test_2_conformance2__transform_feedback__same-buffer-two-binding-points.html]
 fail-if = 1
 [generated/test_2_conformance2__transform_feedback__simultaneous_binding.html]
 fail-if = 1
+[generated/test_conformance__extensions__webgl-draw-buffers-feedback-loop.html]
+# Bad test.
+fail-if = 1
+[generated/test_2_conformance2__rendering__rendering-sampling-feedback-loop.html]
+# Bad test.
+fail-if = 1
 
 ########################################################################
 # Complicated
 
 [generated/test_conformance__context__context-attributes-alpha-depth-stencil-antialias.html]
 # Asserts on linux debug. Crashes on Android.
 skip-if = (os == 'linux') || (os == 'android')
 
@@ -236,19 +238,16 @@ skip-if = (os == 'mac') || (os == 'win')
 # skip because some result logged after SimpleTest.finish()
 skip-if = (os == 'mac') || (os == 'win') || (os == 'linux') || (os == 'android')
 [generated/test_2_conformance__glsl__constructors__glsl-construct-vec-mat-index.html]
 # skip this test because finish() was called multiple times
 skip-if = (os == 'mac') || (os == 'win') || (os == 'linux') || (os == 'android')
 [generated/test_conformance__glsl__constructors__glsl-construct-vec-mat-index.html]
 # skip this test because finish() was called multiple times
 skip-if = (os == 'mac') || (os == 'win') || (os == 'linux') || (os == 'android')
-[generated/test_conformance__textures__misc__cube-incomplete-fbo.html]
-fail-if = (os == 'mac') || (os == 'linux')
-skip-if = (os == 'android')
 [generated/test_conformance__glsl__bugs__sampler-struct-function-arg.html]
 # Crashes
 skip-if = (os == 'linux') || (os == 'android')
 [generated/test_conformance__glsl__constructors__glsl-construct-bvec2.html]
 # mozalloc_abort in libglsl.so
 skip-if = (os == 'linux') || (os == 'android')
 [generated/test_conformance__glsl__bugs__pow-of-small-constant-in-user-defined-function.html]
 skip-if = (os == 'android')
@@ -804,18 +803,16 @@ fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__image__tex-3d-srgb8_alpha8-rgba-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__image_data__tex-2d-rg8ui-rg_integer-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__image_data__tex-2d-rgb8ui-rgb_integer-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__image_data__tex-2d-rgba8ui-rgba_integer-unsigned_byte.html]
 fail-if = (os == 'mac')
-[generated/test_2_conformance2__textures__misc__copy-texture-image-luma-format.html]
-fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__misc__integer-cubemap-texture-sampling.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__misc__tex-mipmap-levels.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__video__tex-3d-r16f-red-float.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__video__tex-3d-r16f-red-half_float.html]
 fail-if = (os == 'mac')
@@ -829,78 +826,16 @@ fail-if = (os == 'mac')
 fail-if = (os == 'mac')
 [generated/test_2_conformance2__rendering__framebuffer-texture-level1.html]
 fail-if = (os == 'mac')
 [generated/test_conformance__glsl__bugs__unary-minus-operator-float-bug.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__glsl__bugs__unary-minus-operator-float-bug.html]
 fail-if = (os == 'mac')
 
-[generated/test_conformance__textures__canvas__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__canvas__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_blob__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image_bitmap__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_bitmap_from_image_data__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_data__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_data__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__image_data__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-
-[generated/test_conformance__textures__svg_image__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__svg_image__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__svg_image__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__webgl_canvas__tex-2d-alpha-alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__webgl_canvas__tex-2d-luminance-luminance-unsigned_byte.html]
-fail-if = (os == 'mac')
-[generated/test_conformance__textures__webgl_canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
-fail-if = (os == 'mac')
-
 [generated/test_2_conformance__textures__canvas__tex-2d-alpha-alpha-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__textures__canvas__tex-2d-luminance-luminance-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__textures__canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__textures__canvas_sub_rectangle__tex-2d-alpha-alpha-unsigned_byte.html]
 fail-if = (os == 'mac')
@@ -958,18 +893,16 @@ fail-if = (os == 'mac')
 fail-if = (os == 'mac')
 [generated/test_2_conformance__textures__webgl_canvas__tex-2d-alpha-alpha-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__textures__webgl_canvas__tex-2d-luminance-luminance-unsigned_byte.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__textures__webgl_canvas__tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html]
 fail-if = (os == 'mac')
 
-[generated/test_2_conformance2__textures__misc__angle-stuck-depth-textures.html]
-fail-if = (os == 'mac')
 [generated/test_2_conformance2__textures__misc__tex-3d-mipmap-levels-intel-bug.html]
 fail-if = (os == 'mac')
 
 [generated/test_2_conformance2__extensions__ext-color-buffer-float.html]
 skip-if = (os == 'mac' && debug)
 [generated/test_2_conformance__limits__gl-line-width.html]
 skip-if = (os == 'mac')
 [generated/test_2_conformance__misc__type-conversion-test.html]
@@ -1144,18 +1077,16 @@ skip-if = (os == 'win' && os_version == 
 # Frequent but intermittent timeout on win7. Bug 1404234
 skip-if = (os == 'win' && os_version == '6.1')
 [generated/test_2_conformance2__textures__misc__tex-input-validation.html]
 skip-if = (os == 'win')
 [generated/test_2_conformance2__buffers__get-buffer-sub-data.html]
 skip-if = (os == 'win')
 [generated/test_2_conformance2__rendering__draw-with-integer-texture-base-level.html]
 fail-if = (os == 'win')
-[generated/test_2_conformance2__rendering__framebuffer-texture-changing-base-level.html]
-fail-if = (os == 'win')
 [generated/test_2_conformance__rendering__preservedrawingbuffer-leak.html]
 skip-if = (os == 'win')
 [generated/test_conformance__context__context-size-change.html]
 skip-if = (os == 'win')
 [generated/test_conformance__rendering__preservedrawingbuffer-leak.html]
 skip-if = (os == 'win')
 [generated/test_2_conformance2__glsl3__array-initialize-with-same-name-array.html]
 fail-if = (os == 'win')