perf tests: Record perf counter metrics.

This adds a new command line argument that will allow the user to
specify perf counters to record into the test output.

Bug: angleproject:4918
Change-Id: Ia7432ff96eadf13ef681f67d2d503d00fd83e06e
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3516970
Reviewed-by: Lingfeng Yang <lfy@google.com>
Reviewed-by: Charlie Lao <cclao@google.com>
Reviewed-by: Yuxin Hu <yuxinhu@google.com>
Commit-Queue: Jamie Madill <jmadill@chromium.org>
diff --git a/src/common/angleutils.h b/src/common/angleutils.h
index dd70f4f..06d412b 100644
--- a/src/common/angleutils.h
+++ b/src/common/angleutils.h
@@ -90,6 +90,8 @@
 };
 
 // AMD_performance_monitor helpers.
+constexpr char kPerfMonitorExtensionName[] = "GL_AMD_performance_monitor";
+
 struct PerfMonitorCounter
 {
     PerfMonitorCounter();
diff --git a/src/tests/gl_tests/VulkanPerformanceCounterTest.cpp b/src/tests/gl_tests/VulkanPerformanceCounterTest.cpp
index 13ab895..c9d0735 100644
--- a/src/tests/gl_tests/VulkanPerformanceCounterTest.cpp
+++ b/src/tests/gl_tests/VulkanPerformanceCounterTest.cpp
@@ -18,62 +18,12 @@
 
 #include "test_utils/gl_raii.h"
 #include "util/random_utils.h"
+#include "util/shader_utils.h"
 
 using namespace angle;
 
 namespace
 {
-constexpr char kExtensionName[] = "GL_AMD_performance_monitor";
-
-using CounterNameToIndexMap = std::map<std::string, GLuint>;
-
-CounterNameToIndexMap BuildCounterNameToIndexMap()
-{
-    GLint numCounters = 0;
-    glGetPerfMonitorCountersAMD(0, &numCounters, nullptr, 0, nullptr);
-    EXPECT_GL_NO_ERROR();
-
-    std::vector<GLuint> counterIndexes(numCounters, 0);
-    glGetPerfMonitorCountersAMD(0, nullptr, nullptr, numCounters, counterIndexes.data());
-    EXPECT_GL_NO_ERROR();
-
-    CounterNameToIndexMap indexMap;
-
-    for (GLuint counterIndex : counterIndexes)
-    {
-        static constexpr size_t kBufSize = 1000;
-        char buffer[kBufSize]            = {};
-        glGetPerfMonitorCounterStringAMD(0, counterIndex, kBufSize, nullptr, buffer);
-        EXPECT_GL_NO_ERROR();
-
-        indexMap[buffer] = counterIndex;
-    }
-
-    return indexMap;
-}
-
-void UpdatePerfCounter(const CounterNameToIndexMap &counterIndexMap,
-                       GLuint *counterOut,
-                       const char *name,
-                       std::vector<angle::PerfMonitorTriplet> &triplets)
-{
-    auto iter = counterIndexMap.find(name);
-    ASSERT(iter != counterIndexMap.end());
-    GLuint counterIndex = iter->second;
-
-    for (const angle::PerfMonitorTriplet &triplet : triplets)
-    {
-        ASSERT(triplet.group == 0);
-        if (triplet.counter == counterIndex)
-        {
-            *counterOut = triplet.value;
-            return;
-        }
-    }
-
-    UNREACHABLE();
-}
-
 class VulkanPerformanceCounterTest : public ANGLETest
 {
   protected:
@@ -213,33 +163,12 @@
 
     angle::VulkanPerfCounters getPerfCounters()
     {
-        GLuint resultSize = 0;
-        glGetPerfMonitorCounterDataAMD(0, GL_PERFMON_RESULT_SIZE_AMD, sizeof(GLuint), &resultSize,
-                                       nullptr);
-        EXPECT_GL_NO_ERROR();
-        EXPECT_GT(resultSize, 0u);
-
-        std::vector<angle::PerfMonitorTriplet> perfResults(resultSize /
-                                                           sizeof(angle::PerfMonitorTriplet));
-        glGetPerfMonitorCounterDataAMD(0, GL_PERFMON_RESULT_AMD,
-                                       perfResults.size() * sizeof(perfResults[0]),
-                                       &perfResults.data()->group, nullptr);
-
         if (mIndexMap.empty())
         {
             mIndexMap = BuildCounterNameToIndexMap();
         }
 
-        angle::VulkanPerfCounters counters;
-
-#define ANGLE_UNPACK_PERF_COUNTER(COUNTER) \
-    UpdatePerfCounter(mIndexMap, &counters.COUNTER, #COUNTER, perfResults);
-
-        ANGLE_VK_PERF_COUNTERS_X(ANGLE_UNPACK_PERF_COUNTER)
-
-#undef ANGLE_UNPACK_PERF_COUNTER
-
-        return counters;
+        return GetPerfCounters(mIndexMap);
     }
 
     CounterNameToIndexMap mIndexMap;
@@ -261,7 +190,7 @@
 // Tests that texture updates to unused textures don't break the RP.
 TEST_P(VulkanPerformanceCounterTest, NewTextureDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     GLColor kInitialData[4] = {GLColor::red, GLColor::blue, GLColor::green, GLColor::yellow};
 
@@ -311,7 +240,7 @@
 // Tests that RGB texture should not break renderpass.
 TEST_P(VulkanPerformanceCounterTest, SampleFromRGBTextureDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
     glUseProgram(program);
@@ -354,7 +283,7 @@
 // Tests that RGB texture should not break renderpass.
 TEST_P(VulkanPerformanceCounterTest, RenderToRGBTextureDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
     glUseProgram(program);
@@ -393,7 +322,7 @@
 // Tests that changing a Texture's max level hits the descriptor set cache.
 TEST_P(VulkanPerformanceCounterTest, ChangingMaxLevelHitsDescriptorCache)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     GLColor kInitialData[4] = {GLColor::red, GLColor::blue, GLColor::green, GLColor::yellow};
 
@@ -445,7 +374,7 @@
 // Tests that two glCopyBufferSubData commands can share a barrier.
 TEST_P(VulkanPerformanceCounterTest, IndependentBufferCopiesShareSingleBarrier)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr GLint srcDataA[] = {1, 2, 3, 4};
     constexpr GLint srcDataB[] = {5, 6, 7, 8};
@@ -491,7 +420,7 @@
 // used
 TEST_P(VulkanPerformanceCounterTest_ES31, MultisampleResolveWithBlit)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr int kSize = 16;
     glViewport(0, 0, kSize, kSize);
@@ -544,7 +473,7 @@
 // Ensures a read-only depth-stencil feedback loop works in a single RenderPass.
 TEST_P(VulkanPerformanceCounterTest, ReadOnlyDepthStencilFeedbackLoopUsesSingleRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr GLsizei kSize = 4;
 
@@ -634,7 +563,7 @@
 // - Scenario: invalidate, disable, draw
 TEST_P(VulkanPerformanceCounterTest, InvalidateDisableDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -682,7 +611,7 @@
 // - Scenario: disable, invalidate, draw
 TEST_P(VulkanPerformanceCounterTest, DisableInvalidateDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -730,7 +659,7 @@
 // - Scenario: disable, draw, invalidate, enable
 TEST_P(VulkanPerformanceCounterTest, DisableDrawInvalidateEnable)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -789,7 +718,7 @@
 // - Scenario: invalidate
 TEST_P(VulkanPerformanceCounterTest, Invalidate)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -828,7 +757,7 @@
 // whole framebuffer.
 TEST_P(VulkanPerformanceCounterTest, InvalidateSub)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -869,7 +798,7 @@
 // - Scenario: invalidate, draw
 TEST_P(VulkanPerformanceCounterTest, InvalidateDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -913,7 +842,7 @@
 // - Scenario: invalidate, draw, disable
 TEST_P(VulkanPerformanceCounterTest, InvalidateDrawDisable)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     // http://anglebug.com/6857
     ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsVulkan());
@@ -967,7 +896,7 @@
 // - Scenario: invalidate, disable, draw, enable
 TEST_P(VulkanPerformanceCounterTest, InvalidateDisableDrawEnable)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+0), stencil(Clears+0, Load+0, Stores+0)
@@ -1020,7 +949,7 @@
 // - Scenario: invalidate, disable, draw, enable, draw
 TEST_P(VulkanPerformanceCounterTest, InvalidateDisableDrawEnableDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+0)
@@ -1075,7 +1004,7 @@
 // - Scenario: invalidate, draw, disable, enable
 TEST_P(VulkanPerformanceCounterTest, InvalidateDrawDisableEnable)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+0)
@@ -1131,7 +1060,7 @@
 // - Scenario: invalidate, draw, disable, enable, invalidate
 TEST_P(VulkanPerformanceCounterTest, InvalidateDrawDisableEnableInvalidate)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+0), stencil(Clears+0, Load+0, Stores+0)
@@ -1189,7 +1118,7 @@
 // - Scenario: invalidate, draw, disable, enable, invalidate, draw
 TEST_P(VulkanPerformanceCounterTest, InvalidateDrawDisableEnableInvalidateDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+0)
@@ -1251,7 +1180,7 @@
 // - Scenario: invalidate, disable, enable, draw
 TEST_P(VulkanPerformanceCounterTest, InvalidateDisableEnableDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+0)
@@ -1303,7 +1232,7 @@
 // Tests that an in renderpass clear after invalidate keeps content stored.
 TEST_P(VulkanPerformanceCounterTest, InvalidateAndClear)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+0)
@@ -1353,7 +1282,7 @@
 // content stored.
 TEST_P(VulkanPerformanceCounterTest, InvalidateAndMaskedClear)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+1, Load+0, Stores+1)
@@ -1414,7 +1343,7 @@
 // - Scenario: invalidate, detach D/S texture and modify it, attach D/S texture, draw with blend
 TEST_P(VulkanPerformanceCounterTest, InvalidateDetachModifyTexAttachDrawWithBlend)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+1)
@@ -1504,7 +1433,7 @@
 // - Scenario: invalidate
 TEST_P(VulkanPerformanceCounterTest, InvalidateDrawAndDeleteRenderbuffer)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+1), stencil(Clears+0, Load+0, Stores+0)
@@ -1549,7 +1478,7 @@
 // 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(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     ANGLE_GL_PROGRAM(redProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
 
@@ -1574,7 +1503,7 @@
 // Tests that masked color clears don't break the RP.
 TEST_P(VulkanPerformanceCounterTest, MaskedColorClearDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     GLTexture texture;
     glBindTexture(GL_TEXTURE_2D, texture);
@@ -1614,7 +1543,7 @@
 // Tests that masked color/depth/stencil clears don't break the RP.
 TEST_P(VulkanPerformanceCounterTest, MaskedClearDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr GLsizei kSize = 64;
 
@@ -1695,7 +1624,7 @@
 // Tests that clear followed by scissored draw uses loadOp to clear.
 TEST_P(VulkanPerformanceCounterTest, ClearThenScissoredDraw)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     uint32_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
     uint32_t expectedDepthClears     = getPerfCounters().depthClears + 1;
@@ -1768,7 +1697,7 @@
 // Tests that scissored clears don't break the RP.
 TEST_P(VulkanPerformanceCounterTest, ScissoredClearDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr GLsizei kSize = 64;
 
@@ -1866,7 +1795,7 @@
 // Tests that draw buffer change with all color channel mask off should not break renderpass
 TEST_P(VulkanPerformanceCounterTest, DrawbufferChangeWithAllColorMaskDisabled)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Passthrough(), essl1_shaders::fs::UniformColor());
     glUseProgram(program);
@@ -1925,7 +1854,7 @@
 // Tests the optimization that a glFlush call issued inside a renderpass will be skipped.
 TEST_P(VulkanPerformanceCounterTest, InRenderpassFlushShouldNotBreakRenderpass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     uint32_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
 
     GLTexture texture;
@@ -1952,7 +1881,7 @@
 // Tests that depth/stencil texture clear/load works correctly.
 TEST_P(VulkanPerformanceCounterTest, DepthStencilTextureClearAndLoad)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     // TODO: http://anglebug.com/5329 Flaky test
     ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
@@ -2065,7 +1994,7 @@
 // Tests that multisampled-render-to-texture depth/stencil textures don't ever load data.
 TEST_P(VulkanPerformanceCounterTest, RenderToTextureDepthStencilTextureShouldNotLoad)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     // http://anglebug.com/5083
     ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());
@@ -2193,7 +2122,7 @@
     ANGLE_SKIP_TEST_IF(IsWindows7() && IsNVIDIA() && IsVulkan());
 
     ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -2316,7 +2245,7 @@
 
     ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
 
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     angle::VulkanPerfCounters expected;
 
@@ -2442,7 +2371,7 @@
 
     ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_multisampled_render_to_texture"));
 
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, no depth/stencil clear, load or store.
@@ -2508,7 +2437,7 @@
 // Ensures we use read-only depth layout when there is no write
 TEST_P(VulkanPerformanceCounterTest, ReadOnlyDepthBufferLayout)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr GLsizei kSize = 64;
 
@@ -2577,7 +2506,7 @@
 // invalidate)
 TEST_P(VulkanPerformanceCounterTest, RenderPassAfterRenderPassWithoutDepthStencilWrite)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+0, Loads+0, Stores+0), stencil(Clears+0, Load+0, Stores+0)
@@ -2636,7 +2565,7 @@
 // etc) don't break the render pass.
 TEST_P(VulkanPerformanceCounterTest, ClearAfterClearDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     uint32_t expectedRenderPassCount = getPerfCounters().renderPasses + 1;
 
     constexpr GLsizei kSize = 6;
@@ -2780,7 +2709,7 @@
 // Ensures that changing the scissor size doesn't break the render pass.
 TEST_P(VulkanPerformanceCounterTest, ScissorDoesNotBreakRenderPass)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     constexpr GLsizei kSize = 16;
 
@@ -3026,7 +2955,7 @@
 // Tests that changing UBO bindings does not allocate new descriptor sets.
 TEST_P(VulkanPerformanceCounterTest, ChangingUBOsHitsDescriptorSetCache)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     // Set up two UBOs, one filled with "1" and the second with "2".
     constexpr GLsizei kCount = 64;
@@ -3155,7 +3084,7 @@
 // waiting for the GPU access to complete before returning a pointer to the buffer.
 TEST_P(VulkanPerformanceCounterTest, MappingGpuReadOnlyBufferGhostsBuffer)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     // 1. Create a buffer, map it, fill it with red
     // 2. Draw with buffer (GPU read-only)
@@ -3252,7 +3181,7 @@
 // Verifies that BufferSubData calls don't trigger state updates for non-translated formats.
 TEST_P(VulkanPerformanceCounterTest, BufferSubDataShouldNotTriggerSyncState)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
     glUseProgram(testProgram);
@@ -3294,7 +3223,7 @@
 // Verifies that rendering to backbuffer discards depth/stencil.
 TEST_P(VulkanPerformanceCounterTest, SwapShouldInvalidateDepthStencil)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+0), stencil(Clears+1, Load+0, Stores+0)
@@ -3323,7 +3252,7 @@
 // Verifies that rendering to MSAA backbuffer discards depth/stencil.
 TEST_P(VulkanPerformanceCounterTest_MSAA, SwapShouldInvalidateDepthStencil)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
     angle::VulkanPerfCounters expected;
 
     // Expect rpCount+1, depth(Clears+1, Loads+0, Stores+0), stencil(Clears+1, Load+0, Stores+0)
@@ -3352,7 +3281,7 @@
 // Tests that uniform updates eventually stop updating descriptor sets.
 TEST_P(VulkanPerformanceCounterTest, UniformUpdatesHitDescriptorSetCache)
 {
-    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kExtensionName));
+    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled(kPerfMonitorExtensionName));
 
     ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
     glUseProgram(testProgram);
diff --git a/src/tests/perf_tests/ANGLEPerfTest.cpp b/src/tests/perf_tests/ANGLEPerfTest.cpp
index 7a4859b..30df5d8 100644
--- a/src/tests/perf_tests/ANGLEPerfTest.cpp
+++ b/src/tests/perf_tests/ANGLEPerfTest.cpp
@@ -13,6 +13,7 @@
 #include "common/debug.h"
 #include "common/mathutil.h"
 #include "common/platform.h"
+#include "common/string_utils.h"
 #include "common/system_utils.h"
 #include "common/utilities.h"
 #include "test_utils/runner/TestSuite.h"
@@ -467,6 +468,43 @@
         mReporter->AddResult(".total_steps", static_cast<size_t>(mTotalNumStepsPerformed));
     }
 
+    for (const auto &iter : mPerfCounterInfo)
+    {
+        const std::string &counterName = iter.second.name;
+        std::vector<GLuint> samples    = iter.second.samples;
+
+        size_t midpoint = samples.size() >> 1;
+        std::nth_element(samples.begin(), samples.begin() + midpoint, samples.end());
+
+        {
+            std::stringstream medianStr;
+            medianStr << "." << counterName << "_median";
+            std::string medianName = medianStr.str();
+
+            mReporter->AddResult(medianName, static_cast<size_t>(samples[midpoint]));
+        }
+
+        {
+            std::string measurement = mName + mBackend + "." + counterName + "_median";
+            TestSuite::GetInstance()->addHistogramSample(measurement, mStory, samples[midpoint],
+                                                         "count");
+        }
+
+        const auto &maxIt = std::max_element(samples.begin(), samples.end());
+
+        {
+            std::stringstream maxStr;
+            maxStr << "." << counterName << "_max";
+            std::string maxName = maxStr.str();
+            mReporter->AddResult(maxName, static_cast<size_t>(*maxIt));
+        }
+
+        {
+            std::string measurement = mName + mBackend + "." + counterName + "_max";
+            TestSuite::GetInstance()->addHistogramSample(measurement, mStory, *maxIt, "count");
+        }
+    }
+
     return retValue;
 }
 
