Bug 1476327 - Test global upper bound for index buffer maxVertId. r=kvark
authorJeff Gilbert <jgilbert@mozilla.com>
Mon, 30 Jul 2018 15:33:10 +0000
changeset 429227 985e470b78d88f0edd223118cfcbb9b8b5670b1a
parent 429226 896812b8a15b806696e6d3b5982c365f754e4502
child 429228 76b7549059a4aaa3ce26587586be3e1ed3bbdbd1
push id67086
push userjgilbert@mozilla.com
push dateMon, 30 Jul 2018 19:48:19 +0000
treeherderautoland@985e470b78d8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskvark
bugs1476327
milestone63.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 1476327 - Test global upper bound for index buffer maxVertId. r=kvark Particularly in CAD applications, it's common to call drawElements many times on small ranges of indices. This causes our naive maxVertId range cache to degenerate. In most cases, the index buffer won't actually contain any indices outside the associated VAO buffers' ranges. We should test first against this global upper-bound, only testing for an exact maxVertId for the subrange if the upper-bound test fails. Differential Revision: https://phabricator.services.mozilla.com/D2488
dom/canvas/WebGLContext.h
dom/canvas/WebGLContextDraw.cpp
dom/canvas/WebGLTransformFeedback.h
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -95,16 +95,17 @@ class VRLayerChild;
 } // namespace gfx
 
 namespace gl {
 class MozFramebuffer;
 } // namespace gl
 
 namespace webgl {
 class AvailabilityRunnable;
+struct CachedDrawFetchLimits;
 struct LinkedProgramInfo;
 class ShaderValidator;
 class TexUnpackBlob;
 struct UniformInfo;
 struct UniformBlockInfo;
 } // namespace webgl
 
 WebGLTexelFormat GetWebGLTexelFormat(TexInternalFormat format);
@@ -275,18 +276,18 @@ public:
 
 class WebGLContext
     : public nsICanvasRenderingContextInternal
     , public nsSupportsWeakReference
     , public WebGLContextUnchecked
     , public nsWrapperCache
 {
     friend class ScopedDrawCallWrapper;
-    friend class ScopedDrawHelper;
     friend class ScopedDrawWithTransformFeedback;
+    friend class ScopedFakeVertexAttrib0;
     friend class ScopedFBRebinder;
     friend class WebGL2Context;
     friend class WebGLContextUserData;
     friend class WebGLExtensionCompressedTextureASTC;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTextureES3;
     friend class WebGLExtensionCompressedTextureETC1;
     friend class WebGLExtensionCompressedTexturePVRTC;
@@ -297,16 +298,19 @@ class WebGLContext
     friend class WebGLExtensionDrawBuffers;
     friend class WebGLExtensionLoseContext;
     friend class WebGLExtensionVertexArray;
     friend class WebGLMemoryTracker;
     friend class webgl::AvailabilityRunnable;
     friend struct webgl::LinkedProgramInfo;
     friend struct webgl::UniformBlockInfo;
 
+    friend const webgl::CachedDrawFetchLimits*
+        ValidateDraw(WebGLContext*, const char*, GLenum, uint32_t);
+
     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,
         CONTEXT_LOST_WEBGL = 0x9242,
         UNPACK_COLORSPACE_CONVERSION_WEBGL = 0x9243,
@@ -1406,21 +1410,18 @@ public:
         const bool isFuncInt = false;
         VertexAttribAnyPointer(funcName, isFuncInt, index, size, type, normalized, stride,
                                byteOffset);
     }
 
     void VertexAttribDivisor(GLuint index, GLuint divisor);
 
 private:
-    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);
+    WebGLBuffer* DrawElements_check(const char* funcName, GLsizei indexCount, GLenum type,
+                                    WebGLintptr byteOffset, GLsizei instanceCount);
     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);
@@ -1428,17 +1429,17 @@ private:
                               const GLfloat* ptr);
 
     bool BindArrayAttribToLocation0(WebGLProgram* prog);
 
 // -----------------------------------------------------------------------------
 // PROTECTED
 protected:
     WebGLVertexAttrib0Status WhatDoesVertexAttrib0Need() const;
