//
// 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.
//
// LinkAndRelinkFailureTest:
//   Link and relink failure tests for rendering pipeline and compute pipeline.

#include <vector>
#include "test_utils/ANGLETest.h"
#include "test_utils/gl_raii.h"

using namespace angle;

namespace
{

class LinkAndRelinkTest : public ANGLETest<>
{
  protected:
    LinkAndRelinkTest() {}
};

class LinkAndRelinkTestES3 : public ANGLETest<>
{
  protected:
    LinkAndRelinkTestES3() {}
};

class LinkAndRelinkTestES31 : public ANGLETest<>
{
  protected:
    LinkAndRelinkTestES31() {}
};

// Test destruction of a context with a pending relink of the current in-use
// program.
TEST_P(LinkAndRelinkTest, DestructionWithPendingRelink)
{
    constexpr char kVS[] = "void main() {}";
    constexpr char kFS[] = "void main() {}";

    GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);

    GLuint program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    glLinkProgram(program);
    glUseProgram(program);
    glLinkProgram(program);
    EXPECT_GL_NO_ERROR();
}

// When a program link or relink fails, if you try to install the unsuccessfully
// linked program (via UseProgram) and start rendering or dispatch compute,
// We can not always report INVALID_OPERATION for rendering/compute pipeline.
// The result depends on the previous state: Whether a valid program is
// installed in current GL state before the link.
// If a program successfully relinks when it is in use, the program might
// change from a rendering program to a compute program in theory,
// or vice versa.

// When program link fails and no valid rendering program is installed in the GL
// state before the link, it should report an error for UseProgram
TEST_P(LinkAndRelinkTest, RenderingProgramFailsWithoutProgramInstalled)
{
    glUseProgram(0);
    GLuint program = glCreateProgram();

    glLinkProgram(program);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    glUseProgram(program);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();
}

// When program link or relink fails and a valid rendering program is installed
// in the GL state before the link, using the failed program via UseProgram
// should report an error, but starting rendering should succeed.
// However, dispatching compute always fails.
TEST_P(LinkAndRelinkTest, RenderingProgramFailsWithProgramInstalled)
{
    // Install a render program in current GL state via UseProgram, then render.
    // It should succeed.
    constexpr char kVS[] = "void main() {}";
    constexpr char kFS[] = "void main() {}";

    GLuint program = glCreateProgram();

    GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);

    glAttachShader(program, vs);
    glDeleteShader(vs);

    glAttachShader(program, fs);
    glDeleteShader(fs);

    glLinkProgram(program);

    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    EXPECT_GL_NO_ERROR();
    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Link failure, and a valid program has been installed in the GL state.
    GLuint programNull = glCreateProgram();

    glLinkProgram(programNull);
    glGetProgramiv(programNull, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    // Starting rendering should succeed.
    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Using the unsuccessfully linked program should report an error.
    glUseProgram(programNull);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Using the unsuccessfully linked program, that program should not
    // replace the program binary residing in the GL state. It will not make
    // the installed program invalid either, like what UseProgram(0) can do.
    // So, starting rendering should succeed.
    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // We try to relink the installed program, but make it fail.

    // No vertex shader, relink fails.
    glDetachShader(program, vs);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);
    EXPECT_GL_NO_ERROR();

    // Starting rendering should succeed.
    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Using the unsuccessfully relinked program should report an error.
    glUseProgram(program);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Using the unsuccessfully relinked program, that program should not
    // replace the program binary residing in the GL state. It will not make
    // the installed program invalid either, like what UseProgram(0) can do.
    // So, starting rendering should succeed.
    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Tests uniform default values.
TEST_P(LinkAndRelinkTest, UniformDefaultValues)
{
    // TODO(anglebug.com/42262609): Understand why rectangle texture CLs made this fail.
    ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel());
    constexpr char kFS[] = R"(precision mediump float;
uniform vec4 u_uniform;

bool isZero(vec4 value) {
    return value == vec4(0,0,0,0);
}

