Inline and micro-optimize more for perf tests.

Using a custom array instead of std::vector speeds up the resource
manager. One reason is because calls to size() are implemented in many
implementations as a difference between two pointers. This sub size
implementations are slower than storing a simple size variable in a
custom class.

Also includes more inlining of hot spots functions.

Also includes a small unit test class for ResourceMap. And an unrelated
but small test fix for TextureLimisTest. Also a small unrelated fix for
a Transform Feedback test.

Increase the scores of the draw call perf test with texture and buffer
bindings and the buffer binding perf test.

Bug: angleproject:2763
Change-Id: I41c327987db27ac45e6a62579f01e1cdc22e396c
Reviewed-on: https://chromium-review.googlesource.com/1171510
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
index dc6326a..f20d64e 100644
--- a/src/libANGLE/Context.cpp
+++ b/src/libANGLE/Context.cpp
@@ -972,11 +972,6 @@
     return mState.mBuffers->getBuffer(handle);
 }
 
-Texture *Context::getTexture(GLuint handle) const
-{
-    return mState.mTextures->getTexture(handle);
-}
-
 Renderbuffer *Context::getRenderbuffer(GLuint handle) const
 {
     return mState.mRenderbuffers->getRenderbuffer(handle);
diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h
index bfaa81d..304317f 100644
--- a/src/libANGLE/Context.h
+++ b/src/libANGLE/Context.h
@@ -474,7 +474,8 @@
     Buffer *getBuffer(GLuint handle) const;
     FenceNV *getFenceNV(GLuint handle);
     Sync *getSync(GLsync handle) const;
-    Texture *getTexture(GLuint handle) const;
+    Texture *getTexture(GLuint handle) const { return mState.mTextures->getTexture(handle); }
+
     Framebuffer *getFramebuffer(GLuint handle) const;
     Renderbuffer *getRenderbuffer(GLuint handle) const;
     VertexArray *getVertexArray(GLuint handle) const;
diff --git a/src/libANGLE/ResourceManager.cpp b/src/libANGLE/ResourceManager.cpp
index b81dc84..31c4f32 100644
--- a/src/libANGLE/ResourceManager.cpp
+++ b/src/libANGLE/ResourceManager.cpp
@@ -242,12 +242,6 @@
     return AllocateEmptyObject(&mHandleAllocator, &mObjectMap);
 }
 