-    bool DoFakeVertexAttrib0(const char* funcName, GLuint vertexCount);
+    bool DoFakeVertexAttrib0(const char* funcName, uint64_t vertexCount);
     void UndoFakeVertexAttrib0();
 
     CheckedUint32 mGeneration;
 
     WebGLContextOptions mOptions;
 
     bool mInvalidated;
     bool mCapturedFrameInvalidated;
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -281,161 +281,142 @@ 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
+const webgl::CachedDrawFetchLimits*
+ValidateDraw(WebGLContext* const webgl, const char* const funcName, const GLenum mode,
+             const uint32_t instanceCount)
+{
+    MOZ_ASSERT(webgl->gl->IsCurrent());
+
+    if (!webgl->BindCurFBForDraw(funcName))
+        return nullptr;
+
+    if (!webgl->ValidateDrawModeEnum(mode, funcName))
+        return nullptr;
+
+    if (!webgl->ValidateStencilParamsForDrawCall(funcName))
+        return nullptr;
+
+    if (!webgl->mActiveProgramLinkInfo) {
+        webgl->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
+        return nullptr;
+    }
+    const auto& linkInfo = webgl->mActiveProgramLinkInfo;
+
+    // -
+    // Check UBO sizes.
+
+    for (const auto& cur : linkInfo->uniformBlocks) {
+        const auto& dataSize = cur->mDataSize;
+        const auto& binding = cur->mBinding;
+        if (!binding) {
+            webgl->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
+                                          funcName);
+            return nullptr;
+        }
+
+        const auto availByteCount = binding->ByteCount();
+        if (dataSize > availByteCount) {
+            webgl->ErrorInvalidOperation("%s: Buffer for uniform block is smaller"
+                                         " than UNIFORM_BLOCK_DATA_SIZE.",
+                                         funcName);
+            return nullptr;
+        }
+
+        if (binding->mBufferBinding->IsBoundForTF()) {
+            webgl->ErrorInvalidOperation("%s: Buffer for uniform block is bound or"
+                                         " in use for transform feedback.",
+                                         funcName);
+            return nullptr;
+        }
+    }
+
+    // -
+
+    const auto& tfo = webgl->mBoundTransformFeedback;
+    if (tfo && tfo->IsActiveAndNotPaused()) {
+        uint32_t numUsed;
+        switch (linkInfo->transformFeedbackBufferMode) {
+        case LOCAL_GL_INTERLEAVED_ATTRIBS:
+            numUsed = 1;
+            break;
+
+        case LOCAL_GL_SEPARATE_ATTRIBS:
+            numUsed = linkInfo->transformFeedbackVaryings.size();
+            break;
+
+        default:
+            MOZ_CRASH();
+        }
+
+        for (uint32_t i = 0; i < numUsed; ++i) {
+            const auto& buffer = tfo->mIndexedBindings[i].mBufferBinding;
+            if (buffer->IsBoundForNonTF()) {
+                webgl->ErrorInvalidOperation("%s: Transform feedback varying %u's buffer"
+                                             " is bound for non-transform-feedback.",
+                                             funcName, i);
+                return nullptr;
+            }
+
+            // Technically we don't know that this will be updated yet, but we can
+            // speculatively mark it.
+            buffer->ResetLastUpdateFenceId();
+        }
+    }
+
+    // -
+
+    const auto fetchLimits = linkInfo->GetDrawFetchLimits(funcName);
+    if (!fetchLimits)
+        return nullptr;
+
+    if (instanceCount > fetchLimits->maxInstances) {
+        webgl->ErrorInvalidOperation("%s: Instance fetch requires %u, but attribs only"
+                                     " supply %u.",
+                                     funcName, instanceCount,
+                                     uint32_t(fetchLimits->maxInstances));
+        return nullptr;
+    }
+
+    // -
+
+    webgl->RunContextLossTimer();
+
+    return fetchLimits;
+}
+
+////////////////////////////////////////
+
+class ScopedFakeVertexAttrib0 final
 {
     WebGLContext* const mWebGL;
-    bool mDidFake;
+    bool mDidFake = false;
 
 public:
-    ScopedDrawHelper(WebGLContext* const webgl, const char* const funcName,
-                     const GLenum mode, const Maybe<uint32_t>& lastRequiredVertex,
-                     const uint32_t instanceCount, bool* const out_error)
+    ScopedFakeVertexAttrib0(WebGLContext* const webgl, const char* const funcName,
+                            const uint64_t vertexCount, bool* const out_error)
         : mWebGL(webgl)
-        , mDidFake(false)
     {
-        MOZ_ASSERT(mWebGL->gl->IsCurrent());
-
-        if (!mWebGL->BindCurFBForDraw(funcName)) {
-            *out_error = true;
-            return;
-        }
+        *out_error = false;
 
-        if (!mWebGL->ValidateDrawModeEnum(mode, funcName)) {
-            *out_error = true;
-            return;
-        }
-
-        if (!mWebGL->ValidateStencilParamsForDrawCall(funcName)) {
-            *out_error = true;
-            return;
-        }
-
-        if (!mWebGL->mActiveProgramLinkInfo) {
-            mWebGL->ErrorInvalidOperation("%s: The current program is not linked.", funcName);
+        if (!mWebGL->DoFakeVertexAttrib0(funcName, vertexCount)) {
             *out_error = true;
             return;
         }
-        const auto& linkInfo = mWebGL->mActiveProgramLinkInfo;
-
-        ////
-        // Check UBO sizes.
-
-        for (const auto& cur : linkInfo->uniformBlocks) {
-            const auto& dataSize = cur->mDataSize;
-            const auto& binding = cur->mBinding;
-            if (!binding) {
-                mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is null.",
-                                              funcName);
-                *out_error = true;
-                return;
-            }
-
-            const auto availByteCount = binding->ByteCount();
-            if (dataSize > availByteCount) {
-                mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is smaller"
-                                              " than UNIFORM_BLOCK_DATA_SIZE.",
-                                              funcName);
-                *out_error = true;
-                return;
-            }
-
-            if (binding->mBufferBinding->IsBoundForTF()) {
-                mWebGL->ErrorInvalidOperation("%s: Buffer for uniform block is bound or"
-                                              " in use for transform feedback.",
-                                              funcName);
-                *out_error = true;
-                return;
-            }
-        }
-
-        ////
-
-        const auto& tfo = mWebGL->mBoundTransformFeedback;
-        if (tfo && tfo->IsActiveAndNotPaused()) {
-            uint32_t numUsed;
-            switch (linkInfo->transformFeedbackBufferMode) {
-            case LOCAL_GL_INTERLEAVED_ATTRIBS:
-                numUsed = 1;
-                break;
-
-            case LOCAL_GL_SEPARATE_ATTRIBS:
-                numUsed = linkInfo->transformFeedbackVaryings.size();
-                break;
-
-            default:
-                MOZ_CRASH();
-            }
-
-            for (uint32_t i = 0; i < numUsed; ++i) {
-                const auto& buffer = tfo->mIndexedBindings[i].mBufferBinding;
-                if (buffer->IsBoundForNonTF()) {
-                    mWebGL->ErrorInvalidOperation("%s: Transform feedback varying %u's"
-                                                  " buffer is bound for"
-                                                  " non-transform-feedback.",
-                                                  funcName, i);
-                    *out_error = true;
-                    return;
-                }
-
-                // Technically we don't know that this will be updated yet, but we can
-                // speculatively mark it.
-                buffer->ResetLastUpdateFenceId();
-            }
-        }
-
-        ////
-
-        const auto& fetchLimits = linkInfo->GetDrawFetchLimits(funcName);
-        if (!fetchLimits) {
-            *out_error = true;
-            return;
-        }
-
-        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();
+        mDidFake = true;
     }
 
