| // |
| // 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: |
| 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); |
| } |
| }; |
| |
| // 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); |
| |
| rx::ProgramVk *programVk = hackProgram(program); |
| |
| // Set a really small min size so that uniform updates often allocates a new buffer. |
| programVk->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); |
| |
| rx::ProgramVk *program1Vk = hackProgram(program1); |
| rx::ProgramVk *program2Vk = hackProgram(program2); |
| |
| // Set a really small min size so that uniform updates often allocates a new buffer. |
| program1Vk->setDefaultUniformBlocksMinSizeForTesting(128); |
| program2Vk->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); |
| } |
| } |
| } |
| |
| ANGLE_INSTANTIATE_TEST(VulkanUniformUpdatesTest, ES2_VULKAN(), ES3_VULKAN()); |
| |
| } // anonymous namespace |