//
// Copyright 2022 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.
//

#include "test_utils/ANGLETest.h"

#include <vector>

#include "GLSLANG/ShaderLang.h"
#include "test_utils/gl_raii.h"

using namespace angle;

class ShaderBinaryTest : public ANGLETest<>
{
  protected:
    ShaderBinaryTest()
    {
        setWindowWidth(128);
        setWindowHeight(128);
        setConfigRedBits(8);
        setConfigGreenBits(8);
        setConfigBlueBits(8);
        setConfigAlphaBits(8);

        // Test flakiness was noticed when reusing displays.
        forceNewDisplay();
    }

    void testSetUp() override
    {
        ASSERT_EQ(sh::Initialize(), true);

        if (!supported())
        {
            // Must return early because the initialization below will crash otherwise.
            // Individal tests will skip themselves as well.
            return;
        }

        mCompileOptions.objectCode                    = true;
        mCompileOptions.emulateGLDrawID               = true;
        mCompileOptions.initializeUninitializedLocals = true;

        sh::InitBuiltInResources(&mResources);

        // Generate a shader binary:
        ShShaderSpec spec     = SH_GLES2_SPEC;
        ShShaderOutput output = SH_SPIRV_VULKAN_OUTPUT;

        // Vertex shader:
        const char *source = essl1_shaders::vs::Simple();
        ShHandle vertexCompiler =
            sh::ConstructCompiler(GL_VERTEX_SHADER, spec, output, &mResources);
        bool compileResult =
            sh::GetShaderBinary(vertexCompiler, &source, 1, mCompileOptions, &mVertexShaderBinary);
        ASSERT_TRUE(compileResult);

        if (mVertexShaderBinary.size() == 0)
        {
            FAIL() << "Creating vertex shader binary failed.";
        }

        // Fragment shader:
        source = essl1_shaders::fs::Red();
        ShHandle fragmentCompiler =
            sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &mResources);
        compileResult = sh::GetShaderBinary(fragmentCompiler, &source, 1, mCompileOptions,
                                            &mFragmentShaderBinary);
        ASSERT_TRUE(compileResult);

        if (mFragmentShaderBinary.size() == 0)
        {
            FAIL() << "Creating fragment shader binary failed.";
        }
    }

    void testTearDown() override
    {
        sh::Finalize();

        if (!supported())
        {
            // Return early because the initialization didn't complete.
            return;
        }

        glDeleteBuffers(1, &mBuffer);
    }

    bool supported() const
    {
        // The system driver compiler version is not expected to match the version compiled into
        // this test, causing glShaderBinary() to return GL_INVALID_VALUE due to the incompatible
        // binaries.
        if (isDriverSystemEgl())
        {
            return false;
        }

        GLint formatCount;
        glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &formatCount);
        if (formatCount == 0)
        {
            std::cout << "Test skipped because no program binary formats are available."
                      << std::endl;
            return false;
        }
        std::vector<GLint> formats(formatCount);
        glGetIntegerv(GL_SHADER_BINARY_FORMATS, formats.data());

        ASSERT(formats[0] == GL_SHADER_BINARY_ANGLE);

        return true;
    }

    ShCompileOptions mCompileOptions = {};
    ShBuiltInResources mResources;
    GLuint mBuffer;
    sh::ShaderBinaryBlob mVertexShaderBinary;
    sh::ShaderBinaryBlob mFragmentShaderBinary;
};

