Bug 1404196 - Simplify and repair vertex fetch. - r=daoshengmu draft
authorJeff Gilbert <jgilbert@mozilla.com>
Fri, 20 Oct 2017 15:40:12 -0700
changeset 689134 8b7373c5396dae3ac2b40ab20ad549ed398b5b95
parent 689133 1f83d85da3a86f5acef633c1f48d3e1686fcca9a
child 738230 18dd35545909a822f49ccd1a3198c37220b547c4
push id86923
push userbmo:jgilbert@mozilla.com
push dateTue, 31 Oct 2017 03:29:56 +0000
reviewersdaoshengmu
bugs1404196
milestone58.0a1
Bug 1404196 - Simplify and repair vertex fetch. - r=daoshengmu MozReview-Commit-ID: FL7uibuv4VY
dom/canvas/WebGLBuffer.cpp
dom/canvas/WebGLBuffer.h
dom/canvas/WebGLContext.cpp
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextBuffers.cpp
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLContextValidate.cpp
dom/canvas/WebGLContextVertexArray.cpp
dom/canvas/WebGLContextVertices.cpp
dom/canvas/WebGLProgram.cpp
dom/canvas/WebGLProgram.h
dom/canvas/WebGLVertexArray.h
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -54,17 +54,20 @@ WebGLBuffer::SetContentAfterBind(GLenum 
     }
 }
 
 void
 WebGLBuffer::Delete()
 {
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteBuffers(1, &mGLName);
+
     mByteLength = 0;
+    mFetchInvalidator.InvalidateCaches();
+
     mIndexCache = nullptr;
     mIndexRanges.clear();
     LinkedListElement<WebGLBuffer>::remove(); // remove from mContext->mBuffers
 }
 
 ////////////////////////////////////////
 
 static bool
