//
// Copyright 2020 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.
//
// VulkanPerformanceCounterTest:
//   Validates specific GL call patterns with ANGLE performance counters.
//   For example we can verify a certain call set doesn't break the render pass.
//

#include "common/unsafe_buffers.h"
#include "include/platform/Feature.h"
#include "test_utils/ANGLETest.h"
#include "test_utils/MultiThreadSteps.h"
#include "test_utils/angle_test_instantiate.h"
#include "test_utils/gl_raii.h"
#include "util/EGLWindow.h"
#include "util/OSWindow.h"
#include "util/random_utils.h"
#include "util/shader_utils.h"
#include "util/test_utils.h"

using namespace angle;

namespace
{
// Normally, if LOAD_OP_NONE is not supported, LOAD_OP_LOAD is used instead.  Similarly, if
// STORE_OP_NONE is not supported, STORE_OP_STORE is used instead.
//
// If an attachment has undefined contents and is unused, LOAD_OP_DONT_CARE and
// STORE_OP_DONT_CARE can be used.  However, these are write operations for synchronization
// purposes, and so ANGLE uses LOAD_OP_NONE and STORE_OP_NONE if available to avoid the
// synchronization.  When NONE is not available, ANGLE foregoes synchronization, producing
// syncval errors.
//
// For the sake of validation, it's unknown if NONE is turned into LOAD/STORE or DONT_CARE.  So
// validation allows variations there.
#define EXPECT_OP_LOAD_AND_NONE(expectedLoads, actualLoads, expectedNones, actualNones) \
    {                                                                                   \
        if (hasLoadOpNoneSupport())                                                     \
        {                                                                               \
            EXPECT_EQ(expectedLoads, actualLoads);                                      \
            EXPECT_EQ(expectedNones, actualNones);                                      \
        }                                                                               \
        else                                                                            \
        {                                                                               \
            EXPECT_EQ(actualNones, 0u);                                                 \
            EXPECT_LE(expectedLoads, actualLoads);                                      \
            EXPECT_GE(expectedLoads + expectedNones, actualLoads);                      \
        }                                                                               \
    }
#define EXPECT_OP_STORE_AND_NONE(expectedStores, actualStores, expectedNones, actualNones) \
    {                                                                                      \
        if (hasStoreOpNoneSupport() && hasLoadOpNoneSupport())                             \
        {                                                                                  \
            EXPECT_EQ(expectedStores, actualStores);                                       \
            EXPECT_EQ(expectedNones, actualNones);                                         \
        }                                                                                  \
        else                                                                               \
        {                                                                                  \
            if (!hasStoreOpNoneSupport())                                                  \
            {                                                                              \
                EXPECT_EQ(actualNones, 0u);                                                \
            }                                                                              \
            EXPECT_LE(expectedStores, actualStores);                                       \
            EXPECT_GE(expectedStores + expectedNones, actualStores + actualNones);         \
        }                                                                                  \
    }

#define EXPECT_DEPTH_OP_COUNTERS(counters, expected)                                       \
    {                                                                                      \
        EXPECT_EQ(expected.depthLoadOpClears, counters.depthLoadOpClears);                 \
        EXPECT_OP_LOAD_AND_NONE(expected.depthLoadOpLoads, counters.depthLoadOpLoads,      \
                                expected.depthLoadOpNones, counters.depthLoadOpNones);     \
        EXPECT_OP_STORE_AND_NONE(expected.depthStoreOpStores, counters.depthStoreOpStores, \
                                 expected.depthStoreOpNones, counters.depthStoreOpNones);  \
    }

#define EXPECT_STENCIL_OP_COUNTERS(counters, expected)                                         \
    {                                                                                          \
        EXPECT_EQ(expected.stencilLoadOpClears, counters.stencilLoadOpClears);                 \
        EXPECT_OP_LOAD_AND_NONE(expected.stencilLoadOpLoads, counters.stencilLoadOpLoads,      \
                                expected.stencilLoadOpNones, counters.stencilLoadOpNones);     \
        EXPECT_OP_STORE_AND_NONE(expected.stencilStoreOpStores, counters.stencilStoreOpStores, \
                                 expected.stencilStoreOpNones, counters.stencilStoreOpNones);  \
    }

#define EXPECT_DEPTH_STENCIL_OP_COUNTERS(counters, expected) \
    {                                                        \
        EXPECT_DEPTH_OP_COUNTERS(counters, expected);        \
        EXPECT_STENCIL_OP_COUNTERS(counters, expected);      \
    }

#define EXPECT_COLOR_OP_COUNTERS(counters, expected)                                       \
    {                                                                                      \
        EXPECT_EQ(expected.colorLoadOpClears, counters.colorLoadOpClears);                 \
        EXPECT_OP_LOAD_AND_NONE(expected.colorLoadOpLoads, counters.colorLoadOpLoads,      \
                                expected.colorLoadOpNones, counters.colorLoadOpNones);     \
        EXPECT_OP_STORE_AND_NONE(expected.colorStoreOpStores, counters.colorStoreOpStores, \
                                 expected.colorStoreOpNones, counters.colorStoreOpNones);  \
    }

#define EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(counters, expected)            \
    {                                                                        \
        EXPECT_EQ(expected.depthLoadOpLoads, counters.depthLoadOpLoads);     \
        EXPECT_EQ(expected.stencilLoadOpLoads, counters.stencilLoadOpLoads); \
    }

#define EXPECT_COUNTERS_FOR_UNRESOLVE_RESOLVE_TEST(counters, expected)                         \
    {                                                                                          \
        EXPECT_EQ(expected.colorAttachmentUnresolves, counters.colorAttachmentUnresolves);     \
        EXPECT_EQ(expected.depthAttachmentUnresolves, counters.depthAttachmentUnresolves);     \
        EXPECT_EQ(expected.stencilAttachmentUnresolves, counters.stencilAttachmentUnresolves); \
        EXPECT_EQ(expected.colorAttachmentResolves, counters.colorAttachmentResolves);         \
        EXPECT_EQ(expected.depthAttachmentResolves, counters.depthAttachmentResolves);         \
        EXPECT_EQ(expected.stencilAttachmentResolves, counters.stencilAttachmentResolves);     \
    }

#define EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected, actual) \
    {                                                      \
        if (hasPreferDrawOverClearAttachments())           \
        {                                                  \
            EXPECT_EQ(actual, 0u);                         \
        }                                                  \
        else                                               \
        {                                                  \
            EXPECT_EQ(actual, expected);                   \
        }                                                  \
    }

enum class BufferUpdate
{
    SubData,  // use glBufferSubData
    Copy,     // use glCopyBufferSubData
};

class VulkanPerformanceCounterTest : public ANGLETest<>
{
  protected:
    VulkanPerformanceCounterTest()
    {
        // Depth/Stencil required for SwapShouldInvalidate*.
        // Also RGBA8 is required to avoid the clear for emulated alpha.
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);
        setConfigDepthBits(24);
        setConfigStencilBits(8);
    }

    static constexpr GLsizei kOpsTestSize = 16;

    void testSetUp() override
    {
        // Using local variable so that ASAN can catch "stack-buffer-overflow" error.
        GLuint localMonitor;
        glGenPerfMonitorsAMD(1, &localMonitor);
        glBeginPerfMonitorAMD(localMonitor);
        monitor = localMonitor;
    }

    void testTearDown() override
    {
        glEndPerfMonitorAMD(monitor);
        glDeletePerfMonitorsAMD(1, &monitor);
    }

    void setupForColorOpsTest(GLFramebuffer *framebuffer, GLTexture *texture)
    {
        // Setup the framebuffer
        glBindFramebuffer(GL_FRAMEBUFFER, *framebuffer);
        glBindTexture(GL_TEXTURE_2D, *texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kOpsTestSize, kOpsTestSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture, 0);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    }

    void setupForColorDepthOpsTest(GLFramebuffer *framebuffer,
                                   GLTexture *texture,
                                   GLRenderbuffer *renderbuffer)
    {
        // Setup color and depth
        glBindFramebuffer(GL_FRAMEBUFFER, *framebuffer);
        glBindTexture(GL_TEXTURE_2D, *texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kOpsTestSize, kOpsTestSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture, 0);
        glBindRenderbuffer(GL_RENDERBUFFER, *renderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, kOpsTestSize, kOpsTestSize);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
                                  *renderbuffer);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Setup depth parameters
        glEnable(GL_DEPTH_TEST);
        glDepthMask(GL_TRUE);
        glDepthFunc(GL_GEQUAL);
        glClearDepthf(0.99f);
        glViewport(0, 0, kOpsTestSize, kOpsTestSize);
        ASSERT_GL_NO_ERROR();
    }

    void setupForDepthStencilOpsTest(GLFramebuffer *framebuffer,
                                     GLTexture *texture,
                                     GLRenderbuffer *renderbuffer)
    {
        // Setup color, depth, and stencil
        glBindFramebuffer(GL_FRAMEBUFFER, *framebuffer);
        glBindTexture(GL_TEXTURE_2D, *texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kOpsTestSize, kOpsTestSize, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, *texture, 0);
        glBindRenderbuffer(GL_RENDERBUFFER, *renderbuffer);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kOpsTestSize, kOpsTestSize);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                                  *renderbuffer);
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

        // Setup depth parameters
        glEnable(GL_DEPTH_TEST);
        glDepthMask(GL_TRUE);
        glDepthFunc(GL_GEQUAL);
        glEnable(GL_STENCIL_TEST);
        glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
        glStencilMask(0xFF);
        glClearDepthf(0.99f);
        glClearStencil(0xAA);
        glViewport(0, 0, kOpsTestSize, kOpsTestSize);
        ASSERT_GL_NO_ERROR();
    }

    void setupClearAndDrawForDepthStencilOpsTest(GLProgram *program,
                                                 GLFramebuffer *framebuffer,
                                                 GLTexture *texture,
                                                 GLRenderbuffer *renderbuffer,
                                                 bool clearStencil)
    {
        setupForDepthStencilOpsTest(framebuffer, texture, renderbuffer);

        // Clear and draw with depth and stencil buffer enabled
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT |
                (clearStencil ? GL_STENCIL_BUFFER_BIT : 0));
        drawQuad(*program, essl1_shaders::PositionAttrib(), 1.f);
        ASSERT_GL_NO_ERROR();
    }

    void setExpectedCountersForColorOps(const angle::VulkanPerfCounters &counters,
                                        uint64_t incrementalRenderPasses,
                                        uint64_t incrementalColorLoadOpClears,
                                        uint64_t incrementalColorLoadOpLoads,
                                        uint64_t incrementalColorLoadOpNones,
                                        uint64_t incrementalColorStoreOpStores,
                                        uint64_t incrementalColorStoreOpNones,
                                        angle::VulkanPerfCounters *expected)
    {
        expected->renderPasses       = counters.renderPasses + incrementalRenderPasses;
        expected->colorLoadOpClears  = counters.colorLoadOpClears + incrementalColorLoadOpClears;
        expected->colorLoadOpLoads   = counters.colorLoadOpLoads + incrementalColorLoadOpLoads;
        expected->colorLoadOpNones   = counters.colorLoadOpNones + incrementalColorLoadOpNones;
        expected->colorStoreOpStores = counters.colorStoreOpStores + incrementalColorStoreOpStores;
        expected->colorStoreOpNones  = counters.colorStoreOpNones + incrementalColorStoreOpNones;
    }

    void setExpectedCountersForUnresolveResolveTest(const angle::VulkanPerfCounters &counters,
                                                    uint64_t incrementalColorAttachmentUnresolves,
                                                    uint64_t incrementalDepthAttachmentUnresolves,
                                                    uint64_t incrementalStencilAttachmentUnresolves,
                                                    uint64_t incrementalColorAttachmentResolves,
                                                    uint64_t incrementalDepthAttachmentResolves,
                                                    uint64_t incrementalStencilAttachmentResolves,
                                                    angle::VulkanPerfCounters *expected)
    {
        expected->colorAttachmentUnresolves =
            counters.colorAttachmentUnresolves + incrementalColorAttachmentUnresolves;
        expected->depthAttachmentUnresolves =
            counters.depthAttachmentUnresolves + incrementalDepthAttachmentUnresolves;
        expected->stencilAttachmentUnresolves =
            counters.stencilAttachmentUnresolves + incrementalStencilAttachmentUnresolves;
        expected->colorAttachmentResolves =
            counters.colorAttachmentResolves + incrementalColorAttachmentResolves;
        expected->depthAttachmentResolves =
            counters.depthAttachmentResolves + incrementalDepthAttachmentResolves;
        expected->stencilAttachmentResolves =
            counters.stencilAttachmentResolves + incrementalStencilAttachmentResolves;
    }

    void adjustExpectedCountersForDynamicRenderingMSRTTEmulationUnresolve(
        uint64_t incrementalUnresolveRenderPasses,
        uint64_t incrementalUnresolveColorAttachments,
        uint64_t incrementalUnresolveDepthStencilAttachment,
        bool unresolveDepth,
        bool unresolveStencil,
        angle::VulkanPerfCounters *expected)
    {
        if (isFeatureEnabled(Feature::PreferDynamicRendering) &&
            !isFeatureEnabled(Feature::SupportsMultisampledRenderToSingleSampled))
        {
            expected->renderPasses += incrementalUnresolveRenderPasses;
            expected->colorStoreOpStores += incrementalUnresolveColorAttachments;

            if (unresolveDepth)
            {
                expected->depthStoreOpStores += incrementalUnresolveDepthStencilAttachment;
            }
            else if (isFeatureEnabled(Feature::SupportsRenderPassLoadStoreOpNone))
            {
                expected->depthStoreOpNones += incrementalUnresolveDepthStencilAttachment;
                expected->depthLoadOpNones += incrementalUnresolveDepthStencilAttachment;
            }

            if (unresolveStencil)
            {
                expected->stencilStoreOpStores += incrementalUnresolveDepthStencilAttachment;
                if (!isFeatureEnabled(Feature::SupportsShaderStencilExport))
                {
                    // Without VK_EXT_shader_stencil_export, the unresolve render pass clears
                    // stencil to 0.
                    expected->stencilLoadOpClears += incrementalUnresolveDepthStencilAttachment;
                }
            }
            else if (isFeatureEnabled(Feature::SupportsRenderPassLoadStoreOpNone))
            {
                expected->stencilStoreOpNones += incrementalUnresolveDepthStencilAttachment;
                expected->stencilLoadOpNones += incrementalUnresolveDepthStencilAttachment;
            }
        }
    }

    void maskedFramebufferFetchDraw(const GLColor &clearColor, GLBuffer &buffer);
    void maskedFramebufferFetchDrawVerify(const GLColor &expectedColor, GLBuffer &buffer);

    void updateBuffer(BufferUpdate update,
                      GLenum target,
                      GLintptr offset,
                      GLsizeiptr size,
                      const void *data)
    {
        if (update == BufferUpdate::SubData)
        {
            // If using glBufferSubData, directly upload data on the specified target (where the
            // buffer is already bound)
            glBufferSubData(target, offset, size, data);
        }
        else
        {
            // Otherwise copy through a temp buffer.  Use a non-zero offset for more coverage.
            constexpr GLintptr kStagingOffset = 123;
            GLBuffer staging;
            glBindBuffer(GL_COPY_READ_BUFFER, staging);
            glBufferData(GL_COPY_READ_BUFFER, offset + size + kStagingOffset * 2, nullptr,
                         GL_STATIC_DRAW);
            glBufferSubData(GL_COPY_READ_BUFFER, kStagingOffset, size, data);
            glCopyBufferSubData(GL_COPY_READ_BUFFER, target, kStagingOffset, offset, size);
        }
    }

    void mappingGpuReadOnlyBufferGhostsBuffer(BufferUpdate update);
    void partialBufferUpdateShouldNotBreakRenderPass(BufferUpdate update);
    void bufferSubDataShouldNotTriggerSyncState(BufferUpdate update);

    angle::VulkanPerfCounters getPerfCounters()
    {
        if (mIndexMap.empty())
        {
            mIndexMap = BuildCounterNameToIndexMap();
        }

        return GetPerfCounters(mIndexMap);
    }

    // Support status for ANGLE features.
    bool isFeatureEnabled(Feature feature) const
    {
        return getEGLWindow()->isFeatureEnabled(feature);
    }
    bool hasLoadOpNoneSupport() const
    {
        return isFeatureEnabled(Feature::SupportsRenderPassLoadStoreOpNone);
    }
    bool hasStoreOpNoneSupport() const
    {
        return isFeatureEnabled(Feature::SupportsRenderPassLoadStoreOpNone) ||
               isFeatureEnabled(Feature::SupportsRenderPassStoreOpNone);
    }
    bool hasMutableMipmapTextureUpload() const
    {
        return isFeatureEnabled(Feature::MutableMipmapTextureUpload);
    }
    bool hasPreferDrawOverClearAttachments() const
    {
        return isFeatureEnabled(Feature::PreferDrawClearOverVkCmdClearAttachments);
    }
    bool hasSupportsPipelineCreationFeedback() const
    {
        return isFeatureEnabled(Feature::SupportsPipelineCreationFeedback);
    }
    bool hasWarmUpPipelineCacheAtLink() const
    {
        return isFeatureEnabled(Feature::WarmUpPipelineCacheAtLink);
    }
    bool skipPipelineCacheSerialization() const
    {
        return isFeatureEnabled(Feature::SkipPipelineCacheSerialization);
    }
    bool hasPreferCPUForBufferSubData() const
    {
        return isFeatureEnabled(Feature::PreferCPUForBufferSubData);
    }
    bool hasPreferSubmitAtFBOBoundary() const
    {
        return isFeatureEnabled(Feature::PreferSubmitAtFBOBoundary);
    }
    bool hasDisallowMixedDepthStencilLoadOpNoneAndLoad() const
    {
        return isFeatureEnabled(Feature::DisallowMixedDepthStencilLoadOpNoneAndLoad);
    }
    bool hasSupportsImagelessFramebuffer() const
    {
        return isFeatureEnabled(Feature::SupportsImagelessFramebuffer);
    }
    bool hasSupportsHostImageCopy() const
    {
        return isFeatureEnabled(Feature::SupportsHostImageCopy);
    }
    bool hasDepthStencilResolveThroughAttachment() const
    {
        return isFeatureEnabled(Feature::SupportsDepthStencilResolve) &&
               !isFeatureEnabled(Feature::DisableDepthStencilResolveThroughAttachment);
    }
    bool hasSupportsTileMemoryOrSimulation() const
    {
        return isFeatureEnabled(Feature::SupportsTileMemoryHeap) ||
               isFeatureEnabled(Feature::SimulateTileMemoryForTesting);
    }
    bool hasSupportsShaderStencilExport() const
    {
        return isFeatureEnabled(Feature::SupportsShaderStencilExport);
    }

    // Setup color texture and depth/stencil render buffer
    void setupColorTextureAndDepthBuffer(GLuint colorTexture,
                                         GLuint depthStencil,
                                         GLenum depthStencilFormat,
                                         GLsizei width,
                                         GLsizei height)
    {
        glBindTexture(GL_TEXTURE_2D, colorTexture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);

        glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
        glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height);
    }

    // Setup FBO with color/depth/stencil attachment
    void setupFBO(GLuint colorTexture,
                  GLuint depthBuffer,
                  GLuint stencilBuffer,
                  GLuint fbo,
                  GLsizei width,
                  GLsizei height)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        if (colorTexture != 0)
        {
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                   colorTexture, 0);
        }
        if (depthBuffer != 0)
        {
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
                                      depthBuffer);
        }
        if (stencilBuffer != 0)
        {
            glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                                      stencilBuffer);
        }
        ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        glViewport(0, 0, width, height);
    }

    // Draw a quad that would display green if depth buffer == depthValue, and display red
    // otherwise.
    void drawQuadToVerifyDepthValue(const GLProgram &drawGreen,
                                    const GLProgram &drawRed,
                                    GLfloat depthValue)
    {
        GLfloat kErrorTolerance = 0.01f;
        glDisable(GL_STENCIL_TEST);
        // Don't modify depth buffer
        glDepthMask(GL_FALSE);
        glEnable(GL_DEPTH_TEST);
        // This should pass
        glDepthFunc(GL_LESS);
        drawQuad(drawGreen, essl1_shaders::PositionAttrib(), depthValue - kErrorTolerance);
        // This should fail
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValue + kErrorTolerance);
        glDepthFunc(GL_GREATER);
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValue - kErrorTolerance);
    }

    // Draw a quad that would display green if stencil buffer == stencilValue.
    void drawQuadToVerifyStencilValue(const GLProgram &drawGreen, GLint stencilValue)
    {
        glDisable(GL_DEPTH_TEST);
        glStencilMask(0x00);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_EQUAL, stencilValue, 0xFF);
        drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.0f);
    }

    void drawWithDepthValue(std::array<Vector3, 6> &quadVertices, float depth)
    {
        for (Vector3 &vertice : quadVertices)
        {
            vertice[2] = depth;
        }
        glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(quadVertices[0]) * quadVertices.size(),
                        quadVertices.data());
        glDrawArrays(GL_TRIANGLES, 0, 6);
    }

    GLuint monitor;
    CounterNameToIndexMap mIndexMap;
};

class VulkanPerformanceCounterTest_DepthStencilLoadStoreOps : public VulkanPerformanceCounterTest
{
  protected:
    void setExpectedCountersForDepthOps(const angle::VulkanPerfCounters &counters,
                                        uint64_t incrementalRenderPasses,
                                        uint64_t incrementalDepthLoadOpClears,
                                        uint64_t incrementalDepthLoadOpLoads,
                                        uint64_t incrementalDepthLoadOpNones,
                                        uint64_t incrementalDepthStoreOpStores,
                                        uint64_t incrementalDepthStoreOpNones,
                                        angle::VulkanPerfCounters *expected)
    {
        expected->renderPasses       = counters.renderPasses + incrementalRenderPasses;
        expected->depthLoadOpClears  = counters.depthLoadOpClears + incrementalDepthLoadOpClears;
        expected->depthLoadOpLoads   = counters.depthLoadOpLoads + incrementalDepthLoadOpLoads;
        expected->depthLoadOpNones   = counters.depthLoadOpNones + incrementalDepthLoadOpNones;
        expected->depthStoreOpStores = counters.depthStoreOpStores + incrementalDepthStoreOpStores;
        expected->depthStoreOpNones  = counters.depthStoreOpNones + incrementalDepthStoreOpNones;
    }

    void setExpectedCountersForStencilOps(const angle::VulkanPerfCounters &counters,
                                          uint64_t incrementalStencilLoadOpClears,
                                          uint64_t incrementalStencilLoadOpLoads,
                                          uint64_t incrementalStencilLoadOpNones,
                                          uint64_t incrementalStencilStoreOpStores,
                                          uint64_t incrementalStencilStoreOpNones,
                                          angle::VulkanPerfCounters *expected)
    {
        expected->stencilLoadOpClears =
            counters.stencilLoadOpClears + incrementalStencilLoadOpClears;
        expected->stencilLoadOpLoads = counters.stencilLoadOpLoads + incrementalStencilLoadOpLoads;
        expected->stencilLoadOpNones = counters.stencilLoadOpNones + incrementalStencilLoadOpNones;
        expected->stencilStoreOpStores =
            counters.stencilStoreOpStores + incrementalStencilStoreOpStores;
        expected->stencilStoreOpNones =
            counters.stencilStoreOpNones + incrementalStencilStoreOpNones;
    }

    void setAndIncrementDepthStencilLoadCountersForOpsTest(
        const angle::VulkanPerfCounters &counters,
        uint64_t incrementalDepthLoadOpLoads,
        uint64_t incrementalStencilLoadOpLoads,
        angle::VulkanPerfCounters *expected)
    {
        expected->depthLoadOpLoads   = counters.depthLoadOpLoads + incrementalDepthLoadOpLoads;
        expected->stencilLoadOpLoads = counters.stencilLoadOpLoads + incrementalStencilLoadOpLoads;
    }

    void setAndIncrementDepthStencilStoreCountersForOpsTest(
        const angle::VulkanPerfCounters &counters,
        uint64_t incrementalDepthStoreOpStores,
        uint64_t incrementalStencilStoreOpStores,
        angle::VulkanPerfCounters *expected)
    {
        expected->depthStoreOpStores = counters.depthStoreOpStores + incrementalDepthStoreOpStores;
        expected->stencilStoreOpStores =
            counters.stencilStoreOpStores + incrementalStencilStoreOpStores;
    }

    void adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(
        const angle::VulkanPerfCounters &counters,
        angle::VulkanPerfCounters *expected)
    {
        if (counters.fallbackFromTileMemory == 0)
        {
            return;
        }

        bool depthContentDefined =
            expected->depthStoreOpStores > 0 || expected->depthStoreOpNones > 0;
        if (depthContentDefined)
        {
            // fallbackFromTileMemory adds one load/store op if there is valid depth data.
            expected->depthLoadOpLoads += counters.fallbackFromTileMemory;
            expected->depthStoreOpStores += counters.fallbackFromTileMemory;
        }
        else
        {
            if (hasLoadOpNoneSupport())
            {
                expected->depthLoadOpNones += counters.fallbackFromTileMemory;
            }
            else
            {
                expected->depthLoadOpLoads += counters.fallbackFromTileMemory;
            }
            if (hasStoreOpNoneSupport())
            {
                expected->depthStoreOpNones += counters.fallbackFromTileMemory;
            }
            else
            {
                expected->depthStoreOpStores += counters.fallbackFromTileMemory;
            }
        }

        // fallbackFromTileMemory adds one load/store op if there is valid stencil data.
        if (expected->stencilStoreOpStores > 0 && hasSupportsShaderStencilExport())
        {
            // If driver supports shader export, copy is implemented with graphics shader write.
            expected->stencilLoadOpLoads += counters.fallbackFromTileMemory;
            expected->stencilStoreOpStores += counters.fallbackFromTileMemory;
        }
        else
        {
            if (hasLoadOpNoneSupport())
            {
                expected->stencilLoadOpNones += counters.fallbackFromTileMemory;
            }
            else
            {
                expected->stencilLoadOpLoads += counters.fallbackFromTileMemory;
            }
            if (hasStoreOpNoneSupport())
            {
                expected->stencilStoreOpNones += counters.fallbackFromTileMemory;
            }
            else
            {
                expected->stencilStoreOpStores += counters.fallbackFromTileMemory;
            }
        }
    }
};

class VulkanPerformanceCounterTest_RGBSurface : public VulkanPerformanceCounterTest
{
  protected:
    VulkanPerformanceCounterTest_RGBSurface() : VulkanPerformanceCounterTest()
    {
        // Only set the Alpha bit to 0 on ARM platforms to avoid EGL configuration
        // failures.
        if (IsARM())
        {
            setConfigAlphaBits(0);
        }
    }
};

class VulkanPerformanceCounterTest_ES31 : public VulkanPerformanceCounterTest
{};

class VulkanPerformanceCounterTest_DepthStencilLoadStoreOps_ES31
    : public VulkanPerformanceCounterTest_DepthStencilLoadStoreOps
{};

class VulkanPerformanceCounterTest_MSAA
    : public VulkanPerformanceCounterTest_DepthStencilLoadStoreOps
{
  protected:
    VulkanPerformanceCounterTest_MSAA() : VulkanPerformanceCounterTest_DepthStencilLoadStoreOps()
    {
        // Make sure the window is non-square to correctly test prerotation
        setWindowWidth(32);
        setWindowHeight(64);
        setSamples(4);
        setMultisampleEnabled(true);
    }
};

class VulkanPerformanceCounterTest_SingleBuffer : public VulkanPerformanceCounterTest
{
  protected:
    VulkanPerformanceCounterTest_SingleBuffer() : VulkanPerformanceCounterTest()
    {
        setMutableRenderBuffer(true);
    }
};

class VulkanPerformanceCounterTest_Prerotation : public VulkanPerformanceCounterTest
{
  protected:
    VulkanPerformanceCounterTest_Prerotation() : VulkanPerformanceCounterTest()
    {
        // Make sure the window is non-square to correctly test prerotation
        setWindowWidth(32);
        setWindowHeight(64);
    }
};

void VulkanPerformanceCounterTest::maskedFramebufferFetchDraw(const GLColor &clearColor,
                                                              GLBuffer &buffer)
{
    // Initialize the color buffer
    angle::Vector4 clearAsVec4 = clearColor.toNormalizedVector();
    glClearColor(clearAsVec4[0], clearAsVec4[1], clearAsVec4[2], clearAsVec4[3]);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, clearColor, 1);

    // Create output buffer
    constexpr GLsizei kBufferSize = kOpsTestSize * kOpsTestSize * sizeof(float[4]);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    // Zero-initialize it
    void *bufferData = glMapBufferRange(
        GL_SHADER_STORAGE_BUFFER, 0, kBufferSize,
        GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT);
    ANGLE_UNSAFE_TODO(memset(bufferData, 0, kBufferSize));
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

    // Mask color output
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    static constexpr char kVS[] = R"(#version 310 es
in highp vec4 a_position;

void main (void)
{
    gl_Position = a_position;
})";

    static constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_framebuffer_fetch_non_coherent : require
layout(noncoherent, location = 0) inout highp vec4 o_color;

layout(std140, binding = 0) buffer outBlock {
    highp vec4 data[256];
};

uniform highp vec4 u_color;
void main (void)
{
    uint index = uint(gl_FragCoord.y) * 16u + uint(gl_FragCoord.x);
    data[index] = o_color;
    o_color += u_color;
})";

    // Draw
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
}

void VulkanPerformanceCounterTest::maskedFramebufferFetchDrawVerify(const GLColor &expectedColor,
                                                                    GLBuffer &buffer)
{
    angle::Vector4 expectedAsVec4 = expectedColor.toNormalizedVector();

    // Read back the storage buffer and make sure framebuffer fetch worked as intended despite
    // masked color.
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    constexpr GLsizei kBufferSize = kOpsTestSize * kOpsTestSize * sizeof(float[4]);
    const float *colorData        = static_cast<const float *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT));
    for (uint32_t y = 0; y < kOpsTestSize; ++y)
    {
        for (uint32_t x = 0; x < kOpsTestSize; ++x)
        {
            uint32_t ssboIndex = (y * kOpsTestSize + x) * 4;
            ANGLE_UNSAFE_TODO({
                EXPECT_NEAR(colorData[ssboIndex + 0], expectedAsVec4[0], 0.05);
                EXPECT_NEAR(colorData[ssboIndex + 1], expectedAsVec4[1], 0.05);
                EXPECT_NEAR(colorData[ssboIndex + 2], expectedAsVec4[2], 0.05);
                EXPECT_NEAR(colorData[ssboIndex + 3], expectedAsVec4[3], 0.05);
            })
        }
    }
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Tests that texture updates to unused textures don't break the RP.
TEST_P(VulkanPerformanceCounterTest, NewTextureDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLColor kInitialData[4] = {GLColor::red, GLColor::blue, GLColor::green, GLColor::yellow};

    // Step 1: Set up a simple 2D Texture rendering loop.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    auto quadVerts = GetQuadVertices();

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
                 GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses;

    // Step 2: Introduce a new 2D Texture with the same Program and Framebuffer.
    GLTexture newTexture;
    glBindTexture(GL_TEXTURE_2D, newTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Tests that each update for a large cube map face results in outside command buffer submission.
TEST_P(VulkanPerformanceCounterTest, LargeCubeMapUpdatesSubmitsOutsideCommandBuffer)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    constexpr size_t kMaxBufferToImageCopySize = 64 * 1024 * 1024;
    constexpr uint64_t kNumSubmits             = 6;
    uint64_t expectedSubmitCommandsCount = getPerfCounters().vkQueueSubmitCallsTotal + kNumSubmits;

    // Set up a simple large cubemap texture so that each face update, when flushed, can result in a
    // separate submission.
    GLTexture textureCube;
    constexpr GLsizei kTexDim         = 4096;
    constexpr uint32_t kPixelSizeRGBA = 4;
    static_assert(kTexDim * kTexDim * kPixelSizeRGBA == kMaxBufferToImageCopySize);

    std::vector<GLColor> kInitialData(kTexDim * kTexDim, GLColor::magenta);
    glBindTexture(GL_TEXTURE_CUBE_MAP, textureCube);

    for (size_t i = 0; i < 6; i++)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, kTexDim, kTexDim, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, kInitialData.data());
    }

    // Flush the previous texture.
    GLTexture lastTexture;
    glBindTexture(GL_TEXTURE_2D, lastTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 kInitialData.data());

    // Verify number of submissions.
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedSubmitCommandsCount);
}

// Tests that submitting the outside command buffer due to texture upload size does not break the
// current render pass.
TEST_P(VulkanPerformanceCounterTest, SubmittingOutsideCommandBufferDoesNotBreakRenderPass)
{
    GLTexture tex;
    GLFramebuffer fbo;
    setupForColorOpsTest(&fbo, &tex);

    constexpr size_t kMaxBufferToImageCopySize = 64 * 1024 * 1024;
    constexpr uint64_t kNumSubmits             = 2;
    uint64_t expectedRenderPassCount           = getPerfCounters().renderPasses + 1;
    uint64_t submitCommandsCountBefore         = getPerfCounters().vkQueueSubmitCallsTotal;

    // Step 1: Set up a simple 2D texture.
    GLTexture texture;
    constexpr GLsizei kTexDim         = 256;
    constexpr uint32_t kPixelSizeRGBA = 4;
    constexpr uint32_t kTextureSize   = kTexDim * kTexDim * kPixelSizeRGBA;
    std::vector<GLColor> kInitialData(kTexDim * kTexDim, GLColor::green);

    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexDim, kTexDim, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 kInitialData.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    auto quadVerts = GetQuadVertices();

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
                 GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Step 2: Load a new 2D Texture multiple times with the same Program and Framebuffer. The total
    // size of the loaded textures must exceed the threshold to submit the outside command buffer.
    constexpr size_t kMaxLoadCount = kMaxBufferToImageCopySize / kTextureSize * kNumSubmits + 1;
    for (size_t loadCount = 0; loadCount < kMaxLoadCount; loadCount++)
    {
        GLTexture newTexture;
        glBindTexture(GL_TEXTURE_2D, newTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexDim, kTexDim, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kInitialData.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        glDrawArrays(GL_TRIANGLES, 0, 6);
        ASSERT_GL_NO_ERROR();
    }

    // Verify render pass and submitted frame counts.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal - submitCommandsCountBefore, kNumSubmits);
}

// Tests that submitting the outside command buffer due to texture upload size does not result in
// garbage collection of render pass resources..
TEST_P(VulkanPerformanceCounterTest, SubmittingOutsideCommandBufferDoesNotCollectRenderPassGarbage)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_disjoint_timer_query"));

    // If VK_EXT_host_image_copy is used, uploads will all be done on the CPU and there would be no
    // submissions.
    ANGLE_SKIP_TEST_IF(hasSupportsHostImageCopy());

    GLTexture tex;
    GLFramebuffer fbo;
    setupForColorOpsTest(&fbo, &tex);

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
    uint64_t submitCommandsCount     = getPerfCounters().vkQueueSubmitCallsTotal;

    // Set up a simple 2D texture.
    GLTexture texture;
    constexpr GLsizei kTexDim = 256;
    std::vector<GLColor> kInitialData(kTexDim * kTexDim, GLColor::green);

    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexDim, kTexDim, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 kInitialData.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    auto quadVerts = GetQuadVertices();

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
                 GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();

    // Issue a timestamp query, just for the sake of using it as a means of knowing when a
    // submission is finished.  In the Vulkan backend, querying the status of the query results in a
    // check of completed submissions, at which point associated garbage is also destroyed.
    GLQuery query;
    glQueryCounterEXT(query, GL_TIMESTAMP_EXT);

    // Issue a draw call, and delete the program
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
    program.reset();

    ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program2);
    ASSERT_EQ(posLoc, glGetAttribLocation(program2, essl1_shaders::PositionAttrib()));

    // Issue uploads until there's an implicit submission
    size_t textureCount = 0;
    while (getPerfCounters().vkQueueSubmitCallsTotal == submitCommandsCount)
    {
        GLTexture newTexture;
        glBindTexture(GL_TEXTURE_2D, newTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexDim, kTexDim, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kInitialData.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        glDrawArrays(GL_TRIANGLES, 0, 6);
        ASSERT_GL_NO_ERROR();
        textureCount++;
    }
    // 256x256 texture upload should not trigger a submission
    ASSERT(textureCount > 1);

    ++submitCommandsCount;
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, submitCommandsCount);

    // Busy wait until the query results are available.
    GLuint ready = GL_FALSE;
    while (ready == GL_FALSE)
    {
        angle::Sleep(0);
        glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_AVAILABLE_EXT, &ready);
    }

    // At this point, the render pass should still not be submitted, and the pipeline that is
    // deleted should still not be garbage collected.  Submit the commands and ensure there is no
    // crash.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ++submitCommandsCount;

    // Verify counters.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, submitCommandsCount);
}