void main()
{
    gl_FragColor = isZero(u_uniform) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    GLint loc = glGetUniformLocation(program, "u_uniform");
    ASSERT_NE(-1, loc);
    glUniform4f(loc, 0.1f, 0.2f, 0.3f, 0.4f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glLinkProgram(program);
    ASSERT_GL_NO_ERROR();

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// When program link fails and no valid compute program is installed in the GL
// state before the link, it should report an error for UseProgram and
// DispatchCompute.
TEST_P(LinkAndRelinkTestES31, ComputeProgramFailsWithoutProgramInstalled)
{
    glUseProgram(0);
    GLuint program = glCreateProgram();

    glLinkProgram(program);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    glUseProgram(program);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// When program link or relink fails and a valid compute program is installed in
// the GL state before the link, using the failed program via UseProgram should
// report an error, but dispatching compute should succeed.
TEST_P(LinkAndRelinkTestES31, ComputeProgramFailsWithProgramInstalled)
{
    // Install a compute program in the GL state via UseProgram, then dispatch
    // compute. It should succeed.
    constexpr char kCS[] =
        R"(#version 310 es
        layout(local_size_x=1) in;
        void main()
        {
        })";

    GLuint program = glCreateProgram();

    GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
    EXPECT_NE(0u, cs);

    glAttachShader(program, cs);
    glDeleteShader(cs);

    glLinkProgram(program);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    EXPECT_GL_NO_ERROR();
    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    // Link failure, and a valid program has been installed in the GL state.
    GLuint programNull = glCreateProgram();

    glLinkProgram(programNull);
    glGetProgramiv(programNull, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    // Dispatching compute should succeed.
    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    // Using the unsuccessfully linked program should report an error.
    glUseProgram(programNull);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Using the unsuccessfully linked program, that program should not
    // replace the program binary residing in the GL state. It will not make
    // the installed program invalid either, like what UseProgram(0) can do.
    // So, dispatching compute should succeed.
    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    // We try to relink the installed program, but make it fail.

    // No compute shader, relink fails.
    glDetachShader(program, cs);
    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);
    EXPECT_GL_NO_ERROR();

    // Dispatching compute should succeed.
    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    // Using the unsuccessfully relinked program should report an error.
    glUseProgram(program);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Using the unsuccessfully relinked program, that program should not
    // replace the program binary residing in the GL state. It will not make
    // the installed program invalid either, like what UseProgram(0) can do.
    // So, dispatching compute should succeed.
    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();
}

// If you compile and link a compute program successfully and use the program,
// then dispatching compute and rendering can succeed (with undefined behavior).
// If you relink the compute program to a rendering program when it is in use,
// then dispatching compute will fail, but starting rendering can succeed.
TEST_P(LinkAndRelinkTestES31, RelinkProgramSucceedsFromComputeToRendering)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";

    GLuint program = glCreateProgram();

    GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
    EXPECT_NE(0u, cs);

    glAttachShader(program, cs);
    glDeleteShader(cs);

    glLinkProgram(program);
    glDetachShader(program, cs);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    EXPECT_GL_NO_ERROR();
    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    constexpr char kVS[] = "void main() {}";
    constexpr char kFS[] = "void main() {}";

    GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);

    glAttachShader(program, vs);
    glDeleteShader(vs);

    glAttachShader(program, fs);
    glDeleteShader(fs);

    glLinkProgram(program);
    glDetachShader(program, vs);
    glDetachShader(program, fs);
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// If you compile and link a rendering program successfully and use the program,
// then starting rendering can succeed, while dispatching compute will fail.
// If you relink the rendering program to a compute program when it is in use,
// then starting rendering will fail, but dispatching compute can succeed.
TEST_P(LinkAndRelinkTestES31, RelinkProgramSucceedsFromRenderingToCompute)
{
    // http://anglebug.com/42263641
    ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());

    constexpr char kVS[] = "void main() {}";
    constexpr char kFS[] = "void main() {}";

    GLuint program = glCreateProgram();

    GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);

    glAttachShader(program, vs);
    glDeleteShader(vs);

    glAttachShader(program, fs);
    glDeleteShader(fs);

    glLinkProgram(program);
    glDetachShader(program, vs);
    glDetachShader(program, fs);
    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    EXPECT_GL_NO_ERROR();
    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
