Edit

kc3-lang/angle/src/tests/perf_tests/VulkanCommandBufferPerf.cpp

Branch :

  • Show log

    Commit

  • Author : Jamie Madill
    Date : 2018-10-30 15:14:52
    Hash : 3402d523
    Message : Try to reduce variance in angle_perftests. This change does a few things: - make perf test runner script print % variation instead of stddev This makes it a bit more clear how much variance there is. - stabilize CPU in the render perf tests Setting a thread affinity and priority should stop from switching cores during the run. Hopefully can prevent background noise from changing the test results. - warm up the benchmark with a few iterations This should hopefully make the test results a bit more stable. - output a new normalized perf result value The new result is normalized against the number of iterations. So it should hopefully be stable even if the number of iterations is changed. - increases the iteration count in the draw call perf tests. These tests were completely dominated by SwapBuffers time. Increasing the iterations per step means we actually are bottlenecked on CPU time instead. Bug: angleproject:2923 Change-Id: I5ee347cf93df239ac33b83dc5effe4c21e066736 Reviewed-on: https://chromium-review.googlesource.com/c/1303679 Commit-Queue: Jamie Madill <jmadill@chromium.org> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Yuly Novikov <ynovikov@chromium.org>

  • src/tests/perf_tests/VulkanCommandBufferPerf.cpp
  • // Copyright 2018 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    // VulkanCommandBufferPerf:
    //   Performance benchmark for Vulkan Primary/Secondary Command Buffer implementations.
    
    #include "ANGLEPerfTest.h"
    #include "test_utils/third_party/vulkan_command_buffer_utils.h"
    
    constexpr char kVertShaderText[] = R"(
    #version 400
    #extension GL_ARB_separate_shader_objects : enable
    #extension GL_ARB_shading_language_420pack : enable
    layout (std140, binding = 0) uniform bufferVals {
        mat4 mvp;
    } myBufferVals;
    layout (location = 0) in vec4 pos;
    layout (location = 1) in vec4 inColor;
    layout (location = 0) out vec4 outColor;
    void main() {
       outColor = inColor;
       gl_Position = myBufferVals.mvp * pos;
    })";
    
    constexpr char kFragShaderText[] = R"(
    #version 400
    #extension GL_ARB_separate_shader_objects : enable
    #extension GL_ARB_shading_language_420pack : enable
    layout (location = 0) in vec4 color;
    layout (location = 0) out vec4 outColor;
    void main() {
       outColor = color;
    })";
    
    using CommandBufferImpl = void (*)(sample_info &info,
                                       VkClearValue *clear_values,
                                       VkFence drawFence,
                                       VkSemaphore imageAcquiredSemaphore,
                                       int numBuffers);
    
    struct CommandBufferTestParams
    {
        CommandBufferImpl CBImplementation;
        std::string suffix;
        int frames  = 1;
        int buffers = 10;
    };
    
    class VulkanCommandBufferPerfTest : public ANGLEPerfTest,
                                        public ::testing::WithParamInterface<CommandBufferTestParams>
    {
      public:
        VulkanCommandBufferPerfTest();
    
        void SetUp() override;
        void TearDown() override;
        void step() override;
    
      private:
        VkClearValue mClearValues[2]        = {};
        VkSemaphore mImageAcquiredSemaphore = VK_NULL_HANDLE;
        VkFence mDrawFence                  = VK_NULL_HANDLE;
    
        VkResult res             = VK_NOT_READY;
        const bool mDepthPresent = true;
        struct sample_info mInfo = {};
        std::string mSampleTitle;
        CommandBufferImpl mCBImplementation = nullptr;
        int mFrames                         = 0;
        int mBuffers                        = 0;
    };
    
    VulkanCommandBufferPerfTest::VulkanCommandBufferPerfTest()
        : ANGLEPerfTest("VulkanCommandBufferPerfTest", GetParam().suffix, GetParam().frames)
    {
        mInfo             = {};
        mSampleTitle      = "Draw Textured Cube";
        mCBImplementation = GetParam().CBImplementation;
        mFrames           = GetParam().frames;
        mBuffers          = GetParam().buffers;
    }
    
    void VulkanCommandBufferPerfTest::SetUp()
    {
        init_global_layer_properties(mInfo);
        init_instance_extension_names(mInfo);
        init_device_extension_names(mInfo);
        init_instance(mInfo, mSampleTitle.c_str());
        init_enumerate_device(mInfo);
        init_window_size(mInfo, 500, 500);
        init_connection(mInfo);
        init_window(mInfo);
        init_swapchain_extension(mInfo);
        init_device(mInfo);
    
        init_command_pool(mInfo);
        init_command_buffer(mInfo);                   // Primary command buffer to hold secondaries
        init_command_buffer_array(mInfo, mBuffers);   // Array of primary command buffers
        init_command_buffer2_array(mInfo, mBuffers);  // Array containing all secondary buffers
        init_device_queue(mInfo);
        init_swap_chain(mInfo);
        init_depth_buffer(mInfo);
        init_uniform_buffer(mInfo);
        init_descriptor_and_pipeline_layouts(mInfo, false);
        init_renderpass(mInfo, mDepthPresent);
        init_shaders(mInfo, kVertShaderText, kFragShaderText);
        init_framebuffers(mInfo, mDepthPresent);
        init_vertex_buffer(mInfo, g_vb_solid_face_colors_Data, sizeof(g_vb_solid_face_colors_Data),
                           sizeof(g_vb_solid_face_colors_Data[0]), false);
        init_descriptor_pool(mInfo, false);
        init_descriptor_set(mInfo);
        init_pipeline_cache(mInfo);
        init_pipeline(mInfo, mDepthPresent);
    
        mClearValues[0].color.float32[0]     = 0.2f;
        mClearValues[0].color.float32[1]     = 0.2f;
        mClearValues[0].color.float32[2]     = 0.2f;
        mClearValues[0].color.float32[3]     = 0.2f;
        mClearValues[1].depthStencil.depth   = 1.0f;
        mClearValues[1].depthStencil.stencil = 0;
    
        VkSemaphoreCreateInfo imageAcquiredSemaphoreCreateInfo;
        imageAcquiredSemaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
        imageAcquiredSemaphoreCreateInfo.pNext = NULL;
        imageAcquiredSemaphoreCreateInfo.flags = 0;
        res = vkCreateSemaphore(mInfo.device, &imageAcquiredSemaphoreCreateInfo, NULL,
                                &mImageAcquiredSemaphore);
        ASSERT_EQ(VK_SUCCESS, res);
    
        VkFenceCreateInfo fenceInfo;
        fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
        fenceInfo.pNext = NULL;
        fenceInfo.flags = 0;
        res             = vkCreateFence(mInfo.device, &fenceInfo, NULL, &mDrawFence);
        ASSERT_EQ(VK_SUCCESS, res);
    }
    
    void VulkanCommandBufferPerfTest::step()
    {
        for (int x = 0; x < mFrames; x++)
        {
            mInfo.current_buffer = x % mInfo.swapchainImageCount;
    
            // Get the index of the next available swapchain image:
            res = vkAcquireNextImageKHR(mInfo.device, mInfo.swap_chain, UINT64_MAX,
                                        mImageAcquiredSemaphore, VK_NULL_HANDLE, &mInfo.current_buffer);
            // Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR
            // return codes
            ASSERT_EQ(VK_SUCCESS, res);
            mCBImplementation(mInfo, mClearValues, mDrawFence, mImageAcquiredSemaphore, mBuffers);
        }
    }
    
    void VulkanCommandBufferPerfTest::TearDown()
    {
        vkDestroySemaphore(mInfo.device, mImageAcquiredSemaphore, NULL);
        vkDestroyFence(mInfo.device, mDrawFence, NULL);
        destroy_pipeline(mInfo);
        destroy_pipeline_cache(mInfo);
        destroy_descriptor_pool(mInfo);
        destroy_vertex_buffer(mInfo);
        destroy_framebuffers(mInfo);
        destroy_shaders(mInfo);
        destroy_renderpass(mInfo);
        destroy_descriptor_and_pipeline_layouts(mInfo);
        destroy_uniform_buffer(mInfo);
        destroy_depth_buffer(mInfo);
        destroy_swap_chain(mInfo);
        destroy_command_buffer2_array(mInfo, mBuffers);
        destroy_command_buffer_array(mInfo, mBuffers);
        destroy_command_buffer(mInfo);
        destroy_command_pool(mInfo);
        destroy_device(mInfo);
        destroy_window(mInfo);
        destroy_instance(mInfo);
        ANGLEPerfTest::TearDown();
    }
    
    void PrimaryCommandBufferBenchmarkHundredIndividual(sample_info &info,
                                                        VkClearValue *clear_values,
                                                        VkFence drawFence,
                                                        VkSemaphore imageAcquiredSemaphore,
                                                        int numBuffers)
    {
        VkResult res;
    
        VkRenderPassBeginInfo rpBegin;
        rpBegin.sType                    = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        rpBegin.pNext                    = NULL;
        rpBegin.renderPass               = info.render_pass;
        rpBegin.framebuffer              = info.framebuffers[info.current_buffer];
        rpBegin.renderArea.offset.x      = 0;
        rpBegin.renderArea.offset.y      = 0;
        rpBegin.renderArea.extent.width  = info.width;
        rpBegin.renderArea.extent.height = info.height;
        rpBegin.clearValueCount          = 2;
        rpBegin.pClearValues             = clear_values;
    
        VkCommandBufferBeginInfo cmdBufferInfo = {};
        cmdBufferInfo.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        cmdBufferInfo.pNext                    = NULL;
        cmdBufferInfo.flags                    = 0;
        cmdBufferInfo.pInheritanceInfo         = NULL;
    
        for (int x = 0; x < numBuffers; x++)
        {
            vkBeginCommandBuffer(info.cmds[x], &cmdBufferInfo);
            vkCmdBeginRenderPass(info.cmds[x], &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
            vkCmdBindPipeline(info.cmds[x], VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline);
            vkCmdBindDescriptorSets(info.cmds[x], VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline_layout,
                                    0, NUM_DESCRIPTOR_SETS, info.desc_set.data(), 0, NULL);
    
            const VkDeviceSize offsets[1] = {0};
            vkCmdBindVertexBuffers(info.cmds[x], 0, 1, &info.vertex_buffer.buf, offsets);
    
            init_viewports_array(info, x);
            init_scissors_array(info, x);
    
            vkCmdDraw(info.cmds[x], 0, 1, 0, 0);
            vkCmdEndRenderPass(info.cmds[x]);
            res = vkEndCommandBuffer(info.cmds[x]);
            ASSERT_EQ(VK_SUCCESS, res);
        }
    
        VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        VkSubmitInfo submitInfo[1]            = {};
        submitInfo[0].pNext                   = NULL;
        submitInfo[0].sType                   = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submitInfo[0].waitSemaphoreCount      = 1;
        submitInfo[0].pWaitSemaphores         = &imageAcquiredSemaphore;
        submitInfo[0].pWaitDstStageMask       = &pipe_stage_flags;
        submitInfo[0].commandBufferCount      = numBuffers;
        submitInfo[0].pCommandBuffers         = info.cmds.data();
        submitInfo[0].signalSemaphoreCount    = 0;
        submitInfo[0].pSignalSemaphores       = NULL;
    
        // Queue the command buffer for execution
        res = vkQueueSubmit(info.graphics_queue, 1, submitInfo, drawFence);
        ASSERT_EQ(VK_SUCCESS, res);
    
        // Now present the image in the window
    
        VkPresentInfoKHR present;
        present.sType              = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        present.pNext              = NULL;
        present.swapchainCount     = 1;
        present.pSwapchains        = &info.swap_chain;
        present.pImageIndices      = &info.current_buffer;
        present.pWaitSemaphores    = NULL;
        present.waitSemaphoreCount = 0;
        present.pResults           = NULL;
    
        // Make sure command buffer is finished before presenting
        do
        {
            res = vkWaitForFences(info.device, 1, &drawFence, VK_TRUE, FENCE_TIMEOUT);
        } while (res == VK_TIMEOUT);
        vkResetFences(info.device, 1, &drawFence);
    
        ASSERT_EQ(VK_SUCCESS, res);
        res = vkQueuePresentKHR(info.present_queue, &present);
        ASSERT_EQ(VK_SUCCESS, res);
    }
    
    void PrimaryCommandBufferBenchmarkOneWithOneHundred(sample_info &info,
                                                        VkClearValue *clear_values,
                                                        VkFence drawFence,
                                                        VkSemaphore imageAcquiredSemaphore,
                                                        int numBuffers)
    {
        VkResult res;
    
        VkRenderPassBeginInfo rpBegin;
        rpBegin.sType                    = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        rpBegin.pNext                    = NULL;
        rpBegin.renderPass               = info.render_pass;
        rpBegin.framebuffer              = info.framebuffers[info.current_buffer];
        rpBegin.renderArea.offset.x      = 0;
        rpBegin.renderArea.offset.y      = 0;
        rpBegin.renderArea.extent.width  = info.width;
        rpBegin.renderArea.extent.height = info.height;
        rpBegin.clearValueCount          = 2;
        rpBegin.pClearValues             = clear_values;
    
        VkCommandBufferBeginInfo cmdBufferInfo = {};
        cmdBufferInfo.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        cmdBufferInfo.pNext                    = NULL;
        cmdBufferInfo.flags                    = 0;
        cmdBufferInfo.pInheritanceInfo         = NULL;
    
        vkBeginCommandBuffer(info.cmd, &cmdBufferInfo);
        for (int x = 0; x < numBuffers; x++)
        {
            vkCmdBeginRenderPass(info.cmd, &rpBegin, VK_SUBPASS_CONTENTS_INLINE);
            vkCmdBindPipeline(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline);
            vkCmdBindDescriptorSets(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline_layout, 0,
                                    NUM_DESCRIPTOR_SETS, info.desc_set.data(), 0, NULL);
    
            const VkDeviceSize offsets[1] = {0};
            vkCmdBindVertexBuffers(info.cmd, 0, 1, &info.vertex_buffer.buf, offsets);
    
            init_viewports(info);
            init_scissors(info);
    
            vkCmdDraw(info.cmd, 0, 1, 0, 0);
            vkCmdEndRenderPass(info.cmd);
        }
        res = vkEndCommandBuffer(info.cmd);
        ASSERT_EQ(VK_SUCCESS, res);
    
        const VkCommandBuffer cmd_bufs[]      = {info.cmd};
        VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        VkSubmitInfo submitInfo[1]            = {};
        submitInfo[0].pNext                   = NULL;
        submitInfo[0].sType                   = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submitInfo[0].waitSemaphoreCount      = 1;
        submitInfo[0].pWaitSemaphores         = &imageAcquiredSemaphore;
        submitInfo[0].pWaitDstStageMask       = &pipe_stage_flags;
        submitInfo[0].commandBufferCount      = 1;
        submitInfo[0].pCommandBuffers         = cmd_bufs;
        submitInfo[0].signalSemaphoreCount    = 0;
        submitInfo[0].pSignalSemaphores       = NULL;
    
        // Queue the command buffer for execution
        res = vkQueueSubmit(info.graphics_queue, 1, submitInfo, drawFence);
        ASSERT_EQ(VK_SUCCESS, res);
    
        // Now present the image in the window
    
        VkPresentInfoKHR present;
        present.sType              = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        present.pNext              = NULL;
        present.swapchainCount     = 1;
        present.pSwapchains        = &info.swap_chain;
        present.pImageIndices      = &info.current_buffer;
        present.pWaitSemaphores    = NULL;
        present.waitSemaphoreCount = 0;
        present.pResults           = NULL;
    
        // Make sure command buffer is finished before presenting
        do
        {
            res = vkWaitForFences(info.device, 1, &drawFence, VK_TRUE, FENCE_TIMEOUT);
        } while (res == VK_TIMEOUT);
        vkResetFences(info.device, 1, &drawFence);
    
        ASSERT_EQ(VK_SUCCESS, res);
        res = vkQueuePresentKHR(info.present_queue, &present);
        ASSERT_EQ(VK_SUCCESS, res);
    }
    
    void SecondaryCommandBufferBenchmark(sample_info &info,
                                         VkClearValue *clear_values,
                                         VkFence drawFence,
                                         VkSemaphore imageAcquiredSemaphore,
                                         int numBuffers)
    {
        VkResult res;
    
        // Record Secondary Command Buffer
        VkCommandBufferInheritanceInfo inheritInfo = {};
        inheritInfo.sType                          = VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO;
        inheritInfo.pNext                          = NULL;
        inheritInfo.renderPass                     = info.render_pass;
        inheritInfo.subpass                        = 0;
        inheritInfo.framebuffer                    = info.framebuffers[info.current_buffer];
        inheritInfo.occlusionQueryEnable           = false;
        inheritInfo.queryFlags                     = 0;
        inheritInfo.pipelineStatistics             = 0;
    
        VkCommandBufferBeginInfo secondaryCommandBufferInfo = {};
        secondaryCommandBufferInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        secondaryCommandBufferInfo.pNext = NULL;
        secondaryCommandBufferInfo.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT |
                                           VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
        secondaryCommandBufferInfo.pInheritanceInfo = &inheritInfo;
    
        for (int x = 0; x < numBuffers; x++)
        {
            vkBeginCommandBuffer(info.cmd2s[x], &secondaryCommandBufferInfo);
            vkCmdBindPipeline(info.cmd2s[x], VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline);
            vkCmdBindDescriptorSets(info.cmd2s[x], VK_PIPELINE_BIND_POINT_GRAPHICS,
                                    info.pipeline_layout, 0, NUM_DESCRIPTOR_SETS, info.desc_set.data(),
                                    0, NULL);
            const VkDeviceSize offsets[1] = {0};
            vkCmdBindVertexBuffers(info.cmd2s[x], 0, 1, &info.vertex_buffer.buf, offsets);
            init_viewports2_array(info, x);
            init_scissors2_array(info, x);
            vkCmdDraw(info.cmd2s[x], 0, 1, 0, 0);
            vkEndCommandBuffer(info.cmd2s[x]);
        }
        // Record Secondary Command Buffer End
    
        // Record Primary Command Buffer Begin
        VkRenderPassBeginInfo rpBegin;
        rpBegin.sType                    = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        rpBegin.pNext                    = NULL;
        rpBegin.renderPass               = info.render_pass;
        rpBegin.framebuffer              = info.framebuffers[info.current_buffer];
        rpBegin.renderArea.offset.x      = 0;
        rpBegin.renderArea.offset.y      = 0;
        rpBegin.renderArea.extent.width  = info.width;
        rpBegin.renderArea.extent.height = info.height;
        rpBegin.clearValueCount          = 2;
        rpBegin.pClearValues             = clear_values;
    
        VkCommandBufferBeginInfo primaryCommandBufferInfo = {};
        primaryCommandBufferInfo.sType                    = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
        primaryCommandBufferInfo.pNext                    = NULL;
        primaryCommandBufferInfo.flags                    = 0;
        primaryCommandBufferInfo.pInheritanceInfo         = NULL;
    
        vkBeginCommandBuffer(info.cmd, &primaryCommandBufferInfo);
        for (int x = 0; x < numBuffers; x++)
        {
            vkCmdBeginRenderPass(info.cmd, &rpBegin, VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS);
            vkCmdExecuteCommands(info.cmd, 1, &info.cmd2s[x]);
            vkCmdEndRenderPass(info.cmd);
        }
        vkEndCommandBuffer(info.cmd);
        // Record Primary Command Buffer End
    
        const VkCommandBuffer cmd_bufs[]      = {info.cmd};
        VkPipelineStageFlags pipe_stage_flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        VkSubmitInfo submitInfo[1]            = {};
        submitInfo[0].pNext                   = NULL;
        submitInfo[0].sType                   = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submitInfo[0].waitSemaphoreCount      = 1;
        submitInfo[0].pWaitSemaphores         = &imageAcquiredSemaphore;
        submitInfo[0].pWaitDstStageMask       = &pipe_stage_flags;
        submitInfo[0].commandBufferCount      = 1;
        submitInfo[0].pCommandBuffers         = cmd_bufs;
        submitInfo[0].signalSemaphoreCount    = 0;
        submitInfo[0].pSignalSemaphores       = NULL;
    
        // Queue the command buffer for execution
        res = vkQueueSubmit(info.graphics_queue, 1, submitInfo, drawFence);
        ASSERT_EQ(VK_SUCCESS, res);
    
        // Now present the image in the window
    
        VkPresentInfoKHR present;
        present.sType              = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        present.pNext              = NULL;
        present.swapchainCount     = 1;
        present.pSwapchains        = &info.swap_chain;
        present.pImageIndices      = &info.current_buffer;
        present.pWaitSemaphores    = NULL;
        present.waitSemaphoreCount = 0;
        present.pResults           = NULL;
    
        // Make sure command buffer is finished before presenting
        do
        {
            res = vkWaitForFences(info.device, 1, &drawFence, VK_TRUE, FENCE_TIMEOUT);
        } while (res == VK_TIMEOUT);
        vkResetFences(info.device, 1, &drawFence);
    
        ASSERT_EQ(VK_SUCCESS, res);
        res = vkQueuePresentKHR(info.present_queue, &present);
        ASSERT_EQ(VK_SUCCESS, res);
    }
    
    CommandBufferTestParams PrimaryCBHundredIndividualParams()
    {
        CommandBufferTestParams params;
        params.CBImplementation = PrimaryCommandBufferBenchmarkHundredIndividual;
        params.suffix           = "_PrimaryCB_Submit_100_With_1_Draw";
        return params;
    }
    
    CommandBufferTestParams PrimaryCBOneWithOneHundredParams()
    {
        CommandBufferTestParams params;
        params.CBImplementation = PrimaryCommandBufferBenchmarkOneWithOneHundred;
        params.suffix           = "_PrimaryCB_Submit_1_With_100_Draw";
        return params;
    }
    
    CommandBufferTestParams SecondaryCBParams()
    {
        CommandBufferTestParams params;
        params.CBImplementation = SecondaryCommandBufferBenchmark;
        params.suffix           = "_SecondaryCB_Submit_1_With_100_Draw_In_Individual_Secondary";
        return params;
    }
    
    TEST_P(VulkanCommandBufferPerfTest, Run)
    {
        run();
    }
    
    INSTANTIATE_TEST_CASE_P(,
                            VulkanCommandBufferPerfTest,
                            ::testing::Values(PrimaryCBHundredIndividualParams(),
                                              PrimaryCBOneWithOneHundredParams(),
                                              SecondaryCBParams()));