@@ -835,6 +873,8 @@
         // between calibration measurements.
         calibrateStepsToRun(RunLoopPolicy::FinishEveryStep);
     }
+
+    initPerfCounters();
 }
 
 void ANGLERenderTest::TearDown()
@@ -865,6 +905,73 @@
     ANGLEPerfTest::TearDown();
 }
 
+void ANGLERenderTest::initPerfCounters()
+{
+    if (!gPerfCounters)
+    {
+        return;
+    }
+
+    if (!IsGLExtensionEnabled(kPerfMonitorExtensionName))
+    {
+        fprintf(stderr, "Cannot report perf metrics because %s is not available.\n",
+                kPerfMonitorExtensionName);
+        return;
+    }
+
+    CounterNameToIndexMap indexMap = BuildCounterNameToIndexMap();
+
+    std::vector<std::string> counters =
+        angle::SplitString(gPerfCounters, ":", angle::WhitespaceHandling::TRIM_WHITESPACE,
+                           angle::SplitResult::SPLIT_WANT_NONEMPTY);
+    for (const std::string &counter : counters)
+    {
+        auto iter = indexMap.find(counter);
+        if (iter == indexMap.end())
+        {
+            fprintf(stderr, "Counter '%s' not in list of available perf counters.\n",
+                    counter.c_str());
+        }
+        else
+        {
+            {
+                std::stringstream medianStr;
+                medianStr << '.' << counter << "_median";
+                std::string medianName = medianStr.str();
+                mReporter->RegisterImportantMetric(medianName, "count");
+            }
+
+            {
+                std::stringstream maxStr;
+                maxStr << '.' << counter << "_max";
+                std::string maxName = maxStr.str();
+                mReporter->RegisterImportantMetric(maxName, "count");
+            }
+
+            GLuint index            = indexMap[counter];
+            mPerfCounterInfo[index] = {counter, {}};
+        }
+    }
+}
+
+void ANGLERenderTest::updatePerfCounters()
+{
+    if (mPerfCounterInfo.empty())
+    {
+        return;
+    }
+
+    std::vector<PerfMonitorTriplet> perfData = GetPerfMonitorTriplets();
+    ASSERT(!perfData.empty());
+
+    for (auto &iter : mPerfCounterInfo)
+    {
+        uint32_t counter             = iter.first;
+        std::vector<GLuint> &samples = iter.second.samples;
+        samples.push_back(perfData[counter].value);
+    }
+}
+
 void ANGLERenderTest::beginInternalTraceEvent(const char *name)
 {
     if (gEnableTrace)
@@ -933,6 +1040,7 @@
         // command queues.
         if (mSwapEnabled)
         {
+            updatePerfCounters();
             mGLWindow->swap();
         }
         mOSWindow->messageLoop();
diff --git a/src/tests/perf_tests/ANGLEPerfTest.h b/src/tests/perf_tests/ANGLEPerfTest.h
index 3093dd4..a44beaf 100644
--- a/src/tests/perf_tests/ANGLEPerfTest.h
+++ b/src/tests/perf_tests/ANGLEPerfTest.h
@@ -122,6 +122,13 @@
     int mIterationsPerStep;
     bool mRunning;
     std::vector<double> mTestTrialResults;
+
+    struct CounterInfo
+    {
+        std::string name;
+        std::vector<GLuint> samples;
+    };
+    angle::HashMap<GLuint, CounterInfo> mPerfCounterInfo;
 };
 
 enum class SurfaceType