-    ~ScopedDrawHelper() {
+    ~ScopedFakeVertexAttrib0()
+    {
         if (mDidFake) {
             mWebGL->UndoFakeVertexAttrib0();
         }
     }
 };
 
 ////////////////////////////////////////
 
@@ -521,68 +502,71 @@ static bool
 HasInstancedDrawing(const WebGLContext& webgl)
 {
     return webgl.IsWebGL2() ||
            webgl.IsExtensionEnabled(WebGLExtensionID::ANGLE_instanced_arrays);
 }
 
 ////////////////////////////////////////
 
-bool
-WebGLContext::DrawArrays_check(const char* const funcName, const GLint first,
-                               const GLsizei vertCount, const GLsizei instanceCount,
-                               Maybe<uint32_t>* const out_lastVert)
+void
+WebGLContext::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
+                                  GLsizei instanceCount, const char* const funcName)
 {
+    AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
+    if (IsContextLost())
+        return;
+    const gl::GLContext::TlsScope inTls(gl);
+
+    // -
+
     if (!ValidateNonNegative(funcName, "first", first) ||
         !ValidateNonNegative(funcName, "vertCount", vertCount) ||
         !ValidateNonNegative(funcName, "instanceCount", instanceCount))
     {
-        return false;
+        return;
     }
 
     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::DrawArraysInstanced(GLenum mode, GLint first, GLsizei vertCount,
-                                  GLsizei instanceCount, const char* const funcName)
-{
-    AUTO_PROFILER_LABEL("WebGLContext::DrawArraysInstanced", GRAPHICS);
-    if (IsContextLost())
+    const auto fetchLimits = ValidateDraw(this, funcName, mode, instanceCount);
+    if (!fetchLimits)
         return;
 
-    const gl::GLContext::TlsScope inTls(gl);
+    // -
+
+    const auto totalVertCount_safe = CheckedInt<uint32_t>(first) + vertCount;
+    if (!totalVertCount_safe.isValid()) {
+        ErrorOutOfMemory("%s: `first+vertCount` out of range.", funcName);
+        return;
+    }
+    auto totalVertCount = totalVertCount_safe.value();
 
-    Maybe<uint32_t> lastVert;
-    if (!DrawArrays_check(funcName, first, vertCount, instanceCount, &lastVert))
+    if (vertCount && instanceCount &&
+        totalVertCount > fetchLimits->maxVerts)
+    {
+        ErrorInvalidOperation("%s: Vertex fetch requires %u, but attribs only supply %u.",
+                              funcName, totalVertCount, uint32_t(fetchLimits->maxVerts));
         return;
+    }
+
+    // -
 
     bool error = false;
-    const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
-                                        &error);
+    const ScopedFakeVertexAttrib0 attrib0(this, funcName, totalVertCount, &error);
     if (error)
         return;
 
     const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     const ScopedDrawWithTransformFeedback scopedTF(this, funcName, mode, vertCount,
@@ -604,37 +588,36 @@ WebGLContext::DrawArraysInstanced(GLenum
     }
 
     Draw_cleanup(funcName);
     scopedTF.Advance();
 }
 
 ////////////////////////////////////////
 
-bool
+WebGLBuffer*
 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)