// This tests the ability to successfully create and load a shader binary.
TEST_P(ShaderBinaryTest, CreateAndLoadBinary)
{
    ANGLE_SKIP_TEST_IF(!supported());

    GLint compileResult;
    // Create vertex shader and load binary
    GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderBinary(1, &vertShader, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    // Create fragment shader and load binary
    GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderBinary(1, &fragShader, GL_SHADER_BINARY_ANGLE, mFragmentShaderBinary.data(),
                   mFragmentShaderBinary.size());
    glGetShaderiv(fragShader, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    // Create program from the shaders
    GLuint newProgram = glCreateProgram();
    glAttachShader(newProgram, vertShader);
    glAttachShader(newProgram, fragShader);
    glLinkProgram(newProgram);
    newProgram = CheckLinkStatusAndReturnProgram(newProgram, true);

    // Test with a basic draw
    drawQuad(newProgram, "a_position", 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Check invalid gl call parameters, such as providing a GL type when a shader handle is expected.
TEST_P(ShaderBinaryTest, InvalidCallParams)
{
    ANGLE_SKIP_TEST_IF(!supported());

    GLuint vertShader[2];
    vertShader[0]     = glCreateShader(GL_VERTEX_SHADER);
    GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);

    // Invalid shader
    vertShader[1] = -1;
    glShaderBinary(1, &vertShader[1], GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    // GL_INVALID_ENUM is generated if binaryFormat is not an accepted value.
    glShaderBinary(1, &vertShader[0], GL_INVALID_ENUM, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    EXPECT_GL_ERROR(GL_INVALID_ENUM);

    // GL_INVALID_VALUE is generated if n or length is negative
    glShaderBinary(-1, &vertShader[0], GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
    glShaderBinary(1, &vertShader[0], GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(), -1);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    // GL_INVALID_OPERATION is generated if any value in shaders is not a shader object.
    GLuint program = glCreateProgram();
    glShaderBinary(1, &program, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // GL_INVALID_OPERATION is generated if more than one of the handles in shaders refers to the
    // same shader object.
    vertShader[1] = vertShader[0];
    glShaderBinary(2, &vertShader[0], GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // GL_INVALID_VALUE is generated if the data pointed to by binary does not match the format
    // specified by binaryFormat.
    std::string invalid("Invalid Shader Blob.");
    glShaderBinary(1, &vertShader[0], GL_SHADER_BINARY_ANGLE, invalid.data(), invalid.size());
    EXPECT_GL_ERROR(GL_INVALID_VALUE);

    // Try loading vertex shader binary into fragment shader
    glShaderBinary(1, &fragShader, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Check attempting to get source code from a shader that was loaded with glShaderBinary.
TEST_P(ShaderBinaryTest, GetSourceFromBinaryShader)
{
    ANGLE_SKIP_TEST_IF(!supported());

    GLint compileResult;
    // Create vertex shader and load binary
    GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderBinary(1, &vertShader, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    GLsizei length = 0;
    glGetShaderSource(vertShader, 0, &length, nullptr);

    EXPECT_EQ(length, 0);
}

// Create a program from both shader source code and a binary blob.
TEST_P(ShaderBinaryTest, CombineSourceAndBinaryShaders)
{
    ANGLE_SKIP_TEST_IF(!supported());

    GLint compileResult;
    // Create vertex shader and load binary
    GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderBinary(1, &vertShader, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    // Create fragment shader
    GLuint fragShader = CompileShader(GL_FRAGMENT_SHADER, essl1_shaders::fs::Red());

    GLuint newProgram = glCreateProgram();
    glAttachShader(newProgram, vertShader);
    glAttachShader(newProgram, fragShader);
    glLinkProgram(newProgram);
    newProgram = CheckLinkStatusAndReturnProgram(newProgram, true);

    // Test with a basic draw
    drawQuad(newProgram, "a_position", 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that shaders loaded with glShaderBinary do not cause false hits in the program cache.
TEST_P(ShaderBinaryTest, ProgramCacheWithShaderBinary)
{
    ANGLE_SKIP_TEST_IF(!supported());
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_get_program_binary"));

    GLint compileResult;
    // Create vertex shader that will be shared between the programs
    GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderBinary(1, &vertShader, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    // Create a program with a red vertex shader
    GLuint fragShaderRed = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderBinary(1, &fragShaderRed, GL_SHADER_BINARY_ANGLE, mFragmentShaderBinary.data(),
                   mFragmentShaderBinary.size());
    glGetShaderiv(fragShaderRed, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    GLuint programRed = glCreateProgram();
    glAttachShader(programRed, vertShader);
    glAttachShader(programRed, fragShaderRed);
    glLinkProgram(programRed);
    programRed = CheckLinkStatusAndReturnProgram(programRed, true);

    // Test with a basic draw
    drawQuad(programRed, "a_position", 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Create a program with a blue fragment shader, also loaded from a binary
    ShShaderSpec spec     = SH_GLES2_SPEC;
    ShShaderOutput output = SH_SPIRV_VULKAN_OUTPUT;

    const char *source = essl1_shaders::fs::Blue();
    sh::ShaderBinaryBlob fragShaderBlueData;
    ShHandle fragmentCompiler =
        sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &mResources);
    bool binaryCompileResult =
        sh::GetShaderBinary(fragmentCompiler, &source, 1, mCompileOptions, &fragShaderBlueData);
    ASSERT_TRUE(binaryCompileResult);
    if (fragShaderBlueData.size() == 0)
    {
        FAIL() << "Creating fragment shader binary failed.";
    }

    GLuint fragShaderBlue = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderBinary(1, &fragShaderBlue, GL_SHADER_BINARY_ANGLE, fragShaderBlueData.data(),
                   fragShaderBlueData.size());
    glGetShaderiv(fragShaderBlue, GL_COMPILE_STATUS, &compileResult);
    ASSERT_GL_TRUE(compileResult);

    GLuint programBlue = glCreateProgram();
    glAttachShader(programBlue, vertShader);
    glAttachShader(programBlue, fragShaderBlue);
    glLinkProgram(programBlue);
    programBlue = CheckLinkStatusAndReturnProgram(programBlue, true);

    // The program cache should miss and create a new program
    drawQuad(programBlue, "a_position", 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
}

class ShaderBinaryTestES31 : public ShaderBinaryTest
{
  protected:
    void testSetUp() override
    {
        ASSERT_EQ(sh::Initialize(), true);

        mCompileOptions.objectCode                    = true;
        mCompileOptions.emulateGLDrawID               = true;
        mCompileOptions.initializeUninitializedLocals = true;

        sh::InitBuiltInResources(&mResources);
        mResources.EXT_geometry_shader     = 1;
        mResources.EXT_tessellation_shader = 1;

        // Generate a shader binary:
        ShShaderSpec spec     = SH_GLES3_1_SPEC;
        ShShaderOutput output = SH_SPIRV_VULKAN_OUTPUT;

        // Vertex shader:
        const char *source = essl31_shaders::vs::Simple();
        ShHandle vertexCompiler =
            sh::ConstructCompiler(GL_VERTEX_SHADER, spec, output, &mResources);
        bool compileResult =
            sh::GetShaderBinary(vertexCompiler, &source, 1, mCompileOptions, &mVertexShaderBinary);
        ASSERT_TRUE(compileResult);

        if (mVertexShaderBinary.size() == 0)
        {
            FAIL() << "Creating vertex shader binary failed.";
        }

        // Fragment shader:
        source = essl31_shaders::fs::Red();
        ShHandle fragmentCompiler =
            sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &mResources);
        compileResult = sh::GetShaderBinary(fragmentCompiler, &source, 1, mCompileOptions,
                                            &mFragmentShaderBinary);
        ASSERT_TRUE(compileResult);

        if (mFragmentShaderBinary.size() == 0)
        {
            FAIL() << "Creating fragment shader binary failed.";
        }
    }
};

// Test all shader stages
TEST_P(ShaderBinaryTestES31, AllShaderStages)
{
    ANGLE_SKIP_TEST_IF(!supported());
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    const char *kGS = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
precision mediump float;

layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;

void main() {
    gl_Position = gl_in[0].gl_Position;
    EmitVertex();

    gl_Position = gl_in[1].gl_Position;
    EmitVertex();

    gl_Position = gl_in[2].gl_Position;
    EmitVertex();

    EndPrimitive();
}
)";

    const char *kTCS = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

void main()
{
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
    gl_TessLevelInner[0] = 1.0;
    gl_TessLevelInner[1] = 1.0;
    gl_TessLevelOuter[0] = 1.0;
    gl_TessLevelOuter[1] = 1.0;
    gl_TessLevelOuter[2] = 1.0;
    gl_TessLevelOuter[3] = 1.0;
}

)";

    const char *kTES = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

void main()
{
    gl_Position = vec4(gl_TessCoord.xy * 2. - 1., 0, 1);
}
)";

    // Generate a shader binary for geo, tcs, tes:
    ShShaderSpec spec                  = SH_GLES3_1_SPEC;
    ShShaderOutput output              = SH_SPIRV_VULKAN_OUTPUT;
    mResources.EXT_geometry_shader     = 1;
    mResources.EXT_tessellation_shader = 1;

    // Geometry shader:
    sh::ShaderBinaryBlob geometryShaderBinary;
    ShHandle geometryCompiler =
        sh::ConstructCompiler(GL_GEOMETRY_SHADER, spec, output, &mResources);
    bool compileResult =
        sh::GetShaderBinary(geometryCompiler, &kGS, 1, mCompileOptions, &geometryShaderBinary);
    ASSERT_TRUE(compileResult);
    if (geometryShaderBinary.size() == 0)
    {
        FAIL() << "Creating geometry shader binary failed.";
    }

    // tesselation control shader:
    sh::ShaderBinaryBlob tessControlShaderBinary;
    ShHandle tessControlCompiler =
        sh::ConstructCompiler(GL_TESS_CONTROL_SHADER, spec, output, &mResources);
    compileResult = sh::GetShaderBinary(tessControlCompiler, &kTCS, 1, mCompileOptions,
                                        &tessControlShaderBinary);
    ASSERT_TRUE(compileResult);
    if (tessControlShaderBinary.size() == 0)
    {
        FAIL() << "Creating tesselation control shader binary failed.";
    }

    // tesselation evaluation shader:
    sh::ShaderBinaryBlob tessEvaluationShaderBinary;
    ShHandle tessEvaluationCompiler =
        sh::ConstructCompiler(GL_TESS_EVALUATION_SHADER, spec, output, &mResources);
    compileResult = sh::GetShaderBinary(tessEvaluationCompiler, &kTES, 1, mCompileOptions,
                                        &tessEvaluationShaderBinary);
    ASSERT_TRUE(compileResult);
    if (tessEvaluationShaderBinary.size() == 0)
    {
        FAIL() << "Creating tesselation evaluation shader binary failed.";
    }

    GLint loadResult;
    // Create vertex shader and load binary
    GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderBinary(1, &vertShader, GL_SHADER_BINARY_ANGLE, mVertexShaderBinary.data(),
                   mVertexShaderBinary.size());
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create geometry shader and load binary
    GLuint geoShader = glCreateShader(GL_GEOMETRY_SHADER);
    glShaderBinary(1, &geoShader, GL_SHADER_BINARY_ANGLE, geometryShaderBinary.data(),
                   geometryShaderBinary.size());
    glGetShaderiv(geoShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create tesselation control shader and load binary
    GLuint tcShader = glCreateShader(GL_TESS_CONTROL_SHADER);
    glShaderBinary(1, &tcShader, GL_SHADER_BINARY_ANGLE, tessControlShaderBinary.data(),
                   tessControlShaderBinary.size());
    glGetShaderiv(tcShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create tesselation evaluation and load binary
    GLuint teShader = glCreateShader(GL_TESS_EVALUATION_SHADER);
    glShaderBinary(1, &teShader, GL_SHADER_BINARY_ANGLE, tessEvaluationShaderBinary.data(),
                   tessEvaluationShaderBinary.size());
    glGetShaderiv(teShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create fragment shader and load binary
    GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderBinary(1, &fragShader, GL_SHADER_BINARY_ANGLE, mFragmentShaderBinary.data(),
                   mFragmentShaderBinary.size());
    glGetShaderiv(fragShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create program from the shaders
    GLuint newProgram = glCreateProgram();
    glAttachShader(newProgram, vertShader);
    glAttachShader(newProgram, geoShader);
    glAttachShader(newProgram, tcShader);
    glAttachShader(newProgram, teShader);
    glAttachShader(newProgram, fragShader);
    glLinkProgram(newProgram);
    newProgram = CheckLinkStatusAndReturnProgram(newProgram, true);

    // Test with a basic draw
    drawPatches(newProgram, "a_position", 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test glShaderBinary with complex shaders
TEST_P(ShaderBinaryTestES31, ComplexShader)
{
    ANGLE_SKIP_TEST_IF(!supported());

    const char *kVertexShader = R"(#version 310 es
uniform vec2 table[4];

in vec2 position;
in vec4 aTest;

out vec2 texCoord;
out vec4 vTest;

void main()
{
    gl_Position = vec4(position + table[gl_InstanceID], 0, 1);
    vTest       = aTest;
    texCoord    = gl_Position.xy * 0.5 + vec2(0.5);
})";

    const char *kFragmentShader = R"(#version 310 es
precision mediump float;

struct S { sampler2D sampler; };
uniform S uStruct;

layout (binding = 0, std430) buffer Input {
    float sampledInput;
};

in vec2 texCoord;
in vec4 vTest;
out vec4 my_FragColor;

void main()
{
    if (sampledInput == 1.0)
    {
        my_FragColor = texture(uStruct.sampler, texCoord);
    }
    else
    {
        my_FragColor = vTest;
    }
})";

    // Generate shader binaries:
    ShShaderSpec spec     = SH_GLES3_1_SPEC;
    ShShaderOutput output = SH_SPIRV_VULKAN_OUTPUT;

    // Vertex shader:
    sh::ShaderBinaryBlob vertexShaderBinary;
    ShHandle vertexCompiler = sh::ConstructCompiler(GL_VERTEX_SHADER, spec, output, &mResources);
    bool compileResult = sh::GetShaderBinary(vertexCompiler, &kVertexShader, 1, mCompileOptions,
                                             &vertexShaderBinary);
    ASSERT_TRUE(compileResult);

    if (vertexShaderBinary.size() == 0)
    {
        FAIL() << "Creating vertex shader binary failed.";
    }

    // Fragment shader:
    sh::ShaderBinaryBlob fragmentShaderBinary;
    ShHandle fragmentCompiler =
        sh::ConstructCompiler(GL_FRAGMENT_SHADER, spec, output, &mResources);
    compileResult = sh::GetShaderBinary(fragmentCompiler, &kFragmentShader, 1, mCompileOptions,
                                        &fragmentShaderBinary);
    ASSERT_TRUE(compileResult);

    if (fragmentShaderBinary.size() == 0)
    {
        FAIL() << "Creating fragment shader binary failed.";
    }

    GLint loadResult;
    // Create vertex shader and load binary
    GLuint vertShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderBinary(1, &vertShader, GL_SHADER_BINARY_ANGLE, vertexShaderBinary.data(),
                   vertexShaderBinary.size());
    glGetShaderiv(vertShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create fragment shader and load binary
    GLuint fragShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderBinary(1, &fragShader, GL_SHADER_BINARY_ANGLE, fragmentShaderBinary.data(),
                   fragmentShaderBinary.size());
    glGetShaderiv(fragShader, GL_COMPILE_STATUS, &loadResult);
    ASSERT_GL_TRUE(loadResult);

    // Create program from the shaders
    GLuint newProgram = glCreateProgram();
    glAttachShader(newProgram, vertShader);
    glAttachShader(newProgram, fragShader);
    glLinkProgram(newProgram);
    newProgram = CheckLinkStatusAndReturnProgram(newProgram, true);
    glUseProgram(newProgram);
    ASSERT_GL_NO_ERROR();

    // Setup instance offset table
    constexpr GLfloat table[] = {-1, -1, -1, 1, 1, -1, 1, 1};
    GLint tableMemberLoc      = glGetUniformLocation(newProgram, "table");
    ASSERT_NE(-1, tableMemberLoc);
    glUniform2fv(tableMemberLoc, 4, table);
    ASSERT_GL_NO_ERROR();

    // Setup red testure and sampler uniform
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {255u, 0u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);

    GLint samplerMemberLoc = glGetUniformLocation(newProgram, "uStruct.sampler");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    ASSERT_GL_NO_ERROR();

    // Setup the `aTest` attribute to blue
    std::vector<Vector4> kInputAttribute(6, Vector4(0.0f, 0.0f, 1.0f, 1.0f));
    GLint positionLocation = glGetAttribLocation(newProgram, "aTest");
    glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, kInputAttribute.data());
    glEnableVertexAttribArray(positionLocation);

    // Setup 'sampledInput' storage buffer to 1
    constexpr GLfloat kInputDataOne = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputDataOne, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    // Test sampling texture with an instanced draw
    drawQuadInstanced(newProgram, "position", 0.5f, 0.5f, GL_FALSE, 4);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Setup 'sampledInput' storage buffer to 0
    constexpr GLfloat kInputDataZero = 0.0f;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputDataZero, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    // Test color attribute with an instanced draw
    drawQuadInstanced(newProgram, "position", 0.5f, 0.5f, GL_FALSE, 4);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
}

// Use this to select which configurations (e.g. which renderer, which GLES major version) these
// tests should be run against.
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31(ShaderBinaryTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(ShaderBinaryTestES31);
ANGLE_INSTANTIATE_TEST_ES31(ShaderBinaryTestES31);
