Cache common DrawElements states.

Similar to how we cache the base common draw states. This will improve
DrawElements performance. Several state checks are optimized into a
single 'if' check of a cached value.

Also includes a regression test for mapping the element array buffer.

Bug: angleproject:2966
Change-Id: Ia6e524a58ad6b7df2e455d67733e15d324b1b893
Reviewed-on: https://chromium-review.googlesource.com/c/1357150
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index b24823f..ec13562 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -8191,7 +8191,8 @@
     : mCachedHasAnyEnabledClientAttrib(false),
       mCachedNonInstancedVertexElementLimit(0),
       mCachedInstancedVertexElementLimit(0),
-      mCachedBasicDrawStatesError(kInvalidPointer)
+      mCachedBasicDrawStatesError(kInvalidPointer),
+      mCachedBasicDrawElementsError(kInvalidPointer)
 {}
 
 StateCache::~StateCache() = default;
@@ -8201,6 +8202,8 @@
     updateValidDrawModes(context);
     updateValidBindTextureTypes(context);
     updateValidDrawElementsTypes(context);
+    updateBasicDrawStatesError();
+    updateBasicDrawElementsError();
 }
 
 void StateCache::updateActiveAttribsMask(Context *context)
@@ -8277,6 +8280,11 @@
     mCachedBasicDrawStatesError = kInvalidPointer;
 }
 
+void StateCache::updateBasicDrawElementsError()
+{
+    mCachedBasicDrawElementsError = kInvalidPointer;
+}
+
 intptr_t StateCache::getBasicDrawStatesErrorImpl(Context *context) const
 {
     ASSERT(mCachedBasicDrawStatesError == kInvalidPointer);
@@ -8284,6 +8292,13 @@
     return mCachedBasicDrawStatesError;
 }
 