+                                 const GLsizei instanceCount)
 {
     if (mBoundTransformFeedback &&
         mBoundTransformFeedback->mIsActive &&
         !mBoundTransformFeedback->mIsPaused)
     {
         ErrorInvalidOperation("%s: DrawElements* functions are incompatible with"
                               " transform feedback.",
                               funcName);
-        return false;
+        return nullptr;
     }
 
     if (!ValidateNonNegative(funcName, "vertCount", rawIndexCount) ||
         !ValidateNonNegative(funcName, "byteOffset", byteOffset) ||
         !ValidateNonNegative(funcName, "instanceCount", instanceCount))
     {
-        return false;
+        return nullptr;
     }
     const auto indexCount = uint32_t(rawIndexCount);
 
     uint8_t bytesPerIndex = 0;
     switch (type) {
     case LOCAL_GL_UNSIGNED_BYTE:
         bytesPerIndex = 1;
         break;
@@ -646,22 +629,22 @@ WebGLContext::DrawElements_check(const c
     case LOCAL_GL_UNSIGNED_INT:
         if (IsWebGL2() || IsExtensionEnabled(WebGLExtensionID::OES_element_index_uint)) {
             bytesPerIndex = 4;
         }
         break;
     }
     if (!bytesPerIndex) {
         ErrorInvalidEnum("%s: Invalid `type`: 0x%04x", funcName, type);
-        return false;
+        return nullptr;
     }
     if (byteOffset % bytesPerIndex != 0) {
         ErrorInvalidOperation("%s: `byteOffset` must be a multiple of the size of `type`",
                               funcName);
-        return false;
+        return nullptr;
     }
 
     ////
 
     if (IsWebGL2() && !gl->IsSupported(gl::GLFeature::prim_restart_fixed)) {
         MOZ_ASSERT(gl->IsSupported(gl::GLFeature::prim_restart));
         if (mPrimRestartTypeBytes != bytesPerIndex) {
             mPrimRestartTypeBytes = bytesPerIndex;
@@ -673,35 +656,29 @@ WebGLContext::DrawElements_check(const c
     }
 
     ////
     // Index fetching
 
     const auto& indexBuffer = mBoundVertexArray->mElementArrayBuffer;
     if (!indexBuffer) {
         ErrorInvalidOperation("%s: Index buffer not bound.", funcName);
-        return false;
+        return nullptr;
     }
     MOZ_ASSERT(!indexBuffer->IsBoundForTF(), "This should be impossible.");
 
-    if (!indexCount || !instanceCount) {
-        *out_lastVert = Nothing();
-        return true;
-    }
-
     const size_t availBytes = indexBuffer->ByteLength();
     const auto availIndices = AvailGroups(availBytes, byteOffset, bytesPerIndex,
                                           bytesPerIndex);
-    if (indexCount > availIndices) {
+    if (instanceCount && indexCount > availIndices) {
         ErrorInvalidOperation("%s: Index buffer too small.", funcName);
-        return false;
+        return nullptr;
     }
 
-    *out_lastVert = indexBuffer->GetIndexedFetchMaxVert(type, byteOffset, indexCount);
-    return true;
+    return indexBuffer.get();
 }
 
 static void
 HandleDrawElementsErrors(WebGLContext* webgl, const char* funcName,
                          gl::GLContext::LocalErrorScope& errorScope)
 {
     const auto err = errorScope.GetError();
     if (err == LOCAL_GL_INVALID_OPERATION) {
@@ -725,26 +702,80 @@ WebGLContext::DrawElementsInstanced(GLen
                                     const char* const funcName)
 {
     AUTO_PROFILER_LABEL("WebGLContext::DrawElementsInstanced", GRAPHICS);
     if (IsContextLost())
         return;
 
     const gl::GLContext::TlsScope inTls(gl);
 
-    Maybe<uint32_t> lastVert;
-    if (!DrawElements_check(funcName, indexCount, type, byteOffset, instanceCount,
-                            &lastVert))
-    {
+    const auto indexBuffer = DrawElements_check(funcName, indexCount, type, byteOffset,
+                                                instanceCount);
+    if (!indexBuffer)
         return;
+
+    // -
+
+    const auto fetchLimits = ValidateDraw(this, funcName, mode, instanceCount);
+    if (!fetchLimits)
+        return;
+
+    bool collapseToDrawArrays = false;
+    auto fakeVertCount = fetchLimits->maxVerts;
+    if (fetchLimits->maxVerts == UINT64_MAX) {
+        // This isn't observable, and keeps FakeVertexAttrib0 sane.
+        collapseToDrawArrays = true;
+        fakeVertCount = 1;
     }
 
+    // -
+
+    {
+        uint64_t indexCapacity = indexBuffer->ByteLength();
+        switch (type) {
+        case LOCAL_GL_UNSIGNED_BYTE:
+            break;
+        case LOCAL_GL_UNSIGNED_SHORT:
+            indexCapacity /= 2;
+            break;
+        case LOCAL_GL_UNSIGNED_INT:
+            indexCapacity /= 4;
+            break;
+        }
+
+        uint32_t maxVertId = 0;
+        const auto isFetchValid = [&]() {
+            if (!indexCount || !instanceCount)
+                return true;
+
+            const auto globalMaxVertId = indexBuffer->GetIndexedFetchMaxVert(type, 0,
+                                                                             indexCapacity);
+            if (!globalMaxVertId)
+                return true;
+            if (globalMaxVertId.value() < fetchLimits->maxVerts)
+                return true;
+
+            const auto exactMaxVertId = indexBuffer->GetIndexedFetchMaxVert(type,
+                                                                            byteOffset,
+                                                                            indexCount);
+            maxVertId = exactMaxVertId.value();
+            return maxVertId < fetchLimits->maxVerts;
+        }();
+        if (!isFetchValid) {
+            ErrorInvalidOperation("%s: Indexed vertex fetch requires %u vertices, but"
+                                  " attribs only supply %u.",
+                                  funcName, maxVertId+1, uint32_t(fetchLimits->maxVerts));
+            return;
+        }
+    }
+
+    // -
+
     bool error = false;
-    const ScopedDrawHelper scopedHelper(this, funcName, mode, lastVert, instanceCount,
-                                        &error);
+    const ScopedFakeVertexAttrib0 attrib0(this, funcName, fakeVertCount, &error);
     if (error)
         return;
 
     const ScopedResolveTexturesForDraw scopedResolve(this, funcName, &error);
     if (error)
         return;
 
     {
@@ -757,23 +788,31 @@ WebGLContext::DrawElementsInstanced(GLen
                 // ANGLE does range validation even when it doesn't need to.
                 // With MOZ_GL_ABORT_ON_ERROR, we need to catch it or hit assertions.
                 errorScope.reset(new gl::GLContext::LocalErrorScope(*gl));
             }
 
             if (indexCount && instanceCount) {
                 AUTO_PROFILER_LABEL("glDrawElementsInstanced", GRAPHICS);
                 if (HasInstancedDrawing(*this)) {
-                    gl->fDrawElementsInstanced(mode, indexCount, type,
-                                               reinterpret_cast<GLvoid*>(byteOffset),
-                                               instanceCount);
+                    if (MOZ_UNLIKELY(collapseToDrawArrays)) {
+                        gl->fDrawArraysInstanced(mode, 0, 1, instanceCount);
+                    } else {
+                        gl->fDrawElementsInstanced(mode, indexCount, type,
+                                                   reinterpret_cast<GLvoid*>(byteOffset),
+                                                   instanceCount);
+                    }
                 } else {
                     MOZ_ASSERT(instanceCount == 1);
-                    gl->fDrawElements(mode, indexCount, type,
-                                      reinterpret_cast<GLvoid*>(byteOffset));
+                    if (MOZ_UNLIKELY(collapseToDrawArrays)) {
+                        gl->fDrawArrays(mode, 0, 1);
+                    } else {
+                        gl->fDrawElements(mode, indexCount, type,
+                                          reinterpret_cast<GLvoid*>(byteOffset));
+                    }
                 }
             }
 
             if (errorScope) {
                 HandleDrawElementsErrors(this, funcName, *errorScope);
             }
         }
     }
@@ -851,17 +890,17 @@ WebGLContext::WhatDoesVertexAttrib0Need(
     }
 
     const auto& isAttribArray0Enabled = mBoundVertexArray->mAttribs[0].mEnabled;
     return isAttribArray0Enabled ? WebGLVertexAttrib0Status::Default
                                  : WebGLVertexAttrib0Status::EmulatedInitializedArray;
 }
 
 bool
-WebGLContext::DoFakeVertexAttrib0(const char* const funcName, const uint32_t lastVert)
+WebGLContext::DoFakeVertexAttrib0(const char* const funcName, const uint64_t vertexCount)
 {
     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 "
@@ -896,22 +935,22 @@ WebGLContext::DoFakeVertexAttrib0(const 
 
     default:
         MOZ_CRASH();
     }
 
     ////
 
     const auto bytesPerVert = sizeof(mFakeVertexAttrib0Data);
-    const auto checked_dataSize = (CheckedUint32(lastVert)+1) * bytesPerVert;
+    const auto checked_dataSize = CheckedUint32(vertexCount) * bytesPerVert;
     if (!checked_dataSize.isValid()) {
         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);
+                         vertexCount);
         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/WebGLTransformFeedback.h
+++ b/dom/canvas/WebGLTransformFeedback.h
@@ -6,28 +6,33 @@
 #ifndef WEBGL_TRANSFORM_FEEDBACK_H_
 #define WEBGL_TRANSFORM_FEEDBACK_H_
 
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 #include "WebGLObjectModel.h"
 
 namespace mozilla {
+namespace webgl {
+struct CachedDrawFetchLimits;
+}
 
 class WebGLTransformFeedback final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLTransformFeedback>
     , public LinkedListElement<WebGLTransformFeedback>
 {
-    friend class ScopedDrawHelper;
     friend class ScopedDrawWithTransformFeedback;
     friend class WebGLContext;
     friend class WebGL2Context;
     friend class WebGLProgram;
 
+    friend const webgl::CachedDrawFetchLimits*
+        ValidateDraw(WebGLContext*, const char*, GLenum, uint32_t);
+
 public:
     const GLuint mGLName;
 private:
     // GLES 3.0.4 p267, Table 6.24 "Transform Feedback State"
     // It's not yet in the ES3 spec, but the generic TF buffer bind point has been moved
     // to context state, instead of TFO state.
     std::vector<IndexedBufferBinding> mIndexedBindings;
     bool mIsPaused;