@@ -133,18 +136,16 @@ WebGLBuffer::BufferData(GLenum target, s
     }
 
     const auto& gl = mContext->gl;
     gl->MakeCurrent();
     const ScopedLazyBind lazyBind(gl, target, this);
 
     const bool sizeChanges = (size != ByteLength());
     if (sizeChanges) {
-        mContext->InvalidateBufferFetching();
-
         gl::GLContext::LocalErrorScope errorScope(*gl);
         gl->fBufferData(target, size, uploadData, usage);
         const auto error = errorScope.GetError();
 
         if (error) {
             MOZ_ASSERT(error == LOCAL_GL_OUT_OF_MEMORY);
             mContext->ErrorOutOfMemory("%s: Error from driver: 0x%04x", funcName, error);
             return;
@@ -152,16 +153,17 @@ WebGLBuffer::BufferData(GLenum target, s
     } else {
         gl->fBufferData(target, size, uploadData, usage);
     }
 
     mContext->OnDataAllocCall();
 
     mUsage = usage;
     mByteLength = size;
+    mFetchInvalidator.InvalidateCaches();
     mIndexCache = Move(newIndexCache);
 
     if (mIndexCache) {
         if (!mIndexRanges.empty()) {
             mContext->GeneratePerfWarning("[%p] Invalidating %u ranges.", this,
                                           uint32_t(mIndexRanges.size()));
             mIndexRanges.clear();
         }
@@ -229,28 +231,28 @@ IndexByteSizeByType(GLenum type)
     case LOCAL_GL_UNSIGNED_SHORT: return 2;
     case LOCAL_GL_UNSIGNED_INT:   return 4;
     default:
         MOZ_CRASH();
     }
 }
 
 void
-WebGLBuffer::InvalidateCacheRange(size_t byteOffset, size_t byteLength) const
+WebGLBuffer::InvalidateCacheRange(uint64_t byteOffset, uint64_t byteLength) const
 {
     MOZ_ASSERT(mIndexCache);
 
     std::vector<IndexRange> invalids;
-    const size_t updateBegin = byteOffset;
-    const size_t updateEnd = updateBegin + byteLength;
+    const uint64_t updateBegin = byteOffset;
+    const uint64_t updateEnd = updateBegin + byteLength;
     for (const auto& cur : mIndexRanges) {
         const auto& range = cur.first;
         const auto& indexByteSize = IndexByteSizeByType(range.type);
-        const size_t rangeBegin = range.first * indexByteSize;
-        const size_t rangeEnd = rangeBegin + range.count*indexByteSize;
+        const auto rangeBegin = range.byteOffset * indexByteSize;
+        const auto rangeEnd = rangeBegin + uint64_t(range.indexCount) * indexByteSize;
         if (rangeBegin >= updateEnd || rangeEnd <= updateBegin)
             continue;
         invalids.push_back(range);
     }
 
     if (!invalids.empty()) {
         mContext->GeneratePerfWarning("[%p] Invalidating %u/%u ranges.", this,
                                       uint32_t(invalids.size()),
@@ -268,89 +270,96 @@ WebGLBuffer::SizeOfIncludingThis(mozilla
     size_t size = mallocSizeOf(this);
     if (mIndexCache) {
         size += mByteLength;
     }
     return size;
 }
 
 template<typename T>
-static size_t
-MaxForRange(const void* data, size_t first, size_t count, const uint32_t ignoredVal)
+static Maybe<uint32_t>
+MaxForRange(const void* const start, const uint32_t count,
+            const Maybe<uint32_t>& untypedIgnoredVal)
 {
-    const T ignoredTVal(ignoredVal);
-    T ret = 0;
+    const Maybe<T> ignoredVal = (untypedIgnoredVal ? Some(T(untypedIgnoredVal.value()))
+                                                   : Nothing());
+    Maybe<uint32_t> maxVal;
 
-    auto itr = (const T*)data + first;
+    auto itr = (const T*)start;
     const auto end = itr + count;
 
     for (; itr != end; ++itr) {
         const auto& val = *itr;
-        if (val <= ret)
+        if (ignoredVal && val == ignoredVal.value())
             continue;
 
-        if (val == ignoredTVal)
+        if (maxVal && val <= maxVal.value())
             continue;
 
-        ret = val;
+        maxVal = Some(val);
     }
 
-    return size_t(ret);
+    return maxVal;
 }
 
-const uint32_t kMaxIndexRanges = 256;
+static const uint32_t kMaxIndexRanges = 256;
 
-bool
-WebGLBuffer::ValidateIndexedFetch(GLenum type, uint32_t numFetchable, size_t first,
-                                  size_t count) const
+Maybe<uint32_t>
+WebGLBuffer::GetIndexedFetchMaxVert(const GLenum type, const uint64_t byteOffset,
+                                    const uint32_t indexCount) const
 {
     if (!mIndexCache)
-        return true;
+        return Nothing();
 
-    if (!count)
-        return true;
-
-    const IndexRange range = { type, first, count };
-    auto res = mIndexRanges.insert({ range, size_t(0) });
+    const IndexRange range = { type, byteOffset, indexCount };
+    auto res = mIndexRanges.insert({ range, Nothing() });
     if (mIndexRanges.size() > kMaxIndexRanges) {
         mContext->GeneratePerfWarning("[%p] Clearing mIndexRanges after exceeding %u.",
                                       this, kMaxIndexRanges);
         mIndexRanges.clear();
-        res = mIndexRanges.insert({ range, size_t(0) });
+        res = mIndexRanges.insert({ range, Nothing() });
     }
 
     const auto& itr = res.first;
     const auto& didInsert = res.second;
 
     auto& maxFetchIndex = itr->second;
     if (didInsert) {
         const auto& data = mIndexCache.get();
-        const uint32_t ignoreVal = (mContext->IsWebGL2() ? UINT32_MAX : 0);
+
+        const auto start = (const uint8_t*)data + byteOffset;
+
+        Maybe<uint32_t> ignoredVal;
+        if (mContext->IsWebGL2()) {
+            ignoredVal = Some(UINT32_MAX);
+        }
 
         switch (type) {
         case LOCAL_GL_UNSIGNED_BYTE:
-            maxFetchIndex = MaxForRange<uint8_t>(data, first, count, ignoreVal);
+            maxFetchIndex = MaxForRange<uint8_t>(start, indexCount, ignoredVal);
             break;
         case LOCAL_GL_UNSIGNED_SHORT:
-            maxFetchIndex = MaxForRange<uint16_t>(data, first, count, ignoreVal);
+            maxFetchIndex = MaxForRange<uint16_t>(start, indexCount, ignoredVal);
             break;
         case LOCAL_GL_UNSIGNED_INT:
-            maxFetchIndex = MaxForRange<uint32_t>(data, first, count, ignoreVal);
+            maxFetchIndex = MaxForRange<uint32_t>(start, indexCount, ignoredVal);
             break;
         default:
             MOZ_CRASH();
         }
-
-        mContext->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %u, %u): %u", this,
-                                      uint32_t(mIndexRanges.size()), type,
-                                      uint32_t(first), uint32_t(count),
-                                      uint32_t(maxFetchIndex));
+        const auto displayMaxVertIndex = maxFetchIndex ? int64_t(maxFetchIndex.value())
+                                                       : -1;
+        mContext->GeneratePerfWarning("[%p] New range #%u: (0x%04x, %" PRIu64 ", %u):"
+                                      " %" PRIi64,
+                                      this, uint32_t(mIndexRanges.size()), range.type,
+                                      range.byteOffset, range.indexCount,
+                                      displayMaxVertIndex);
     }
 
-    return maxFetchIndex < numFetchable;
+    return maxFetchIndex;
 }
 
 ////
 
 bool
 WebGLBuffer::ValidateCanBindToTarget(const char* funcName, GLenum target)
 {
     /* https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.1
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -3,16 +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 "GLDefs.h"
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 #include "WebGLObjectModel.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
 
@@ -39,17 +40,18 @@ public:
 
     void Delete();
 
     size_t SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const;
 
     GLenum Usage() const { return mUsage; }
     size_t ByteLength() const { return mByteLength; }
 
-    bool ValidateIndexedFetch(GLenum type, uint32_t max_allowed, size_t first, size_t count) const;
+    Maybe<uint32_t> GetIndexedFetchMaxVert(GLenum type, uint64_t byteOffset,
+                                           uint32_t indexCount) const;
     bool ValidateRange(const char* funcName, size_t byteOffset, size_t byteLen) const;
 
     WebGLContext* GetParentObject() const {
         return mContext;
     }
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override;
 
@@ -62,16 +64,17 @@ public:
 
     static void AddBindCount(GLenum target, WebGLBuffer* buffer, int8_t addVal) {
         if (!buffer)
             return;
 
         if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER) {
             MOZ_ASSERT_IF(addVal < 0, buffer->mTFBindCount >= size_t(-addVal));
             buffer->mTFBindCount += addVal;
+            buffer->mFetchInvalidator.InvalidateCaches();
         } else {
             MOZ_ASSERT_IF(addVal < 0, buffer->mNonTFBindCount >= size_t(-addVal));
             buffer->mNonTFBindCount += addVal;
         }
     }
 
     static void SetSlot(GLenum target, WebGLBuffer* newBuffer,
                         WebGLRefPtr<WebGLBuffer>* const out_slot)
@@ -90,39 +93,42 @@ public:
     const GLenum mGLName;
 
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLBuffer)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLBuffer)
 
 protected:
     ~WebGLBuffer();
 
-    void InvalidateCacheRange(size_t offset, size_t length) const;
+    void InvalidateCacheRange(uint64_t byteOffset, uint64_t byteLength) const;
 
     Kind mContent;
     GLenum mUsage;
     size_t mByteLength;
     size_t mTFBindCount;
     size_t mNonTFBindCount;
 
     struct IndexRange final {
         GLenum type;
-        size_t first;
-        size_t count;
+        uint64_t byteOffset;
+        uint32_t indexCount;
 
         bool operator<(const IndexRange& x) const {
             if (type != x.type)
                 return type < x.type;
 
-            if (first != x.first)
-                return first < x.first;
+            if (byteOffset != x.byteOffset)
+                return byteOffset < x.byteOffset;
 
-            return count < x.count;
+            return indexCount < x.indexCount;
         }
     };
 
     UniqueBuffer mIndexCache;
-    mutable std::map<IndexRange, size_t> mIndexRanges;
+    mutable std::map<IndexRange, Maybe<uint32_t>> mIndexRanges;
+
+public:
+    CacheMapInvalidator mFetchInvalidator;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_BUFFER_H_
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -105,20 +105,16 @@ WebGLContextOptions::WebGLContextOptions
 }
 
 WebGLContext::WebGLContext()
     : WebGLContextUnchecked(nullptr)
     , mMaxPerfWarnings(gfxPrefs::WebGLMaxPerfWarnings())
     , mNumPerfWarnings(0)
     , mMaxAcceptableFBStatusInvals(gfxPrefs::WebGLMaxAcceptableFBStatusInvals())
     , mDataAllocGLCallCount(0)
-    , mBufferFetchingIsVerified(false)
-    , mBufferFetchingHasPerVertex(false)
-    , mMaxFetchedVertices(0)
-    , mMaxFetchedInstances(0)
     , mBypassShaderValidation(false)
     , mEmptyTFO(0)
     , mContextLossHandler(this)
     , mNeedsFakeNoAlpha(false)
     , mNeedsFakeNoDepth(false)
     , mNeedsFakeNoStencil(false)
     , mNeedsEmulatedLoneDepthStencil(false)
     , mAllowFBInvalidation(gfxPrefs::WebGLFBInvalidation())
@@ -177,18 +173,16 @@ WebGLContext::WebGLContext()
     mMaxWarnings = gfxPrefs::WebGLMaxWarningsPerContext();
     if (mMaxWarnings < -1) {
         GenerateWarning("webgl.max-warnings-per-context size is too large (seems like a negative value wrapped)");
         mMaxWarnings = 0;
     }
 
     mLastUseIndex = 0;
 
-    InvalidateBufferFetching();
-
     mDisableFragHighP = false;
 
     mDrawCallsSinceLastFlush = 0;
 }
 
 WebGLContext::~WebGLContext()
 {
     RemovePostRefreshObserver();
@@ -2274,16 +2268,37 @@ Intersect(const int32_t srcSize, const i
     }
 
     *out_intRead0 = intRead0;
     *out_intWrite0 = intWrite0;
     *out_intSize = intSize;
     return true;
 }
 
+// --
+
+uint64_t
+AvailGroups(const uint64_t totalAvailItems, const uint64_t firstItemOffset,
+            const uint32_t groupSize, const uint32_t groupStride)
+{
+    MOZ_ASSERT(groupSize && groupStride);
+    MOZ_ASSERT(groupSize <= groupStride);
+
+    if (totalAvailItems <= firstItemOffset)
+        return 0;
+    const size_t availItems = totalAvailItems - firstItemOffset;
+
+    size_t availGroups     = availItems / groupStride;
+    const size_t tailItems = availItems % groupStride;
+    if (tailItems >= groupSize) {
+        availGroups += 1;
+    }
+    return availGroups;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 
 CheckedUint32
 WebGLContext::GetUnpackSize(bool isFunc3D, uint32_t width, uint32_t height,
                             uint32_t depth, uint8_t bytesPerPixel)
 {
     if (!width || !height || !depth)
         return 0;
@@ -2389,32 +2404,34 @@ WebGLContext::ValidateArrayBufferView(co
         elemCount = elemCountOverride;
     }
 
     *out_bytes = bytes + (elemOffset * elemSize);
     *out_byteLen = elemCount * elemSize;
     return true;
 }
 
-////////////////////////////////////////////////////////////////////////////////
-// XPCOM goop
+////
 
 void
 WebGLContext::UpdateMaxDrawBuffers()
 {
     gl->MakeCurrent();
     mGLMaxColorAttachments = gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_COLOR_ATTACHMENTS);
     mGLMaxDrawBuffers = gl->GetIntAs<uint32_t>(LOCAL_GL_MAX_DRAW_BUFFERS);
 
     // WEBGL_draw_buffers:
     // "The value of the MAX_COLOR_ATTACHMENTS_WEBGL parameter must be greater than or
     //  equal to that of the MAX_DRAW_BUFFERS_WEBGL parameter."
     mGLMaxDrawBuffers = std::min(mGLMaxDrawBuffers, mGLMaxColorAttachments);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// XPCOM goop
+
 void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags)
 {
     for (const auto& cur : field) {
         ImplCycleCollectionTraverse(callback, cur.mBufferBinding, name, flags);
     }
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -29,16 +29,17 @@
 #include "ScopedGLHelpers.h"
 #include "TexUnpackBlob.h"
 
 #ifdef XP_MACOSX
 #include "ForceDiscreteGPUHelperCGL.h"
 #endif
 
 // Local
+#include "CacheMap.h"
 #include "WebGLContextLossHandler.h"
 #include "WebGLContextUnchecked.h"
 #include "WebGLFormats.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 #include "WebGLTexture.h"
 
 // Generated
@@ -288,16 +289,17 @@ class WebGLContext
     friend class WebGLExtensionCompressedTextureS3TC;
     friend class WebGLExtensionCompressedTextureS3TC_SRGB;
     friend class WebGLExtensionDepthTexture;
     friend class WebGLExtensionDisjointTimerQuery;
     friend class WebGLExtensionDrawBuffers;
     friend class WebGLExtensionLoseContext;
     friend class WebGLExtensionVertexArray;
     friend class WebGLMemoryTracker;
+    friend struct webgl::LinkedProgramInfo;
     friend struct webgl::UniformBlockInfo;
 
     enum {
         UNPACK_FLIP_Y_WEBGL = 0x9240,
         UNPACK_PREMULTIPLY_ALPHA_WEBGL = 0x9241,
         // We throw InvalidOperation in TexImage if we fail to use GPU fast-path
         // for texture copy when it is set to true, only for debug purpose.
         UNPACK_REQUIRE_FASTPATH = 0x10001,
@@ -1380,59 +1382,41 @@ public:
         const bool isFuncInt = false;
         VertexAttribAnyPointer(funcName, isFuncInt, index, size, type, normalized, stride,
                                byteOffset);
     }
 
     void VertexAttribDivisor(GLuint index, GLuint divisor);
 
 private:
-    // Cache the max number of vertices and instances that can be read from
-    // bound VBOs (result of ValidateBuffers).
-    bool mBufferFetchingIsVerified;
-    bool mBufferFetchingHasPerVertex;
-    uint32_t mMaxFetchedVertices;
-    uint32_t mMaxFetchedInstances;
-    bool mBufferFetch_IsAttrib0Active;
-
-    bool DrawArrays_check(const char* funcName, GLenum mode, GLint first,
-                          GLsizei vertCount, GLsizei instanceCount);
-    bool DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount,
-                            GLenum type, WebGLintptr byteOffset,
-                            GLsizei instanceCount);
-    bool DrawInstanced_check(const char* info);
+    bool DrawArrays_check(const char* funcName, GLint first, GLsizei vertCount,
+                          GLsizei instanceCount, Maybe<uint32_t>* out_lastVert);
+    bool DrawElements_check(const char* funcName, GLsizei indexCount, GLenum type,
+                            WebGLintptr byteOffset, GLsizei instanceCount,
+                            Maybe<uint32_t>* out_lastVert);
     void Draw_cleanup(const char* funcName);
 
     void VertexAttrib1fv_base(GLuint index, uint32_t arrayLength,
                               const GLfloat* ptr);
     void VertexAttrib2fv_base(GLuint index, uint32_t arrayLength,
                               const GLfloat* ptr);
     void VertexAttrib3fv_base(GLuint index, uint32_t arrayLength,
                               const GLfloat* ptr);
     void VertexAttrib4fv_base(GLuint index, uint32_t arrayLength,
                               const GLfloat* ptr);
 
-    bool ValidateBufferFetching(const char* info);
     bool BindArrayAttribToLocation0(WebGLProgram* prog);
 
 // -----------------------------------------------------------------------------
 // PROTECTED
 protected:
     WebGLVertexAttrib0Status WhatDoesVertexAttrib0Need() const;
     bool DoFakeVertexAttrib0(const char* funcName, GLuint vertexCount);
     void UndoFakeVertexAttrib0();
 
-    inline void InvalidateBufferFetching()
-    {
-        mBufferFetchingIsVerified = false;
-        mBufferFetchingHasPerVertex = false;
-        mMaxFetchedVertices = 0;
-        mMaxFetchedInstances = 0;
-    }
-
     CheckedUint32 mGeneration;
 
     WebGLContextOptions mOptions;
 
     bool mInvalidated;
     bool mCapturedFrameInvalidated;
     bool mResetLayer;
     bool mOptionsFrozen;
@@ -1917,16 +1901,17 @@ 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;
     uint8_t mGenericVertexAttrib0Data[sizeof(float) * 4];
+    CacheMapInvalidator mGenericVertexAttribTypeInvalidator;
 
     GLuint mFakeVertexAttrib0BufferObject;
     size_t mFakeVertexAttrib0BufferObjectSize;
     bool mFakeVertexAttrib0DataDefined;
     uint8_t mFakeVertexAttrib0Data[sizeof(float) * 4];
 
     JSObject* GetVertexAttribFloat32Array(JSContext* cx, GLuint index);
     JSObject* GetVertexAttribInt32Array(JSContext* cx, GLuint index);
@@ -2179,16 +2164,20 @@ private:
 };
 
 ////
 
 bool
 Intersect(int32_t srcSize, int32_t read0, int32_t readSize, int32_t* out_intRead0,
           int32_t* out_intWrite0, int32_t* out_intSize);
 
+uint64_t
+AvailGroups(uint64_t totalAvailItems, uint64_t firstItemOffset, uint32_t groupSize,
+            uint32_t groupStride);
+
 ////
 
 void
 ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
                             const std::vector<IndexedBufferBinding>& field,
                             const char* name, uint32_t flags = 0);
 
 void
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -496,18 +496,16 @@ WebGLContext::DeleteBuffer(WebGLBuffer* 
         for (auto& binding : mIndexedUniformBufferBindings) {
             fnClearIfBuffer(0, binding.mBufferBinding);
         }
     }
 
     ////
 
     buffer->RequestDelete();
-
-    InvalidateBufferFetching();
 }
 
 bool
 WebGLContext::IsBuffer(WebGLBuffer* buffer)
 {
     if (!ValidateIsObject("isBuffer", buffer))
         return false;
 
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -1,15 +1,16 @@
 /* -*- 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/. */
 
 #include "WebGLContext.h"
 
+#include "GeckoProfiler.h"
 #include "GLContext.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "nsPrintfCString.h"
 #include "WebGLBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
@@ -225,140 +226,62 @@ WebGLContext::BindFakeBlack(uint32_t tex
     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + texUnit);
     gl->fBindTexture(target.get(), fakeBlackTex->mGLName);
     gl->fActiveTexture(LOCAL_GL_TEXTURE0 + mActiveTexture);
     return true;
 }
 
 ////////////////////////////////////////
 
-bool
-WebGLContext::DrawInstanced_check(const char* info)
-{
-    MOZ_ASSERT(IsWebGL2() ||
-               IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays));
-    if (!mBufferFetchingHasPerVertex) {
-        /* http://www.khronos.org/registry/gles/extensions/ANGLE/ANGLE_instanced_arrays.txt
-         *  If all of the enabled vertex attribute arrays that are bound to active
-         *  generic attributes in the program have a non-zero divisor, the draw
-         *  call should return INVALID_OPERATION.
-         *
-         * NB: This also appears to apply to NV_instanced_arrays, though the
-         * INVALID_OPERATION emission is not explicitly stated.
-         * ARB_instanced_arrays does not have this restriction.
-         */
-        ErrorInvalidOperation("%s: at least one vertex attribute divisor should be 0", info);
-        return false;
-    }
-
-    return true;
-}
-
-bool
-WebGLContext::DrawArrays_check(const char* funcName, GLenum mode, GLint first,
-                               GLsizei vertCount, GLsizei instanceCount)
-{
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return false;
-
-    if (!ValidateNonNegative(funcName, "first", first) ||
-        !ValidateNonNegative(funcName, "vertCount", vertCount) ||
-        !ValidateNonNegative(funcName, "instanceCount", instanceCount))
-    {
-        return false;
-    }
-
-    if (!ValidateStencilParamsForDrawCall())
-        return false;
-
-    if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
-        MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
-        if (mPrimRestartTypeBytes != 0) {
-            mPrimRestartTypeBytes = 0;
-
-            // OSX appears to have severe perf issues with leaving this enabled.
-            gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
-        }
-    }
-
-    if (!vertCount || !instanceCount)
-        return false; // No error, just early out.
-
-    if (!ValidateBufferFetching(funcName))
-        return false;
-
-    const auto checked_firstPlusCount = CheckedInt<GLsizei>(first) + vertCount;
-    if (!checked_firstPlusCount.isValid()) {
-        ErrorInvalidOperation("%s: overflow in first+vertCount", funcName);
-        return false;
-    }
-
-    if (uint32_t(checked_firstPlusCount.value()) > mMaxFetchedVertices) {
-        ErrorInvalidOperation("%s: Bound vertex attribute buffers do not have sufficient"
-                              " size for given first and count.",
-                              funcName);
-        return false;
-    }
-
-    return true;
-}
-
-////////////////////////////////////////
-
 template<typename T>
 static bool
 DoSetsIntersect(const std::set<T>& a, const std::set<T>& b)
 {
     std::vector<T> intersection;
     std::set_intersection(a.begin(), a.end(), b.begin(), b.end(),
                           std::back_inserter(intersection));
     return bool(intersection.size());
 }
 
 class ScopedDrawHelper final
 {
     WebGLContext* const mWebGL;
     bool mDidFake;
 
 public:
-    ScopedDrawHelper(WebGLContext* webgl, const char* funcName, uint32_t firstVertex,
-                     uint32_t vertCount, uint32_t instanceCount, bool* const out_error)
+    ScopedDrawHelper(WebGLContext* const webgl, const char* const funcName,
+                     const GLenum mode, const Maybe<uint32_t>& lastRequiredVertex,
+                     const uint32_t instanceCount, bool* const out_error)
         : mWebGL(webgl)
         , mDidFake(false)
     {
-        if (instanceCount > mWebGL->mMaxFetchedInstances) {
-            mWebGL->ErrorInvalidOperation("%s: Bound instance attribute buffers do not"
-                                          " have sufficient size for given"
-                                          " `instanceCount`.",
-                                          funcName);
+        MOZ_ASSERT(mWebGL->gl->IsCurrent());
+
+        if (!mWebGL->ValidateDrawModeEnum(mode, funcName)) {
             *out_error = true;
             return;
         }
 
-        MOZ_ASSERT(mWebGL->gl->IsCurrent());
+        if (!mWebGL->ValidateStencilParamsForDrawCall()) {
+            *out_error = true;
+            return;
+        }
+
+        ////
 
         if (mWebGL->mBoundDrawFramebuffer) {
             if (!mWebGL->mBoundDrawFramebuffer->ValidateAndInitAttachments(funcName)) {
                 *out_error = true;
                 return;
             }
         } else {
             mWebGL->ClearBackbufferIfNeeded();
         }
 
         ////
-
-        const size_t requiredVerts = firstVertex + vertCount;
-        if (!mWebGL->DoFakeVertexAttrib0(funcName, requiredVerts)) {
-            *out_error = true;
-            return;
-        }
-        mDidFake = true;
-
-        ////
         // Check UBO sizes.
 
         const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
 
         for (const auto& cur : linkInfo->uniformBlocks) {
             const auto& dataSize = cur->mDataSize;
             const auto& binding = cur->mBinding;
             if (!binding) {
@@ -414,49 +337,49 @@ public:
                     *out_error = true;
                     return;
                 }
             }
         }
 
         ////
 
-        for (const auto& progAttrib : linkInfo->attribs) {
-            const auto& loc = progAttrib.mLoc;
-            if (loc == -1)
-                continue;
-
-            const auto& attribData = mWebGL->mBoundVertexArray->mAttribs[loc];
-
-            GLenum attribDataBaseType;
-            if (attribData.mEnabled) {
-                attribDataBaseType = attribData.BaseType();
+        const auto& fetchLimits = linkInfo->GetDrawFetchLimits(funcName);
+        if (!fetchLimits) {
+            *out_error = true;
+            return;
+        }
 
-                if (attribData.mBuf->IsBoundForTF()) {
-                    mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u's buffer is bound"
-                                                  " or in use for transform feedback.",
-                                                  funcName, loc);
-                    *out_error = true;
-                    return;
-                }
-            } else {
-                attribDataBaseType = mWebGL->mGenericVertexAttribTypes[loc];
-            }
-
-            if (attribDataBaseType != progAttrib.mBaseType) {
-                nsCString progType, dataType;
-                WebGLContext::EnumName(progAttrib.mBaseType, &progType);
-                WebGLContext::EnumName(attribDataBaseType, &dataType);
-                mWebGL->ErrorInvalidOperation("%s: Vertex attrib %u requires data of type"
-                                              " %s, but is being supplied with type %s.",
-                                              funcName, loc, progType.BeginReading(),
-                                              dataType.BeginReading());
+        if (lastRequiredVertex && instanceCount) {
+            if (lastRequiredVertex.value() >= fetchLimits->maxVerts) {
+                mWebGL->ErrorInvalidOperation("%s: Vertex fetch requires vertex #%u, but"
+                                              " attribs only supply %" PRIu64 ".",
+                                              funcName, lastRequiredVertex.value(),
+                                              fetchLimits->maxVerts);
                 *out_error = true;
                 return;
             }
+            if (instanceCount > fetchLimits->maxInstances) {
+                mWebGL->ErrorInvalidOperation("%s: Instance fetch requires %u, but"
+                                              " attribs only supply %" PRIu64 ".",
+                                              funcName, instanceCount,
+                                              fetchLimits->maxInstances);
+                *out_error = true;
+                return;
+            }
+        }
+
+        ////
+
+        if (lastRequiredVertex) {
+            if (!mWebGL->DoFakeVertexAttrib0(funcName, lastRequiredVertex.value())) {
+                *out_error = true;
+                return;
+            }
+            mDidFake = true;
         }
 
         ////
 
         mWebGL->RunContextLossTimer();
     }
 
     ~ScopedDrawHelper() {
@@ -543,48 +466,87 @@ public:
             return;
 
         mTFO->mActive_VertPosition += mUsedVerts;
     }
 };
 
 ////////////////////////////////////////
 
+bool
+WebGLContext::DrawArrays_check(const char* const funcName, const GLint first,
+                               const GLsizei vertCount, const GLsizei instanceCount,
+                               Maybe<uint32_t>* const out_lastVert)
+{
+    if (!ValidateNonNegative(funcName, "first", first) ||
+        !ValidateNonNegative(funcName, "vertCount", vertCount) ||
+        !ValidateNonNegative(funcName, "instanceCount", instanceCount))
+    {
+        return false;
+    }
+
+    if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
+        MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
+        if (mPrimRestartTypeBytes != 0) {
+            mPrimRestartTypeBytes = 0;
+
+            // OSX appears to have severe perf issues with leaving this enabled.
+            gl->fDisable(LOCAL_GL_PRIMITIVE_RESTART);
+        }
+    }
+
+    if (!vertCount) {
+        *out_lastVert = Nothing();
+    } else {
+        const auto lastVert_checked = CheckedInt<uint32_t>(first) + vertCount - 1;
+        if (!lastVert_checked.isValid()) {
+            ErrorOutOfMemory("%s: `first+vertCount` out of range.", funcName);
+            return false;
+        }
+        *out_lastVert = Some(lastVert_checked.value());
+    }
+    return true;
+}
+
 void
 WebGLContext::DrawArrays(GLenum mode, GLint first, GLsizei vertCount)
 {
     AUTO_PROFILER_LABEL("WebGLContext::DrawArrays", GRAPHICS);
     const char funcName[] = "drawArrays";
     if (IsContextLost())
         return;
 
     MakeContextCurrent();
 
     bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     const GLsizei instanceCount = 1;
-    if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
+    Maybe<uint32_t> lastVert;
+    if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert))
         return;
 