// Tests that submitting the outside command buffer due to texture upload size and triggers
// endRenderPass works correctly.
TEST_P(VulkanPerformanceCounterTest, SubmittingOutsideCommandBufferTriggersEndRenderPass)
{
    // If VK_EXT_host_image_copy is used, uploads will all be done on the CPU and there would be no
    // submissions.
    ANGLE_SKIP_TEST_IF(hasSupportsHostImageCopy());

    const int width                  = getWindowWidth();
    const int height                 = getWindowHeight();
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
    uint64_t submitCommandsCount     = getPerfCounters().vkQueueSubmitCallsTotal;

    GLTexture tex;
    GLFramebuffer fbo;
    setupForColorOpsTest(&fbo, &tex);

    // Start a new renderpass with red quad on left.
    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glScissor(0, 0, width / 2, height);
    glEnable(GL_SCISSOR_TEST);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);

    // Issue texture uploads and draw green quad on right until endRenderPass is triggered
    glScissor(width / 2, 0, width / 2, height);
    ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
                     essl1_shaders::fs::Texture2D());
    glUseProgram(textureProgram);
    GLint textureLoc = glGetUniformLocation(textureProgram, essl1_shaders::Texture2DUniform());
    ASSERT_NE(-1, textureLoc);
    glUniform1i(textureLoc, 0);

    // This test is specifically try to test outsideRPCommands submission drain the reserved
    // queueSerials. Right now we are reserving 15 queue Serials. This is to ensure if the
    // implementation changes, we do not end up with infinite loop here.
    constexpr GLsizei kMaxOutsideRPCommandsSubmitCount = 17;
    constexpr GLsizei kTexDim                          = 1024;
    std::vector<GLColor> kInitialData(kTexDim * kTexDim, GLColor::green);
    // Put a limit on the loop to avoid infinite loop in bad case.
    while (getPerfCounters().renderPasses == expectedRenderPassCount &&
           getPerfCounters().vkQueueSubmitCallsTotal <
               submitCommandsCount + kMaxOutsideRPCommandsSubmitCount)
    {
        GLTexture newTexture;
        glBindTexture(GL_TEXTURE_2D, newTexture);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kTexDim, kTexDim);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kTexDim, kTexDim, GL_RGBA, GL_UNSIGNED_BYTE,
                        kInitialData.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();
    }

    ++expectedRenderPassCount;
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);

    // Verify renderpass draw quads correctly
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(width / 2 + 1, 0, GLColor::green);
}

// Tests that a color 2D image is cleared via vkCmdClearColorImage.
TEST_P(VulkanPerformanceCounterTest, ClearTextureEXTFullColorImageClear2D)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clear_texture"));
    uint32_t expectedFullImageClearsAfterFullClear1 = getPerfCounters().fullImageClears + 1;
    uint32_t expectedFullImageClearsAfterFullClear2 = getPerfCounters().fullImageClears + 2;

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // The following is the first full clear.
    glClearTexImageEXT(tex, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, 16, 16, GLColor::red);
    EXPECT_EQ(getPerfCounters().fullImageClears, expectedFullImageClearsAfterFullClear1);

    // The following are several partial clears.
    glClearTexSubImageEXT(tex, 0, 0, 0, 0, 8, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glClearTexSubImageEXT(tex, 0, 8, 0, 0, 8, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::blue);
    glClearTexSubImageEXT(tex, 0, 0, 8, 0, 8, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::cyan);
    glClearTexSubImageEXT(tex, 0, 8, 8, 0, 8, 8, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::yellow);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, 8, 8, GLColor::transparentBlack);
    EXPECT_EQ(getPerfCounters().fullImageClears, expectedFullImageClearsAfterFullClear1);

    // The following is the second full clear (extents of a full clear).
    glClearTexSubImageEXT(tex, 0, 0, 0, 0, 16, 16, 1, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::magenta);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, 16, 16, GLColor::magenta);
    EXPECT_EQ(getPerfCounters().fullImageClears, expectedFullImageClearsAfterFullClear2);
}

// Tests that a color 3D image is cleared via vkCmdClearColorImage.
TEST_P(VulkanPerformanceCounterTest, ClearTextureEXTFullColorImageClear3D)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clear_texture"));
    uint32_t expectedFullImageClears = getPerfCounters().fullImageClears + 1;

    constexpr uint32_t kWidth  = 4;
    constexpr uint32_t kHeight = 4;
    constexpr uint32_t kDepth  = 4;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_3D, texture);
    glTexStorage3D(GL_TEXTURE_3D, 1, GL_RGBA8, kWidth, kHeight, kDepth);
    glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAX_LEVEL, 0);

    glClearTexImageEXT(texture, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::white);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, 0);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::white);
    EXPECT_EQ(getPerfCounters().fullImageClears, expectedFullImageClears);

    glClearTexSubImageEXT(texture, 0, 0, 0, 0, kWidth, kHeight, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                          &GLColor::green);
    glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, 0);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);

    EXPECT_EQ(getPerfCounters().fullImageClears, expectedFullImageClears);
}

// Tests that mutable texture is uploaded with appropriate mip level attributes.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCompatibleMipLevelsInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that a single-level mutable texture is uploaded with appropriate attributes.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCompatibleSingleMipLevelInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable texture is uploaded with appropriate mip level attributes, even with unequal
// dimensions.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCompatibleMipLevelsNonSquareInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 2, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 1, GLColor::red);
    std::vector<GLColor> mip2Color(1 * 1, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 2, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip1Color.data());
    glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip2Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that a mutable texture is not uploaded if there are no data or updates for it.
TEST_P(VulkanPerformanceCounterTest, MutableTextureSingleLevelWithNoDataNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable texture is not uploaded with more than one updates in a single mip level.
TEST_P(VulkanPerformanceCounterTest, MutableTextureSingleLevelWithMultipleUpdatesNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexSubImage2D(GL_TEXTURE_2D, 0, 2, 0, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexSubImage2D(GL_TEXTURE_2D, 0, 2, 2, 2, 2, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that the optimization is not triggered when a mutable texture becomes immutable, e.g.,
// after glTexStorage2D.
TEST_P(VulkanPerformanceCounterTest, MutableTextureChangeToImmutableNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip1Color.data());
    glTexStorage2D(GL_TEXTURE_2D, 2, GL_RGBA8, 4, 4);
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable texture is not uploaded when there is no base mip level (0).
TEST_P(VulkanPerformanceCounterTest, MutableTextureNoBaseLevelNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip1Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip2Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip1Color.data());
    glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip2Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable texture is uploaded even when there is a missing mip level greater than 1
// despite the defined mip levels being compatible.
TEST_P(VulkanPerformanceCounterTest, MutableTextureMissingMipLevelGreaterThanOneInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(8 * 8, GLColor::red);
    std::vector<GLColor> mip1Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip3Color(1 * 1, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip1Color.data());
    glTexImage2D(GL_TEXTURE_2D, 3, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip3Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable texture is not uploaded with incompatible mip level sizes.
TEST_P(VulkanPerformanceCounterTest, MutableTextureMipLevelsWithIncompatibleSizesNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(8 * 8, GLColor::red);
    std::vector<GLColor> mip1Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip2Color(3 * 3, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 8, 8, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip1Color.data());
    glTexImage2D(GL_TEXTURE_2D, 2, GL_RGBA, 3, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip2Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable texture is not uploaded with incompatible mip level formats.
TEST_P(VulkanPerformanceCounterTest, MutableTextureMipLevelsWithIncompatibleFormatsNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE, mip0Color.data());
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable 3D texture is uploaded with appropriate mip level attributes.
TEST_P(VulkanPerformanceCounterTest, MutableTexture3DCompatibleMipLevelsInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_3D, texture1);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_3D, texture2);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable 3D texture is uploaded with appropriate mip level attributes, even with
// unequal dimensions.
TEST_P(VulkanPerformanceCounterTest, MutableTexture3DCompatibleMipLevelsNonCubeInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 2 * 2, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 1 * 1, GLColor::red);
    std::vector<GLColor> mip2Color(1 * 1 * 1, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_3D, texture1);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 4, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA, 2, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());
    glTexImage3D(GL_TEXTURE_3D, 2, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_3D, texture2);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable 3D texture is not uploaded with incompatible mip level sizes.
TEST_P(VulkanPerformanceCounterTest, MutableTexture3DMipLevelsWithIncompatibleSizesNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2 * 3, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_3D, texture1);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage3D(GL_TEXTURE_3D, 1, GL_RGBA, 2, 2, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_3D, texture2);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable 2D array texture is not uploaded with incompatible mip level sizes.
TEST_P(VulkanPerformanceCounterTest, MutableTexture2DArrayMipLevelsWithIncompatibleSizesNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2 * 3, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture1);
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, 4, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage3D(GL_TEXTURE_2D_ARRAY, 1, GL_RGBA, 2, 2, 3, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_3D, texture2);
    glTexImage3D(GL_TEXTURE_3D, 0, GL_RGBA, 1, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable cubemap texture is uploaded with appropriate mip level attributes.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCubemapCompatibleMipLevelsInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture1);
    for (size_t i = 0; i < 6; i++)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, 4, 4, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, mip0Color.data());
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 1, GL_RGBA, 2, 2, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, mip1Color.data());
    }
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable cubemap texture is not uploaded with no data or updates.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCubemapWithNoDataNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture1);
    for (size_t i = 0; i < 6; i++)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, 4, 4, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 1, GL_RGBA, 2, 2, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    }
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable cubemap texture is not uploaded with more than one update in a cube face.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCubemapMultipleUpdatesForOneFaceNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture1);
    for (size_t i = 0; i < 6; i++)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGBA, 4, 4, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 1, GL_RGBA, 2, 2, 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    }
    EXPECT_GL_NO_ERROR();
    for (size_t i = 0; i < 6; i++)
    {
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, 0, 0, 4, 4, GL_RGBA,
                        GL_UNSIGNED_BYTE, mip0Color.data());
        glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 1, 0, 0, 2, 2, GL_RGBA,
                        GL_UNSIGNED_BYTE, mip1Color.data());
    }
    glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 1, 1, 1, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                    GLColor::red.data());

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable cubemap texture is not uploaded if not complete.
TEST_P(VulkanPerformanceCounterTest, MutableTextureCubemapIncompleteInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_CUBE_MAP, texture1);
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGBA, 4, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 1, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());
    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that RGB texture should not break renderpass.
TEST_P(VulkanPerformanceCounterTest, SampleFromRGBTextureDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program);
    GLint textureLoc = glGetUniformLocation(program, essl1_shaders::Texture2DUniform());
    ASSERT_NE(-1, textureLoc);

    GLTexture textureRGBA;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureRGBA);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLTexture textureRGB;
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, textureRGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // First draw with textureRGBA which should start the renderpass
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glUniform1i(textureLoc, 0);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Next draw with textureRGB which should not end the renderpass
    glUniform1i(textureLoc, 1);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Tests that RGB texture should not break renderpass.
TEST_P(VulkanPerformanceCounterTest, RenderToRGBTextureDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorUniformLocation =
        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    GLTexture textureRGB;
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, textureRGB);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureRGB, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Draw into FBO
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);  // clear to green
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, 256, 256);
    glUniform4fv(colorUniformLocation, 1, GLColor::blue.toNormalizedVector().data());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Tests that changing a Texture's max level hits the descriptor set cache.
TEST_P(VulkanPerformanceCounterTest, ChangingMaxLevelHitsDescriptorCache)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLColor kInitialData[4] = {GLColor::red, GLColor::blue, GLColor::green, GLColor::yellow};

    // Step 1: Set up a simple mipped 2D Texture rendering loop.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
    glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, kInitialData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);

    auto quadVerts = GetQuadVertices();

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
                 GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Step 2: Change max level and draw.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedWriteDescriptorSetCount = getPerfCounters().writeDescriptorSets;

    // Step 3: Change max level back to original value and verify we hit the cache.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualWriteDescriptorSetCount = getPerfCounters().writeDescriptorSets;
    EXPECT_EQ(expectedWriteDescriptorSetCount, actualWriteDescriptorSetCount);
}

// Tests that two glCopyBufferSubData commands can share a barrier.
TEST_P(VulkanPerformanceCounterTest, IndependentBufferCopiesShareSingleBarrier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLint srcDataA[] = {1, 2, 3, 4};
    constexpr GLint srcDataB[] = {5, 6, 7, 8};

    // Step 1: Set up four buffers for two copies.
    GLBuffer srcA;
    glBindBuffer(GL_COPY_READ_BUFFER, srcA);
    // Note: We can't use GL_STATIC_COPY. Using STATIC will cause driver to allocate a host
    // invisible memory and issue a copyToBuffer, which will trigger outsideRenderPassCommandBuffer
    // flush when glCopyBufferSubData is called due to read after write. That will break the
    // expectations and cause test to fail.
    glBufferData(GL_COPY_READ_BUFFER, sizeof(srcDataA), srcDataA, GL_DYNAMIC_COPY);

    GLBuffer dstA;
    glBindBuffer(GL_COPY_WRITE_BUFFER, dstA);
    // Note: We can't use GL_STATIC_COPY. Using STATIC will cause driver to allocate a host
    // invisible memory and issue a copyToBuffer, which will trigger outsideRenderPassCommandBuffer
    // flush when glCopyBufferSubData is called due to write after write. That will break the
    // expectations and cause test to fail.
    glBufferData(GL_COPY_WRITE_BUFFER, sizeof(srcDataA[0]) * 2, nullptr, GL_DYNAMIC_COPY);

    GLBuffer srcB;
    glBindBuffer(GL_COPY_READ_BUFFER, srcB);
    glBufferData(GL_COPY_READ_BUFFER, sizeof(srcDataB), srcDataB, GL_DYNAMIC_COPY);

    GLBuffer dstB;
    glBindBuffer(GL_COPY_WRITE_BUFFER, dstB);
    glBufferData(GL_COPY_WRITE_BUFFER, sizeof(srcDataB[0]) * 2, nullptr, GL_DYNAMIC_COPY);

    // We expect that ANGLE generate zero additional command buffers.
    uint64_t expectedFlushCount = getPerfCounters().flushedOutsideRenderPassCommandBuffers;

    // Step 2: Do the two copies.
    glBindBuffer(GL_COPY_READ_BUFFER, srcA);
    glBindBuffer(GL_COPY_WRITE_BUFFER, dstA);
    glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, sizeof(srcDataB[0]), 0,
                        sizeof(srcDataA[0]) * 2);

    glBindBuffer(GL_COPY_READ_BUFFER, srcB);
    glBindBuffer(GL_COPY_WRITE_BUFFER, dstB);
    glCopyBufferSubData(GL_COPY_READ_BUFFER, GL_COPY_WRITE_BUFFER, sizeof(srcDataB[0]), 0,
                        sizeof(srcDataB[0]) * 2);

    ASSERT_GL_NO_ERROR();

    uint64_t actualFlushCount = getPerfCounters().flushedOutsideRenderPassCommandBuffers;
    EXPECT_EQ(expectedFlushCount, actualFlushCount);
}

// Tests that mutable cubemap array texture is uploaded with appropriate mip level attributes.
TEST_P(VulkanPerformanceCounterTest_ES31, MutableTextureCubemapArrayCompatibleMipLevelsInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_cube_map_array"));

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded + 1;

    std::vector<GLColor> mip0Color(4 * 4 * 6, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2 * 6, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texture1);
    glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA, 4, 4, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, GL_RGBA, 2, 2, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());

    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Tests that mutable cubemap array texture is not uploaded with different layer-faces.
TEST_P(VulkanPerformanceCounterTest_ES31, MutableTextureCubemapArrayDifferentLayerFacesNoInit)
{
    ANGLE_SKIP_TEST_IF(!hasMutableMipmapTextureUpload());
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_cube_map_array"));

    uint32_t expectedMutableTexturesUploaded = getPerfCounters().mutableTexturesUploaded;

    std::vector<GLColor> mip0Color(4 * 4 * 6, GLColor::red);
    std::vector<GLColor> mip1Color(2 * 2 * 12, GLColor::red);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, texture1);
    glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 0, GL_RGBA, 4, 4, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip0Color.data());
    glTexImage3D(GL_TEXTURE_CUBE_MAP_ARRAY, 1, GL_RGBA, 2, 2, 12, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 mip1Color.data());

    EXPECT_GL_NO_ERROR();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::green.data());
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(getPerfCounters().mutableTexturesUploaded, expectedMutableTexturesUploaded);
}

// Test resolving a multisampled texture with blit doesn't break the render pass so a subpass can be
// used
TEST_P(VulkanPerformanceCounterTest_ES31, MultisampleResolveWithBlit)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr int kSize = 16;
    glViewport(0, 0, kSize, kSize);

    GLFramebuffer msaaFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, texture);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kSize, kSize, false);
    ASSERT_GL_NO_ERROR();
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, texture,
                           0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    ANGLE_GL_PROGRAM(gradientProgram, essl31_shaders::vs::Passthrough(),
                     essl31_shaders::fs::RedGreenGradient());
    drawQuad(gradientProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();

    // Create another FBO to resolve the multisample buffer into.
    GLTexture resolveTexture;
    GLFramebuffer resolveFBO;
    glBindTexture(GL_TEXTURE_2D, resolveTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);
    EXPECT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

    glBindFramebuffer(GL_READ_FRAMEBUFFER, msaaFBO);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
    glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(getPerfCounters().resolveImageCommands, 0u);

    glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO);
    constexpr uint8_t kHalfPixelGradient = 256 / kSize / 2;
    EXPECT_PIXEL_NEAR(0, 0, kHalfPixelGradient, kHalfPixelGradient, 0, 255, 1.0);
    EXPECT_PIXEL_NEAR(kSize - 1, 0, 255 - kHalfPixelGradient, kHalfPixelGradient, 0, 255, 1.0);
    EXPECT_PIXEL_NEAR(0, kSize - 1, kHalfPixelGradient, 255 - kHalfPixelGradient, 0, 255, 1.0);
    EXPECT_PIXEL_NEAR(kSize - 1, kSize - 1, 255 - kHalfPixelGradient, 255 - kHalfPixelGradient, 0,
                      255, 1.0);
}

// Test resolving a multisampled texture with blit and then invalidate the msaa buffer
TEST_P(VulkanPerformanceCounterTest_ES31, ResolveToFBOWithInvalidate)
{
    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);
    expected.colorAttachmentResolves = getPerfCounters().colorAttachmentResolves + 1;

    constexpr int kWindowWidth  = 4;
    constexpr int kWindowHeight = 4;
    GLTexture resolveTexture;
    glBindTexture(GL_TEXTURE_2D, resolveTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWindowWidth, kWindowHeight, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLFramebuffer resolveFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture, 0);

    GLTexture msaaTexture;
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaaTexture);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kWindowWidth, kWindowHeight,
                              GL_FALSE);

    GLFramebuffer msaaFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msaaFBO,
                           0);
    ANGLE_GL_PROGRAM(redprogram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());

    drawQuad(redprogram, essl1_shaders::PositionAttrib(), 0.5f);
    // Resolve into FBO
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
    glBlitFramebuffer(0, 0, kWindowWidth, kWindowHeight, 0, 0, kWindowWidth, kWindowHeight,
                      GL_COLOR_BUFFER_BIT, GL_NEAREST);

    GLenum attachment = GL_COLOR_ATTACHMENT0;
    glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, &attachment);
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);

    // Top-left pixels should be all red.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_EQ(expected.colorAttachmentResolves, getPerfCounters().colorAttachmentResolves);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    ASSERT_GL_NO_ERROR();
}

// Test resolving different attachments of an FBO to separate FBOs then invalidate
TEST_P(VulkanPerformanceCounterTest_ES31, MultisampleResolveBothAttachments)
{
    enum class Invalidate
    {
        AfterEachResolve,
        AllAtEnd,
    };

    constexpr char kFS[] = R"(#version 300 es
precision highp float;

uniform vec4 value0;
uniform vec4 value2;

layout(location = 0) out vec4 color0;
layout(location = 2) out vec4 color2;

void main()
{
    color0 = value0;
    color2 = value2;
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    const GLint color0Loc = glGetUniformLocation(program, "value0");
    const GLint color1Loc = glGetUniformLocation(program, "value2");

    constexpr int kWidth  = 16;
    constexpr int kHeight = 20;
    glViewport(0, 0, kWidth, kHeight);

    GLTexture msaa0, msaa1;
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaa0);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kWidth, kHeight, false);
    glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msaa1);
    glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kWidth, kHeight, false);

    GLFramebuffer msaaFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE, msaa0,
                           0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D_MULTISAMPLE, msaa1,
                           0);
    ASSERT_GL_NO_ERROR();
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    GLenum bufs[3] = {GL_COLOR_ATTACHMENT0, GL_NONE, GL_COLOR_ATTACHMENT2};
    glDrawBuffers(3, bufs);

    // Create two resolve FBOs and textures. Use different texture levels and layers.
    GLTexture resolveTexture1;
    glBindTexture(GL_TEXTURE_2D, resolveTexture1);
    glTexStorage2D(GL_TEXTURE_2D, 3, GL_RGBA8, kWidth * 2, kHeight * 2);

    GLFramebuffer resolveFBO1;
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO1);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, resolveTexture1, 1);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    GLTexture resolveTexture2;
    glBindTexture(GL_TEXTURE_2D_ARRAY, resolveTexture2);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 4, GL_RGBA8, kWidth * 4, kHeight * 4, 5);

    GLFramebuffer resolveFBO2;
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO2);
    glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, resolveTexture2, 2, 3);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    auto test = [&](GLColor color0, GLColor color1, Invalidate invalidate) {
        const GLenum discards[] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT2};

        // Resolve attachments should be used and the MSAA attachments should be invalidated.
        // Only the resolve attachments should have Store.
        //
        // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+0, Stores+2, StoreNones+0)
        angle::VulkanPerfCounters expected;
        setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 0, 2, 0, &expected);
        expected.colorAttachmentResolves = getPerfCounters().colorAttachmentResolves + 2;

        glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);
        glUniform4fv(color0Loc, 1, color0.toNormalizedVector().data());
        glUniform4fv(color1Loc, 1, color1.toNormalizedVector().data());
        drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();

        // Resolve the first attachment
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO1);
        glReadBuffer(GL_COLOR_ATTACHMENT0);
        glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
                          GL_NEAREST);
        ASSERT_GL_NO_ERROR();

        if (invalidate == Invalidate::AfterEachResolve)
        {
            glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, discards);
        }

        // Resolve the second attachment
        glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO2);
        glReadBuffer(GL_COLOR_ATTACHMENT2);
        glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
                          GL_NEAREST);
        ASSERT_GL_NO_ERROR();

        if (invalidate == Invalidate::AfterEachResolve)
        {
            glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, ANGLE_UNSAFE_TODO(discards + 1));
        }
        else if (invalidate == Invalidate::AllAtEnd)
        {
            glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 2, discards);
        }

        glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO1);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, color0);
        glBindFramebuffer(GL_READ_FRAMEBUFFER, resolveFBO2);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, color1);
        ASSERT_GL_NO_ERROR();

        EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
        EXPECT_EQ(expected.colorAttachmentResolves, getPerfCounters().colorAttachmentResolves);
        EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    };

    test(GLColor::blue, GLColor::yellow, Invalidate::AfterEachResolve);
    test(GLColor::cyan, GLColor::magenta, Invalidate::AllAtEnd);
}

// Test resolving the depth/stencil attachment
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps_ES31, MultisampleDepthStencilResolve)
{
    ANGLE_SKIP_TEST_IF(!hasDepthStencilResolveThroughAttachment());

    constexpr int kWidth  = 24;
    constexpr int kHeight = 12;
    glViewport(0, 0, kWidth, kHeight);

    GLFramebuffer msaaFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);

    GLRenderbuffer depthStencil;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, kWidth, kHeight);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencil);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Create two resolve FBOs and textures. Use different texture levels and layers.
    GLTexture resolveTexture;
    glBindTexture(GL_TEXTURE_2D, resolveTexture);
    glTexStorage2D(GL_TEXTURE_2D, 4, GL_DEPTH24_STENCIL8, kWidth * 4, kHeight * 4);

    GLFramebuffer resolveFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, resolveFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
                           resolveTexture, 2);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    ANGLE_GL_PROGRAM(red, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::Red());

    // Resolve attachment should be used and the MSAA attachment should be invalidated.
    // Only the resolve attachment should have Store.
    //
    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    angle::VulkanPerfCounters expected;
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 1, 0, &expected);
    expected.depthAttachmentResolves   = getPerfCounters().depthAttachmentResolves + 1;
    expected.stencilAttachmentResolves = getPerfCounters().stencilAttachmentResolves + 1;

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_TRUE);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x55, 0xFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glStencilMask(0xFF);

    // Initialize the depth/stencil image
    glBindFramebuffer(GL_FRAMEBUFFER, msaaFBO);
    drawQuad(red, essl1_shaders::PositionAttrib(), 0.3f);
    ASSERT_GL_NO_ERROR();

    // Resolve depth and stencil, then verify the results
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, resolveFBO);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight,
                      GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT, GL_NEAREST);

    // Invalidate depth/stencil
    const GLenum discards[] = {GL_DEPTH_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, discards);

    // Break the render pass
    glFinish();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_EQ(expected.depthAttachmentResolves, getPerfCounters().depthAttachmentResolves);
    EXPECT_EQ(expected.stencilAttachmentResolves, getPerfCounters().stencilAttachmentResolves);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    ASSERT_GL_NO_ERROR();
}

// Ensures a read-only depth-stencil feedback loop works in a single RenderPass.
TEST_P(VulkanPerformanceCounterTest, ReadOnlyDepthStencilFeedbackLoopUsesSingleRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 4;

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    setupQuadVertexBuffer(0.5f, 1.0f);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // Set up a depth texture and fill it with an arbitrary initial value.
    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                 GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);

    GLFramebuffer depthAndColorFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthAndColorFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    GLFramebuffer depthOnlyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthOnlyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Draw to a first FBO to initialize the depth buffer.
    glBindFramebuffer(GL_FRAMEBUFFER, depthOnlyFBO);
    glEnable(GL_DEPTH_TEST);
    glUseProgram(redProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Start new RenderPass with depth write disabled and no loop.
    glBindFramebuffer(GL_FRAMEBUFFER, depthAndColorFBO);
    glDepthMask(false);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Now set up the read-only feedback loop.
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glUseProgram(texProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Tweak the bits to keep it read-only.
    glEnable(GL_DEPTH_TEST);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Render with just the depth attachment.
    glUseProgram(redProgram);
    glBindTexture(GL_TEXTURE_2D, 0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Rebind the depth texture.
    glUseProgram(texProgram);
    glDepthMask(GL_FALSE);
    glEnable(GL_DEPTH_TEST);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);

    // Do a final write to depth to make sure we can switch out of read-only mode.
    glBindTexture(GL_TEXTURE_2D, 0);
    glDepthMask(GL_TRUE);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
}

// Ensures clear color buffer and read-only depth-stencil works in a single RenderPass (as seen in
// gfxbench manhattan).
TEST_P(VulkanPerformanceCounterTest, ClearColorBufferAndReadOnlyDepthStencilUsesSingleRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 4;

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());

    setupQuadVertexBuffer(0.5f, 1.0f);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // Set up a depth texture and draw a quad to initialize it with known value.
    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                 GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);
    GLFramebuffer depthOnlyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthOnlyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_TRUE);
    glUseProgram(redProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Setup a color texture and FBO with color and read only depth attachment.
    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    GLFramebuffer depthAndColorFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthAndColorFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
    // First clear color buffer. This should not cause this render pass to switch to read only depth
    // stencil mode
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    // Now set up the read-only feedback loop.
    glDepthMask(GL_FALSE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glUseProgram(texProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Render with just the depth attachment.
    glUseProgram(redProgram);
    glBindTexture(GL_TEXTURE_2D, 0);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Rebind the depth texture.
    glUseProgram(texProgram);
    glDepthMask(GL_FALSE);
    glEnable(GL_DEPTH_TEST);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);

    // Do a final write to depth to make sure we can switch out of read-only mode.
    glBindTexture(GL_TEXTURE_2D, 0);
    glDepthMask(GL_TRUE);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
}

// Similar scenario as in ClearColorBufferAndReadOnlyDepthStencilUsesSingleRenderPass based on
// Manhattan, but involving queries that end up marking a render pass for closure.  This results in
// the switch to read-only depth/stencil mode to flush the render pass twice.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       QueryThenClearColorBufferAndReadOnlyDepthStencilUsesSingleRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 4;

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());

    setupQuadVertexBuffer(0.5f, 1.0f);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // Set up a depth texture and framebuffer.
    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                 GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Issue a draw call that writes to the depth image.  At the same time, end a query after the
    // draw call.  The end of query can mark the render pass for closure.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_TRUE);
    glUseProgram(redProgram);
    GLQuery query;
    glBeginQuery(GL_ANY_SAMPLES_PASSED, query);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glEndQuery(GL_ANY_SAMPLES_PASSED);
    ASSERT_GL_NO_ERROR();

    // Add a color texture to the FBO.  This changes the render pass such that glClear isn't
    // translated to vkCmdClearAttachments (which it would, if the color attachment was added
    // earlier).
    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    angle::VulkanPerfCounters expected;
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);

    // First clear the color buffer.  This leads to a deferred clear.
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    // Now set up the read-only feedback loop and issue a draw call.  The clear and draw should be
    // done in the same render pass.
    glDepthMask(GL_FALSE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glUseProgram(texProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
}

// Make sure depth/stencil clears followed by a switch to read-only mode still lets the color clears
// use loadOp=CLEAR optimally.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       SwitchToReadOnlyDepthStencilLeavesOtherAspectsOptimal)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 4;

    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());

    setupQuadVertexBuffer(0.5f, 1.0f);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // Set up a framebuffer.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexStorage2D(GL_TEXTURE_2D, 2, GL_DEPTH24_STENCIL8, kSize * 2, kSize * 2);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depthTexture,
                           1);

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Expect rpCount+1, color(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    // depth(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    angle::VulkanPerfCounters expected;
    setExpectedCountersForColorOps(getPerfCounters(), 0, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 0, 1, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 1, 0, &expected);

    // Clear all aspects.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Then issue a draw call where the depth aspect is in feedback loop.  Depth should be cleared
    // before the render pass, but the color and stencil clears should still be optimized as loadOp.
    glDepthMask(GL_FALSE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glUseProgram(texProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    glFinish();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Ensures an actual depth feedback loop (i.e, render and sample from same texture which is
// undefined behavior according to spec) works in a single RenderPass. This is adoptted from usage
// pattern from gangstar_vegas.
TEST_P(VulkanPerformanceCounterTest, DepthFeedbackLoopUsesSingleRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 4;

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    setupQuadVertexBuffer(0.5f, 1.0f);
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // Set up a depth texture and fill it with an arbitrary initial value.
    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                 GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glBindTexture(GL_TEXTURE_2D, 0);

    GLFramebuffer depthAndColorFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthAndColorFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    GLFramebuffer depthOnlyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthOnlyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Draw to a first FBO to initialize the depth buffer.
    glBindFramebuffer(GL_FRAMEBUFFER, depthOnlyFBO);
    glEnable(GL_DEPTH_TEST);
    glUseProgram(redProgram);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Start new RenderPass with depth write enabled and form the actual feedback loop.
    glBindFramebuffer(GL_FRAMEBUFFER, depthAndColorFBO);
    glDepthMask(true);
    glUseProgram(texProgram);
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Now set up the read-only feedback loop.
    glDepthMask(false);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);

    // Do a final write to depth to make sure we can switch out of read-only mode.
    glBindTexture(GL_TEXTURE_2D, 0);
    glDepthMask(GL_TRUE);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
}

// Tests that invalidate followed by masked draws results in no load and store.
//
// - Scenario: invalidate, mask color, draw
TEST_P(VulkanPerformanceCounterTest, ColorInvalidateMaskDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Invalidate
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);
    ASSERT_GL_NO_ERROR();

    // Mask color output
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass without color mask
    ++expected.renderPasses;
    ++expected.colorStoreOpStores;

    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Test glFenceSync followed by glInvalidateFramebuffer should still allow storeOp being optimized
// out.
TEST_P(VulkanPerformanceCounterTest, FenceSyncAndColorInvalidate)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 0, 0, 0, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Insert a fence which should trigger a deferred renderPass end
    GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

    // Invalidate FBO. This should still allow vulkan backend to optimize out the storeOp, even
    // though we just called glFenceSync .
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);
    ASSERT_GL_NO_ERROR();

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    glClientWaitSync(sync, GL_SYNC_FLUSH_COMMANDS_BIT, GL_TIMEOUT_IGNORED);
}
// Tests that invalidate followed by discarded draws results in no load and store.
//
// - Scenario: invalidate, rasterizer discard, draw
TEST_P(VulkanPerformanceCounterTest, ColorInvalidateDiscardDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Invalidate
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);
    ASSERT_GL_NO_ERROR();

    // Mask color output
    glEnable(GL_RASTERIZER_DISCARD);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass without color mask
    ++expected.renderPasses;
    ++expected.colorStoreOpStores;

    glDisable(GL_RASTERIZER_DISCARD);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that masked draws results in no load and store.
//
// - Scenario: mask color, draw
TEST_P(VulkanPerformanceCounterTest, ColorMaskedDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initialize the color buffer
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Mask color output
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass without color mask
    ++expected.renderPasses;
    ++expected.colorLoadOpLoads;
    ++expected.colorStoreOpStores;

    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that discarded draws results in no load and store.
//
// - Scenario: rasterizer discard, draw
TEST_P(VulkanPerformanceCounterTest, ColorDiscardDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initialize the color buffer
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Mask color output
    glEnable(GL_RASTERIZER_DISCARD);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass without color mask
    ++expected.renderPasses;
    ++expected.colorLoadOpLoads;
    ++expected.colorStoreOpStores;

    glDisable(GL_RASTERIZER_DISCARD);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Test that read-only color usage results in load but no store.
//
// - Scenario: mask color, framebuffer fetch draw
TEST_P(VulkanPerformanceCounterTest_ES31, ColorMaskedFramebufferFetchDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    GLBuffer buffer;
    const GLColor kClearColor(40, 70, 100, 150);
    maskedFramebufferFetchDraw(kClearColor, buffer);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_NEAR(0, 0, kClearColor, 1);

    maskedFramebufferFetchDrawVerify(kClearColor, buffer);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that clear after masked draws is optimized to use loadOp
//
// - Scenario: clear, mask color, draw, clear
TEST_P(VulkanPerformanceCounterTest, ColorClearMaskedDrawThenClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    // No vkCmdClearAttachments should be issued.
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    expected.colorClearAttachments = getPerfCounters().colorClearAttachments;

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Clear color first
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    // Mask color output
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Then clear color again.  This clear is moved up to loadOp, overriding the initial clear.
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glClearColor(0, 0, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.colorClearAttachments,
                                     getPerfCounters().colorClearAttachments);
}

// Test that clear of read-only color is not reordered with the draw.
//
// - Scenario: mask color, framebuffer fetch draw, clear
TEST_P(VulkanPerformanceCounterTest_ES31, ColorMaskedFramebufferFetchDrawThenClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    // vkCmdClearAttachments should be used for the second clear.
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    expected.colorClearAttachments = getPerfCounters().colorClearAttachments + 1;

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    GLBuffer buffer;
    const GLColor kClearColor(40, 70, 100, 150);
    maskedFramebufferFetchDraw(kClearColor, buffer);

    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    maskedFramebufferFetchDrawVerify(kClearColor, buffer);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.colorClearAttachments,
                                     getPerfCounters().colorClearAttachments);
}

// Test that masked draw after a framebuffer fetch render pass doesn't load color.
//
// - Scenario: framebuffer fetch render pass, mask color, normal draw
TEST_P(VulkanPerformanceCounterTest_ES31, FramebufferFetchRenderPassThenColorMaskedDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    GLBuffer buffer;
    const GLColor kClearColor(40, 70, 100, 150);
    maskedFramebufferFetchDraw(kClearColor, buffer);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_NEAR(0, 0, kClearColor, 1);

    maskedFramebufferFetchDrawVerify(kClearColor, buffer);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Start another render pass, and don't use framebuffer fetch.  Color is masked, so it should be
    // neither loaded nor stored.

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);

    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_NEAR(0, 0, kClearColor, 1);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that clear after unused depth/stencil is optimized to use loadOp
//
// - Scenario: disable depth/stencil, draw, clear
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilMaskedDrawThenClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    // disallowMixedDepthStencilLoadOpNoneAndLoad makes it quite complicated to set expectations
    // correct.
    ANGLE_SKIP_TEST_IF(hasSupportsTileMemoryOrSimulation() &&
                       hasDisallowMixedDepthStencilLoadOpNoneAndLoad());

    // This optimization is not implemented when this workaround is in effect.
    ANGLE_SKIP_TEST_IF(hasPreferDrawOverClearAttachments());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 1, 0, &expected);

    // No vkCmdClearAttachments should be issued.
    expected.depthClearAttachments   = getPerfCounters().depthClearAttachments;
    expected.stencilClearAttachments = getPerfCounters().stencilClearAttachments;

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Disable depth/stencil
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Issue a draw call
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    // Clear depth/stencil
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    if (hasSupportsTileMemoryOrSimulation())
    {
        // If tile memory is used, mid-renderPass clear will use draw instead of clear.
        expected.depthLoadOpClears   = 0;
        expected.stencilLoadOpClears = 0;
    }
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.depthClearAttachments,
                                     getPerfCounters().depthClearAttachments);
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.stencilClearAttachments,
                                     getPerfCounters().depthClearAttachments);
}