void main()
{
})";

    GLuint cs = CompileShader(GL_COMPUTE_SHADER, kCS);
    EXPECT_NE(0u, cs);

    glAttachShader(program, cs);
    glDeleteShader(cs);

    glLinkProgram(program);
    glDetachShader(program, cs);
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    glDispatchCompute(8, 4, 2);
    EXPECT_GL_NO_ERROR();

    glDrawArrays(GL_POINTS, 0, 1);
    EXPECT_GL_NO_ERROR();
}

// Parallel link should continue unscathed even if the attached shaders to the program are modified.
TEST_P(LinkAndRelinkTestES31, ReattachShadersWhileParallelLinking)
{
    constexpr char kVS[]      = R"(#version 300 es
void main()
{
    vec2 position = vec2(-1, -1);
    if (gl_VertexID == 1)
        position = vec2(3, -1);
    else if (gl_VertexID == 2)
        position = vec2(-1, 3);
    gl_Position = vec4(position, 0, 1);
})";
    constexpr char kFSGreen[] = R"(#version 300 es
out mediump vec4 color;
void main()
{
    color = vec4(0, 1, 0, 1);
})";
    constexpr char kFSRed[]   = R"(#version 300 es
out mediump vec4 color;
void main()
{
    color = vec4(1, 0, 0, 1);
})";

    GLuint program = glCreateProgram();

    GLuint vs    = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint green = CompileShader(GL_FRAGMENT_SHADER, kFSGreen);
    GLuint red   = CompileShader(GL_FRAGMENT_SHADER, kFSRed);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, green);
    EXPECT_NE(0u, red);

    glAttachShader(program, vs);
    glAttachShader(program, green);
    glLinkProgram(program);
    ASSERT_GL_NO_ERROR();

    // Immediately reattach another shader
    glDetachShader(program, green);
    glAttachShader(program, red);
    ASSERT_GL_NO_ERROR();

    // Make sure the linked program draws with green
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();

    glDeleteShader(vs);
    glDeleteShader(green);
    glDeleteShader(red);
    ASSERT_GL_NO_ERROR();
}

// Parallel link should continue unscathed even if new shaders are attached to the program.
TEST_P(LinkAndRelinkTestES31, AttachNewShadersWhileParallelLinking)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
void main()
{
    vec2 position = vec2(-1, -1);
    if (gl_VertexID == 1)
        position = vec2(3, -1);
    else if (gl_VertexID == 2)
        position = vec2(-1, 3);
    gl_Position = vec4(position, 0, 1);
})";
    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
out mediump vec4 color;
void main()
{
    color = vec4(0, 1, 0, 1);
})";
    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (invocations = 3, triangles) in;
layout (triangle_strip, max_vertices = 3) out;
void main()
{
})";

    GLuint program = glCreateProgram();

    GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
    GLuint gs = CompileShader(GL_GEOMETRY_SHADER, kGS);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);
    EXPECT_NE(0u, gs);

    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);
    ASSERT_GL_NO_ERROR();

    // Immediately attach another shader
    glAttachShader(program, gs);
    ASSERT_GL_NO_ERROR();

    // Make sure the linked program draws with green
    glUseProgram(program);
    ASSERT_GL_NO_ERROR();

    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();

    glDeleteShader(vs);
    glDeleteShader(fs);
    glDeleteShader(gs);
    ASSERT_GL_NO_ERROR();
}

// Make sure the shader can be compiled in between attach and link
TEST_P(LinkAndRelinkTest, AttachShaderThenCompile)
{
    GLuint program = glCreateProgram();

    GLShader vs(GL_VERTEX_SHADER);
    GLShader fs(GL_FRAGMENT_SHADER);

    // Attach the shaders to the program first.  This makes sure the program doesn't prematurely
    // attempt to look into the shader's compilation result.
    glAttachShader(program, vs);
    glAttachShader(program, fs);

    // Compile the shaders after that.
    const char *kVS = essl1_shaders::vs::Simple();
    const char *kFS = essl1_shaders::fs::Green();
    glShaderSource(vs, 1, &kVS, nullptr);
    glShaderSource(fs, 1, &kFS, nullptr);
    EXPECT_GL_NO_ERROR();

    glCompileShader(vs);
    glCompileShader(fs);

    // Then link
    glLinkProgram(program);
    ASSERT_GL_NO_ERROR();

    // Make sure it works
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();

    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();
}