-    const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
+    const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
+                                        &error);
     if (error)
         return;
 
     const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
                                                    instanceCount, &error);
     if (error)
         return;
 
     {
         ScopedDrawCallWrapper wrapper(*this);
-        AUTO_PROFILER_LABEL("glDrawArrays", GRAPHICS);
-        gl->fDrawArrays(mode, first, vertCount);
+        if (vertCount) {
+            AUTO_PROFILER_LABEL("glDrawArrays", GRAPHICS);
+            gl->fDrawArrays(mode, first, vertCount);
+        }
     }
 
     Draw_cleanup(funcName);
     scopedTF.Advance();
 }
 
 void
 WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
@@ -597,164 +559,130 @@ WebGLContext::DrawArraysInstanced(GLenum
 
     MakeContextCurrent();
 
     bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    if (!DrawArrays_check(funcName, mode, first, vertCount, instanceCount))
+    Maybe<uint32_t> lastVert;
+    if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert))
         return;
 
-    if (!DrawInstanced_check(funcName))
-        return;
-
-    const ScopedDrawHelper scopedHelper(this, funcName, first, vertCount, instanceCount, &error);
+    const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
+                                        &error);
     if (error)
         return;
 
     const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
                                                    instanceCount, &error);
     if (error)
         return;
 
     {
         ScopedDrawCallWrapper wrapper(*this);
-        AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS);
-        gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
+        if (vertCount && instanceCount) {
+            AUTO_PROFILER_LABEL("glDrawArraysInstanced", GRAPHICS);
+            gl->fDrawArraysInstanced(mode, first, vertCount, instanceCount);
+        }
     }
 
     Draw_cleanup(funcName);
     scopedTF.Advance();
 }
 
 ////////////////////////////////////////
 
 bool