@@ -190,6 +197,7 @@
     void endGLTraceEvent(const char *name, double hostTimeSec);
 
     void disableTestHarnessSwap() { mSwapEnabled = false; }
+    void updatePerfCounters();
 
     bool mIsTimestampQueryAvailable;
 
@@ -204,6 +212,8 @@
 
     bool areExtensionPrerequisitesFulfilled() const;
 
+    void initPerfCounters();
+
     GLWindowBase *mGLWindow;
     OSWindow *mOSWindow;
     std::vector<const char *> mExtensionPrerequisites;
diff --git a/src/tests/perf_tests/ANGLEPerfTestArgs.cpp b/src/tests/perf_tests/ANGLEPerfTestArgs.cpp
index 313ab2c..af27464 100644
--- a/src/tests/perf_tests/ANGLEPerfTestArgs.cpp
+++ b/src/tests/perf_tests/ANGLEPerfTestArgs.cpp
@@ -29,6 +29,7 @@
 bool gRetraceMode              = false;
 bool gMinimizeGPUWork          = false;
 bool gTraceTestValidation      = false;
+const char *gPerfCounters      = nullptr;
 
 // Default to three warmup loops. There's no science to this. More than two loops was experimentally
 // helpful on a Windows NVIDIA setup when testing with Vulkan and native trace tests.
