blob: a43c69baf3295a3fce6a3574cd2d2533cbf75529 [file] [log] [blame]
//
// 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();
mMaxSetsPerPool = rx::vk::DynamicDescriptorPool::GetMaxSetsPerPoolForTesting();
mMaxSetsPerPoolMultiplier =
rx::vk::DynamicDescriptorPool::GetMaxSetsPerPoolMultiplierForTesting();
}
void testTearDown() override
{
rx::vk::DynamicDescriptorPool::SetMaxSetsPerPoolForTesting(mMaxSetsPerPool);
rx::vk::DynamicDescriptorPool::SetMaxSetsPerPoolMultiplierForTesting(
mMaxSetsPerPoolMultiplier);
}
rx::ContextVk *hackANGLE() const
{
// Hack the angle!
const gl::Context *context = static_cast<gl::Context *>(getEGLWindow()->getContext());
return rx::GetImplAs<rx::ContextVk>(context);
}
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 = 1;
static constexpr uint32_t kMaxSetsMultiplierForTesting = 1;
void limitMaxSets()
{
rx::vk::DynamicDescriptorPool::SetMaxSetsPerPoolForTesting(kMaxSetsForTesting);
rx::vk::DynamicDescriptorPool::SetMaxSetsPerPoolMultiplierForTesting(
kMaxSetsMultiplierForTesting);
}
static constexpr size_t kTextureStagingBufferSizeForTesting = 128;
void limitTextureStagingBufferSize(GLuint texture)
{
rx::TextureVk *textureVk = hackTexture(texture);
textureVk->overrideStagingBufferSizeForTesting(kTextureStagingBufferSizeForTesting);
}
private:
EGLContext mLastContext;
uint32_t mMaxSetsPerPool;
uint32_t mMaxSetsPerPoolMultiplier;
};
// 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();
// 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();
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();
// 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();
// 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();
limitMaxSets();
// 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();
// 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();
// 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