-Texture *TextureManager::getTexture(GLuint handle) const
-{
-    ASSERT(mObjectMap.query(0) == nullptr);
-    return mObjectMap.query(handle);
-}
-
 void TextureManager::signalAllTexturesDirty(const Context *context) const
 {
     for (const auto &texture : mObjectMap)
diff --git a/src/libANGLE/ResourceManager.h b/src/libANGLE/ResourceManager.h
index 828f3fb..d4deb57 100644
--- a/src/libANGLE/ResourceManager.h
+++ b/src/libANGLE/ResourceManager.h
@@ -166,7 +166,11 @@
 {
   public:
     GLuint createTexture();
-    Texture *getTexture(GLuint handle) const;
+    Texture *getTexture(GLuint handle) const
+    {
+        ASSERT(mObjectMap.query(0) == nullptr);
+        return mObjectMap.query(handle);
+    }
 
     void signalAllTexturesDirty(const Context *context) const;
 
diff --git a/src/libANGLE/ResourceMap.h b/src/libANGLE/ResourceMap.h
index b00da68..7cfb9bd 100644
--- a/src/libANGLE/ResourceMap.h
+++ b/src/libANGLE/ResourceMap.h
@@ -23,7 +23,16 @@
     ResourceMap();
     ~ResourceMap();
 
-    ResourceType *query(GLuint handle) const;
+    ANGLE_INLINE ResourceType *query(GLuint handle) const
+    {
+        if (handle < mFlatResourcesSize)
+        {
+            ResourceType *value = mFlatResources[handle];
+            return (value == InvalidPointer() ? nullptr : value);
+        }
+        auto it = mHashedResources.find(handle);
+        return (it == mHashedResources.end() ? nullptr : it->second);
+    }
 
     // Returns true if the handle was reserved. Not necessarily if the resource is created.
     bool contains(GLuint handle) const;
@@ -84,7 +93,11 @@
     // Experimental testing suggests that 16k is a reasonable upper limit.
     static constexpr size_t kFlatResourcesLimit = 0x4000;
 
-    std::vector<ResourceType *> mFlatResources;
+    // Size of one map element.
+    static constexpr size_t kElementSize = sizeof(ResourceType *);
+
+    size_t mFlatResourcesSize;
+    ResourceType **mFlatResources;
 
     // A map of GL objects indexed by object ID.
     HashMap mHashedResources;
@@ -92,8 +105,11 @@
 
 template <typename ResourceType>
 ResourceMap<ResourceType>::ResourceMap()
-    : mFlatResources(kInitialFlatResourcesSize, InvalidPointer()), mHashedResources()
+    : mFlatResourcesSize(kInitialFlatResourcesSize),
+      mFlatResources(new ResourceType *[kInitialFlatResourcesSize]),
+      mHashedResources()
 {
+    memset(mFlatResources, kInvalidPointer, mFlatResourcesSize * kElementSize);
 }
 
 template <typename ResourceType>
@@ -103,21 +119,9 @@
 }
 
 template <typename ResourceType>
-ResourceType *ResourceMap<ResourceType>::query(GLuint handle) const
-{
-    if (handle < mFlatResources.size())
-    {
-        auto value = mFlatResources[handle];
-        return (value == InvalidPointer() ? nullptr : value);
-    }
-    auto it = mHashedResources.find(handle);
-    return (it == mHashedResources.end() ? nullptr : it->second);
-}
-
-template <typename ResourceType>
 bool ResourceMap<ResourceType>::contains(GLuint handle) const
 {
-    if (handle < mFlatResources.size())
+    if (handle < mFlatResourcesSize)
     {
         return (mFlatResources[handle] != InvalidPointer());
     }
@@ -127,7 +131,7 @@
 template <typename ResourceType>
 bool ResourceMap<ResourceType>::erase(GLuint handle, ResourceType **resourceOut)
 {
-    if (handle < mFlatResources.size())
+    if (handle < mFlatResourcesSize)
     {
         auto &value = mFlatResources[handle];
         if (value == InvalidPointer())
@@ -155,17 +159,25 @@
 {
     if (handle < kFlatResourcesLimit)
     {
-        if (handle >= mFlatResources.size())
+        if (handle >= mFlatResourcesSize)
         {
             // Use power-of-two.
-            size_t newSize = mFlatResources.size();
+            size_t newSize = mFlatResourcesSize;
             while (newSize <= handle)
             {
                 newSize *= 2;
             }
-            mFlatResources.resize(newSize, nullptr);
+
+            ResourceType **oldResources = mFlatResources;
+
+            mFlatResources = new ResourceType *[newSize];
+            memset(&mFlatResources[mFlatResourcesSize], kInvalidPointer,
+                   (newSize - mFlatResourcesSize) * kElementSize);
+            memcpy(mFlatResources, oldResources, mFlatResourcesSize * kElementSize);
+            mFlatResourcesSize = newSize;
+            delete[] oldResources;
         }
-        ASSERT(mFlatResources.size() > handle);
+        ASSERT(mFlatResourcesSize > handle);
         mFlatResources[handle] = resource;
     }
     else
@@ -183,13 +195,13 @@
 template <typename ResourceType>
 typename ResourceMap<ResourceType>::Iterator ResourceMap<ResourceType>::end() const
 {
-    return Iterator(*this, static_cast<GLuint>(mFlatResources.size()), mHashedResources.end());
+    return Iterator(*this, static_cast<GLuint>(mFlatResourcesSize), mHashedResources.end());
 }
 
 template <typename ResourceType>
 typename ResourceMap<ResourceType>::Iterator ResourceMap<ResourceType>::find(GLuint handle) const
 {
-    if (handle < mFlatResources.size())
+    if (handle < mFlatResourcesSize)
     {
         return (mFlatResources[handle] != InvalidPointer()
                     ? Iterator(handle, mHashedResources.begin())
@@ -210,21 +222,22 @@
 template <typename ResourceType>
 void ResourceMap<ResourceType>::clear()
 {
-    mFlatResources.assign(kInitialFlatResourcesSize, InvalidPointer());
+    memset(mFlatResources, kInvalidPointer, kInitialFlatResourcesSize * kElementSize);
+    mFlatResourcesSize = kInitialFlatResourcesSize;
     mHashedResources.clear();
 }
 
 template <typename ResourceType>
 GLuint ResourceMap<ResourceType>::nextNonNullResource(size_t flatIndex) const
 {
-    for (size_t index = flatIndex; index < mFlatResources.size(); index++)
+    for (size_t index = flatIndex; index < mFlatResourcesSize; index++)
     {
         if (mFlatResources[index] != nullptr && mFlatResources[index] != InvalidPointer())
         {
             return static_cast<GLuint>(index);
         }
     }
-    return static_cast<GLuint>(mFlatResources.size());
+    return static_cast<GLuint>(mFlatResourcesSize);
 }
 
 template <typename ResourceType>
@@ -259,7 +272,7 @@
 template <typename ResourceType>
 typename ResourceMap<ResourceType>::Iterator &ResourceMap<ResourceType>::Iterator::operator++()
 {
-    if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResources.size()))
+    if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResourcesSize))
     {
         mFlatIndex = mOrigin.nextNonNullResource(mFlatIndex + 1);
     }
@@ -288,7 +301,7 @@
 template <typename ResourceType>
 void ResourceMap<ResourceType>::Iterator::updateValue()
 {
-    if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResources.size()))
+    if (mFlatIndex < static_cast<GLuint>(mOrigin.mFlatResourcesSize))
     {
         mValue.first  = mFlatIndex;
         mValue.second = mOrigin.mFlatResources[mFlatIndex];
diff --git a/src/libANGLE/ResourceMap_unittest.cpp b/src/libANGLE/ResourceMap_unittest.cpp
new file mode 100644
index 0000000..fd9d3e9
--- /dev/null
+++ b/src/libANGLE/ResourceMap_unittest.cpp
@@ -0,0 +1,136 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ResourceMap_unittest:
+//   Unit tests for the ResourceMap template class.
+//
+
+#include <gtest/gtest.h>
+
+#include "libANGLE/ResourceMap.h"
+
+using namespace gl;
+
+namespace
+{
+// Tests assigning slots in the map and then deleting elements.
+TEST(ResourceMapTest, AssignAndErase)
+{
+    constexpr size_t kSize = 64;
+    ResourceMap<size_t> resourceMap;
+    std::vector<size_t> objects(kSize, 1);
+    for (size_t index = 0; index < kSize; ++index)
+    {
+        resourceMap.assign(index + 1, &objects[index]);
+    }
+
+    for (size_t index = 0; index < kSize; ++index)
+    {
+        size_t *found = nullptr;
+        ASSERT_TRUE(resourceMap.erase(index + 1, &found));
+        ASSERT_EQ(&objects[index], found);
+    }
+
+    ASSERT_TRUE(resourceMap.empty());
+}
+
+// Tests assigning slots in the map and then using clear() to free it.
+TEST(ResourceMapTest, AssignAndClear)
+{
+    constexpr size_t kSize = 64;
+    ResourceMap<size_t> resourceMap;
+    std::vector<size_t> objects(kSize, 1);
+    for (size_t index = 0; index < kSize; ++index)
+    {
+        resourceMap.assign(index + 1, &objects[index]);
+    }
+
+    resourceMap.clear();
+    ASSERT_TRUE(resourceMap.empty());
+}
+
+// Tests growing a map more than double the size.
+TEST(ResourceMapTest, BigGrowth)
+{
+    constexpr size_t kSize = 8;
+
+    ResourceMap<size_t> resourceMap;
+    std::vector<size_t> objects;
+
+    for (size_t index = 0; index < kSize; ++index)
+    {
+        objects.push_back(index);
+    }
+
+    // Assign a large value.
+    constexpr size_t kLargeIndex = 128;
+    objects.push_back(kLargeIndex);
+
+    for (size_t &object : objects)
+    {
+        resourceMap.assign(object, &object);
+    }
+
+    for (size_t object : objects)
+    {
+        size_t *found = nullptr;
+        ASSERT_TRUE(resourceMap.erase(object, &found));
+        ASSERT_EQ(object, *found);
+    }
+
+    ASSERT_TRUE(resourceMap.empty());
+}
+
+// Tests querying unassigned or erased values.
+TEST(ResourceMapTest, QueryUnassigned)
+{
+    constexpr size_t kSize = 8;
+
+    ResourceMap<size_t> resourceMap;
+    std::vector<size_t> objects;
+
+    for (size_t index = 0; index < kSize; ++index)
+    {
+        objects.push_back(index);
+    }
+
+    ASSERT_FALSE(resourceMap.contains(0));
+    ASSERT_EQ(nullptr, resourceMap.query(0));
+    ASSERT_FALSE(resourceMap.contains(100));
+    ASSERT_EQ(nullptr, resourceMap.query(100));
+
+    for (size_t &object : objects)
+    {
+        resourceMap.assign(object, &object);
+    }
+
+    ASSERT_FALSE(resourceMap.empty());
+
+    for (size_t &object : objects)
+    {
+        ASSERT_TRUE(resourceMap.contains(object));
+        ASSERT_EQ(&object, resourceMap.query(object));
+    }
+
+    ASSERT_FALSE(resourceMap.contains(10));
+    ASSERT_EQ(nullptr, resourceMap.query(10));
+    ASSERT_FALSE(resourceMap.contains(100));
+    ASSERT_EQ(nullptr, resourceMap.query(100));
+
+    for (size_t object : objects)
+    {
+        size_t *found = nullptr;
+        ASSERT_TRUE(resourceMap.erase(object, &found));
+        ASSERT_EQ(object, *found);
+    }
+
+    ASSERT_TRUE(resourceMap.empty());
+
+    ASSERT_FALSE(resourceMap.contains(0));
+    ASSERT_EQ(nullptr, resourceMap.query(0));
+    ASSERT_FALSE(resourceMap.contains(100));
+    ASSERT_EQ(nullptr, resourceMap.query(100));
+}
+}  // anonymous namespace
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
index 7a78ae4..2a5b8f0 100644
--- a/src/libANGLE/State.cpp
+++ b/src/libANGLE/State.cpp
@@ -1033,12 +1033,6 @@
     return getSamplerTexture(static_cast<unsigned int>(mActiveSampler), type);
 }
 
-Texture *State::getSamplerTexture(unsigned int sampler, TextureType type) const
-{
-    ASSERT(sampler < mSamplerTextures[type].size());
-    return mSamplerTextures[type][sampler].get();
-}
-
 GLuint State::getSamplerTextureId(unsigned int sampler, TextureType type) const
 {
     ASSERT(sampler < mSamplerTextures[type].size());
diff --git a/src/libANGLE/State.h b/src/libANGLE/State.h
index 4d1f0e6..84446ec 100644
--- a/src/libANGLE/State.h
+++ b/src/libANGLE/State.h
@@ -162,10 +162,7 @@
     void setFragmentShaderDerivativeHint(GLenum hint);
 
     // GL_CHROMIUM_bind_generates_resource
-    bool isBindGeneratesResourceEnabled() const
-    {
-        return mBindGeneratesResource;
-    }
+    bool isBindGeneratesResourceEnabled() const { return mBindGeneratesResource; }
 
     // GL_ANGLE_client_arrays
     bool areClientArraysEnabled() const;
@@ -179,7 +176,13 @@
     unsigned int getActiveSampler() const;
     void setSamplerTexture(const Context *context, TextureType type, Texture *texture);
     Texture *getTargetTexture(TextureType type) const;
-    Texture *getSamplerTexture(unsigned int sampler, TextureType type) const;
+
+    Texture *getSamplerTexture(unsigned int sampler, TextureType type) const
+    {
+        ASSERT(sampler < mSamplerTextures[type].size());
+        return mSamplerTextures[type][sampler].get();
+    }
+
     GLuint getSamplerTextureId(unsigned int sampler, TextureType type) const;
     void detachTexture(const Context *context, const TextureMap &zeroTextures, GLuint texture);
     void initializeZeroTextures(const Context *context, const TextureMap &zeroTextures);
diff --git a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
index 62be3fb..cfc22cf 100644
--- a/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
+++ b/src/libANGLE/renderer/d3d/d3d9/Renderer9.cpp
@@ -979,7 +979,7 @@
                                     gl::Texture *texture)
 {
     int d3dSamplerOffset = (type == gl::ShaderType::Fragment) ? 0 : D3DVERTEXTEXTURESAMPLER0;
-    int d3dSampler       = index + d3dSamplerOffset;
+    int d3dSampler                    = index + d3dSamplerOffset;
     IDirect3DBaseTexture9 *d3dTexture = nullptr;
     bool forceSetTexture              = false;
 
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
index 5a1521e..30b8251 100644
--- a/src/libANGLE/validationES2.cpp
+++ b/src/libANGLE/validationES2.cpp
@@ -2981,20 +2981,6 @@
 
 bool ValidateBindTexture(Context *context, TextureType target, GLuint texture)
 {
-    Texture *textureObject = context->getTexture(texture);
-    if (textureObject && textureObject->getType() != target && texture != 0)
-    {
-        ANGLE_VALIDATION_ERR(context, InvalidOperation(), TypeMismatch);
-        return false;
-    }
-
-    if (!context->getGLState().isBindGeneratesResourceEnabled() &&
-        !context->isTextureGenerated(texture))
-    {
-        context->handleError(InvalidOperation() << "Texture was not generated");
-        return false;
-    }
-
     switch (target)
     {
         case TextureType::_2D:
@@ -3046,6 +3032,25 @@
             return false;
     }
 
+    if (texture == 0)
+    {
+        return true;
+    }
+
+    Texture *textureObject = context->getTexture(texture);
+    if (textureObject && textureObject->getType() != target)
+    {
+        ANGLE_VALIDATION_ERR(context, InvalidOperation(), TypeMismatch);
+        return false;
+    }
+
+    if (!context->getGLState().isBindGeneratesResourceEnabled() &&
+        !context->isTextureGenerated(texture))
+    {
+        context->handleError(InvalidOperation() << "Texture was not generated");
+        return false;
+    }
+
     return true;
 }
 
diff --git a/src/tests/angle_unittests.gni b/src/tests/angle_unittests.gni
index b39b5a5..f475ae1 100644
--- a/src/tests/angle_unittests.gni
+++ b/src/tests/angle_unittests.gni
@@ -25,6 +25,7 @@
   "../libANGLE/Observer_unittest.cpp",
   "../libANGLE/Program_unittest.cpp",
   "../libANGLE/ResourceManager_unittest.cpp",
+  "../libANGLE/ResourceMap_unittest.cpp",
   "../libANGLE/SizedMRUCache_unittest.cpp",
   "../libANGLE/Surface_unittest.cpp",
   "../libANGLE/TransformFeedback_unittest.cpp",
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
index 4f1cf76..d05ad9e 100644
--- a/src/tests/gl_tests/TextureTest.cpp
+++ b/src/tests/gl_tests/TextureTest.cpp
@@ -2774,7 +2774,18 @@
         setConfigAlphaBits(8);
     }
 
-    ~TextureLimitsTest()
+    void SetUp() override
+    {
+        ANGLETest::SetUp();
+
+        glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mMaxVertexTextures);
+        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxFragmentTextures);
+        glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mMaxCombinedTextures);
+
+        ASSERT_GL_NO_ERROR();
+    }
+
+    void TearDown() override
     {
         if (mProgram != 0)
         {
@@ -2786,17 +2797,8 @@
                 glDeleteTextures(static_cast<GLsizei>(mTextures.size()), &mTextures[0]);
             }
         }
-    }
 