// Tests that depth compare function change, get correct loadop for depth buffer
//
// - Scenario: depth test enabled, depth write mask = 0,
//   clear depth, draw red quad with compare function always,
//   and then  draw green quad with compare function less equal
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthFunctionDynamicChangeLoadOp)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // This optimization is not implemented when this workaround is in effect.
    ANGLE_SKIP_TEST_IF(hasPreferDrawOverClearAttachments());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForColorDepthOpsTest(&framebuffer, &texture, &renderbuffer);

    // Clear color and depth.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // No depth write
    glDepthMask(GL_FALSE);
    // Depth function always
    glDepthFunc(GL_ALWAYS);

    // Draw read quad.
    ANGLE_GL_PROGRAM(redprogram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redprogram, essl1_shaders::PositionAttrib(), 0.5f);

    // Depth function switch to less equal
    glDepthFunc(GL_LEQUAL);

    // Draw green quad.
    ANGLE_GL_PROGRAM(greenprogram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(greenprogram, essl1_shaders::PositionAttrib(), 0.7f);

    GLenum attachments = GL_DEPTH_ATTACHMENT;
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &attachments);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and check how many clears were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.depthLoadOpClears,
                                     getPerfCounters().depthLoadOpClears);
}

// Tests that common PUBG MOBILE case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, disable, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidateDisableDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Draw (since disabled, shouldn't change result)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that alternative PUBG MOBILE case does not break render pass, and that counts are correct:
//
// - Scenario: disable, invalidate, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DisableInvalidateDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Invalidate (storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since disabled, shouldn't change result)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: disable, draw, invalidate, enable
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DisableDrawInvalidateEnable)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Note: setupClearAndDrawForDepthStencilOpsTest() did an enable and draw

    // Disable (since not invalidated, shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Draw (since not invalidated, shouldn't change result)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    // Note: The above enable calls will be ignored, since no drawing was done to force the enable
    // dirty bit to be processed

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass by reading back a pixel.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    setAndIncrementDepthStencilStoreCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that common TRex case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidate)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    setAndIncrementDepthStencilStoreCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Similar to Invalidate, but uses glInvalidateSubFramebuffer such that the given area covers the
// whole framebuffer.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidateSub)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateSubFramebuffer(GL_FRAMEBUFFER, 2, discards, -100, -100, kOpsTestSize + 200,
                               kOpsTestSize + 200);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    setAndIncrementDepthStencilStoreCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Similar to InvalidateSub, but uses glInvalidateSubFramebuffer such that the given area does NOT
// covers the whole framebuffer.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilPartialInvalidateSub)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    // disallowMixedDepthStencilLoadOpNoneAndLoad makes it quite complicated to set expectations
    // correct.
    ANGLE_SKIP_TEST_IF(hasSupportsTileMemoryOrSimulation() &&
                       hasDisallowMixedDepthStencilLoadOpNoneAndLoad());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 1, 0, &expected);

    // Create the framebuffer and make sure depth/stencil have valid contents.
    ANGLE_GL_PROGRAM(drawRed, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&drawRed, &framebuffer, &texture, &renderbuffer, true);

    // Break the render pass so depth/stencil values are stored.
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start a new render pass that is scissored.  Depth/stencil should be loaded.  The draw call is
    // followed by an invalidate, so store shouldn't happen.

    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 0, 0, &expected);

    glEnable(GL_SCISSOR_TEST);
    glScissor(kOpsTestSize / 8, kOpsTestSize / 4, kOpsTestSize / 2, kOpsTestSize / 3);

    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glStencilFunc(GL_ALWAYS, 0x55, 0xFF);
    glDepthFunc(GL_ALWAYS);

    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateSubFramebuffer(GL_FRAMEBUFFER, 2, discards, kOpsTestSize / 8, kOpsTestSize / 8,
                               7 * kOpsTestSize / 8, 7 * kOpsTestSize / 8);

    // Break the render pass so depth/stencil values are discarded.
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::green);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start another render pass without scissor.  Because parts of the framebuffer attachments were
    // not invalidated, depth/stencil should be loaded.

    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 1, 0, &expected);

    glDisable(GL_SCISSOR_TEST);

    ANGLE_GL_PROGRAM(drawBlue, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Verify results
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidateDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, draw, disable
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidateDrawDisable)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // http://anglebug.com/40096809
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsVulkan());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    // Note: this draw is just so that the disable dirty bits will be processed
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and then check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 1, 1, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, disable, draw, enable
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDisableDrawEnable)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Draw (since disabled, shouldn't change result)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    // Note: The above enable calls will be ignored, since no drawing was done to force the enable
    // dirty bit to be processed

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    setAndIncrementDepthStencilStoreCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, disable, draw, enable, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDisableDrawEnableDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    // disallowMixedDepthStencilLoadOpNoneAndLoad makes it quite complicated to set expectations
    // correct.
    ANGLE_SKIP_TEST_IF(hasSupportsTileMemoryOrSimulation() &&
                       hasDisallowMixedDepthStencilLoadOpNoneAndLoad());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 1, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Draw (since disabled, shouldn't change result)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and then check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 1, 1, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, draw, disable, enable
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDrawDisableEnable)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    // Note: this draw is just so that the disable dirty bits will be processed
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);
    // Note: The above enable calls will be ignored, since no drawing was done to force the enable
    // dirty bit to be processed

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Break the render pass and then check how many loads and stores were actually done
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    // setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, draw, disable, enable, invalidate
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDrawDisableEnableInvalidate)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    // Note: this draw is just so that the disable dirty bits will be processed
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    setAndIncrementDepthStencilStoreCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, draw, disable, enable, invalidate, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDrawDisableEnableInvalidateDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    // Note: this draw is just so that the disable dirty bits will be processed
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    setAndIncrementDepthStencilStoreCountersForOpsTest(getPerfCounters(), 1, 0, &expected);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that another common (dEQP) case does not break render pass, and that counts are correct:
//
// - Scenario: invalidate, disable, enable, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDisableEnableDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Execute the scenario that this test is for:

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Disable (shouldn't change result)
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    // Note: this draw is just so that the disable dirty bits will be processed
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable (shouldn't change result)
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);

    // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Ensure that the render pass wasn't broken
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that an in renderpass clear after invalidate keeps content stored.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidateAndClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    // Clear should vkCmdClearAttachments
    expected.depthClearAttachments = getPerfCounters().depthClearAttachments + 1;

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, false);

    // Disable depth test but with depth mask enabled so that clear should still work.
    glDisable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Do in-renderpass clear. This should result in StoreOp=STORE; mContentDefined = true.
    glClearDepthf(1.0f);
    glClear(GL_DEPTH_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
    if (getPerfCounters().fallbackFromTileMemory > 0)
    {
        // Mid-renderPass clear will use draw instead of clearAttachments
        expected.depthClearAttachments = 0;
    }
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.depthClearAttachments,
                                     getPerfCounters().depthClearAttachments);

    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    // Bind FBO again and try to use the depth buffer without clear. This should result in
    // loadOp=LOAD and StoreOP=STORE
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glDisable(GL_STENCIL_TEST);
    ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    // Should pass depth test: (0.5+1.0)/2.0=0.75 < 1.0
    drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::blue);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that the draw path for clear after invalidate and disabling depth/stencil test keeps
// content stored.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthStencilInvalidateAndMaskedClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    // disallowMixedDepthStencilLoadOpNoneAndLoad makes it quite complicated to set expectations
    // correct.
    ANGLE_SKIP_TEST_IF(hasSupportsTileMemoryOrSimulation() &&
                       hasDisallowMixedDepthStencilLoadOpNoneAndLoad());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 1, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer, true);

    // Invalidate (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Disable depth/stencil test but make stencil masked
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);
    glDepthMask(GL_TRUE);
    glStencilMask(0xF0);

    // Enable scissor for the draw path to be taken.
    glEnable(GL_SCISSOR_TEST);
    glScissor(kOpsTestSize / 4, kOpsTestSize / 4, kOpsTestSize / 2, kOpsTestSize / 2);

    // Do in-renderpass clear. This should result in StoreOp=STORE
    glClearDepthf(1.0f);
    glClearStencil(0x55);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1)
    // Note that depth write is enabled, while stencil is disabled.
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 0, 1, &expected);

    // Bind FBO again and try to use the depth buffer without clear. This should result in
    // loadOp=LOAD and StoreOP=STORE
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x50, 0xF0);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);
    ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(blueProgram, essl1_shaders::PositionAttrib(), 0.95f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::blue);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that the renderpass is using depthFunc(GL_ALWAYS) and depthMask(GL_FALSE), it should not
// load or store depth value.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthFuncALWAYSWithDepthMaskDisabledShouldNotLoadStore)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    // disallowMixedDepthStencilLoadOpNoneAndLoad makes it quite complicated to set expectations
    // correct.
    ANGLE_SKIP_TEST_IF(hasSupportsTileMemoryOrSimulation() &&
                       hasDisallowMixedDepthStencilLoadOpNoneAndLoad());

    angle::VulkanPerfCounters expected;

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);
    if (hasDisallowMixedDepthStencilLoadOpNoneAndLoad())
    {
        // stencil(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1)
        setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 0, 1, &expected);
    }
    else
    {
        // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
        setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);
    }
    // Initialize the buffers with known value
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_ALWAYS);
    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.95f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::red);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1),
    //                 stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);
    glDepthFunc(GL_ALWAYS);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.95f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that the renderpass is using depthFunc(GL_ALWAYS) and depthMask(GL_FALSE) and draw. Then it
// followed by glClear, it should not load or store depth value.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthFuncALWAYSWithDepthMaskDisabledThenClearShouldNotLoadStore)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    // disallowMixedDepthStencilLoadOpNoneAndLoad makes it quite complicated to set expectations
    // correct.
    ANGLE_SKIP_TEST_IF(hasDisallowMixedDepthStencilLoadOpNoneAndLoad());
    // This test is tricky to get expectations right with combination of nones and tile memory
    // support.
    ANGLE_SKIP_TEST_IF(hasSupportsTileMemoryOrSimulation() && !hasLoadOpNoneSupport());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initialize the buffers with known value
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_ALWAYS);
    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.95f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::red);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    if (hasPreferDrawOverClearAttachments())
    {
        // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
        setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    }
    else
    {
        // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
        setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    }
    // Expect stencil(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 1, 0, &expected);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);
    glDepthFunc(GL_ALWAYS);
    glEnable(GL_STENCIL_TEST);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.95f);
    glDepthMask(GL_TRUE);
    glClearDepthf(1);
    glClearStencil(0);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glDepthFunc(GL_LEQUAL);
    glDepthMask(GL_FALSE);
    glEnable(GL_STENCIL_TEST);
    glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, 0, 255);
    glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilFuncSeparate(GL_BACK, GL_ALWAYS, 0, 255);
    glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(255);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.9f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that the renderpass is using depthFunc(GL_NEVER) and depthMask(GL_FALSE), it should not
// load or store depth value.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthFuncNEVERWithDepthMaskDisabledShouldNotLoadStore)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initialize the buffers with known value
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_ALWAYS);
    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.95f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::red);

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1),
    //                 stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_FALSE);
    glDepthFunc(GL_NEVER);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.95f);
    EXPECT_PIXEL_COLOR_EQ(kOpsTestSize / 2, kOpsTestSize / 2, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests whether depth-stencil ContentDefined will be correct when:
//
// - Scenario: invalidate, detach D/S texture and modify it, attach D/S texture, draw with blend
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDetachModifyTexAttachDrawWithBlend)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);

    GLTexture depthStencilTexture;
    glBindTexture(GL_TEXTURE_2D, depthStencilTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 2, 2, 0, GL_DEPTH_STENCIL,
                 GL_UNSIGNED_INT_24_8, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
                           depthStencilTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Clear and draw with depth-stencil enabled
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_LEQUAL);
    glClearDepthf(0.99f);
    glEnable(GL_STENCIL_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Invalidate depth & stencil (should result: in storeOp = DONT_CARE; mContentDefined = false)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Check for the expected number of render passes, expected color, and other expected counters
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Detach depth-stencil attachment
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Modify depth-stencil
    constexpr uint32_t kDepthStencilInitialValue = 0xafffff00;
    uint32_t depthStencilData[4] = {kDepthStencilInitialValue, kDepthStencilInitialValue,
                                    kDepthStencilInitialValue, kDepthStencilInitialValue};
    glBindTexture(GL_TEXTURE_2D, depthStencilTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, 2, 2, 0, GL_DEPTH_STENCIL,
                 GL_UNSIGNED_INT_24_8, depthStencilData);

    // Re-attach depth-stencil
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
                           depthStencilTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Draw again, showing that the modified depth-stencil value prevents a new color value
    //
    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1)
    // Note that depth write is enabled, while stencil is disabled.
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 0, 1, &expected);
    drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    // Check for the expected number of render passes, expected color, and other expected counters
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Draw again, using a different depth value, so that the drawing takes place
    //
    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 0, 1, &expected);
    drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.2f);
    ASSERT_GL_NO_ERROR();
    // Check for the expected number of render passes, expected color, and other expected counters
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that a GLRenderbuffer can be deleted before the render pass ends, and that everything
// still works.
//
// - Scenario: invalidate
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       DepthStencilInvalidateDrawAndDeleteRenderbuffer)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    GLFramebuffer framebuffer;
    GLTexture texture;
    {
        // Declare the RAII-based GLRenderbuffer object within this set of curly braces, so that it
        // will be deleted early (at the close-curly-brace)
        GLRenderbuffer renderbuffer;
        setupClearAndDrawForDepthStencilOpsTest(&program, &framebuffer, &texture, &renderbuffer,
                                                false);

        // Invalidate (storeOp = DONT_CARE; mContentDefined = false)
        const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
        glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
        ASSERT_GL_NO_ERROR();

        // Draw (since enabled, should result: in storeOp = STORE; mContentDefined = true)
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();

        // Ensure that the render pass wasn't broken
        EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    }

    // The renderbuffer should now be deleted.

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Start and end another render pass, to check that the load ops are as expected
    setAndIncrementDepthStencilLoadCountersForOpsTest(getPerfCounters(), 0, 0, &expected);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    swapBuffers();
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
}

// Test that disabling color buffer after clear continues to use loadOp for it.
//
// - Scenario: clear color and depth, disable color, draw, enable color, draw
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps_ES31,
       ColorDisableThenDrawThenEnableThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForColorDepthOpsTest(&framebuffer, &texture, &renderbuffer);

    // Expected:
    //   rpCount+1,
    //   depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    //   color(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 0, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);

    // Clear color and depth first
    glClearColor(1, 0, 0, 1);
    glClearDepthf(0.123);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Disable color output
    GLenum drawBuffers[] = {GL_NONE};
    glDrawBuffers(1, drawBuffers);

    // Issue a draw call, only affecting depth
    glDepthFunc(GL_ALWAYS);
    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.75f);

    // Enable color output
    drawBuffers[0] = GL_COLOR_ATTACHMENT0;
    glDrawBuffers(1, drawBuffers);

    // Issue another draw call, verifying depth simultaneously
    glDepthFunc(GL_LESS);
    ANGLE_GL_PROGRAM(drawBlue, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), 0.74f);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Verify results and check how many loads and stores were actually done.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
    ASSERT_GL_NO_ERROR();
}

// Tests that even if the app clears depth, it should be invalidated if there is no read.
TEST_P(VulkanPerformanceCounterTest, SwapShouldInvalidateDepthAfterClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());

    // Clear depth.
    glClear(GL_DEPTH_BUFFER_BIT);

    // Ensure we never read from depth.
    glDisable(GL_DEPTH_TEST);

    // Do one draw, then swap.
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedDepthClears = getPerfCounters().depthLoadOpClears;

    swapBuffers();

    uint64_t actualDepthClears = getPerfCounters().depthLoadOpClears;
    EXPECT_EQ(expectedDepthClears, actualDepthClears);
}

// Tests that masked color clears don't break the RP.
TEST_P(VulkanPerformanceCounterTest, MaskedColorClearDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Mask color channels and clear the framebuffer multiple times.
    glClearColor(0.25f, 0.25f, 0.25f, 0.25f);
    glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT);

    glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
    glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT);

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT);

    glClearColor(0.75f, 0.75f, 0.75f, 0.75f);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
    glClear(GL_COLOR_BUFFER_BIT);

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);

    EXPECT_PIXEL_NEAR(0, 0, 63, 127, 255, 191, 1);
}

// Tests that masked color/depth/stencil clears don't break the RP.
TEST_P(VulkanPerformanceCounterTest, MaskedClearDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 64;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer renderbuffer;
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              renderbuffer);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorUniformLocation =
        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    // Clear the framebuffer with a draw call to start a render pass.
    glViewport(0, 0, kSize, kSize);
    glDepthFunc(GL_ALWAYS);
    glStencilFunc(GL_ALWAYS, 0x55, 0xFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 1.0f);

    // Issue a masked clear.
    glClearColor(0.25f, 1.0f, 0.25f, 1.25f);
    glClearDepthf(0.0f);
    glClearStencil(0x3F);
    glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
    glStencilMask(0xF0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Make sure the render pass wasn't broken.
    EXPECT_EQ(expectedRenderPassCount, getPerfCounters().renderPasses);

    // Verify that clear was done correctly.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::yellow);

    glDisable(GL_SCISSOR_TEST);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glStencilMask(0xFF);

    // Make sure depth = 0.0f, stencil = 0x35
    glDepthFunc(GL_GREATER);
    glStencilFunc(GL_EQUAL, 0x35, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.05f);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::blue);
}

// Tests that clear followed by scissored draw uses loadOp to clear.
TEST_P(VulkanPerformanceCounterTest, ClearThenScissoredDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
    uint64_t expectedDepthClears     = getPerfCounters().depthLoadOpClears + 1;
    uint64_t expectedStencilClears   = getPerfCounters().stencilLoadOpClears + 1;

    constexpr GLsizei kSize = 64;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer renderbuffer;
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              renderbuffer);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Clear depth/stencil
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(1.0f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Issue a scissored draw call, expecting depth/stencil to be 1.0 and 0x55.
    glViewport(0, 0, kSize, kSize);
    glScissor(0, 0, kSize / 2, kSize);
    glEnable(GL_SCISSOR_TEST);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);

    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass.
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glCopyTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 0, 0, kSize, kSize, 0);
    ASSERT_GL_NO_ERROR();

    // Make sure a single render pass was used and depth/stencil clear used loadOp=CLEAR.
    EXPECT_EQ(expectedRenderPassCount, getPerfCounters().renderPasses);
    EXPECT_EQ(expectedDepthClears, getPerfCounters().depthLoadOpClears);
    EXPECT_EQ(expectedStencilClears, getPerfCounters().stencilLoadOpClears);

    // Verify correctness.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2 - 1, 0, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2 - 1, kSize - 1, GLColor::green);

    EXPECT_PIXEL_COLOR_EQ(kSize / 2, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::red);
}

// Tests that scissored clears don't break the RP.
TEST_P(VulkanPerformanceCounterTest, ScissoredClearDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 64;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer renderbuffer;
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              renderbuffer);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_STENCIL_TEST);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorUniformLocation =
        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    // Clear the framebuffer with a draw call to start a render pass.
    glViewport(0, 0, kSize, kSize);
    glDepthFunc(GL_ALWAYS);
    glStencilFunc(GL_ALWAYS, 0x55, 0xFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 1.0f);

    // Issue a scissored clear.
    glEnable(GL_SCISSOR_TEST);
    glScissor(kSize / 4, kSize / 4, kSize / 2, kSize / 2);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClearDepthf(0.0f);
    glClearStencil(0x3F);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Make sure the render pass wasn't broken.
    EXPECT_EQ(expectedRenderPassCount, getPerfCounters().renderPasses);

    // Verify that clear was done correctly.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::red);

    EXPECT_PIXEL_COLOR_EQ(kSize / 4, kSize / 4, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(3 * kSize / 4 - 1, kSize / 4, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 4, 3 * kSize / 4 - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(3 * kSize / 4 - 1, 3 * kSize / 4 - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::green);

    glDisable(GL_SCISSOR_TEST);

    // Make sure the border has depth = 1.0f, stencil = 0x55
    glDepthFunc(GL_LESS);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Make sure the center has depth = 0.0f, stencil = 0x3F
    glDepthFunc(GL_GREATER);
    glStencilFunc(GL_EQUAL, 0x3F, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.05f);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::blue);

    EXPECT_PIXEL_COLOR_EQ(kSize / 4, kSize / 4, GLColor::magenta);
    EXPECT_PIXEL_COLOR_EQ(3 * kSize / 4 - 1, kSize / 4, GLColor::magenta);
    EXPECT_PIXEL_COLOR_EQ(kSize / 4, 3 * kSize / 4 - 1, GLColor::magenta);
    EXPECT_PIXEL_COLOR_EQ(3 * kSize / 4 - 1, 3 * kSize / 4 - 1, GLColor::magenta);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::magenta);
}

// Tests that draw buffer change with all color channel mask off should not break renderpass
TEST_P(VulkanPerformanceCounterTest, DrawbufferChangeWithAllColorMaskDisabled)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorUniformLocation =
        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    GLTexture textureRGBA;
    glBindTexture(GL_TEXTURE_2D, textureRGBA);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 64, 64, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    GLTexture textureDepth;
    glBindTexture(GL_TEXTURE_2D, textureDepth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, 64, 64, 0, GL_DEPTH_COMPONENT,
                 GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureRGBA, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, textureDepth, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Draw into FBO
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);  // clear to green
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glViewport(0, 0, 256, 256);
    glUniform4fv(colorUniformLocation, 1, GLColor::blue.toNormalizedVector().data());
    GLenum glDrawBuffers_bufs_1[] = {GL_COLOR_ATTACHMENT0};
    glDrawBuffers(1, glDrawBuffers_bufs_1);
    glEnable(GL_DEPTH_TEST);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    // Change draw buffer state and color mask
    GLenum glDrawBuffers_bufs_0[] = {GL_NONE};
    glDrawBuffers(1, glDrawBuffers_bufs_0);
    glColorMask(false, false, false, false);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.6f);
    // Change back draw buffer state and color mask
    glDrawBuffers(1, glDrawBuffers_bufs_1);
    glColorMask(true, true, true, true);
    glUniform4fv(colorUniformLocation, 1, GLColor::red.toNormalizedVector().data());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.7f);

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Tests the optimization that a glFlush call issued inside a renderpass will be skipped.
TEST_P(VulkanPerformanceCounterTest, InRenderpassFlushShouldNotBreakRenderpass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 256, 256, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    glFlush();
    ANGLE_GL_PROGRAM(greenProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(greenProgram, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Tests switch from query enabled draw to query disabled draw should break renderpass (so that wait
// for query result will be available sooner).
TEST_P(VulkanPerformanceCounterTest,
       SwitchFromQueryEnabledDrawToQueryDisabledDrawShouldBreakRenderpass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());

    uint64_t renderPassCountBefore = getPerfCounters().renderPasses;

    GLQueryEXT query1, query2;
    // Draw inside query
    glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query1);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f);
    glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
    glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query2);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f);
    glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
    // Draw outside his query
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.8f, 0.5f);

    GLuint results[2];
    // will block waiting for result
    glGetQueryObjectuivEXT(query1, GL_QUERY_RESULT_EXT, &results[0]);
    glGetQueryObjectuivEXT(query2, GL_QUERY_RESULT_EXT, &results[1]);
    EXPECT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses - renderPassCountBefore;
    if (hasSupportsTileMemoryOrSimulation() ||
        !isFeatureEnabled(Feature::PreferSubmitOnAnySamplesPassedQueryEnd))
    {
        // When tile memory is used we may not submit if depthStencil has valid data.
        EXPECT_EQ(1ul, actualRenderPassCount);
    }
    else
    {
        EXPECT_EQ(2ul, actualRenderPassCount);
    }
}

// Tests that depth/stencil texture clear/load works correctly.
TEST_P(VulkanPerformanceCounterTest, DepthStencilTextureClearAndLoad)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // TODO: http://anglebug.com/42263870 Flaky test
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());

    uint64_t expectedDepthClearCount   = getPerfCounters().depthLoadOpClears + 1;
    uint64_t expectedDepthLoadCount    = getPerfCounters().depthLoadOpLoads + 3;
    uint64_t expectedStencilClearCount = getPerfCounters().stencilLoadOpClears + 1;
    uint64_t expectedStencilLoadCount  = getPerfCounters().stencilLoadOpLoads + 3;

    constexpr GLsizei kSize = 6;

    // Create framebuffer to draw into, with both color and depth attachments.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLTexture depth;
    glBindTexture(GL_TEXTURE_2D, depth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, kSize, kSize, 0, GL_DEPTH_STENCIL,
                 GL_UNSIGNED_INT_24_8_OES, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depth, 0);
    ASSERT_GL_NO_ERROR();

    // Set up texture for copy operation that breaks the render pass
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set viewport and clear depth/stencil
    glViewport(0, 0, kSize, kSize);
    glClearDepthf(1);
    glClearStencil(0x55);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // If depth is not cleared to 1, rendering would fail.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glDepthMask(GL_FALSE);

    // If stencil is not clear to 0x55, rendering would fail.
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw red
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw green
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw blue
    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw yellow
    glUniform4f(colorUniformLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Verify the counters
    EXPECT_EQ(getPerfCounters().depthLoadOpClears, expectedDepthClearCount);
    EXPECT_EQ(getPerfCounters().depthLoadOpLoads, expectedDepthLoadCount);
    EXPECT_EQ(getPerfCounters().stencilLoadOpClears, expectedStencilClearCount);
    EXPECT_EQ(getPerfCounters().stencilLoadOpLoads, expectedStencilLoadCount);

    // Verify that copies were done correctly.
    GLFramebuffer verifyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copyTex, 0);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, 0, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(0, kSize / 2, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::yellow);
}

// Tests that multisampled-render-to-texture depth/stencil textures don't ever load data.
TEST_P(VulkanPerformanceCounterTest, RenderToTextureDepthStencilTextureShouldNotLoad)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // http://anglebug.com/42263651
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());

    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture2"));

    uint64_t expectedDepthClearCount   = getPerfCounters().depthLoadOpClears + 1;
    uint64_t expectedDepthLoadCount    = getPerfCounters().depthLoadOpLoads;
    uint64_t expectedStencilClearCount = getPerfCounters().stencilLoadOpClears + 1;
    uint64_t expectedStencilLoadCount  = getPerfCounters().stencilLoadOpLoads;

    constexpr GLsizei kSize = 6;

    // Create multisampled framebuffer to draw into, with both color and depth attachments.
    GLTexture colorMS;
    glBindTexture(GL_TEXTURE_2D, colorMS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLTexture depthMS;
    glBindTexture(GL_TEXTURE_2D, depthMS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, kSize, kSize, 0, GL_DEPTH_STENCIL,
                 GL_UNSIGNED_INT_24_8_OES, nullptr);

    GLFramebuffer fboMS;
    glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                         colorMS, 0, 4);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D,
                                         depthMS, 0, 4);
    ASSERT_GL_NO_ERROR();

    // Set up texture for copy operation that breaks the render pass
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set viewport and clear depth
    glViewport(0, 0, kSize, kSize);
    glClearDepthf(1);
    glClearStencil(0x55);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // If depth is not cleared to 1, rendering would fail.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    // If stencil is not clear to 0x55, rendering would fail.
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw red
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw green
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw blue
    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw yellow
    glUniform4f(colorUniformLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Verify the counters
    EXPECT_EQ(getPerfCounters().depthLoadOpClears, expectedDepthClearCount);
    EXPECT_EQ(getPerfCounters().depthLoadOpLoads, expectedDepthLoadCount);
    EXPECT_EQ(getPerfCounters().stencilLoadOpClears, expectedStencilClearCount);
    EXPECT_EQ(getPerfCounters().stencilLoadOpLoads, expectedStencilLoadCount);

    // Verify that copies were done correctly.  Only the first copy can be verified because the
    // contents of the depth/stencil buffer is undefined after the first render pass break, meaning
    // it is unknown whether the three subsequent draw calls passed the depth or stencil tests.
    GLFramebuffer verifyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copyTex, 0);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2 - 1, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(0, kSize / 2 - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2 - 1, kSize / 2 - 1, GLColor::red);
}

// Tests that multisampled-render-to-texture depth/stencil renderbuffers don't ever load
// depth/stencil data.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       RenderToTextureDepthStencilRenderbufferShouldNotLoad)
{
    // http://anglebug.com/42263651
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
    // http://anglebug.com/42263920
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsVulkan());

    // http://crbug.com/1134286
    ANGLE_SKIP_TEST_IF(IsWindows7() && IsNVIDIA() && IsVulkan());

    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // This test creates 4 render passes. In the first render pass, color, depth and stencil are
    // cleared.  In the following render passes, they must be loaded.  However, given that the
    // attachments are multisampled-render-to-texture, loads are done through an unresolve
    // operation.  All 4 render passes resolve the attachments.

    // Expect rpCount+4, depth(Clears+1, Loads+3, LoadNones+0, Stores+3, StoreNones+0),
    // stencil(Clears+1, Loads+3, LoadNones+0, Stores+3, StoreNones+0). Note that the Loads and
    // Stores are from the resolve attachments.
    setExpectedCountersForDepthOps(getPerfCounters(), 4, 1, 3, 0, 3, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 3, 0, 3, 0, &expected);

    // Additionally, expect 4 resolves and 3 unresolves.
    setExpectedCountersForUnresolveResolveTest(getPerfCounters(), 3, 3, 3, 4, 4, 4, &expected);
    adjustExpectedCountersForDynamicRenderingMSRTTEmulationUnresolve(3, 3, 3, true, true,
                                                                     &expected);

    constexpr GLsizei kSize = 6;

    // Create multisampled framebuffer to draw into, with both color and depth attachments.
    GLTexture colorMS;
    glBindTexture(GL_TEXTURE_2D, colorMS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer depthStencilMS;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencilMS);
    glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer fboMS;
    glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                         colorMS, 0, 4);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencilMS);
    ASSERT_GL_NO_ERROR();

    // Set up texture for copy operation that breaks the render pass
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set viewport and clear color, depth and stencil
    glViewport(0, 0, kSize, kSize);
    glClearDepthf(1);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // If depth is not cleared to 1, rendering would fail.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    // If stencil is not clear to 0x55, rendering would fail.
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw red
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.75f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw green
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw blue
    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.25f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw yellow
    glUniform4f(colorUniformLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Verify the counters
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_COUNTERS_FOR_UNRESOLVE_RESOLVE_TEST(getPerfCounters(), expected);

    // Verify that copies were done correctly.
    GLFramebuffer verifyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, copyTex, 0);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, 0, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(0, kSize / 2, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::yellow);
}

// Tests counters when multisampled-render-to-texture color/depth/stencil renderbuffers are
// invalidated.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, RenderToTextureInvalidate)
{
    // http://anglebug.com/42263651
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());

    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // This test creates 4 render passes. In the first render pass, color, depth and stencil are
    // cleared.  After every render pass, the attachments are invalidated.  In the following render
    // passes thus they are not loaded (rather unresolved, as the attachments are
    // multisampled-render-to-texture).  Due to the invalidate call, neither of the 4 render passes
    // should resolve the attachments.

    // Expect rpCount+4, color(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 4, 1, 0, 0, 0, 0, &expected);
    // Expect rpCount+4, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 4, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 0, 0, &expected);

    // Additionally, expect no resolve and unresolve.
    setExpectedCountersForUnresolveResolveTest(getPerfCounters(), 0, 0, 0, 0, 0, 0, &expected);

    constexpr GLsizei kSize = 6;

    // Create multisampled framebuffer to draw into, with both color and depth attachments.
    GLTexture colorMS;
    glBindTexture(GL_TEXTURE_2D, colorMS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer depthStencilMS;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencilMS);
    glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer fboMS;
    glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                         colorMS, 0, 4);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencilMS);
    ASSERT_GL_NO_ERROR();

    // Set up texture for copy operation that breaks the render pass
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set viewport and clear color, depth and stencil
    glViewport(0, 0, kSize, kSize);
    glClearColor(0, 0, 0, 1.0f);
    glClearDepthf(1);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Output depth/stencil, but disable testing so all draw calls succeed
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);

    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x55, 0xFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
    glStencilMask(0xFF);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw red
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.75f);
    ASSERT_GL_NO_ERROR();

    // Invalidate everything
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0, GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, discards);

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw green
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Invalidate everything
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, discards);

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw blue
    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.25f);
    ASSERT_GL_NO_ERROR();

    // Invalidate everything
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, discards);

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Draw yellow
    glUniform4f(colorUniformLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Invalidate everything
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 3, discards);

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, kSize / 2, kSize / 2, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Verify the counters
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_COUNTERS_FOR_UNRESOLVE_RESOLVE_TEST(getPerfCounters(), expected);
}

// Tests counters when uninitialized multisampled-render-to-texture depth/stencil renderbuffers are
// unused but not invalidated.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       RenderToTextureUninitializedAndUnusedDepthStencil)
{
    // http://anglebug.com/42263651
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());

    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, no depth/stencil clear, load or store.
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    // Additionally, expect only color resolve.
    setExpectedCountersForUnresolveResolveTest(getPerfCounters(), 0, 0, 0, 1, 0, 0, &expected);

    constexpr GLsizei kSize = 6;

    // Create multisampled framebuffer to draw into, with both color and depth attachments.
    GLTexture colorMS;
    glBindTexture(GL_TEXTURE_2D, colorMS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer depthStencilMS;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencilMS);
    glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer fboMS;
    glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                         colorMS, 0, 4);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencilMS);
    ASSERT_GL_NO_ERROR();

    // Set up texture for copy operation that breaks the render pass
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set viewport and clear color only
    glViewport(0, 0, kSize, kSize);
    glClearColor(0, 0, 0, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Disable depth/stencil testing.
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_STENCIL_TEST);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw red
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.75f);
    ASSERT_GL_NO_ERROR();

    // Break the render pass
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize / 2, kSize / 2);
    ASSERT_GL_NO_ERROR();

    // Verify the counters
    EXPECT_DEPTH_STENCIL_LOAD_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_COUNTERS_FOR_UNRESOLVE_RESOLVE_TEST(getPerfCounters(), expected);
}