-WebGLContext::DrawElements_check(const char* funcName, GLenum mode, GLsizei vertCount,
-                                 GLenum type, WebGLintptr byteOffset,
-                                 GLsizei instanceCount)
+WebGLContext::DrawElements_check(const char* const funcName, const GLsizei rawIndexCount,
+                                 const GLenum type, const WebGLintptr byteOffset,
+                                 const GLsizei instanceCount,
+                                 Maybe<uint32_t>* const out_lastVert)
 {
-    if (!ValidateDrawModeEnum(mode, funcName))
-        return false;
-
     if (mBoundTransformFeedback &&
         mBoundTransformFeedback->mIsActive &&
         !mBoundTransformFeedback->mIsPaused)
     {
         ErrorInvalidOperation("%s: DrawElements* functions are incompatible with"
                               " transform feedback.",
                               funcName);
         return false;
     }
 
-    if (!ValidateNonNegative(funcName, "vertCount", vertCount) ||
+    if (!ValidateNonNegative(funcName, "vertCount", rawIndexCount) ||
         !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
         !ValidateNonNegative(funcName, "instanceCount", instanceCount))
     {
         return false;
     }
-
-    if (!ValidateStencilParamsForDrawCall())
-        return false;
+    const auto indexCount = uint32_t(rawIndexCount);
 
-    if (!vertCount || !instanceCount)
-        return false; // No error, just early out.
-
-    uint8_t bytesPerElem = 0;
+    uint8_t bytesPerIndex = 0;
     switch (type) {
     case LOCAL_GL_UNSIGNED_BYTE:
-        bytesPerElem = 1;
+        bytesPerIndex = 1;
         break;
 
     case LOCAL_GL_UNSIGNED_SHORT:
-        bytesPerElem = 2;
+        bytesPerIndex = 2;
         break;
 
     case LOCAL_GL_UNSIGNED_INT:
         if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
-            bytesPerElem = 4;
+            bytesPerIndex = 4;
         }
         break;
     }
-
-    if (!bytesPerElem) {
+    if (!bytesPerIndex) {
         ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
         return false;
     }
-
-    if (byteOffset % bytesPerElem != 0) {
+    if (byteOffset % bytesPerIndex != 0) {
         ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
                               funcName);
         return false;
     }
 
     ////
 
     if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
         MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
-        if (mPrimRestartTypeBytes != bytesPerElem) {
-            mPrimRestartTypeBytes = bytesPerElem;
+        if (mPrimRestartTypeBytes != bytesPerIndex) {
+            mPrimRestartTypeBytes = bytesPerIndex;
 
             const uint32_t ones = UINT32_MAX >> (32 - 8*mPrimRestartTypeBytes);
             gl->fEnable(LOCAL_GL_PRIMITIVE_RESTART);
             gl->fPrimitiveRestartIndex(ones);
         }
     }
 
     ////
-
-    const GLsizei first = byteOffset / bytesPerElem;
-    const CheckedUint32 checked_byteCount = bytesPerElem * CheckedUint32(vertCount);
+    // Index fetching
 
-    if (!checked_byteCount.isValid()) {
-        ErrorInvalidValue("%s: Overflow in byteCount.", funcName);
-        return false;
+    if (!indexCount || !instanceCount) {
+        *out_lastVert = Nothing();
+        return true;
     }
 
-    if (!mBoundVertexArray->mElementArrayBuffer) {
-        ErrorInvalidOperation("%s: Must have element array buffer binding.", funcName);
-        return false;
-    }
+    const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
 
-    WebGLBuffer& elemArrayBuffer = *mBoundVertexArray->mElementArrayBuffer;
-
-    if (!elemArrayBuffer.ByteLength()) {
-        ErrorInvalidOperation("%s: Bound element array buffer doesn't have any data.",
-                              funcName);
+    size_t availBytes = 0;
+    if (indexBuffer) {
+        MOZ_ASSERT(!indexBuffer->IsBoundForTF(), "This should be impossible.");
+        availBytes = indexBuffer->ByteLength();
+    }
+    const auto availIndices = AvailGroups(availBytes, byteOffset, bytesPerIndex,
+                                          bytesPerIndex);
+    if (indexCount > availIndices) {
+        ErrorInvalidOperation("%s: Index buffer too small.", funcName);
         return false;
     }
 
-    CheckedInt<GLsizei> checked_neededByteCount = checked_byteCount.toChecked<GLsizei>() + byteOffset;
-
-    if (!checked_neededByteCount.isValid()) {
-        ErrorInvalidOperation("%s: Overflow in byteOffset+byteCount.", funcName);
-        return false;
-    }
-
-    if (uint32_t(checked_neededByteCount.value()) > elemArrayBuffer.ByteLength()) {
-        ErrorInvalidOperation("%s: Bound element array buffer is too small for given"
-                              " count and offset.",
-                              funcName);
-        return false;
-    }
-
-    if (!ValidateBufferFetching(funcName))
-        return false;
-
-    if (!mMaxFetchedVertices ||
-        !elemArrayBuffer.ValidateIndexedFetch(type, mMaxFetchedVertices, first, vertCount))
-    {
-        ErrorInvalidOperation("%s: bound vertex attribute buffers do not have sufficient "
-                              "size for given indices from the bound element array",
-                              funcName);
-        return false;
-    }
-
+    *out_lastVert = indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
     return true;
 }
 
 static void
 HandleDrawElementsErrors(WebGLContext* webgl, const char* funcName,
                          gl::GLContext::LocalErrorScope& errorScope)
 {
     const auto err = errorScope.GetError();
@@ -769,105 +697,113 @@ HandleDrawElementsErrors(WebGLContext* w
         webgl->ErrorImplementationBug("%s: Unexpected driver error during indexed draw"
                                       " call. Please file a bug.",
                                       funcName);
         return;
     }
 }
 
 void
-WebGLContext::DrawElements(GLenum mode, GLsizei vertCount, GLenum type,
+WebGLContext::DrawElements(GLenum mode, GLsizei indexCount, GLenum type,
                            WebGLintptr byteOffset, const char* funcName)
 {
     AUTO_PROFILER_LABEL("WebGLContext::DrawElements", GRAPHICS);
     if (!funcName) {
         funcName = "drawElements";
     }
-
     if (IsContextLost())
         return;
 
     MakeContextCurrent();
 
     bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     const GLsizei instanceCount = 1;
-    if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
+    Maybe<uint32_t> lastVert;
+    if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount,
+                            &lastVert))
+    {
         return;
+    }
 
-    const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
+    const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
                                         &error);
     if (error)
         return;
 
     {
         ScopedDrawCallWrapper wrapper(*this);
         {
             UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
 
             if (gl->IsANGLE()) {
                 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
             }
 
-            AUTO_PROFILER_LABEL("glDrawElements", GRAPHICS);
-            gl->fDrawElements(mode, vertCount, type,
-                              reinterpret_cast<GLvoid*>(byteOffset));
+            if (lastVert) {
+                AUTO_PROFILER_LABEL("glDrawElements", GRAPHICS);
+                gl->fDrawElements(mode, indexCount, type,
+                                  reinterpret_cast<GLvoid*>(byteOffset));
+            }
 
             if (errorScope) {
                 HandleDrawElementsErrors(this, funcName, *errorScope);
             }
         }
     }
 
     Draw_cleanup(funcName);
 }
 
 void
-WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei vertCount, GLenum type,
+WebGLContext::DrawElementsInstanced(GLenum mode, GLsizei indexCount, GLenum type,
                                     WebGLintptr byteOffset, GLsizei instanceCount)
 {
     AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
     const char funcName[] = "drawElementsInstanced";
     if (IsContextLost())
         return;
 
     MakeContextCurrent();
 
     bool error = false;
     ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
-    if (!DrawElements_check(funcName, mode, vertCount, type, byteOffset, instanceCount))
+    Maybe<uint32_t> lastVert;
+    if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount,
+                            &lastVert))
+    {
         return;
+    }
 