-    void SetUp() override
-    {
-        ANGLETest::SetUp();
-
-        glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &mMaxVertexTextures);
-        glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &mMaxFragmentTextures);
-        glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &mMaxCombinedTextures);
-
-        ASSERT_GL_NO_ERROR();
+        ANGLETest::TearDown();
     }
 
     void compileProgramWithTextureCounts(const std::string &vertexPrefix,
diff --git a/src/tests/gl_tests/WebGLCompatibilityTest.cpp b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
index 21c517b..ae790c7 100644
--- a/src/tests/gl_tests/WebGLCompatibilityTest.cpp
+++ b/src/tests/gl_tests/WebGLCompatibilityTest.cpp
@@ -3232,12 +3232,12 @@
     // Create textures and allocate storage
     GLTexture tex0;
     GLTexture tex1;
-    GLRenderbuffer rb;
+    GLTexture tex2;
     FillTexture2D(tex0.get(), width, height, GLColor::black, 0, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE);
     FillTexture2D(tex1.get(), width, height, 0x80, 0, GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT,
                   GL_UNSIGNED_INT);
-    glBindRenderbuffer(GL_RENDERBUFFER, rb.get());
-    glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, width, height);
+    FillTexture2D(tex2.get(), width, height, 0x40, 0, GL_DEPTH_STENCIL, GL_DEPTH_STENCIL,
+                  GL_UNSIGNED_INT_24_8);
     ASSERT_GL_NO_ERROR();
 
     GLFramebuffer fbo;