// If a program is linked successfully once, it should retain its executable if a relink fails.
TEST_P(LinkAndRelinkTestES3, SuccessfulLinkThenFailingRelink)
{
    // Install a render program in current GL state via UseProgram, then render.
    // It should succeed.
    constexpr char kVS[]    = R"(#version 300 es
out vec4 color;
void main()
{
    vec2 position = vec2(-1, -1);
    if (gl_VertexID == 1)
        position = vec2(3, -1);
    else if (gl_VertexID == 2)
        position = vec2(-1, 3);

    gl_Position = vec4(position, 0, 1);
    color = vec4(0, 1, 0, 1);
})";
    constexpr char kBadFS[] = R"(#version 300 es
flat in uvec2 color;
out mediump vec4 colorOut;
void main()
{
    colorOut = vec4(1, color, 1);
})";

    GLuint program = glCreateProgram();
    GLuint vs      = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs      = CompileShader(GL_FRAGMENT_SHADER, essl3_shaders::fs::Green());
    GLuint badfs   = CompileShader(GL_FRAGMENT_SHADER, kBadFS);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);
    EXPECT_NE(0u, badfs);

    glAttachShader(program, vs);
    glAttachShader(program, fs);

    glLinkProgram(program);

    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    const int w = getWindowWidth();
    const int h = getWindowHeight();

    glViewport(0, 0, w, h);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, w / 2, h / 2);

    glUseProgram(program);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_GL_NO_ERROR();

    // Cause the program to fail linking
    glDetachShader(program, fs);
    glAttachShader(program, badfs);

    glLinkProgram(program);

    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    // Link failed, but the program should still be usable.
    glScissor(w / 2, h / 2, w / 2, h / 2);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_RECT_EQ(0, 0, w / 2, h / 2, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(w / 2, 0, w / 2, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, h / 2, w / 2, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(w / 2, h / 2, w / 2, h / 2, GLColor::green);

    glDeleteShader(vs);
    glDeleteShader(fs);
    glDeleteShader(badfs);
    glDeleteProgram(program);
}

// Similar to SuccessfulLinkThenFailingRelink, but with a more complicated mix of resources.
TEST_P(LinkAndRelinkTestES31, SuccessfulLinkThenFailingRelink2)
{
    GLint maxFragmentShaderStorageBlocks;
    glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks);

    ANGLE_SKIP_TEST_IF(maxFragmentShaderStorageBlocks < 1);

    // Install a render program in current GL state via UseProgram, then render.
    // It should succeed.
    constexpr char kVS[]     = R"(#version 310 es
out vec4 color;
void main()
{
    vec2 position = vec2(-1, -1);
    if (gl_VertexID == 1)
        position = vec2(3, -1);
    else if (gl_VertexID == 2)
        position = vec2(-1, 3);

    gl_Position = vec4(position, 0, 1);
    color = vec4(0, 1, 0, 1);
})";
    constexpr char kGoodFS[] = R"(#version 310 es
precision highp int;
in mediump vec4 color;
out mediump vec4 colorOut;
mediump uniform float u;  // should be 0.5;
uniform UBO
{
    highp float b;  // should be 1.75
};
layout(std140, binding = 1) buffer SSBO
{
    uint s;  // should be 0x12345678
};
void main()
{
    if (abs(u - 0.5) > 0.01)
        colorOut = vec4(1, 0, 0, 1);
    else if (abs(b - 1.75) > 0.01)
        colorOut = vec4(0, 0, 1, 1);
    else if (s != 0x12345678u)
        colorOut = vec4(1, 0, 1, 1);
    else
        colorOut = color;
})";
    constexpr char kBadFS[]  = R"(#version 310 es