@@ -171,5 +172,10 @@
             gTestTrials          = 1;
             gMaxTrialTimeSeconds = 600.0;
         }
+        else if (strcmp("--perf-counters", argv[argIndex]) == 0 && argIndex < *argc - 1)
+        {
+            gPerfCounters = argv[argIndex + 1];
+            argIndex++;
+        }
     }
 }
diff --git a/src/tests/perf_tests/ANGLEPerfTestArgs.h b/src/tests/perf_tests/ANGLEPerfTestArgs.h
index 91d8c51..3c71736 100644
--- a/src/tests/perf_tests/ANGLEPerfTestArgs.h
+++ b/src/tests/perf_tests/ANGLEPerfTestArgs.h
@@ -31,6 +31,7 @@
 extern bool gRetraceMode;
 extern bool gMinimizeGPUWork;
 extern bool gTraceTestValidation;
+extern const char *gPerfCounters;
 
 inline bool OneFrame()
 {
diff --git a/src/tests/perf_tests/README.md b/src/tests/perf_tests/README.md
index 77e8666..f0d1ba9 100644
--- a/src/tests/perf_tests/README.md
+++ b/src/tests/perf_tests/README.md
@@ -42,6 +42,7 @@
 * `--enable-all-trace-tests`: Offscreen and vsync-limited trace tests are disabled by default to reduce test time.
 * `--minimize-gpu-work`: Modify API calls so that GPU work is reduced to minimum.
 * `--validation`: Enable serialization validation in the trace tests. Normally used with SwiftShader and retracing.
+* `--perf-counters`: Additional performance counters to include in the result output. Separate multiple entries with colons: ':'.
 
 For example, for an endless run with no warmup, run:
 
diff --git a/src/tests/perf_tests/TracePerfTest.cpp b/src/tests/perf_tests/TracePerfTest.cpp
index 7be1a7a..b76422c 100644
--- a/src/tests/perf_tests/TracePerfTest.cpp
+++ b/src/tests/perf_tests/TracePerfTest.cpp
@@ -1418,6 +1418,8 @@
     mTraceLibrary->replayFrame(mCurrentFrame);
     stopGpuTimer();
 
+    updatePerfCounters();
+
     if (mParams.surfaceType == SurfaceType::Offscreen)
     {
         if (gMinimizeGPUWork)
diff --git a/src/tests/run_perf_tests.py b/src/tests/run_perf_tests.py
index 9d31507..693e4c8 100755
--- a/src/tests/run_perf_tests.py
+++ b/src/tests/run_perf_tests.py
@@ -303,6 +303,8 @@
         default=DEFAULT_CALIBRATION_TIME)
     parser.add_argument(
         '--show-test-stdout', help='Prints all test stdout during execution.', action='store_true')
+    parser.add_argument(
+        '--perf-counters', help='Colon-separated list of extra perf counter metrics.')
 
     args, extra_flags = parser.parse_known_args()
 
@@ -407,10 +409,15 @@
                 '--trials',
                 str(args.trials_per_sample),
             ]