@@ -3252,7 +3252,7 @@
     // The same image is used as depth buffer during rendering.
     glEnable(GL_DEPTH_TEST);
     drawQuad(program.get(), "aPosition", 0.5f, 1.0f, true);
-    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Same image as depth buffer should fail";
 
     // The same image is used as depth buffer. But depth mask is false.
     glDepthMask(GL_FALSE);
@@ -3266,9 +3266,9 @@
     EXPECT_GL_NO_ERROR();
 
     // Test rendering and sampling feedback loop for stencil buffer
-    glBindTexture(GL_RENDERBUFFER, rb.get());
+    glBindTexture(GL_TEXTURE_2D, tex2.get());
     glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
-    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, rb.get());
+    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, tex2.get(), 0);
     ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
     constexpr GLint stencilClearValue = 0x40;
     glClearBufferiv(GL_STENCIL, 0, &stencilClearValue);
@@ -3276,7 +3276,7 @@
     // The same image is used as stencil buffer during rendering.
     glEnable(GL_STENCIL_TEST);
     drawQuad(program.get(), "aPosition", 0.5f, 1.0f, true);
-    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
+    EXPECT_GL_ERROR(GL_INVALID_OPERATION) << "Same image as stencil buffer should fail";
 
     // The same image is used as stencil buffer. But stencil mask is zero.
     glStencilMask(0x0);