+intptr_t StateCache::getBasicDrawElementsErrorImpl(Context *context) const
+{
+    ASSERT(mCachedBasicDrawElementsError == kInvalidPointer);
+    mCachedBasicDrawElementsError = reinterpret_cast<intptr_t>(ValidateDrawElementsStates(context));
+    return mCachedBasicDrawElementsError;
+}
+
 void StateCache::onVertexArrayBindingChange(Context *context)
 {
     updateActiveAttribsMask(context);
@@ -8320,6 +8335,7 @@
 void StateCache::onVertexArrayBufferStateChange(Context *context)
 {
     updateBasicDrawStatesError();
+    updateBasicDrawElementsError();
 }
 
 void StateCache::onGLES1ClientStateChange(Context *context)
@@ -8360,6 +8376,7 @@
 void StateCache::onActiveTransformFeedbackChange(Context *context)
 {
     updateBasicDrawStatesError();
+    updateBasicDrawElementsError();
     updateValidDrawModes(context);
 }
 
@@ -8371,6 +8388,7 @@
 void StateCache::onBufferBindingChange(Context *context)
 {
     updateBasicDrawStatesError();
+    updateBasicDrawElementsError();
 }
 
 void StateCache::setValidDrawModes(bool pointsOK,
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index 3b50e39..b09b6f7 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -143,6 +143,20 @@
         return getBasicDrawStatesErrorImpl(context);
     }
 
+    // Places that can trigger updateBasicDrawElementsError:
+    // 1. onActiveTransformFeedbackChange.
+    // 2. onVertexArrayBufferStateChange.
+    // 3. onBufferBindingChange.
+    intptr_t getBasicDrawElementsError(Context *context) const
+    {
+        if (mCachedBasicDrawElementsError != kInvalidPointer)
+        {
+            return mCachedBasicDrawElementsError;
+        }
+
+        return getBasicDrawElementsErrorImpl(context);
+    }
+
     // Places that can trigger updateValidDrawModes:
     // 1. onProgramExecutableChange.
     // 2. onActiveTransformFeedbackChange.
@@ -185,14 +199,16 @@
     // Cache update functions.
     void updateActiveAttribsMask(Context *context);
     void updateVertexElementLimits(Context *context);
-    void updateBasicDrawStatesError();
     void updateValidDrawModes(Context *context);
     void updateValidBindTextureTypes(Context *context);
     void updateValidDrawElementsTypes(Context *context);
+    void updateBasicDrawStatesError();
+    void updateBasicDrawElementsError();
 
     void setValidDrawModes(bool pointsOK, bool linesOK, bool trisOK, bool lineAdjOK, bool triAdjOK);
 
     intptr_t getBasicDrawStatesErrorImpl(Context *context) const;
+    intptr_t getBasicDrawElementsErrorImpl(Context *context) const;
 
     static constexpr intptr_t kInvalidPointer = 1;
 
@@ -203,6 +219,7 @@
     GLint64 mCachedNonInstancedVertexElementLimit;
     GLint64 mCachedInstancedVertexElementLimit;
     mutable intptr_t mCachedBasicDrawStatesError;
+    mutable intptr_t mCachedBasicDrawElementsError;
 
     // Reserve an extra slot at the end of these maps for invalid enum.
     angle::PackedEnumMap<PrimitiveMode, bool, angle::EnumSize<PrimitiveMode>() + 1>
diff --git a/src/libANGLE/VertexArray.cpp b/src/libANGLE/VertexArray.cpp
index 57f6d44..9aefca4 100644
--- a/src/libANGLE/VertexArray.cpp
+++ b/src/libANGLE/VertexArray.cpp
@@ -438,8 +438,8 @@
             if (!IsElementArrayBufferSubjectIndex(index))
             {
                 updateCachedMappedArrayBuffers(&mState.mVertexBindings[index]);
-                onStateChange(context, angle::SubjectMessage::RESOURCE_MAPPED);
             }
+            onStateChange(context, angle::SubjectMessage::RESOURCE_MAPPED);
             break;
 
         case angle::SubjectMessage::RESOURCE_UNMAPPED:
@@ -448,8 +448,8 @@
             if (!IsElementArrayBufferSubjectIndex(index))
             {
                 updateCachedMappedArrayBuffers(&mState.mVertexBindings[index]);
-                onStateChange(context, angle::SubjectMessage::RESOURCE_UNMAPPED);
             }
+            onStateChange(context, angle::SubjectMessage::RESOURCE_UNMAPPED);
             break;
 
         default:
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
index 95653e5..9170c07 100644
--- a/src/libANGLE/validationES.cpp
+++ b/src/libANGLE/validationES.cpp
@@ -2926,7 +2926,21 @@
         return false;
     }
 
-    // TODO(jmadill): Cache all of these into fast checks. http://anglebug.com/2966
+    intptr_t drawElementsError = context->getStateCache().getBasicDrawElementsError(context);
+    if (drawElementsError)
+    {
+        // All errors from ValidateDrawElementsStates return INVALID_OPERATION.
+        const char *errorMessage = reinterpret_cast<const char *>(drawElementsError);
+        context->validationError(GL_INVALID_OPERATION, errorMessage);
+        return false;
+    }
+
+    // Note that we are missing overflow checks for active transform feedback buffers.
+    return true;
+}
+
+const char *ValidateDrawElementsStates(Context *context)
+{
     const State &state = context->getGLState();
 
     TransformFeedback *curTransformFeedback = state.getCurrentTransformFeedback();
@@ -2940,12 +2954,8 @@
             // It is an invalid operation to call DrawElements, DrawRangeElements or
             // DrawElementsInstanced while transform feedback is active, (3.0.2, section 2.14, pg
             // 86)
-            context->validationError(GL_INVALID_OPERATION,
-                                     kUnsupportedDrawModeForTransformFeedback);
-            return false;
+            return kUnsupportedDrawModeForTransformFeedback;
         }
-
-        // Note that we are missing overflow checks for the transform feedback buffers.
     }
 
     const VertexArray *vao     = state.getVertexArray();
@@ -2957,9 +2967,7 @@
         {
             if (elementArrayBuffer->isBoundForTransformFeedbackAndOtherUse())
             {
-                context->validationError(GL_INVALID_OPERATION,
-                                         kElementArrayBufferBoundForTransformFeedback);
-                return false;
+                return kElementArrayBufferBoundForTransformFeedback;
             }
         }
         else if (elementArrayBuffer->isMapped())
