Edit

kc3-lang/angle/src/tests/gl_tests/VulkanUniformUpdatesTest.cpp

Branch :

  • Show log

    Commit

  • Author : Charlie Lao
    Date : 2020-07-15 17:10:35
    Hash : 8765b46a
    Message : Vulkan: Make mDefaultUniformStorage per ContextVk Right now the dynamic buffer for default uniform is per program. Most of time the buffer is unused. That is a huge waste of memory (and these memory are wired memory). This CL moves the mDefaultUniformStorage from per ProgramVk to ContextVk so that we all share with each other. Bug: b/161391337 Change-Id: I1fe8523b2b2dbc39bec3509a3432e38e34bd5713 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2274870 Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Tim Van Patten <timvp@google.com> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Courtney Goeltzenleuchter <courtneygo@google.com> Commit-Queue: Charlie Lao <cclao@google.com>

  • src/tests/gl_tests/VulkanUniformUpdatesTest.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.
    //
    // VulkanUniformUpdatesTest:
    //   Tests to validate our Vulkan dynamic uniform updates are working as expected.
    //
    
    #include "test_utils/ANGLETest.h"
    #include "test_utils/angle_test_instantiate.h"
    // 'None' is defined as 'struct None {};' in
    // third_party/googletest/src/googletest/include/gtest/internal/gtest-type-util.h.
    // But 'None' is also defined as a numeric constant 0L in <X11/X.h>.
    // So we need to include ANGLETest.h first to avoid this conflict.
    
    #include "libANGLE/Context.h"
    #include "libANGLE/angletypes.h"
    #include "libANGLE/renderer/vulkan/ContextVk.h"
    #include "libANGLE/renderer/vulkan/ProgramVk.h"
    #include "libANGLE/renderer/vulkan/TextureVk.h"
    #include "test_utils/gl_raii.h"
    #include "util/EGLWindow.h"
    #include "util/shader_utils.h"
    
    using namespace angle;
    
    namespace
    {
    
    class VulkanUniformUpdatesTest : public ANGLETest
    {
      protected:
        VulkanUniformUpdatesTest() : mLastContext(nullptr) {}
    
        virtual void testSetUp() override
        {
            // Some of the tests bellow forces uniform buffer size to 128 bytes which may affect other
            // tests. This is to ensure that the assumption that each TEST_P will recreate context.
            ASSERT(mLastContext != getEGLWindow()->getContext());
            mLastContext = getEGLWindow()->getContext();
        }
    
        rx::ContextVk *hackANGLE() const
        {
            // Hack the angle!
            const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
            return rx::GetImplAs<rx::ContextVk>(context);
        }
    
        rx::ProgramVk *hackProgram(GLuint handle) const
        {
            // Hack the angle!
            const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
            const gl::Program *program = context->getProgramResolveLink({handle});
            return rx::vk::GetImpl(program);
        }
    
        rx::TextureVk *hackTexture(GLuint handle) const
        {
            // Hack the angle!
            const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
            const gl::Texture *texture = context->getTexture({handle});
            return rx::vk::GetImpl(texture);
        }
    
        static constexpr uint32_t kMaxSetsForTesting = 32;
    
        void limitMaxSets(GLuint program)
        {
            rx::ContextVk *contextVk = hackANGLE();
            rx::ProgramVk *programVk = hackProgram(program);
    
            // Force a small limit on the max sets per pool to more easily trigger a new allocation.
            rx::vk::DynamicDescriptorPool *uniformPool =
                programVk->getDynamicDescriptorPool(rx::kUniformsAndXfbDescriptorSetIndex);
            uniformPool->setMaxSetsPerPoolForTesting(kMaxSetsForTesting);
            VkDescriptorPoolSize uniformSetSize = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC,
                                                   rx::kReservedDefaultUniformBindingCount};
            (void)uniformPool->init(contextVk, &uniformSetSize, 1);
    
            uint32_t textureCount =
                static_cast<uint32_t>(programVk->getState().getSamplerBindings().size());
    
            // To support the bindEmptyForUnusedDescriptorSets workaround.
            textureCount = std::max(textureCount, 1u);
    
            rx::vk::DynamicDescriptorPool *texturePool =
                programVk->getDynamicDescriptorPool(rx::kTextureDescriptorSetIndex);
            texturePool->setMaxSetsPerPoolForTesting(kMaxSetsForTesting);
            VkDescriptorPoolSize textureSetSize = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
                                                   textureCount};
            (void)texturePool->init(contextVk, &textureSetSize, 1);
        }
    
        static constexpr size_t kTextureStagingBufferSizeForTesting = 128;
    
        void limitTextureStagingBufferSize(GLuint texture)
        {
            rx::TextureVk *textureVk = hackTexture(texture);
            textureVk->overrideStagingBufferSizeForTesting(kTextureStagingBufferSizeForTesting);
        }
    
      private:
        EGLContext mLastContext;
    };
    
    // This test updates a uniform until a new buffer is allocated and then make sure the uniform
    // updates still work.
    TEST_P(VulkanUniformUpdatesTest, UpdateUntilNewBufferIsAllocated)
    {
        ASSERT_TRUE(IsVulkan());
    
        constexpr char kPositionUniformVertexShader[] = R"(attribute vec2 position;
    uniform vec2 uniPosModifier;
    void main()
    {
        gl_Position = vec4(position + uniPosModifier, 0, 1);
    })";
    
        constexpr char kColorUniformFragmentShader[] = R"(precision mediump float;
    uniform vec4 uniColor;
    void main()
    {
        gl_FragColor = uniColor;
    })";
    
        ANGLE_GL_PROGRAM(program, kPositionUniformVertexShader, kColorUniformFragmentShader);
        glUseProgram(program);
    
        limitMaxSets(program);
    
        // Set a really small min size so that uniform updates often allocates a new buffer.
        rx::ContextVk *contextVk = hackANGLE();
        contextVk->setDefaultUniformBlocksMinSizeForTesting(128);
    
        GLint posUniformLocation = glGetUniformLocation(program, "uniPosModifier");
        ASSERT_NE(posUniformLocation, -1);
        GLint colorUniformLocation = glGetUniformLocation(program, "uniColor");
        ASSERT_NE(colorUniformLocation, -1);
    
        // Sets both uniforms 10 times, it should certainly trigger new buffers creations by the
        // underlying StreamingBuffer.
        for (int i = 0; i < 100; i++)
        {
            glUniform2f(posUniformLocation, -0.5, 0.0);
            glUniform4f(colorUniformLocation, 1.0, 0.0, 0.0, 1.0);
            drawQuad(program, "position", 0.5f, 1.0f);
            swapBuffers();
            ASSERT_GL_NO_ERROR();
        }
    }
    
    void InitTexture(GLColor color, GLTexture *texture)
    {
        const std::vector<GLColor> colors(4, color);
        glBindTexture(GL_TEXTURE_2D, *texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, colors.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    }
    
    // Force uniform updates until the dynamic descriptor pool wraps into a new pool allocation.
    TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUpdates)
    {
        ASSERT_TRUE(IsVulkan());
    
        // Initialize texture program.
        GLuint program = get2DTexturedQuadProgram();
        ASSERT_NE(0u, program);
        glUseProgram(program);
    
        // Force a small limit on the max sets per pool to more easily trigger a new allocation.
        limitMaxSets(program);
    
        GLint texLoc = glGetUniformLocation(program, "tex");
        ASSERT_NE(-1, texLoc);
    
        // Initialize basic red texture.
        const std::vector<GLColor> redColors(4, GLColor::red);
        GLTexture texture;
        glBindTexture(GL_TEXTURE_2D, texture);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, redColors.data());
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        ASSERT_GL_NO_ERROR();
    
        // Draw multiple times, each iteration will create a new descriptor set.
        for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 8; ++iteration)
        {
            glUniform1i(texLoc, 0);
            drawQuad(program, "position", 0.5f, 1.0f, true);
            swapBuffers();
            ASSERT_GL_NO_ERROR();
        }
    }
    
    // Uniform updates along with Texture updates.
    TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUniformAndTextureUpdates)
    {
        ASSERT_TRUE(IsVulkan());
    
        // Initialize texture program.
        constexpr char kFS[] = R"(varying mediump vec2 v_texCoord;
    uniform sampler2D tex;
    uniform mediump vec4 colorMask;
    void main()
    {
        gl_FragColor = texture2D(tex, v_texCoord) * colorMask;
    })";
    
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), kFS);
        glUseProgram(program);
    
        limitMaxSets(program);
    
        // Get uniform locations.
        GLint texLoc = glGetUniformLocation(program, "tex");
        ASSERT_NE(-1, texLoc);
    
        GLint colorMaskLoc = glGetUniformLocation(program, "colorMask");
        ASSERT_NE(-1, colorMaskLoc);
    
        // Initialize white texture.
        GLTexture whiteTexture;
        InitTexture(GLColor::white, &whiteTexture);
        ASSERT_GL_NO_ERROR();
    
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, whiteTexture);
    
        // Initialize magenta texture.
        GLTexture magentaTexture;
        InitTexture(GLColor::magenta, &magentaTexture);
        ASSERT_GL_NO_ERROR();
    
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, magentaTexture);
    
        // Draw multiple times, each iteration will create a new descriptor set.
        for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
        {
            // Draw with white.
            glUniform1i(texLoc, 0);
            glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
            // Draw with white masking out red.
            glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
            // Draw with magenta.
            glUniform1i(texLoc, 1);
            glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
            // Draw with magenta masking out red.
            glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
            swapBuffers();
            ASSERT_GL_NO_ERROR();
        }
    }
    
    // Uniform updates along with Texture regeneration.
    TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUniformAndTextureRegeneration)
    {
        ASSERT_TRUE(IsVulkan());
    
        // Initialize texture program.
        constexpr char kFS[] = R"(varying mediump vec2 v_texCoord;
    uniform sampler2D tex;
    uniform mediump vec4 colorMask;
    void main()
    {
        gl_FragColor = texture2D(tex, v_texCoord) * colorMask;
    })";
    
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), kFS);
        glUseProgram(program);
    
        limitMaxSets(program);
    
        // Initialize large arrays of textures.
        std::vector<GLTexture> whiteTextures;
        std::vector<GLTexture> magentaTextures;
    
        for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
        {
            // Initialize white texture.
            GLTexture whiteTexture;
            InitTexture(GLColor::white, &whiteTexture);
            ASSERT_GL_NO_ERROR();
            whiteTextures.emplace_back(std::move(whiteTexture));
    
            // Initialize magenta texture.
            GLTexture magentaTexture;
            InitTexture(GLColor::magenta, &magentaTexture);
            ASSERT_GL_NO_ERROR();
            magentaTextures.emplace_back(std::move(magentaTexture));
        }
    
        // Get uniform locations.
        GLint texLoc = glGetUniformLocation(program, "tex");
        ASSERT_NE(-1, texLoc);
    
        GLint colorMaskLoc = glGetUniformLocation(program, "colorMask");
        ASSERT_NE(-1, colorMaskLoc);
    
        // Draw multiple times, each iteration will create a new descriptor set.
        for (int outerIteration = 0; outerIteration < 2; ++outerIteration)
        {
            for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
            {
                glActiveTexture(GL_TEXTURE0);
                glBindTexture(GL_TEXTURE_2D, whiteTextures[iteration]);
    
                glActiveTexture(GL_TEXTURE1);
                glBindTexture(GL_TEXTURE_2D, magentaTextures[iteration]);
    
                // Draw with white.
                glUniform1i(texLoc, 0);
                glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
                drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
                // Draw with white masking out red.
                glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
                drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
                // Draw with magenta.
                glUniform1i(texLoc, 1);
                glUniform4f(colorMaskLoc, 1.0f, 1.0f, 1.0f, 1.0f);
                drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
                // Draw with magenta masking out red.
                glUniform4f(colorMaskLoc, 0.0f, 1.0f, 1.0f, 1.0f);
                drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    
                swapBuffers();
                ASSERT_GL_NO_ERROR();
            }
        }
    }
    
    // Uniform updates along with Texture updates.
    TEST_P(VulkanUniformUpdatesTest, DescriptorPoolUniformAndTextureUpdatesTwoShaders)
    {
        ASSERT_TRUE(IsVulkan());
    
        // Initialize program.
        constexpr char kVS[] = R"(attribute vec2 position;
    varying mediump vec2 texCoord;
    void main()
    {
        gl_Position = vec4(position, 0, 1);
        texCoord = position * 0.5 + vec2(0.5);
    })";
    
        constexpr char kFS[] = R"(varying mediump vec2 texCoord;
    uniform mediump vec4 colorMask;
    void main()
    {
        gl_FragColor = colorMask;
    })";
    
        ANGLE_GL_PROGRAM(program1, kVS, kFS);
        ANGLE_GL_PROGRAM(program2, kVS, kFS);
        glUseProgram(program1);
    
        // Force a small limit on the max sets per pool to more easily trigger a new allocation.
        limitMaxSets(program1);
        limitMaxSets(program2);
    
        // Set a really small min size so that uniform updates often allocates a new buffer.
        rx::ContextVk *contextVk = hackANGLE();
        contextVk->setDefaultUniformBlocksMinSizeForTesting(128);
    
        // Get uniform locations.
        GLint colorMaskLoc1 = glGetUniformLocation(program1, "colorMask");
        ASSERT_NE(-1, colorMaskLoc1);
        GLint colorMaskLoc2 = glGetUniformLocation(program2, "colorMask");
        ASSERT_NE(-1, colorMaskLoc2);
    
        // Draw with white using program1.
        glUniform4f(colorMaskLoc1, 1.0f, 1.0f, 1.0f, 1.0f);
        drawQuad(program1, "position", 0.5f, 1.0f, true);
        swapBuffers();
        ASSERT_GL_NO_ERROR();
    
        // Now switch to use program2
        glUseProgram(program2);
        // Draw multiple times w/ program2, each iteration will create a new descriptor set.
        // This will cause the first descriptor pool to be cleaned up
        for (uint32_t iteration = 0; iteration < kMaxSetsForTesting * 2; ++iteration)
        {
            // Draw with white.
            glUniform4f(colorMaskLoc2, 1.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program2, "position", 0.5f, 1.0f, true);
    
            // Draw with white masking out red.
            glUniform4f(colorMaskLoc2, 0.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program2, "position", 0.5f, 1.0f, true);
    
            // Draw with magenta.
            glUniform4f(colorMaskLoc2, 1.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program2, "position", 0.5f, 1.0f, true);
    
            // Draw with magenta masking out red.
            glUniform4f(colorMaskLoc2, 0.0f, 1.0f, 1.0f, 1.0f);
            drawQuad(program2, "position", 0.5f, 1.0f, true);
    
            swapBuffers();
            ASSERT_GL_NO_ERROR();
        }
        // Finally, attempt to draw again with program1, with original uniform values.
        glUseProgram(program1);
        drawQuad(program1, "position", 0.5f, 1.0f, true);
        swapBuffers();
        ASSERT_GL_NO_ERROR();
    }
    
    // Verify that overflowing a Texture's staging buffer doesn't overwrite current data.
    TEST_P(VulkanUniformUpdatesTest, TextureStagingBufferRecycling)
    {
        ASSERT_TRUE(IsVulkan());
    
        // Fails on older MESA drivers.  http://crbug.com/979349
        ANGLE_SKIP_TEST_IF(IsAMD() && IsLinux());
    
        GLTexture tex;
        glBindTexture(GL_TEXTURE_2D, tex);
        limitTextureStagingBufferSize(tex);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 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);
    
        const GLColor kColors[4] = {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow};
    
        // Repeatedly update the staging buffer to trigger multiple recyclings.
        const GLsizei kHalfX      = getWindowWidth() / 2;
        const GLsizei kHalfY      = getWindowHeight() / 2;
        constexpr int kIterations = 4;
        for (int x = 0; x < 2; ++x)
        {
            for (int y = 0; y < 2; ++y)
            {
                const int kColorIndex = x + y * 2;
                const GLColor kColor  = kColors[kColorIndex];
    
                for (int iteration = 0; iteration < kIterations; ++iteration)
                {
                    for (int subX = 0; subX < kHalfX; ++subX)
                    {
                        for (int subY = 0; subY < kHalfY; ++subY)
                        {
                            const GLsizei xoffset = x * kHalfX + subX;
                            const GLsizei yoffset = y * kHalfY + subY;
    
                            // Update a single pixel.
                            glTexSubImage2D(GL_TEXTURE_2D, 0, xoffset, yoffset, 1, 1, GL_RGBA,
                                            GL_UNSIGNED_BYTE, kColor.data());
                        }
                    }
                }
            }
        }
    
        draw2DTexturedQuad(0.5f, 1.0f, true);
        ASSERT_GL_NO_ERROR();
    
        // Verify pixels.
        for (int x = 0; x < 2; ++x)
        {
            for (int y = 0; y < 2; ++y)
            {
                const GLsizei xoffset = x * kHalfX;
                const GLsizei yoffset = y * kHalfY;
                const int kColorIndex = x + y * 2;
                const GLColor kColor  = kColors[kColorIndex];
                EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, kColor);
            }
        }
    }
    
    // This test tries to create a situation that VS and FS's uniform data might get placed in
    // different buffers and verify uniforms not getting stale data.
    TEST_P(VulkanUniformUpdatesTest, UpdateAfterNewBufferIsAllocated)
    {
        ASSERT_TRUE(IsVulkan());
    
        constexpr char kPositionUniformVertexShader[] = R"(attribute vec2 position;
    uniform float uniformVS;
    varying vec4 outVS;
    void main()
    {
        outVS = vec4(uniformVS, uniformVS, uniformVS, uniformVS);
        gl_Position = vec4(position, 0, 1);
    })";
    
        constexpr char kColorUniformFragmentShader[] = R"(precision mediump float;
    varying vec4 outVS;
    uniform float uniformFS;
    void main()
    {
        if(outVS[0] > uniformFS)
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        else
            gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    })";
    
        ANGLE_GL_PROGRAM(program, kPositionUniformVertexShader, kColorUniformFragmentShader);
        glUseProgram(program);
    
        limitMaxSets(program);
    
        // Set a really small min size so that every uniform update actually allocates a new buffer.
        rx::ContextVk *contextVk = hackANGLE();
        contextVk->setDefaultUniformBlocksMinSizeForTesting(128);
    
        GLint uniformVSLocation = glGetUniformLocation(program, "uniformVS");
        ASSERT_NE(uniformVSLocation, -1);
        GLint uniformFSLocation = glGetUniformLocation(program, "uniformFS");
        ASSERT_NE(uniformFSLocation, -1);
    
        glUniform1f(uniformVSLocation, 10.0);
        glUniform1f(uniformFSLocation, 11.0);
        drawQuad(program, "position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
    
        const GLsizei kHalfX  = getWindowWidth() / 2;
        const GLsizei kHalfY  = getWindowHeight() / 2;
        const GLsizei xoffset = kHalfX;
        const GLsizei yoffset = kHalfY;
        // 10.0f < 11.0f, should see green
        EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, GLColor::green);
    
        // Now only update FS's uniform
        for (int i = 0; i < 3; i++)
        {
            glUniform1f(uniformFSLocation, 1.0f + i / 10.0f);
            drawQuad(program, "position", 0.5f, 1.0f);
            ASSERT_GL_NO_ERROR();
        }
        // 10.0f > 9.0f, should see red
        EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, GLColor::red);
    
        // 10.0f < 11.0f, should see green again
        glUniform1f(uniformFSLocation, 11.0f);
        drawQuad(program, "position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, GLColor::green);
    
        // Now only update VS's uniform and flush the draw and readback and verify for every iteration.
        // This will ensure the old buffers are finished and possibly recycled.
        for (int i = 0; i < 100; i++)
        {
            // Make VS uniform value ping pong across FS uniform value
            float vsUniformValue  = (i % 2) == 0 ? (11.0 + (i - 50)) : (11.0 - (i - 50));
            GLColor expectedColor = vsUniformValue > 11.0f ? GLColor::red : GLColor::green;
            glUniform1f(uniformVSLocation, vsUniformValue);
            drawQuad(program, "position", 0.5f, 1.0f);
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, expectedColor);
        }
    }
    
    ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN(), ES3_VULKAN());
    
    // This test tries to test uniform data update while switching between PPO and monolithic program.
    // The uniform data update occurred on one should carry over to the other. Also buffers are hacked
    // to smallest size to force updates occur in the new buffer so that any bug related to buffer
    // recycling will be exposed.
    class PipelineProgramUniformUpdatesTest : public VulkanUniformUpdatesTest
    {};
    TEST_P(PipelineProgramUniformUpdatesTest, ToggleBetweenPPOAndProgramVKWithUniformUpdate)
    {
        ASSERT_TRUE(IsVulkan());
    
        const GLchar *kPositionUniformVertexShader = R"(attribute vec2 position;
    uniform float uniformVS;
    varying vec4 outVS;
    void main()
    {
        outVS = vec4(uniformVS, uniformVS, uniformVS, uniformVS);
        gl_Position = vec4(position, 0, 1);
    })";
    
        const GLchar *kColorUniformFragmentShader = R"(precision mediump float;
    varying vec4 outVS;
    uniform float uniformFS;
    void main()
    {
        if(outVS[0] > uniformFS)
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        else
            gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    })";
    
        // Compile and link a separable vertex shader
        GLShader vertShader(GL_VERTEX_SHADER);
        glShaderSource(vertShader, 1, &kPositionUniformVertexShader, nullptr);
        glCompileShader(vertShader);
        GLShader fragShader(GL_FRAGMENT_SHADER);
        glShaderSource(fragShader, 1, &kColorUniformFragmentShader, nullptr);
        glCompileShader(fragShader);
        GLuint program = glCreateProgram();
        glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
        glAttachShader(program, vertShader);
        glAttachShader(program, fragShader);
        glLinkProgram(program);
        EXPECT_GL_NO_ERROR();
    
        glUseProgram(program);
        limitMaxSets(program);
        // Set a really small min size so that every uniform update actually allocates a new buffer.
        rx::ContextVk *contextVk = hackANGLE();
        contextVk->setDefaultUniformBlocksMinSizeForTesting(128);
    
        // Setup vertices
        std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
        GLint positionLocation              = glGetAttribLocation(program, "position");
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
        glEnableVertexAttribArray(positionLocation);
    
        GLint uniformVSLocation = glGetUniformLocation(program, "uniformVS");
        ASSERT_NE(uniformVSLocation, -1);
        GLint uniformFSLocation = glGetUniformLocation(program, "uniformFS");
        ASSERT_NE(uniformFSLocation, -1);
    
        glUseProgram(0);
    
        // Generate a pipeline program out of the monolithic program
        GLuint pipeline;
        glGenProgramPipelines(1, &pipeline);
        EXPECT_GL_NO_ERROR();
        glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, program);
        EXPECT_GL_NO_ERROR();
        glBindProgramPipeline(pipeline);
        EXPECT_GL_NO_ERROR();
    
        // First use monolithic program and update uniforms
        glUseProgram(program);
        glUniform1f(uniformVSLocation, 10.0);
        glUniform1f(uniformFSLocation, 11.0);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        ASSERT_GL_NO_ERROR();
        const GLsizei kHalfX  = getWindowWidth() / 2;
        const GLsizei kHalfY  = getWindowHeight() / 2;
        const GLsizei xoffset = kHalfX;
        const GLsizei yoffset = kHalfY;
        // 10.0f < 11.0f, should see green
        EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, GLColor::green);
    
        // Now use PPO and only update FS's uniform
        glUseProgram(0);
        for (int i = 0; i < 3; i++)
        {
            glActiveShaderProgram(pipeline, program);
            glUniform1f(uniformFSLocation, 1.0f + i / 10.0f);
            glDrawArrays(GL_TRIANGLES, 0, 6);
            ASSERT_GL_NO_ERROR();
        }
        // 10.0f > 9.0f, should see red
        EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, GLColor::red);
    
        // Now switch back to monolithic program and only update FS's uniform.
        // 10.0f < 11.0f, should see green again
        glUseProgram(program);
        glUniform1f(uniformFSLocation, 11.0f);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, GLColor::green);
    
        // Now only update VS's uniform and flush the draw and readback and verify for every iteration.
        // This will ensure the old buffers are finished and possibly recycled.
        for (int i = 0; i < 100; i++)
        {
            bool iteration_even = (i % 2) == 0 ? true : false;
            float vsUniformValue;
    
            // Make VS uniform value ping pong across FS uniform value and also pin pong between
            // monolithic program and PPO
            if (iteration_even)
            {
                vsUniformValue = 11.0 + (i - 50);
                glUseProgram(program);
                glUniform1f(uniformVSLocation, vsUniformValue);
            }
            else
            {
                vsUniformValue = 11.0 - (i - 50);
                glUseProgram(0);
                glActiveShaderProgram(pipeline, program);
                glUniform1f(uniformVSLocation, vsUniformValue);
            }
    
            GLColor expectedColor = vsUniformValue > 11.0f ? GLColor::red : GLColor::green;
            glDrawArrays(GL_TRIANGLES, 0, 6);
            ASSERT_GL_NO_ERROR();
            EXPECT_PIXEL_RECT_EQ(xoffset, yoffset, kHalfX, kHalfY, expectedColor);
        }
    }
    
    ANGLE_INSTANTIATE_TEST(PipelineProgramUniformUpdatesTest, ES31_VULKAN());
    
    }  // anonymous namespace