// Tests that multisampled-render-to-texture depth/stencil renderbuffers resolve to be updated
// respectively.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       MultisampleDepthStencilResolveSeparately)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // This test creates 4 render passes. In the first render pass, color, depth and stencil are
    // cleared. In the following render passes, depth and stencil toggle between Load and Clear
    // respectively.

    constexpr GLsizei kSize = 6;

    // Create multisampled framebuffer to draw into, with both color and depth attachments.
    GLTexture colorMS;
    glBindTexture(GL_TEXTURE_2D, colorMS);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer depthStencilMS;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencilMS);
    glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER, 4, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer fboMS;
    glBindFramebuffer(GL_FRAMEBUFFER, fboMS);
    glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                                         colorMS, 0, 4);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              depthStencilMS);
    ASSERT_GL_NO_ERROR();

    // Set up texture for copy operation that breaks the render pass
    GLTexture copyTex;
    glBindTexture(GL_TEXTURE_2D, copyTex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    // Set viewport and clear color, depth and stencil
    glViewport(0, 0, kSize, kSize);
    glClearColor(0, 0, 0, 0.0f);
    glClearDepthf(1.0);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Output depth/stencil but disable testing so all draw calls succeed
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);

    glEnable(GL_STENCIL_TEST);
    glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, 0, 255);
    glStencilOpSeparate(GL_FRONT, GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilFuncSeparate(GL_BACK, GL_ALWAYS, 0, 255);
    glStencilOpSeparate(GL_BACK, GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(255);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // First render pass: The depthLoadOp and stencilLoadOp are Clear.
    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 1, 0, &expected);

    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.75f);
    ASSERT_GL_NO_ERROR();

    // Break the first render pass and verify the counters.
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize, kSize);
    ASSERT_GL_NO_ERROR();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Second render pass: The depthLoadOp and stencilLoadOp are Load.
    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 1, 0, &expected);
    adjustExpectedCountersForDynamicRenderingMSRTTEmulationUnresolve(1, 0, 1, true, true,
                                                                     &expected);

    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Break the second render pass and verify the counters.
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize, kSize);
    ASSERT_GL_NO_ERROR();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Clear depth, but not stencil buffer (loadOp D=CLEAR)
    glClear(GL_DEPTH_BUFFER_BIT);

    // Third render pass: The depthLoadOp is Clear and stencilLoadOp is Load.
    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 1, 0, 1, 0, &expected);
    adjustExpectedCountersForDynamicRenderingMSRTTEmulationUnresolve(1, 0, 1, false, true,
                                                                     &expected);

    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.25f);
    ASSERT_GL_NO_ERROR();

    // Break the third render pass and verify the counters.
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize, kSize);
    ASSERT_GL_NO_ERROR();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Clear stencil, but not depth buffer (loadOp S=CLEAR)
    glClear(GL_STENCIL_BUFFER_BIT);

    // Fourth render pass: The depthLoadOp is Load and stencilLoadOp is Clear.
    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 1, 0, &expected);
    adjustExpectedCountersForDynamicRenderingMSRTTEmulationUnresolve(1, 0, 1, true, false,
                                                                     &expected);

    glUniform4f(colorUniformLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Break the fourth render pass and verify the counters.
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, kSize, kSize);
    ASSERT_GL_NO_ERROR();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Ensures we use read-only depth layout when there is no write
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, ReadOnlyDepthBufferLayout)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 64;

    angle::VulkanPerfCounters expected;

    // Create depth only FBO and fill depth texture to leftHalf=0.0 and rightHalf=1.0. This should
    // use writeable layout
    expected.readOnlyDepthStencilRenderPasses = getPerfCounters().readOnlyDepthStencilRenderPasses;

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    GLTexture depthTexture;
    glBindTexture(GL_TEXTURE_2D, depthTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                 GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLFramebuffer depthOnlyFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthOnlyFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_TRUE);
    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glViewport(0, 0, kSize / 2, kSize);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.0f);
    glViewport(kSize / 2, 0, kSize / 2, kSize);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 1.0f);
    glViewport(0, 0, kSize, kSize);
    ASSERT_GL_NO_ERROR();

    // Because the layout counter is updated at end of renderpass, we need to issue a finish call
    // here to end the renderpass.
    glFinish();

    uint64_t actualReadOnlyDepthStencilCount = getPerfCounters().readOnlyDepthStencilRenderPasses;
    EXPECT_EQ(expected.readOnlyDepthStencilRenderPasses, actualReadOnlyDepthStencilCount);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Create a color+depth FBO and use depth as read only. This should use read only layout
    ++expected.readOnlyDepthStencilRenderPasses;
    // Expect rpCount+1, depth(Clears+0, Loads+1, LoadNones+0, Stores+0, StoreNones+1),
    // stencil(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 1, 0, 0, 1, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 0, 0, 0, &expected);

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    GLFramebuffer depthAndColorFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, depthAndColorFBO);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTexture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Clear color to blue and draw a green quad with depth=0.5
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glDepthMask(GL_FALSE);

    angle::Vector4 clearColor = GLColor::blue.toNormalizedVector();
    glClearColor(clearColor[0], clearColor[1], clearColor[2], clearColor[3]);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    // The pixel check will end renderpass.
    EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(1 + kSize / 2, 1, GLColor::red);
    actualReadOnlyDepthStencilCount = getPerfCounters().readOnlyDepthStencilRenderPasses;
    EXPECT_EQ(expected.readOnlyDepthStencilRenderPasses, actualReadOnlyDepthStencilCount);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Ensures depth/stencil is not loaded after storeOp=DONT_CARE due to optimization (as opposed to
// invalidate)
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
       RenderPassAfterRenderPassWithoutDepthStencilWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    constexpr GLsizei kSize = 64;

    // Create FBO with color, depth and stencil.  Leave depth/stencil uninitialized.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLRenderbuffer renderbuffer;
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kSize, kSize);

    GLFramebuffer framebuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER,
                              renderbuffer);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    ASSERT_GL_NO_ERROR();

    // Draw to the FBO, without enabling depth/stencil.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
    glUseProgram(program);
    GLint colorUniformLocation =
        glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(-1, colorUniformLocation);
    ASSERT_GL_NO_ERROR();

    glViewport(0, 0, kSize, kSize);
    glUniform4f(colorUniformLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 1.0f);

    // Break the render pass and ensure no depth/stencil load/store was done.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1),
    // stencil(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 0, 0, 1, 0, 1, &expected);

    // Draw again with similar conditions, and again make sure no load/store is done.
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 1.0f);

    // Break the render pass and ensure no depth/stencil load/store was done.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Ensures repeated clears of various kind (all attachments, some attachments, scissored, masked
// etc) don't break the render pass.
TEST_P(VulkanPerformanceCounterTest, ClearAfterClearDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    constexpr GLsizei kSize = 6;

    // Create a framebuffer to clear with both color and depth/stencil attachments.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLTexture depth;
    glBindTexture(GL_TEXTURE_2D, depth);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH24_STENCIL8, kSize, kSize, 0, GL_DEPTH_STENCIL,
                 GL_UNSIGNED_INT_24_8_OES, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, depth, 0);
    ASSERT_GL_NO_ERROR();

    // Clear color and depth, but not stencil.
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepthf(0.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Clear color and stencil, but not depth.
    glClearColor(1.0f, 0.0f, 0.0f, 0.0f);
    glClearStencil(0x11);
    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clear depth and stencil, but not color.
    glClearDepthf(0.1f);
    glClearStencil(0x22);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clear masked color, and unmasked depth.
    glClearDepthf(0.2f);
    glClearColor(0.1f, 1.0f, 0.0f, 1.0f);
    glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Clear unmasked color, and masked stencil.
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glClearStencil(0x33);
    glStencilMask(0xF0);
    glClear(GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clear unmasked depth and stencil.
    glClearDepthf(0.3f);
    glClearStencil(0x44);
    glStencilMask(0xFF);
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clear with scissor.
    glEnable(GL_SCISSOR_TEST);
    glScissor(kSize / 3, kSize / 3, kSize / 3, kSize / 3);
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(1.0f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Verify render pass count.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);

    // Make sure the result is correct.  The border of the image should be blue with depth 0.3f and
    // stencil 0x44.  The center is red with depth 1.0f and stencil 0x55.

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::blue);

    EXPECT_PIXEL_COLOR_EQ(kSize / 3, kSize / 3, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3 - 1, kSize / 3, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 3, 2 * kSize / 3 - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3 - 1, 2 * kSize / 3 - 1, GLColor::red);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::red);

    glViewport(0, 0, kSize, kSize);
    glDisable(GL_SCISSOR_TEST);

    // Center: If depth is not cleared to 1, rendering would fail.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    // Center: If stencil is not clear to 0x55, rendering would fail.
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0x55, 0xFF);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
    glStencilMask(0xFF);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw green
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Verify that only the center has changed
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::blue);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::blue);

    EXPECT_PIXEL_COLOR_EQ(kSize / 3, kSize / 3, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3 - 1, kSize / 3, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 3, 2 * kSize / 3 - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3 - 1, 2 * kSize / 3 - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::green);

    // Border: If depth is not cleared to 0.3f, rendering would fail.
    glDepthFunc(GL_LESS);

    // Center: If stencil is not clear to 0x44, rendering would fail.
    glStencilFunc(GL_EQUAL, 0x44, 0xFF);

    // Draw yellow
    glUniform4f(colorUniformLocation, 1.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), -0.5f);
    ASSERT_GL_NO_ERROR();

    // Verify that only the border has changed
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, 0, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(0, kSize - 1, GLColor::yellow);
    EXPECT_PIXEL_COLOR_EQ(kSize - 1, kSize - 1, GLColor::yellow);

    EXPECT_PIXEL_COLOR_EQ(kSize / 3, kSize / 3, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3 - 1, kSize / 3, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 3, 2 * kSize / 3 - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(2 * kSize / 3 - 1, 2 * kSize / 3 - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(kSize / 2, kSize / 2, GLColor::green);
}

// Ensures that changing the scissor size doesn't break the render pass.
TEST_P(VulkanPerformanceCounterTest, ScissorDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr GLsizei kSize = 16;

    // Create a framebuffer with a color attachment.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    ASSERT_GL_NO_ERROR();

    // First, issue a clear and make sure it's done.  Later we can verify that areas outside
    // scissors are not rendered to.
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // This test starts with a small scissor and gradually grows it and issues draw calls and
    // various kinds of clears:
    //
    // - Clear the center to red
    //
    //     +----------------------+
    //     | K                    |
    //     |                      |
    //     |       +-----+        |
    //     |       |     |        |
    //     |       |  R  |        |
    //     |       |     |        |
    //     |       +-----+        |
    //     |                      |
    //     |                      |
    //     |                      |
    //     |                      |
    //     +----------------------+
    //
    // - Draw green to center right
    //
    //     +----------------------+
    //     |                      |
    //     |              +-------+
    //     |       +-----+|       |
    //     |       |     ||       |
    //     |       |  R  ||  G    |
    //     |       |     ||       |
    //     |       +-----+|       |
    //     |              |       |
    //     |              |       |
    //     |              |       |
    //     |              +-------+
    //     +----------------------+
    //
    // - Masked clear of center column, only outputting to the blue channel
    //
    //     +----------------------+
    //     |      +---+           |
    //     |      | B |   +-------+
    //     |      |+--+--+|       |
    //     |      ||  |  ||       |
    //     |      ||M |R ||  G    |
    //     |      ||  |  ||       |
    //     |      |+--+--+|       |
    //     |      |   |   |       |
    //     |      |   |   |       |
    //     |      |   |   |       |
    //     |      |   |   +-------+
    //     +------+---+-----------+
    //
    // - Masked draw of center row, only outputting to alpha.
    //
    //     +----------------------+
    //     | K    +---+ K         |
    //     |      | B |   +-------+
    //     |      |+--+--+|       |
    //     |      ||M |R ||   G   |
    //     | +----++--+--++-----+ |
    //     | |    ||TM|TR||     | |
    //     | | TK |+--+--+|  TG | |
    //     | |    |TB |TK |     | |
    //     | +----+---+---+-----+ |
    //     |      |   |   |   G   |
    //     | K    | B | K +-------+
    //     +------+---+-----------+
    //
    // Where: K=Black, R=Red, G=Green, B=Blue, M=Magenta, T=Transparent

    constexpr GLsizei kClearX      = kSize / 3;
    constexpr GLsizei kClearY      = kSize / 3;
    constexpr GLsizei kClearWidth  = kSize / 3;
    constexpr GLsizei kClearHeight = kSize / 3;

    constexpr GLsizei kDrawX      = kClearX + kClearWidth + 2;
    constexpr GLsizei kDrawY      = kSize / 5;
    constexpr GLsizei kDrawWidth  = kSize - kDrawX;
    constexpr GLsizei kDrawHeight = 7 * kSize / 10;

    constexpr GLsizei kMaskedClearX      = kSize / 4;
    constexpr GLsizei kMaskedClearY      = kSize / 8;
    constexpr GLsizei kMaskedClearWidth  = kSize / 4;
    constexpr GLsizei kMaskedClearHeight = 7 * kSize / 8;

    constexpr GLsizei kMaskedDrawX      = kSize / 8;
    constexpr GLsizei kMaskedDrawY      = kSize / 2;
    constexpr GLsizei kMaskedDrawWidth  = 6 * kSize / 8;
    constexpr GLsizei kMaskedDrawHeight = kSize / 4;

    glEnable(GL_SCISSOR_TEST);

    // Clear center to red
    glScissor(kClearX, kClearY, kClearWidth, kClearHeight);
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw green to center right
    glScissor(kDrawX, kDrawY, kDrawWidth, kDrawHeight);
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0);
    ASSERT_GL_NO_ERROR();

    // Masked blue-channel clear of center column
    glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_FALSE);
    glScissor(kMaskedClearX, kMaskedClearY, kMaskedClearWidth, kMaskedClearHeight);
    glClearColor(0.5f, 0.5f, 1.0f, 0.5f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Masked alpha-channel draw of center row
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_TRUE);
    glScissor(kMaskedDrawX, kMaskedDrawY, kMaskedDrawWidth, kMaskedDrawHeight);
    glUniform4f(colorUniformLocation, 0.5f, 0.5f, 0.5f, 0.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0);
    ASSERT_GL_NO_ERROR();

    // Verify render pass count.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);

    // Make sure the result is correct:
    //
    //     +----------------------+  <-- 0
    //     | K    +---+ K         |  <-- kMaskedClearY
    //     |      | B |   +-------+  <-- kDrawY
    //     |      |+--+--+|       |  <-- kClearY
    //     |      ||M |R ||   G   |
    //     | +----++--+--++-----+ |  <-- kMaskedDrawY
    //     | |    ||TM|TR||     | |
    //     | | TK |+--+--+|  TG | |  <-- kClearY + kClearHeight
    //     | |    |TB |TK |     | |
    //     | +----+---+---+-----+ |  <-- kMaskedDrawY + kMaskedDrawHeight
    //     |      |   |   |   G   |
    //     | K    | B | K +-------+  <-- kDrawY + kDrawHeight
    //     +------+---+-----------+  <-- kSize == kMaskedClearY + kMaskedClearHeight
    //     | |    ||  |  ||     | |
    //     | |    ||  |  ||     |  \---> kSize == kDrawX + kDrawWidth
    //     | |    ||  |  ||      \-----> kMaskedDrawX + kMaskedDrawWidth
    //     | |    ||  |  | \-----------> kDrawX
    //     | |    ||  |   \------------> kClearX + kClearWidth
    //     | |    ||   \---------------> kMaskedClearX + kMaskedClearWidth
    //     | |    | \------------------> kClearX
    //     | |     \-------------------> kMaskedClearX
    //     |  \------------------------> kMaskedDrawX
    //      \--------------------------> 0

    constexpr GLsizei kClearX2       = kClearX + kClearWidth;
    constexpr GLsizei kClearY2       = kClearY + kClearHeight;
    constexpr GLsizei kDrawX2        = kDrawX + kDrawWidth;
    constexpr GLsizei kDrawY2        = kDrawY + kDrawHeight;
    constexpr GLsizei kMaskedClearX2 = kMaskedClearX + kMaskedClearWidth;
    constexpr GLsizei kMaskedClearY2 = kMaskedClearY + kMaskedClearHeight;
    constexpr GLsizei kMaskedDrawX2  = kMaskedDrawX + kMaskedDrawWidth;
    constexpr GLsizei kMaskedDrawY2  = kMaskedDrawY + kMaskedDrawHeight;

    constexpr GLColor kTransparentRed(255, 0, 0, 0);
    constexpr GLColor kTransparentGreen(0, 255, 0, 0);
    constexpr GLColor kTransparentBlue(0, 0, 255, 0);
    constexpr GLColor kTransparentMagenta(255, 0, 255, 0);

    // Verify the black areas.
    EXPECT_PIXEL_RECT_EQ(0, 0, kMaskedClearX, kMaskedDrawY, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, kMaskedDrawY2, kMaskedClearX, kSize - kMaskedDrawY2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, 0, kSize - kMaskedClearX2, kDrawY, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, kDrawY2, kSize - kMaskedClearX2, kSize - kDrawY2,
                         GLColor::black);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX, 0, kMaskedClearWidth, kMaskedClearY, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, kDrawY, kDrawX - kMaskedClearX2, kClearY - kDrawY,
                         GLColor::black);
    EXPECT_PIXEL_RECT_EQ(kClearX2, kClearY, kDrawX - kClearX2, kMaskedDrawY - kClearY,
                         GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, kMaskedDrawY, kMaskedDrawX, kMaskedDrawHeight, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, kMaskedDrawY2, kDrawX - kMaskedClearX2,
                         kSize - kMaskedDrawY2, GLColor::black);

    // Verify the red area:
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, kClearY, kClearX2 - kMaskedClearX2, kMaskedDrawY - kClearY,
                         GLColor::red);
    // Verify the transparent red area:
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, kMaskedDrawY, kClearX2 - kMaskedClearX2,
                         kClearY2 - kMaskedDrawY, kTransparentRed);
    // Verify the magenta area:
    EXPECT_PIXEL_RECT_EQ(kClearX, kClearY, kMaskedClearX2 - kClearX, kMaskedDrawY - kClearY,
                         GLColor::magenta);
    // Verify the transparent magenta area:
    EXPECT_PIXEL_RECT_EQ(kClearX, kMaskedDrawY, kMaskedClearX2 - kClearX, kClearY2 - kMaskedDrawY,
                         kTransparentMagenta);
    // Verify the green area:
    EXPECT_PIXEL_RECT_EQ(kDrawX, kDrawY, kDrawWidth, kMaskedDrawY - kDrawY, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(kDrawX, kMaskedDrawY2, kDrawWidth, kDrawY2 - kMaskedDrawY2,
                         GLColor::green);
    EXPECT_PIXEL_RECT_EQ(kMaskedDrawX2, kMaskedDrawY, kDrawX2 - kMaskedDrawX2, kMaskedDrawHeight,
                         GLColor::green);
    // Verify the transparent green area:
    EXPECT_PIXEL_RECT_EQ(kDrawX, kMaskedDrawY, kMaskedDrawX2 - kDrawX, kMaskedDrawHeight,
                         kTransparentGreen);
    // Verify the blue area:
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX, kMaskedClearY, kMaskedClearWidth, kClearY - kMaskedClearY,
                         GLColor::blue);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX, kMaskedDrawY2, kMaskedClearWidth,
                         kMaskedClearY2 - kMaskedDrawY2, GLColor::blue);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX, kClearY, kClearX - kMaskedClearX, kMaskedDrawY - kClearY,
                         GLColor::blue);
    // Verify the transparent blue area:
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX, kClearY2, kMaskedClearWidth, kMaskedDrawY2 - kClearY2,
                         kTransparentBlue);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX, kMaskedDrawY, kClearX - kMaskedClearX,
                         kClearY2 - kMaskedDrawY, kTransparentBlue);
    // Verify the transparent black area:
    EXPECT_PIXEL_RECT_EQ(kMaskedDrawX, kMaskedDrawY, kMaskedClearX - kMaskedDrawX,
                         kMaskedDrawHeight, GLColor::transparentBlack);
    EXPECT_PIXEL_RECT_EQ(kMaskedClearX2, kClearY2, kDrawX - kMaskedClearX2,
                         kMaskedDrawY2 - kClearY2, GLColor::transparentBlack);
    EXPECT_PIXEL_RECT_EQ(kClearX2, kMaskedDrawY, kDrawX - kClearX2, kMaskedDrawHeight,
                         GLColor::transparentBlack);
}

// Tests that changing UBO bindings does not allocate new descriptor sets.
TEST_P(VulkanPerformanceCounterTest, ChangingUBOsHitsDescriptorSetCache)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // Set up two UBOs, one filled with "1" and the second with "2".
    constexpr GLsizei kCount = 64;
    std::vector<GLint> data1(kCount, 1);
    std::vector<GLint> data2(kCount, 2);

    GLBuffer ubo1;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo1);
    glBufferData(GL_UNIFORM_BUFFER, kCount * sizeof(data1[0]), data1.data(), GL_STATIC_DRAW);

    GLBuffer ubo2;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo2);
    glBufferData(GL_UNIFORM_BUFFER, kCount * sizeof(data2[0]), data2.data(), GL_STATIC_DRAW);

    // Set up a program that verifies the contents of uniform blocks.
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;
in vec4 position;
void main()
{
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform buf {
    int data[64/4];
};
uniform int checkValue;
out vec4 outColor;

void main()
{
    for (int i = 0; i < 64/4; ++i) {
        if (data[i] != checkValue) {
            outColor = vec4(1, 0, 0, 1);
            return;
        }
    }
    outColor = vec4(0, 1, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    GLint uniLoc = glGetUniformLocation(program, "checkValue");
    ASSERT_NE(-1, uniLoc);

    GLuint blockIndex = glGetUniformBlockIndex(program, "buf");
    ASSERT_NE(blockIndex, GL_INVALID_INDEX);

    glUniformBlockBinding(program, blockIndex, 0);
    ASSERT_GL_NO_ERROR();

    // Set up the rest of the GL state.
    auto quadVerts = GetQuadVertices();
    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
                 GL_STATIC_DRAW);

    GLint posLoc = glGetAttribLocation(program, "position");
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(posLoc);

    // Draw a few times with each UBO. Stream out one pixel for post-render verification.
    constexpr int kIterations         = 5;
    constexpr GLsizei kPackBufferSize = sizeof(GLColor) * kIterations * 2;

    GLBuffer packBuffer;
    glBindBuffer(GL_PIXEL_PACK_BUFFER, packBuffer);
    glBufferData(GL_PIXEL_PACK_BUFFER, kPackBufferSize, nullptr, GL_STREAM_READ);

    GLsizei offset = 0;

    uint64_t expectedUniformBuffersCacheMisses = 0;

    for (int iteration = 0; iteration < kIterations; ++iteration)
    {
        glUniform1i(uniLoc, 1);
        glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo1);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                     reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(offset)));
        offset += sizeof(GLColor);
        glUniform1i(uniLoc, 2);
        glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo2);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
                     reinterpret_cast<GLvoid *>(static_cast<uintptr_t>(offset)));
        offset += sizeof(GLColor);

        // Capture the allocations counter after the first run.
        if (iteration == 0)
        {
            expectedUniformBuffersCacheMisses =
                getPerfCounters().uniformBuffersDescriptorSetCacheMisses;
        }
    }

    ASSERT_GL_NO_ERROR();
    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        EXPECT_GT(expectedUniformBuffersCacheMisses, 0u);
    }

    // Verify correctness first.
    std::vector<GLColor> expectedData(kIterations * 2, GLColor::green);
    std::vector<GLColor> actualData(kIterations * 2, GLColor::black);

    void *mapPtr = glMapBufferRange(GL_PIXEL_PACK_BUFFER, 0, kPackBufferSize, GL_MAP_READ_BIT);
    ASSERT_NE(nullptr, mapPtr);
    ANGLE_UNSAFE_TODO(memcpy(actualData.data(), mapPtr, kPackBufferSize));

    glUnmapBuffer(GL_PIXEL_PACK_BUFFER);

    EXPECT_EQ(expectedData, actualData);

    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        // Check for unnecessary descriptor set allocations.
        uint64_t actualUniformBuffersCacheMisses =
            getPerfCounters().uniformBuffersDescriptorSetCacheMisses;
        EXPECT_EQ(expectedUniformBuffersCacheMisses, actualUniformBuffersCacheMisses);
    }
}

// Test that mapping a buffer that the GPU is using as read-only ghosts the buffer, rather than
// waiting for the GPU access to complete before returning a pointer to the buffer.
void VulkanPerformanceCounterTest::mappingGpuReadOnlyBufferGhostsBuffer(BufferUpdate update)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // 1. Create a buffer, map it, fill it with red
    // 2. Draw with buffer (GPU read-only)
    // 3. Map the same buffer and fill with white
    //    - This should ghost the buffer, rather than ending the render pass.
    // 4. Draw with buffer
    // 5. Update the buffer with glBufferSubData() or glCopyBufferSubData
    // 6. Draw with the buffer
    // The render pass should only be broken (counters.renderPasses == 0) due to the glReadPixels()
    // to verify the draw at the end.

    const std::array<GLColor, 4> kInitialData = {GLColor::red, GLColor::red, GLColor::red,
                                                 GLColor::red};
    const std::array<GLColor, 4> kUpdateData1 = {GLColor::white, GLColor::white, GLColor::white,
                                                 GLColor::white};
    const std::array<GLColor, 4> kUpdateData2 = {GLColor::blue, GLColor::blue, GLColor::blue,
                                                 GLColor::blue};

    GLBuffer buffer;
    glBindBuffer(GL_UNIFORM_BUFFER, buffer);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(kInitialData), kInitialData.data(), GL_DYNAMIC_DRAW);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);
    ASSERT_GL_NO_ERROR();

    // Draw
    constexpr char kVerifyUBO[] = R"(#version 300 es
precision mediump float;
uniform block {
    uvec4 data;
} ubo;
uniform highp uint expect;
uniform vec4 successOutput;
out vec4 colorOut;
void main()
{
    if (all(equal(ubo.data, uvec4(expect))))
        colorOut = successOutput;
    else
        colorOut = vec4(1.0, 0, 0, 1.0);
})";

    ANGLE_GL_PROGRAM(verifyUbo, essl3_shaders::vs::Simple(), kVerifyUBO);
    glUseProgram(verifyUbo);

    GLint expectLoc = glGetUniformLocation(verifyUbo, "expect");
    ASSERT_NE(-1, expectLoc);
    GLint successLoc = glGetUniformLocation(verifyUbo, "successOutput");
    ASSERT_NE(-1, successLoc);

    glUniform1ui(expectLoc, kInitialData[0].asUint());
    glUniform4f(successLoc, 0, 1, 0, 1);

    drawQuad(verifyUbo, essl3_shaders::PositionAttrib(), 0.5);
    ASSERT_GL_NO_ERROR();

    // Map the buffer and update it.
    // This should ghost the buffer and avoid breaking the render pass, since the GPU is only
    // reading it.
    void *mappedBuffer =
        glMapBufferRange(GL_UNIFORM_BUFFER, 0, sizeof(kInitialData), GL_MAP_WRITE_BIT);
    // 'renderPasses == 0' here means the render pass was broken and a new one was started.
    ASSERT_EQ(getPerfCounters().renderPasses, 1u);
    ASSERT_EQ(getPerfCounters().buffersGhosted, 1u);

    ANGLE_UNSAFE_TODO(memcpy(mappedBuffer, kUpdateData1.data(), sizeof(kInitialData)));

    glUnmapBuffer(GL_UNIFORM_BUFFER);
    ASSERT_GL_NO_ERROR();

    // Verify that the buffer has the updated value.
    glUniform1ui(expectLoc, kUpdateData1[0].asUint());
    glUniform4f(successLoc, 0, 0, 1, 1);

    drawQuad(verifyUbo, essl3_shaders::PositionAttrib(), 0.5);
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(getPerfCounters().renderPasses, 1u);

    // Update the buffer
    updateBuffer(update, GL_UNIFORM_BUFFER, 0, sizeof(kUpdateData2), kUpdateData2.data());
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(getPerfCounters().renderPasses, 1u);

    // Verify that the buffer has the updated value.
    glUniform1ui(expectLoc, kUpdateData2[0].asUint());
    glUniform4f(successLoc, 0, 1, 1, 1);

    drawQuad(verifyUbo, essl3_shaders::PositionAttrib(), 0.5);
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(getPerfCounters().renderPasses, 1u);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
}

// Test that mapping a buffer that the GPU is using as read-only ghosts the buffer, rather than
// waiting for the GPU access to complete before returning a pointer to the buffer.  This test uses
// glBufferSubData to update the buffer.
TEST_P(VulkanPerformanceCounterTest, MappingGpuReadOnlyBufferGhostsBuffer_SubData)
{
    mappingGpuReadOnlyBufferGhostsBuffer(BufferUpdate::SubData);
}

// Same as MappingGpuReadOnlyBufferGhostsBuffer_SubData, but using glCopyBufferSubData to update the
// buffer.
TEST_P(VulkanPerformanceCounterTest, MappingGpuReadOnlyBufferGhostsBuffer_Copy)
{
    mappingGpuReadOnlyBufferGhostsBuffer(BufferUpdate::Copy);
}

void VulkanPerformanceCounterTest::partialBufferUpdateShouldNotBreakRenderPass(BufferUpdate update)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
    if (!hasPreferCPUForBufferSubData())
    {
        ++expectedRenderPassCount;
    }

    const std::array<GLColor, 4> kInitialData = {GLColor::red, GLColor::green, GLColor::blue,
                                                 GLColor::yellow};
    const std::array<GLColor, 1> kUpdateData1 = {GLColor::cyan};
    const std::array<GLColor, 3> kUpdateData2 = {GLColor::magenta, GLColor::black, GLColor::white};

    GLBuffer buffer;
    glBindBuffer(GL_UNIFORM_BUFFER, buffer);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(kInitialData), kInitialData.data(), GL_DYNAMIC_DRAW);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, buffer);
    ASSERT_GL_NO_ERROR();

    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float height;
void main()
{
    // gl_VertexID    x    y
    //      0        -1   -1
    //      1         1   -1
    //      2        -1    1
    //      3         1    1
    int bit0 = gl_VertexID & 1;
    int bit1 = gl_VertexID >> 1;
    gl_Position = vec4(bit0 * 2 - 1, bit1 * 2 - 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 colorOut;
uniform block {
    uvec4 data;
} ubo;
uniform uvec4 expect;
uniform vec4 successColor;
void main()
{
    if (all(equal(ubo.data, expect)))
        colorOut = successColor;
    else
        colorOut = vec4(0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint expectLoc = glGetUniformLocation(program, "expect");
    ASSERT_NE(-1, expectLoc);
    GLint successLoc = glGetUniformLocation(program, "successColor");
    ASSERT_NE(-1, successLoc);

    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    // Draw once, using the buffer in the render pass.
    glUniform4ui(expectLoc, kInitialData[0].asUint(), kInitialData[1].asUint(),
                 kInitialData[2].asUint(), kInitialData[3].asUint());
    glUniform4f(successLoc, 1, 0, 0, 1);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Upload a small part of the buffer, and draw again.
    updateBuffer(update, GL_UNIFORM_BUFFER, 0, sizeof(kUpdateData1), kUpdateData1.data());

    glUniform4ui(expectLoc, kUpdateData1[0].asUint(), kInitialData[1].asUint(),
                 kInitialData[2].asUint(), kInitialData[3].asUint());
    glUniform4f(successLoc, 0, 1, 0, 1);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Upload a large part of the buffer, and draw again.
    updateBuffer(update, GL_UNIFORM_BUFFER, sizeof(kUpdateData1), sizeof(kUpdateData2),
                 kUpdateData2.data());

    glUniform4ui(expectLoc, kUpdateData1[0].asUint(), kUpdateData2[0].asUint(),
                 kUpdateData2[1].asUint(), kUpdateData2[2].asUint());
    glUniform4f(successLoc, 0, 0, 1, 1);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Verify results
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    ASSERT_GL_NO_ERROR();

    // Only one render pass should have been used.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);
}

// Verifies that BufferSubData calls don't cause a render pass break when it only uses the buffer
// read-only.  This test uses glBufferSubData to update the buffer.
TEST_P(VulkanPerformanceCounterTest, PartialBufferUpdateShouldNotBreakRenderPass_SubData)
{
    partialBufferUpdateShouldNotBreakRenderPass(BufferUpdate::SubData);
}

// Same as PartialBufferUpdateShouldNotBreakRenderPass_SubData, but using glCopyBufferSubData to
// update the buffer.
TEST_P(VulkanPerformanceCounterTest, PartialBufferUpdateShouldNotBreakRenderPass_Copy)
{
    partialBufferUpdateShouldNotBreakRenderPass(BufferUpdate::Copy);
}

void VulkanPerformanceCounterTest::bufferSubDataShouldNotTriggerSyncState(BufferUpdate update)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    glUseProgram(testProgram);

    GLint posLoc = glGetAttribLocation(testProgram, essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, posLoc);

    setupQuadVertexBuffer(0.5f, 1.0f);
    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    EXPECT_EQ(getPerfCounters().vertexArraySyncStateCalls, 1u);

    const std::array<Vector3, 6> &quadVertices = GetQuadVertices();
    size_t bufferSize                          = sizeof(quadVertices[0]) * quadVertices.size();

    updateBuffer(update, GL_ARRAY_BUFFER, 0, bufferSize, quadVertices.data());

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    EXPECT_EQ(getPerfCounters().vertexArraySyncStateCalls, 1u);

    // Verify the BufferData with a whole buffer size is treated like the SubData call.
    glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices[0]) * quadVertices.size(),
                 quadVertices.data(), GL_STATIC_DRAW);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    EXPECT_EQ(getPerfCounters().vertexArraySyncStateCalls, 1u);
}

// Verifies that BufferSubData calls don't trigger state updates for non-translated formats.  This
// test uses glBufferSubData to update the buffer.
TEST_P(VulkanPerformanceCounterTest, BufferSubDataShouldNotTriggerSyncState_SubData)
{
    bufferSubDataShouldNotTriggerSyncState(BufferUpdate::SubData);
}

// Same as BufferSubDataShouldNotTriggerSyncState_SubData, but using glCopyBufferSubData to update
// the buffer.
TEST_P(VulkanPerformanceCounterTest, BufferSubDataShouldNotTriggerSyncState_Copy)
{
    bufferSubDataShouldNotTriggerSyncState(BufferUpdate::Copy);
}

// Verifies that rendering to backbuffer discards depth/stencil.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, SwapShouldInvalidateDepthStencil)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 0, 0, &expected);

    // Clear to verify that _some_ counters did change (as opposed to for example all being reset on
    // swap)
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_ALWAYS);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x00, 0xFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Swap buffers to implicitely resolve
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Verifies that rendering to MSAA backbuffer discards depth/stencil.
TEST_P(VulkanPerformanceCounterTest_MSAA, SwapShouldInvalidateDepthStencil)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    // stencil(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForStencilOps(getPerfCounters(), 1, 0, 0, 0, 0, &expected);

    // Clear to verify that _some_ counters did change (as opposed to for example all being reset on
    // swap)
    glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    glDepthFunc(GL_ALWAYS);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_ALWAYS, 0x00, 0xFF);
    glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);

    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Swap buffers to implicitely resolve
    swapBuffers();
    EXPECT_DEPTH_STENCIL_OP_COUNTERS(getPerfCounters(), expected);
}