@@ -2967,8 +2975,7 @@
             // WebGL buffers cannot be mapped/unmapped because the MapBufferRange,
             // FlushMappedBufferRange, and UnmapBuffer entry points are removed from the
             // WebGL 2.0 API. https://www.khronos.org/registry/webgl/specs/latest/2.0/#5.14
-            context->validationError(GL_INVALID_OPERATION, kBufferMapped);
-            return false;
+            return kBufferMapped;
         }
     }
     else
@@ -2979,12 +2986,11 @@
         if (!context->getGLState().areClientArraysEnabled() ||
             context->getExtensions().webglCompatibility)
         {
-            context->validationError(GL_INVALID_OPERATION, kMustHaveElementArrayBinding);
-            return false;
+            return kMustHaveElementArrayBinding;
         }
     }
 
-    return true;
+    return nullptr;
 }
 
 bool ValidateDrawElementsCommon(Context *context,
diff --git a/src/libANGLE/validationES.h b/src/libANGLE/validationES.h
index c283b01..0cb403f 100644
--- a/src/libANGLE/validationES.h
+++ b/src/libANGLE/validationES.h
@@ -287,6 +287,7 @@
                                         Format *textureFormatOut);
 
 bool ValidateDrawMode(Context *context, PrimitiveMode mode);
+const char *ValidateDrawElementsStates(Context *context);
 
 ANGLE_INLINE bool ValidateDrawBase(Context *context, PrimitiveMode mode, GLsizei count)
 {
diff --git a/src/tests/gl_tests/StateChangeTest.cpp b/src/tests/gl_tests/StateChangeTest.cpp
index c7dd5ba..9a3e184 100644
--- a/src/tests/gl_tests/StateChangeTest.cpp
+++ b/src/tests/gl_tests/StateChangeTest.cpp
@@ -3038,6 +3038,47 @@
     glDrawArrays(GL_TRIANGLES, 0, 6);
     EXPECT_GL_ERROR(GL_INVALID_OPERATION);
 }
+
+// Tests that mapping the element array buffer triggers errors.
+TEST_P(ValidationStateChangeTest, MapElementArrayBuffer)
+{
+    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
+    glUseProgram(program);
+
+    std::array<GLushort, 6> quadIndices = GetQuadIndices();
+    std::array<Vector3, 4> quadVertices = GetIndexedQuadVertices();
+
+    GLsizei elementBufferSize = sizeof(quadIndices[0]) * quadIndices.size();
+
+    GLBuffer elementArrayBuffer;
+    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elementArrayBuffer);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, elementBufferSize, quadIndices.data(), GL_STATIC_DRAW);
+
+    GLBuffer arrayBuffer;
+    glBindBuffer(GL_ARRAY_BUFFER, arrayBuffer);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices[0]) * quadVertices.size(),
+                 quadVertices.data(), GL_STATIC_DRAW);
+
+    GLint positionLoc = glGetAttribLocation(program, essl3_shaders::PositionAttrib());
+    ASSERT_NE(-1, positionLoc);
+    glVertexAttribPointer(positionLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
+    glEnableVertexAttribArray(positionLoc);
+
+    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
+    ASSERT_GL_NO_ERROR();
+
+    void *ptr = glMapBufferRange(GL_ELEMENT_ARRAY_BUFFER, 0, elementBufferSize, GL_MAP_READ_BIT);
+    ASSERT_NE(nullptr, ptr);
+
+    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+
+    glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);
+
+    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
+    ASSERT_GL_NO_ERROR();
+    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
+}
 }  // anonymous namespace
 
 ANGLE_INSTANTIATE_TEST(StateChangeTest, ES2_D3D9(), ES2_D3D11(), ES2_OPENGL(), ES2_VULKAN());
diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp
index 876ebfe..fd310a0 100644
--- a/src/tests/test_utils/ANGLETest.cpp
+++ b/src/tests/test_utils/ANGLETest.cpp
@@ -42,7 +42,7 @@
     return static_cast<GLubyte>(colorValue * 255.0f);
 }
 
