| // |
| // Copyright 2017 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. |
| // |
| // ProgramPipelineTest: |
| // Various tests related to Program Pipeline. |
| // |
| |
| #include "test_utils/ANGLETest.h" |
| #include "test_utils/gl_raii.h" |
| |
| using namespace angle; |
| |
| namespace |
| { |
| |
| class ProgramPipelineTest : public ANGLETest<> |
| { |
| protected: |
| ProgramPipelineTest() |
| { |
| setWindowWidth(64); |
| setWindowHeight(64); |
| setConfigRedBits(8); |
| setConfigGreenBits(8); |
| setConfigBlueBits(8); |
| setConfigAlphaBits(8); |
| } |
| }; |
| |
| // Verify that program pipeline is not supported in version lower than ES31. |
| TEST_P(ProgramPipelineTest, GenerateProgramPipelineObject) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| GLuint pipeline; |
| glGenProgramPipelines(1, &pipeline); |
| if (getClientMajorVersion() < 3 || getClientMinorVersion() < 1) |
| { |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| else |
| { |
| EXPECT_GL_NO_ERROR(); |
| |
| glDeleteProgramPipelines(1, &pipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| } |
| |
| // Verify that program pipeline errors out without GL_EXT_separate_shader_objects extension. |
| TEST_P(ProgramPipelineTest, GenerateProgramPipelineObjectEXT) |
| { |
| GLuint pipeline; |
| glGenProgramPipelinesEXT(1, &pipeline); |
| if (!IsGLExtensionEnabled("GL_EXT_separate_shader_objects")) |
| { |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| else |
| { |
| EXPECT_GL_NO_ERROR(); |
| |
| glDeleteProgramPipelinesEXT(1, &pipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| } |
| |
| class ProgramPipelineTest31 : public ProgramPipelineTest |
| { |
| protected: |
| void testTearDown() override |
| { |
| glDeleteProgram(mVertProg); |
| glDeleteProgram(mFragProg); |
| glDeleteProgramPipelines(1, &mPipeline); |
| } |
| |
| void bindProgramPipeline(const GLchar *vertString, const GLchar *fragString); |
| void drawQuadWithPPO(const std::string &positionAttribName, |
| const GLfloat positionAttribZ, |
| const GLfloat positionAttribXYScale); |
| GLint getAvailableProgramBinaryFormatCount() const; |
| |
| GLuint mVertProg = 0; |
| GLuint mFragProg = 0; |
| GLuint mPipeline = 0; |
| }; |
| |
| class ProgramPipelineXFBTest31 : public ProgramPipelineTest31 |
| { |
| protected: |
| void testSetUp() override |
| { |
| glGenBuffers(1, &mTransformFeedbackBuffer); |
| glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBuffer); |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, mTransformFeedbackBufferSize, nullptr, |
| GL_STATIC_DRAW); |
| |
| glGenTransformFeedbacks(1, &mTransformFeedback); |
| |
| ASSERT_GL_NO_ERROR(); |
| } |
| void testTearDown() override |
| { |
| if (mTransformFeedbackBuffer != 0) |
| { |
| glDeleteBuffers(1, &mTransformFeedbackBuffer); |
| mTransformFeedbackBuffer = 0; |
| } |
| |
| if (mTransformFeedback != 0) |
| { |
| glDeleteTransformFeedbacks(1, &mTransformFeedback); |
| mTransformFeedback = 0; |
| } |
| } |
| |
| void bindProgramPipelineWithXFBVaryings(const GLchar *vertString, |
| const GLchar *fragStringconst, |
| const std::vector<std::string> &tfVaryings, |
| GLenum bufferMode); |
| |
| static const size_t mTransformFeedbackBufferSize = 1 << 24; |
| GLuint mTransformFeedbackBuffer; |
| GLuint mTransformFeedback; |
| }; |
| |
| void ProgramPipelineTest31::bindProgramPipeline(const GLchar *vertString, const GLchar *fragString) |
| { |
| mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString); |
| ASSERT_NE(mFragProg, 0u); |
| |
| // Generate a program pipeline and attach the programs to their respective stages |
| glGenProgramPipelines(1, &mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| void ProgramPipelineXFBTest31::bindProgramPipelineWithXFBVaryings( |
| const GLchar *vertString, |
| const GLchar *fragString, |
| const std::vector<std::string> &tfVaryings, |
| GLenum bufferMode) |
| { |
| GLShader vertShader(GL_VERTEX_SHADER); |
| mVertProg = glCreateProgram(); |
| |
| glShaderSource(vertShader, 1, &vertString, nullptr); |
| glCompileShader(vertShader); |
| glProgramParameteri(mVertProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mVertProg, vertShader); |
| glLinkProgram(mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString); |
| ASSERT_NE(mFragProg, 0u); |
| |
| if (tfVaryings.size() > 0) |
| { |
| std::vector<const char *> constCharTFVaryings; |
| |
| for (const std::string &transformFeedbackVarying : tfVaryings) |
| { |
| constCharTFVaryings.push_back(transformFeedbackVarying.c_str()); |
| } |
| |
| glTransformFeedbackVaryings(mVertProg, static_cast<GLsizei>(tfVaryings.size()), |
| &constCharTFVaryings[0], bufferMode); |
| glLinkProgram(mVertProg); |
| } |
| // Generate a program pipeline and attach the programs to their respective stages |
| glGenProgramPipelines(1, &mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test generate or delete program pipeline. |
| TEST_P(ProgramPipelineTest31, GenOrDeleteProgramPipelineTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| GLuint pipeline; |
| glGenProgramPipelines(-1, &pipeline); |
| EXPECT_GL_ERROR(GL_INVALID_VALUE); |
| glGenProgramPipelines(0, &pipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDeleteProgramPipelines(-1, &pipeline); |
| EXPECT_GL_ERROR(GL_INVALID_VALUE); |
| glDeleteProgramPipelines(0, &pipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test BindProgramPipeline. |
| TEST_P(ProgramPipelineTest31, BindProgramPipelineTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| glBindProgramPipeline(0); |
| EXPECT_GL_NO_ERROR(); |
| |
| glBindProgramPipeline(2); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| GLuint pipeline; |
| glGenProgramPipelines(1, &pipeline); |
| glBindProgramPipeline(pipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDeleteProgramPipelines(1, &pipeline); |
| glBindProgramPipeline(pipeline); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Test IsProgramPipeline |
| TEST_P(ProgramPipelineTest31, IsProgramPipelineTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| EXPECT_GL_FALSE(glIsProgramPipeline(0)); |
| EXPECT_GL_NO_ERROR(); |
| |
| EXPECT_GL_FALSE(glIsProgramPipeline(2)); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLuint pipeline; |
| glGenProgramPipelines(1, &pipeline); |
| glBindProgramPipeline(pipeline); |
| EXPECT_GL_TRUE(glIsProgramPipeline(pipeline)); |
| EXPECT_GL_NO_ERROR(); |
| |
| glBindProgramPipeline(0); |
| glDeleteProgramPipelines(1, &pipeline); |
| EXPECT_GL_FALSE(glIsProgramPipeline(pipeline)); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Simulates a call to glCreateShaderProgramv() |
| GLuint createShaderProgram(GLenum type, |
| const GLchar *shaderString, |
| unsigned int varyingsCount, |
| const char *const *varyings) |
| { |
| GLShader shader(type); |
| if (!shader) |
| { |
| return 0; |
| } |
| |
| glShaderSource(shader, 1, &shaderString, nullptr); |
| EXPECT_GL_NO_ERROR(); |
| |
| glCompileShader(shader); |
| |
| GLint compiled; |
| glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); |
| |
| if (!compiled) |
| { |
| GLint infoLogLength; |
| glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); |
| std::vector<GLchar> infoLog(infoLogLength); |
| glGetShaderInfoLog(shader, infoLogLength, NULL, infoLog.data()); |
| INFO() << "Compilation failed:\n" |
| << (infoLogLength > 0 ? infoLog.data() : "") << "\n for shader:\n" |
| << shaderString << "\n"; |
| return 0; |
| } |
| |
| GLuint program = glCreateProgram(); |
| |
| if (program) |
| { |
| glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| if (compiled) |
| { |
| glAttachShader(program, shader); |
| EXPECT_GL_NO_ERROR(); |
| |
| if (varyingsCount > 0) |
| { |
| glTransformFeedbackVaryings(program, varyingsCount, varyings, GL_SEPARATE_ATTRIBS); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| glLinkProgram(program); |
| |
| GLint linked = 0; |
| glGetProgramiv(program, GL_LINK_STATUS, &linked); |
| |
| if (linked == 0) |
| { |
| glDeleteProgram(program); |
| return 0; |
| } |
| glDetachShader(program, shader); |
| } |
| } |
| |
| EXPECT_GL_NO_ERROR(); |
| |
| return program; |
| } |
| |
| GLuint createShaderProgram(GLenum type, const GLchar *shaderString) |
| { |
| return createShaderProgram(type, shaderString, 0, nullptr); |
| } |
| |
| void ProgramPipelineTest31::drawQuadWithPPO(const std::string &positionAttribName, |
| const GLfloat positionAttribZ, |
| const GLfloat positionAttribXYScale) |
| { |
| return drawQuadPPO(mVertProg, positionAttribName, positionAttribZ, positionAttribXYScale); |
| } |
| |
| GLint ProgramPipelineTest31::getAvailableProgramBinaryFormatCount() const |
| { |
| GLint formatCount = 0; |
| glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS_OES, &formatCount); |
| return formatCount; |
| } |
| |
| // Test glUseProgramStages |
| TEST_P(ProgramPipelineTest31, UseProgramStages) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = essl31_shaders::fs::Red(); |
| |
| mVertProg = createShaderProgram(GL_VERTEX_SHADER, vertString); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = createShaderProgram(GL_FRAGMENT_SHADER, fragString); |
| ASSERT_NE(mFragProg, 0u); |
| |
| // Generate a program pipeline and attach the programs to their respective stages |
| GLuint pipeline; |
| glGenProgramPipelines(1, &pipeline); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| glBindProgramPipeline(pipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| } |
| |
| // Test glUseProgramStages with different programs |
| TEST_P(ProgramPipelineTest31, UseProgramStagesWithDifferentPrograms) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString1 = R"(#version 310 es |
| precision highp float; |
| uniform float redColorIn; |
| uniform float greenColorIn; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, 0.0, 1.0); |
| })"; |
| const GLchar *fragString2 = R"(#version 310 es |
| precision highp float; |
| uniform float greenColorIn; |
| uniform float blueColorIn; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(0.0, greenColorIn, blueColorIn, 1.0); |
| })"; |
| |
| bindProgramPipeline(vertString, fragString1); |
| |
| // Set the output color to red |
| GLint location = glGetUniformLocation(mFragProg, "redColorIn"); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| glUniform1f(location, 1.0); |
| location = glGetUniformLocation(mFragProg, "greenColorIn"); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| glUniform1f(location, 0.0); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| GLuint fragProg; |
| fragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString2); |
| ASSERT_NE(fragProg, 0u); |
| EXPECT_GL_NO_ERROR(); |
| |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, fragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| // Set the output color to blue |
| location = glGetUniformLocation(fragProg, "greenColorIn"); |
| glActiveShaderProgram(mPipeline, fragProg); |
| glUniform1f(location, 0.0); |
| location = glGetUniformLocation(fragProg, "blueColorIn"); |
| glActiveShaderProgram(mPipeline, fragProg); |
| glUniform1f(location, 1.0); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue); |
| |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| glDeleteProgram(mVertProg); |
| glDeleteProgram(mFragProg); |
| glDeleteProgram(fragProg); |
| } |
| |
| // Test glUseProgramStages |
| TEST_P(ProgramPipelineTest31, UseCreateShaderProgramv) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = essl31_shaders::fs::Red(); |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| } |
| |
| // Test pipeline without vertex shader |
| TEST_P(ProgramPipelineTest31, PipelineWithoutVertexShader) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create a separable program object with a fragment shader |
| const GLchar *fragString = essl31_shaders::fs::Red(); |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString); |
| ASSERT_NE(mFragProg, 0u); |
| |
| // Generate a program pipeline and attach the program to it's respective stage |
| glGenProgramPipelines(1, &mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDrawArrays(GL_POINTS, 0, 3); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test pipeline without any shaders |
| TEST_P(ProgramPipelineTest31, PipelineWithoutShaders) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Generate a program pipeline |
| glGenProgramPipelines(1, &mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| glDrawArrays(GL_POINTS, 0, 3); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| // Ensure validation fails |
| GLint value; |
| glValidateProgramPipeline(mPipeline); |
| glGetProgramPipelineiv(mPipeline, GL_VALIDATE_STATUS, &value); |
| EXPECT_FALSE(value); |
| } |
| |
| // Test glUniform |
| TEST_P(ProgramPipelineTest31, FragmentStageUniformTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| uniform float redColorIn; |
| uniform float greenColorIn; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, 0.0, 1.0); |
| })"; |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| // Set the output color to yellow |
| GLint location = glGetUniformLocation(mFragProg, "redColorIn"); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| glUniform1f(location, 1.0); |
| location = glGetUniformLocation(mFragProg, "greenColorIn"); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| glUniform1f(location, 1.0); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow); |
| |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow); |
| |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| // Set the output color to red |
| location = glGetUniformLocation(mFragProg, "redColorIn"); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| glUniform1f(location, 1.0); |
| location = glGetUniformLocation(mFragProg, "greenColorIn"); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| glUniform1f(location, 0.0); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| glDeleteProgram(mVertProg); |
| glDeleteProgram(mFragProg); |
| } |
| |
| // Test glUniformBlockBinding and then glBufferData |
| TEST_P(ProgramPipelineTest31, FragmentStageUniformBlockBufferDataTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout (std140) uniform color_ubo |
| { |
| float redColorIn; |
| float greenColorIn; |
| }; |
| |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, 0.0, 1.0); |
| })"; |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| // Set the output color to yellow |
| glActiveShaderProgram(mPipeline, mFragProg); |
| GLint uboIndex = glGetUniformBlockIndex(mFragProg, "color_ubo"); |
| GLBuffer uboBuf; |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBuf); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatRed, GL_STATIC_DRAW); |
| glUniformBlockBinding(mFragProg, uboIndex, 0); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| // Clear and test again |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| // Set the output color to red |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatGreen, GL_STATIC_DRAW); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBuf); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); |
| |
| glDeleteProgram(mVertProg); |
| glDeleteProgram(mFragProg); |
| } |
| |
| // Test glUniformBlockBinding followed by glBindBufferRange |
| TEST_P(ProgramPipelineTest31, FragmentStageUniformBlockBindBufferRangeTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout (std140) uniform color_ubo |
| { |
| float redColorIn; |
| float greenColorIn; |
| float blueColorIn; |
| }; |
| |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, blueColorIn, 1.0); |
| })"; |
| |
| // Setup three uniform buffers, one with red, one with green, and one with blue |
| GLBuffer uboBufRed; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufRed); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatRed, GL_STATIC_DRAW); |
| GLBuffer uboBufGreen; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufGreen); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatGreen, GL_STATIC_DRAW); |
| GLBuffer uboBufBlue; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufBlue); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatBlue, GL_STATIC_DRAW); |
| |
| // Setup pipeline program using red uniform buffer |
| bindProgramPipeline(vertString, fragString); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| GLint uboIndex = glGetUniformBlockIndex(mFragProg, "color_ubo"); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBufRed); |
| glUniformBlockBinding(mFragProg, uboIndex, 0); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| // Clear and test again |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| // bind to green uniform buffer |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBufGreen); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| // bind to blue uniform buffer |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBufBlue); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue); |
| |
| glDeleteProgram(mVertProg); |
| glDeleteProgram(mFragProg); |
| } |
| |
| // Test that glUniformBlockBinding can successfully change the binding for PPOs |
| TEST_P(ProgramPipelineTest31, FragmentStageUniformBlockBinding) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout (std140) uniform color_ubo |
| { |
| float redColorIn; |
| float greenColorIn; |
| float blueColorIn; |
| }; |
| |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, blueColorIn, 1.0); |
| })"; |
| |
| // Setup three uniform buffers, one with red, one with green, and one with blue |
| GLBuffer uboBufRed; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufRed); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatRed, GL_STATIC_DRAW); |
| GLBuffer uboBufGreen; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufGreen); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatGreen, GL_STATIC_DRAW); |
| GLBuffer uboBufBlue; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufBlue); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatBlue, GL_STATIC_DRAW); |
| |
| // Setup pipeline program using red uniform buffer |
| bindProgramPipeline(vertString, fragString); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| GLint uboIndex = glGetUniformBlockIndex(mFragProg, "color_ubo"); |
| |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBufRed); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 1, uboBufGreen); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 2, uboBufBlue); |
| |
| // Bind the UBO to binding 0 and draw, should be red |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| glEnable(GL_SCISSOR_TEST); |
| glScissor(0, 0, w / 2, h / 2); |
| glUniformBlockBinding(mFragProg, uboIndex, 0); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| |
| // Bind it to binding 1 and draw, should be green |
| glScissor(w / 2, 0, w - w / 2, h / 2); |
| glUniformBlockBinding(mFragProg, uboIndex, 1); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| |
| // Bind it to binding 2 and draw, should be blue |
| glScissor(0, h / 2, w, h); |
| glUniformBlockBinding(mFragProg, uboIndex, 2); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 2, h / 2, GLColor::red); |
| EXPECT_PIXEL_RECT_EQ(w / 2, 0, w - w / 2, h / 2, GLColor::green); |
| EXPECT_PIXEL_RECT_EQ(0, h / 2, w, h - h / 2, GLColor::blue); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test that dirty bits related to UBOs propagates to the PPO. |
| TEST_P(ProgramPipelineTest31, UniformBufferUpdatesBeforeBindToPPO) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout (std140) uniform color_ubo |
| { |
| float redColorIn; |
| float greenColorIn; |
| float blueColorIn; |
| }; |
| |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, blueColorIn, 1.0); |
| })"; |
| |
| GLuint vs = CompileShader(GL_VERTEX_SHADER, vertString); |
| GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragString); |
| mVertProg = glCreateProgram(); |
| mFragProg = glCreateProgram(); |
| |
| // Compile and link a separable vertex shader |
| glProgramParameteri(mVertProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mVertProg, vs); |
| glLinkProgram(mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Compile and link a separable fragment shader |
| glProgramParameteri(mFragProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mFragProg, fs); |
| glLinkProgram(mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Generate a program pipeline and attach the programs |
| glGenProgramPipelines(1, &mPipeline); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| GLBuffer uboBufRed; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufRed); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatRed, GL_STATIC_DRAW); |
| GLBuffer uboBufGreen; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufGreen); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatGreen, GL_STATIC_DRAW); |
| GLBuffer uboBufBlue; |
| glBindBuffer(GL_UNIFORM_BUFFER, uboBufBlue); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatBlue, GL_STATIC_DRAW); |
| |
| glActiveShaderProgram(mPipeline, mFragProg); |
| GLint uboIndex = glGetUniformBlockIndex(mFragProg, "color_ubo"); |
| glUniformBlockBinding(mFragProg, uboIndex, 0); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, uboBufRed); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 1, uboBufGreen); |
| |
| // Draw once so all dirty bits are handled. Should be red |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| glEnable(GL_SCISSOR_TEST); |
| glScissor(0, 0, w / 2, h / 2); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| |
| // Unbind the fragment program from the PPO |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, 0); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Modify the UBO bindings, reattach the program and draw again. Should be green |
| glUniformBlockBinding(mFragProg, uboIndex, 1); |
| |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glScissor(w / 2, 0, w - w / 2, h / 2); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| |
| // Unbind the fragment program from the PPO again, and modify the UBO bindings differently. |
| // Draw again, which should be blue. |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, 0); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 1, uboBufBlue); |
| |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glScissor(0, h / 2, w, h); |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 2, h / 2, GLColor::red); |
| EXPECT_PIXEL_RECT_EQ(w / 2, 0, w - w / 2, h / 2, GLColor::green); |
| EXPECT_PIXEL_RECT_EQ(0, h / 2, w, h - h / 2, GLColor::blue); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test glBindBufferRange between draw calls in the presence of multiple UBOs between VS and FS |
| TEST_P(ProgramPipelineTest31, BindBufferRangeForMultipleUBOsInMultipleStages) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| layout (std140) uniform vsUBO1 |
| { |
| float redIn; |
| }; |
| |
| layout (std140) uniform vsUBO2 |
| { |
| float greenIn; |
| }; |
| |
| out float red; |
| out float green; |
| |
| void main() |
| { |
| vec2 pos = vec2(0.0); |
| switch (gl_VertexID) { |
| case 0: pos = vec2(-1.0, -1.0); break; |
| case 1: pos = vec2(3.0, -1.0); break; |
| case 2: pos = vec2(-1.0, 3.0); break; |
| }; |
| gl_Position = vec4(pos, 0.0, 1.0); |
| red = redIn; |
| green = greenIn; |
| })"; |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout (std140) uniform fsUBO1 |
| { |
| float blueIn; |
| }; |
| |
| layout (std140) uniform fsUBO2 |
| { |
| float alphaIn; |
| }; |
| |
| in float red; |
| in float green; |
| |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(red, green, blueIn, alphaIn); |
| })"; |
| |
| // Setup two uniform buffers, one with 1, one with 0 |
| GLBuffer ubo0; |
| glBindBuffer(GL_UNIFORM_BUFFER, ubo0); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatBlack, GL_STATIC_DRAW); |
| GLBuffer ubo1; |
| glBindBuffer(GL_UNIFORM_BUFFER, ubo1); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLColor32F), &kFloatRed, GL_STATIC_DRAW); |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| // Setup the initial bindings to draw red |
| const GLint vsUBO1 = glGetUniformBlockIndex(mVertProg, "vsUBO1"); |
| const GLint vsUBO2 = glGetUniformBlockIndex(mVertProg, "vsUBO2"); |
| const GLint fsUBO1 = glGetUniformBlockIndex(mFragProg, "fsUBO1"); |
| const GLint fsUBO2 = glGetUniformBlockIndex(mFragProg, "fsUBO2"); |
| glUniformBlockBinding(mVertProg, vsUBO1, 0); |
| glUniformBlockBinding(mVertProg, vsUBO2, 1); |
| glUniformBlockBinding(mFragProg, fsUBO1, 2); |
| glUniformBlockBinding(mFragProg, fsUBO2, 3); |
| |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo1); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 1, ubo0); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 2, ubo0); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 3, ubo1); |
| |
| const int w = getWindowWidth(); |
| const int h = getWindowHeight(); |
| glEnable(GL_SCISSOR_TEST); |
| glScissor(0, 0, w / 2, h / 2); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| // Change the bindings in the vertex shader UBO binding and draw again, should be yellow. |
| glBindBufferBase(GL_UNIFORM_BUFFER, 1, ubo1); |
| glScissor(w / 2, 0, w - w / 2, h / 2); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| // Change the bindings in the fragment shader UBO binding and draw again, should be white. |
| glBindBufferBase(GL_UNIFORM_BUFFER, 2, ubo1); |
| glScissor(0, h / 2, w / 2, h - h / 2); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| // Change the bindings in both shader UBO bindings and draw again, should be green. |
| glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo0); |
| glBindBufferBase(GL_UNIFORM_BUFFER, 2, ubo0); |
| glScissor(w / 2, h / 2, w - w / 2, h - h / 2); |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_PIXEL_RECT_EQ(0, 0, w / 2, h / 2, GLColor::red); |
| EXPECT_PIXEL_RECT_EQ(w / 2, 0, w - w / 2, h / 2, GLColor::yellow); |
| EXPECT_PIXEL_RECT_EQ(0, h / 2, w / 2, h - h / 2, GLColor::white); |
| EXPECT_PIXEL_RECT_EQ(w / 2, h / 2, w - w / 2, h - h / 2, GLColor::green); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test varyings |
| TEST_P(ProgramPipelineTest31, ProgramPipelineVaryings) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Passthrough(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| in vec4 v_position; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = round(v_position); |
| })"; |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| int w = getWindowWidth() - 2; |
| int h = getWindowHeight() - 2; |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black); |
| EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::red); |
| EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow); |
| } |
| |
| // Creates a program pipeline with a 2D texture and renders with it. |
| TEST_P(ProgramPipelineTest31, DrawWith2DTexture) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| in vec4 a_position; |
| out vec2 texCoord; |
| void main() |
| { |
| gl_Position = a_position; |
| texCoord = vec2(a_position.x, a_position.y) * 0.5 + vec2(0.5); |
| })"; |
| |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| in vec2 texCoord; |
| uniform sampler2D tex; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = texture(tex, texCoord); |
| })"; |
| |
| std::array<GLColor, 4> colors = { |
| {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}}; |
| |
| GLTexture tex; |
| glBindTexture(GL_TEXTURE_2D, tex); |
| 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); |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| int w = getWindowWidth() - 2; |
| int h = getWindowHeight() - 2; |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue); |
| EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow); |
| } |
| |
| // Test modifying a shader after it has been detached from a pipeline |
| TEST_P(ProgramPipelineTest31, DetachAndModifyShader) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = essl31_shaders::fs::Green(); |
| |
| GLShader vertShader(GL_VERTEX_SHADER); |
| GLShader fragShader(GL_FRAGMENT_SHADER); |
| mVertProg = glCreateProgram(); |
| mFragProg = glCreateProgram(); |
| |
| // Compile and link a separable vertex shader |
| glShaderSource(vertShader, 1, &vertString, nullptr); |
| glCompileShader(vertShader); |
| glProgramParameteri(mVertProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mVertProg, vertShader); |
| glLinkProgram(mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Compile and link a separable fragment shader |
| glShaderSource(fragShader, 1, &fragString, nullptr); |
| glCompileShader(fragShader); |
| glProgramParameteri(mFragProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mFragProg, fragShader); |
| glLinkProgram(mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Generate a program pipeline and attach the programs |
| glGenProgramPipelines(1, &mPipeline); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Draw once to ensure this worked fine |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); |
| |
| // Detach the fragment shader and modify it such that it no longer fits with this pipeline |
| glDetachShader(mFragProg, fragShader); |
| |
| // Add an input to the fragment shader, which will make it incompatible |
| const GLchar *fragString2 = R"(#version 310 es |
| precision highp float; |
| in vec4 color; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = color; |
| })"; |
| glShaderSource(fragShader, 1, &fragString2, nullptr); |
| glCompileShader(fragShader); |
| |
| // Link and draw with the program again, which should be fine since the shader was detached |
| glLinkProgram(mFragProg); |
| |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test binding two programs that use a texture as different types |
| TEST_P(ProgramPipelineTest31, DifferentTextureTypes) |
| { |
| // Only the Vulkan backend supports PPO |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Per the OpenGL ES 3.1 spec: |
| // |
| // It is not allowed to have variables of different sampler types pointing to the same texture |
| // image unit within a program object. This situation can only be detected at the next rendering |
| // command issued which triggers shader invocations, and an INVALID_OPERATION error will then |
| // be generated |
| // |
| |
| // Create a vertex shader that uses the texture as 2D |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| in vec4 a_position; |
| uniform sampler2D tex2D; |
| layout(location = 0) out vec4 texColorOut; |
| layout(location = 1) out vec2 texCoordOut; |
| void main() |
| { |
| gl_Position = a_position; |
| vec2 texCoord = vec2(a_position.x, a_position.y) * 0.5 + vec2(0.5); |
| texColorOut = textureLod(tex2D, texCoord, 0.0); |
| texCoordOut = texCoord; |
| })"; |
| |
| // Create a fragment shader that uses the texture as Cube |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout(location = 0) in vec4 texColor; |
| layout(location = 1) in vec2 texCoord; |
| uniform samplerCube texCube; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = texture(texCube, vec3(texCoord.x, texCoord.y, 0.0)); |
| })"; |
| |
| // Create and populate the 2D texture |
| std::array<GLColor, 4> colors = { |
| {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}}; |
| GLTexture tex; |
| glBindTexture(GL_TEXTURE_2D, tex); |
| 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); |
| |
| // Create a pipeline that uses the bad combination. This should fail to link the pipeline. |
| bindProgramPipeline(vertString, fragString); |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_ERROR(GL_INVALID_OPERATION); |
| |
| // Update the fragment shader to correctly use 2D texture |
| const GLchar *fragString2 = R"(#version 310 es |
| precision highp float; |
| layout(location = 0) in vec4 texColor; |
| layout(location = 1) in vec2 texCoord; |
| uniform sampler2D tex2D; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = texture(tex2D, texCoord); |
| })"; |
| |
| // Bind the pipeline again, which should succeed. |
| bindProgramPipeline(vertString, fragString2); |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Tests that we receive a PPO link validation error when attempting to draw with the bad PPO |
| TEST_P(ProgramPipelineTest31, VerifyPpoLinkErrorSignalledCorrectly) |
| { |
| // Create pipeline that should fail link |
| // Bind program |
| // Draw |
| // Unbind program |
| // Draw <<--- expect a link validation error here |
| |
| // Only the Vulkan backend supports PPOs |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = essl31_shaders::fs::Red(); |
| // Create a fragment shader that takes a color input |
| // This should cause the PPO link to fail, since the varyings don't match (no output from VS). |
| const GLchar *fragStringBad = R"(#version 310 es |
| precision highp float; |
| layout(location = 0) in vec4 colorIn; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = colorIn; |
| })"; |
| bindProgramPipeline(vertString, fragStringBad); |
| |
| ANGLE_GL_PROGRAM(program, vertString, fragString); |
| drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f, 1.0f, true); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| // Draw with the PPO, which should generate an error due to the link failure. |
| glUseProgram(0); |
| ASSERT_GL_NO_ERROR(); |
| drawQuadWithPPO(essl1_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Tests creating two program pipelines with a common shader and a varying location mismatch. |
| TEST_P(ProgramPipelineTest31, VaryingLocationMismatch) |
| { |
| // Only the Vulkan backend supports PPOs |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create a fragment shader using the varying location "5". |
| const char *kFS = R"(#version 310 es |
| precision mediump float; |
| layout(location = 5) in vec4 color; |
| out vec4 colorOut; |
| void main() |
| { |
| colorOut = color; |
| })"; |
| |
| // Create a pipeline with a vertex shader using varying location "5". Should succeed. |
| const char *kVSGood = R"(#version 310 es |
| precision mediump float; |
| layout(location = 5) out vec4 color; |
| in vec4 position; |
| uniform float uniOne; |
| void main() |
| { |
| gl_Position = position; |
| color = vec4(0, uniOne, 0, 1); |
| })"; |
| |
| mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &kVSGood); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &kFS); |
| ASSERT_NE(mFragProg, 0u); |
| |
| // Generate a program pipeline and attach the programs to their respective stages |
| glGenProgramPipelines(1, &mPipeline); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(mPipeline); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLint location = glGetUniformLocation(mVertProg, "uniOne"); |
| ASSERT_NE(-1, location); |
| glActiveShaderProgram(mPipeline, mVertProg); |
| glUniform1f(location, 1.0); |
| ASSERT_GL_NO_ERROR(); |
| |
| drawQuadWithPPO("position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); |
| |
| // Create a pipeline with a vertex shader using varying location "3". Should fail. |
| const char *kVSBad = R"(#version 310 es |
| precision mediump float; |
| layout(location = 3) out vec4 color; |
| in vec4 position; |
| uniform float uniOne; |
| void main() |
| { |
| gl_Position = position; |
| color = vec4(0, uniOne, 0, 1); |
| })"; |
| |
| glDeleteProgram(mVertProg); |
| mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &kVSBad); |
| ASSERT_NE(mVertProg, 0u); |
| |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| ASSERT_GL_NO_ERROR(); |
| |
| drawQuadWithPPO("position", 0.5f, 1.0f); |
| EXPECT_GL_ERROR(GL_INVALID_OPERATION); |
| } |
| |
| // Test that uniform updates are propagated with minimal state changes. |
| TEST_P(ProgramPipelineTest31, UniformUpdate) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| uniform float redColorIn; |
| uniform float greenColorIn; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, 0.0, 1.0); |
| })"; |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| GLint redLoc = glGetUniformLocation(mFragProg, "redColorIn"); |
| ASSERT_NE(-1, redLoc); |
| GLint greenLoc = glGetUniformLocation(mFragProg, "greenColorIn"); |
| ASSERT_NE(-1, greenLoc); |
| |
| glActiveShaderProgram(mPipeline, mFragProg); |
| |
| std::array<Vector3, 6> verts = GetQuadVertices(); |
| |
| GLBuffer vbo; |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(verts[0]), verts.data(), GL_STATIC_DRAW); |
| |
| GLint posLoc = glGetAttribLocation(mVertProg, essl31_shaders::PositionAttrib()); |
| glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); |
| glEnableVertexAttribArray(posLoc); |
| |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| // Set the output color to red, draw to left half of window. |
| glUniform1f(redLoc, 1.0); |
| glUniform1f(greenLoc, 0.0); |
| glViewport(0, 0, getWindowWidth() / 2, getWindowHeight()); |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Set the output color to green, draw to right half of window. |
| glUniform1f(redLoc, 0.0); |
| glUniform1f(greenLoc, 1.0); |
| |
| glViewport(getWindowWidth() / 2, 0, getWindowWidth() / 2, getWindowHeight()); |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2 + 1, 0, GLColor::green); |
| } |
| |
| // Test that uniform updates propagate to two pipelines. |
| TEST_P(ProgramPipelineTest31, UniformUpdateTwoPipelines) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two separable program objects from a |
| // single source string respectively (vertSrc and fragSrc) |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| uniform float redColorIn; |
| uniform float greenColorIn; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(redColorIn, greenColorIn, 0.0, 1.0); |
| })"; |
| |
| mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString); |
| ASSERT_NE(mFragProg, 0u); |
| |
| GLint redLoc = glGetUniformLocation(mFragProg, "redColorIn"); |
| ASSERT_NE(-1, redLoc); |
| GLint greenLoc = glGetUniformLocation(mFragProg, "greenColorIn"); |
| ASSERT_NE(-1, greenLoc); |
| |
| GLProgramPipeline ppo1; |
| glUseProgramStages(ppo1, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(ppo1, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(ppo1); |
| glActiveShaderProgram(ppo1, mFragProg); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLProgramPipeline ppo2; |
| glUseProgramStages(ppo2, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(ppo2, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(ppo2); |
| glActiveShaderProgram(ppo2, mFragProg); |
| ASSERT_GL_NO_ERROR(); |
| |
| std::array<Vector3, 6> verts = GetQuadVertices(); |
| |
| GLBuffer vbo; |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(verts[0]), verts.data(), GL_STATIC_DRAW); |
| |
| GLint posLoc = glGetAttribLocation(mVertProg, essl31_shaders::PositionAttrib()); |
| glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); |
| glEnableVertexAttribArray(posLoc); |
| |
| glClearColor(0.0, 0.0, 0.0, 0.0); |
| glClear(GL_COLOR_BUFFER_BIT); |
| |
| const GLsizei w = getWindowWidth() / 2; |
| const GLsizei h = getWindowHeight() / 2; |
| |
| // Set the output color to red, draw to UL quad of window with first PPO. |
| glUniform1f(redLoc, 1.0); |
| glUniform1f(greenLoc, 0.0); |
| glBindProgramPipeline(ppo1); |
| glViewport(0, 0, w, h); |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Draw red to UR half of window with second PPO. |
| glBindProgramPipeline(ppo2); |
| glViewport(w, 0, w, h); |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Draw green to LL corner of window with first PPO. |
| glUniform1f(redLoc, 0.0); |
| glUniform1f(greenLoc, 1.0); |
| glBindProgramPipeline(ppo1); |
| glViewport(0, h, w, h); |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Draw green to LR half of window with second PPO. |
| glBindProgramPipeline(ppo2); |
| glViewport(w, h, w, h); |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| EXPECT_PIXEL_COLOR_EQ(w + 1, 0, GLColor::red); |
| EXPECT_PIXEL_COLOR_EQ(0, h + 1, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(w + 1, h + 1, GLColor::green); |
| } |
| |
| // Tests that setting sampler bindings on a program before the pipeline works as expected. |
| TEST_P(ProgramPipelineTest31, BindSamplerBeforeCreatingPipeline) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| // Create two textures - red and green. |
| GLTexture redTex; |
| glBindTexture(GL_TEXTURE_2D, redTex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::red); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| GLTexture greenTex; |
| glBindTexture(GL_TEXTURE_2D, greenTex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| // Bind red to texture unit 0 and green to unit 1. |
| glActiveTexture(GL_TEXTURE0); |
| glBindTexture(GL_TEXTURE_2D, redTex); |
| |
| glActiveTexture(GL_TEXTURE1); |
| glBindTexture(GL_TEXTURE_2D, greenTex); |
| |
| // Create the separable programs. |
| const char *vsSource = essl1_shaders::vs::Texture2D(); |
| mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vsSource); |
| ASSERT_NE(0u, mVertProg); |
| |
| const char *fsSource = essl1_shaders::fs::Texture2D(); |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fsSource); |
| ASSERT_NE(0u, mFragProg); |
| |
| // Set the program to sample from the green texture. |
| GLint texLoc = glGetUniformLocation(mFragProg, essl1_shaders::Texture2DUniform()); |
| ASSERT_NE(-1, texLoc); |
| |
| glUseProgram(mFragProg); |
| glUniform1i(texLoc, 1); |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| // Create and draw with the pipeline. |
| GLProgramPipeline ppo; |
| glUseProgramStages(ppo, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(ppo, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(ppo); |
| |
| ASSERT_GL_NO_ERROR(); |
| |
| drawQuadWithPPO(essl1_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| // We should have sampled from the second texture bound to unit 1. |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); |
| } |
| |
| // Test that a shader IO block varying with separable program links |
| // successfully. |
| TEST_P(ProgramPipelineTest31, VaryingIOBlockSeparableProgram) |
| { |
| // Only the Vulkan backend supports PPOs |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks")); |
| |
| constexpr char kVS[] = |
| R"(#version 310 es |
| #extension GL_EXT_shader_io_blocks : require |
| |
| precision highp float; |
| in vec4 inputAttribute; |
| out Block_inout { vec4 value; } user_out; |
| |
| void main() |
| { |
| gl_Position = inputAttribute; |
| user_out.value = vec4(4.0, 5.0, 6.0, 7.0); |
| })"; |
| |
| constexpr char kFS[] = |
| R"(#version 310 es |
| #extension GL_EXT_shader_io_blocks : require |
| |
| precision highp float; |
| layout(location = 0) out mediump vec4 color; |
| in Block_inout { vec4 value; } user_in; |
| |
| void main() |
| { |
| color = vec4(1, 0, 0, 1); |
| })"; |
| |
| bindProgramPipeline(kVS, kFS); |
| drawQuadWithPPO("inputAttribute", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| } |
| |
| // Test that a shader IO block varying with separable program links |
| // successfully. |
| TEST_P(ProgramPipelineXFBTest31, VaryingIOBlockSeparableProgramWithXFB) |
| { |
| // Only the Vulkan backend supports PPOs |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks")); |
| |
| constexpr char kVS[] = |
| R"(#version 310 es |
| #extension GL_EXT_shader_io_blocks : require |
| |
| precision highp float; |
| in vec4 inputAttribute; |
| out Block_inout { vec4 value; vec4 value2; } user_out; |
| |
| void main() |
| { |
| gl_Position = inputAttribute; |
| user_out.value = vec4(4.0, 5.0, 6.0, 7.0); |
| user_out.value2 = vec4(8.0, 9.0, 10.0, 11.0); |
| })"; |
| |
| constexpr char kFS[] = |
| R"(#version 310 es |
| #extension GL_EXT_shader_io_blocks : require |
| |
| precision highp float; |
| layout(location = 0) out mediump vec4 color; |
| in Block_inout { vec4 value; vec4 value2; } user_in; |
| |
| void main() |
| { |
| color = vec4(1, 0, 0, 1); |
| })"; |
| std::vector<std::string> tfVaryings; |
| tfVaryings.push_back("Block_inout.value"); |
| tfVaryings.push_back("Block_inout.value2"); |
| bindProgramPipelineWithXFBVaryings(kVS, kFS, tfVaryings, GL_INTERLEAVED_ATTRIBS); |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, mTransformFeedbackBuffer); |
| |
| // Make sure reconfiguring the vertex shader's transform feedback varyings without a link does |
| // not affect the pipeline. Same with changing buffer modes |
| std::vector<const char *> tfVaryingsBogus = {"some", "invalid[0]", "names"}; |
| glTransformFeedbackVaryings(mVertProg, static_cast<GLsizei>(tfVaryingsBogus.size()), |
| tfVaryingsBogus.data(), GL_SEPARATE_ATTRIBS); |
| |
| glBeginTransformFeedback(GL_TRIANGLES); |
| drawQuadWithPPO("inputAttribute", 0.5f, 1.0f); |
| glEndTransformFeedback(); |
| |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| |
| void *mappedBuffer = |
| glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, sizeof(float) * 8, GL_MAP_READ_BIT); |
| ASSERT_NE(nullptr, mappedBuffer); |
| |
| float *mappedFloats = static_cast<float *>(mappedBuffer); |
| for (unsigned int cnt = 0; cnt < 8; ++cnt) |
| { |
| EXPECT_EQ(4 + cnt, mappedFloats[cnt]); |
| } |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test that XFB GL_SEPARATE_ATTRIBS behaves correctly with PPO. |
| TEST_P(ProgramPipelineXFBTest31, SeparableProgramWithXFBSeparateMode) |
| { |
| // Only the Vulkan backend supports PPOs |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| in vec4 inputAttribute; |
| uniform vec4 u_color; |
| out vec4 texCoord; |
| out vec4 unused1; |
| void main() |
| { |
| gl_Position = inputAttribute; |
| texCoord = u_color; |
| })"; |
| |
| GLShader vertShader(GL_VERTEX_SHADER); |
| mVertProg = glCreateProgram(); |
| |
| // Compile and attach a separable vertex shader |
| glShaderSource(vertShader, 1, &vertString, nullptr); |
| glCompileShader(vertShader); |
| glProgramParameteri(mVertProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mVertProg, vertShader); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Select the varyings for XFB with GL_SEPARATE_ATTRIBS mode |
| std::vector<const char *> tfVaryings = {"unused1", "texCoord"}; |
| glTransformFeedbackVaryings(mVertProg, static_cast<GLsizei>(tfVaryings.size()), |
| tfVaryings.data(), GL_SEPARATE_ATTRIBS); |
| glLinkProgram(mVertProg); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLint uniformLoc = glGetUniformLocation(mVertProg, "u_color"); |
| ASSERT_NE(uniformLoc, -1); |
| glProgramUniform4f(mVertProg, uniformLoc, 1, 2, 3, 4); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Generate a program pipeline and attach the program to respective stage |
| glGenProgramPipelines(1, &mPipeline); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| glBindProgramPipeline(mPipeline); |
| ASSERT_GL_NO_ERROR(); |
| |
| GLuint transformFeedbackBuffer[2] = {0}; |
| // Allocate space for vec4 |
| static const size_t transformFeedbackBufferSize = 16; |
| glGenBuffers(2, &transformFeedbackBuffer[0]); |
| |
| // Bind buffers to the transform feedback binding points |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, transformFeedbackBuffer[0]); |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, transformFeedbackBufferSize, NULL, GL_STATIC_DRAW); |
| |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, transformFeedbackBuffer[1]); |
| glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, transformFeedbackBufferSize, NULL, GL_STATIC_DRAW); |
| |
| // Process one point with transform feedback |
| glBeginTransformFeedback(GL_POINTS); |
| glDrawArrays(GL_POINTS, 0, 1); |
| glEndTransformFeedback(); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Second XFB buffer |
| glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 1, transformFeedbackBuffer[1]); |
| void *mappedBuffer = glMapBufferRange(GL_TRANSFORM_FEEDBACK_BUFFER, 0, |
| transformFeedbackBufferSize, GL_MAP_READ_BIT); |
| ASSERT_NE(nullptr, mappedBuffer); |
| |
| float *mappedFloats = static_cast<float *>(mappedBuffer); |
| // Expect vec4(1, 2, 3, 4) |
| EXPECT_EQ(1, mappedFloats[0]); |
| EXPECT_EQ(2, mappedFloats[1]); |
| EXPECT_EQ(3, mappedFloats[2]); |
| EXPECT_EQ(4, mappedFloats[3]); |
| |
| glUnmapBuffer(GL_TRANSFORM_FEEDBACK_BUFFER); |
| glDeleteBuffers(2, transformFeedbackBuffer); |
| |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test modifying a shader and re-linking it updates the PPO too |
| TEST_P(ProgramPipelineTest31, ModifyAndRelinkShader) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragStringGreen = essl31_shaders::fs::Green(); |
| const GLchar *fragStringRed = essl31_shaders::fs::Red(); |
| |
| GLShader vertShader(GL_VERTEX_SHADER); |
| GLShader fragShader(GL_FRAGMENT_SHADER); |
| mVertProg = glCreateProgram(); |
| mFragProg = glCreateProgram(); |
| |
| // Compile and link a separable vertex shader |
| glShaderSource(vertShader, 1, &vertString, nullptr); |
| glCompileShader(vertShader); |
| glProgramParameteri(mVertProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mVertProg, vertShader); |
| glLinkProgram(mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Compile and link a separable fragment shader |
| glShaderSource(fragShader, 1, &fragStringGreen, nullptr); |
| glCompileShader(fragShader); |
| glProgramParameteri(mFragProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mFragProg, fragShader); |
| glLinkProgram(mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Generate a program pipeline and attach the programs |
| glGenProgramPipelines(1, &mPipeline); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Draw once to ensure this worked fine |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green); |
| |
| // Detach the fragment shader and modify it such that it no longer fits with this pipeline |
| glDetachShader(mFragProg, fragShader); |
| |
| // Modify the FS and re-link it |
| glShaderSource(fragShader, 1, &fragStringRed, nullptr); |
| glCompileShader(fragShader); |
| glProgramParameteri(mFragProg, GL_PROGRAM_SEPARABLE, GL_TRUE); |
| glAttachShader(mFragProg, fragShader); |
| glLinkProgram(mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Draw with the PPO again and verify it's now red |
| drawQuadWithPPO(essl31_shaders::PositionAttrib(), 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| } |
| |
| // Test that a PPO can be used when the attached shader programs are created with glProgramBinary(). |
| // This validates the necessary programs' information is serialized/deserialized so they can be |
| // linked by the PPO during glDrawArrays. |
| TEST_P(ProgramPipelineTest31, ProgramBinary) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| ANGLE_SKIP_TEST_IF(getAvailableProgramBinaryFormatCount() == 0); |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| in vec4 a_position; |
| out vec2 texCoord; |
| void main() |
| { |
| gl_Position = a_position; |
| texCoord = vec2(a_position.x, a_position.y) * 0.5 + vec2(0.5); |
| })"; |
| |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| in vec2 texCoord; |
| uniform sampler2D tex; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = texture(tex, texCoord); |
| })"; |
| |
| std::array<GLColor, 4> colors = { |
| {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}}; |
| |
| GLTexture tex; |
| glBindTexture(GL_TEXTURE_2D, tex); |
| 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); |
| |
| mVertProg = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertString); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &fragString); |
| ASSERT_NE(mFragProg, 0u); |
| |
| // Save the VS program binary out |
| std::vector<uint8_t> vsBinary(0); |
| GLint vsProgramLength = 0; |
| GLint vsWrittenLength = 0; |
| GLenum vsBinaryFormat = 0; |
| glGetProgramiv(mVertProg, GL_PROGRAM_BINARY_LENGTH, &vsProgramLength); |
| ASSERT_GL_NO_ERROR(); |
| vsBinary.resize(vsProgramLength); |
| glGetProgramBinary(mVertProg, vsProgramLength, &vsWrittenLength, &vsBinaryFormat, |
| vsBinary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Save the FS program binary out |
| std::vector<uint8_t> fsBinary(0); |
| GLint fsProgramLength = 0; |
| GLint fsWrittenLength = 0; |
| GLenum fsBinaryFormat = 0; |
| glGetProgramiv(mFragProg, GL_PROGRAM_BINARY_LENGTH, &fsProgramLength); |
| ASSERT_GL_NO_ERROR(); |
| fsBinary.resize(fsProgramLength); |
| glGetProgramBinary(mFragProg, fsProgramLength, &fsWrittenLength, &fsBinaryFormat, |
| fsBinary.data()); |
| ASSERT_GL_NO_ERROR(); |
| |
| mVertProg = glCreateProgram(); |
| glProgramBinary(mVertProg, vsBinaryFormat, vsBinary.data(), vsWrittenLength); |
| mFragProg = glCreateProgram(); |
| glProgramBinary(mFragProg, fsBinaryFormat, fsBinary.data(), fsWrittenLength); |
| |
| // Generate a program pipeline and attach the programs to their respective stages |
| glGenProgramPipelines(1, &mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| int w = getWindowWidth() - 2; |
| int h = getWindowHeight() - 2; |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| EXPECT_PIXEL_COLOR_EQ(w, 0, GLColor::green); |
| EXPECT_PIXEL_COLOR_EQ(0, h, GLColor::blue); |
| EXPECT_PIXEL_COLOR_EQ(w, h, GLColor::yellow); |
| } |
| |
| // Test that updating a sampler uniform in a separable program behaves correctly with PPOs. |
| TEST_P(ProgramPipelineTest31, SampleTextureAThenTextureB) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| constexpr int kWidth = 2; |
| constexpr int kHeight = 2; |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| in vec2 a_position; |
| out vec2 texCoord; |
| void main() |
| { |
| gl_Position = vec4(a_position, 0, 1); |
| texCoord = a_position * 0.5 + vec2(0.5); |
| })"; |
| |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| in vec2 texCoord; |
| uniform sampler2D tex; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = texture(tex, texCoord); |
| })"; |
| |
| std::array<GLColor, kWidth * kHeight> redColor = { |
| {GLColor::red, GLColor::red, GLColor::red, GLColor::red}}; |
| std::array<GLColor, kWidth * kHeight> greenColor = { |
| {GLColor::green, GLColor::green, GLColor::green, GLColor::green}}; |
| |
| // Create a red texture and bind to texture unit 0 |
| GLTexture redTex; |
| glActiveTexture(GL_TEXTURE0); |
| glBindTexture(GL_TEXTURE_2D, redTex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| redColor.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(); |
| // Create a green texture and bind to texture unit 1 |
| GLTexture greenTex; |
| glActiveTexture(GL_TEXTURE1); |
| glBindTexture(GL_TEXTURE_2D, greenTex); |
| glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, |
| greenColor.data()); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| glActiveTexture(GL_TEXTURE0); |
| ASSERT_GL_NO_ERROR(); |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| GLint location1 = glGetUniformLocation(mFragProg, "tex"); |
| ASSERT_NE(location1, -1); |
| glActiveShaderProgram(mPipeline, mFragProg); |
| ASSERT_GL_NO_ERROR(); |
| |
| glEnable(GL_BLEND); |
| glBlendEquation(GL_FUNC_ADD); |
| glBlendFunc(GL_ONE, GL_ONE); |
| |
| // Draw red |
| glUniform1i(location1, 0); |
| ASSERT_GL_NO_ERROR(); |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| // Draw green |
| glUniform1i(location1, 1); |
| ASSERT_GL_NO_ERROR(); |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| |
| EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::yellow); |
| } |
| |
| // Verify that image uniforms can be used with separable programs |
| TEST_P(ProgramPipelineTest31, ImageUniforms) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| GLint maxVertexImageUniforms; |
| glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &maxVertexImageUniforms); |
| ANGLE_SKIP_TEST_IF(maxVertexImageUniforms == 0); |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| precision highp image2D; |
| layout(binding = 0, r32f) uniform image2D img; |
| |
| void main() |
| { |
| gl_Position = imageLoad(img, ivec2(0, 0)); |
| })"; |
| |
| const GLchar *fragString = essl31_shaders::fs::Red(); |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| GLTexture texture; |
| GLfloat value = 1.0; |
| |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32F, 1, 1); |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED, GL_FLOAT, &value); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32F); |
| |
| glDrawArrays(GL_POINTS, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Verify that image uniforms can link in separable programs |
| TEST_P(ProgramPipelineTest31, LinkedImageUniforms) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| GLint maxVertexImageUniforms; |
| glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &maxVertexImageUniforms); |
| ANGLE_SKIP_TEST_IF(maxVertexImageUniforms == 0); |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| precision highp image2D; |
| layout(binding = 0, r32f) uniform image2D img; |
| |
| void main() |
| { |
| vec2 position = -imageLoad(img, ivec2(0, 0)).rr; |
| if (gl_VertexID == 1) |
| position = vec2(3, -1); |
| else if (gl_VertexID == 2) |
| position = vec2(-1, 3); |
| |
| gl_Position = vec4(position, 0, 1); |
| })"; |
| |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| precision highp image2D; |
| layout(binding = 0, r32f) uniform image2D img; |
| layout(location = 0) out vec4 color; |
| |
| void main() |
| { |
| color = imageLoad(img, ivec2(0, 0)); |
| })"; |
| |
| bindProgramPipeline(vertString, fragString); |
| |
| GLTexture texture; |
| GLfloat value = 1.0; |
| |
| glBindTexture(GL_TEXTURE_2D, texture); |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32F, 1, 1); |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED, GL_FLOAT, &value); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32F); |
| |
| glDrawArrays(GL_TRIANGLES, 0, 3); |
| |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Verify that we can have the max amount of uniform buffer objects as part of a program |
| // pipeline. |
| TEST_P(ProgramPipelineTest31, MaxFragmentUniformBufferObjects) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| GLint maxUniformBlocks; |
| glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, &maxUniformBlocks); |
| |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| std::stringstream fragStringStream; |
| fragStringStream << R"(#version 310 es |
| precision highp float; |
| out vec4 my_FragColor; |
| layout(binding = 0) uniform block { |
| float data; |
| } ubo[)"; |
| fragStringStream << maxUniformBlocks; |
| fragStringStream << R"(]; |
| void main() |
| { |
| my_FragColor = vec4(1.0); |
| )"; |
| for (GLint index = 0; index < maxUniformBlocks; index++) |
| { |
| fragStringStream << "my_FragColor.x + ubo[" << index << "].data;" << std::endl; |
| } |
| fragStringStream << "}" << std::endl; |
| |
| bindProgramPipeline(vertString, fragStringStream.str().c_str()); |
| |
| std::vector<GLBuffer> buffers(maxUniformBlocks); |
| for (GLint index = 0; index < maxUniformBlocks; ++index) |
| { |
| glBindBuffer(GL_UNIFORM_BUFFER, buffers[index]); |
| glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat), NULL, GL_STATIC_DRAW); |
| glBindBufferBase(GL_UNIFORM_BUFFER, index, buffers[index]); |
| } |
| |
| glDrawArrays(GL_POINTS, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Verify that we can have the max amount of shader storage buffer objects as part of a program |
| // pipeline. |
| TEST_P(ProgramPipelineTest31, MaxFragmentShaderStorageBufferObjects) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| GLint maxShaderStorageBuffers; |
| glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxShaderStorageBuffers); |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| std::stringstream fragStringStream; |
| fragStringStream << R"(#version 310 es |
| precision highp float; |
| out vec4 my_FragColor; |
| layout(binding = 0) buffer buf { |
| float data; |
| } ssbo[)"; |
| fragStringStream << maxShaderStorageBuffers; |
| fragStringStream << R"(]; |
| void main() |
| { |
| my_FragColor = vec4(1.0); |
| )"; |
| for (GLint index = 0; index < maxShaderStorageBuffers; index++) |
| { |
| fragStringStream << "my_FragColor.x + ssbo[" << index << "].data;" << std::endl; |
| } |
| fragStringStream << "}" << std::endl; |
| |
| bindProgramPipeline(vertString, fragStringStream.str().c_str()); |
| |
| std::vector<GLBuffer> buffers(maxShaderStorageBuffers); |
| for (GLint index = 0; index < maxShaderStorageBuffers; ++index) |
| { |
| glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffers[index]); |
| glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), NULL, GL_STATIC_DRAW); |
| glBindBufferBase(GL_SHADER_STORAGE_BUFFER, index, buffers[index]); |
| } |
| |
| glDrawArrays(GL_POINTS, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Test validation of redefinition of gl_Position and gl_PointSize in the vertex shader when |
| // GL_EXT_separate_shader_objects is enabled. |
| TEST_P(ProgramPipelineTest31, ValidatePositionPointSizeRedefinition) |
| { |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_separate_shader_objects")); |
| |
| { |
| constexpr char kVS[] = R"(#version 310 es |
| #extension GL_EXT_separate_shader_objects: require |
| |
| out float gl_PointSize; |
| |
| void main() |
| { |
| gl_Position = vec4(0); |
| gl_PointSize = 1.; |
| })"; |
| |
| // Should fail because gl_Position is not declared. |
| GLuint shader = createShaderProgram(GL_VERTEX_SHADER, kVS); |
| EXPECT_EQ(shader, 0u); |
| } |
| |
| { |
| constexpr char kVS[] = R"(#version 310 es |
| #extension GL_EXT_separate_shader_objects: require |
| |
| out float gl_PointSize; |
| |
| void f() |
| { |
| gl_PointSize = 1.; |
| } |
| |
| out vec4 gl_Position; |
| |
| void main() |
| { |
| gl_Position = vec4(0); |
| })"; |
| |
| // Should fail because gl_PointSize is used before gl_Position is declared. |
| GLuint shader = createShaderProgram(GL_VERTEX_SHADER, kVS); |
| EXPECT_EQ(shader, 0u); |
| } |
| |
| { |
| constexpr char kVS[] = R"(#version 310 es |
| #extension GL_EXT_separate_shader_objects: require |
| |
| out float gl_PointSize; |
| out vec4 gl_Position; |
| |
| void main() |
| { |
| gl_Position = vec4(0); |
| gl_PointSize = 1.; |
| })"; |
| |
| // Should compile. |
| GLuint shader = createShaderProgram(GL_VERTEX_SHADER, kVS); |
| EXPECT_NE(shader, 0u); |
| } |
| } |
| |
| // Basic draw test with GL_EXT_separate_shader_objects enabled in the vertex shader |
| TEST_P(ProgramPipelineTest31, BasicDrawWithPositionPointSizeRedefinition) |
| { |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_separate_shader_objects")); |
| |
| const char kVS[] = R"(#version 310 es |
| #extension GL_EXT_separate_shader_objects: require |
| |
| out float gl_PointSize; |
| out vec4 gl_Position; |
| |
| in vec4 a_position; |
| |
| void main() |
| { |
| gl_Position = a_position; |
| })"; |
| |
| mVertProg = createShaderProgram(GL_VERTEX_SHADER, kVS); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = createShaderProgram(GL_FRAGMENT_SHADER, essl31_shaders::fs::Red()); |
| ASSERT_NE(mFragProg, 0u); |
| |
| // Generate a program pipeline and attach the programs to their respective stages |
| GLuint pipeline; |
| glGenProgramPipelines(1, &pipeline); |
| glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| glBindProgramPipeline(pipeline); |
| EXPECT_GL_NO_ERROR(); |
| |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red); |
| } |
| |
| // Test a PPO scenario from a game, calling glBindBufferRange between two draws with |
| // multiple binding points, some unused. This would result in a crash without the fix. |
| // https://issuetracker.google.com/issues/299532942 |
| TEST_P(ProgramPipelineTest31, ProgramPipelineBindBufferRange) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = R"(#version 310 es |
| in vec4 position; |
| layout(std140, binding = 0) uniform ubo1 { |
| vec4 color; |
| }; |
| layout(location=0) out vec4 vsColor; |
| void main() |
| { |
| vsColor = color; |
| gl_Position = position; |
| })"; |
| |
| const GLchar *fragString = R"(#version 310 es |
| precision mediump float; |
| layout(std140, binding = 1) uniform globals { |
| vec4 fsColor; |
| }; |
| layout(std140, binding = 2) uniform params { |
| vec4 foo; |
| }; |
| layout(std140, binding = 3) uniform layer { |
| vec4 bar; |
| }; |
| layout(location=0) highp in vec4 vsColor; |
| layout(location=0) out vec4 diffuse; |
| void main() |
| { |
| diffuse = vsColor + fsColor; |
| })"; |
| |
| // Create the pipeline |
| GLProgramPipeline programPipeline; |
| glBindProgramPipeline(programPipeline); |
| |
| // Create the vertex shader |
| GLShader vertShader(GL_VERTEX_SHADER); |
| glShaderSource(vertShader, 1, &vertString, nullptr); |
| glCompileShader(vertShader); |
| mVertProg = glCreateProgram(); |
| glProgramParameteri(mVertProg, GL_PROGRAM_SEPARABLE, 1); |
| glAttachShader(mVertProg, vertShader); |
| glLinkProgram(mVertProg); |
| glUseProgramStages(programPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Create the fragment shader |
| GLShader fragShader(GL_FRAGMENT_SHADER); |
| glShaderSource(fragShader, 1, &fragString, nullptr); |
| glCompileShader(fragShader); |
| mFragProg = glCreateProgram(); |
| glProgramParameteri(mFragProg, GL_PROGRAM_SEPARABLE, 1); |
| glAttachShader(mFragProg, fragShader); |
| glLinkProgram(mFragProg); |
| glUseProgramStages(programPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Set up a uniform buffer with room for five offsets, four active at a time |
| GLBuffer ubo; |
| glBindBuffer(GL_UNIFORM_BUFFER, ubo); |
| glBufferData(GL_UNIFORM_BUFFER, 2048, 0, GL_DYNAMIC_DRAW); |
| uint8_t *mappedBuffer = |
| static_cast<uint8_t *>(glMapBufferRange(GL_UNIFORM_BUFFER, 0, 2048, GL_MAP_WRITE_BIT)); |
| ASSERT_NE(nullptr, mappedBuffer); |
| |
| // Only set up three of the five offsets. The other two must be present, but unused. |
| GLColor32F *binding0 = reinterpret_cast<GLColor32F *>(mappedBuffer); |
| GLColor32F *binding1 = reinterpret_cast<GLColor32F *>(mappedBuffer + 256); |
| GLColor32F *binding4 = reinterpret_cast<GLColor32F *>(mappedBuffer + 1024); |
| *binding0 = kFloatRed; |
| *binding1 = kFloatGreen; |
| *binding4 = kFloatBlue; |
| glUnmapBuffer(GL_UNIFORM_BUFFER); |
| |
| // Start with binding0=red and binding1=green |
| glBindBufferRange(GL_UNIFORM_BUFFER, 0, ubo, 0, 256); |
| glBindBufferRange(GL_UNIFORM_BUFFER, 1, ubo, 256, 512); |
| glBindBufferRange(GL_UNIFORM_BUFFER, 2, ubo, 512, 768); |
| glBindBufferRange(GL_UNIFORM_BUFFER, 3, ubo, 768, 1024); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Set up data for draw |
| std::array<Vector3, 6> verts = GetQuadVertices(); |
| GLBuffer vbo; |
| glBindBuffer(GL_ARRAY_BUFFER, vbo); |
| glBufferData(GL_ARRAY_BUFFER, verts.size() * sizeof(verts[0]), verts.data(), GL_STATIC_DRAW); |
| GLint posLoc = glGetAttribLocation(mVertProg, "position"); |
| glVertexAttribPointer(posLoc, 3, GL_FLOAT, GL_FALSE, 0, nullptr); |
| glEnableVertexAttribArray(posLoc); |
| EXPECT_GL_NO_ERROR(); |
| |
| // Perform the first draw |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| |
| // At this point we have red+green=yellow, but read-back changes dirty bits and breaks the test |
| // EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow); |
| EXPECT_GL_NO_ERROR(); |
| |
| // This is the key here - call glBindBufferRange between glDraw* calls, changing binding0=blue |
| glBindBufferRange(GL_UNIFORM_BUFFER, 0, ubo, 1024, 1280); |
| EXPECT_GL_NO_ERROR(); |
| |
| // The next draw would crash in handleDirtyGraphicsUniformBuffers without the accompanying fix |
| glDrawArrays(GL_TRIANGLES, 0, 6); |
| |
| // We should now have green+blue=cyan |
| EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test that GetProgramPipelineivf works. |
| TEST_P(ProgramPipelineTest31, ProgramPipelineivTest) |
| { |
| GLuint pipeline; |
| GLint log_length = -1; |
| glGenProgramPipelines(1, &pipeline); |
| glGetProgramPipelineiv(pipeline, GL_INFO_LOG_LENGTH, &log_length); |
| glGetProgramPipelineInfoLog(pipeline, 0, NULL, NULL); |
| EXPECT_GL_NO_ERROR(); |
| glDeleteProgramPipelines(1, &pipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Test that shader interface matching does not accidentally match by name |
| // when location is specified in separable programs. |
| TEST_P(ProgramPipelineTest31, ShaderInterfaceMatchingTest) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan()); |
| |
| const GLchar *vertString = R"(#version 310 es |
| precision highp float; |
| in vec4 a_position; |
| layout(location = 0) flat out uvec3 u3; |
| layout(location = 1) out float f[2]; |
| void main() |
| { |
| gl_Position = a_position; |
| })"; |
| |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| layout(location = 0) flat in uvec3 f; |
| layout(location = 1) in float u3[2]; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(1.0, 1.0, 0.0, 1.0); |
| })"; |
| bindProgramPipeline(vertString, fragString); |
| |
| drawQuadWithPPO("a_position", 0.5f, 1.0f); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| class ProgramPipelineTest32 : public ProgramPipelineTest |
| { |
| protected: |
| void testTearDown() override |
| { |
| glDeleteProgram(mVertProg); |
| glDeleteProgram(mFragProg); |
| glDeleteProgramPipelines(1, &mPipeline); |
| } |
| |
| void bindProgramPipeline(const GLchar *vertString, |
| const GLchar *fragString, |
| const GLchar *geomString); |
| void drawQuadWithPPO(const std::string &positionAttribName, |
| const GLfloat positionAttribZ, |
| const GLfloat positionAttribXYScale); |
| |
| GLuint mVertProg = 0; |
| GLuint mFragProg = 0; |
| GLuint mGeomProg = 0; |
| GLuint mPipeline = 0; |
| }; |
| |
| void ProgramPipelineTest32::bindProgramPipeline(const GLchar *vertString, |
| const GLchar *fragString, |
| const GLchar *geomString) |
| { |
| mVertProg = createShaderProgram(GL_VERTEX_SHADER, vertString); |
| ASSERT_NE(mVertProg, 0u); |
| mFragProg = createShaderProgram(GL_FRAGMENT_SHADER, fragString); |
| ASSERT_NE(mFragProg, 0u); |
| mGeomProg = createShaderProgram(GL_GEOMETRY_SHADER, geomString); |
| ASSERT_NE(mGeomProg, 0u); |
| |
| // Generate a program pipeline and attach the programs to their respective stages |
| glGenProgramPipelines(1, &mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_VERTEX_SHADER_BIT, mVertProg); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_FRAGMENT_SHADER_BIT, mFragProg); |
| EXPECT_GL_NO_ERROR(); |
| glUseProgramStages(mPipeline, GL_GEOMETRY_SHADER_BIT, mGeomProg); |
| EXPECT_GL_NO_ERROR(); |
| glBindProgramPipeline(mPipeline); |
| EXPECT_GL_NO_ERROR(); |
| } |
| |
| // Verify that we can have the max amount of uniforms with a geometry shader as part of a program |
| // pipeline. |
| TEST_P(ProgramPipelineTest32, MaxGeometryImageUniforms) |
| { |
| ANGLE_SKIP_TEST_IF(!IsVulkan() || !IsGLExtensionEnabled("GL_EXT_geometry_shader")); |
| |
| GLint maxGeometryImageUnits; |
| glGetIntegerv(GL_MAX_GEOMETRY_IMAGE_UNIFORMS_EXT, &maxGeometryImageUnits); |
| |
| const GLchar *vertString = essl31_shaders::vs::Simple(); |
| const GLchar *fragString = R"(#version 310 es |
| precision highp float; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(1.0); |
| })"; |
| |
| std::stringstream geomStringStream; |
| |
| geomStringStream << R"(#version 310 es |
| #extension GL_OES_geometry_shader : require |
| layout (points) in; |
| layout (points, max_vertices = 1) out; |
| |
| precision highp iimage2D; |
| |
| ivec4 counter = ivec4(0); |
| )"; |
| |
| for (GLint index = 0; index < maxGeometryImageUnits; ++index) |
| { |
| geomStringStream << "layout(binding = " << index << ", r32i) uniform iimage2D img" << index |
| << ";" << std::endl; |
| } |
| |
| geomStringStream << R"( |
| void main() |
| { |
| )"; |
| |
| for (GLint index = 0; index < maxGeometryImageUnits; ++index) |
| { |
| geomStringStream << "counter += imageLoad(img" << index << ", ivec2(0, 0));" << std::endl; |
| } |
| |
| geomStringStream << R"( |
| gl_Position = vec4(float(counter.x), 0.0, 0.0, 1.0); |
| EmitVertex(); |
| } |
| )"; |
| |
| bindProgramPipeline(vertString, fragString, geomStringStream.str().c_str()); |
| |
| std::vector<GLTexture> textures(maxGeometryImageUnits); |
| for (GLint index = 0; index < maxGeometryImageUnits; ++index) |
| { |
| GLint value = index + 1; |
| |
| glBindTexture(GL_TEXTURE_2D, textures[index]); |
| glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, 1, 1); |
| glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_INT, &value); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| |
| glBindImageTexture(index, textures[index], 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32I); |
| } |
| |
| glDrawArrays(GL_POINTS, 0, 6); |
| ASSERT_GL_NO_ERROR(); |
| } |
| |
| // Verify creation of seperable tessellation control shader program with transform feeback varying |
| TEST_P(ProgramPipelineTest32, CreateProgramWithTransformFeedbackVarying) |
| { |
| ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader")); |
| |
| const char *kVS = |
| "#version 320 es\n" |
| "\n" |
| "#extension GL_EXT_shader_io_blocks : require\n" |
| "\n" |
| "precision highp float;\n" |
| |
| "out BLOCK_INOUT { vec4 value; } user_out;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = vec4(1.0, 0.0, 0.0, 1.0);\n" |
| " user_out.value = vec4(4.0, 5.0, 6.0, 7.0);\n" |
| "}\n"; |
| |
| // Fragment shader body |
| const char *kFS = |
| "#version 320 es\n" |
| "\n" |
| "#extension GL_EXT_shader_io_blocks : require\n" |
| "\n" |
| "precision highp float;\n" |
| "in BLOCK_INOUT { vec4 value; } user_in;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| "}\n"; |
| |
| // Geometry shader body |
| const char *kGS = |
| "#version 320 es\n" |
| "\n" |
| "#extension GL_EXT_geometry_shader : require\n" |
| "\n" |
| "layout(points) in;\n" |
| "layout(points, max_vertices = 1) out;\n" |
| "\n" |
| "precision highp float;\n" |
| "//${IN_PER_VERTEX_DECL_ARRAY}\n" |
| "//${OUT_PER_VERTEX_DECL}\n" |
| "in BLOCK_INOUT { vec4 value; } user_in[];\n" |
| "out BLOCK_INOUT { vec4 value; } user_out;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " user_out.value = vec4(1.0, 2.0, 3.0, 4.0);\n" |
| " gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" |
| "\n" |
| " EmitVertex();\n" |
| "}\n"; |
| |
| // tessellation control shader body |
| const char *kTCS = |
| "#version 320 es\n" |
| "\n" |
| "#extension GL_EXT_tessellation_shader : require\n" |
| "#extension GL_EXT_shader_io_blocks : require\n" |
| "\n" |
| "layout (vertices=4) out;\n" |
| "\n" |
| "precision highp float;\n" |
| "in BLOCK_INOUT { vec4 value; } user_in[];\n" |
| "out BLOCK_INOUT { vec4 value; } user_out[];\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_out [gl_InvocationID].gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n" |
| " user_out [gl_InvocationID].value = vec4(2.0, 3.0, 4.0, 5.0);\n" |
| "\n" |
| " gl_TessLevelOuter[0] = 1.0;\n" |
| " gl_TessLevelOuter[1] = 1.0;\n" |
| "}\n"; |
| |
| // Tessellation evaluation shader |
| const char *kTES = |
| "#version 320 es\n" |
| "\n" |
| "#extension GL_EXT_tessellation_shader : require\n" |
| "#extension GL_EXT_shader_io_blocks : require\n" |
| "\n" |
| "layout (isolines, point_mode) in;\n" |
| "\n" |
| "precision highp float;\n" |
| "in BLOCK_INOUT { vec4 value; } user_in[];\n" |
| "out BLOCK_INOUT { vec4 value; } user_out;\n" |
| "\n" |
| "void main()\n" |
| "{\n" |
| " gl_Position = gl_in[0].gl_Position;\n" |
| " user_out.value = vec4(3.0, 4.0, 5.0, 6.0);\n" |
| "}\n"; |
| const GLchar *kVaryingName = "BLOCK_INOUT.value"; |
| |
| GLuint fsProgram = createShaderProgram(GL_FRAGMENT_SHADER, kFS); |
| ASSERT_NE(0u, fsProgram); |
| |
| GLuint vsProgram = createShaderProgram(GL_VERTEX_SHADER, kVS, 1, &kVaryingName); |
| ASSERT_NE(0u, vsProgram); |
| |
| GLuint gsProgram = 0u; |
| if (IsGLExtensionEnabled("GL_EXT_geometry_shader")) |
| { |
| gsProgram = createShaderProgram(GL_GEOMETRY_SHADER, kGS, 1, &kVaryingName); |
| ASSERT_NE(0u, gsProgram); |
| } |
| |
| GLuint tcsProgram = createShaderProgram(GL_TESS_CONTROL_SHADER, kTCS, 1, &kVaryingName); |
| // Should fail here. |
| ASSERT_EQ(0u, tcsProgram); |
| |
| // try compiling without transform feedback varying it should pass |
| tcsProgram = createShaderProgram(GL_TESS_CONTROL_SHADER, kTCS); |
| ASSERT_NE(0u, tcsProgram); |
| |
| GLuint tesProgram = createShaderProgram(GL_TESS_EVALUATION_SHADER, kTES, 1, &kVaryingName); |
| ASSERT_NE(0u, tesProgram); |
| |
| glDeleteProgram(fsProgram); |
| glDeleteProgram(vsProgram); |
| if (gsProgram != 0u) |
| { |
| glDeleteProgram(gsProgram); |
| } |
| glDeleteProgram(tcsProgram); |
| glDeleteProgram(tesProgram); |
| } |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineTest); |
| ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineTest31); |
| ANGLE_INSTANTIATE_TEST_ES31(ProgramPipelineTest31); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineXFBTest31); |
| ANGLE_INSTANTIATE_TEST_ES31(ProgramPipelineXFBTest31); |
| |
| GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ProgramPipelineTest32); |
| ANGLE_INSTANTIATE_TEST_ES32(ProgramPipelineTest32); |
| |
| } // namespace |