-    if (!DrawInstanced_check(funcName))
-        return;
-
-    const ScopedDrawHelper scopedHelper(this, funcName, 0, mMaxFetchedVertices, instanceCount,
+    const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
                                         &error);
     if (error)
         return;
 
     {
         ScopedDrawCallWrapper wrapper(*this);
         {
             UniquePtr<gl::GLContext::LocalErrorScope> errorScope;
 
             if (gl->IsANGLE()) {
                 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
             }
 
-            AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
-            gl->fDrawElementsInstanced(mode, vertCount, type,
-                                       reinterpret_cast<GLvoid*>(byteOffset),
-                                       instanceCount);
+            if (lastVert && instanceCount) {
+                AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
+                gl->fDrawElementsInstanced(mode, indexCount, type,
+                                           reinterpret_cast<GLvoid*>(byteOffset),
+                                           instanceCount);
+            }
 
             if (errorScope) {
                 HandleDrawElementsErrors(this, funcName, *errorScope);
             }
         }
     }
 
     Draw_cleanup(funcName);
@@ -914,156 +850,47 @@ WebGLContext::Draw_cleanup(const char* f
             GenerateWarning("%s: Drawing to a destination rect smaller than the viewport"
                             " rect. (This warning will only be given once)",
                             funcName);
             mAlreadyWarnedAboutViewportLargerThanDest = true;
         }
     }
 }
 