+
             if args.smoke_test_mode:
                 cmd_run += ['--no-warmup']
             else:
                 cmd_run += ['--warmup-loops', str(args.warmup_loops)]
+
+            if args.perf_counters:
+                cmd_run += ['--perf-counters', args.perf_counters]
+
             with common.temporary_file() as histogram_file_path:
                 cmd_run += ['--isolated-script-test-perf-output=%s' % histogram_file_path]
                 exit_code, output = _run_and_get_output(args, cmd_run, env)
diff --git a/src/tests/test_utils/runner/HistogramWriter.cpp b/src/tests/test_utils/runner/HistogramWriter.cpp
index 14074b4..6665a01 100644
--- a/src/tests/test_utils/runner/HistogramWriter.cpp
+++ b/src/tests/test_utils/runner/HistogramWriter.cpp
@@ -34,11 +34,59 @@
 {
 namespace
 {
-std::string GetUnitAndDirection(proto::UnitAndDirection unit)
+std::string UnitAndDirectionToString(proto::UnitAndDirection unit)
 {
-    ASSERT(unit.improvement_direction() == proto::SMALLER_IS_BETTER);
-    ASSERT(unit.unit() == proto::MS_BEST_FIT_FORMAT);
-    return "msBestFitFormat_smallerIsBetter";
+    std::stringstream strstr;
+
+    switch (unit.unit())
+    {
+        case proto::MS_BEST_FIT_FORMAT:
+            strstr << "msBestFitFormat";
+            break;
+        case proto::COUNT:
+            strstr << "count";
+            break;
+        default:
+            UNREACHABLE();
+            strstr << "error";
+            break;
+    }
+
+    switch (unit.improvement_direction())
+    {
+        case proto::NOT_SPECIFIED:
+            break;
+        case proto::SMALLER_IS_BETTER:
+            strstr << "_smallerIsBetter";
+            break;
+        default:
+            UNREACHABLE();
+            break;
+    }
+
+    return strstr.str();
+}
+
+proto::UnitAndDirection StringToUnitAndDirection(const std::string &str)
+{
+    proto::UnitAndDirection unitAndDirection;
+
+    if (str == "count")
+    {
+        unitAndDirection.set_improvement_direction(proto::NOT_SPECIFIED);
+        unitAndDirection.set_unit(proto::COUNT);
+    }
+    else if (str == "msBestFitFormat_smallerIsBetter")
+    {
+        unitAndDirection.set_improvement_direction(proto::SMALLER_IS_BETTER);
+        unitAndDirection.set_unit(proto::MS_BEST_FIT_FORMAT);
+    }
+    else
+    {
+        UNREACHABLE();
+    }
+
+    return unitAndDirection;
 }
 }  // namespace
 
@@ -54,9 +102,7 @@
     std::string measurementAndStory = measurement + story;
     if (mHistograms.count(measurementAndStory) == 0)
     {
-        proto::UnitAndDirection unitAndDirection;
-        unitAndDirection.set_improvement_direction(proto::SMALLER_IS_BETTER);
-        unitAndDirection.set_unit(proto::MS_BEST_FIT_FORMAT);
+        proto::UnitAndDirection unitAndDirection = StringToUnitAndDirection(units);
 
         std::unique_ptr<catapult::HistogramBuilder> builder =
             std::make_unique<catapult::HistogramBuilder>(measurement, unitAndDirection);
@@ -102,7 +148,7 @@
         js::Value description(histogram.description(), allocator);
         obj.AddMember("description", description, allocator);
 
-        js::Value unitAndDirection(GetUnitAndDirection(histogram.unit()), allocator);
+        js::Value unitAndDirection(UnitAndDirectionToString(histogram.unit()), allocator);
         obj.AddMember("unit", unitAndDirection, allocator);
 
         if (histogram.has_diagnostics())
diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp
index afc41d1..4473a13 100644
--- a/util/shader_utils.cpp
+++ b/util/shader_utils.cpp
@@ -135,6 +135,28 @@
         callbackChain(source, type, id, severity, length, message, gCallbackChainUserParam);
     }
 }