flat in uvec2 color;
layout(location = 0) out mediump vec4 colorOut;
layout(location = 1) out mediump vec4 colorOut2;
void main()
{
    colorOut = vec4(1, color, 1);
    colorOut2 = vec4(color, 0, 1);
})";

    GLuint program = glCreateProgram();
    GLuint vs      = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs      = CompileShader(GL_FRAGMENT_SHADER, kGoodFS);
    GLuint badfs   = CompileShader(GL_FRAGMENT_SHADER, kBadFS);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);
    EXPECT_NE(0u, badfs);

    glAttachShader(program, vs);
    glAttachShader(program, fs);

    glLinkProgram(program);

    GLint linkStatus;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_TRUE(linkStatus);

    EXPECT_GL_NO_ERROR();

    constexpr float kUBOValue = 1.75;
    GLBuffer ubo;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(kUBOValue), &kUBOValue, GL_STATIC_DRAW);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);

    constexpr uint32_t kSSBOValue = 0x12345678;
    GLBuffer ssbo;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kSSBOValue), &kSSBOValue, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, ssbo);

    EXPECT_GL_NO_ERROR();

    const int w = getWindowWidth();
    const int h = getWindowHeight();

    glViewport(0, 0, w, h);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, w / 2, h / 2);

    glUseProgram(program);

    const GLint uniLoc = glGetUniformLocation(program, "u");
    ASSERT_NE(uniLoc, -1);
    glUniform1f(uniLoc, 0.5);

    const GLint uboIndex = glGetUniformBlockIndex(program, "UBO");
    ASSERT_NE(uboIndex, -1);
    glUniformBlockBinding(program, uboIndex, 0);

    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_GL_NO_ERROR();

    // Cause the program to fail linking
    glDetachShader(program, fs);
    glAttachShader(program, badfs);

    glLinkProgram(program);

    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    // Link failed, but the program should still be usable.
    glScissor(w / 2, h / 2, w / 2, h / 2);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_RECT_EQ(0, 0, w / 2, h / 2, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(w / 2, 0, w / 2, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, h / 2, w / 2, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(w / 2, h / 2, w / 2, h / 2, GLColor::green);

    glDeleteShader(vs);
    glDeleteShader(fs);
    glDeleteShader(badfs);
    glDeleteProgram(program);
}

// Same as SuccessfulLinkThenFailingRelink, but with PPOs.
TEST_P(LinkAndRelinkTestES31, SuccessfulLinkThenFailingRelinkWithPPO)
{
    // Only the Vulkan backend supports PPOs.
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));

    // Install a render program in current GL state via UseProgram, then render.
    // It should succeed.
    constexpr char kVS[]    = R"(#version 300 es
void main()
{
    vec2 position = vec2(-1, -1);
    if (gl_VertexID == 1)
        position = vec2(3, -1);
    else if (gl_VertexID == 2)
        position = vec2(-1, 3);

    gl_Position = vec4(position, 0, 1);
})";
    constexpr char kBadGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (invocations = 3, triangles) in;