-/*
- * Verify that state is consistent for drawing, and compute max number of elements (maxAllowedCount)
- * that will be legal to be read from bound VBOs.
- */
-
-bool
-WebGLContext::ValidateBufferFetching(const char* info)
-{
-    MOZ_ASSERT(mCurrentProgram);
-    // Note that mCurrentProgram->IsLinked() is NOT GUARANTEED.
-    MOZ_ASSERT(mActiveProgramLinkInfo);
-
-#ifdef DEBUG
-    GLint currentProgram = 0;
-    MakeContextCurrent();
-    gl->fGetIntegerv(LOCAL_GL_CURRENT_PROGRAM, &currentProgram);
-    MOZ_ASSERT(GLuint(currentProgram) == mCurrentProgram->mGLName,
-               "WebGL: current program doesn't agree with GL state");
-#endif
-
-    if (mBufferFetchingIsVerified)
-        return true;
-
-    bool hasPerVertex = false;
-    uint32_t maxVertices = UINT32_MAX;
-    uint32_t maxInstances = UINT32_MAX;
-    const uint32_t attribCount = mBoundVertexArray->mAttribs.Length();
-
-    uint32_t i = 0;
-    for (const auto& vd : mBoundVertexArray->mAttribs) {
-        // If the attrib array isn't enabled, there's nothing to check;
-        // it's a static value.
-        if (!vd.mEnabled)
-            continue;
-
-        if (!vd.mBuf) {
-            ErrorInvalidOperation("%s: no VBO bound to enabled vertex attrib index %du!",
-                                  info, i);
-            return false;
-        }
-
-        ++i;
-    }
-
-    mBufferFetch_IsAttrib0Active = false;
-
-    for (const auto& attrib : mActiveProgramLinkInfo->attribs) {
-        if (attrib.mLoc == -1)
-            continue;
-
-        const uint32_t attribLoc(attrib.mLoc);
-        if (attribLoc >= attribCount)
-            continue;
-
-        if (attribLoc == 0) {
-            mBufferFetch_IsAttrib0Active = true;
-        }
-
-        const auto& vd = mBoundVertexArray->mAttribs[attribLoc];
-        if (!vd.mEnabled)
-            continue;
-
-        const auto& bufByteLen = vd.mBuf->ByteLength();
-        if (vd.ByteOffset() > bufByteLen) {
-            maxVertices = 0;
-            maxInstances = 0;
-            break;
-        }
-
-        size_t availBytes = bufByteLen - vd.ByteOffset();
-        if (vd.BytesPerVertex() > availBytes) {
-            maxVertices = 0;
-            maxInstances = 0;
-            break;
-        }
-        availBytes -= vd.BytesPerVertex(); // Snip off the tail.
-        const size_t vertCapacity = availBytes / vd.ExplicitStride() + 1; // Add +1 for the snipped tail.
-
-        if (vd.mDivisor == 0) {
-            if (vertCapacity < maxVertices) {
-                maxVertices = vertCapacity;
-            }
-            hasPerVertex = true;
-        } else {
-            const auto curMaxInstances = CheckedInt<size_t>(vertCapacity) * vd.mDivisor;
-            // If this isn't valid, it's because we overflowed, which means we can support
-            // *too much*. Don't update maxInstances in this case.
-            if (curMaxInstances.isValid() &&
-                curMaxInstances.value() < maxInstances)
-            {
-                maxInstances = curMaxInstances.value();
-            }
-        }
-    }
-
-    mBufferFetchingIsVerified = true;
-    mBufferFetchingHasPerVertex = hasPerVertex;
-    mMaxFetchedVertices = maxVertices;
-    mMaxFetchedInstances = maxInstances;
-
-    return true;
-}
-
 WebGLVertexAttrib0Status
 WebGLContext::WhatDoesVertexAttrib0Need() const
 {
     MOZ_ASSERT(mCurrentProgram);
     MOZ_ASSERT(mActiveProgramLinkInfo);
 
-    const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
-
     bool legacyAttrib0 = gl->IsCompatibilityProfile();
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs()) {
         // Failures in conformance/attribs/gl-disabled-vertex-attrib.
         // Even in Core profiles on NV. Sigh.
         legacyAttrib0 |= (gl->Vendor() == gl::GLVendor::NVIDIA);
     }
 #endif
 
     if (!legacyAttrib0)
         return WebGLVertexAttrib0Status::Default;
 
-    if (isAttribArray0Enabled && mBufferFetch_IsAttrib0Active)
-        return WebGLVertexAttrib0Status::Default;
+    if (!mActiveProgramLinkInfo->attrib0Active) {
+        // Ensure that the legacy code has enough buffer.
+        return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
+    }
 