// Verifies that multisample swapchain resolve occurs in subpass.
TEST_P(VulkanPerformanceCounterTest_MSAA, SwapShouldResolveWithSubpass)
{
    angle::VulkanPerfCounters expected;
    // Expect rpCount+1, color(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);

    uint64_t expectedResolvesSubpass = getPerfCounters().swapchainResolveInSubpass + 1;
    uint64_t expectedResolvesOutside = getPerfCounters().swapchainResolveOutsideSubpass;

    // Clear color.
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw green
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Swap buffers to implicitly resolve
    swapBuffers();
    EXPECT_EQ(getPerfCounters().swapchainResolveInSubpass, expectedResolvesSubpass);
    EXPECT_EQ(getPerfCounters().swapchainResolveOutsideSubpass, expectedResolvesOutside);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Verifies that in a multisample swapchain, drawing to the default FBO followed by user FBO and
// then swapping triggers the resolve outside the optimization subpass.
TEST_P(VulkanPerformanceCounterTest_MSAA, SwapAfterDrawToDifferentFBOsShouldResolveOutsideSubpass)
{
    constexpr GLsizei kSize = 16;
    angle::VulkanPerfCounters expected;
    // Expect rpCount+1, color(Clears+1, Loads+0, LoadNones+0, Stores+2, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 2, 0, &expected);

    uint64_t expectedResolvesSubpass = getPerfCounters().swapchainResolveInSubpass;
    uint64_t expectedResolvesOutside = getPerfCounters().swapchainResolveOutsideSubpass + 1;

    // Create a framebuffer to clear.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    ASSERT_GL_NO_ERROR();

    // Clear color.
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Draw green to default framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Draw blue to the user framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Swap buffers to resolve outside the optimization subpass
    swapBuffers();
    EXPECT_EQ(getPerfCounters().swapchainResolveInSubpass, expectedResolvesSubpass);
    EXPECT_EQ(getPerfCounters().swapchainResolveOutsideSubpass, expectedResolvesOutside);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Verifies that in a multisample swapchain, subpass resolve only happens when the render pass
// covers the entire area.
TEST_P(VulkanPerformanceCounterTest_MSAA, ResolveWhenRenderPassNotEntireArea)
{
    constexpr GLsizei kSize = 16;
    angle::VulkanPerfCounters expected;
    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+2, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 2, 0, &expected);

    uint64_t expectedResolvesSubpass = getPerfCounters().swapchainResolveInSubpass;
    uint64_t expectedResolvesOutside = getPerfCounters().swapchainResolveOutsideSubpass + 1;

    // Create a framebuffer to clear.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    ASSERT_GL_NO_ERROR();

    // Clear color.
    glDisable(GL_SCISSOR_TEST);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);

    // Set up program
    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Scissor the render area
    glEnable(GL_SCISSOR_TEST);
    glScissor(kSize / 4, kSize / 4, kSize / 2, kSize / 2);

    // Draw blue to the user framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glUniform4f(colorUniformLocation, 0.0f, 0.0f, 1.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Draw green to default framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), 0.95f);
    ASSERT_GL_NO_ERROR();

    // Swap buffers, which should not resolve the image in subpass
    swapBuffers();
    EXPECT_EQ(getPerfCounters().swapchainResolveInSubpass, expectedResolvesSubpass);
    EXPECT_EQ(getPerfCounters().swapchainResolveOutsideSubpass, expectedResolvesOutside);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that uniform updates eventually stop updating descriptor sets.
TEST_P(VulkanPerformanceCounterTest, UniformUpdatesHitDescriptorSetCache)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(testProgram);
    GLint posLoc = glGetAttribLocation(testProgram, essl1_shaders::PositionAttrib());
    GLint uniLoc = glGetUniformLocation(testProgram, essl1_shaders::ColorUniform());

    std::array<Vector3, 6> quadVerts = GetQuadVertices();

    GLBuffer vbo;
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, quadVerts.size() * sizeof(quadVerts[0]), quadVerts.data(),
                 GL_STATIC_DRAW);

    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);

    ASSERT_GL_NO_ERROR();

    // Choose a number of iterations sufficiently large to ensure all uniforms are cached.
    constexpr int kIterations = 2000;

    // First pass: cache all the uniforms.
    RNG rng;
    for (int iteration = 0; iteration < kIterations; ++iteration)
    {
        Vector3 randomVec3 = RandomVec3(rng.randomInt(), 0.0f, 1.0f);

        glUniform4f(uniLoc, randomVec3.x(), randomVec3.y(), randomVec3.z(), 1.0f);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        GLColor expectedColor = GLColor(randomVec3);
        EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedColor, 5);
    }

    ASSERT_GL_NO_ERROR();

    uint64_t expectedCacheMisses = getPerfCounters().uniformsAndXfbDescriptorSetCacheMisses;
    GLint allocationsBefore      = getPerfCounters().descriptorSetAllocations;
    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        EXPECT_GT(expectedCacheMisses, 0u);
    }

    // Second pass: ensure all the uniforms are cached.
    for (int iteration = 0; iteration < kIterations; ++iteration)
    {
        Vector3 randomVec3 = RandomVec3(rng.randomInt(), 0.0f, 1.0f);

        glUniform4f(uniLoc, randomVec3.x(), randomVec3.y(), randomVec3.z(), 1.0f);
        glDrawArrays(GL_TRIANGLES, 0, 6);

        GLColor expectedColor = GLColor(randomVec3);
        EXPECT_PIXEL_COLOR_NEAR(0, 0, expectedColor, 5);
    }

    ASSERT_GL_NO_ERROR();

    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        uint64_t actualCacheMisses = getPerfCounters().uniformsAndXfbDescriptorSetCacheMisses;
        EXPECT_EQ(expectedCacheMisses, actualCacheMisses);
    }
    else
    {
        // If cache is disabled, we still expect descriptorSets to be reused instead of keep
        // allocating new descriptorSets. The underline reuse logic is implementation detail (as of
        // now it will not reuse util the pool is full), but we expect it will not increasing for
        // every iteration.
        GLint descriptorSetAllocationsIncrease =
            getPerfCounters().descriptorSetAllocations - allocationsBefore;
        EXPECT_GE(kIterations - 1, descriptorSetAllocationsIncrease);
    }
}

// Test one texture sampled by fragment shader, then image load it by compute
// shader, at last fragment shader do something else.
TEST_P(VulkanPerformanceCounterTest_ES31, DrawDispatchImageReadDrawWithEndRP)
{

    constexpr char kVSSource[] = R"(#version 310 es
in vec4 a_position;
out vec2 v_texCoord;

void main()
{
    gl_Position = vec4(a_position.xy, 0.0, 1.0);
    v_texCoord = a_position.xy * 0.5 + vec2(0.5);
})";

    constexpr char kFSSource[] = R"(#version 310 es
precision mediump float;
uniform sampler2D u_tex2D;
in vec2 v_texCoord;
out vec4 out_FragColor;
void main()
{
    out_FragColor = texture(u_tex2D, v_texCoord);
})";

    constexpr char kFSSource1[] = R"(#version 310 es
precision mediump float;
out vec4 out_FragColor;
void main()
{
    out_FragColor = vec4(1.0);
})";

    constexpr char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32f, binding=0) readonly  uniform highp image2D uIn;
layout(std140, binding=0) buffer buf {
    vec4 outData;
};

void main()
{
    outData = imageLoad(uIn, ivec2(gl_LocalInvocationID.xy));
})";

    GLfloat initValue[4] = {1.0, 1.0, 1.0, 1.0};

    // Step 1: Set up a simple 2D Texture rendering loop.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, initValue);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLBuffer vertexBuffer;
    GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), vertices, GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, "a_position");
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();
    glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);

    GLBuffer ssbo;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, 16, nullptr, GL_STREAM_DRAW);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    // This is actually suboptimal, and ideally only one render pass should be necessary.
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 2;

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Step 2: load this image through compute
    ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
    glUseProgram(csProgram);

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    const GLfloat *ptr = reinterpret_cast<const GLfloat *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, 16, GL_MAP_READ_BIT));

    EXPECT_GL_NO_ERROR();
    for (unsigned int idx = 0; idx < 4; idx++)
    {
        ANGLE_UNSAFE_TODO(EXPECT_EQ(1.0, *(ptr + idx)));
    }

    // Step3
    ANGLE_GL_PROGRAM(program2, kVSSource, kFSSource1);
    glUseProgram(program2);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Test one texture sampled by fragment shader, followed by glReadPixels, then image
// load it by compute shader, and at last fragment shader do something else.
TEST_P(VulkanPerformanceCounterTest_ES31, DrawDispatchImageReadDrawWithoutEndRP)
{

    constexpr char kVSSource[] = R"(#version 310 es
in vec4 a_position;
out vec2 v_texCoord;

void main()
{
    gl_Position = vec4(a_position.xy, 0.0, 1.0);
    v_texCoord = a_position.xy * 0.5 + vec2(0.5);
})";

    constexpr char kFSSource[] = R"(#version 310 es
precision mediump float;
uniform sampler2D u_tex2D;
in vec2 v_texCoord;
out vec4 out_FragColor;
void main()
{
    out_FragColor = texture(u_tex2D, v_texCoord);
})";

    constexpr char kFSSource1[] = R"(#version 310 es
precision mediump float;
out vec4 out_FragColor;
void main()
{
    out_FragColor = vec4(1.0);
})";

    constexpr char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32f, binding=0) readonly  uniform highp image2D uIn;
layout(std140, binding=0) buffer buf {
    vec4 outData;
};

void main()
{
    outData = imageLoad(uIn, ivec2(gl_LocalInvocationID.xy));
})";

    GLfloat initValue[4] = {1.0, 1.0, 1.0, 1.0};

    // Step 1: Set up a simple 2D Texture rendering loop.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, initValue);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLBuffer vertexBuffer;
    GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), vertices, GL_STATIC_DRAW);

    GLBuffer ssbo;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, 16, nullptr, GL_STREAM_DRAW);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, "a_position");
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Call glReadPixels to reset the getPerfCounters().renderPasses
    std::vector<GLColor> actualColors(1);
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, actualColors.data());

    // Ideally, the following "FS sample + CS image load + FS something", should
    // handle in one render pass.
    // Currently, we can ensure the first of "FS sample + CS image load" in one
    // render pass, but will start new render pass if following the last FS operations,
    // which need to be optimized further.
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 2;

    // Now this texture owns none layout transition
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    // Step 2: load this image through compute
    ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
    glUseProgram(csProgram);

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);
    glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    // Step3
    ANGLE_GL_PROGRAM(program2, kVSSource, kFSSource1);
    glUseProgram(program2);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Test that one texture sampled by fragment shader, compute shader and fragment
// shader sequentlly.
TEST_P(VulkanPerformanceCounterTest_ES31, TextureSampleByDrawDispatchDraw)
{
    constexpr char kVSSource[] = R"(#version 310 es
in vec4 a_position;
out vec2 v_texCoord;

void main()
{
gl_Position = vec4(a_position.xy, 0.0, 1.0);
v_texCoord = a_position.xy * 0.5 + vec2(0.5);
})";

    constexpr char kFSSource[] = R"(#version 310 es
uniform sampler2D u_tex2D;
precision highp float;
in vec2 v_texCoord;
out vec4 out_FragColor;
void main()
{
out_FragColor = texture(u_tex2D, v_texCoord);
})";

    constexpr char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
precision highp sampler2D;
uniform sampler2D tex;
layout(std140, binding=0) buffer buf {
vec4 outData;
};
void main()
{
uint x = gl_LocalInvocationID.x;
uint y = gl_LocalInvocationID.y;
outData = texture(tex, vec2(x, y));
})";

    GLfloat initValue[4] = {1.0, 1.0, 1.0, 1.0};

    // Step 1: Set up a simple 2D Texture rendering loop.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_FLOAT, initValue);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLfloat vertices[] = {-1, -1, 1, -1, -1, 1, 1, 1};

    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, 8 * sizeof(GLfloat), vertices, GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, kVSSource, kFSSource);
    glUseProgram(program);

    GLint posLoc = glGetAttribLocation(program, "a_position");
    ASSERT_NE(-1, posLoc);

    glVertexAttribPointer(posLoc, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(posLoc);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    ASSERT_GL_NO_ERROR();
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Step 2: sample this texture through compute
    GLBuffer ssbo;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, 16, nullptr, GL_STREAM_DRAW);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
    glUseProgram(csProgram);

    glBindTexture(GL_TEXTURE_2D, texture);
    glUniform1i(glGetUniformLocation(csProgram, "tex"), 0);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, ssbo);

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    // Step3: use the first program sample texture again
    glUseProgram(program);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    ASSERT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Verify a mid-render pass clear of a newly enabled attachment uses LOAD_OP_CLEAR.
TEST_P(VulkanPerformanceCounterTest, DisableThenMidRenderPassClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // This optimization is not implemented when this workaround is in effect.
    ANGLE_SKIP_TEST_IF(hasPreferDrawOverClearAttachments());

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+1, Loads+0, LoadNones+0, Stores+2, StoreNones+0)
    // vkCmdClearAttachments should be used for color attachment 0.
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 2, 0, &expected);
    expected.colorClearAttachments = getPerfCounters().colorClearAttachments + 1;

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLTexture textures[2];

    glBindTexture(GL_TEXTURE_2D, textures[0]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[0], 0);

    glBindTexture(GL_TEXTURE_2D, textures[1]);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, textures[1], 0);

    // Only enable attachment 0.
    GLenum drawBuffers[] = {GL_COLOR_ATTACHMENT0, GL_NONE};
    glDrawBuffers(2, drawBuffers);

    // Draw red.
    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Enable attachment 1.
    drawBuffers[1] = GL_COLOR_ATTACHMENT1;
    glDrawBuffers(2, drawBuffers);

    // Clear both attachments to green.
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
layout(location = 0) out vec4 my_FragColor0;
layout(location = 1) out vec4 my_FragColor1;
void main()
{
    my_FragColor0 = vec4(0.0, 0.0, 1.0, 1.0);
    my_FragColor1 = vec4(0.0, 0.0, 1.0, 1.0);
})";

    // Draw blue to both attachments.
    ANGLE_GL_PROGRAM(blueProgram, essl3_shaders::vs::Simple(), kFS);
    drawQuad(blueProgram, essl3_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Verify attachment 0.
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_EQ(0, 0, 0, 0, 255, 255);
    // Verify attachment 1.
    glReadBuffer(GL_COLOR_ATTACHMENT1);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_EQ(0, 0, 0, 0, 255, 255);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_CLEAR_ATTACHMENTS_COUNTER(expected.colorClearAttachments,
                                     getPerfCounters().colorClearAttachments);

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);

    GLFramebuffer fbo2;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textures[1], 0);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    // Blend red
    drawQuad(redProgram, essl3_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Verify purple
    glReadBuffer(GL_COLOR_ATTACHMENT0);
    EXPECT_PIXEL_EQ(0, 0, 255, 0, 255, 255);
    ASSERT_GL_NO_ERROR();

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Copy of ClearTest.InceptionScissorClears.
// Clears many small concentric rectangles using scissor regions. Verifies vkCmdClearAttachments()
// is used for the scissored clears, rather than vkCmdDraw().
TEST_P(VulkanPerformanceCounterTest, InceptionScissorClears)
{
    // https://issuetracker.google.com/166809097
    ANGLE_SKIP_TEST_IF(IsQualcomm() && IsVulkan());

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);

    angle::RNG rng;

    constexpr GLuint kSize = 16;

    // Create a square user FBO so we have more control over the dimensions.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLRenderbuffer rbo;
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSize, kSize);

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glViewport(0, 0, kSize, kSize);

    // Clear small concentric squares using scissor.
    std::vector<GLColor> expectedColors;
    // TODO(syoussefi): verify this
    [[maybe_unused]] uint64_t numScissoredClears = 0;
    for (GLuint index = 0; index < (kSize - 1) / 2; index++)
    {
        // Do the first clear without the scissor.
        if (index > 0)
        {
            glEnable(GL_SCISSOR_TEST);
            glScissor(index, index, kSize - (index * 2), kSize - (index * 2));
            ++numScissoredClears;
        }

        GLColor color = RandomColor(&rng);
        expectedColors.push_back(color);
        Vector4 floatColor = color.toNormalizedVector();
        glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
        glClear(GL_COLOR_BUFFER_BIT);
    }

    ASSERT_GL_NO_ERROR();

    // Make sure everything was done in a single renderpass.
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    std::vector<GLColor> actualColors(expectedColors.size());
    glReadPixels(0, kSize / 2, actualColors.size(), 1, GL_RGBA, GL_UNSIGNED_BYTE,
                 actualColors.data());

    EXPECT_EQ(expectedColors, actualColors);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Copy of ClearTest.Depth16Scissored.
// Clears many small concentric rectangles using scissor regions. Verifies vkCmdClearAttachments()
// is used for the scissored clears, rather than vkCmdDraw().
TEST_P(VulkanPerformanceCounterTest, Depth16Scissored)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLRenderbuffer renderbuffer;
    glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
    constexpr int kRenderbufferSize = 64;
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kRenderbufferSize,
                          kRenderbufferSize);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);

    glClearDepthf(0.0f);
    glClear(GL_DEPTH_BUFFER_BIT);

    glEnable(GL_SCISSOR_TEST);
    constexpr int kNumSteps = 13;
    // TODO(syoussefi): verify this
    [[maybe_unused]] uint64_t numScissoredClears = 0;
    for (int ndx = 1; ndx < kNumSteps; ndx++)
    {
        float perc = static_cast<float>(ndx) / static_cast<float>(kNumSteps);
        glScissor(0, 0, static_cast<int>(kRenderbufferSize * perc),
                  static_cast<int>(kRenderbufferSize * perc));
        glClearDepthf(perc);
        glClear(GL_DEPTH_BUFFER_BIT);
        ++numScissoredClears;
    }

    // Make sure everything was done in a single renderpass.
    EXPECT_EQ(getPerfCounters().renderPasses, 1u);
}

// Copy of ClearTest.InceptionScissorClears.
// Clears many small concentric rectangles using scissor regions.
TEST_P(VulkanPerformanceCounterTest, DrawThenInceptionScissorClears)
{
    // https://issuetracker.google.com/166809097
    ANGLE_SKIP_TEST_IF(IsQualcomm() && IsVulkan());

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);

    angle::RNG rng;
    std::vector<GLColor> expectedColors;
    constexpr GLuint kSize = 16;

    // Create a square user FBO so we have more control over the dimensions.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLRenderbuffer rbo;
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kSize, kSize);

    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    glViewport(0, 0, kSize, kSize);

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    expectedColors.push_back(GLColor::red);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    // TODO: Optimize scissored clears to use loadOp = CLEAR. anglebug.com/42263754
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);

    // Draw small concentric squares using scissor.
    // TODO(syoussefi): verify this
    [[maybe_unused]] uint64_t numScissoredClears = 0;
    // All clears are to a scissored render area.
    for (GLuint index = 1; index < (kSize - 1) / 2; index++)
    {
        glEnable(GL_SCISSOR_TEST);
        glScissor(index, index, kSize - (index * 2), kSize - (index * 2));
        ++numScissoredClears;

        GLColor color = RandomColor(&rng);
        expectedColors.push_back(color);
        Vector4 floatColor = color.toNormalizedVector();
        glClearColor(floatColor[0], floatColor[1], floatColor[2], floatColor[3]);
        glClear(GL_COLOR_BUFFER_BIT);
    }

    ASSERT_GL_NO_ERROR();

    // Make sure everything was done in a single renderpass.
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Close the render pass to update the performance counters.
    std::vector<GLColor> actualColors(expectedColors.size());
    glReadPixels(0, kSize / 2, actualColors.size(), 1, GL_RGBA, GL_UNSIGNED_BYTE,
                 actualColors.data());
    EXPECT_EQ(expectedColors, actualColors);

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Test that color clears are respected after invalidate
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, ColorClearAfterInvalidate)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForColorDepthOpsTest(&framebuffer, &texture, &renderbuffer);

    // Execute the scenario that this test is for:

    // color+depth invalidate, color+depth clear
    //
    // Expected:
    //   rpCount+1,
    //   depth(Clears+1, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    //   color(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 0, 1, 0, 0, 0, 0, &expected);
    setExpectedCountersForColorOps(getPerfCounters(), 1, 1, 0, 0, 1, 0, &expected);

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);

    // Invalidate (loadOp C=DONTCARE, D=DONTCARE)
    const GLenum discards[] = {GL_DEPTH_ATTACHMENT, GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 2, discards);
    ASSERT_GL_NO_ERROR();

    // Clear (loadOp C=CLEAR, D=CLEAR)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Save existing draw buffers
    GLint maxDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    std::vector<GLenum> savedDrawBuffers(maxDrawBuffers);
    for (int i = 0; i < maxDrawBuffers; i++)
        glGetIntegerv(GL_DRAW_BUFFER0 + i, (GLint *)&savedDrawBuffers[i]);

    // Draw depth-only
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glDrawBuffers(0, nullptr);
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    glDrawBuffers(maxDrawBuffers, savedDrawBuffers.data());
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    ASSERT_GL_NO_ERROR();

    // Invalidate depth only (storeOp should be C=STORE/D=CLEAR)
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done
    swapBuffers();
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
    ASSERT_GL_NO_ERROR();
}

// Test that depth clears are picked up as loadOp even if a color blit is done in between.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, DepthClearThenColorBlitThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForColorDepthOpsTest(&framebuffer, &texture, &renderbuffer);

    // Execute the scenario that this test is for:

    // color+depth clear, blit color as source, draw
    //
    // Expected:
    //   rpCount+1,
    //   depth(Clears+1, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    //   color(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForDepthOps(getPerfCounters(), 0, 1, 0, 0, 1, 0, &expected);
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);

    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClearColor(0, 1, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // Blit color into another FBO
    GLTexture destColor;
    glBindTexture(GL_TEXTURE_2D, destColor);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kOpsTestSize, kOpsTestSize);

    GLFramebuffer destFbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destFbo);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, destColor, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_DRAW_FRAMEBUFFER);

    glBlitFramebuffer(0, 0, kOpsTestSize, kOpsTestSize, 0, 0, kOpsTestSize, kOpsTestSize,
                      GL_COLOR_BUFFER_BIT, GL_NEAREST);

    // Draw back to the original framebuffer
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffer);
    drawQuad(program, essl1_shaders::PositionAttrib(), 1.f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    // Use swapBuffers and then check how many loads and stores were actually done.  The clear
    // applied to depth should be done as loadOp, not flushed during the blit.
    swapBuffers();
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
    adjustExpectedDepthStencilLoadStoreOpsForTileMemoryFallback(getPerfCounters(), &expected);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
    ASSERT_GL_NO_ERROR();

    // Verify results
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Result of blit should be green
    glBindFramebuffer(GL_READ_FRAMEBUFFER, destFbo);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Ensure that image gets marked as defined after clear + invalidate + clear, and that we use
// LoadOp=Load for a renderpass which draws to it after the clear has been flushed with a blit.
TEST_P(VulkanPerformanceCounterTest, InvalidateThenRepeatedClearThenBlitThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    constexpr GLsizei kSize = 2;

    // tex[0] is what's being tested.  The others are helpers.
    GLTexture tex[3];
    for (int i = 0; i < 3; ++i)
    {
        glBindTexture(GL_TEXTURE_2D, ANGLE_UNSAFE_TODO(tex[i]));
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     nullptr);
    }

    GLFramebuffer fbo[3];
    for (int i = 0; i < 3; ++i)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, ANGLE_UNSAFE_TODO(fbo[i]));
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
                               ANGLE_UNSAFE_TODO(tex[i]), 0);
    }

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);

    // Clear the image through fbo[0], and make sure the clear is flushed outside the render pass.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Invalidate it such that the contents are marked as undefined. Note that regardless of the
    // marking, the image is cleared nevertheless.
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);

    // Clear it again to the same color.
    glClear(GL_COLOR_BUFFER_BIT);

    // Bind tex[0] to fbo[1] as the read fbo, and blit to fbo[2]
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[1]);
    glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex[0], 0);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[2]);

    // Blit.  This causes the second clear of tex[0] to be flushed outside the render pass, which
    // may be optimized out.
    glBlitFramebuffer(0, 0, kSize, kSize, 0, 0, kSize, kSize, GL_COLOR_BUFFER_BIT, GL_NEAREST);

    // Switch back to fbo[0] and draw with blend.  If the second clear is dropped and the image
    // continues to be marked as invalidated, loadOp=DONT_CARE would be used instead of loadOp=LOAD.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo[0]);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0);

    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Ensure that image gets marked as defined after clear + invalidate + clear, and that we use
// LoadOp=Load for a renderpass which draws to it after the clear has been flushed with read pixels.
TEST_P(VulkanPerformanceCounterTest, InvalidateThenRepeatedClearThenReadbackThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    constexpr GLsizei kSize = 2;

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    // Expect rpCount+1, color(Clears+0, Loads+1, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 1, 0, 1, 0, &expected);

    // Clear the image, and make sure the clear is flushed outside the render pass.
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Invalidate it such that the contents are marked as undefined.  Note that regarldess of the
    // marking, the image is cleared nevertheless.
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);

    // Clear it again to the same color, and make sure the clear is flushed outside the render pass,
    // which may be optimized out.
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Draw with blend.  If the second clear is dropped and the image continues to be marked as
    // invalidated, loadOp=DONT_CARE would be used instead of loadOp=LOAD.
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Test that draw after invalidate restores the contents of the color image.
TEST_P(VulkanPerformanceCounterTest, InvalidateThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    constexpr GLsizei kSize = 2;

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+0, Stores+1, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 0, 1, 0, &expected);

    ANGLE_GL_PROGRAM(blue, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(blue, essl1_shaders::PositionAttrib(), 0);

    // Invalidate it such that the contents are marked as undefined
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);

    // Draw again.
    ANGLE_GL_PROGRAM(green, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(green, essl1_shaders::PositionAttrib(), 0);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Test that masked draw after invalidate does NOT restore the contents of the color image.
TEST_P(VulkanPerformanceCounterTest, InvalidateThenMaskedDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    constexpr GLsizei kSize = 2;

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kSize, kSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 0, 0, 0, &expected);

    ANGLE_GL_PROGRAM(blue, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(blue, essl1_shaders::PositionAttrib(), 0);

    // Invalidate it such that the contents are marked as undefined
    const GLenum discards[] = {GL_COLOR_ATTACHMENT0};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, discards);

    // Draw again.
    glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
    ANGLE_GL_PROGRAM(green, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(green, essl1_shaders::PositionAttrib(), 0);

    // Break the render pass
    glFinish();

    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);
}

// Tests that the submission counters count the implicit submission in eglSwapBuffers().
TEST_P(VulkanPerformanceCounterTest, VerifySubmitCountersForSwapBuffer)
{
    uint64_t expectedVkQueueSubmitCount      = getPerfCounters().vkQueueSubmitCallsTotal;
    uint64_t expectedCommandQueueSubmitCount = getPerfCounters().commandQueueSubmitCallsTotal;

    // One submission coming from clear and read back
    ++expectedVkQueueSubmitCount;
    ++expectedCommandQueueSubmitCount;

    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedVkQueueSubmitCount);
    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCount);

    // One submission coming from draw and implicit submission from eglSwapBuffers
    ++expectedVkQueueSubmitCount;
    ++expectedCommandQueueSubmitCount;

    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 1.f);
    swapBuffers();

    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedVkQueueSubmitCount);
    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCount);
}

// Tests that PreferSubmitAtFBOBoundary feature works properly. Bind to different FBO and should
// trigger submit of previous FBO. In this specific test, we switch to system default framebuffer
// which is always considered as "dirty".
TEST_P(VulkanPerformanceCounterTest, VerifySubmitCounterForSwitchUserFBOToSystemFramebuffer)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedCommandQueueSubmitCount = getPerfCounters().commandQueueSubmitCallsTotal;
    uint64_t expectedCommandQueueWaitSemaphoreCount =
        getPerfCounters().commandQueueWaitSemaphoresTotal;

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // One submission coming from glBindFramebuffer and draw
    ++expectedCommandQueueSubmitCount;
    // This submission should not wait for any semaphore.

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCount);
    EXPECT_EQ(getPerfCounters().commandQueueWaitSemaphoresTotal,
              expectedCommandQueueWaitSemaphoreCount);

    // This submission must wait for ANI's semaphore
    ++expectedCommandQueueWaitSemaphoreCount;
    ++expectedCommandQueueSubmitCount;
    swapBuffers();
    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCount);
    EXPECT_EQ(getPerfCounters().commandQueueWaitSemaphoresTotal,
              expectedCommandQueueWaitSemaphoreCount);
}

// Tests that PreferSubmitAtFBOBoundary feature works properly. Binding to a different FBO should
// trigger the submission of the previous FBO if enough workload has been accumulated. In this
// specific test, we test binding to a new user FBO after issuing the minimum amount of draw calls
// for submission.
TEST_P(VulkanPerformanceCounterTest, VerifySubmitCounterForSwitchUserFBOToDirtyUserFBO)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedCommandQueueSubmitCount = getPerfCounters().commandQueueSubmitCallsTotal;
    uint64_t expectedCommandQueueWaitSemaphoreCount =
        getPerfCounters().commandQueueWaitSemaphoresTotal;

    GLFramebuffer framebuffer1;
    GLTexture texture1;
    setupForColorOpsTest(&framebuffer1, &texture1);

    // Issue the draws. In case of preference to submit at FBO boundary, there is a minimum render
    // pass command count to submit. (Currently, the exception is if there is a clear or invalidate
    // command at the boundary, or if there are no in-flight commands. In those cases, the minimum
    // command count is ignored.)
    constexpr uint32_t kMinCommandCountToSubmit = 32;
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    for (uint32_t i = 0; i < kMinCommandCountToSubmit; i++)
    {
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    }
    ASSERT_GL_NO_ERROR();

    if (hasPreferSubmitAtFBOBoundary())
    {
        // One submission coming from glBindFramebuffer and draw calls.
        ++expectedCommandQueueSubmitCount;
        // This submission should not wait for any semaphore.
    }

    // Create and bind to a new FBO
    GLFramebuffer framebuffer2;
    GLTexture texture2;
    setupForColorOpsTest(&framebuffer2, &texture2);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    ++expectedCommandQueueSubmitCount;
    // This submission should not wait for ANI's semaphore
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCount);
    EXPECT_EQ(getPerfCounters().commandQueueWaitSemaphoresTotal,
              expectedCommandQueueWaitSemaphoreCount);
}

// Ensure that glFlush doesn't lead to vkQueueSubmit if there's nothing to submit.
TEST_P(VulkanPerformanceCounterTest, UnnecessaryFlushDoesntCauseSubmission)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    swapBuffers();
    uint64_t expectedVkQueueSubmitCalls = getPerfCounters().vkQueueSubmitCallsTotal;

    glFlush();
    glFlush();
    glFlush();

    // Nothing was recorded, so there shouldn't be anything to flush.
    glFinish();
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedVkQueueSubmitCalls);

    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // One submission for the above readback
    ++expectedVkQueueSubmitCalls;

    glFinish();
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedVkQueueSubmitCalls);

    glFlush();
    glFlush();
    glFlush();

    // No additional submissions since last one
    glFinish();
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedVkQueueSubmitCalls);
}

// Ensure that glFenceSync doesn't lead to vkQueueSubmit if there's nothing to submit.
TEST_P(VulkanPerformanceCounterTest, SyncWihtoutCommandsDoesntCauseSubmission)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    swapBuffers();
    uint64_t expectedVkQueueSubmitCalls = getPerfCounters().vkQueueSubmitCallsTotal;

    glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

    // Nothing was recorded, so there shouldn't be anything to flush.
    glFinish();
    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expectedVkQueueSubmitCalls);
}

// In single-buffer mode, ensure that unnecessary eglSwapBuffers is completely ignored (i.e. doesn't
// lead to a command queue submission, consuming a submission serial).  Used to verify an
// optimization that ensures CPU throttling doesn't incur GPU bubbles with unnecessary
// eglSwapBuffers calls.
TEST_P(VulkanPerformanceCounterTest_SingleBuffer, SwapBuffersAfterFlushIgnored)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    // Set mode to single buffer
    EXPECT_EGL_TRUE(eglSurfaceAttrib(getEGLWindow()->getDisplay(), getEGLWindow()->getSurface(),
                                     EGL_RENDER_BUFFER, EGL_SINGLE_BUFFER));

    // Swap buffers so mode switch takes effect.
    swapBuffers();
    uint64_t expectedCommandQueueSubmitCalls = getPerfCounters().commandQueueSubmitCallsTotal;

    // Further swap buffers should be ineffective.
    swapBuffers();
    swapBuffers();
    swapBuffers();
    swapBuffers();

    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCalls);

    // Issue commands and flush them.
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // One submission for the above readback
    ++expectedCommandQueueSubmitCalls;
    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCalls);

    // Further swap buffers should again be ineffective.
    swapBuffers();
    swapBuffers();
    swapBuffers();
    swapBuffers();
    swapBuffers();

    EXPECT_EQ(getPerfCounters().commandQueueSubmitCallsTotal, expectedCommandQueueSubmitCalls);
}

// Verifies that we share Texture descriptor sets between programs.
TEST_P(VulkanPerformanceCounterTest, TextureDescriptorsAreShared)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(testProgram1, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    ANGLE_GL_PROGRAM(testProgram2, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    setupQuadVertexBuffer(0.5f, 1.0f);

    glUseProgram(testProgram1);

    ASSERT_GL_NO_ERROR();

    glBindTexture(GL_TEXTURE_2D, texture1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindTexture(GL_TEXTURE_2D, texture2);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    ASSERT_GL_NO_ERROR();

    GLuint expectedCacheMisses = getPerfCounters().textureDescriptorSetCacheMisses;
    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        EXPECT_GT(expectedCacheMisses, 0u);
    }

    glUseProgram(testProgram2);

    glBindTexture(GL_TEXTURE_2D, texture1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindTexture(GL_TEXTURE_2D, texture2);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    ASSERT_GL_NO_ERROR();

    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        GLuint actualCacheMisses = getPerfCounters().textureDescriptorSetCacheMisses;
        EXPECT_EQ(expectedCacheMisses, actualCacheMisses);
    }
    else
    {
        GLint descriptorSetAllocations = getPerfCounters().descriptorSetAllocations;
        EXPECT_EQ(4, descriptorSetAllocations);
    }
}

// Verifies that we share Uniform Buffer descriptor sets between programs.
TEST_P(VulkanPerformanceCounterTest, UniformBufferDescriptorsAreShared)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform block {
   vec4 uniColor;
};

void main() {
    color = uniColor;
})";

    ANGLE_GL_PROGRAM(testProgram1, essl3_shaders::vs::Simple(), kFS);
    ANGLE_GL_PROGRAM(testProgram2, essl3_shaders::vs::Simple(), kFS);

    Vector4 red(1, 0, 0, 1);
    Vector4 green(0, 1, 0, 1);

    GLBuffer ubo1;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo1);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(Vector4), red.data(), GL_STATIC_DRAW);

    GLBuffer ubo2;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo2);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(Vector4), green.data(), GL_STATIC_DRAW);

    setupQuadVertexBuffer(0.5f, 1.0f);

    glUseProgram(testProgram1);

    ASSERT_GL_NO_ERROR();

    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo2);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    ASSERT_GL_NO_ERROR();

    GLuint expectedCacheMisses = getPerfCounters().uniformBuffersDescriptorSetCacheMisses;
    GLuint totalAllocationsAtFirstProgram = getPerfCounters().descriptorSetAllocations;
    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        EXPECT_GT(expectedCacheMisses, 0u);
    }

    glUseProgram(testProgram2);

    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo2);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    ASSERT_GL_NO_ERROR();

    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        GLuint actualCacheMisses = getPerfCounters().uniformBuffersDescriptorSetCacheMisses;
        EXPECT_EQ(expectedCacheMisses, actualCacheMisses);
    }
    else
    {
        // Because between program switch there is no submit and wait for finish, we expect draw
        // with second program will end up allocating same number of descriptorSets as the draws
        // with the first program.
        GLuint totalAllocationsAtSecondProgram = getPerfCounters().descriptorSetAllocations;
        EXPECT_EQ(totalAllocationsAtFirstProgram * 2, totalAllocationsAtSecondProgram);
    }
}

