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 500040 59befcc4a2d6886d0d642710dd4b0ebc25a62082
parent 500039 daf3e491e6fff54278e1564b7b7c25f115076e1c
child 500041 46c013fd9f6a051e5bc47ddf1405b9564e907f59
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1498070
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 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')