-    if (mBufferFetch_IsAttrib0Active)
-        return WebGLVertexAttrib0Status::EmulatedInitializedArray;
-
-    // Ensure that the legacy code has enough buffer.
-    return WebGLVertexAttrib0Status::EmulatedUninitializedArray;
+    const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
+    return isAttribArray0Enabled ? WebGLVertexAttrib0Status::Default
+                                 : WebGLVertexAttrib0Status::EmulatedInitializedArray;
 }
 
 bool
-WebGLContext::DoFakeVertexAttrib0(const char* funcName, GLuint vertexCount)
+WebGLContext::DoFakeVertexAttrib0(const char* const funcName, const uint32_t lastVert)
 {
-    if (!vertexCount) {
-        vertexCount = 1;
-    }
-
     const auto whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
     if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
         return true;
 
     if (!mAlreadyWarnedAboutFakeVertexAttrib0) {
         GenerateWarning("Drawing without vertex attrib 0 array enabled forces the browser "
                         "to do expensive emulation work when running on desktop OpenGL "
                         "platforms, for example on Mac. It is preferable to always draw "
@@ -1097,20 +924,22 @@ WebGLContext::DoFakeVertexAttrib0(const 
 
     default:
         MOZ_CRASH();
     }
 
     ////
 
     const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
-    const auto checked_dataSize = CheckedUint32(vertexCount) * bytesPerVert;
+    const auto checked_dataSize = (CheckedUint32(lastVert)+1) * bytesPerVert;
     if (!checked_dataSize.isValid()) {
-        ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0 array for a draw-operation "
-                         "with %d vertices. Try reducing the number of vertices.", vertexCount);
+        ErrorOutOfMemory("Integer overflow trying to construct a fake vertex attrib 0"
+                         " array for a draw-operation with %" PRIu64 " vertices. Try"
+                         " reducing the number of vertices.",
+                         uint64_t(lastVert) + 1);
         return false;
     }
     const auto dataSize = checked_dataSize.value();
 
     if (mFakeVertexAttrib0BufferObjectSize < dataSize) {
         gl->fBufferData(LOCAL_GL_ARRAY_BUFFER, dataSize, nullptr, LOCAL_GL_DYNAMIC_DRAW);
         mFakeVertexAttrib0BufferObjectSize = dataSize;
         mFakeVertexAttrib0DataDefined = false;
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -748,16 +748,17 @@ WebGLContext::InitAndValidateGL(FailureR
     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);
+    mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     static const float kDefaultGenericVertexAttribData[4] = { 0, 0, 0, 1 };
     memcpy(mGenericVertexAttrib0Data, kDefaultGenericVertexAttribData,
            sizeof(mGenericVertexAttrib0Data));
 
     mFakeVertexAttrib0BufferObject = 0;
 
     mNeedsIndexValidation = !gl->IsSupported(gl::GLFeature::robust_buffer_access_behavior);
--- a/dom/canvas/WebGLContextVertexArray.cpp
+++ b/dom/canvas/WebGLContextVertexArray.cpp
@@ -16,18 +16,16 @@ void
 WebGLContext::BindVertexArray(WebGLVertexArray* array)
 {
     if (IsContextLost())
         return;
 
     if (array && !ValidateObject("bindVertexArrayObject", *array))
         return;
 
-    InvalidateBufferFetching();
-
     MakeContextCurrent();
 
     if (mBoundVertexArray) {
         mBoundVertexArray->AddBufferBindCounts(-1);
     }
 
     if (array == nullptr) {
         array = mDefaultVertexArray;
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -77,16 +77,17 @@ WebGLContext::VertexAttrib4f(GLuint inde
     gl->MakeCurrent();
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fVertexAttrib4f(index, x, y, z, w);
     }
 
     ////
 
     mGenericVertexAttribTypes[index] = LOCAL_GL_FLOAT;
+    mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     if (!index) {
         const float data[4] = { x, y, z, w };
         memcpy(mGenericVertexAttrib0Data, data, sizeof(data));
     }
 }
 
 void
@@ -108,16 +109,17 @@ WebGL2Context::VertexAttribI4i(GLuint in
     gl->MakeCurrent();
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fVertexAttribI4i(index, x, y, z, w);
     }
 
     ////
 
     mGenericVertexAttribTypes[index] = LOCAL_GL_INT;
+    mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     if (!index) {
         const int32_t data[4] = { x, y, z, w };
         memcpy(mGenericVertexAttrib0Data, data, sizeof(data));
     }
 }
 
 void
@@ -139,16 +141,17 @@ WebGL2Context::VertexAttribI4ui(GLuint i
     gl->MakeCurrent();
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fVertexAttribI4ui(index, x, y, z, w);
     }
 
     ////
 
     mGenericVertexAttribTypes[index] = LOCAL_GL_UNSIGNED_INT;
+    mGenericVertexAttribTypeInvalidator.InvalidateCaches();
 
     if (!index) {
         const uint32_t data[4] = { x, y, z, w };
         memcpy(mGenericVertexAttrib0Data, data, sizeof(data));
     }
 }
 
 ////////////////////////////////////////
@@ -158,42 +161,42 @@ WebGLContext::EnableVertexAttribArray(GL
 {
     if (IsContextLost())
         return;
 
     if (!ValidateAttribIndex(index, "enableVertexAttribArray"))
         return;
 
     MakeContextCurrent();
-    InvalidateBufferFetching();
 
     gl->fEnableVertexAttribArray(index);
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->mAttribs[index].mEnabled = true;
+    mBoundVertexArray->InvalidateCaches();
 }
 
 void
 WebGLContext::DisableVertexAttribArray(GLuint index)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateAttribIndex(index, "disableVertexAttribArray"))
         return;
 
     MakeContextCurrent();
-    InvalidateBufferFetching();
 
     if (index || !gl->IsCompatibilityProfile()) {
         gl->fDisableVertexAttribArray(index);
     }
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->mAttribs[index].mEnabled = false;
+    mBoundVertexArray->InvalidateCaches();
 }
 
 JS::Value
 WebGLContext::GetVertexAttrib(JSContext* cx, GLuint index, GLenum pname,
                               ErrorResult& rv)
 {
     const char funcName[] = "getVertexAttrib";
     if (IsContextLost())
@@ -420,36 +423,32 @@ WebGLContext::VertexAttribAnyPointer(con
                                   reinterpret_cast<void*>(byteOffset));
     } else {
         gl->fVertexAttribPointer(index, size, type, normalized, stride,
                                  reinterpret_cast<void*>(byteOffset));
     }
 
     WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
     vd.VertexAttribPointer(isFuncInt, buffer, size, type, normalized, stride, byteOffset);
-
-    InvalidateBufferFetching();
+    mBoundVertexArray->InvalidateCaches();
 }
 
 ////////////////////////////////////////
 
 void
 WebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateAttribIndex(index, "vertexAttribDivisor"))
         return;
 
     MOZ_ASSERT(mBoundVertexArray);
-
-    WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
-    vd.mDivisor = divisor;
-
-    InvalidateBufferFetching();
+    mBoundVertexArray->mAttribs[index].mDivisor = divisor;
+    mBoundVertexArray->InvalidateCaches();
 
     MakeContextCurrent();
 
     gl->fVertexAttribDivisor(index, divisor);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLProgram.cpp
+++ b/dom/canvas/WebGLProgram.cpp
@@ -7,21 +7,23 @@
 
 #include "GLContext.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "mozilla/RefPtr.h"
 #include "nsPrintfCString.h"
 #include "WebGLActiveInfo.h"
+#include "WebGLBuffer.h"
 #include "WebGLContext.h"
 #include "WebGLShader.h"
 #include "WebGLTransformFeedback.h"
 #include "WebGLUniformLocation.h"
 #include "WebGLValidateStrings.h"
