Context: Cache VAO element limits.

Cache the minimum value for non instanced and instanced active
attributes. The cache is updated in the following places:

1. Context: bindVertexArray.
2. Context: any executable change (linkProgram/useProgram/programBinary).
3. Vertex Array: any state change call.
4. Buffer: a dependent buffer resize.

This greatly reduces the time we're spending in ValidateDrawAttribs.

Bug: angleproject:1391
Change-Id: I84bb222a1b9736e6165fe40e972cd4299ca1178d
Reviewed-on: https://chromium-review.googlesource.com/1150516
Commit-Queue: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Geoff Lang <geofflang@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index 578ee3d..8a02896 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -1108,6 +1108,7 @@
     mGLState.setVertexArrayBinding(this, vertexArray);
     mVertexArrayObserverBinding.bind(vertexArray);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::bindVertexBuffer(GLuint bindingIndex,
@@ -1144,6 +1145,7 @@
 {
     mGLState.setProgram(this, getProgram(program));
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::useProgramStages(GLuint pipeline, GLbitfield stages, GLuint program)
@@ -2798,6 +2800,7 @@
 {
     mGLState.setVertexAttribDivisor(this, index, divisor);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::samplerParameteri(GLuint sampler, GLenum pname, GLint param)
@@ -4405,6 +4408,7 @@
 {
     mGLState.setEnableVertexAttribArray(index, false);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::enable(GLenum cap)
@@ -4416,6 +4420,7 @@
 {
     mGLState.setEnableVertexAttribArray(index, true);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::frontFace(GLenum mode)
@@ -4624,6 +4629,7 @@
     mGLState.setVertexAttribPointer(this, index, mGLState.getTargetBuffer(BufferBinding::Array),
                                     size, type, ConvertToBool(normalized), false, stride, ptr);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::vertexAttribFormat(GLuint attribIndex,
@@ -4634,6 +4640,7 @@
 {
     mGLState.setVertexAttribFormat(attribIndex, size, type, ConvertToBool(normalized), false,
                                    relativeOffset);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::vertexAttribIFormat(GLuint attribIndex,
@@ -4642,17 +4649,20 @@
                                   GLuint relativeOffset)
 {
     mGLState.setVertexAttribFormat(attribIndex, size, type, false, true, relativeOffset);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::vertexAttribBinding(GLuint attribIndex, GLuint bindingIndex)
 {
     mGLState.setVertexAttribBinding(this, attribIndex, bindingIndex);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::vertexBindingDivisor(GLuint bindingIndex, GLuint divisor)
 {
     mGLState.setVertexBindingDivisor(bindingIndex, divisor);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::viewport(GLint x, GLint y, GLsizei width, GLsizei height)
@@ -4669,6 +4679,7 @@
     mGLState.setVertexAttribPointer(this, index, mGLState.getTargetBuffer(BufferBinding::Array),
                                     size, type, false, true, stride, pointer);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::vertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w)
@@ -5555,6 +5566,7 @@
     handleError(programObject->link(this));
     mGLState.onProgramExecutableChange(programObject);
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::releaseShaderCompiler()
@@ -5763,6 +5775,7 @@
 
     handleError(programObject->loadBinary(this, binaryFormat, binary, length));
     mStateCache.updateActiveAttribsMask(this);
+    mStateCache.updateVertexElementLimits(this);
 }
 
 void Context::uniform1ui(GLint location, GLuint v0)
@@ -7567,6 +7580,7 @@
     {
         case kVertexArraySubjectIndex:
             mGLState.setObjectDirty(GL_VERTEX_ARRAY);
+            mStateCache.updateVertexElementLimits(this);
             break;
 
         case kReadFramebufferSubjectIndex:
@@ -7629,7 +7643,10 @@
 }
 
 // StateCache implementation.
-StateCache::StateCache() : mCachedHasAnyEnabledClientAttrib(false)
+StateCache::StateCache()
+    : mCachedHasAnyEnabledClientAttrib(false),
+      mCachedNonInstancedVertexElementLimit(0),
+      mCachedInstancedVertexElementLimit(0)
 {
 }
 
@@ -7662,4 +7679,44 @@
     mCachedActiveBufferedAttribsMask = activeAttribs & ~clientAttribs;
     mCachedHasAnyEnabledClientAttrib = (clientAttribs & enabledAttribs).any();
 }
+
+void StateCache::updateVertexElementLimits(Context *context)
+{
+    const VertexArray *vao = context->getGLState().getVertexArray();
+
+    mCachedNonInstancedVertexElementLimit = std::numeric_limits<GLint64>::max();
+    mCachedInstancedVertexElementLimit    = std::numeric_limits<GLint64>::max();
+
+    // VAO can be null on Context startup. If we make this computation lazier we could ASSERT.
+    // If there are no buffered attributes then we should not limit the draw call count.
+    if (!vao || !mCachedActiveBufferedAttribsMask.any())
+    {
+        return;
+    }
+
+    const auto &vertexAttribs  = vao->getVertexAttributes();
+    const auto &vertexBindings = vao->getVertexBindings();
+
+    for (size_t attributeIndex : mCachedActiveBufferedAttribsMask)
+    {
+        const VertexAttribute &attrib = vertexAttribs[attributeIndex];
+        ASSERT(attrib.enabled);
+
+        const VertexBinding &binding = vertexBindings[attrib.bindingIndex];
+        ASSERT(context->isGLES1() ||
+               context->getGLState().getProgram()->isAttribLocationActive(attributeIndex));
+
+        GLint64 limit = attrib.getCachedElementLimit();
+        if (binding.getDivisor() > 0)
+        {
+            mCachedInstancedVertexElementLimit =
+                std::min(mCachedInstancedVertexElementLimit, limit);
+        }
+        else
+        {
+            mCachedNonInstancedVertexElementLimit =
+                std::min(mCachedNonInstancedVertexElementLimit, limit);
+        }
+    }
+}
 }  // namespace gl
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index 28b068f..0db5870 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -98,13 +98,27 @@
     AttributesMask getActiveClientAttribsMask() const { return mCachedActiveClientAttribsMask; }
     bool hasAnyEnabledClientAttrib() const { return mCachedHasAnyEnabledClientAttrib; }
 
+    // Places that can trigger updateVertexElementLimits:
+    // 1. Context: bindVertexArray.
+    // 2. Context: any executable change (linkProgram/useProgram/programBinary).
+    // 3. Vertex Array: any state change call.
+    // 4. Buffer: a dependent buffer resize.
+    GLint64 getNonInstancedVertexElementLimit() const
+    {
+        return mCachedNonInstancedVertexElementLimit;
+    }
+    GLint64 getInstancedVertexElementLimit() const { return mCachedInstancedVertexElementLimit; }
+
     // Cache update functions.
     void updateActiveAttribsMask(Context *context);
+    void updateVertexElementLimits(Context *context);
 
   private:
     AttributesMask mCachedActiveBufferedAttribsMask;
     AttributesMask mCachedActiveClientAttribsMask;
     bool mCachedHasAnyEnabledClientAttrib;
+    GLint64 mCachedNonInstancedVertexElementLimit;
+    GLint64 mCachedInstancedVertexElementLimit;
 };
 
 class Context final : public egl::LabeledObject, angle::NonCopyable, public angle::ObserverInterface
diff --git a/src/libANGLE/VertexArray.cpp b/src/libANGLE/VertexArray.cpp
index 30fb447..8d933ac 100644
--- a/src/libANGLE/VertexArray.cpp
+++ b/src/libANGLE/VertexArray.cpp
@@ -410,11 +410,11 @@
             break;
 
         case angle::SubjectMessage::STORAGE_CHANGED:
-            setDependentDirtyBit(context, false, index);
             if (index < mArrayBufferObserverBindings.size())
             {
                 updateCachedBufferBindingSize(&mState.mVertexBindings[index]);
             }
+            setDependentDirtyBit(context, false, index);
             break;
 
         case angle::SubjectMessage::BINDING_CHANGED:
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index a014ac3..2650fbc 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -109,9 +109,6 @@
 
 bool ValidateDrawAttribs(Context *context, GLint primcount, GLint maxVertex, GLint vertexCount)
 {
-    const gl::State &state     = context->getGLState();
-    const gl::Program *program = state.getProgram();
-
     if (!ValidateDrawClientAttribs(context))
     {
         return false;
@@ -123,39 +120,24 @@
         return true;
     }
 
-    const VertexArray *vao     = state.getVertexArray();
-    const auto &vertexAttribs  = vao->getVertexAttributes();
-    const auto &vertexBindings = vao->getVertexBindings();
-
-    const AttributesMask &activeAttribs = context->getStateCache().getActiveBufferedAttribsMask();
-
-    for (size_t attributeIndex : activeAttribs)
+    if (maxVertex <= context->getStateCache().getNonInstancedVertexElementLimit() &&
+        (primcount - 1) <= context->getStateCache().getInstancedVertexElementLimit())
     {
-        const VertexAttribute &attrib = vertexAttribs[attributeIndex];
-        ASSERT(attrib.enabled);
-
-        const VertexBinding &binding = vertexBindings[attrib.bindingIndex];
-        ASSERT(context->isGLES1() || program->isAttribLocationActive(attributeIndex));
-
-        GLint maxVertexElement = binding.getDivisor() != 0 ? (primcount - 1) : maxVertex;
-
-        if (maxVertexElement > attrib.getCachedElementLimit())
-        {
-            // An overflow can happen when adding the offset. Negative indicates overflow.
-            if (attrib.getCachedElementLimit() < 0)
-            {
-                ANGLE_VALIDATION_ERR(context, InvalidOperation(), IntegerOverflow);
-                return false;
-            }
-
-            // [OpenGL ES 3.0.2] section 2.9.4 page 40:
-            // We can return INVALID_OPERATION if our buffer does not have enough backing data.
-            ANGLE_VALIDATION_ERR(context, InvalidOperation(), InsufficientVertexBufferSize);
-            return false;
-        }
+        return true;
     }
 
-    return true;
+    // An overflow can happen when adding the offset. Negative indicates overflow.
+    if (context->getStateCache().getNonInstancedVertexElementLimit() < 0 ||
+        context->getStateCache().getInstancedVertexElementLimit() < 0)
+    {
+        ANGLE_VALIDATION_ERR(context, InvalidOperation(), IntegerOverflow);
+        return false;
+    }
+
+    // [OpenGL ES 3.0.2] section 2.9.4 page 40:
+    // We can return INVALID_OPERATION if our buffer does not have enough backing data.
+    ANGLE_VALIDATION_ERR(context, InvalidOperation(), InsufficientVertexBufferSize);
+    return false;
 }
 
 bool ValidReadPixelsTypeEnum(Context *context, GLenum type)
diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
index d3cffeb..5c10fe2 100644
--- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp
+++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
@@ -1590,32 +1590,32 @@
     // Test touching the last element is valid.
     glVertexAttribPointer(wLocation, 1, GL_UNSIGNED_BYTE, GL_FALSE, 0, zeroOffset + 12);
     glDrawArraysInstancedANGLE(GL_POINTS, 0, 1, 4);
-    ASSERT_GL_NO_ERROR();
+    ASSERT_GL_NO_ERROR() << "touching the last element.";
 
     // Test touching the last element + 1 is invalid.
     glVertexAttribPointer(wLocation, 1, GL_UNSIGNED_BYTE, GL_FALSE, 0, zeroOffset + 13);
     glDrawArraysInstancedANGLE(GL_POINTS, 0, 1, 4);
-    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "touching the last element + 1.";
 
     // Test touching the last element is valid, using a stride.
     glVertexAttribPointer(wLocation, 1, GL_UNSIGNED_BYTE, GL_FALSE, 2, zeroOffset + 9);
     glDrawArraysInstancedANGLE(GL_POINTS, 0, 1, 4);
-    ASSERT_GL_NO_ERROR();
+    ASSERT_GL_NO_ERROR() << "touching the last element using a stride.";
 
     // Test touching the last element + 1 is invalid, using a stride.
     glVertexAttribPointer(wLocation, 1, GL_UNSIGNED_BYTE, GL_FALSE, 2, zeroOffset + 10);
     glDrawArraysInstancedANGLE(GL_POINTS, 0, 1, 4);
-    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "touching the last element + 1 using a stride.";
 
     // Test any offset is valid if no vertices are drawn.
     glVertexAttribPointer(wLocation, 1, GL_UNSIGNED_BYTE, GL_FALSE, 0, zeroOffset + 32);
     glDrawArraysInstancedANGLE(GL_POINTS, 0, 0, 1);
-    ASSERT_GL_NO_ERROR();
+    ASSERT_GL_NO_ERROR() << "any offset with no vertices.";
 
     // Test any offset is valid if no primitives are drawn.
     glVertexAttribPointer(wLocation, 1, GL_UNSIGNED_BYTE, GL_FALSE, 0, zeroOffset + 32);
     glDrawArraysInstancedANGLE(GL_POINTS, 0, 1, 0);
-    ASSERT_GL_NO_ERROR();
+    ASSERT_GL_NO_ERROR() << "any offset with primitives.";
 }
 
 // Test the checks for OOB reads in the index buffer