// Test modifying texture size and render to it does not cause VkFramebuffer cache explode
TEST_P(VulkanPerformanceCounterTest, ResizeFBOAttachedTexture)
{
    ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());

    int32_t framebufferCacheSizeBefore = getPerfCounters().framebufferCacheSize;
    GLTexture texture;
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    for (GLint texWidth = 1; texWidth <= 10; texWidth++)
    {
        for (GLint texHeight = 1; texHeight <= 10; texHeight++)
        {
            // Allocate texture
            glBindTexture(GL_TEXTURE_2D, texture);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, texWidth, texHeight, 0, GL_RGBA,
                         GL_UNSIGNED_BYTE, nullptr);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
            ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

            // Draw to FBO backed by the texture
            glUseProgram(blueProgram);
            drawQuad(blueProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
        }
    }
    int32_t framebufferCacheSizeAfter    = getPerfCounters().framebufferCacheSize;
    int32_t framebufferCacheSizeIncrease = framebufferCacheSizeAfter - framebufferCacheSizeBefore;
    int32_t expectedFramebufferCacheSizeIncrease = (hasSupportsImagelessFramebuffer()) ? 0 : 1;
    printf("\tframebufferCacheCountIncrease:%u\n", framebufferCacheSizeIncrease);
    // We should not cache obsolete VkImages. Only current VkImage should be cached.
    EXPECT_EQ(framebufferCacheSizeIncrease, expectedFramebufferCacheSizeIncrease);
}

// Test calling glTexParameteri(GL_TEXTURE_SWIZZLE_*) on a texture that attached to FBO with the
// same value did not cause VkFramebuffer cache explode
TEST_P(VulkanPerformanceCounterTest, SetTextureSwizzleWithSameValueOnFBOAttachedTexture)
{
    ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
                     essl1_shaders::fs::Texture2D());

    // Allocate texture
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    int32_t framebufferCacheSizeBefore = getPerfCounters().framebufferCacheSize;
    for (GLint loop = 0; loop < 10; loop++)
    {
        // Draw to FBO
        glBindFramebuffer(GL_FRAMEBUFFER, fbo);
        glUseProgram(blueProgram);
        drawQuad(blueProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);

        // Sample from texture
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
        glClearColor(1, 0, 0, 1);
        glClear(GL_COLOR_BUFFER_BIT);
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
        drawQuad(textureProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    }
    // Now make fbo current and read out cache size and verify it does not grow just because of
    // swizzle update even though there is no actual change.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glUseProgram(blueProgram);
    drawQuad(blueProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
    ASSERT_GL_NO_ERROR();
    int32_t framebufferCacheSizeAfter    = getPerfCounters().framebufferCacheSize;
    int32_t framebufferCacheSizeIncrease = framebufferCacheSizeAfter - framebufferCacheSizeBefore;
    int32_t expectedFramebufferCacheSizeIncrease = (hasSupportsImagelessFramebuffer()) ? 0 : 1;
    // This should not cause frame buffer cache increase.
    EXPECT_EQ(framebufferCacheSizeIncrease, expectedFramebufferCacheSizeIncrease);
}

// Test calling glTexParameteri(GL_TEXTURE_SWIZZLE_*) on a texture that attached to FBO with
// different value did not cause VkFramebuffer cache explode
TEST_P(VulkanPerformanceCounterTest, SetTextureSwizzleWithDifferentValueOnFBOAttachedTexture)
{
    ANGLE_GL_PROGRAM(blueProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
                     essl1_shaders::fs::Texture2D());

    // Allocate texture
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 16, 16, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_RED);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, GL_GREEN);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_BLUE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    GLColor expectedColors[] = {GLColor::black,   GLColor::black,  GLColor::blue,  GLColor::black,
                                GLColor::black,   GLColor::blue,   GLColor::green, GLColor::green,
                                GLColor::cyan,    GLColor::black,  GLColor::black, GLColor::blue,
                                GLColor::black,   GLColor::black,  GLColor::blue,  GLColor::green,
                                GLColor::green,   GLColor::cyan,   GLColor::red,   GLColor::red,
                                GLColor::magenta, GLColor::red,    GLColor::red,   GLColor::magenta,
                                GLColor::yellow,  GLColor::yellow, GLColor::white};
    int32_t framebufferCacheSizeBefore = getPerfCounters().framebufferCacheSize;
    int loop                           = 0;
    for (GLenum swizzle_R = GL_RED; swizzle_R <= GL_BLUE; swizzle_R++)
    {
        for (GLenum swizzle_G = GL_RED; swizzle_G <= GL_BLUE; swizzle_G++)
        {
            for (GLenum swizzle_B = GL_RED; swizzle_B <= GL_BLUE; swizzle_B++)
            {
                // Draw to FBO
                glBindFramebuffer(GL_FRAMEBUFFER, fbo);
                glUseProgram(blueProgram);
                drawQuad(blueProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);

                // Sample from texture
                glBindFramebuffer(GL_FRAMEBUFFER, 0);
                glClearColor(1, 0, 0, 1);
                glClear(GL_COLOR_BUFFER_BIT);
                glBindTexture(GL_TEXTURE_2D, texture);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, swizzle_R);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, swizzle_G);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, swizzle_B);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, GL_ALPHA);
                drawQuad(textureProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
                ANGLE_UNSAFE_TODO(EXPECT_PIXEL_COLOR_EQ(0, 0, expectedColors[loop]));
                loop++;
            }
        }
    }
    // Now make fbo current and read out cache size and verify it does not grow just because of
    // swizzle update even though there is no actual change.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glUseProgram(blueProgram);
    drawQuad(blueProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
    ASSERT_GL_NO_ERROR();
    int32_t framebufferCacheSizeAfter    = getPerfCounters().framebufferCacheSize;
    int32_t framebufferCacheSizeIncrease = framebufferCacheSizeAfter - framebufferCacheSizeBefore;
    int32_t expectedFramebufferCacheSizeIncrease = (hasSupportsImagelessFramebuffer()) ? 0 : 1;
    // This should not cause frame buffer cache increase.
    EXPECT_EQ(framebufferCacheSizeIncrease, expectedFramebufferCacheSizeIncrease);
}

// Test calling glEGLImageTargetTexture2DOES repeatedly with same arguments will not leak
// DescriptorSets. This is the same usage pattern surafceflinger is doing with notification shades
// except with AHB.
TEST_P(VulkanPerformanceCounterTest, Source2DAndRepeatedlyRespecifyTarget2DWithSameParameter)
{
    EGLWindow *window = getEGLWindow();
    EGLDisplay dpy    = window->getDisplay();
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_EGL_image") ||
                       !IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_image_base") ||
                       !IsEGLDisplayExtensionEnabled(dpy, "EGL_KHR_gl_texture_2D_image"));

    ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
                     essl1_shaders::fs::Texture2D());

    // Create a source 2D texture
    GLTexture sourceTexture;
    glBindTexture(GL_TEXTURE_2D, sourceTexture);
    GLubyte kLinearColor[] = {132, 55, 219, 255};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 static_cast<void *>(&kLinearColor));
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();
    // Create an eglImage from the source texture
    constexpr EGLint kDefaultAttribs[] = {EGL_IMAGE_PRESERVED, EGL_TRUE, EGL_NONE};
    EGLClientBuffer clientBuffer =
        reinterpret_cast<EGLClientBuffer>(static_cast<size_t>(sourceTexture));
    EGLImageKHR image = eglCreateImageKHR(window->getDisplay(), window->getContext(),
                                          EGL_GL_TEXTURE_2D_KHR, clientBuffer, kDefaultAttribs);
    ASSERT_EGL_SUCCESS();

    // Create the target in a loop
    GLTexture targetTexture;
    constexpr size_t kMaxLoop            = 20;
    GLint descriptorSetAllocationsBefore = 0;
    GLint textureDescriptorSetCacheTotalSizeBefore =
        getPerfCounters().textureDescriptorSetCacheTotalSize;
    for (size_t loop = 0; loop < kMaxLoop; loop++)
    {
        //  Create a target texture from the image
        glBindTexture(GL_TEXTURE_2D, targetTexture);
        glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, image);

        // Disable mipmapping
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

        // Draw a quad with the target texture
        glBindTexture(GL_TEXTURE_2D, targetTexture);
        drawQuad(textureProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
        // Expect that the rendered quad's color is the same as the reference color with a tolerance
        // of 1
        EXPECT_PIXEL_NEAR(0, 0, kLinearColor[0], kLinearColor[1], kLinearColor[2], kLinearColor[3],
                          1);
        if (loop == 0)
        {
            descriptorSetAllocationsBefore = getPerfCounters().descriptorSetAllocations;
        }
    }
    size_t descriptorSetAllocationsIncrease =
        getPerfCounters().descriptorSetAllocations - descriptorSetAllocationsBefore;
    GLint textureDescriptorSetCacheTotalSizeIncrease =
        getPerfCounters().textureDescriptorSetCacheTotalSize -
        textureDescriptorSetCacheTotalSizeBefore;

    if (isFeatureEnabled(Feature::DescriptorSetCache))
    {
        // We don't expect descriptorSet cache to keep growing
        EXPECT_EQ(1, textureDescriptorSetCacheTotalSizeIncrease);
    }
    else
    {
        // Because we call EXPECT_PIXEL_NEAR which will wait for draw to finish, we don't expect
        // descriptorSet allocation to keep growing. The underline reuse logic is implementation
        // detail (as of now it will not reuse util the pool is full), but we expect it will not
        // increasing for every iteration.
        EXPECT_GE(kMaxLoop - 1, descriptorSetAllocationsIncrease);
    }
    // Clean up
    eglDestroyImageKHR(window->getDisplay(), image);
}

// Test create, use and then destroy a texture does not increase number of DescriptoprSets.
// DesriptorSet should be destroyed promptly. We are seeing this type of usage pattern in
// surfaceflinger, except that the texture is created from AHB (and AHB keeps changing as well).
TEST_P(VulkanPerformanceCounterTest, CreateDestroyTextureDoesNotIncreaseDescriptporSetCache)
{
    ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
                     essl1_shaders::fs::Texture2D());

    // Respecify texture in a loop
    GLubyte kLinearColor[]    = {132, 55, 219, 255};
    constexpr size_t kMaxLoop = 2;
    GLint textureDescriptorSetCacheTotalSizeBefore =
        getPerfCounters().textureDescriptorSetCacheTotalSize;
    for (size_t loop = 0; loop < kMaxLoop; loop++)
    {
        // Create a 2D texture
        GLTexture sourceTexture;
        glBindTexture(GL_TEXTURE_2D, sourceTexture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     static_cast<void *>(&kLinearColor));
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        ASSERT_GL_NO_ERROR();

        drawQuad(textureProgram, std::string(essl1_shaders::PositionAttrib()), 0.0f);
        // Expect that the rendered quad's color is the same as the reference color with a tolerance
        // of 1
        EXPECT_PIXEL_NEAR(0, 0, kLinearColor[0], kLinearColor[1], kLinearColor[2], kLinearColor[3],
                          1);
    }
    GLint textureDescriptorSetCacheTotalSizeIncrease =
        getPerfCounters().textureDescriptorSetCacheTotalSize -
        textureDescriptorSetCacheTotalSizeBefore;

    // We don't expect descriptorSet cache to keep growing
    EXPECT_EQ(0, textureDescriptorSetCacheTotalSizeIncrease);
}

// Similar to CreateDestroyTextureDoesNotIncreaseDescriptporSetCache, but for shader image.
TEST_P(VulkanPerformanceCounterTest_ES31,
       CreateDestroyTextureDoesNotIncreaseComputeShaderDescriptporSetCache)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(r32ui, binding = 0) readonly uniform highp uimage2D uImage_1;
layout(r32ui, binding = 1) writeonly uniform highp uimage2D uImage_2;
void main()
{
    uvec4 value = imageLoad(uImage_1, ivec2(gl_LocalInvocationID.xy));
    imageStore(uImage_2, ivec2(gl_LocalInvocationID.xy), value);
})";
    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    glUseProgram(program);

    constexpr int kWidth = 1, kHeight = 1;
    constexpr GLuint kInputValues[2][1] = {{200}, {100}};
    constexpr size_t kMaxLoop           = 20;
    GLint shaderResourceDescriptorSetCacheTotalSizeBefore =
        getPerfCounters().shaderResourcesDescriptorSetCacheTotalSize;
    for (size_t loop = 0; loop < kMaxLoop; loop++)
    {
        // Respecify texture in a loop
        GLTexture texture0;
        glBindTexture(GL_TEXTURE_2D, texture0);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
                        kInputValues[0]);

        GLTexture texture1;
        glBindTexture(GL_TEXTURE_2D, texture1);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, kWidth, kHeight);
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RED_INTEGER, GL_UNSIGNED_INT,
                        kInputValues[1]);

        glBindImageTexture(0, texture0, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
        glBindImageTexture(1, texture1, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_R32UI);
        glDispatchCompute(1, 1, 1);
    }
    glFinish();
    GLint shaderResourceDescriptorSetCacheTotalSizeIncrease =
        getPerfCounters().shaderResourcesDescriptorSetCacheTotalSize -
        shaderResourceDescriptorSetCacheTotalSizeBefore;

    // We don't expect descriptorSet cache to keep growing
    EXPECT_EQ(0, shaderResourceDescriptorSetCacheTotalSizeIncrease);
}

// Similar to CreateDestroyTextureDoesNotIncreaseDescriptporSetCache, but for atomic acounter
// buffer.
TEST_P(VulkanPerformanceCounterTest_ES31, DestroyAtomicCounterBufferAlsoDestroyDescriptporSetCache)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    constexpr char kFS[] =
        "#version 310 es\n"
        "precision highp float;\n"
        "layout(binding = 0, offset = 4) uniform atomic_uint ac;\n"
        "out highp vec4 my_color;\n"
        "void main()\n"
        "{\n"
        "    uint a1 = atomicCounter(ac);\n"
        "    my_color = vec4(float(a1)/255.0, 0.0, 0.0, 1.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);

    // Warm up. Make a draw to ensure other descriptorSets are created if needed.
    GLBuffer intialBuffer;
    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, intialBuffer);
    uint32_t bufferData[3] = {0, 0u, 0u};
    glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(bufferData), bufferData, GL_STATIC_DRAW);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, intialBuffer);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.0f);
    GLColor expectedColor = GLColor::black;
    EXPECT_PIXEL_COLOR_EQ(0, 0, expectedColor);

    GLint DescriptorSetCacheTotalSizeBefore = getPerfCounters().descriptorSetCacheTotalSize;

    // Create atomic counter buffer and use it and then destroy it.
    constexpr int kBufferCount = 16;
    GLBuffer paddingBuffers[kBufferCount];
    for (uint32_t i = 0; i < kBufferCount; i++)
    {
        // Allocate a padding buffer so that atomicCounterBuffer will be allocated in different
        // offset
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, ANGLE_UNSAFE_TODO(paddingBuffers[i]));
        glBufferData(GL_ATOMIC_COUNTER_BUFFER, 256, nullptr, GL_STATIC_DRAW);
        // Allocate, use, destroy atomic counter buffer
        GLBuffer atomicCounterBuffer;
        glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
        bufferData[1] = i;
        glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(bufferData), bufferData, GL_STATIC_DRAW);
        glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
        drawQuad(program, essl31_shaders::PositionAttrib(), 0.0f);
        expectedColor.R = bufferData[1];
        EXPECT_PIXEL_COLOR_EQ(0, 0, expectedColor);
    }
    ASSERT_GL_NO_ERROR();

    GLint DescriptorSetCacheTotalSizeIncrease =
        getPerfCounters().descriptorSetCacheTotalSize - DescriptorSetCacheTotalSizeBefore;
    // We expect most of descriptorSet caches for temporary atomic counter buffers gets destroyed.
    // Give extra room in case a new descriptorSet is allocated due to a new driver uniform buffer
    // gets allocated.
    EXPECT_LT(DescriptorSetCacheTotalSizeIncrease, 2);
}

// Regression test for a bug in the Vulkan backend, where a perf warning added after
// serials for outside render pass commands were exhausted led to an assertion failure.
// http://issuetracker.google.com/375661776
TEST_P(VulkanPerformanceCounterTest_ES31, RunOutOfReservedOutsideRenderPassSerial)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Step 1: Draw to start a render pass.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    // Step 2: Switch to fbo and clear it to set mRenderPassCommandBuffer to null,
    // but keep the render pass open.
    GLTexture fbTexture;
    glBindTexture(GL_TEXTURE_2D, fbTexture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbTexture, 0);

    glClear(GL_COLOR_BUFFER_BIT);

    // Step 3: Run > kMaxReservedOutsideRenderPassQueueSerials compute with glMemoryBarrier
    // to trigger the "Running out of reserved outsideRenderPass queueSerial" path.
    constexpr char kCSSource[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(std140, binding=0) buffer buf {
vec4 outData;
};
void main()
{
outData = vec4(1.0, 1.0, 1.0, 1.0);
})";

    ANGLE_GL_COMPUTE_PROGRAM(csProgram, kCSSource);
    glUseProgram(csProgram);

    GLBuffer ssbo;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, 16, nullptr, GL_STREAM_DRAW);

    for (int i = 0; i < 100; i++)
    {
        glMemoryBarrier(GL_SHADER_STORAGE_BARRIER_BIT);
        glDispatchCompute(1, 1, 1);
    }
    EXPECT_GL_NO_ERROR();

    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
}

// Test that post-render-pass-to-swapchain glFenceSync followed by eglSwapBuffers incurs only a
// single submission.
TEST_P(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps, FenceThenSwapBuffers)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    angle::VulkanPerfCounters expected;

    // Expect rpCount+1, depth(Clears+0, Loads+0, LoadNones+0, Stores+0, StoreNones+0),
    setExpectedCountersForDepthOps(getPerfCounters(), 1, 0, 0, 0, 0, 0, &expected);
    expected.vkQueueSubmitCallsTotal = getPerfCounters().vkQueueSubmitCallsTotal + 1;

    // Start a render pass and render to the surface.  Enable depth write so the depth/stencil image
    // is written to.
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    // Issue a fence
    glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);

    EXPECT_EQ(getPerfCounters().renderPasses, expected.renderPasses);

    // Swap buffers.  The depth/stencil attachment's storeOp should be optimized to DONT_CARE.  This
    // would not have been possible if the previous glFenceSync caused a submission.
    swapBuffers();

    EXPECT_EQ(getPerfCounters().vkQueueSubmitCallsTotal, expected.vkQueueSubmitCallsTotal);
    EXPECT_DEPTH_OP_COUNTERS(getPerfCounters(), expected);
}

// Verify that ending transform feedback after a render pass is closed, doesn't cause the following
// render pass to close when the transform feedback buffer is used.
TEST_P(VulkanPerformanceCounterTest, EndXfbAfterRenderPassClosed)
{
    // There should be two render passes; one for the transform feedback draw, one for the other two
    // draw calls.
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 2;

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Set the program's transform feedback varyings (just gl_Position)
    std::vector<std::string> tfVaryings;
    tfVaryings.push_back("gl_Position");
    ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(drawRed, essl3_shaders::vs::SimpleForPoints(),
                                        essl3_shaders::fs::Red(), tfVaryings,
                                        GL_INTERLEAVED_ATTRIBS);

    glUseProgram(drawRed);
    GLint positionLocation = glGetAttribLocation(drawRed, essl1_shaders::PositionAttrib());

    const GLfloat vertices[] = {
        -0.5f, 0.5f, 0.5f, -0.5f, -0.5f, 0.5f, 0.5f, -0.5f, 0.5f,
        -0.5f, 0.5f, 0.5f, 0.5f,  -0.5f, 0.5f, 0.5f, 0.5f,  0.5f,
    };

    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, vertices);
    glEnableVertexAttribArray(positionLocation);

    // Bind the buffer for transform feedback output and start transform feedback
    GLBuffer xfbBuffer;
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 100, nullptr, GL_STATIC_DRAW);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
    glBeginTransformFeedback(GL_POINTS);

    glDrawArrays(GL_POINTS, 0, 6);

    // Break the render pass
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);

    // End transform feedback after the render pass is closed
    glEndTransformFeedback();

    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
    EXPECT_GL_NO_ERROR();

    // Issue an unrelated draw call.
    ANGLE_GL_PROGRAM(drawGreen, essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());
    drawQuad(drawGreen, essl3_shaders::PositionAttrib(), 0.0f);

    // Issue a draw call using the transform feedback buffer.
    glBindBuffer(GL_ARRAY_BUFFER, xfbBuffer);
    glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(positionLocation);

    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glUseProgram(drawRed);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    const int w = getWindowWidth();
    const int h = getWindowHeight();

    EXPECT_PIXEL_RECT_EQ(0, 0, w, h / 4, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, 3 * h / 4, w, h / 4, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, h / 4, w / 4, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(3 * w / 4, h / 4, w / 4, h / 2, GLColor::black);

    EXPECT_PIXEL_RECT_EQ(w / 4, h / 4, w / 2, h / 2, GLColor::red);
    EXPECT_GL_NO_ERROR();

    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);
}

// Verify that monolithic pipeline handles correctly replace the linked pipelines, if
// VK_EXT_graphics_pipeline_library is supported.
TEST_P(VulkanPerformanceCounterTest, AsyncMonolithicPipelineCreation)
{
    const bool hasAsyncMonolithicPipelineCreation =
        isFeatureEnabled(Feature::SupportsGraphicsPipelineLibrary) &&
        isFeatureEnabled(Feature::PreferMonolithicPipelinesOverLibraries);
    ANGLE_SKIP_TEST_IF(!hasAsyncMonolithicPipelineCreation);

    uint64_t expectedMonolithicPipelineCreationCount =
        getPerfCounters().monolithicPipelineCreation + 2;

    // Create two programs:
    ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
    ANGLE_GL_PROGRAM(drawGreen, essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());

    // Ping pong between the programs, letting async monolithic pipeline creation happen.
    uint32_t drawCount                 = 0;
    constexpr uint32_t kDrawCountLimit = 200;

    while (getPerfCounters().monolithicPipelineCreation < expectedMonolithicPipelineCreationCount)
    {
        drawQuad(drawGreen, essl3_shaders::PositionAttrib(), 0.0f);
        drawQuad(drawRed, essl3_shaders::PositionAttrib(), 0.0f);

        ++drawCount;
        if (drawCount > kDrawCountLimit)
        {
            drawCount = 0;
            EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
        }
    }

    // Make sure the monolithic pipelines are replaced correctly
    drawQuad(drawGreen, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    drawQuad(drawRed, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Verify that changing framebuffer and back doesn't break the render pass.
TEST_P(VulkanPerformanceCounterTest, FBOChangeAndBackDoesNotBreakRenderPass)
{
    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0);

    // Verify render pass count.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Verify that changing framebuffer and issue a (nop or deferred) clear and change framebuffer back
// doesn't break the render pass.
TEST_P(VulkanPerformanceCounterTest, FBOChangeAndClearAndBackDoesNotBreakRenderPass)
{
    // switch to FBO and issue clear on fbo and then switch back to default framebuffer
    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);  // clear to green
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0);

    // switch to FBO and issue a deferred clear on fbo and then switch back to default framebuffer
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glClear(GL_COLOR_BUFFER_BIT);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0);

    // Verify render pass count.
    EXPECT_EQ(getPerfCounters().renderPasses, expectedRenderPassCount);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that just switching programs without breaking the renderpass
// doesn't cause updates to GraphicsDriverUniforms
TEST_P(VulkanPerformanceCounterTest, NoUpdatesToGraphicsDriverUniformsOnProgramChange)
{
    // Create 2 programs and switch between them
    ANGLE_GL_PROGRAM(program1, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    glUseProgram(program1);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program1, essl1_shaders::PositionAttrib(), 0);
    uint64_t program1Count = getPerfCounters().graphicsDriverUniformsUpdated;

    glUseProgram(program2);
    drawQuad(program2, essl1_shaders::PositionAttrib(), 0);
    uint64_t program2Count = getPerfCounters().graphicsDriverUniformsUpdated;

    // Verify vkCmdUpdatePushConstants perf counter
    EXPECT_EQ(program1Count, program2Count);

    glUseProgram(program1);
    drawQuad(program1, essl1_shaders::PositionAttrib(), 0);
    program1Count = getPerfCounters().graphicsDriverUniformsUpdated;

    // Verify vkCmdUpdatePushConstants perf counter
    EXPECT_EQ(program1Count, program2Count);

    // Verify pixel color
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Now that we have broken the render pass expect a call to vkCmdUpdatePushConstants
    // even if using the same program
    glUseProgram(program1);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program1, essl1_shaders::PositionAttrib(), 0);
    program1Count = getPerfCounters().graphicsDriverUniformsUpdated;

    // Verify vkCmdUpdatePushConstants perf counter
    EXPECT_EQ(program1Count, program2Count + 1);
}

// This is test for optimization in vulkan backend. efootball_pes_2021 usage shows this usage
// pattern and we expect implementation to reuse the storage for performance.
TEST_P(VulkanPerformanceCounterTest,
       BufferDataWithSizeFollowedByZeroAndThenSizeAgainShouldReuseStorage)
{
    GLBuffer buffer;
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    constexpr size_t count = 288;
    std::array<uint32_t, count> data;
    constexpr size_t bufferSize = data.size() * sizeof(uint32_t);
    data.fill(0x1234567);

    glBufferData(GL_ARRAY_BUFFER, bufferSize, data.data(), GL_DYNAMIC_DRAW);
    glBufferData(GL_ARRAY_BUFFER, 0, nullptr, GL_DYNAMIC_DRAW);
    // This should get back the original storage with proper BufferVk optimization
    data.fill(0x89abcdef);
    uint64_t expectedSuballocationCalls = getPerfCounters().bufferSuballocationCalls;
    glBufferData(GL_ARRAY_BUFFER, bufferSize, data.data(), GL_DYNAMIC_DRAW);
    EXPECT_EQ(getPerfCounters().bufferSuballocationCalls, expectedSuballocationCalls);

    uint32_t *mapPtr = reinterpret_cast<uint32_t *>(
        glMapBufferRange(GL_ARRAY_BUFFER, 0, bufferSize, GL_MAP_READ_BIT));
    ASSERT_NE(nullptr, mapPtr);
    EXPECT_EQ(0x89abcdef, mapPtr[0]);
    ANGLE_UNSAFE_TODO(EXPECT_EQ(0x89abcdef, mapPtr[count - 1]));
    glUnmapBuffer(GL_ARRAY_BUFFER);
    ASSERT_GL_NO_ERROR();
}

// Regression test for a bug where submitting the outside command buffer during flushing staged
// updates did not properly update the command buffer state.
TEST_P(VulkanPerformanceCounterTest, SubmittingOutsideCommandBufferAssertIsOpen)
{
    // If VK_EXT_host_image_copy is used, uploads will all be done on the CPU and there would be no
    // submissions.
    ANGLE_SKIP_TEST_IF(hasSupportsHostImageCopy());

    GLTexture tex;
    GLFramebuffer fbo;
    setupForColorOpsTest(&fbo, &tex);

    uint64_t submitCommandsCount = getPerfCounters().vkQueueSubmitCallsTotal;

    ANGLE_GL_PROGRAM(textureProgram, essl1_shaders::vs::Texture2D(),
                     essl1_shaders::fs::Texture2D());
    glUseProgram(textureProgram);
    GLint textureLoc = glGetUniformLocation(textureProgram, essl1_shaders::Texture2DUniform());
    ASSERT_NE(-1, textureLoc);
    glUniform1i(textureLoc, 0);

    // This loop should update texture with multiple staged updates. When kMaxBufferToImageCopySize
    // threshold reached, outside command buffer will be submitted in the middle of staged updates
    // flushing.
    constexpr GLsizei kMaxOutsideRPCommandsSubmitCount = 10;
    constexpr GLsizei kTexDim                          = 1024;
    constexpr GLint kMaxSubOffset                      = 10;
    std::vector<GLColor> kInitialData(kTexDim * kTexDim, GLColor::green);
    while (getPerfCounters().vkQueueSubmitCallsTotal <
           submitCommandsCount + kMaxOutsideRPCommandsSubmitCount)
    {
        GLTexture newTexture;
        glBindTexture(GL_TEXTURE_2D, newTexture);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        // Provide data for the texture without previously defining a storage.
        // This should prevent immediate staged update flushing.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTexDim, kTexDim, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                     kInitialData.data());
        // Append staged updates...
        for (GLsizei offset = 1; offset <= kMaxSubOffset; ++offset)
        {
            glTexSubImage2D(GL_TEXTURE_2D, 0, offset, offset, kTexDim - offset, kTexDim - offset,
                            GL_RGBA, GL_UNSIGNED_BYTE, kInitialData.data());
        }
        // This will flush multiple staged updates
        drawQuad(textureProgram, essl1_shaders::PositionAttrib(), 0.5f);
        ASSERT_GL_NO_ERROR();
    }

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Demonstrate the WAW syncVal bug https://anglebug.com/400789178
TEST_P(VulkanPerformanceCounterTest, DepthBufferWriteThenReadThenWriteInThreeRenderPasses)
{
    ANGLE_GL_PROGRAM(drawGreen, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    ANGLE_GL_PROGRAM(drawBlue, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());

    constexpr GLsizei kWidth  = 4;
    constexpr GLsizei kHeight = 4;

    GLTexture colorTexture1;
    glBindTexture(GL_TEXTURE_2D, colorTexture1);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);

    GLTexture colorTexture2;
    glBindTexture(GL_TEXTURE_2D, colorTexture2);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);

    GLRenderbuffer depthStencil;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kWidth, kHeight);

    GLFramebuffer fbo1;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture1, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencil);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencil);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glViewport(0, 0, kWidth, kHeight);

    GLFramebuffer fbo2;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexture2, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthStencil);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, depthStencil);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glViewport(0, 0, kWidth, kHeight);

    GLfloat depthValue = 0.0f;
    // Draw to fbo1 with depth buffer write
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(depthValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    glDepthMask(GL_TRUE);
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), depthValue);

    // Draw to fbo2 with read only depth buffer
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glClear(GL_COLOR_BUFFER_BIT);
    glDepthMask(GL_FALSE);
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), depthValue);

    std::array<GLenum, 2> attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 2, attachments.data());

    // Draw to fbo1 with depth buffer write
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glDepthMask(GL_TRUE);
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), depthValue);
    glFinish();
}

// Verifies that clear followed by eglSwapBuffers() on multisampled FBO does not result
// extra resolve pass, if the surface is double-buffered and when the egl swap behavior
// is EGL_BUFFER_DESTROYED
TEST_P(VulkanPerformanceCounterTest_MSAA, SwapAfterClearOnMultisampledFBOShouldNotResolve)
{
    // Skip test if
    // 1) the EGL Surface is single-buffered or
    // 2) the EGL_SWAP_BEHAVIOR is EGL_BUFFER_PRESERVED
    EGLint renderBufferType;
    eglQuerySurface(getEGLWindow()->getDisplay(), getEGLWindow()->getSurface(), EGL_RENDER_BUFFER,
                    &renderBufferType);
    EGLint swapBehavior;
    eglQuerySurface(getEGLWindow()->getDisplay(), getEGLWindow()->getSurface(), EGL_SWAP_BEHAVIOR,
                    &swapBehavior);
    ANGLE_SKIP_TEST_IF(
        !(renderBufferType == EGL_BACK_BUFFER && swapBehavior == EGL_BUFFER_DESTROYED));

    uint32_t expectedResolvesSubpass = getPerfCounters().swapchainResolveInSubpass;
    uint32_t expectedResolvesOutside = getPerfCounters().swapchainResolveOutsideSubpass;
    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    swapBuffers();
    EXPECT_EQ(getPerfCounters().swapchainResolveInSubpass, expectedResolvesSubpass);
    EXPECT_EQ(getPerfCounters().swapchainResolveOutsideSubpass, expectedResolvesOutside);
}

// MSAA draw and the blit that uses sub-pass resolve and then followed by masked clear
TEST_P(VulkanPerformanceCounterTest_MSAA, MultisampleDrawThenBlitThenMaskedClear)
{
    // http://anglebug.com/40096654
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    ANGLE_GL_PROGRAM(drawRed, essl3_shaders::vs::Simple(), essl3_shaders::fs::Red());
    ANGLE_GL_PROGRAM(drawGreen, essl3_shaders::vs::Simple(), essl3_shaders::fs::Green());
    ANGLE_GL_PROGRAM(drawBlue, essl3_shaders::vs::Simple(), essl3_shaders::fs::Blue());

    constexpr GLsizei kWidth  = 256;
    constexpr GLsizei kHeight = 256;
    // Setup framebufferResolved and clear to black
    GLRenderbuffer colorResolved;
    glBindRenderbuffer(GL_RENDERBUFFER, colorResolved);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, kWidth, kHeight);
    GLFramebuffer framebufferResolved;
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferResolved);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorResolved);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(255, 0, GLColor::black);

    uint64_t renderPassesBefore = getPerfCounters().renderPasses;

    // Setup framebufferMS, clear color/depth attachments, and draw quad
    GLsizei kSamples = 2;
    GLRenderbuffer depthMS;
    glBindRenderbuffer(GL_RENDERBUFFER, depthMS);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, kSamples, GL_DEPTH_COMPONENT24,
                                     kSamples * kWidth, kSamples * kHeight);
    GLRenderbuffer colorMS;
    glBindRenderbuffer(GL_RENDERBUFFER, colorMS);
    glRenderbufferStorageMultisample(GL_RENDERBUFFER, kSamples, GL_RGBA8, kSamples * kWidth,
                                     kSamples * kHeight);
    GLFramebuffer framebufferMS;
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferMS);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthMS);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorMS);
    // Clear depth buffer to 0.5 and color to blue.
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    GLfloat depthValue = 0.0f;
    glClearDepthf(depthValue * 0.5f + 0.5f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);

    // Resolve the color buffer to make sure the above draw worked correctly, which in turn implies
    // that the multi-sampled depth clear worked.
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferResolved);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferMS);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
                      GL_NEAREST);

    // Blit should reuse the render pass of draw.
    EXPECT_EQ(1u, getPerfCounters().renderPasses - renderPassesBefore);

    // Bind framebufferMS and do masked clear of blue channel
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferMS);
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glColorMask(false, false, true, false);
    glClear(GL_COLOR_BUFFER_BIT);

    // Masked clear should not reuse the previous render pass.
    EXPECT_EQ(2u, getPerfCounters().renderPasses - renderPassesBefore);

    // Now verify the data
    glBindFramebuffer(GL_FRAMEBUFFER, framebufferResolved);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferMS);
    // This should use resolveColorWithCommand
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_COLOR_BUFFER_BIT,
                      GL_NEAREST);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, framebufferResolved);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
    EXPECT_PIXEL_COLOR_EQ(255, 255, GLColor::blue);
    EXPECT_EQ(2u, getPerfCounters().renderPasses - renderPassesBefore);
}

// Test that swapchain does not get necessary recreation with 90 or 270 emulated pre-rotation
TEST_P(VulkanPerformanceCounterTest_Prerotation, swapchainCreateCounterTest)
{
    uint64_t expectedSwapchainCreateCounter = getPerfCounters().swapchainCreate;
    for (uint32_t i = 0; i < 10; ++i)
    {
        swapBuffers();
    }
    EXPECT_EQ(getPerfCounters().swapchainCreate, expectedSwapchainCreateCounter);
}

// Test that fully overwriting a sampled texture does not close the render pass due to texture
// ghosting.
TEST_P(VulkanPerformanceCounterTest, TextureOverwriteDoesNotBreakRenderPass)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
    glUseProgram(program);
    GLint textureLoc = glGetUniformLocation(program, essl1_shaders::Texture2DUniform());
    ASSERT_NE(-1, textureLoc);

    constexpr uint32_t kWidth  = 16;
    constexpr uint32_t kHeight = 24;

    const std::vector<GLColor> texDataRed(kWidth * kHeight, GLColor::red);
    const std::vector<GLColor> texDataGreen(kWidth * kHeight, GLColor::green);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 texDataRed.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

    // Draw multiple times, completely overwriting the texture each time.
    for (uint32_t i = 0; i < 10; ++i)
    {
        glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                        i % 2 == 0 ? texDataGreen.data() : texDataRed.data());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    }

    // There should be only one render pass.
    uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
    EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);

    // For completeness, verify rendering results.
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::red);
}