-void TestPlatform_logError(angle::PlatformMethods *platform, const char *errorMessage)
+void TestPlatform_logError(PlatformMethods *platform, const char *errorMessage)
 {
     auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
     if (testPlatformContext->ignoreMessages)
@@ -51,7 +51,7 @@
     FAIL() << errorMessage;
 }
 
-void TestPlatform_logWarning(angle::PlatformMethods *platform, const char *warningMessage)
+void TestPlatform_logWarning(PlatformMethods *platform, const char *warningMessage)
 {
     auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
     if (testPlatformContext->ignoreMessages)
@@ -60,17 +60,16 @@
     std::cerr << "Warning: " << warningMessage << std::endl;
 }
 
-void TestPlatform_logInfo(angle::PlatformMethods *platform, const char *infoMessage)
+void TestPlatform_logInfo(PlatformMethods *platform, const char *infoMessage)
 {
     auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
     if (testPlatformContext->ignoreMessages)
         return;
 
-    angle::WriteDebugMessage("%s\n", infoMessage);
+    WriteDebugMessage("%s\n", infoMessage);
 }
 
-void TestPlatform_overrideWorkaroundsD3D(angle::PlatformMethods *platform,
-                                         WorkaroundsD3D *workaroundsD3D)
+void TestPlatform_overrideWorkaroundsD3D(PlatformMethods *platform, WorkaroundsD3D *workaroundsD3D)
 {
     auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
     if (testPlatformContext->currentTest)
@@ -79,8 +78,7 @@
     }
 }
 
-void TestPlatform_overrideFeaturesVk(angle::PlatformMethods *platform,
-                                     FeaturesVk *workaroundsVulkan)
+void TestPlatform_overrideFeaturesVk(PlatformMethods *platform, FeaturesVk *workaroundsVulkan)
 {
     auto *testPlatformContext = static_cast<TestPlatformContext *>(platform->context);
     if (testPlatformContext->currentTest)
@@ -89,17 +87,23 @@
     }
 }
 
-std::array<angle::Vector3, 4> GetIndexedQuadVertices()
-{
-    std::array<angle::Vector3, 4> vertices;
-    vertices[0] = angle::Vector3(-1.0f, 1.0f, 0.5f);
-    vertices[1] = angle::Vector3(-1.0f, -1.0f, 0.5f);
-    vertices[2] = angle::Vector3(1.0f, -1.0f, 0.5f);
-    vertices[3] = angle::Vector3(1.0f, 1.0f, 0.5f);
-    return vertices;
-}
+const std::array<Vector3, 6> kQuadVertices = {{
+    Vector3(-1.0f, 1.0f, 0.5f),
+    Vector3(-1.0f, -1.0f, 0.5f),
+    Vector3(1.0f, -1.0f, 0.5f),
+    Vector3(-1.0f, 1.0f, 0.5f),
+    Vector3(1.0f, -1.0f, 0.5f),
+    Vector3(1.0f, 1.0f, 0.5f),
+}};
 
-static constexpr std::array<GLushort, 6> IndexedQuadIndices = {{0, 1, 2, 0, 2, 3}};
+const std::array<Vector3, 4> kIndexedQuadVertices = {{
+    Vector3(-1.0f, 1.0f, 0.5f),
+    Vector3(-1.0f, -1.0f, 0.5f),
+    Vector3(1.0f, -1.0f, 0.5f),
+    Vector3(1.0f, 1.0f, 0.5f),
+}};
+
+constexpr std::array<GLushort, 6> kIndexedQuadIndices = {{0, 1, 2, 0, 2, 3}};
 
 const char *GetColorName(GLColor color)
 {
@@ -156,7 +160,7 @@
 
 GLColorRGB::GLColorRGB(GLubyte r, GLubyte g, GLubyte b) : R(r), G(g), B(b) {}
 
-GLColorRGB::GLColorRGB(const angle::Vector3 &floatColor)
+GLColorRGB::GLColorRGB(const Vector3 &floatColor)
     : R(ColorDenorm(floatColor.x())), G(ColorDenorm(floatColor.y())), B(ColorDenorm(floatColor.z()))
 {}
 
@@ -164,7 +168,7 @@
 
 GLColor::GLColor(GLubyte r, GLubyte g, GLubyte b, GLubyte a) : R(r), G(g), B(b), A(a) {}
 
-GLColor::GLColor(const angle::Vector4 &floatColor)
+GLColor::GLColor(const Vector4 &floatColor)
     : R(ColorDenorm(floatColor.x())),
       G(ColorDenorm(floatColor.y())),
       B(ColorDenorm(floatColor.z())),
@@ -201,9 +205,9 @@
     }
 }
 