layout (triangle_strip, max_vertices = 3) out;
void main()
{
})";

    GLuint vs    = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint fs    = CompileShader(GL_FRAGMENT_SHADER, essl3_shaders::fs::Green());
    GLuint badgs = CompileShader(GL_GEOMETRY_SHADER, kBadGS);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs);
    EXPECT_NE(0u, badgs);

    GLuint vsProg = glCreateProgram();
    GLuint fsProg = glCreateProgram();

    glProgramParameteri(vsProg, GL_PROGRAM_SEPARABLE, GL_TRUE);
    glAttachShader(vsProg, vs);
    glLinkProgram(vsProg);
    EXPECT_GL_NO_ERROR();

    glProgramParameteri(fsProg, GL_PROGRAM_SEPARABLE, GL_TRUE);
    glAttachShader(fsProg, fs);
    glLinkProgram(fsProg);
    EXPECT_GL_NO_ERROR();

    GLuint pipeline;
    glGenProgramPipelines(1, &pipeline);
    glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vsProg);
    glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fsProg);
    EXPECT_GL_NO_ERROR();

    const int w = getWindowWidth();
    const int h = getWindowHeight();

    glViewport(0, 0, w, h);

    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);

    glEnable(GL_SCISSOR_TEST);
    glScissor(0, 0, w / 2, h / 2);

    glUseProgram(0);
    glBindProgramPipeline(pipeline);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_GL_NO_ERROR();

    // Cause the fs program to fail linking
    glAttachShader(fsProg, badgs);
    glLinkProgram(fsProg);
    glUseProgramStages(pipeline, GL_GEOMETRY_SHADER_BIT, fsProg);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    GLint linkStatus;
    glGetProgramiv(fsProg, GL_LINK_STATUS, &linkStatus);
    EXPECT_GL_FALSE(linkStatus);

    // Program link failed, but the program pipeline should still be usable.
    glScissor(w / 2, h / 2, w / 2, h / 2);
    glDrawArrays(GL_TRIANGLES, 0, 3);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_RECT_EQ(0, 0, w / 2, h / 2, GLColor::green);
    EXPECT_PIXEL_RECT_EQ(w / 2, 0, w / 2, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(0, h / 2, w / 2, h / 2, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(w / 2, h / 2, w / 2, h / 2, GLColor::green);

    glDeleteShader(vs);
    glDeleteShader(fs);
    glDeleteShader(badgs);
    glDeleteProgram(vsProg);
    glDeleteProgram(fsProg);
    glDeleteProgramPipelines(1, &pipeline);
}

// Test relinking a program with a shader that uses fewer textures.
TEST_P(LinkAndRelinkTestES3, RelinkRemovesOneTexture)
{
    // Create a program with two textures, then relink it with a shader that removes one of the
    // textures.
    constexpr char kFS1[] = R"(#version 300 es
precision mediump float;
uniform sampler2D t0;
uniform sampler2D t1;
out vec4 color;
void main()
{
    color = texture(t0, vec2(0)) + texture(t1, vec2(0));
})";
    constexpr char kFS2[] = R"(#version 300 es
precision mediump float;
uniform sampler2D t0;
out vec4 color;
void main()
{
    color = texture(t0, vec2(0));
})";

    GLuint program = glCreateProgram();

    GLuint vs  = CompileShader(GL_VERTEX_SHADER, essl3_shaders::vs::Simple());
    GLuint fs1 = CompileShader(GL_FRAGMENT_SHADER, kFS1);
    GLuint fs2 = CompileShader(GL_FRAGMENT_SHADER, kFS2);

    EXPECT_NE(0u, vs);
    EXPECT_NE(0u, fs1);
    EXPECT_NE(0u, fs2);

    glAttachShader(program, vs);
    glAttachShader(program, fs1);
    glLinkProgram(program);
    glUseProgram(program);
    glUniform1i(glGetUniformLocation(program, "t0"), 0);
    glUniform1i(glGetUniformLocation(program, "t1"), 1);
    ASSERT_GL_NO_ERROR();

    constexpr GLColor kTexture0Color = GLColor(20, 30, 0, 100);
    constexpr GLColor kTexture1Color = GLColor(70, 10, 55, 50);

    GLTexture texture0;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texture0);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &kTexture0Color);

    GLTexture texture1;
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texture1);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &kTexture1Color);

    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE);

    // Draw once, which sums the two textures.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();

    // Mark texture1 dirty so it's tracked for getting synced.
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);

    // Attach the second fragment shader and relink the program
    glDetachShader(program, fs1);
    glAttachShader(program, fs2);
    glLinkProgram(program);
    glUseProgram(program);
    glUniform1i(glGetUniformLocation(program, "t0"), 0);

    // Delete the texture and bind another one in its place.
    texture1.reset();

    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &kTexture1Color);

    // Draw again, which accumulates the color from the first texture.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();

    // Verify results
    constexpr GLColor kExpect(
        kTexture0Color.R * 2 + kTexture1Color.R, kTexture0Color.G * 2 + kTexture1Color.G,
        kTexture0Color.B * 2 + kTexture1Color.B, kTexture0Color.A * 2 + kTexture1Color.A);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, kExpect, 9);

    glDeleteShader(vs);
    glDeleteShader(fs1);
    glDeleteShader(fs2);
    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();
}

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(LinkAndRelinkTest);

ANGLE_INSTANTIATE_TEST_ES3(LinkAndRelinkTestES3);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LinkAndRelinkTestES31);
ANGLE_INSTANTIATE_TEST_ES31(LinkAndRelinkTestES31);

}  // namespace
