Edit

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

Branch :

  • Show log

    Commit

  • Author : Tim Van Patten
    Date : 2020-09-21 13:25:46
    Hash : 001c7e8c
    Message : Vulkan: Link PPO during draw validation From the OpenGL ES 3.1 spec: 11.1.3.11 Validation It is not always possible to determine at link time if a program object can execute successfully, given that LinkProgram can not know the state of the remainder of the pipeline. Therefore validation is done when the first rendering command which triggers shader invocations is issued, to determine if the set of active program objects can be executed. For draws, this CL moves the PPO link operation to ValidateDrawStates() to generate PPO link failures within ANGLE's validation layer, so we fail any rendering commands during command validation. For dispatch, PPOs are linked during Context::prepareForDispatch(), where the PPO is converted from draw to compute, since that conversion requires a re-link. This re-link shouldn't fail due to errors that would have been caught during validation, since the compute shader must have successfully linked before it can be included in the PPO in the first place. We don't re-link when converting back to draw, since it's possible there are validation errors (which we want to catch during validation of the next rendering command). Bug: angleproject:5064 Test: dEQP.GLES31/functional_separate_shader_validation_es31_* Test: ContextNoErrorTest31.DrawWithPPO Test: ProgramPipelineTest31.VerifyPpoLinkErrorSignalledCorrectly Change-Id: Ibb249e893c007a83cc6b813f848a660bfa34ecb0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2422375 Commit-Queue: Tim Van Patten <timvp@google.com> Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Ian Elliott <ianelliott@google.com>

  • src/tests/gl_tests/ProgramPipelineTest.cpp
  • //
    // 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();
        }
    }
    
    class ProgramPipelineTest31 : public ProgramPipelineTest
    {
      protected:
        ~ProgramPipelineTest31()
        {
            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);
    
        GLuint mVertProg;
        GLuint mFragProg;
        GLuint mPipeline;
    };
    
    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();
    }
    
    // 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)
    {
        GLShader shader(type);
        if (!shader.get())
        {
            return 0;
        }
    
        glShaderSource(shader, 1, &shaderString, nullptr);
        glCompileShader(shader);
    
        GLuint program = glCreateProgram();
    
        if (program)
        {
            GLint compiled;
            glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
            glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
            if (compiled)
            {
                glAttachShader(program, shader);
                glLinkProgram(program);
                glDetachShader(program, shader);
            }
        }
    
        EXPECT_GL_NO_ERROR();
    
        return program;
    }
    
    void ProgramPipelineTest31::drawQuadWithPPO(const std::string &positionAttribName,
                                                const GLfloat positionAttribZ,
                                                const GLfloat positionAttribXYScale)
    {
        glUseProgram(0);
    
        std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
    
        for (Vector3 &vertex : quadVertices)
        {
            vertex.x() *= positionAttribXYScale;
            vertex.y() *= positionAttribXYScale;
            vertex.z() = positionAttribZ;
        }
    
        GLint positionLocation = glGetAttribLocation(mVertProg, positionAttribName.c_str());
    
        glBindBuffer(GL_ARRAY_BUFFER, 0);
        glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
        glEnableVertexAttribArray(positionLocation);
    
        glDrawArrays(GL_TRIANGLES, 0, 6);
    
        glDisableVertexAttribArray(positionLocation);
        glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
    }
    
    // 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("a_position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    
    // 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("a_position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    }
    
    // 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("a_position", 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("a_position", 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("a_position", 0.5f, 1.0f);
        ASSERT_GL_NO_ERROR();
        EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    
        glDeleteProgram(mVertProg);
        glDeleteProgram(mFragProg);
    }
    
    // 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("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::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());
    
        // TODO (timvp): Fix this test for Vulkan with PPO
        // http://anglebug.com/3570
        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("a_position", 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("a_position", 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());
    
        // TODO (timvp): Fix this test for Vulkan with PPO
        // http://anglebug.com/3570
        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.get(), 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);
    }
    
    ANGLE_INSTANTIATE_TEST_ES3_AND_ES31(ProgramPipelineTest);
    ANGLE_INSTANTIATE_TEST_ES31(ProgramPipelineTest31);
    
    }  // namespace