-angle::Vector4 GLColor::toNormalizedVector() const
+Vector4 GLColor::toNormalizedVector() const
 {
-    return angle::Vector4(ColorNorm(R), ColorNorm(G), ColorNorm(B), ColorNorm(A));
+    return Vector4(ColorNorm(R), ColorNorm(G), ColorNorm(B), ColorNorm(A));
 }
 
 GLColor ReadColor(GLint x, GLint y)
@@ -262,20 +266,19 @@
 // static
 std::array<angle::Vector3, 6> ANGLETestBase::GetQuadVertices()
 {
-    std::array<angle::Vector3, 6> vertices;
-    vertices[0] = angle::Vector3(-1.0f, 1.0f, 0.5f);
-    vertices[1] = angle::Vector3(-1.0f, -1.0f, 0.5f);
-    vertices[2] = angle::Vector3(1.0f, -1.0f, 0.5f);
-    vertices[3] = angle::Vector3(-1.0f, 1.0f, 0.5f);
-    vertices[4] = angle::Vector3(1.0f, -1.0f, 0.5f);
-    vertices[5] = angle::Vector3(1.0f, 1.0f, 0.5f);
-    return vertices;
+    return angle::kQuadVertices;
 }
 
 // static
 std::array<GLushort, 6> ANGLETestBase::GetQuadIndices()
 {
-    return angle::IndexedQuadIndices;
+    return angle::kIndexedQuadIndices;
+}
+
+// static
+std::array<angle::Vector3, 4> ANGLETestBase::GetIndexedQuadVertices()
+{
+    return angle::kIndexedQuadVertices;
 }
 
 ANGLETestBase::ANGLETestBase(const angle::PlatformParameters &params)
@@ -454,7 +457,7 @@
         glGenBuffers(1, &mQuadVertexBuffer);
     }
 
-    auto quadVertices = angle::GetIndexedQuadVertices();
+    auto quadVertices = angle::kIndexedQuadVertices;
     for (angle::Vector3 &vertex : quadVertices)
     {
         vertex.x() *= positionAttribXYScale;
@@ -474,8 +477,8 @@
     }
 
     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mQuadIndexBuffer);
-    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(angle::IndexedQuadIndices),
-                 angle::IndexedQuadIndices.data(), GL_STATIC_DRAW);
+    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(angle::kIndexedQuadIndices),
+                 angle::kIndexedQuadIndices.data(), GL_STATIC_DRAW);
 }
 
 // static
@@ -646,7 +649,7 @@
     }
     else
     {
-        indices = angle::IndexedQuadIndices.data();
+        indices = angle::kIndexedQuadIndices.data();
     }
 
     if (!restrictedRange)
diff --git a/src/tests/test_utils/ANGLETest.h b/src/tests/test_utils/ANGLETest.h
index d80768a..b359a35 100644
--- a/src/tests/test_utils/ANGLETest.h
+++ b/src/tests/test_utils/ANGLETest.h
@@ -289,6 +289,8 @@
 
     static std::array<angle::Vector3, 6> GetQuadVertices();
     static std::array<GLushort, 6> GetQuadIndices();
+    static std::array<angle::Vector3, 4> GetIndexedQuadVertices();
+
     void drawIndexedQuad(GLuint program,
                          const std::string &positionAttribName,
                          GLfloat positionAttribZ);