+#include "WebGLVertexArray.h"
 
 namespace mozilla {
 
 /* If `name`: "foo[3]"
  * Then returns true, with
  *     `out_baseName`: "foo"
  *     `out_isArray`: true
  *     `out_index`: 3
@@ -259,16 +261,20 @@ 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};
         info->attribs.push_back(attrib);
+
+        if (loc == 0) {
+            info->attrib0Active = true;
+        }
     }
 
     // Uniforms (can be basically anything)
 
     const bool needsCheckForArrays = gl->WorkAroundDriverBugs();
 
     GLuint numActiveUniforms = 0;
     gl->fGetProgramiv(prog->mGLName, LOCAL_GL_ACTIVE_UNIFORMS,
@@ -438,28 +444,124 @@ QueryProgramInfo(WebGLProgram* prog, gl:
     return info.forget();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 
 webgl::LinkedProgramInfo::LinkedProgramInfo(WebGLProgram* prog)
     : prog(prog)
     , transformFeedbackBufferMode(prog->mNextLink_TransformFeedbackBufferMode)
+    , attrib0Active(false)
 { }
 
 webgl::LinkedProgramInfo::~LinkedProgramInfo()
 {
     for (auto& cur : uniforms) {
         delete cur;
     }
     for (auto& cur : uniformBlocks) {
         delete cur;
     }
 }
 
+const webgl::CachedDrawFetchLimits*
+webgl::LinkedProgramInfo::GetDrawFetchLimits(const char* const funcName) 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;
+    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) {
+            if (cur.mEnabled && !cur.mBuf) {
+                webgl->ErrorInvalidOperation("%s: Vertex attrib array %u is enabled but"
+                                             " has no buffer bound.",
+                                             funcName, i);
+                return nullptr;
+            }
+        }
+    }
+
+    bool hasActiveAttrib = false;
+    bool hasActiveDivisor0 = false;
+    webgl::CachedDrawFetchLimits fetchLimits = { UINT64_MAX, UINT64_MAX };
+
+    for (const auto& progAttrib : this->attribs) {
+        const auto& loc = progAttrib.mLoc;
+        if (loc == -1)
+            continue;
+        hasActiveAttrib |= true;
+
+        const auto& attribData = vao->mAttribs[loc];
+        hasActiveDivisor0 |= (attribData.mDivisor == 0);
+
+        GLenum attribDataBaseType;
+        if (attribData.mEnabled) {
+            MOZ_ASSERT(attribData.mBuf);
+            if (attribData.mBuf->IsBoundForTF()) {
+                webgl->ErrorInvalidOperation("%s: Vertex attrib %u's buffer is bound for"
+                                             " transform feedback.",
+                                              funcName, loc);
+                return nullptr;
+            }
+            cacheDeps.push_back(&attribData.mBuf->mFetchInvalidator);
+
+            attribDataBaseType = attribData.BaseType();
+
+            const size_t availBytes = attribData.mBuf->ByteLength();
+            const auto availElems = AvailGroups(availBytes, attribData.ByteOffset(),
+                                                attribData.BytesPerVertex(),
+                                                attribData.ExplicitStride());
+            if (attribData.mDivisor) {
+                const auto availInstances = CheckedInt<uint64_t>(availElems) * attribData.mDivisor;
+                if (availInstances.isValid()) {
+                    fetchLimits.maxInstances = std::min(fetchLimits.maxInstances,
+                                                        availInstances.value());
+                } // 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);
+            webgl->ErrorInvalidOperation("%s: Vertex attrib %u requires data of type %s,"
+                                         " but is being supplied with type %s.",
+                                         funcName, loc, progType.BeginReading(),
+                                         dataType.BeginReading());
+            return nullptr;
+        }
+    }
+
+    if (hasActiveAttrib && !hasActiveDivisor0) {
+        webgl->ErrorInvalidOperation("%s: One active vertex attrib (if any are active)"
+                                     " must have a divisor of 0.",
+                                     funcName);
+        return nullptr;
+    }
+
+    // --
+
+    return mDrawFetchCache.Insert(vao.get(), Move(fetchLimits), Move(cacheDeps));
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // WebGLProgram
 
 static GLuint
 CreateProgram(gl::GLContext* gl)
 {
     gl->MakeCurrent();
     return gl->fCreateProgram();
@@ -1064,17 +1166,16 @@ WebGLProgram::LinkProgram()
     if (mNumActiveTFOs) {
         mContext->ErrorInvalidOperation("%s: Program is in-use by one or more active"
                                         " transform feedback objects.",
                                         funcName);
         return;
     }
 
     mContext->MakeContextCurrent();
-    mContext->InvalidateBufferFetching(); // we do it early in this function
     // as some of the validation changes program state
 
     mLinkLog.Truncate();
     mMostRecentLinkInfo = nullptr;
 
     if (!ValidateForLink()) {
         mContext->GenerateWarning("%s: %s", funcName, mLinkLog.BeginReading());
         return;
@@ -1360,18 +1461,16 @@ WebGLProgram::UseProgram() const
     {
         mContext->ErrorInvalidOperation("%s: Transform feedback active and not paused.",
                                         funcName);
         return false;
     }
 
     mContext->MakeContextCurrent();
 
-    mContext->InvalidateBufferFetching();
-
     mContext->gl->fUseProgram(mGLName);
     return true;
 }
 
 void
 WebGLProgram::ValidateProgram() const
 {
     mContext->MakeContextCurrent();
--- a/dom/canvas/WebGLProgram.h
+++ b/dom/canvas/WebGLProgram.h
@@ -12,16 +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 "WebGLContext.h"
 #include "WebGLObjectModel.h"
 
 namespace mozilla {
 class ErrorResult;
 class WebGLActiveInfo;
 class WebGLProgram;
 class WebGLShader;
@@ -70,16 +71,21 @@ struct UniformBlockInfo final
                      const nsACString& mappedName, uint32_t dataSize)
         : mUserName(userName)
         , mMappedName(mappedName)
         , mDataSize(dataSize)
         , mBinding(&webgl->mIndexedUniformBufferBindings[0])
     { }
 };
 
+struct CachedDrawFetchLimits final {
+    uint64_t maxVerts;
+    uint64_t maxInstances;
+};
+
 struct LinkedProgramInfo final
     : public RefCounted<LinkedProgramInfo>
     , public SupportsWeakPtr<LinkedProgramInfo>
 {
     friend class WebGLProgram;
 
     MOZ_DECLARE_REFCOUNTED_TYPENAME(LinkedProgramInfo)
     MOZ_DECLARE_WEAKREFERENCE_TYPENAME(LinkedProgramInfo)
@@ -94,21 +100,32 @@ struct LinkedProgramInfo final
     std::vector<UniformBlockInfo*> uniformBlocks; // Owns its contents.
     std::vector<RefPtr<WebGLActiveInfo>> transformFeedbackVaryings;
 
     // Needed for draw call validation.
     std::vector<UniformInfo*> uniformSamplers;
 
     mutable std::vector<size_t> componentsPerTFVert;
 
+    bool attrib0Active;
+
     //////
 
     // The maps for the frag data names to the translated names.
     std::map<nsCString, const nsCString> fragDataMap;
 
+    //////
+
+    mutable CacheMap<const WebGLVertexArray*,
+                     CachedDrawFetchLimits> mDrawFetchCache;
+
+    const CachedDrawFetchLimits* GetDrawFetchLimits(const char* funcName) const;
+
+    //////
+
     explicit LinkedProgramInfo(WebGLProgram* prog);
     ~LinkedProgramInfo();
 
     bool FindAttrib(const nsCString& userName, const AttribInfo** const out_info) const;
     bool FindUniform(const nsCString& userName, nsCString* const out_mappedName,
                      size_t* const out_arrayIndex, UniformInfo** const out_info) const;
     bool MapFragDataName(const nsCString& userName,
                          nsCString* const out_mappedName) const;
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -5,28 +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 "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:
     static WebGLVertexArray* Create(WebGLContext* webgl);
 
     void BindVertexArray() {
         // Bind to dummy value to signal that this vertex array has ever been
         // bound.
         BindVertexArrayImpl();
@@ -61,13 +66,14 @@ protected:
     GLuint mGLName;
     nsTArray<WebGLVertexAttribData> mAttribs;
     WebGLRefPtr<WebGLBuffer> mElementArrayBuffer;
 
     friend class ScopedDrawHelper;
     friend class WebGLContext;
     friend class WebGLVertexArrayFake;
     friend class WebGL2Context;
+    friend struct webgl::LinkedProgramInfo;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_VERTEX_ARRAY_H_