// Demonstrate usage pattern seeing in asphalt_9 and ensure we use one render pass for maximum
// performance
TEST_P(VulkanPerformanceCounterTest, ClearThenBlitThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    constexpr uint32_t kWidth  = 4;
    constexpr uint32_t kHeight = 4;

    // Initialize two FBOs with red color and depthValue
    GLTexture colorTextures[2];
    GLRenderbuffer depthStencils[2];
    GLFramebuffer fbos[2];
    GLfloat depthValue  = 0.0f;
    GLuint stencilValue = 0x55;
    for (size_t i = 0; i < 2; i++)
    {
        ANGLE_UNSAFE_TODO(setupColorTextureAndDepthBuffer(colorTextures[i], depthStencils[i],
                                                          GL_DEPTH24_STENCIL8, kWidth, kHeight));
        ANGLE_UNSAFE_TODO(setupFBO(colorTextures[i], depthStencils[i], depthStencils[i], fbos[i],
                                   kWidth, kHeight));

        glDepthMask(GL_TRUE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        glStencilMask(0xFF);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_ALWAYS, stencilValue, 0xFF);
        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValue);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
    }

    uint64_t renderPassesBefore = getPerfCounters().renderPasses;

    // Clear
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glClearDepthf(1.0f);
    glClearStencil(0x00);
    glDepthMask(GL_TRUE);
    glStencilMask(0xFF);
    glDisable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Blit depth buffer from fbo1 to fbo0
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                      GL_NEAREST);

    // Verify depth value
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);

    // There should be only one render pass.
    EXPECT_EQ(1u, getPerfCounters().renderPasses - renderPassesBefore);

    // verify stencil results.
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuadToVerifyStencilValue(drawGreen, 0x00);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}

// Similar to ClearThenBlitThenDraw, but with scissor changes
TEST_P(VulkanPerformanceCounterTest, ScissoredDrawThenBlitThenDraw)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen, drawBlue;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawBlue.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());

    constexpr uint32_t kWidth  = 64;
    constexpr uint32_t kHeight = 64;

    // Initialize two FBOs with red color and depth and stencil value
    GLTexture colorTextures[2];
    GLRenderbuffer depthStencils[2];
    GLFramebuffer fbos[2];
    std::array<GLfloat, 2> depthValues = {0.0f, 0.5f};
    GLuint stencilValue                = 0x55;
    for (size_t i = 0; i < 2; i++)
    {
        ANGLE_UNSAFE_TODO(setupColorTextureAndDepthBuffer(colorTextures[i], depthStencils[i],
                                                          GL_DEPTH24_STENCIL8, kWidth, kHeight));
        ANGLE_UNSAFE_TODO(setupFBO(colorTextures[i], depthStencils[i], depthStencils[i], fbos[i],
                                   kWidth, kHeight));

        glDepthMask(GL_TRUE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        glStencilMask(0xFF);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_ALWAYS, stencilValue, 0xFF);
        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValues[i]);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
    }

    uint64_t renderPassesBefore = getPerfCounters().renderPasses;

    // Draw with viewport/scissor set to small rect. It should fail depth test.
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    glDepthFunc(GL_LESS);
    glViewport(kWidth / 2, kHeight / 2, 1, 1);
    glEnable(GL_SCISSOR_TEST);
    glScissor(kWidth / 4, kHeight / 4, kWidth / 2, kHeight / 2);
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), depthValues[0] + 0.1f);

    // Mid render pass blit depth/stencil buffer from fbo1 to fbo0 with scissor disabled. It should
    // cover th whole framebuffer.
    glDisable(GL_SCISSOR_TEST);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                      GL_NEAREST);

    // Draw again without modify any viewport/scissor state. It should still fail depth test.
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), depthValues[1] + 0.1f);

    // end render pass.
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
    // There should be only one render pass.
    EXPECT_EQ(1u, getPerfCounters().renderPasses - renderPassesBefore);

    // Verify depth value. It should contain fbos[1]'s depth value if blit successful.
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glViewport(0, 0, kWidth, kHeight);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValues[1]);
    // verify stencil results.
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuadToVerifyStencilValue(drawGreen, stencilValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}

// Test interaction between transform feedback and mid render pass blit
TEST_P(VulkanPerformanceCounterTest, DrawThenBlitThenDrawWithXFB)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());

    constexpr uint32_t kWidth  = 4;
    constexpr uint32_t kHeight = 4;

    // Initialize two FBOs with red color and depthValue
    GLTexture colorTextures[2];
    GLRenderbuffer depthStencils[2];
    GLFramebuffer fbos[2];
    GLfloat depthValue  = 0.0f;
    GLuint stencilValue = 0x55;
    for (size_t i = 0; i < 2; i++)
    {
        ANGLE_UNSAFE_TODO(setupColorTextureAndDepthBuffer(colorTextures[i], depthStencils[i],
                                                          GL_DEPTH24_STENCIL8, kWidth, kHeight));
        ANGLE_UNSAFE_TODO(setupFBO(colorTextures[i], depthStencils[i], depthStencils[i], fbos[i],
                                   kWidth, kHeight));

        glDepthMask(GL_TRUE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        glStencilMask(0xFF);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_ALWAYS, stencilValue, 0xFF);
        glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValue);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
    }

    uint64_t renderPassesBefore = getPerfCounters().renderPasses;

    std::vector<std::string> tfVaryings = {"gl_Position"};
    ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(xfbProgram, essl1_shaders::vs::Simple(),
                                        essl1_shaders::fs::Red(), tfVaryings,
                                        GL_INTERLEAVED_ATTRIBS);
    glUseProgram(xfbProgram);

    GLBuffer xfbBuffer;
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 1024, nullptr, GL_STATIC_DRAW);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);
    glBeginTransformFeedback(GL_TRIANGLES);

    // Draw
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    drawQuad(xfbProgram, essl1_shaders::PositionAttrib(), depthValue);

    // Blit depth buffer from fbo1 to fbo0
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                      GL_NEAREST);

    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    drawQuad(xfbProgram, essl1_shaders::PositionAttrib(), depthValue);

    glEndTransformFeedback();
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, 0);
    EXPECT_GL_NO_ERROR();

    // XFB should prevent ANGLE to use mid render pass blit
    EXPECT_LT(1u, getPerfCounters().renderPasses - renderPassesBefore);

    // Ensure that triangles were actually captured correctly.
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    // We have two quads
    GLuint quadCount = 2;
    // Each quad have two triangles, each triangle have 3 vertices, each vertex have 4 floats.
    GLuint vertexCount = 2 * 3 * 4;
    void *mappedBuffer = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0,
                                          quadCount * vertexCount * sizeof(float), GL_MAP_READ_BIT);
    ASSERT_NE(nullptr, mappedBuffer);

    const GLfloat expect[] = {
        // clang-format off
        -1.0f, 1.0f, depthValue, 1.0f,
        -1.0f, -1.0f, depthValue, 1.0f,
        1.0f, -1.0f, depthValue, 1.0f,

        -1.0f, 1.0f, depthValue, 1.0f,
        1.0f,  -1.0f, depthValue, 1.0f,
        1.0f, 1.0f,  depthValue, 1.0f,
        // clang-format on
    };

    float *mappedFloats = static_cast<float *>(mappedBuffer);
    for (uint32_t quadIndex = 0; quadIndex < quadCount; quadIndex++)
    {
        for (uint32_t i = 0; i < vertexCount; ++i)
        {
            ANGLE_UNSAFE_TODO({
                EXPECT_EQ(*mappedFloats, expect[i]);
                mappedFloats++;
            })
        }
    }
    glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER);
}

// Test interaction between occlusion query and mid render pass blit
TEST_P(VulkanPerformanceCounterTest, DrawThenBlitThenDrawWithQuery)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    constexpr uint32_t kWidth  = 4;
    constexpr uint32_t kHeight = 4;

    // Initialize two FBOs
    GLTexture colorTextures[2];
    GLRenderbuffer depthStencils[2];
    GLFramebuffer fbos[2];
    std::array<GLfloat, 2> depthValues = {0.0f, 0.5};
    for (size_t i = 0; i < 2; i++)
    {
        ANGLE_UNSAFE_TODO(setupColorTextureAndDepthBuffer(colorTextures[i], depthStencils[i],
                                                          GL_DEPTH24_STENCIL8, kWidth, kHeight));
        ANGLE_UNSAFE_TODO(setupFBO(colorTextures[i], depthStencils[i], depthStencils[i], fbos[i],
                                   kWidth, kHeight));

        glDepthMask(GL_TRUE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        drawQuad(drawGreen, essl1_shaders::PositionAttrib(), depthValues[i]);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    }

    uint64_t renderPassesBefore = getPerfCounters().renderPasses;

    GLQueryEXT query;
    glBeginQueryEXT(GL_ANY_SAMPLES_PASSED_EXT, query);

    // Draw with depthValue+0.1f, this should fail depth test.
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    glDepthFunc(GL_LESS);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValues[0] + 0.1f);

    // Blit depth buffer from fbo1 to fbo0
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                      GL_NEAREST);

    // Draw with depthValue+0.2f, this should still fail depth test if blit successful.
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    glDepthFunc(GL_GREATER);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValues[0] + 0.2f);

    glEndQueryEXT(GL_ANY_SAMPLES_PASSED_EXT);
    EXPECT_GL_NO_ERROR();
    // Query should prevent ANGLE from using mid render pass blit
    EXPECT_LT(1u, getPerfCounters().renderPasses - renderPassesBefore);

    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);

    GLuint result = GL_TRUE;
    glGetQueryObjectuivEXT(query, GL_QUERY_RESULT_EXT, &result);
    EXPECT_GL_FALSE(result);
}

// Test draw with same GL state after mid render pass blit works properly
TEST_P(VulkanPerformanceCounterTest, DrawThenBlitThenDrawWithSameProgram)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    auto quadVertices = GetQuadVertices();
    GLBuffer vertexBuffer;
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, quadVertices.size() * sizeof(quadVertices[0]),
                 quadVertices.data(), GL_STATIC_DRAW);

    GLProgram drawRed;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    const GLint positionLocation = glGetAttribLocation(drawRed, essl1_shaders::PositionAttrib());
    ASSERT_NE(-1, positionLocation);
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(positionLocation);

    GLProgram drawGreen;
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    ASSERT(positionLocation == glGetAttribLocation(drawGreen, essl1_shaders::PositionAttrib()));

    constexpr uint32_t kWidth  = 4;
    constexpr uint32_t kHeight = 4;

    // Initialize two FBOs with red color and depthValue
    GLTexture colorTextures[2];
    GLRenderbuffer depthStencils[2];
    GLFramebuffer fbos[2];
    std::array<GLfloat, 2> depthValues = {0.0f, 0.5};
    for (size_t i = 0; i < 2; i++)
    {
        ANGLE_UNSAFE_TODO(setupColorTextureAndDepthBuffer(colorTextures[i], depthStencils[i],
                                                          GL_DEPTH24_STENCIL8, kWidth, kHeight));
        ANGLE_UNSAFE_TODO(setupFBO(colorTextures[i], depthStencils[i], depthStencils[i], fbos[i],
                                   kWidth, kHeight));

        glDepthMask(GL_TRUE);
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_ALWAYS);
        glUseProgram(drawRed);
        drawWithDepthValue(quadVertices, 0.75f);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
    }

    uint64_t renderPassesBefore = getPerfCounters().renderPasses;

    // Draw with depthValue+0.1f, this should fail depth test.
    glUseProgram(drawGreen);
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    glDepthFunc(GL_LESS);
    drawWithDepthValue(quadVertices, depthValues[0] + 0.1f);

    // Blit depth buffer from fbo1 to fbo0
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[1]);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                      GL_NEAREST);

    // Draw with depthValues[1] - 0.1f, this should still pass depth test if blit successful, but
    // fail if blit is unsuccessful.
    glBindFramebuffer(GL_FRAMEBUFFER, fbos[0]);
    drawWithDepthValue(quadVertices, depthValues[1] - 0.1f);

    // ANGLE should use mid render pass blit
    EXPECT_EQ(1u, getPerfCounters().renderPasses - renderPassesBefore);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}

// Tests that back-to-back clear calls on color attachment with the same value are optimized
// (skipped).
TEST_P(VulkanPerformanceCounterTest, RedundantColorClearWithCommand)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color (first clear, mid-RP)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Clear color again with the same value, should be skipped!
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should only increase by 1.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 1);
    // red + blue
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
}

// Tests that back-to-back clear calls on color attachment of fbo with gaped attachments with the
// same value are optimized (skipped).
TEST_P(VulkanPerformanceCounterTest, RedundantColorClearOnFBOWithGapedAttachments)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture0, texture2;
    glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
    glBindTexture(GL_TEXTURE_2D, texture0);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kOpsTestSize, kOpsTestSize, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture0, 0);
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kOpsTestSize, kOpsTestSize, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, texture2, 0);
    glViewport(0, 0, kOpsTestSize, kOpsTestSize);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Initial clear of all targets to black
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    GLenum drawBuffers[3] = {GL_COLOR_ATTACHMENT0, GL_NONE, GL_COLOR_ATTACHMENT2};
    glDrawBuffers(3, drawBuffers);
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color (first clear, mid-RP)
    uint32_t colorClearsBefore              = getPerfCounters().colorClearAttachments;
    const std::array<GLfloat, 4> clearColor = {1.0f, 0.0f, 0.0f, 1.0f};
    glClearBufferfv(GL_COLOR, 2, clearColor.data());

    // Clear color again with the same value, should be skipped!
    glClearBufferfv(GL_COLOR, 2, clearColor.data());

    // Draw
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should only increase by 1.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 1);
    // red
    glReadBuffer(GL_COLOR_ATTACHMENT2);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test sequence of draw, clear, read, draw, clear, read. Clear should not skip.
TEST_P(VulkanPerformanceCounterTest, ColorDrawClearReadDrawClearRead)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color (first clear, mid-RP)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    // red
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Draw
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    // Clear color again with the same value, should not be skipped!
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 2);
    // red
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test sequence of draw, clear, read, draw, draw, clear, read. Clear should not skip.
TEST_P(VulkanPerformanceCounterTest, ColorDrawClearReadDrawDrawClearRead)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color (first clear, mid-RP)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    // red
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Draw twice
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    // Clear color again with the same value, should not be skipped!
    glClear(GL_COLOR_BUFFER_BIT);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 2);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Tests that back-to-back clear calls on color attachment with mask are NOT skipped.
TEST_P(VulkanPerformanceCounterTest, NonRedundantColorClearWithDifferentMask)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color with mask set to red only (first clear, mid-RP, should be using draw)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT);

    // Clear color again with the same value with mask set to all enabled, should not be skipped!
    glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should increase by 1.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 1);
    // red + blue
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
}

// Tests that back-to-back clear calls on color attachment with different values are NOT skipped.
TEST_P(VulkanPerformanceCounterTest, NonRedundantColorClearWithCommand)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color (first clear)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Clear color again with different value, should NOT be skipped!
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 2);
    // red + blue
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
}

// With scissor enabled for the first clear
TEST_P(VulkanPerformanceCounterTest, NonRedundantColorClearWithScissor1)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color with scissor (first clear)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    // Clear color again with the same value with scissor disabled, should NOT be skipped!
    glDisable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 2);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
}

// With scissor enabled for the second clear
TEST_P(VulkanPerformanceCounterTest, NonRedundantColorClearWithScissor2)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLFramebuffer framebuffer;
    GLTexture texture;
    setupForColorOpsTest(&framebuffer, &texture);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear color without scissor (first clear)
    uint32_t colorClearsBefore = getPerfCounters().colorClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glDisable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);

    // Clear color again with different value with scissor enabled, should NOT be skipped!
    glScissor(0, 0, 1, 1);
    glEnable(GL_SCISSOR_TEST);
    glClear(GL_COLOR_BUFFER_BIT);

    // Draw
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter colorClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 2);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
}

// Tests that back-to-back clear calls on depth attachment with the same value are optimized
// (skipped).
TEST_P(VulkanPerformanceCounterTest, RedundantDepthClearWithCommand)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    GLfloat zValue = 0.0f;
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    // Initial clear
    glClearDepthf(1.0f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw something. Depth test should pass.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.2f);
    ASSERT_GL_NO_ERROR();

    // Clear depth (first clear, mid-RP)
    uint32_t depthClearsBefore = getPerfCounters().depthClearAttachments;
    glClearDepthf(zValue * 0.5f + 0.5f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Clear depth again with the same value, should be skipped!
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw. Depth test should fail.
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.1f);
    ASSERT_GL_NO_ERROR();

    // The counter depthClearAttachments should only increase by 1.
    EXPECT_EQ(getPerfCounters().depthClearAttachments, depthClearsBefore + 1);
    // Verify depth buffer value contains cleared depth value
    drawQuadToVerifyDepthValue(drawGreen, drawRed, zValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Same as RedundantDepthClearWithCommand but with depth test disabled
TEST_P(VulkanPerformanceCounterTest, RedundantDepthClearWithDepthDisabled)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    GLfloat zValue = 0.0f;
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    // Initial clear
    glClearDepthf(1.0f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw something. Depth test should pass.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.2f);
    ASSERT_GL_NO_ERROR();

    // Clear depth (first clear, mid-RP).
    uint32_t depthClearsBefore = getPerfCounters().depthClearAttachments;
    glClearDepthf(zValue * 0.5f + 0.5f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw with depth disabled
    glDisable(GL_DEPTH_TEST);
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue - 0.1f);

    // Clear depth again with the same value, should be skipped!
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw. Depth test should fail.
    glEnable(GL_DEPTH_TEST);
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.2f);
    ASSERT_GL_NO_ERROR();

    // In theory we could optimize out second clear as well, but we dont track draws with depth
    // disabled, so there is no optimization for this usage case yet.
    EXPECT_EQ(getPerfCounters().depthClearAttachments, depthClearsBefore + 2);
    // Verify depth buffer value contains cleared depth value
    drawQuadToVerifyDepthValue(drawGreen, drawRed, zValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Tests that back-to-back clear calls on depth attachment with different values are NOT skipped.
TEST_P(VulkanPerformanceCounterTest, NonRedundantDepthClearWithCommand)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    GLfloat zValue = 0.0f;
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);

    // Initial clear
    glClearDepthf(1.0f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.2f);
    ASSERT_GL_NO_ERROR();

    // Clear depth (first clear)
    uint32_t depthClearsBefore = getPerfCounters().depthClearAttachments;
    glClearDepthf(zValue * 0.5f + 0.6f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Clear depth again with different value, should NOT be skipped!
    glClearDepthf(zValue * 0.5f + 0.5f);
    glClear(GL_DEPTH_BUFFER_BIT);

    // Draw. Should fail depth test
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.1f);
    ASSERT_GL_NO_ERROR();

    // The counter depthClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().depthClearAttachments, depthClearsBefore + 2);
    // Verify depth buffer value contains cleared depth value
    drawQuadToVerifyDepthValue(drawGreen, drawRed, zValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Tests that back-to-back clear calls on stencil attachment with the same value are optimized
// (skipped).
TEST_P(VulkanPerformanceCounterTest, RedundantStencilClearWithCommand)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawGreen;
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initial clear
    glClearStencil(0xAA);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Draw something
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear stencil (first clear, mid-RP)
    uint32_t stencilClearsBefore = getPerfCounters().stencilClearAttachments;
    glClearStencil(0x55);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Clear stencil again with the same value, should be skipped!
    glClear(GL_STENCIL_BUFFER_BIT);

    // Draw
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter stencilClearAttachments should only increase by 1.
    EXPECT_EQ(getPerfCounters().stencilClearAttachments, stencilClearsBefore + 1);
    drawQuadToVerifyStencilValue(drawGreen, 0x55);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Tests that back-to-back clear calls on stencil attachment with different values are NOT skipped.
TEST_P(VulkanPerformanceCounterTest, NonRedundantMaskedStencilClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawGreen;
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initial clear
    glClearStencil(0x00);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear stencil (first clear)
    uint32_t stencilClearsBefore = getPerfCounters().stencilClearAttachments;
    glClearStencil(0x55);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Clear stencil again with same value but mask, it should NOT be skipped but will not use
    // clearAttachments either.
    glStencilMask(0x0F);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Draw
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    EXPECT_EQ(getPerfCounters().stencilClearAttachments, stencilClearsBefore + 1);
    drawQuadToVerifyStencilValue(drawGreen, 0x55);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Tests that back-to-back clear calls on stencil attachment with different values are NOT skipped.
TEST_P(VulkanPerformanceCounterTest, NonRedundantStencilClearWithCommand)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawGreen;
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initial clear
    glClearStencil(0x00);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Draw
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear stencil (first clear)
    uint32_t stencilClearsBefore = getPerfCounters().stencilClearAttachments;
    glClearStencil(0x55);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Clear stencil again with different value, should NOT be skipped!
    glClearStencil(0xAA);
    glClear(GL_STENCIL_BUFFER_BIT);

    // Draw
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // The counter stencilClearAttachments should increase by 2.
    EXPECT_EQ(getPerfCounters().stencilClearAttachments, stencilClearsBefore + 2);
    drawQuadToVerifyStencilValue(drawGreen, 0xAA);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// clear all buffers back to back
TEST_P(VulkanPerformanceCounterTest, RedundantAllAttachmentsClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawRed, drawGreen;
    drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(1.0f);
    glClearStencil(0xAA);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Draw something with depth and stencil test passing, color should be blue
    GLfloat zValue = 0.0f;
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glStencilMask(0xFF);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0xAA, 0xFF);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear all attachments (first clear, mid-RP) with red/0.5f/0x55
    uint32_t colorClearsBefore   = getPerfCounters().colorClearAttachments;
    uint32_t depthClearsBefore   = getPerfCounters().depthClearAttachments;
    uint32_t stencilClearsBefore = getPerfCounters().stencilClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(zValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clear stencil again with the same value, should be skipped!
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Draw, Depth and stencil test should all fail.
    glStencilFunc(GL_EQUAL, 0xAA, 0xFF);
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.1f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::red);

    // The counter *ClearAttachments should only increase by 1.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 1);
    EXPECT_EQ(getPerfCounters().depthClearAttachments, depthClearsBefore + 1);
    EXPECT_EQ(getPerfCounters().stencilClearAttachments, stencilClearsBefore + 1);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, zValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
    drawQuadToVerifyStencilValue(drawGreen, 0x55);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Similar to RedundantAllAttachmentsClear but with depth mask off
TEST_P(VulkanPerformanceCounterTest, RedundantAllAttachmentsClearDepthMaskOff)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    GLProgram drawGreen;
    drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    GLFramebuffer framebuffer;
    GLTexture texture;
    GLRenderbuffer renderbuffer;
    setupForDepthStencilOpsTest(&framebuffer, &texture, &renderbuffer);

    // Initial clear
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(1.0f);
    glClearStencil(0xAA);
    glDepthMask(GL_FALSE);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Draw something with depth and stencil test passing, color should be blue
    GLfloat zValue = 0.0f;
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
    glDepthFunc(GL_LESS);
    glEnable(GL_DEPTH_TEST);
    glStencilMask(0xFF);
    glEnable(GL_STENCIL_TEST);
    glStencilFunc(GL_EQUAL, 0xAA, 0xFF);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // Clear all attachments (first clear, mid-RP) with red/0.5f/0x55
    uint32_t colorClearsBefore   = getPerfCounters().colorClearAttachments;
    uint32_t depthClearsBefore   = getPerfCounters().depthClearAttachments;
    uint32_t stencilClearsBefore = getPerfCounters().stencilClearAttachments;
    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(zValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Clear stencil again with the same value, should be skipped!
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    // Draw, Depth and stencil test should all fail.
    glStencilFunc(GL_EQUAL, 0xAA, 0xFF);
    drawQuad(program, essl1_shaders::PositionAttrib(), zValue + 0.1f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::red);

    // The counter color/stencil clearAttachments should only increase by 1. No depth clear is
    // performed due to mask disabled.
    EXPECT_EQ(getPerfCounters().colorClearAttachments, colorClearsBefore + 1);
    EXPECT_EQ(getPerfCounters().depthClearAttachments, depthClearsBefore);
    EXPECT_EQ(getPerfCounters().stencilClearAttachments, stencilClearsBefore + 1);
    drawQuadToVerifyStencilValue(drawGreen, 0x55);
    EXPECT_PIXEL_RECT_EQ(0, 0, kOpsTestSize, kOpsTestSize, GLColor::green);
}

// Verifies whether, when GL_RASTERIZER_DISCARD is enabled and no glClear is issued,
// the Vulkan color attachment uses VK_ATTACHMENT_LOAD_OP_NONE and VK_ATTACHMENT_STORE_OP_NONE,
// as no actual rendering or clearing is expected.
TEST_P(VulkanPerformanceCounterTest_RGBSurface, UnrenderedSurfaceShouldNotClear)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!hasLoadOpNoneSupport());

    // Use default EGL window, display, surface, and context.
    EGLWindow *window  = getEGLWindow();
    EGLDisplay dpy     = window->getDisplay();
    EGLSurface surface = window->getSurface();
    EGLContext context = window->getContext();

    // Make the default context current.
    EXPECT_TRUE(eglMakeCurrent(dpy, surface, surface, context));
    ASSERT_EGL_SUCCESS() << "eglMakeCurrent failed.";

    angle::VulkanPerfCounters expected;
    // Expect rpCount+1, color(Clears+0, Loads+0, LoadNones+1, Stores+0, StoreNones+1)
    setExpectedCountersForColorOps(getPerfCounters(), 1, 0, 0, 1, 0, 1, &expected);

    glEnable(GL_RASTERIZER_DISCARD);
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    glDisable(GL_RASTERIZER_DISCARD);

    // Validate no GL errors and check performance counters.
    ASSERT_GL_NO_ERROR();
    EXPECT_EQ(expected.renderPasses, getPerfCounters().renderPasses);
    glFinish();
    EXPECT_COLOR_OP_COUNTERS(getPerfCounters(), expected);

    // Restore original context (optional if already using default).
    eglMakeCurrent(dpy, window->getSurface(), window->getSurface(), window->getContext());
}

class VulkanPerformanceCounterTest_TileMemory : public VulkanPerformanceCounterTest
{
  protected:
    VulkanPerformanceCounterTest_TileMemory() : VulkanPerformanceCounterTest()
    {
        setWindowWidth(64);
        setWindowHeight(64);
        setConfigDepthBits(24);
    }

    void setupPrograms()
    {
        drawRed.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        ASSERT_TRUE(drawRed.valid());
        drawGreen.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
        ASSERT_TRUE(drawGreen.valid());
        drawBlue.makeRaster(essl1_shaders::vs::Simple(), essl1_shaders::fs::Blue());
        ASSERT_TRUE(drawBlue.valid());
    }

    void setupColorTexturesAndDepthBuffer(GLTexture &colorTexture1,
                                          GLTexture &colorTexture2,
                                          GLRenderbuffer &depthStencil,
                                          GLenum depthStencilFormat,
                                          GLsizei width,
                                          GLsizei height)
    {
        glBindTexture(GL_TEXTURE_2D, colorTexture1);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);

        glBindTexture(GL_TEXTURE_2D, colorTexture2);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);

        glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
        glRenderbufferStorage(GL_RENDERBUFFER, depthStencilFormat, width, height);
    }

    void drawQuadToVerifyDepthStencilValue(GLfloat depthValue, GLint stencilValue)
    {
        glStencilMask(0x0);
        glEnable(GL_STENCIL_TEST);
        glStencilFunc(GL_EQUAL, stencilValue, 0xFF);

        GLfloat kErrorTolerance = 0.01f;
        // Don't modify depth buffer
        glDepthMask(GL_FALSE);
        glEnable(GL_DEPTH_TEST);
        // This should pass
        glDepthFunc(GL_LESS);
        drawQuad(drawGreen, essl1_shaders::PositionAttrib(), depthValue - kErrorTolerance);
        // This should fail
        drawQuad(drawRed, essl1_shaders::PositionAttrib(), depthValue + kErrorTolerance);
        glDepthFunc(GL_GREATER);
        drawQuad(drawBlue, essl1_shaders::PositionAttrib(), depthValue - kErrorTolerance);
    }

    void oneDepthStencilBufferUsedInOneRenderPassAndThenInvalidate(GLenum depthStencilFormat)
    {
        setupPrograms();

        constexpr GLsizei kWidth  = 4;
        constexpr GLsizei kHeight = 4;
        bool hasStencil           = depthStencilFormat == GL_DEPTH24_STENCIL8 ? true : false;

        uint64_t tileMemoryImageCountBefore = getPerfCounters().tileMemoryImages;

        GLTexture colorTexture;
        GLRenderbuffer depthStencil;
        setupColorTextureAndDepthBuffer(colorTexture, depthStencil, depthStencilFormat, kWidth,
                                        kHeight);
        GLFramebuffer fbo;
        if (hasStencil)
        {
            setupFBO(colorTexture, depthStencil, depthStencil, fbo, kWidth, kHeight);
        }
        else
        {
            setupFBO(colorTexture, depthStencil, 0, fbo, kWidth, kHeight);
        }

        uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;

        GLfloat depthValue = 0.0f;
        // Clear color/depth/stencil buffers
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClearDepthf(depthValue * 0.5f + 0.5f);
        glClearStencil(0x55);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        glEnable(GL_DEPTH_TEST);

        if (hasStencil)
        {
            drawQuadToVerifyDepthStencilValue(depthValue, 0x55);
        }
        else
        {
            drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);
        }

        std::array<GLenum, 2> attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
        glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data());
        EXPECT_GL_NO_ERROR();

        // There should be only one render pass.
        uint64_t actualRenderPassCount = getPerfCounters().renderPasses;
        EXPECT_EQ(expectedRenderPassCount, actualRenderPassCount);
        // For completeness, verify rendering results.
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);

        EXPECT_EQ(1u, getPerfCounters().tileMemoryImages - tileMemoryImageCountBefore);
        EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);
    }

    GLProgram drawRed;
    GLProgram drawGreen;
    GLProgram drawBlue;
};

// Test that depth buffer of window surface should use tile memory since EGL spec says data is
// invalid after swapBuffer.
TEST_P(VulkanPerformanceCounterTest_TileMemory, SurfaceDepthStencilBufferShouldUseTileMemory)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    ANGLE_GL_PROGRAM(drawColor, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
    glUseProgram(drawColor);
    GLint colorUniformLocation =
        glGetUniformLocation(drawColor, angle::essl1_shaders::ColorUniform());
    ASSERT_NE(colorUniformLocation, -1);

    // Clear color to red and the depth/stencil buffer to 1.0
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(0.5f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glDepthRangef(-1, 1);
    EXPECT_GL_NO_ERROR();

    glDepthMask(GL_TRUE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glUniform4f(colorUniformLocation, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(drawColor, essl1_shaders::PositionAttrib(), -1.0f);

    swapBuffers();

    // depthStencil should be using tile memory
    EXPECT_EQ(1u, getPerfCounters().tileMemoryImages);
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);
}

// Test that depth buffer of FBO should use tile memory if invalidateFramebuffer is called.
TEST_P(VulkanPerformanceCounterTest_TileMemory, OneDSBufferUsedInOneRenderPassThenInvalidate_D24S8)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    oneDepthStencilBufferUsedInOneRenderPassAndThenInvalidate(GL_DEPTH24_STENCIL8);
}

// Test that depth buffer of FBO should use tile memory if invalidateFramebuffer is called.
TEST_P(VulkanPerformanceCounterTest_TileMemory, OneDSBufferUsedInOneRenderPassThenInvalidate_D24)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    oneDepthStencilBufferUsedInOneRenderPassAndThenInvalidate(GL_DEPTH_COMPONENT24);
}

// Test that depth buffer of FBO should use tile memory if invalidateFramebuffer is called.
TEST_P(VulkanPerformanceCounterTest_TileMemory, OneDSBufferUsedInOneRenderPassThenInvalidate_D16)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    oneDepthStencilBufferUsedInOneRenderPassAndThenInvalidate(GL_DEPTH_COMPONENT16);
}

// depth buffer of FBO should use tile memory if was valid in first render pass but invalidate in
// second render pass.
TEST_P(VulkanPerformanceCounterTest_TileMemory, OneDSBufferUsedInTwoRenderPassesThenInvalidate)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    // Surface's depth buffer should also using tile memory.
    uint64_t expectedTileMemoryImageCount = getPerfCounters().tileMemoryImages + 1;

    constexpr GLsizei kWidth  = 4;
    constexpr GLsizei kHeight = 4;

    GLTexture colorTexture1, colorTexture2;
    GLRenderbuffer depthStencil;
    setupColorTexturesAndDepthBuffer(colorTexture1, colorTexture2, depthStencil,
                                     GL_DEPTH24_STENCIL8, kWidth, kHeight);

    // Setup two fbos share the same depth stencil buffer
    GLFramebuffer fbo1, fbo2;
    setupFBO(colorTexture1, depthStencil, depthStencil, fbo1, kWidth, kHeight);
    setupFBO(colorTexture2, depthStencil, depthStencil, fbo2, kWidth, kHeight);

    uint64_t expectedRenderPassCount = getPerfCounters().renderPasses + 2;

    GLfloat depthValue = 0.0f;
    // draw to fbo1, keep depth buffer valid
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(depthValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthStencilValue(depthValue, 0x55);

    // draw to fbo2 without modifying depth buffer, and invalidate depth buffer
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuadToVerifyDepthStencilValue(depthValue, 0x55);
    std::array<GLenum, 2> attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, 2, attachments.data());
    EXPECT_GL_NO_ERROR();

    // There should be two render pass.
    EXPECT_EQ(expectedRenderPassCount, getPerfCounters().renderPasses);

    // For completeness, verify rendering results.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);

    // depthStencil should also be using tile memory
    EXPECT_EQ(expectedTileMemoryImageCount, getPerfCounters().tileMemoryImages);
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);
}

// Similar to OneDepthStencilRenderBufferUsedInTwoRenderPasses, but repeat. This will create many
// render passes that uses depthStencil buffers which could allocate in tile memory heap and exceed
// heap capacity and fail. They may fallback to regular memory but should still work.
TEST_P(VulkanPerformanceCounterTest_TileMemory, ManyDSBufferUsedInOneSubmit)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    constexpr GLsizei kWidth      = 1024;
    constexpr GLsizei kHeight     = 1024;
    constexpr size_t kRepeatCount = 10;

    std::array<GLTexture, kRepeatCount> colorTextures1, colorTextures2;
    std::array<GLRenderbuffer, kRepeatCount> depthStencils;
    std::array<GLFramebuffer, kRepeatCount> fbos1, fbos2;

    // Surface's depth buffer should also using tile memory.
    uint64_t expectedMaxTileMemoryImageCount = getPerfCounters().tileMemoryImages + kRepeatCount;
    uint64_t expectedMinTileMemoryImageCount = getPerfCounters().tileMemoryImages + 1;
    uint64_t expectedRenderPassCount         = getPerfCounters().renderPasses + 2 * kRepeatCount;

    std::array<GLenum, 2> attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    GLfloat depthValue                = 0.0f;
    for (size_t i = 0; i < kRepeatCount; i++)
    {
        setupColorTexturesAndDepthBuffer(colorTextures1[i], colorTextures2[i], depthStencils[i],
                                         GL_DEPTH24_STENCIL8, kWidth, kHeight);
        setupFBO(colorTextures1[i], depthStencils[i], depthStencils[i], fbos1[i], kWidth, kHeight);
        setupFBO(colorTextures2[i], depthStencils[i], depthStencils[i], fbos2[i], kWidth, kHeight);

        // Clear color/depth/stencil buffer
        glBindFramebuffer(GL_FRAMEBUFFER, fbos1[i]);
        glDepthMask(GL_TRUE);
        glStencilMask(0xFF);
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClearDepthf(depthValue * 0.5f + 0.5f);
        glClearStencil(0x55);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
        drawQuadToVerifyDepthStencilValue(depthValue, 0x55);

        // draw to fbo2 without modifying depth buffer, and invalidate depth buffer
        glBindFramebuffer(GL_FRAMEBUFFER, fbos2[i]);
        glClear(GL_COLOR_BUFFER_BIT);
        drawQuadToVerifyDepthStencilValue(depthValue, 0x55);
        glInvalidateFramebuffer(GL_DRAW_FRAMEBUFFER, attachments.size(), attachments.data());
    }

    // There should be two render pass for every kRepeatCount loop.
    EXPECT_EQ(expectedRenderPassCount, getPerfCounters().renderPasses);
    // depthStencil should also be using tile memory
    EXPECT_LE(getPerfCounters().tileMemoryImages, expectedMaxTileMemoryImageCount);
    EXPECT_GE(getPerfCounters().tileMemoryImages, expectedMinTileMemoryImageCount);

    // For completeness, verify rendering results.
    for (size_t i = 0; i < kRepeatCount; i++)
    {
        glBindFramebuffer(GL_FRAMEBUFFER, fbos1[i]);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
        glBindFramebuffer(GL_FRAMEBUFFER, fbos2[i]);
        EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    }
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);
}