+
+void GetPerfCounterValue(const CounterNameToIndexMap &counterIndexMap,
+                         std::vector<angle::PerfMonitorTriplet> &triplets,
+                         const char *name,
+                         GLuint *counterOut)
+{
+    auto iter = counterIndexMap.find(name);
+    ASSERT(iter != counterIndexMap.end());
+    GLuint counterIndex = iter->second;
+
+    for (const angle::PerfMonitorTriplet &triplet : triplets)
+    {
+        ASSERT(triplet.group == 0);
+        if (triplet.counter == counterIndex)
+        {
+            *counterOut = triplet.value;
+            return;
+        }
+    }
+
+    UNREACHABLE();
+}
 }  // namespace
 
 GLuint CompileShader(GLenum type, const char *source)
@@ -372,6 +394,98 @@
     glDebugMessageCallbackKHR(DebugMessageCallback, reinterpret_cast<const void *>(callbackChain));
 }
 
+CounterNameToIndexMap BuildCounterNameToIndexMap()
+{
+    GLint numCounters = 0;
+    glGetPerfMonitorCountersAMD(0, &numCounters, nullptr, 0, nullptr);
+    if (glGetError() != GL_NO_ERROR)
+    {
+        return {};
+    }
+
+    std::vector<GLuint> counterIndexes(numCounters, 0);
+    glGetPerfMonitorCountersAMD(0, nullptr, nullptr, numCounters, counterIndexes.data());
+    if (glGetError() != GL_NO_ERROR)
+    {
+        return {};
+    }
+
+    CounterNameToIndexMap indexMap;
+
+    for (GLuint counterIndex : counterIndexes)
+    {
+        static constexpr size_t kBufSize = 1000;
+        char buffer[kBufSize]            = {};
+        glGetPerfMonitorCounterStringAMD(0, counterIndex, kBufSize, nullptr, buffer);
+        if (glGetError() != GL_NO_ERROR)
+        {
+            return {};
+        }
+
+        indexMap[buffer] = counterIndex;
+    }
+
+    return indexMap;
+}
+
+std::vector<angle::PerfMonitorTriplet> GetPerfMonitorTriplets()
+{
+    GLuint resultSize = 0;
+    glGetPerfMonitorCounterDataAMD(0, GL_PERFMON_RESULT_SIZE_AMD, sizeof(GLuint), &resultSize,
+                                   nullptr);
+    if (glGetError() != GL_NO_ERROR || resultSize == 0)
+    {
+        return {};
+    }
+
+    std::vector<angle::PerfMonitorTriplet> perfResults(resultSize /
+                                                       sizeof(angle::PerfMonitorTriplet));
+    glGetPerfMonitorCounterDataAMD(
+        0, GL_PERFMON_RESULT_AMD, static_cast<GLsizei>(perfResults.size() * sizeof(perfResults[0])),
+        &perfResults.data()->group, nullptr);
+
+    if (glGetError() != GL_NO_ERROR)
+    {
+        return {};
+    }
+
+    return perfResults;
+}
+
+angle::VulkanPerfCounters GetPerfCounters(const CounterNameToIndexMap &indexMap)
+{
+    std::vector<angle::PerfMonitorTriplet> perfResults = GetPerfMonitorTriplets();
+
+    angle::VulkanPerfCounters counters;
+
+#define ANGLE_UNPACK_PERF_COUNTER(COUNTER) \
+    GetPerfCounterValue(indexMap, perfResults, #COUNTER, &counters.COUNTER);
+
+    ANGLE_VK_PERF_COUNTERS_X(ANGLE_UNPACK_PERF_COUNTER)
+
+#undef ANGLE_UNPACK_PERF_COUNTER
+
+    return counters;
+}
+
+CounterNameToIndexMap BuildCounterNameToValueMap()
+{
+    CounterNameToIndexMap indexMap                     = BuildCounterNameToIndexMap();
+    std::vector<angle::PerfMonitorTriplet> perfResults = GetPerfMonitorTriplets();
+
+    CounterNameToValueMap valueMap;
+
+    for (const auto &iter : indexMap)
+    {
+        const std::string &name = iter.first;
+        GLuint index            = iter.second;
+
+        valueMap[name] = perfResults[index].value;
+    }
+
+    return valueMap;
+}
+
 namespace angle
 {
 
diff --git a/util/shader_utils.h b/util/shader_utils.h
index e2efcc7..d490b17 100644
--- a/util/shader_utils.h
+++ b/util/shader_utils.h
@@ -8,9 +8,11 @@
 #define SAMPLE_UTIL_SHADER_UTILS_H
 
 #include <functional>
+#include <map>
 #include <string>
 #include <vector>
 
+#include "common/angleutils.h"
 #include "util/util_export.h"
 #include "util/util_gl.h"
 
@@ -51,6 +53,14 @@
 
 ANGLE_UTIL_EXPORT void EnableDebugCallback(GLDEBUGPROC callbackChain, const void *userParam);
 
+using CounterNameToIndexMap = std::map<std::string, GLuint>;
+using CounterNameToValueMap = std::map<std::string, GLuint>;
+
+ANGLE_UTIL_EXPORT CounterNameToIndexMap BuildCounterNameToIndexMap();
+ANGLE_UTIL_EXPORT angle::VulkanPerfCounters GetPerfCounters(const CounterNameToIndexMap &indexMap);
+ANGLE_UTIL_EXPORT CounterNameToValueMap BuildCounterNameToValueMap();
+ANGLE_UTIL_EXPORT std::vector<angle::PerfMonitorTriplet> GetPerfMonitorTriplets();
+
 namespace angle
 {