// Test that depth buffer of FBO that uses tile memory will switch back to regular memory if it has
// valid data across submissions
TEST_P(VulkanPerformanceCounterTest_TileMemory, OneDSBufferUsedInTwoRenderPassesWithTwoSubmission)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    uint64_t tileMemoryStartingImageCount = getPerfCounters().tileMemoryImages;

    constexpr GLsizei kWidth  = 4;
    constexpr GLsizei kHeight = 4;

    GLTexture colorTexture1, colorTexture2;
    GLRenderbuffer depthStencil;
    setupColorTexturesAndDepthBuffer(colorTexture1, colorTexture2, depthStencil,
                                     GL_DEPTH24_STENCIL8, kWidth, kHeight);

    // Setup two fbos share the same depth stencil buffer
    GLFramebuffer fbo1, fbo2;
    setupFBO(colorTexture1, depthStencil, depthStencil, fbo1, kWidth, kHeight);
    setupFBO(colorTexture2, depthStencil, depthStencil, fbo2, kWidth, kHeight);

    GLfloat depthValue = 0.0f;
    // draw to fbo1, keep depth buffer valid
    glBindFramebuffer(GL_FRAMEBUFFER, fbo1);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(depthValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthStencilValue(depthValue, 0x55);
    EXPECT_GL_NO_ERROR();
    // depthStencil should also be using tile memory
    EXPECT_EQ(tileMemoryStartingImageCount + 1, getPerfCounters().tileMemoryImages);
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);
    // Submit
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    // depthStencil should not be using tile memory
    EXPECT_EQ(tileMemoryStartingImageCount, getPerfCounters().tileMemoryImages);
    EXPECT_EQ(1u, getPerfCounters().fallbackFromTileMemory);

    // draw to fbo2 without modifying depth buffer
    glBindFramebuffer(GL_FRAMEBUFFER, fbo2);
    glClear(GL_COLOR_BUFFER_BIT);
    // Verify depth has correct value
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    glClear(GL_COLOR_BUFFER_BIT);
    // Verify stencil has correct value
    drawQuadToVerifyStencilValue(drawGreen, 0x55);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}

// Test that depth buffer of surface that uses tile memory will switch back to regular memory if it
// has valid data across submissions
TEST_P(VulkanPerformanceCounterTest_TileMemory,
       SurfaceDepthBufferUsedInTwoRenderPassesWithTwoSubmission)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    GLfloat depthValue = 0.0f;
    // draw to depth depth buffer and keep data valid
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(depthValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);
    EXPECT_GL_NO_ERROR();
    // depthStencil should also be using tile memory
    EXPECT_EQ(1u, getPerfCounters().tileMemoryImages);
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);
    // Submit
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
    // depthStencil should not be using tile memory
    EXPECT_EQ(0u, getPerfCounters().tileMemoryImages);
    EXPECT_EQ(1u, getPerfCounters().fallbackFromTileMemory);

    // draw again without modifying depth buffer
    glClear(GL_COLOR_BUFFER_BIT);
    // Verify depth buffer has correct value
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Regression test for UAF in finalizeImageWithTileMemory after readPixels triggered
// fallbackFromTileMemory.
TEST_P(VulkanPerformanceCounterTest_TileMemory, DepthBufferPBOReadThenDelete)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_read_depth_stencil"));

    setupPrograms();

    GLenum depthStencilFormat = GL_DEPTH24_STENCIL8;
    constexpr GLsizei kWidth  = 256;
    constexpr GLsizei kHeight = 256;

    uint64_t tileMemoryImageCountBefore = getPerfCounters().tileMemoryImages;

    GLTexture colorTexture;
    GLRenderbuffer depthStencil;
    setupColorTextureAndDepthBuffer(colorTexture, depthStencil, depthStencilFormat, kWidth,
                                    kHeight);
    GLFramebuffer fbo;
    setupFBO(colorTexture, depthStencil, depthStencil, fbo, kWidth, kHeight);

    GLfloat depthValue = 0.0f;
    // Clear color/depth/stencil buffers
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(depthValue * 0.5f + 0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, depthValue);
    EXPECT_EQ(1u, getPerfCounters().tileMemoryImages - tileMemoryImageCountBefore);

    // PBO read
    GLBuffer pbo;
    glBindBuffer(GL_PIXEL_PACK_BUFFER, pbo);
    glBufferData(GL_PIXEL_PACK_BUFFER, kWidth * kHeight * sizeof(uint32_t), nullptr,
                 GL_DYNAMIC_READ);
    glReadPixels(0, 0, kWidth, kHeight, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0);
    glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
    EXPECT_GL_NO_ERROR();

    // Delete depthBuffer
    depthStencil.reset();

    std::array<GLenum, 2> attachments = {GL_DEPTH_ATTACHMENT, GL_STENCIL_ATTACHMENT};
    glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data());

    // For completeness, verify rendering results.
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    EXPECT_GL_NO_ERROR();
}

// Test blit depth buffer works, similar to
// dEQP-GLES3.functional.fbo.blit.depth_stencil.depth32_basic.
TEST_P(VulkanPerformanceCounterTest_TileMemory, BlitGradientDepthBuffer)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    constexpr GLsizei kWidth  = 128;
    constexpr GLsizei kHeight = 128;

    setupPrograms();

    GLTexture srcColorTexture, dstColorTexture;
    GLRenderbuffer srcDepthStencil, dstDepthStencil;
    setupColorTexturesAndDepthBuffer(srcColorTexture, dstColorTexture, srcDepthStencil,
                                     GL_DEPTH_COMPONENT32F, kWidth, kHeight);
    glBindRenderbuffer(GL_RENDERBUFFER, dstDepthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32F, kWidth, kHeight);

    // Create frame buffers and clear depth to 1.0f
    GLFramebuffer srcFbo, dstFbo;
    setupFBO(srcColorTexture, srcDepthStencil, 0, srcFbo, kWidth, kHeight);
    glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);
    setupFBO(dstColorTexture, dstDepthStencil, 0, dstFbo, kWidth, kHeight);
    glClearBufferfi(GL_DEPTH_STENCIL, 0, 1.0f, 0);

    // Fill source with gradient depth value: depth = [-1..1]
    const std::array<angle::Vector3, 4> kGradientQuadVertices = {
        {angle::Vector3(-1.0f, -1.0f, -1.0f), angle::Vector3(-1.0f, 1.0f, 0.0f),
         angle::Vector3(1.0f, -1.0f, 0.0f), angle::Vector3(1.0f, 1.0f, 1.0f)}};
    const uint16_t indices[] = {0, 1, 2, 2, 1, 3};
    glBindFramebuffer(GL_FRAMEBUFFER, srcFbo);
    glViewport(0, 0, kWidth, kHeight);
    glEnable(GL_DEPTH_TEST);
    glUseProgram(drawRed);
    int32_t posLoc = glGetAttribLocation(drawRed, essl1_shaders::PositionAttrib());
    glEnableVertexAttribArray(posLoc);
    glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, kGradientQuadVertices.data());
    glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_SHORT,
                   &indices[0]);

    glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
    drawQuad(drawGreen, essl1_shaders::PositionAttrib(), 0.0f);

    // Perform copy.
    glBindFramebuffer(GL_READ_FRAMEBUFFER, srcFbo);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dstFbo);
    glBlitFramebuffer(0, 0, kWidth, kHeight, 0, 0, kWidth, kHeight, GL_DEPTH_BUFFER_BIT,
                      GL_NEAREST);

    // Render blue color where depth < 0, decrement on depth failure.
    glBindFramebuffer(GL_FRAMEBUFFER, dstFbo);
    drawQuad(drawBlue, essl1_shaders::PositionAttrib(), 0.0f);
    EXPECT_GL_NO_ERROR();

    std::vector<GLColor> resultData(kWidth * kHeight);
    glReadPixels(0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE, resultData.data());

    // The top left triangle should fail depth test, thus keep the dstFbo color which is
    // gradientData.
    bool topLeftTriangleIsGreen = true;
    for (int y = 0; y < kHeight && topLeftTriangleIsGreen; y++)
    {
        for (int x = 0; x < (kWidth - y); x++)
        {
            if (resultData[y * kWidth + x] != GLColor::green)
            {
                topLeftTriangleIsGreen = false;
                break;
            }
        }
    }
    EXPECT_EQ(topLeftTriangleIsGreen, true);

    // The bottom right triangle should pass depth test, thus render blue.
    bool bottomRightTriangleIsBlue = true;
    for (int y = 0; y < kHeight && bottomRightTriangleIsBlue; y++)
    {
        for (int x = (kWidth - y); x < kWidth; x++)
        {
            if (resultData[y * kWidth + x] != GLColor::blue)
            {
                bottomRightTriangleIsBlue = false;
                break;
            }
        }
    }
    EXPECT_EQ(bottomRightTriangleIsBlue, true);
}

// Similar to OneDSBufferUsedInOneRenderPassThenInvalidate_D24S8, add sample from cubemap texture
// which on some GPU, it may trigger finishImpl call.
TEST_P(VulkanPerformanceCounterTest_TileMemory, DSBufferUsedInOneRPThenSampleFromCubemapTexture)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    const char kCubemapTextureFS[] = R"(precision highp float;
            uniform samplerCube texCube;
            varying vec2 v_texCoord;
            void main()
            {
                vec3 cubecoord = vec3(1, v_texCoord);
                gl_FragColor = textureCube(texCube, cubecoord);
            })";

    constexpr GLsizei kWidth  = 4;
    constexpr GLsizei kHeight = 4;

    uint64_t tileMemoryImageCountBefore = getPerfCounters().tileMemoryImages;
    uint64_t renderPassCountBefore      = getPerfCounters().renderPasses;

    // Create a cubemap texture. This will create a texture with mHasBeenBoundAsAttachment=false.
    GLTexture cubeMapTexture;
    glBindTexture(GL_TEXTURE_CUBE_MAP, cubeMapTexture);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexStorage2D(GL_TEXTURE_CUBE_MAP, 7, GL_RGB5_A1, 64, 64);
    std::array<uint16_t, 64 * 64> textureData;
    textureData.fill(0xF801);
    for (GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
         target++)
    {
        glTexSubImage2D(target, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1,
                        reinterpret_cast<const GLubyte *>(textureData.data()));
    }
    // This will make the cubemap texture's mHasBeenBoundAsAttachment=true.
    GLFramebuffer fboWithCubemapTexture;
    glBindFramebuffer(GL_FRAMEBUFFER, fboWithCubemapTexture);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
                           cubeMapTexture, 0);

    // Create FBO, it's depthStencil buffer should allocated with tile memory.
    GLTexture colorTexture;
    GLRenderbuffer depthStencil;
    setupColorTextureAndDepthBuffer(colorTexture, depthStencil, GL_DEPTH24_STENCIL8, kWidth,
                                    kHeight);
    GLFramebuffer fbo;
    setupFBO(colorTexture, depthStencil, depthStencil, fbo, kWidth, kHeight);

    // Start renderPass
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthStencilValue(0.0f, 0x55);
    EXPECT_GL_NO_ERROR();

    // There should be only one render pass.
    uint64_t renderPassCount = getPerfCounters().renderPasses - renderPassCountBefore;
    EXPECT_EQ(1u, renderPassCount);
    EXPECT_EQ(1u, getPerfCounters().tileMemoryImages - tileMemoryImageCountBefore);
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);

    // Sample from cubeMapTexture. This may trigger submitCommands on some GPUs that has to recreate
    // VkImage for cubemap texture due to mHasBeenBoundAsAttachment==true and trigger a finishImpl
    // call due to data copy back.
    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), kCubemapTextureFS);
    glUseProgram(texProgram);
    glBindTexture(GL_TEXTURE_CUBE_MAP, cubeMapTexture);
    glDisable(GL_STENCIL_TEST);
    glDepthMask(GL_TRUE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(texProgram, essl1_shaders::PositionAttrib(), 0.6f);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, 0.6f);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}

// Similar to DSBufferUsedInOneRPThenSampleFromCubemapTexture, but added glTexSubImage2D before read
// pixels
TEST_P(VulkanPerformanceCounterTest_TileMemory,
       DSBufferUsedInOneRPThenSampleFromCubemapAndThenSubTexture)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    const char kCubemapTextureFS[] = R"(precision highp float;
            uniform samplerCube texCube;
            varying vec2 v_texCoord;
            void main()
            {
                vec3 cubecoord = vec3(1, v_texCoord);
                gl_FragColor = textureCube(texCube, cubecoord);
            })";

    constexpr GLsizei kWidth  = 4;
    constexpr GLsizei kHeight = 4;

    uint64_t tileMemoryImageCountBefore = getPerfCounters().tileMemoryImages;
    uint64_t renderPassCountBefore      = getPerfCounters().renderPasses;

    // Create a cubemap texture. This will create a texture with mHasBeenBoundAsAttachment=false.
    GLTexture cubeMapTexture;
    glBindTexture(GL_TEXTURE_CUBE_MAP, cubeMapTexture);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexStorage2D(GL_TEXTURE_CUBE_MAP, 7, GL_RGB5_A1, 64, 64);
    std::array<uint16_t, 64 * 64> textureData;
    textureData.fill(0xF801);
    for (GLenum target = GL_TEXTURE_CUBE_MAP_POSITIVE_X; target <= GL_TEXTURE_CUBE_MAP_NEGATIVE_Z;
         target++)
    {
        glTexSubImage2D(target, 0, 0, 0, 64, 64, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1,
                        reinterpret_cast<const GLubyte *>(textureData.data()));
    }
    // This will make the cubemap texture's mHasBeenBoundAsAttachment=true.
    GLFramebuffer fboWithCubemapTexture;
    glBindFramebuffer(GL_FRAMEBUFFER, fboWithCubemapTexture);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_CUBE_MAP_POSITIVE_X,
                           cubeMapTexture, 0);

    // Create FBO, it's depthStencil buffer should allocated with tile memory.
    GLTexture colorTexture;
    GLRenderbuffer depthStencil;
    setupColorTextureAndDepthBuffer(colorTexture, depthStencil, GL_DEPTH24_STENCIL8, kWidth,
                                    kHeight);
    GLFramebuffer fbo;
    setupFBO(colorTexture, depthStencil, depthStencil, fbo, kWidth, kHeight);

    // Start renderPass
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(0.5f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthStencilValue(0.0f, 0x55);
    EXPECT_GL_NO_ERROR();

    // There should be only one render pass.
    uint64_t renderPassCount = getPerfCounters().renderPasses - renderPassCountBefore;
    EXPECT_EQ(1u, renderPassCount);
    EXPECT_EQ(1u, getPerfCounters().tileMemoryImages - tileMemoryImageCountBefore);
    EXPECT_EQ(0u, getPerfCounters().fallbackFromTileMemory);

    // Sample from cubeMapTexture. This may trigger submitCommands on some GPUs that has to recreate
    // VkImage for cubemap texture due to mHasBeenBoundAsAttachment==true and trigger a finishImpl
    // call due to data copy back.
    ANGLE_GL_PROGRAM(texProgram, essl1_shaders::vs::Texture2D(), kCubemapTextureFS);
    glUseProgram(texProgram);
    glBindTexture(GL_TEXTURE_CUBE_MAP, cubeMapTexture);
    glDisable(GL_STENCIL_TEST);
    glDepthMask(GL_TRUE);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(texProgram, essl1_shaders::PositionAttrib(), 0.6f);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, 0.6f);
    glTexSubImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, 0, 0, 64, 64, GL_RGBA,
                    GL_UNSIGNED_SHORT_5_5_5_1,
                    reinterpret_cast<const GLubyte *>(textureData.data()));
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    // Now verify depth buffer again.
    drawQuadToVerifyDepthValue(drawGreen, drawRed, 0.6f);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
}

// Regression test: deleting a depth renderbuffer that is the (stale) cached
// depth-stencil render target of the currently-bound but unsynced draw
// framebuffer must not lead to a use-after-free.
TEST_P(VulkanPerformanceCounterTest_TileMemory, DeleteDepthRenderbufferOfUnsyncedDrawFBOThenFinish)
{
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    constexpr GLsizei kWidth  = 64;
    constexpr GLsizei kHeight = 64;

    // fboA with depth renderbuffer depthA.
    GLTexture colA;
    glBindTexture(GL_TEXTURE_2D, colA);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
    GLRenderbuffer depthA;
    glBindRenderbuffer(GL_RENDERBUFFER, depthA);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
    GLFramebuffer fboA;
    glBindFramebuffer(GL_FRAMEBUFFER, fboA);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colA, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthA);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glViewport(0, 0, kWidth, kHeight);

    // Step 1: start a render pass on fboA with depthA -> populates
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_TRUE);
    glClearDepthf(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);

    // Step 2: invalidate depth so depthA is content-undefined when RP_A ends.
    GLenum depthAtt = GL_DEPTH_ATTACHMENT;
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &depthAtt);

    // fboB with depth renderbuffer depthB.
    GLTexture colB;
    glBindTexture(GL_TEXTURE_2D, colB);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
    GLRenderbuffer depthB;
    glBindRenderbuffer(GL_RENDERBUFFER, depthB);  // also unbinds depthA
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
    GLFramebuffer fboB;
    glBindFramebuffer(GL_FRAMEBUFFER, fboB);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colB, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthB);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Step 3: draw on fboB -> closes RP_A, opens RP_B,
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);

    // Step 4: rebind fboA as the *draw* framebuffer. Front-end state only:
    // FramebufferVk for fboA is NOT synced; its mRenderTargetCache.depthStencil
    // still points at depthA's RenderTargetVk. RenderPass B remains open in the backend.
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboA);

    // Step 5: delete depthA. This auto-detached from fboA.
    depthA.reset();

    // Step 6: glFinish(), which triggers submission. It should not access deleted render targets.
    glFinish();

    EXPECT_GL_NO_ERROR();
}

// Regression test: deleting a color renderbuffer of the currently-bound but unsynced draw
// framebuffer must not lead to a use-after-free.
TEST_P(VulkanPerformanceCounterTest_TileMemory, DeleteColorRenderbufferOfUnsyncedDrawFBOThenFinish)
{
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    constexpr GLsizei kWidth  = 64;
    constexpr GLsizei kHeight = 64;

    // fboA with depth renderbuffer depthA
    GLTexture colA;
    glBindTexture(GL_TEXTURE_2D, colA);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
    GLRenderbuffer depthA;
    glBindRenderbuffer(GL_RENDERBUFFER, depthA);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
    GLFramebuffer fboA;
    glBindFramebuffer(GL_FRAMEBUFFER, fboA);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colA, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthA);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glViewport(0, 0, kWidth, kHeight);

    // Step 1: start a render pass on fboA with depthA
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_ALWAYS);
    glDepthMask(GL_TRUE);
    glClearDepthf(1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.0f);

    // Step 2: invalidate depthA so that it is content-undefined.
    GLenum depthAtt = GL_DEPTH_ATTACHMENT;
    glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &depthAtt);

    // fboB with depth renderbuffer depthB.
    GLTexture colB;
    glBindTexture(GL_TEXTURE_2D, colB);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);
    GLRenderbuffer depthB;
    glBindRenderbuffer(GL_RENDERBUFFER, depthB);  // also unbinds depthA
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, kWidth, kHeight);
    GLFramebuffer fboB;
    glBindFramebuffer(GL_FRAMEBUFFER, fboB);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colB, 0);
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthB);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Step 3: draw on fboB
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawQuad(drawRed, essl1_shaders::PositionAttrib(), 0.6f);

    // Step 4: rebind fboA as the draw framebuffer.
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboA);

    // Step 5: delete colA. This auto-detached from fboA and makes fboA dirty.
    colA.reset();

    // Step 6: glFinish(), which triggers submission. It should not access any deleted objects.
    glFinish();
    EXPECT_GL_NO_ERROR();

    // The previous glFinish should have caused depthB fallback from tile memory. Now verify depthB
    // buffer again.
    glBindFramebuffer(GL_FRAMEBUFFER, fboB);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, 0.6f);
    EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
    EXPECT_EQ(1u, getPerfCounters().fallbackFromTileMemory);
}

// Test tile memory allocation failure is handled properly
TEST_P(VulkanPerformanceCounterTest_TileMemory, DSBufferAllocateFailureThenBlit)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    setupPrograms();

    GLfloat zValue = 0.0f;

    // draw to default fbo to initialize depth buffer
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(zValue * 0.5f + 0.5f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);
    drawQuadToVerifyDepthValue(drawGreen, drawRed, zValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
    EXPECT_EQ(1u, getPerfCounters().fallbackFromTileMemory);

    uint64_t tileMemoryImageCountBefore = getPerfCounters().tileMemoryImages;

    // Set up depthStencil with 4097x4097, the simulation code path will fail to allocate for
    // testing purpose (allocation size > 64M).
    constexpr GLsizei kWidth  = 4097;
    constexpr GLsizei kHeight = 4097;

    GLTexture colorTexture;
    glBindTexture(GL_TEXTURE_2D, colorTexture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);

    GLRenderbuffer depthStencil;
    glBindRenderbuffer(GL_RENDERBUFFER, depthStencil);
    glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, kWidth, kHeight);

    GLFramebuffer fbo;
    setupFBO(colorTexture, depthStencil, depthStencil, fbo, kWidth, kHeight);

    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClearDepthf(0.0f);
    glClearStencil(0x55);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
    glEnable(GL_DEPTH_TEST);

    // blit depth from surface to to fbo
    glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);
    ASSERT(getWindowWidth() < kWidth);
    ASSERT(getWindowHeight() < kHeight);
    glBlitFramebuffer(0, 0, getWindowWidth(), getWindowHeight(), 0, 0, getWindowWidth(),
                      getWindowHeight(), GL_DEPTH_BUFFER_BIT, GL_NEAREST);

    if (isFeatureEnabled(Feature::SupportsTileMemoryHeap))
    {
        // depthStencil may or may not be using tile memory.
        EXPECT_TRUE(getPerfCounters().tileMemoryImages - tileMemoryImageCountBefore <= 1);
    }
    else
    {
        // simulation code path should fail to allocate tile memory and automatically fall back to
        // regular memory
        ASSERT(isFeatureEnabled(Feature::SimulateTileMemoryForTesting));
        EXPECT_EQ(0u, getPerfCounters().tileMemoryImages - tileMemoryImageCountBefore);
    }

    // Verify depth buffer has correct value
    drawQuadToVerifyDepthValue(drawGreen, drawRed, zValue);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

class VulkanPerformanceCounterTest_ClipDistance : public VulkanPerformanceCounterTest
{};

// Switch clip distance should update driver uniforms.
TEST_P(VulkanPerformanceCounterTest_ClipDistance, ToggleClipDistanceShouldUpdateDriverUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));

    ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());

    constexpr GLsizei kWidth  = 4;
    constexpr GLsizei kHeight = 4;

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB565, kWidth, kHeight);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glViewport(0, 0, kWidth, kHeight);

    glDisable(GL_CLIP_DISTANCE0_EXT);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    uint64_t program1Count = getPerfCounters().graphicsDriverUniformsUpdated;
    glEnable(GL_CLIP_DISTANCE0_EXT);
    drawQuad(redProgram, essl1_shaders::PositionAttrib(), 0.5f);
    uint64_t program2Count = getPerfCounters().graphicsDriverUniformsUpdated;
    EXPECT_EQ(program2Count, program1Count + 1);
    ASSERT_GL_NO_ERROR();
}

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest);
ANGLE_INSTANTIATE_TEST(
    VulkanPerformanceCounterTest,
    ES3_VULKAN(),
    ES3_VULKAN().enable(Feature::PadBuffersToMaxVertexAttribStride),
    ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferMonolithicPipelinesOverLibraries),
    ES3_VULKAN_SWIFTSHADER()
        .enable(Feature::PreferMonolithicPipelinesOverLibraries)
        .enable(Feature::SlowDownMonolithicPipelineCreationForTesting),
    ES3_VULKAN_SWIFTSHADER()
        .enable(Feature::PreferMonolithicPipelinesOverLibraries)
        .disable(Feature::MergeProgramPipelineCachesToGlobalCache));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
    VulkanPerformanceCounterTest_DepthStencilLoadStoreOps);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps,
                       ES3_VULKAN(),
                       ES3_VULKAN().enable(Feature::SimulateTileMemoryForTesting),
                       ES3_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_ES31);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_ES31, ES31_VULKAN(), ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(
    VulkanPerformanceCounterTest_DepthStencilLoadStoreOps_ES31);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_DepthStencilLoadStoreOps_ES31,
                       ES31_VULKAN(),
                       ES3_VULKAN().enable(Feature::SimulateTileMemoryForTesting),
                       ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_MSAA);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_MSAA,
                       ES3_VULKAN(),
                       ES3_VULKAN_SWIFTSHADER(),
                       ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
                       ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
                       ES3_VULKAN().enable(Feature::EmulatedPrerotation270));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_Prerotation);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_Prerotation,
                       ES3_VULKAN(),
                       ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
                       ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
                       ES3_VULKAN().enable(Feature::EmulatedPrerotation270));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_SingleBuffer);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_SingleBuffer, ES3_VULKAN());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_RGBSurface);
ANGLE_INSTANTIATE_TEST(
    VulkanPerformanceCounterTest_RGBSurface,
    ES3_VULKAN(),
    ES3_VULKAN().enable(Feature::PadBuffersToMaxVertexAttribStride),
    ES3_VULKAN_SWIFTSHADER().enable(Feature::PreferMonolithicPipelinesOverLibraries),
    ES3_VULKAN_SWIFTSHADER()
        .enable(Feature::PreferMonolithicPipelinesOverLibraries)
        .enable(Feature::SlowDownMonolithicPipelineCreationForTesting),
    ES3_VULKAN_SWIFTSHADER()
        .enable(Feature::PreferMonolithicPipelinesOverLibraries)
        .disable(Feature::MergeProgramPipelineCachesToGlobalCache));

// Regression test: redefining a depth texture that is bound as the depth attachment of the current
// draw FBO in two share-group contexts (each with an open render pass and a surviving
// mImageWithTileMemory pointer) must not dereference a freed RenderTargetVk via
// FramebufferVk::getImageWithTileMemory() during the re-entrant submitCommands() inside
// TextureVk::releaseImage().  See ContextVk::submitCommands' hasAnyDirtyBit() guard.
TEST_P(VulkanPerformanceCounterTest_TileMemory, RedefineSharedDepthTextureWithOpenRenderPasses)
{
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    constexpr GLsizei kSize = 64;

    enum class Step
    {
        Start,
        Thread1CreatedDepthTex,
        Thread0OpenedRP,
        Thread1OpenedRP,
        Thread0Redefined,
        Finish,
        Abort,
    };
    Step currentStep = Step::Start;
    std::mutex mutex;
    std::condition_variable condVar;
    GLuint sharedDepthTex = 0;

    // Per-context setup:
    // 1. FBO_A: Color and a depth renderbuffer that is invalidated.  The Vulkan backend may use
    //    tile memory for this depth renderbuffer
    // 2. FBO_B: Color and the shared depth texture.  The Vulkan backend does not use tile memory
    //    for textures.
    auto setupContextState = [&](GLuint colorTexA, GLuint depthRB, GLuint fboA, GLuint colorTexB,
                                 GLuint fboB, GLuint depthTex, GLuint program) {
        glBindTexture(GL_TEXTURE_2D, colorTexA);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
        glBindRenderbuffer(GL_RENDERBUFFER, depthRB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, kSize, kSize);
        glBindFramebuffer(GL_FRAMEBUFFER, fboA);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexA, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRB);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        glViewport(0, 0, kSize, kSize);
        glEnable(GL_DEPTH_TEST);
        glDepthMask(GL_TRUE);
        glDepthFunc(GL_ALWAYS);
        glClearDepthf(1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        const GLenum kDepthAttachment = GL_DEPTH_ATTACHMENT;
        glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &kDepthAttachment);
        EXPECT_GL_NO_ERROR();

        glBindTexture(GL_TEXTURE_2D, colorTexB);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
        glBindFramebuffer(GL_FRAMEBUFFER, fboB);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexB, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
    };

    auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
        ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));

        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreatedDepthTex));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        GLTexture colorTexA, colorTexB;
        GLRenderbuffer depthRB;
        GLFramebuffer fboA, fboB;
        setupContextState(colorTexA, depthRB, fboA, colorTexB, fboB, sharedDepthTex, program);

        threadSynchronization.nextStep(Step::Thread0OpenedRP);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1OpenedRP));

        // Both contexts now have an open render pass with |sharedDepthTex| as depth attachment.
        // In the Vulkan backend if tile memory is used, a reference to |depthRB| may be kept.
        // The draw framebuffer is not dirty.
        //
        // Redefine level 0 of |sharedDepthTex| to recreate its views.  This should not cause
        // use-after-free.
        glBindTexture(GL_TEXTURE_2D, sharedDepthTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, kSize * 2, kSize * 2, 0,
                     GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
        EXPECT_GL_NO_ERROR();

        glFinish();
        threadSynchronization.nextStep(Step::Thread0Redefined);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    };

    auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
        ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

        // Create the shared depth texture as a mutable texture so glTexImage2D can redefine it.
        GLuint depthTex;
        glGenTextures(1, &depthTex);
        glBindTexture(GL_TEXTURE_2D, depthTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                     GL_UNSIGNED_INT, nullptr);
        EXPECT_GL_NO_ERROR();
        sharedDepthTex = depthTex;

        threadSynchronization.nextStep(Step::Thread1CreatedDepthTex);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0OpenedRP));

        GLTexture colorTexA, colorTexB;
        GLRenderbuffer depthRB;
        GLFramebuffer fboA, fboB;
        setupContextState(colorTexA, depthRB, fboA, colorTexB, fboB, sharedDepthTex, program);

        threadSynchronization.nextStep(Step::Thread1OpenedRP);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Redefined));

        glFinish();
        glDeleteTextures(1, &depthTex);
        threadSynchronization.nextStep(Step::Finish);
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    };

    std::array<LockStepThreadFunc, 2> threadFuncs = {std::move(thread0), std::move(thread1)};
    RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
    ASSERT_NE(currentStep, Step::Abort);
}

// Regression test: respecifying a depth texture's max level that is bound as the depth attachment
// of the current draw FBO in two share-group contexts (each with an open render pass and a
// surviving mImageWithTileMemory pointer) must not dereference a freed RenderTargetVk via
// FramebufferVk::getImageWithTileMemory() during the re-entrant submitCommands() inside
// TextureVk::stageSelfAsSubresourceUpdates().
TEST_P(VulkanPerformanceCounterTest_TileMemory, RespecifySharedDepthTextureWithOpenRenderPasses)
{
    ANGLE_SKIP_TEST_IF(!isFeatureEnabled(Feature::SimulateTileMemoryForTesting) &&
                       !isFeatureEnabled(Feature::SupportsTileMemoryHeap));

    constexpr GLsizei kSize = 64;

    enum class Step
    {
        Start,
        Thread1CreatedDepthTex,
        Thread0OpenedRP,
        Thread1OpenedRP,
        Thread0Respecified,
        Finish,
        Abort,
    };
    Step currentStep = Step::Start;
    std::mutex mutex;
    std::condition_variable condVar;
    GLuint sharedDepthTex = 0;

    // Per-context setup:
    // 1. FBO_A: Color and a depth renderbuffer that is invalidated.  The Vulkan backend may use
    //    tile memory for this depth renderbuffer
    // 2. FBO_B: Color and the shared depth texture.  The Vulkan backend does not use tile memory
    //    for textures.
    auto setupContextState = [&](GLuint colorTexA, GLuint depthRB, GLuint fboA, GLuint colorTexB,
                                 GLuint fboB, GLuint depthTex, GLuint program) {
        glBindTexture(GL_TEXTURE_2D, colorTexA);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
        glBindRenderbuffer(GL_RENDERBUFFER, depthRB);
        glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, kSize, kSize);
        glBindFramebuffer(GL_FRAMEBUFFER, fboA);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexA, 0);
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRB);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        glViewport(0, 0, kSize, kSize);
        glEnable(GL_DEPTH_TEST);
        glDepthMask(GL_TRUE);
        glDepthFunc(GL_ALWAYS);
        glClearDepthf(1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        const GLenum kDepthAttachment = GL_DEPTH_ATTACHMENT;
        glInvalidateFramebuffer(GL_FRAMEBUFFER, 1, &kDepthAttachment);
        EXPECT_GL_NO_ERROR();

        glBindTexture(GL_TEXTURE_2D, colorTexB);
        glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSize, kSize);
        glBindFramebuffer(GL_FRAMEBUFFER, fboB);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colorTexB, 0);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthTex, 0);
        EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();
    };

    auto thread0 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
        ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));

        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1CreatedDepthTex));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
        GLTexture colorTexA, colorTexB;
        GLRenderbuffer depthRB;
        GLFramebuffer fboA, fboB;
        setupContextState(colorTexA, depthRB, fboA, colorTexB, fboB, sharedDepthTex, program);

        threadSynchronization.nextStep(Step::Thread0OpenedRP);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread1OpenedRP));

        // Both contexts now have an open render pass with |sharedDepthTex| as depth attachment.
        // In the Vulkan backend if tile memory is used, a reference to |depthRB| may be kept.
        // The draw framebuffer is not dirty.
        //
        // Respecify max level of |sharedDepthTex| to recreate its views.  This should not cause
        // use-after-free.
        glBindTexture(GL_TEXTURE_2D, sharedDepthTex);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
        EXPECT_GL_NO_ERROR();

        glFinish();
        threadSynchronization.nextStep(Step::Thread0Respecified);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Finish));
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    };

    auto thread1 = [&](EGLDisplay dpy, EGLSurface surface, EGLContext context) {
        ThreadSynchronization<Step> threadSynchronization(&currentStep, &mutex, &condVar);
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, surface, surface, context));

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

        // Create the shared depth texture as a mutable texture so glTexParameteri can respecify it.
        GLuint depthTex;
        glGenTextures(1, &depthTex);
        glBindTexture(GL_TEXTURE_2D, depthTex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, kSize, kSize, 0, GL_DEPTH_COMPONENT,
                     GL_UNSIGNED_INT, nullptr);
        EXPECT_GL_NO_ERROR();
        sharedDepthTex = depthTex;

        threadSynchronization.nextStep(Step::Thread1CreatedDepthTex);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0OpenedRP));

        GLTexture colorTexA, colorTexB;
        GLRenderbuffer depthRB;
        GLFramebuffer fboA, fboB;
        setupContextState(colorTexA, depthRB, fboA, colorTexB, fboB, sharedDepthTex, program);

        threadSynchronization.nextStep(Step::Thread1OpenedRP);
        ASSERT_TRUE(threadSynchronization.waitForStep(Step::Thread0Respecified));

        glFinish();
        glDeleteTextures(1, &depthTex);
        threadSynchronization.nextStep(Step::Finish);
        EXPECT_EGL_TRUE(eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
    };

    std::array<LockStepThreadFunc, 2> threadFuncs = {std::move(thread0), std::move(thread1)};
    RunLockStepThreads(getEGLWindow(), threadFuncs.size(), threadFuncs.data());
    ASSERT_NE(currentStep, Step::Abort);
}

// Enable SimulateTileMemoryForTesting feature to get some test coverage on bots. Note that if both
// SimulateTileMemoryForTesting and SupportsTileMemoryHeap are enabled, SupportsTileMemoryHeap will
// take precedence.
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_TileMemory);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_TileMemory,
                       ES3_VULKAN(),
                       ES3_VULKAN().enable(Feature::SimulateTileMemoryForTesting),
                       ES3_VULKAN()
                           .enable(Feature::SimulateTileMemoryForTesting)
                           .disable(Feature::SupportsImagelessFramebuffer),
                       ES3_VULKAN_SWIFTSHADER().enable(Feature::SimulateTileMemoryForTesting));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(VulkanPerformanceCounterTest_ClipDistance);
ANGLE_INSTANTIATE_TEST(VulkanPerformanceCounterTest_ClipDistance, ES3_VULKAN_SWIFTSHADER());
}  // anonymous namespace
