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

#ifdef UNSAFE_BUFFERS_BUILD
#    pragma allow_unsafe_buffers
#endif

#include "test_utils/ANGLETest.h"

#include "test_utils/angle_test_configs.h"
#include "test_utils/angle_test_instantiate.h"
#include "test_utils/gl_raii.h"
#include "util/gles_loader_autogen.h"
#include "util/shader_utils.h"

#include <array>
#include <cmath>
#include <sstream>

using namespace angle;

namespace
{
// The relative precision factor of mediump float-point variables declared in shaders
// Reference: https://developer.arm.com/documentation/102502/0101/Shader-precision
const float kMediumpFloatRelativePrecisionFactor = std::pow(2.0, -10.0);

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

// Test that we can get and set a float uniform successfully.
TEST_P(SimpleUniformTest, FloatUniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform float uniF;
void main() {
    gl_FragColor = vec4(uniF, 0.0, 0.0, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);
    GLint uniformLocation = glGetUniformLocation(program, "uniF");
    ASSERT_NE(uniformLocation, -1);

    GLfloat expected = 1.02f;
    glUniform1f(uniformLocation, expected);

    GLfloat f = 0.0f;
    glGetUniformfv(program, uniformLocation, &f);
    ASSERT_GL_NO_ERROR();
    EXPECT_NEAR(f, expected, std::abs(expected) * kMediumpFloatRelativePrecisionFactor);
}

// Test that we can get and set an int uniform successfully.
TEST_P(SimpleUniformTest, IntUniformStateQuery)
{
    constexpr char kFragShader[] = R"(uniform int uniI;
void main() {
    gl_FragColor = vec4(uniI, 0.0, 0.0, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "uniI");
    ASSERT_NE(uniformLocation, -1);

    GLint expected = 4;
    glUniform1i(uniformLocation, expected);

    GLint i = 0;
    glGetUniformiv(program, uniformLocation, &i);
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(i, expected);
}

// Test that we can get and set a vec2 uniform successfully.
TEST_P(SimpleUniformTest, FloatVec2UniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform vec2 uniVec2;
void main() {
    gl_FragColor = vec4(uniVec2, 0.0, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "uniVec2");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.5f}};
    glUniform2fv(uniformLocation, 1, expected.data());

    std::vector<GLfloat> floats(2, 0);
    glGetUniformfv(program, uniformLocation, floats.data());
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(floats, expected);
}

// Test that we can get and set a vec3 uniform successfully.
TEST_P(SimpleUniformTest, FloatVec3UniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform vec3 uniVec3;
void main() {
    gl_FragColor = vec4(uniVec3, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "uniVec3");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.5f, 0.2f}};
    glUniform3fv(uniformLocation, 1, expected.data());

    std::vector<GLfloat> floats(3, 0);
    glGetUniformfv(program, uniformLocation, floats.data());
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < 3; ++i)
    {
        EXPECT_NEAR(floats[i], expected[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set a vec4 uniform successfully.
TEST_P(SimpleUniformTest, FloatVec4UniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform vec4 uniVec4;
void main() {
    gl_FragColor = uniVec4;
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "uniVec4");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.5f, 0.2f, -0.8f}};
    glUniform4fv(uniformLocation, 1, expected.data());

    std::vector<GLfloat> floats(4, 0);
    glGetUniformfv(program, uniformLocation, floats.data());
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < 4; ++i)
    {
        EXPECT_NEAR(floats[i], expected[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set mediump vec4 uniform arrays successfully.
TEST_P(SimpleUniformTest, FloatVec4UniformArrayStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform vec4 uniVec4[2];
void main() {
    // Use the uniforms to prevent them from being optimized out.
    gl_FragColor = uniVec4[0] + uniVec4[1];
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "uniVec4");
    ASSERT_NE(uniformLocation, -1);

    // Define data for an array of two vec4 uniforms
    std::vector<GLfloat> expected = {
        1.0f,  0.5f, 0.2f, -0.8f,  // First vec4: uniVec4[0]
        -0.1f, 0.3f, 0.7f, -0.9f   // Second vec4: uniVec4[1]
    };

    // Set both uniforms in the array with a single API call
    glUniform4fv(uniformLocation, 2, expected.data());
    ASSERT_GL_NO_ERROR();

    // Query and verify the first uniform in the array
    std::vector<GLfloat> actual1(4, 0);
    glGetUniformfv(program, uniformLocation, actual1.data());
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < 4; ++i)
    {
        EXPECT_NEAR(actual1[i], expected[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }

    // Query and verify the second uniform in the array.
    // The location of subsequent array elements is found by incrementing the base location.
    std::vector<GLfloat> actual2(4, 0);
    glGetUniformfv(program, uniformLocation + 1, actual2.data());
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < 4; ++i)
    {
        EXPECT_NEAR(actual2[i], expected[i + 4],
                    std::abs(expected[i + 4]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set a 2x2 float Matrix uniform successfully.
TEST_P(SimpleUniformTest, FloatMatrix2UniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform mat2 umat2;
void main() {
    gl_FragColor = vec4(umat2);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "umat2");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.5f, 0.2f, -0.8f}};
    glUniformMatrix2fv(uniformLocation, 1, false, expected.data());

    std::vector<GLfloat> floats(4, 0);
    glGetUniformfv(program, uniformLocation, floats.data());
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < 4; ++i)
    {
        EXPECT_NEAR(expected[i], floats[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set a 3x3 float Matrix uniform successfully.
TEST_P(SimpleUniformTest, FloatMatrix3UniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform mat3 umat3;
void main() {
    gl_FragColor = vec4(umat3);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "umat3");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.5f, 0.2f, -0.8f, -0.2f, 0.1f, 0.1f, 0.2f, 0.7f}};
    glUniformMatrix3fv(uniformLocation, 1, false, expected.data());

    std::vector<GLfloat> floats(9, 0);
    glGetUniformfv(program, uniformLocation, floats.data());
    ASSERT_GL_NO_ERROR();

    for (size_t i = 0; i < 9; ++i)
    {
        EXPECT_NEAR(expected[i], floats[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set a 4x4 float Matrix uniform successfully.
TEST_P(SimpleUniformTest, FloatMatrix4UniformStateQuery)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform mat4 umat4;
void main() {
    gl_FragColor = umat4 * vec4(1.0, 1.0, 1.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "umat4");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.5f, 0.2f, -0.8f, -0.2f, 0.1f, 0.1f, 0.2f, 0.7f, 0.1f,
                                      0.7f, 0.1f, 0.7f, 0.1f, 0.7f, 0.1f}};
    glUniformMatrix4fv(uniformLocation, 1, false, expected.data());

    std::vector<GLfloat> floats(16, 0);
    glGetUniformfv(program, uniformLocation, floats.data());
    ASSERT_GL_NO_ERROR();
    for (size_t i = 0; i < 16; ++i)
    {
        EXPECT_NEAR(expected[i], floats[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set a float array of uniforms.
TEST_P(SimpleUniformTest, FloatArrayUniformStateQuery)
{

    constexpr char kFragShader[] = R"(
precision mediump float;
uniform float ufloats[4];
void main() {
    gl_FragColor = vec4(ufloats[0], ufloats[1], ufloats[2], ufloats[3]);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);
    std::vector<GLfloat> expected = {{0.1f, 0.2f, 0.3f, 0.4f}};

    for (size_t i = 0; i < expected.size(); i++)
    {
        std::string locationName = "ufloats[" + std::to_string(i) + "]";
        GLint uniformLocation    = glGetUniformLocation(program, locationName.c_str());
        glUniform1f(uniformLocation, expected[i]);
        ASSERT_GL_NO_ERROR();
        ASSERT_NE(uniformLocation, -1);

        GLfloat result = 0;
        glGetUniformfv(program, uniformLocation, &result);
        ASSERT_GL_NO_ERROR();
        EXPECT_NEAR(result, expected[i],
                    std::abs(expected[i]) * kMediumpFloatRelativePrecisionFactor);
    }
}

// Test that we can get and set an array of matrices uniform.
TEST_P(SimpleUniformTest, ArrayOfMat3UniformStateQuery)
{
    constexpr char kFragShader[] = R"(
precision mediump float;
uniform mat3 umatarray[2];
void main() {
    gl_FragColor = vec4(umatarray[1]);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);
    std::vector<std::vector<GLfloat>> expected = {
        {1.0f, 0.5f, 0.2f, -0.8f, -0.2f, 0.1f, 0.1f, 0.2f, 0.7f},
        {0.9f, 0.4f, 0.1f, -0.9f, -0.3f, 0.0f, 0.0f, 0.1f, 0.6f}};

    for (size_t i = 0; i < expected.size(); i++)
    {
        std::string locationName = "umatarray[" + std::to_string(i) + "]";
        GLint uniformLocation    = glGetUniformLocation(program, locationName.c_str());
        glUniformMatrix3fv(uniformLocation, 1, false, expected[i].data());
        ASSERT_GL_NO_ERROR();
        ASSERT_NE(uniformLocation, -1);

        std::vector<GLfloat> results(9, 0);
        glGetUniformfv(program, uniformLocation, results.data());
        ASSERT_GL_NO_ERROR();
        for (size_t matrixElementIndex = 0; matrixElementIndex < 9; ++matrixElementIndex)
        {
            EXPECT_NEAR(
                results[matrixElementIndex], expected[i][matrixElementIndex],
                std::abs(expected[i][matrixElementIndex]) * kMediumpFloatRelativePrecisionFactor);
        }
    }
}

// Test that we can get and set an int array of uniforms.
TEST_P(SimpleUniformTest, FloatIntUniformStateQuery)
{

    constexpr char kFragShader[] = R"(
precision mediump float;
uniform int uints[4];
void main() {
    gl_FragColor = vec4(uints[0], uints[1], uints[2], uints[3]);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Zero(), kFragShader);
    glUseProgram(program);
    std::vector<GLint> expected = {{1, 2, 3, 4}};

    for (size_t i = 0; i < expected.size(); i++)
    {
        std::string locationName = "uints[" + std::to_string(i) + "]";
        GLint uniformLocation    = glGetUniformLocation(program, locationName.c_str());
        glUniform1i(uniformLocation, expected[i]);
        ASSERT_GL_NO_ERROR();
        ASSERT_NE(uniformLocation, -1);

        GLint result = 0;
        glGetUniformiv(program, uniformLocation, &result);
        ASSERT_GL_NO_ERROR();
        ASSERT_EQ(result, expected[i]);
    }
}

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

    void testSetUp() override
    {

        constexpr char kFS[] = R"(
            precision mediump float;
            uniform float uniF;
            uniform int uniI;
            uniform vec4 uniVec4;
            void main() {
              gl_FragColor = vec4(uniF + float(uniI));
              gl_FragColor += uniVec4;
            })";
        mProgram             = CompileProgram(essl1_shaders::vs::Simple(), kFS);
        ASSERT_NE(mProgram, 0u);

        mUniformFLocation = glGetUniformLocation(mProgram, "uniF");
        ASSERT_NE(mUniformFLocation, -1);

        mUniformILocation = glGetUniformLocation(mProgram, "uniI");
        ASSERT_NE(mUniformILocation, -1);

        mUniformVec4Location = glGetUniformLocation(mProgram, "uniVec4");
        ASSERT_NE(mUniformVec4Location, -1);

        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override { glDeleteProgram(mProgram); }

    GLuint mProgram            = 0;
    GLint mUniformFLocation    = -1;
    GLint mUniformILocation    = -1;
    GLint mUniformVec4Location = -1;
};

// Tests that setting a float uniform with glUniform1f() is actually observable in the shader.
TEST_P(BasicUniformUsageTest, Float)
{
    glUseProgram(mProgram);

    glUniform1f(mUniformFLocation, 1.0f);
    glUniform1i(mUniformILocation, 0);
    glUniform4f(mUniformVec4Location, 0.0f, 0.0f, 0.0f, 1.0f);

    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that setting an int uniform with glUniform1i() is actually observable in the shader.
TEST_P(BasicUniformUsageTest, Integer)
{
    glUseProgram(mProgram);

    glUniform1f(mUniformFLocation, 0.0f);
    glUniform1i(mUniformILocation, 1);
    glUniform4f(mUniformVec4Location, 0.0f, 0.0f, 0.0f, 1.0f);

    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that setting a vec4 uniform with glUniform4f() is actually observable in the shader.
TEST_P(BasicUniformUsageTest, Vec4)
{
    glUseProgram(mProgram);

    glUniform1f(mUniformFLocation, 0.0f);
    glUniform1i(mUniformILocation, 0);
    // green
    glUniform4f(mUniformVec4Location, 0.0f, 1.0f, 0.0f, 1.0f);

    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests that setting a vec4 uniform with glUniform4f() is actually observable in the shader, across
// multiple draw calls, even without a glFlush() in between the draw calls.
TEST_P(BasicUniformUsageTest, Vec4MultipleDraws)
{
    glUseProgram(mProgram);

    glUniform1f(mUniformFLocation, 0.0f);
    glUniform1i(mUniformILocation, 0);
    // green
    glUniform4f(mUniformVec4Location, 0.0f, 1.0f, 0.0f, 1.0f);

    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // readPixels caused a flush, try red now
    glUniform4f(mUniformVec4Location, 1.0f, 0.0f, 0.0f, 1.0f);

    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // green
    glUniform4f(mUniformVec4Location, 0.0f, 1.0f, 0.0f, 1.0f);
    // But only draw a quad half the size
    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f, /*positionAttribXYScale=*/0.5f);
    // Still red at (0,0)
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    // Green in the middle.
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::green);

    // Now, do a similar thing but no flush in the middle.
    // Draw the screen green:
    glUniform4f(mUniformVec4Location, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);
    // Draw the middle of the screen red:
    glUniform4f(mUniformVec4Location, 1.0f, 0.0f, 0.0f, 1.0f);
    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f, /*positionAttribXYScale=*/0.5f);
    // Still green at (0,0)
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    // Red in the middle.
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, GLColor::red);
}

// Test that we can index a uniform matrix correctly.
TEST_P(SimpleUniformTest, FloatMatrix2UniformIndexed)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform mat2 umat2;
void main() {
    gl_FragColor = vec4(umat2[0], umat2[1]);
})";

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "umat2");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> expected = {{1.0f, 0.0f, 0.0f, 1.0f}};
    glUniformMatrix2fv(uniformLocation, 1, false, expected.data());

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that we can index a uniform matrix correctly, when that uniform matrix is an array element.
TEST_P(SimpleUniformTest, FloatMatrix2ArrayUniformDoubleIndexed)
{
    constexpr char kFragShader[] = R"(precision mediump float;
uniform mat2 umat2Array[2];
void main() {
    gl_FragColor = vec4(umat2Array[1][0], umat2Array[1][1]);
})";

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), kFragShader);
    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "umat2Array");
    ASSERT_NE(uniformLocation, -1);

    std::vector<GLfloat> uniformData = {// Data for umat2Array[0]
                                        0.0f, 0.0f, 1.0f, 1.0f,

                                        // Data for umat2Array[1]
                                        0.0f, 1.0f, 0.0f, 1.0f};

    glUniformMatrix2fv(uniformLocation, 2, false, uniformData.data());

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// In std140, the member following a struct will need to be aligned to 16. This tests that backends
// like WGSL which take std140 buffers correctly align this member.
TEST_P(SimpleUniformTest, NestedStructAlignedCorrectly)
{
    constexpr char kFragShader[] = R"(precision mediump float;
struct NestedUniforms {
    float x;
};
struct Uniforms {
    NestedUniforms a;
    float b;
    float c;
};
uniform Uniforms unis;
void main() {
    gl_FragColor = vec4(unis.a.x, unis.b, unis.c, 1.0);
})";

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);
    GLint uniformAXLocation = glGetUniformLocation(program, "unis.a.x");
    ASSERT_NE(uniformAXLocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.b");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.c");
    ASSERT_NE(uniformCLocation, -1);

    // Set to red
    glUniform1f(uniformAXLocation, 1.0f);
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Set to green
    glUniform1f(uniformAXLocation, 0.0f);
    glUniform1f(uniformBLocation, 1.0f);
    glUniform1f(uniformCLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to blue
    glUniform1f(uniformAXLocation, 0.0f);
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    glDeleteProgram(program);
}

// Similarly to the above, tests that structs as array elements are aligned correctly, and nested
// structs that follow float members are aligned correctly.
TEST_P(SimpleUniformTest, NestedStructAlignedCorrectly2)
{
    constexpr char kFragShader[] = R"(precision mediump float;
struct NestedUniforms {
    float x;
};
struct Uniforms {
    float b;
    NestedUniforms nested;
    float c;
    NestedUniforms[2] arr;
    float d;
};
uniform Uniforms unis;
void main() {
    gl_FragColor = vec4(unis.nested.x, unis.b, unis.c, 1.0);
    gl_FragColor += vec4(unis.arr[0].x, unis.arr[1].x, unis.d, 1.0);
})";

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformNestedXLocation = glGetUniformLocation(program, "unis.nested.x");
    ASSERT_NE(uniformNestedXLocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.b");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.c");
    ASSERT_NE(uniformCLocation, -1);
    GLint uniformArr0Location = glGetUniformLocation(program, "unis.arr[0].x");
    ASSERT_NE(uniformArr0Location, -1);
    GLint uniformArr1Location = glGetUniformLocation(program, "unis.arr[1].x");
    ASSERT_NE(uniformArr1Location, -1);
    GLint uniformDLocation = glGetUniformLocation(program, "unis.d");
    ASSERT_NE(uniformDLocation, -1);

    // Init to 0
    glUniform1f(uniformArr0Location, 0.0f);
    glUniform1f(uniformArr1Location, 0.0f);
    glUniform1f(uniformDLocation, 0.0f);

    // Set to red
    glUniform1f(uniformNestedXLocation, 1.0f);
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Set to green
    glUniform1f(uniformNestedXLocation, 0.0f);
    glUniform1f(uniformBLocation, 1.0f);
    glUniform1f(uniformCLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to blue
    glUniform1f(uniformNestedXLocation, 0.0f);
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    // Zero out
    glUniform1f(uniformNestedXLocation, 0.0f);
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 0.0f);
    // Set to red
    glUniform1f(uniformArr0Location, 1.0f);
    glUniform1f(uniformArr1Location, 0.0f);
    glUniform1f(uniformDLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Set to green
    glUniform1f(uniformArr0Location, 0.0f);
    glUniform1f(uniformArr1Location, 1.0f);
    glUniform1f(uniformDLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to blue
    glUniform1f(uniformArr0Location, 0.0f);
    glUniform1f(uniformArr1Location, 0.0f);
    glUniform1f(uniformDLocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    glDeleteProgram(program);
}

// Tests that arrays in uniforms function corectly. In particular, WGSL requires arrays in uniforms
// to have a stride a multiple of 16, but some arrays (e.g. vec2[N] or float[N]) will not
// automatically have stride 16 and need special handling.
TEST_P(SimpleUniformTest, ArraysInUniforms)
{
    constexpr char kFragShader[] = R"(
precision mediump float;
struct NestedUniforms {
    vec2 x[5];
};
struct Uniforms {
    NestedUniforms a;
    float b;
    float c;
    float[5] d;
    float e;
    vec3 f[7];
};
uniform Uniforms unis;
void main() {
    gl_FragColor = vec4(unis.a.x[2].x, unis.d[1], unis.e, 1.0);
    gl_FragColor += vec4(unis.f[2], 0.0);
})";

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformAXLocation = glGetUniformLocation(program, "unis.a.x[2]");
    ASSERT_NE(uniformAXLocation, -1);
    GLint uniformDLocation = glGetUniformLocation(program, "unis.d[1]");
    ASSERT_NE(uniformDLocation, -1);
    GLint uniformELocation = glGetUniformLocation(program, "unis.e");
    ASSERT_NE(uniformELocation, -1);
    GLint uniformFLocation = glGetUniformLocation(program, "unis.f[2]");
    ASSERT_NE(uniformFLocation, -1);

    // Set to red
    glUniform2f(uniformAXLocation, 1.0f, 0.0);
    glUniform1f(uniformDLocation, 0.0f);
    glUniform1f(uniformELocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Set to green
    glUniform2f(uniformAXLocation, 0.0f, 0.0f);
    glUniform1f(uniformDLocation, 1.0f);
    glUniform1f(uniformELocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to blue
    glUniform2f(uniformAXLocation, 0.0f, 0.0f);
    glUniform1f(uniformDLocation, 0.0f);
    glUniform1f(uniformELocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    // Set to red
    glUniform1f(uniformELocation, 0.0f);
    glUniform3f(uniformFLocation, 1.0f, 0.0f, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glDeleteProgram(program);
}

// Tests that inactive uniforms do not cause buffer offsets to be incorrectly calculated.
TEST_P(SimpleUniformTest, MiddleInactiveUniform)
{
    constexpr char kFragShader[] = R"(
precision mediump float;

uniform vec4 one;
uniform vec4 two;
uniform vec4 three;

void main() {
    gl_FragColor = vec4(0.0);
    gl_FragColor += one;
    gl_FragColor += three;
})";

    GLuint program = CompileProgram(essl1_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformOneLocation = glGetUniformLocation(program, "one");
    ASSERT_NE(uniformOneLocation, -1);
    GLint uniformTwoLocation = glGetUniformLocation(program, "two");
    ASSERT_EQ(uniformTwoLocation, -1);
    GLint uniformThreeLocation = glGetUniformLocation(program, "three");
    ASSERT_NE(uniformThreeLocation, -1);

    // Set to magenta.
    glUniform4f(uniformOneLocation, 1.0f, 0.0f, 0.0f, 1.0f);
    glUniform4f(uniformThreeLocation, 0.0f, 0.0f, 1.0f, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);

    glDeleteProgram(program);
}

using SimpleUniformTestES3 = SimpleUniformTest;

// Tests that making a copy of a struct of uniforms functions correctly.
TEST_P(SimpleUniformTestES3, CopyOfUniformsWithArrays)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct NestedUniforms {
    vec2 x[5];
};
struct Uniforms {
    NestedUniforms a;
    float b;
    float c;
    float[5] d;
    float e;
    vec3 f[7];
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
    Uniforms copy = unis;
    fragColor = vec4(copy.a.x[2].x, copy.d[1], copy.e, 1.0);
    fragColor += vec4(copy.f[2], 0.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformAXLocation = glGetUniformLocation(program, "unis.a.x[2]");
    ASSERT_NE(uniformAXLocation, -1);
    GLint uniformDLocation = glGetUniformLocation(program, "unis.d[1]");
    ASSERT_NE(uniformDLocation, -1);
    GLint uniformELocation = glGetUniformLocation(program, "unis.e");
    ASSERT_NE(uniformELocation, -1);
    GLint uniformFLocation = glGetUniformLocation(program, "unis.f[2]");
    ASSERT_NE(uniformFLocation, -1);

    // Set to red
    glUniform2f(uniformAXLocation, 1.0f, 0.0);
    glUniform1f(uniformDLocation, 0.0f);
    glUniform1f(uniformELocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Set to green
    glUniform2f(uniformAXLocation, 0.0f, 0.0f);
    glUniform1f(uniformDLocation, 1.0f);
    glUniform1f(uniformELocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to blue
    glUniform2f(uniformAXLocation, 0.0f, 0.0f);
    glUniform1f(uniformDLocation, 0.0f);
    glUniform1f(uniformELocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);

    // Set to red
    glUniform1f(uniformELocation, 0.0f);
    glUniform3f(uniformFLocation, 1.0f, 0.0f, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glDeleteProgram(program);
}

// Tests that making a copy of an array from a uniform functions correctly.
TEST_P(SimpleUniformTestES3, CopyOfArrayInUniform)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct NestedUniforms {
    vec2 x[5];
};
struct Uniforms {
    NestedUniforms a;
    float b;
    float c;
    float[5] d;
    float[4] d2;
    float e;
    vec3 f[7];
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
    float[5] dCopy = unis.d;
    float[4] d2Copy = unis.d2;
    fragColor = vec4(dCopy[1], d2Copy[0], 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformDLocation = glGetUniformLocation(program, "unis.d[1]");
    ASSERT_NE(uniformDLocation, -1);
    GLint uniformD2Location = glGetUniformLocation(program, "unis.d2[0]");
    ASSERT_NE(uniformD2Location, -1);

    // Set to black
    glUniform1f(uniformDLocation, 0.0f);
    glUniform1f(uniformD2Location, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);

    // Set to red
    glUniform1f(uniformDLocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glDeleteProgram(program);
}

// Tests that ternaries function correctly when retrieving an array element from a uniform.
TEST_P(SimpleUniformTestES3, TernarySelectAnArrayElement)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    float a;
    float b[2];
    float c[2];
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
    fragColor = vec4((unis.a > 0.5 ? unis.b[1] : unis.c[1]),
                     (unis.a > 0.5 ? unis.c[1] : unis.b[1]),
                     0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.a");
    ASSERT_NE(uniformALocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.b[1]");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.c[1]");
    ASSERT_NE(uniformCLocation, -1);

    // Set to red
    glUniform1f(uniformALocation, 1.0f);
    glUniform1f(uniformBLocation, 1.0f);
    glUniform1f(uniformCLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Flip unis.a to set to green
    glUniform1f(uniformALocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to red by flipping unis.b[1] and unis.c[1].
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Flip unis.a to set to green
    glUniform1f(uniformALocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glDeleteProgram(program);
}
// Tests that ternaries function correctly when retrieving an array a uniform and then indexing the
// result of the ternary.
TEST_P(SimpleUniformTestES3, TernarySelectAnArrayThenIndex)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    float a;
    float b[2];
    float c[2];
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
    fragColor = vec4((unis.a > 0.5 ? unis.b : unis.c)[1],
                     (unis.a > 0.5 ? unis.c : unis.b)[1],
                     0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.a");
    ASSERT_NE(uniformALocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.b[1]");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.c[1]");
    ASSERT_NE(uniformCLocation, -1);

    // Set to red
    glUniform1f(uniformALocation, 1.0f);
    glUniform1f(uniformBLocation, 1.0f);
    glUniform1f(uniformCLocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Flip unis.a to set to green
    glUniform1f(uniformALocation, 0.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    // Set to red by flipping unis.b[1] and unis.c[1].
    glUniform1f(uniformBLocation, 0.0f);
    glUniform1f(uniformCLocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Flip unis.a to set to green
    glUniform1f(uniformALocation, 1.0f);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glDeleteProgram(program);
}

// Tests that a struct used in the uniform address space can also be used outside of the uniform
// address space. The WGSL translator changes the type signature of the struct which can cause
// problems assigning to fields.
TEST_P(SimpleUniformTestES3, UseUniformStructOutsideOfUniformAddressSpace)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct NestedUniforms {
    float x[3];
};
struct Uniforms {
    NestedUniforms a;
    float b;
    float c;
    float[5] d;
    float e;
    vec3 f[7];
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
    NestedUniforms privUnis;
    privUnis.x = float[3](1.0, 1.0, 1.0);
    NestedUniforms privUnis2;
    privUnis2.x = unis.a.x;
    Uniforms privUnisWholeStruct;
    privUnisWholeStruct = unis;
    fragColor = vec4(privUnis.x[1], privUnis2.x[1], privUnisWholeStruct.a.x[1], 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformAXLocation = glGetUniformLocation(program, "unis.a.x");
    ASSERT_NE(uniformAXLocation, -1);

    GLfloat x[3] = {0.0, 1.0, 0.0};

    // Set to white
    glUniform1fv(uniformAXLocation, 3, x);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    glDeleteProgram(program);
}

// Tests that bools function correctly in a uniform. WGSL does not allow booleans in the uniform
// address space.
TEST_P(SimpleUniformTestES3, Bool)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    bool a;

    bool[2] aArr;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  bool a = unis.a;

  fragColor = vec4(a, 0.0, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.a");
    ASSERT_NE(uniformALocation, -1);

    GLuint a = 1;

    glUniform1ui(uniformALocation, a);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glDeleteProgram(program);
}

// Tests that bool in an array in a uniform can be used in a shader.
TEST_P(SimpleUniformTestES3, BoolInArray)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    bool a;

    bool[2] aArr;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  bool[2] a = unis.aArr;

  fragColor = vec4(a[0], a[1], 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.aArr");
    ASSERT_NE(uniformALocation, -1);

    GLuint aArr[] = {1, 0};

    glUniform1uiv(uniformALocation, 2, aArr);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    GLuint aArrFlipped[] = {0, 1};

    glUniform1uiv(uniformALocation, 2, aArrFlipped);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glDeleteProgram(program);
}

// Tests that a uniform array containing bool can be indexed into correctly.
// The WGSL translator includes some optimizations around this case.
TEST_P(SimpleUniformTestES3, BoolInArrayWithOptimization)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    bool a;

    bool[2] aArr;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  bool a0 = unis.aArr[0];
  bool a1 = unis.aArr[1];

  fragColor = vec4(a0, a1, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.aArr");
    ASSERT_NE(uniformALocation, -1);

    GLuint aArr[] = {1, 0};

    glUniform1uiv(uniformALocation, 2, aArr);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    GLuint aArrFlipped[] = {0, 1};

    glUniform1uiv(uniformALocation, 2, aArrFlipped);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glDeleteProgram(program);
}

// Tests that matCx2 (matrix with C columns and 2 rows) functions correctly in a
// uniform. WGSL's matCx2 does not match std140 layout.
TEST_P(SimpleUniformTestES3, MatCx2)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    mat2 a;
    mat3x2 b;
    mat4x2 c;

    mat2[2] aArr;
    mat3x2[2] bArr;
    mat4x2[2] cArr;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  mat2 a = unis.a;
  mat3x2 b = unis.b;
  mat4x2 c = unis.c;
  vec2 aMult = vec2(1.0, 1.0);
  vec3 bMult = vec3(0.25, 0.25, 0.5);
  vec4 cMult = vec4(0.25, 0.25, 0.25, 0.25);

  fragColor = vec4(a * aMult, 0.0, 1.0);
  fragColor += vec4(b * bMult, 0.0, 1.0);
  fragColor += vec4(c * cMult, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.a");
    ASSERT_NE(uniformALocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.b");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.c");
    ASSERT_NE(uniformCLocation, -1);

    GLfloat a[4] = {1.0, 0.0, 0.0, 1.0};
    GLfloat b[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    GLfloat c[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b);
    glUniformMatrix4x2fv(uniformCLocation, 1, GL_FALSE, c);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);

    // reset a and test b
    GLfloat a2[4] = {0.0, 0.0, 0.0, 0.0};
    GLfloat b2[6] = {1.0, 0.0, 1.0, 0.0, 1.0, 1.0};
    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a2);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b2);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255u, 127u, 0, 255u), 1.0);

    // reset a, b and test c
    GLfloat a3[4] = {0.0, 0.0, 0.0, 0.0};
    GLfloat b3[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    GLfloat c3[8] = {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0};
    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a3);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b3);
    glUniformMatrix4x2fv(uniformCLocation, 1, GL_FALSE, c3);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255u, 64u, 0, 255u), 1.0);

    glDeleteProgram(program);
}

// Tests that matCx2 in an array in a uniform can be used in a shader.
TEST_P(SimpleUniformTestES3, MatCx2InArray)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    mat2[2] aArr;
    mat3x2[2] bArr;
    mat4x2[2] cArr;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  mat2[2] aArr = unis.aArr;
  mat3x2[2] bArr = unis.bArr;
  mat4x2[2] cArr = unis.cArr;

  vec2 aMult = vec2(1.0, 1.0);
  vec3 bMult = vec3(0.25, 0.25, 0.5);
  vec4 cMult = vec4(0.25, 0.25, 0.25, 0.25);

  fragColor = vec4(aArr[0] * aMult, 0.0, 1.0);
  fragColor += vec4(bArr[0] * bMult, 0.0, 1.0);
  fragColor += vec4(cArr[0] * cMult, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.aArr[0]");
    ASSERT_NE(uniformALocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.bArr[0]");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.cArr[0]");
    ASSERT_NE(uniformCLocation, -1);

    GLfloat a[4] = {1.0, 0.0, 0.0, 1.0};
    GLfloat b[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    GLfloat c[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b);
    glUniformMatrix4x2fv(uniformCLocation, 1, GL_FALSE, c);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);

    // reset a and test b
    GLfloat a2[4] = {0.0, 0.0, 0.0, 0.0};
    GLfloat b2[6] = {1.0, 0.0, 1.0, 0.0, 1.0, 1.0};
    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a2);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b2);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255u, 127u, 0, 255u), 1.0);

    // reset a, b and test c
    GLfloat a3[4] = {0.0, 0.0, 0.0, 0.0};
    GLfloat b3[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    GLfloat c3[8] = {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0};
    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a3);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b3);
    glUniformMatrix4x2fv(uniformCLocation, 1, GL_FALSE, c3);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255u, 64u, 0, 255u), 1.0);

    glDeleteProgram(program);
}

// Tests that a uniform array containing matCx2 can be indexed into correctly.
// The WGSL translator includes some optimizations around this case..
TEST_P(SimpleUniformTestES3, MatCx2InArrayWithOptimization)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    mat2[2] aArr;
    mat3x2[2] bArr;
    mat4x2[2] cArr;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  mat2 aIndexed = unis.aArr[1];
  mat3x2 bIndexed = unis.bArr[1];
  mat4x2 cIndexed = unis.cArr[1];

  vec2 aMult = vec2(1.0, 1.0);
  vec3 bMult = vec3(0.25, 0.25, 0.5);
  vec4 cMult = vec4(0.25, 0.25, 0.25, 0.25);

  fragColor = vec4(aIndexed * aMult, 0.0, 1.0);
  fragColor += vec4(bIndexed * bMult, 0.0, 1.0);
  fragColor += vec4(cIndexed * cMult, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.aArr[1]");
    ASSERT_NE(uniformALocation, -1);
    GLint uniformBLocation = glGetUniformLocation(program, "unis.bArr[1]");
    ASSERT_NE(uniformBLocation, -1);
    GLint uniformCLocation = glGetUniformLocation(program, "unis.cArr[1]");
    ASSERT_NE(uniformCLocation, -1);

    GLfloat a[4] = {1.0, 0.0, 0.0, 1.0};
    GLfloat b[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    GLfloat c[8] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b);
    glUniformMatrix4x2fv(uniformCLocation, 1, GL_FALSE, c);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);

    // reset a and test b
    GLfloat a2[4] = {0.0, 0.0, 0.0, 0.0};
    GLfloat b2[6] = {1.0, 0.0, 1.0, 0.0, 1.0, 1.0};
    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a2);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b2);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255u, 127u, 0, 255u), 1.0);

    // reset a, b and test c
    GLfloat a3[4] = {0.0, 0.0, 0.0, 0.0};
    GLfloat b3[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
    GLfloat c3[8] = {1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0};
    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a3);
    glUniformMatrix3x2fv(uniformBLocation, 1, GL_FALSE, b3);
    glUniformMatrix4x2fv(uniformCLocation, 1, GL_FALSE, c3);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255u, 64u, 0, 255u), 1.0);

    glDeleteProgram(program);
}

// Tests that matCx2 can be used in a uniform at the same time an array of
// matCx2s is used in a uniform. (The WGSL translator had trouble with this)
TEST_P(SimpleUniformTestES3, MatCx2InArrayAndOutOfArray)
{
    constexpr char kFragShader[] = R"(#version 300 es
precision mediump float;
struct Uniforms {
    mat2 a;
    mat2[2] aArr;
    mat2[3] aArr2;
};
uniform Uniforms unis;
out vec4 fragColor;
void main() {
  mat2 aIndexed = unis.aArr[1] + unis.a + unis.aArr2[1];

  vec2 aMult = vec2(1.0, 1.0);

  fragColor = vec4(aIndexed * aMult, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint uniformALocation = glGetUniformLocation(program, "unis.a");
    ASSERT_NE(uniformALocation, -1);
    GLint uniformAArrLocation = glGetUniformLocation(program, "unis.aArr[1]");
    ASSERT_NE(uniformAArrLocation, -1);
    GLint uniformAArr2Location = glGetUniformLocation(program, "unis.aArr2[1]");
    ASSERT_NE(uniformAArr2Location, -1);

    GLfloat a[4]     = {0.5, 0.0, 0.0, 0.5};
    GLfloat aArr[4]  = {0.5, 0.0, 0.0, 0.5};
    GLfloat aArr2[4] = {0.0, 0.0, 0.0, 0.0};

    glUniformMatrix2fv(uniformALocation, 1, GL_FALSE, a);
    glUniformMatrix2fv(uniformAArrLocation, 1, GL_FALSE, aArr);
    glUniformMatrix2fv(uniformAArr2Location, 1, GL_FALSE, aArr2);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);

    glDeleteProgram(program);
}

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

    void testSetUp() override
    {
        // TODO(anglebug.com/40096755): asserting with latest direct-to-Metal compiler
        // changes. Must skip all tests explicitly.
        // if (IsMetal())
        //    return;
        constexpr char kVS[] = "void main() { gl_Position = vec4(1); }";

        constexpr char kFS[] =
            "precision mediump float;\n"
            "uniform float uniF;\n"
            "uniform int uniI;\n"
            "uniform bool uniB;\n"
            "uniform bool uniBArr[4];\n"
            "void main() {\n"
            "  gl_FragColor = vec4(uniF + float(uniI));\n"
            "  gl_FragColor += vec4(uniB ? 1.0 : 0.0);\n"
            "  gl_FragColor += vec4(uniBArr[0] ? 1.0 : 0.0);\n"
            "  gl_FragColor += vec4(uniBArr[1] ? 1.0 : 0.0);\n"
            "  gl_FragColor += vec4(uniBArr[2] ? 1.0 : 0.0);\n"
            "  gl_FragColor += vec4(uniBArr[3] ? 1.0 : 0.0);\n"
            "}";

        mProgram = CompileProgram(kVS, kFS);
        ASSERT_NE(mProgram, 0u);

        mUniformFLocation = glGetUniformLocation(mProgram, "uniF");
        ASSERT_NE(mUniformFLocation, -1);

        mUniformILocation = glGetUniformLocation(mProgram, "uniI");
        ASSERT_NE(mUniformILocation, -1);

        mUniformBLocation = glGetUniformLocation(mProgram, "uniB");
        ASSERT_NE(mUniformBLocation, -1);

        ASSERT_GL_NO_ERROR();
    }

    void testTearDown() override { glDeleteProgram(mProgram); }

    GLuint mProgram         = 0;
    GLint mUniformFLocation = -1;
    GLint mUniformILocation = -1;
    GLint mUniformBLocation = -1;
};

TEST_P(UniformTest, GetUniformNoCurrentProgram)
{
    glUseProgram(mProgram);
    glUniform1f(mUniformFLocation, 1.0f);
    glUniform1i(mUniformILocation, 1);
    glUseProgram(0);

    GLfloat f = 0.0f;
    glGetUniformfv(mProgram, mUniformFLocation, &f);
    ASSERT_GL_NO_ERROR();
    EXPECT_EQ(1.0f, f);

    GLint i = 0;
    glGetUniformiv(mProgram, mUniformILocation, &i);
    ASSERT_GL_NO_ERROR();
    EXPECT_EQ(1, i);

    if (IsGLExtensionEnabled("GL_EXT_robustness"))
    {
        f = 0.0f;
        glGetnUniformfvEXT(mProgram, mUniformFLocation, 4, &f);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(1.0f, f);

        i = 0;
        glGetnUniformivEXT(mProgram, mUniformILocation, 4, &i);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(1, i);
    }
}

TEST_P(UniformTest, UniformArrayLocations)
{

    constexpr char kVS[] = R"(precision mediump float;
uniform float uPosition[4];
void main(void)
{
    gl_Position = vec4(uPosition[0], uPosition[1], uPosition[2], uPosition[3]);
})";

    constexpr char kFS[] = R"(precision mediump float;
uniform float uColor[4];
void main(void)
{
    gl_FragColor = vec4(uColor[0], uColor[1], uColor[2], uColor[3]);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    // Array index zero should be equivalent to the un-indexed uniform
    EXPECT_NE(-1, glGetUniformLocation(program, "uPosition"));
    EXPECT_EQ(glGetUniformLocation(program, "uPosition"),
              glGetUniformLocation(program, "uPosition[0]"));

    EXPECT_NE(-1, glGetUniformLocation(program, "uColor"));
    EXPECT_EQ(glGetUniformLocation(program, "uColor"), glGetUniformLocation(program, "uColor[0]"));

    // All array uniform locations should be unique
    GLint positionLocations[4] = {
        glGetUniformLocation(program, "uPosition[0]"),
        glGetUniformLocation(program, "uPosition[1]"),
        glGetUniformLocation(program, "uPosition[2]"),
        glGetUniformLocation(program, "uPosition[3]"),
    };

    GLint colorLocations[4] = {
        glGetUniformLocation(program, "uColor[0]"),
        glGetUniformLocation(program, "uColor[1]"),
        glGetUniformLocation(program, "uColor[2]"),
        glGetUniformLocation(program, "uColor[3]"),
    };

    for (size_t i = 0; i < 4; i++)
    {
        EXPECT_NE(-1, positionLocations[i]);
        EXPECT_NE(-1, colorLocations[i]);

        for (size_t j = i + 1; j < 4; j++)
        {
            EXPECT_NE(positionLocations[i], positionLocations[j]);
            EXPECT_NE(colorLocations[i], colorLocations[j]);
        }
    }

    glDeleteProgram(program);
}

// Test that float to integer GetUniform rounds values correctly for highp float uniforms
TEST_P(UniformTest, HighpFloatUniformStateQuery)
{

    std::vector<double> inValues;
    std::vector<GLfloat> expectedFValues;
    std::vector<GLint> expectedIValues;

    double intMaxD = static_cast<double>(std::numeric_limits<GLint>::max());
    double intMinD = static_cast<double>(std::numeric_limits<GLint>::min());

    // TODO(jmadill): Investigate rounding of .5
    inValues.push_back(-1.0);
    inValues.push_back(-0.6);
    // inValues.push_back(-0.5); // undefined behaviour?
    inValues.push_back(-0.4);
    inValues.push_back(0.0);
    inValues.push_back(0.4);
    // inValues.push_back(0.5); // undefined behaviour?
    inValues.push_back(0.6);
    inValues.push_back(1.0);
    inValues.push_back(999999.2);
    inValues.push_back(intMaxD * 2.0);
    inValues.push_back(intMaxD + 1.0);
    inValues.push_back(intMinD * 2.0);
    inValues.push_back(intMinD - 1.0);

    for (double value : inValues)
    {
        expectedFValues.push_back(static_cast<GLfloat>(value));

        double clampedValue = std::max(intMinD, std::min(intMaxD, value));
        double rounded      = round(clampedValue);
        expectedIValues.push_back(static_cast<GLint>(rounded));
    }

    constexpr char kVS[] = "void main() { gl_Position = vec4(1); }";

    constexpr char kFS[] =
        "precision highp float;\n"
        "uniform float uniF;\n"
        "uniform int uniI;\n"
        "uniform bool uniB;\n"
        "uniform bool uniBArr[4];\n"
        "void main() {\n"
        "  gl_FragColor = vec4(uniF + float(uniI));\n"
        "  gl_FragColor += vec4(uniB ? 1.0 : 0.0);\n"
        "  gl_FragColor += vec4(uniBArr[0] ? 1.0 : 0.0);\n"
        "  gl_FragColor += vec4(uniBArr[1] ? 1.0 : 0.0);\n"
        "  gl_FragColor += vec4(uniBArr[2] ? 1.0 : 0.0);\n"
        "  gl_FragColor += vec4(uniBArr[3] ? 1.0 : 0.0);\n"
        "}";

    mProgram = CompileProgram(kVS, kFS);
    ASSERT_NE(mProgram, 0u);

    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    mUniformFLocation = glGetUniformLocation(mProgram, "uniF");
    ASSERT_NE(mUniformFLocation, -1);

    for (size_t index = 0; index < inValues.size(); ++index)
    {
        GLfloat inValue       = static_cast<GLfloat>(inValues[index]);
        GLfloat expectedValue = expectedFValues[index];

        glUniform1f(mUniformFLocation, inValue);
        GLfloat testValue;
        glGetUniformfv(mProgram, mUniformFLocation, &testValue);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(expectedValue, testValue);
    }

    for (size_t index = 0; index < inValues.size(); ++index)
    {
        GLfloat inValue     = static_cast<GLfloat>(inValues[index]);
        GLint expectedValue = expectedIValues[index];

        glUniform1f(mUniformFLocation, inValue);
        GLint testValue;
        glGetUniformiv(mProgram, mUniformFLocation, &testValue);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(expectedValue, testValue);
    }
}

// Test that float to integer GetUniform rounds values correctly for mediump float uniforms
TEST_P(UniformTest, MediumpFloatUniformStateQuery)
{

    std::vector<double> inValues;
    std::vector<GLfloat> expectedFValues;
    std::vector<GLint> expectedIValues;

    double intMaxD = static_cast<double>(std::numeric_limits<GLint>::max());
    double intMinD = static_cast<double>(std::numeric_limits<GLint>::min());

    // TODO(jmadill): Investigate rounding of .5
    inValues.push_back(-1.0);
    inValues.push_back(-0.6);
    inValues.push_back(-0.4);
    inValues.push_back(0.0);
    inValues.push_back(0.4);
    inValues.push_back(0.6);
    inValues.push_back(1.0);

    for (double value : inValues)
    {
        expectedFValues.push_back(static_cast<GLfloat>(value));

        double clampedValue = std::max(intMinD, std::min(intMaxD, value));
        double rounded      = round(clampedValue);
        expectedIValues.push_back(static_cast<GLint>(rounded));
    }

    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    for (size_t index = 0; index < inValues.size(); ++index)
    {
        GLfloat inValue       = static_cast<GLfloat>(inValues[index]);
        GLfloat expectedValue = expectedFValues[index];

        glUniform1f(mUniformFLocation, inValue);
        GLfloat testValue;
        glGetUniformfv(mProgram, mUniformFLocation, &testValue);
        ASSERT_GL_NO_ERROR();
        EXPECT_NEAR(expectedValue, testValue,
                    std::abs(expectedValue) * kMediumpFloatRelativePrecisionFactor);
    }

    for (size_t index = 0; index < inValues.size(); ++index)
    {
        GLfloat inValue     = static_cast<GLfloat>(inValues[index]);
        GLint expectedValue = expectedIValues[index];

        glUniform1f(mUniformFLocation, inValue);
        GLint testValue;
        glGetUniformiv(mProgram, mUniformFLocation, &testValue);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(expectedValue, testValue);
    }
}

// Test that integer to float GetUniform rounds values correctly.
TEST_P(UniformTest, IntUniformStateQuery)
{
    // Qualcomm seems to have a bug where integer uniforms are internally stored as float, and
    // large values are rounded to the nearest float representation of an integer.
    // TODO(jmadill): Lift this suppression when/if the bug is fixed.
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    std::vector<GLint> inValues;
    std::vector<GLint> expectedIValues;
    std::vector<GLfloat> expectedFValues;

    GLint intMax = std::numeric_limits<GLint>::max();
    GLint intMin = std::numeric_limits<GLint>::min();

    inValues.push_back(-1);
    inValues.push_back(0);
    inValues.push_back(1);
    inValues.push_back(999999);
    inValues.push_back(intMax);
    inValues.push_back(intMax - 1);
    inValues.push_back(intMin);
    inValues.push_back(intMin + 1);

    for (GLint value : inValues)
    {
        expectedIValues.push_back(value);
        expectedFValues.push_back(static_cast<GLfloat>(value));
    }

    glUseProgram(mProgram);
    ASSERT_GL_NO_ERROR();

    for (size_t index = 0; index < inValues.size(); ++index)
    {
        GLint inValue       = inValues[index];
        GLint expectedValue = expectedIValues[index];

        glUniform1i(mUniformILocation, inValue);
        GLint testValue = 1234567;
        glGetUniformiv(mProgram, mUniformILocation, &testValue);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(expectedValue, testValue) << " with glGetUniformiv";
    }

    for (size_t index = 0; index < inValues.size(); ++index)
    {
        GLint inValue         = inValues[index];
        GLfloat expectedValue = expectedFValues[index];

        glUniform1i(mUniformILocation, inValue);
        GLfloat testValue = 124567.0;
        glGetUniformfv(mProgram, mUniformILocation, &testValue);
        ASSERT_GL_NO_ERROR();
        EXPECT_EQ(expectedValue, testValue) << " with glGetUniformfv";
    }
}

// Test that queries of boolean uniforms round correctly.
TEST_P(UniformTest, BooleanUniformStateQuery)
{

    glUseProgram(mProgram);
    GLint intValue     = 0;
    GLfloat floatValue = 0.0f;

    // Calling Uniform1i
    glUniform1i(mUniformBLocation, GL_FALSE);

    glGetUniformiv(mProgram, mUniformBLocation, &intValue);
    EXPECT_EQ(0, intValue);

    glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
    EXPECT_EQ(0.0f, floatValue);

    glUniform1i(mUniformBLocation, GL_TRUE);

    glGetUniformiv(mProgram, mUniformBLocation, &intValue);
    EXPECT_EQ(1, intValue);

    glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
    EXPECT_EQ(1.0f, floatValue);

    // Calling Uniform1f
    glUniform1f(mUniformBLocation, 0.0f);

    glGetUniformiv(mProgram, mUniformBLocation, &intValue);
    EXPECT_EQ(0, intValue);

    glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
    EXPECT_EQ(0.0f, floatValue);

    glUniform1f(mUniformBLocation, 1.0f);

    glGetUniformiv(mProgram, mUniformBLocation, &intValue);
    EXPECT_EQ(1, intValue);

    glGetUniformfv(mProgram, mUniformBLocation, &floatValue);
    EXPECT_EQ(1.0f, floatValue);

    ASSERT_GL_NO_ERROR();
}

// Test queries for arrays of boolean uniforms.
TEST_P(UniformTest, BooleanArrayUniformStateQuery)
{

    glUseProgram(mProgram);
    GLint boolValuesi[4]   = {0, 1, 0, 1};
    GLfloat boolValuesf[4] = {0, 1, 0, 1};

    GLint locations[4] = {
        glGetUniformLocation(mProgram, "uniBArr"),
        glGetUniformLocation(mProgram, "uniBArr[1]"),
        glGetUniformLocation(mProgram, "uniBArr[2]"),
        glGetUniformLocation(mProgram, "uniBArr[3]"),
    };

    for (int i = 0; i < 4; ++i)
    {
        ASSERT_NE(-1, locations[i]) << " with i=" << i;
    }

    // Calling Uniform1iv
    glUniform1iv(locations[0], 4, boolValuesi);

    for (unsigned int idx = 0; idx < 4; ++idx)
    {
        int value = -1;
        glGetUniformiv(mProgram, locations[idx], &value);
        EXPECT_EQ(boolValuesi[idx], value) << " with Uniform1iv/GetUniformiv at " << idx;
    }

    for (unsigned int idx = 0; idx < 4; ++idx)
    {
        float value = -1.0f;
        glGetUniformfv(mProgram, locations[idx], &value);
        EXPECT_EQ(boolValuesf[idx], value) << " with Uniform1iv/GetUniformfv at " << idx;
    }

    // Calling Uniform1fv
    glUniform1fv(locations[0], 4, boolValuesf);

    for (unsigned int idx = 0; idx < 4; ++idx)
    {
        int value = -1;
        glGetUniformiv(mProgram, locations[idx], &value);
        EXPECT_EQ(boolValuesi[idx], value) << " with Uniform1fv/GetUniformiv at " << idx;
    }

    for (unsigned int idx = 0; idx < 4; ++idx)
    {
        float value = -1.0f;
        glGetUniformfv(mProgram, locations[idx], &value);
        EXPECT_EQ(boolValuesf[idx], value) << " with Uniform1fv/GetUniformfv at " << idx;
    }

    ASSERT_GL_NO_ERROR();
}

class UniformTestES3 : public ANGLETest<>
{
  protected:
    UniformTestES3() : mProgram(0) {}

    void testTearDown() override
    {
        if (mProgram != 0)
        {
            glDeleteProgram(mProgram);
            mProgram = 0;
        }
    }

    GLuint mProgram;
};

// Test that we can get and set an array of matrices uniform.
TEST_P(UniformTestES3, MatrixArrayUniformStateQuery)
{
    constexpr char kFragShader[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "uniform mat3x4 uniMat3x4[5];\n"
        "out vec4 fragColor;\n"
        "void main() {\n"
        "    fragColor = vec4(uniMat3x4[0]);\n"
        "    fragColor += vec4(uniMat3x4[1]);\n"
        "    fragColor += vec4(uniMat3x4[2]);\n"
        "    fragColor += vec4(uniMat3x4[3]);\n"
        "    fragColor += vec4(uniMat3x4[4]);\n"
        "}\n";
    constexpr unsigned int kArrayCount   = 5;
    constexpr unsigned int kMatrixStride = 3 * 4;

    mProgram = CompileProgram(essl3_shaders::vs::Zero(), kFragShader);
    ASSERT_NE(mProgram, 0u);

    glUseProgram(mProgram);
    GLfloat expected[kArrayCount][kMatrixStride] = {
        {0.6f, -0.4f, 0.6f, 0.9f, -0.6f, 0.3f, -0.3f, -0.1f, -0.4f, -0.3f, 0.7f, 0.1f},
        {-0.4f, -0.4f, -0.5f, -0.7f, 0.1f, -0.5f, 0.0f, -0.9f, -0.4f, 0.8f, -0.6f, 0.9f},
        {0.4f, 0.1f, -0.9f, 1.0f, -0.8f, 0.4f, -0.2f, 0.4f, -0.0f, 0.2f, 0.9f, -0.3f},
        {0.5f, 0.7f, -0.0f, 1.0f, 0.7f, 0.7f, 0.7f, -0.7f, -0.8f, 0.6f, 0.5f, -0.2f},
        {-1.0f, 0.8f, 1.0f, -0.4f, 0.7f, 0.5f, 0.5f, 0.8f, 0.6f, 0.1f, 0.4f, -0.9f}};

    GLint baseLocation = glGetUniformLocation(mProgram, "uniMat3x4");
    ASSERT_NE(-1, baseLocation);

    glUniformMatrix3x4fv(baseLocation, kArrayCount, GL_FALSE, &expected[0][0]);

    for (size_t i = 0; i < kArrayCount; i++)
    {
        std::stringstream nameStr;
        nameStr << "uniMat3x4[" << i << "]";
        std::string name = nameStr.str();
        GLint location   = glGetUniformLocation(mProgram, name.c_str());
        ASSERT_GL_NO_ERROR();
        ASSERT_NE(-1, location);

        std::vector<GLfloat> results(12, 0);
        glGetUniformfv(mProgram, location, results.data());
        ASSERT_GL_NO_ERROR();

        for (size_t compIdx = 0; compIdx < kMatrixStride; compIdx++)
        {
            EXPECT_NEAR(results[compIdx], expected[i][compIdx],
                        std::abs(expected[i][compIdx]) * kMediumpFloatRelativePrecisionFactor);
        }
    }
}

// Test queries for transposed arrays of non-square matrix uniforms.
TEST_P(UniformTestES3, TransposedMatrixArrayUniformStateQuery)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "uniform mat3x2 uniMat3x2[5];\n"
        "out vec4 color;\n"
        "void main() {\n"
        "  color = vec4(uniMat3x2[0][0][0]);\n"
        "  color += vec4(uniMat3x2[1][0][0]);\n"
        "  color += vec4(uniMat3x2[2][0][0]);\n"
        "  color += vec4(uniMat3x2[3][0][0]);\n"
        "  color += vec4(uniMat3x2[4][0][0]);\n"
        "}";

    mProgram = CompileProgram(essl3_shaders::vs::Zero(), kFS);
    ASSERT_NE(mProgram, 0u);

    glUseProgram(mProgram);

    std::vector<GLfloat> transposedValues;

    for (size_t arrayElement = 0; arrayElement < 5; ++arrayElement)
    {
        transposedValues.push_back(1.0f + arrayElement);
        transposedValues.push_back(3.0f + arrayElement);
        transposedValues.push_back(5.0f + arrayElement);
        transposedValues.push_back(2.0f + arrayElement);
        transposedValues.push_back(4.0f + arrayElement);
        transposedValues.push_back(6.0f + arrayElement);
    }

    // Setting as a clump
    GLint baseLocation = glGetUniformLocation(mProgram, "uniMat3x2");
    ASSERT_NE(-1, baseLocation);

    glUniformMatrix3x2fv(baseLocation, 5, GL_TRUE, &transposedValues[0]);

    for (size_t arrayElement = 0; arrayElement < 5; ++arrayElement)
    {
        std::stringstream nameStr;
        nameStr << "uniMat3x2[" << arrayElement << "]";
        std::string name = nameStr.str();
        GLint location   = glGetUniformLocation(mProgram, name.c_str());
        ASSERT_NE(-1, location);

        std::vector<GLfloat> sequentialValues(6, 0);
        glGetUniformfv(mProgram, location, &sequentialValues[0]);

        ASSERT_GL_NO_ERROR();

        for (size_t comp = 0; comp < 6; ++comp)
        {
            EXPECT_EQ(static_cast<GLfloat>(comp + 1 + arrayElement), sequentialValues[comp]);
        }
    }
}

// Check that trying setting too many elements of an array doesn't overflow
TEST_P(UniformTestES3, OverflowArray)
{

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "uniform float uniF[5];\n"
        "uniform mat3x2 uniMat3x2[5];\n"
        "out vec4 color;\n"
        "void main() {\n"
        "  color = vec4(uniMat3x2[0][0][0] + uniF[0]);\n"
        "  color = vec4(uniMat3x2[1][0][0] + uniF[1]);\n"
        "  color = vec4(uniMat3x2[2][0][0] + uniF[2]);\n"
        "  color = vec4(uniMat3x2[3][0][0] + uniF[3]);\n"
        "  color = vec4(uniMat3x2[4][0][0] + uniF[4]);\n"
        "}";

    mProgram = CompileProgram(essl3_shaders::vs::Zero(), kFS);
    ASSERT_NE(mProgram, 0u);

    glUseProgram(mProgram);

    const size_t kOverflowSize = 10000;
    std::vector<GLfloat> values(10000 * 6);

    // Setting as a clump
    GLint floatLocation = glGetUniformLocation(mProgram, "uniF");
    ASSERT_NE(-1, floatLocation);
    GLint matLocation = glGetUniformLocation(mProgram, "uniMat3x2");
    ASSERT_NE(-1, matLocation);

    // Set too many float uniforms
    glUniform1fv(floatLocation, kOverflowSize, &values[0]);

    // Set too many matrix uniforms, transposed or not
    glUniformMatrix3x2fv(matLocation, kOverflowSize, GL_FALSE, &values[0]);
    glUniformMatrix3x2fv(matLocation, kOverflowSize, GL_TRUE, &values[0]);

    // Same checks but with offsets
    GLint floatLocationOffset = glGetUniformLocation(mProgram, "uniF[3]");
    ASSERT_NE(-1, floatLocationOffset);
    GLint matLocationOffset = glGetUniformLocation(mProgram, "uniMat3x2[3]");
    ASSERT_NE(-1, matLocationOffset);

    glUniform1fv(floatLocationOffset, kOverflowSize, &values[0]);
    glUniformMatrix3x2fv(matLocationOffset, kOverflowSize, GL_FALSE, &values[0]);
    glUniformMatrix3x2fv(matLocationOffset, kOverflowSize, GL_TRUE, &values[0]);
}

// Check setting a sampler uniform
TEST_P(UniformTest, Sampler)
{
    constexpr char kVS[] =
        "uniform sampler2D tex2D;\n"
        "void main() {\n"
        "  gl_Position = vec4(0, 0, 0, 1);\n"
        "}";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "uniform sampler2D tex2D;\n"
        "void main() {\n"
        "  gl_FragColor = texture2D(tex2D, vec2(0, 0));\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint location = glGetUniformLocation(program, "tex2D");
    ASSERT_NE(-1, location);

    const GLint sampler[] = {0, 0, 0, 0};

    // before UseProgram
    glUniform1i(location, sampler[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glUseProgram(program);

    // Uniform1i
    glUniform1i(location, sampler[0]);
    glUniform1iv(location, 1, sampler);
    EXPECT_GL_NO_ERROR();

    // Uniform{234}i
    glUniform2i(location, sampler[0], sampler[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform3i(location, sampler[0], sampler[0], sampler[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform4i(location, sampler[0], sampler[0], sampler[0], sampler[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform2iv(location, 1, sampler);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform3iv(location, 1, sampler);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform4iv(location, 1, sampler);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // Uniform{1234}f
    const GLfloat f[] = {0, 0, 0, 0};
    glUniform1f(location, f[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform2f(location, f[0], f[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform3f(location, f[0], f[0], f[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform4f(location, f[0], f[0], f[0], f[0]);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform1fv(location, 1, f);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform2fv(location, 1, f);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform3fv(location, 1, f);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
    glUniform4fv(location, 1, f);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    // < 0 or >= max
    GLint tooHigh;
    glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &tooHigh);
    constexpr GLint tooLow[] = {-1};
    glUniform1i(location, tooLow[0]);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
    glUniform1iv(location, 1, tooLow);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
    glUniform1i(location, tooHigh);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
    glUniform1iv(location, 1, &tooHigh);
    EXPECT_GL_ERROR(GL_INVALID_VALUE);
}

// Check that sampler uniforms only show up one time in the list
TEST_P(UniformTest, SamplerUniformsAppearOnce)
{
    int maxVertexTextureImageUnits = 0;
    glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxVertexTextureImageUnits);

    // Renderer doesn't support vertex texture fetch, skipping test.
    ANGLE_SKIP_TEST_IF(!maxVertexTextureImageUnits);

    constexpr char kVS[] =
        "attribute vec2 position;\n"
        "uniform sampler2D tex2D;\n"
        "varying vec4 color;\n"
        "void main() {\n"
        "  gl_Position = vec4(position, 0, 1);\n"
        "  color = texture2D(tex2D, vec2(0));\n"
        "}";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying vec4 color;\n"
        "uniform sampler2D tex2D;\n"
        "void main() {\n"
        "  gl_FragColor = texture2D(tex2D, vec2(0)) + color;\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint activeUniformsCount = 0;
    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniformsCount);
    ASSERT_EQ(1, activeUniformsCount);

    GLint size       = 0;
    GLenum type      = GL_NONE;
    GLchar name[120] = {0};
    glGetActiveUniform(program, 0, 100, nullptr, &size, &type, name);
    EXPECT_EQ(1, size);
    EXPECT_GLENUM_EQ(GL_SAMPLER_2D, type);
    EXPECT_STREQ("tex2D", name);

    EXPECT_GL_NO_ERROR();

    glDeleteProgram(program);
}

template <typename T, typename GetUniformV>
void CheckOneElement(GetUniformV getUniformv,
                     GLuint program,
                     const std::string &name,
                     int components,
                     T canary)
{
    // The buffer getting the results has three chunks
    //  - A chunk to see underflows
    //  - A chunk that will hold the result
    //  - A chunk to see overflows for when components = kChunkSize
    static const size_t kChunkSize = 4;
    std::array<T, 3 * kChunkSize> buffer;
    buffer.fill(canary);

    GLint location = glGetUniformLocation(program, name.c_str());
    ASSERT_NE(location, -1);

    getUniformv(program, location, &buffer[kChunkSize]);
    for (size_t i = 0; i < kChunkSize; i++)
    {
        ASSERT_EQ(canary, buffer[i]);
    }
    for (size_t i = kChunkSize + components; i < buffer.size(); i++)
    {
        ASSERT_EQ(canary, buffer[i]);
    }
}

// Check that getting an element array doesn't return the whole array.
TEST_P(UniformTestES3, ReturnsOnlyOneArrayElement)
{

    static const size_t kArraySize = 4;
    struct UniformArrayInfo
    {
        UniformArrayInfo(std::string type, std::string name, int components)
            : type(type), name(name), components(components)
        {}
        std::string type;
        std::string name;
        int components;
    };

    // Check for various number of components and types
    std::vector<UniformArrayInfo> uniformArrays;
    uniformArrays.emplace_back("bool", "uBool", 1);
    uniformArrays.emplace_back("vec2", "uFloat", 2);
    uniformArrays.emplace_back("ivec3", "uInt", 3);
    uniformArrays.emplace_back("uvec4", "uUint", 4);

    std::ostringstream uniformStream;
    std::ostringstream additionStream;
    for (const auto &array : uniformArrays)
    {
        uniformStream << "uniform " << array.type << " " << array.name << "["
                      << ToString(kArraySize) << "];\n";

        // We need to make use of the uniforms or they get compiled out.
        for (int i = 0; i < 4; i++)
        {
            if (array.components == 1)
            {
                additionStream << " + float(" << array.name << "[" << i << "])";
            }
            else
            {
                for (int component = 0; component < array.components; component++)
                {
                    additionStream << " + float(" << array.name << "[" << i << "][" << component
                                   << "])";
                }
            }
        }
    }

    const std::string vertexShader = "#version 300 es\n" + uniformStream.str() +
                                     "void main()\n"
                                     "{\n"
                                     "    gl_Position = vec4(1.0" +
                                     additionStream.str() +
                                     ");\n"
                                     "}";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 color;\n"
        "void main ()\n"
        "{\n"
        "    color = vec4(1, 0, 0, 1);\n"
        "}";

    mProgram = CompileProgram(vertexShader.c_str(), kFS);
    ASSERT_NE(0u, mProgram);

    glUseProgram(mProgram);

    for (const auto &uniformArray : uniformArrays)
    {
        for (size_t index = 0; index < kArraySize; index++)
        {
            std::string strIndex = "[" + ToString(index) + "]";
            // Check all the different glGetUniformv functions
            CheckOneElement<float>(glGetUniformfv, mProgram, uniformArray.name + strIndex,
                                   uniformArray.components, 42.4242f);
            CheckOneElement<int>(glGetUniformiv, mProgram, uniformArray.name + strIndex,
                                 uniformArray.components, 0x7BADBED5);
            CheckOneElement<unsigned int>(glGetUniformuiv, mProgram, uniformArray.name + strIndex,
                                          uniformArray.components, 0xDEADBEEF);
        }
    }
}

// This test reproduces a regression when Intel windows driver upgrades to 4944. In some situation,
// when a boolean uniform with false value is used as the if and for condtions, the bug will be
// triggered. It seems that the shader doesn't get a right 'false' value from the uniform.
TEST_P(UniformTestES3, BooleanUniformAsIfAndForCondition)
{
    const char kFragShader[] =
        R"(#version 300 es
        precision mediump float;
        uniform bool u;
        out vec4 result;
        int sideEffectCounter;

        bool foo() {
          ++sideEffectCounter;
          return true;
        }

        void main() {
          sideEffectCounter = 0;
          bool condition = u;
          if (condition)
          {
            condition = foo();
          }
          for(int iterations = 0; condition;) {
            ++iterations;
            if (iterations >= 10) {
              break;
            }

            if (condition)
            {
              condition = foo();
            }
          }

          bool success = (!u && sideEffectCounter == 0);
          result = (success) ? vec4(0, 1.0, 0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFragShader);

    glUseProgram(program);

    GLint uniformLocation = glGetUniformLocation(program, "u");
    ASSERT_NE(uniformLocation, -1);

    glUniform1i(uniformLocation, GL_FALSE);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

class UniformTestES31 : public ANGLETest<>
{
  protected:
    UniformTestES31() : mProgram(0) {}

    void testTearDown() override
    {
        if (mProgram != 0)
        {
            glDeleteProgram(mProgram);
            mProgram = 0;
        }
    }

    GLuint mProgram;
};

// Test that uniform locations get set correctly for structure members.
// ESSL 3.10.4 section 4.4.3.
TEST_P(UniformTestES31, StructLocationLayoutQualifier)
{
    constexpr char kFS[] =
        "#version 310 es\n"
        "out highp vec4 my_FragColor;\n"
        "struct S\n"
        "{\n"
        "    highp float f;\n"
        "    highp float f2;\n"
        "};\n"
        "uniform layout(location=12) S uS;\n"
        "void main()\n"
        "{\n"
        "    my_FragColor = vec4(uS.f, uS.f2, 0, 1);\n"
        "}";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Zero(), kFS);

    EXPECT_EQ(12, glGetUniformLocation(program, "uS.f"));
    EXPECT_EQ(13, glGetUniformLocation(program, "uS.f2"));
}

// Set uniform location with a layout qualifier in the fragment shader. The same uniform exists in
// the vertex shader, but doesn't have a location specified there.
TEST_P(UniformTestES31, UniformLocationInFragmentShader)
{
    constexpr char kVS[] =
        "#version 310 es\n"
        "uniform highp sampler2D tex2D;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = texture(tex2D, vec2(0));\n"
        "}";

    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out vec4 my_FragColor;\n"
        "uniform layout(location=12) highp sampler2D tex2D;\n"
        "void main()\n"
        "{\n"
        "    my_FragColor = texture(tex2D, vec2(0));\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    EXPECT_EQ(12, glGetUniformLocation(program, "tex2D"));
}

// Test two unused uniforms that have the same location.
// ESSL 3.10.4 section 4.4.3: "No two default-block uniform variables in the program can have the
// same location, even if they are unused, otherwise a compiler or linker error will be generated."
TEST_P(UniformTestES31, UnusedUniformsConflictingLocation)
{
    constexpr char kVS[] =
        "#version 310 es\n"
        "uniform layout(location=12) highp sampler2D texA;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(0);\n"
        "}";

    constexpr char kFS[] =
        "#version 310 es\n"
        "out highp vec4 my_FragColor;\n"
        "uniform layout(location=12) highp sampler2D texB;\n"
        "void main()\n"
        "{\n"
        "    my_FragColor = vec4(0);\n"
        "}";

    mProgram = CompileProgram(kVS, kFS);
    EXPECT_EQ(0u, mProgram);
}

// Test two unused uniforms that have overlapping locations once all array elements are taken into
// account.
// ESSL 3.10.4 section 4.4.3: "No two default-block uniform variables in the program can have the
// same location, even if they are unused, otherwise a compiler or linker error will be generated."
TEST_P(UniformTestES31, UnusedUniformArraysConflictingLocation)
{
    constexpr char kVS[] =
        "#version 310 es\n"
        "uniform layout(location=11) highp vec4 uA[2];\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(0);\n"
        "}";

    constexpr char kFS[] =
        "#version 310 es\n"
        "out highp vec4 my_FragColor;\n"
        "uniform layout(location=12) highp vec4 uB;\n"
        "void main()\n"
        "{\n"
        "    my_FragColor = vec4(0);\n"
        "}";

    mProgram = CompileProgram(kVS, kFS);
    EXPECT_EQ(0u, mProgram);
}

// Test that having a combination of different float type uniforms with different precisions work
// properly with uniform sort
TEST_P(UniformTestES31, PackUniformFP16Test)
{
    setWindowWidth(1);
    setWindowHeight(1);
    constexpr char kFragShader[] = R"(#version 310 es
layout(std140, binding=0) buffer debugBlock {
    uvec4 data[];
};
precision mediump float;
struct Structs{
    float floatInStructs;
};
uniform float floatUniform;
uniform highp float highpFloatUniform;
uniform mat4 mat4Uniform;
uniform highp mat4 highpMat4Uniform;
uniform vec4 vec4Uniform;
uniform highp vec4 highpVec4Uniform;
uniform mat3x2 mat32Uniform;
uniform highp mat3x2 highpMat32Uniform;
uniform mat2x3 mat23Uniform;
uniform highp mat2x3 highpMat23Uniform;
uniform Structs structUniform;

out vec4 fragColor;

void main() {
  data[0] = floatBitsToUint(vec4(floatUniform, highpFloatUniform, structUniform.floatInStructs, 1.0));

  vec4 verifyMat4Uniform = vec4(1.0, 1.0, 1.0, 1.0) * mat4Uniform;
  data[1] = floatBitsToUint(verifyMat4Uniform);

  highp vec4 verifyHighpMat4Uniform = vec4(1.0, 1.0, 1.0, 1.0) * highpMat4Uniform;
  data[2] = floatBitsToUint(verifyHighpMat4Uniform);

  data[3] = floatBitsToUint(vec4Uniform);

  data[4] = floatBitsToUint(highpVec4Uniform);

  vec3 verifyMat3x2Uniform = vec2(1.0, 1.0) * mat32Uniform;
  data[5] = floatBitsToUint(vec4(verifyMat3x2Uniform, 1.0));

  highp vec3 verifyMat3x2HighpUniform = vec2(1.0, 1.0) * highpMat32Uniform;
  data[6] = floatBitsToUint(vec4(verifyMat3x2HighpUniform, 1.0));

  vec2 verifyMat2x3Uniform = vec3(1.0, 1.0, 1.0) * mat23Uniform;
  highp vec2 verifyMat2x3HighpUniform = vec3(1.0, 1.0, 1.0) * highpMat23Uniform;
  data[7] = floatBitsToUint(vec4(verifyMat2x3Uniform, verifyMat2x3HighpUniform));

  fragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint floatUniformLocation = glGetUniformLocation(program, "floatUniform");
    ASSERT_NE(floatUniformLocation, -1);
    GLint highpFloatUniformLocation = glGetUniformLocation(program, "highpFloatUniform");
    ASSERT_NE(highpFloatUniformLocation, -1);
    GLint mat4UniformLocation = glGetUniformLocation(program, "mat4Uniform");
    ASSERT_NE(mat4UniformLocation, -1);
    GLint highpMat4UniformLocation = glGetUniformLocation(program, "highpMat4Uniform");
    ASSERT_NE(highpMat4UniformLocation, -1);
    GLint vec4UniformLocation = glGetUniformLocation(program, "vec4Uniform");
    ASSERT_NE(vec4UniformLocation, -1);
    GLint highpVec4UniformLocation = glGetUniformLocation(program, "highpVec4Uniform");
    ASSERT_NE(highpVec4UniformLocation, -1);
    GLint mat32UniformLocation = glGetUniformLocation(program, "mat32Uniform");
    ASSERT_NE(mat32UniformLocation, -1);
    GLint highpMat32UniformLocation = glGetUniformLocation(program, "highpMat32Uniform");
    ASSERT_NE(highpMat32UniformLocation, -1);
    GLint mat23UniformLocation = glGetUniformLocation(program, "mat23Uniform");
    ASSERT_NE(mat23UniformLocation, -1);
    GLint highpMat23UniformLocation = glGetUniformLocation(program, "highpMat23Uniform");
    ASSERT_NE(highpMat23UniformLocation, -1);
    GLint structUniformLocation = glGetUniformLocation(program, "structUniform.floatInStructs");
    ASSERT_NE(structUniformLocation, -1);

    GLfloat floatUniformValue         = 1.0;
    GLfloat highpFloatUniformValue    = 131072.0;
    GLfloat mat4UniformValue[16]      = {1.0, 2.0,  3.0,  4.0,  5.0,  6.0,  7.0,  8.0,
                                         9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
    GLfloat highpMat4UniformValue[16] = {131072.0, 131073.0, 131074.0, 131075.0, 131076.0, 131077.0,
                                         131078.0, 131079.0, 131080.0, 131081.0, 131082.0, 131083.0,
                                         131084.0, 131085.0, 131086.0, 131087.0};
    GLfloat vec4UniformValue[4]       = {17.0, 18.0, 19.0, 20.0};
    GLfloat highpVec4UniformValue[4]  = {131088.0, 131089.0, 131090.0, 131091.0};
    GLfloat mat32UniformValue[6]      = {21.0, 22.0, 23.0, 24.0, 25.0, 26.0};
    GLfloat highpMat32UniformValue[6] = {131092.0, 131093.0, 131094.0,
                                         131095.0, 131096.0, 131097.0};
    GLfloat mat23UniformValue[6]      = {27.0, 28.0, 29.0, 30.0, 31.0, 32.0};
    GLfloat highpMat23UniformValue[6] = {131098.0, 131099.0, 131100.0,
                                         131101.0, 131102.0, 131103.0};
    GLfloat structUniformValue        = 33.0;
    glUniform1f(floatUniformLocation, floatUniformValue);
    glUniform1f(highpFloatUniformLocation, highpFloatUniformValue);
    glUniformMatrix4fv(mat4UniformLocation, 1, GL_FALSE, mat4UniformValue);
    glUniformMatrix4fv(highpMat4UniformLocation, 1, GL_FALSE, highpMat4UniformValue);
    glUniform4fv(vec4UniformLocation, 1, vec4UniformValue);
    glUniform4fv(highpVec4UniformLocation, 1, highpVec4UniformValue);
    glUniformMatrix3x2fv(mat32UniformLocation, 1, GL_FALSE, mat32UniformValue);
    glUniformMatrix3x2fv(highpMat32UniformLocation, 1, GL_FALSE, highpMat32UniformValue);
    glUniformMatrix2x3fv(mat23UniformLocation, 1, GL_FALSE, mat23UniformValue);
    glUniformMatrix2x3fv(highpMat23UniformLocation, 1, GL_FALSE, highpMat23UniformValue);
    glUniform1f(structUniformLocation, structUniformValue);
    constexpr GLint kBufferSize = 8 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);
    ASSERT_GL_NO_ERROR();

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    float *data = static_cast<float *>(ptr);
    ASSERT_EQ(data[0], 1);
    ASSERT_EQ(data[1], 131072);
    ASSERT_EQ(data[2], 33);
    ASSERT_EQ(data[3], 1);
    ASSERT_EQ(data[4], 10);
    ASSERT_EQ(data[5], 26);
    ASSERT_EQ(data[6], 42);
    ASSERT_EQ(data[7], 58);
    ASSERT_EQ(data[8], 524294);
    ASSERT_EQ(data[9], 524310);
    ASSERT_EQ(data[10], 524326);
    ASSERT_EQ(data[11], 524342);
    ASSERT_EQ(data[12], 17);
    ASSERT_EQ(data[13], 18);
    ASSERT_EQ(data[14], 19);
    ASSERT_EQ(data[15], 20);
    ASSERT_EQ(data[16], 131088);
    ASSERT_EQ(data[17], 131089);
    ASSERT_EQ(data[18], 131090);
    ASSERT_EQ(data[19], 131091);
    ASSERT_EQ(data[20], 43);
    ASSERT_EQ(data[21], 47);
    ASSERT_EQ(data[22], 51);
    ASSERT_EQ(data[23], 1);
    ASSERT_EQ(data[24], 262185);
    ASSERT_EQ(data[25], 262189);
    ASSERT_EQ(data[26], 262193);
    ASSERT_EQ(data[27], 1);
    ASSERT_EQ(data[28], 84);
    ASSERT_EQ(data[29], 93);
    ASSERT_EQ(data[30], 393297);
    ASSERT_EQ(data[31], 393306);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);

    glDeleteProgram(program);
}

// Test that nested struct uniform with mediump float works properly with the float being
// transformed from 32-bit to 16-bit. Also check that highp float member and non-float members
// are unaffected.
TEST_P(UniformTestES31, PackUniformFP16TestNestedStruct)
{
    setWindowWidth(1);
    setWindowHeight(1);
    constexpr char kFragShader[] = R"(#version 310 es
layout(std140, binding=0) buffer debugBlock {
     uvec4 data[];
};
 precision mediump float;
 struct InnerStruct {
    highp float highpFloatInStructs;
    float mediumpFloatInStructs;
    uint integerInStructs;
 };

 struct OuterStruct {
     InnerStruct structMember;
 };
 uniform OuterStruct structUniform;


 out vec4 fragColor;

 void main() {
   data[0] = floatBitsToUint(vec4(structUniform.structMember.mediumpFloatInStructs, structUniform.structMember.highpFloatInStructs, 0.0, 1.0));
   data[0].z = structUniform.structMember.integerInStructs;

  fragColor = vec4(1.0, 0.0, 0.0, 1.0);
})";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);
    GLint mediumpFloatInStructsUniformLocation =
        glGetUniformLocation(program, "structUniform.structMember.mediumpFloatInStructs");
    ASSERT_NE(mediumpFloatInStructsUniformLocation, -1);
    GLint highpFloatInStructsUniformLocation =
        glGetUniformLocation(program, "structUniform.structMember.highpFloatInStructs");
    ASSERT_NE(highpFloatInStructsUniformLocation, -1);
    GLint integerInStructsUniformLocation =
        glGetUniformLocation(program, "structUniform.structMember.integerInStructs");
    ASSERT_NE(integerInStructsUniformLocation, -1);
    GLfloat mediumpFloatInStructUniformValue = 1.0;
    glUniform1f(mediumpFloatInStructsUniformLocation, mediumpFloatInStructUniformValue);
    GLfloat highpFloatInStructUniformValue = 131072.0;
    glUniform1f(highpFloatInStructsUniformLocation, highpFloatInStructUniformValue);
    GLuint integerInStructUniformValue = 10;
    glUniform1ui(integerInStructsUniformLocation, integerInStructUniformValue);
    constexpr GLint kBufferSize = 1 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    unsigned int *data = static_cast<unsigned int *>(ptr);
    ASSERT_EQ(*reinterpret_cast<float *>(&data[0]), 1.0);
    ASSERT_EQ(*reinterpret_cast<float *>(&data[1]), 131072.0);
    ASSERT_EQ(data[2], 10u);
    ASSERT_EQ(*reinterpret_cast<float *>(&data[3]), 1.0);
    ASSERT_GL_NO_ERROR();

    glDeleteProgram(program);
}

// Test that TCompiler::sortUniforms() does not break the shader code when there are multiple
// uniforms of the same struct data type, and one of them is the struct specifier.
TEST_P(UniformTestES31, UniformReorderDoesNotBreakStructUniforms)
{
    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out highp vec4 my_FragColor;\n"
        "uniform struct{vec3 b;}G[1],S;\n"
        "void main()\n"
        "{}";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFS);
    ASSERT_NE(program, 0u);
}

// That that TCompiler::sortUniforms() does not break the shader code when there are multiple
// uniforms of the struct data type, and both of them are struct specifiers, and one struct
// references the other struct.
TEST_P(UniformTestES31, UniformReorderDoesNotBreakStructUniformsV2)
{
    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "uniform struct S1 { samplerCube ar; } a1;\n"
        "uniform struct S2 { S1 s; } a2;\n"
        "void main (void)\n"
        "{}";
    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFS);
    ASSERT_NE(program, 0u);
}

// Test that with the PackedSPIRVBlockEncoder applies to "defaultUniform" ubo interface block,
// it does not incorrectly interfere with other structs that are also named "defaultUniforms"
TEST_P(UniformTestES31, PackedEncoderDoesNotApplyToStructNamedDefaultUniforms)
{
    setWindowWidth(1);
    setWindowHeight(1);
    constexpr char kFragShader[] = R"(#version 310 es
        layout(std140, binding=0) buffer debugBlock {
            uvec4 data[];
        };

        precision mediump float;
        uniform vec3 vec3Uniform1;
        uniform vec3 vec3Uniform2;
        struct defaultUniforms {
          vec3 vec3Uniform1;
          vec3 vec3Uniform2;
        };

        out highp vec4 my_color;
        void main() {
            defaultUniforms nonDefaultUniform = defaultUniforms(vec3(1.0, 2.0, 3.0), vec3(4.0, 5.0, 6.0));
            data[0] = floatBitsToUint(vec4(nonDefaultUniform.vec3Uniform1.x, nonDefaultUniform.vec3Uniform1.y, nonDefaultUniform.vec3Uniform1.z, 1.0));
            data[1] = floatBitsToUint(vec4(nonDefaultUniform.vec3Uniform2.x, nonDefaultUniform.vec3Uniform2.y, nonDefaultUniform.vec3Uniform2.z, 1.0));
            data[2] = floatBitsToUint(vec4(vec3Uniform1.x, vec3Uniform1.y, vec3Uniform1.z, 1.0));
            data[3] = floatBitsToUint(vec4(vec3Uniform2.x, vec3Uniform2.y, vec3Uniform2.z, 1.0));
            my_color = vec4(1.0, 0.0, 0.0, 1.0);
        })";
    GLuint program               = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint vec3Uniform1Location = glGetUniformLocation(program, "vec3Uniform1");
    ASSERT_NE(vec3Uniform1Location, -1);
    GLfloat vec3Uniform1Value[3] = {11.0f, 12.0f, 13.0f};
    glUniform3fv(vec3Uniform1Location, 1, vec3Uniform1Value);

    GLint vec3Uniform2Location = glGetUniformLocation(program, "vec3Uniform2");
    ASSERT_NE(vec3Uniform2Location, -1);
    GLfloat vec3Uniform2Value[3] = {14.0f, 15.0f, 16.0f};
    glUniform3fv(vec3Uniform2Location, 1, vec3Uniform2Value);

    constexpr GLint kBufferSize = 4 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    float *data = static_cast<float *>(ptr);

    ASSERT_EQ(data[0], 1);
    ASSERT_EQ(data[1], 2);
    ASSERT_EQ(data[2], 3);
    ASSERT_EQ(data[3], 1);
    ASSERT_EQ(data[4], 4);
    ASSERT_EQ(data[5], 5);
    ASSERT_EQ(data[6], 6);
    ASSERT_EQ(data[7], 1);
    ASSERT_EQ(data[8], 11);
    ASSERT_EQ(data[9], 12);
    ASSERT_EQ(data[10], 13);
    ASSERT_EQ(data[11], 1);
    ASSERT_EQ(data[12], 14);
    ASSERT_EQ(data[13], 15);
    ASSERT_EQ(data[14], 16);
    ASSERT_EQ(data[15], 1);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    glDeleteProgram(program);
}

// Test that with the PackedSPIRVBlockEncoder applies to "defaultUniform" ubo interface block,
// it does not incorrectly interfere with other ubo created that are also named "defaultUniforms"
TEST_P(UniformTestES31, PackedEncoderDoesNotApplyToCustomizedUBONamedDefaultUniforms)
{
    setWindowWidth(1);
    setWindowHeight(1);

    constexpr char kFragShader[] = R"(#version 310 es
        layout(std140, binding=0) buffer debugBlock {
            uvec4 data[];
        };

        precision mediump float;
        uniform vec3 vec3Uniform1;
        uniform vec3 vec3Uniform2;
        layout(std140) uniform defaultUniforms {
            vec3 vec3Uniform1;
            vec3 vec3Uniform2;
        } defaultUniformsBufferInstance;

        out highp vec4 my_color;
        void main() {
            data[0] = floatBitsToUint(vec4(defaultUniformsBufferInstance.vec3Uniform1.x, defaultUniformsBufferInstance.vec3Uniform1.y, defaultUniformsBufferInstance.vec3Uniform1.z, 1.0));
            data[1] = floatBitsToUint(vec4(defaultUniformsBufferInstance.vec3Uniform2.x, defaultUniformsBufferInstance.vec3Uniform2.y, defaultUniformsBufferInstance.vec3Uniform2.z, 1.0));
            data[2] = floatBitsToUint(vec4(vec3Uniform1.x, vec3Uniform1.y, vec3Uniform1.z, 1.0));
            data[3] = floatBitsToUint(vec4(vec3Uniform2.x, vec3Uniform2.y, vec3Uniform2.z, 1.0));
            my_color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint vec3Uniform1Location = glGetUniformLocation(program, "vec3Uniform1");
    ASSERT_NE(vec3Uniform1Location, -1);
    GLfloat vec3Uniform1Value[3] = {11.0f, 12.0f, 13.0f};
    glUniform3fv(vec3Uniform1Location, 1, vec3Uniform1Value);

    GLint vec3Uniform2Location = glGetUniformLocation(program, "vec3Uniform2");
    ASSERT_NE(vec3Uniform2Location, -1);
    GLfloat vec3Uniform2Value[3] = {14.0f, 15.0f, 16.0f};
    glUniform3fv(vec3Uniform2Location, 1, vec3Uniform2Value);

    std::vector<float> uboData;
    uboData.insert(uboData.end(), {1.0f, 2.0f, 3.0f, 0.0f});
    uboData.insert(uboData.end(), {4.0f, 5.0f, 6.0f, 0.0f});
    GLBuffer ubo;
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);
    glBufferData(GL_UNIFORM_BUFFER, uboData.size() * sizeof(float), uboData.data(), GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    GLuint uboIndex = glGetUniformBlockIndex(program, "defaultUniforms");
    EXPECT_NE(GL_INVALID_INDEX, uboIndex);
    glBindBufferRange(GL_UNIFORM_BUFFER, 0, ubo, 0, uboData.size() * sizeof(float));
    glUniformBlockBinding(program, uboIndex, 0);

    constexpr GLint kBufferSize = 4 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    float *data = static_cast<float *>(ptr);

    ASSERT_EQ(data[0], 1);
    ASSERT_EQ(data[1], 2);
    ASSERT_EQ(data[2], 3);
    ASSERT_EQ(data[3], 1);
    ASSERT_EQ(data[4], 4);
    ASSERT_EQ(data[5], 5);
    ASSERT_EQ(data[6], 6);
    ASSERT_EQ(data[7], 1);
    ASSERT_EQ(data[8], 11);
    ASSERT_EQ(data[9], 12);
    ASSERT_EQ(data[10], 13);
    ASSERT_EQ(data[11], 1);
    ASSERT_EQ(data[12], 14);
    ASSERT_EQ(data[13], 15);
    ASSERT_EQ(data[14], 16);
    ASSERT_EQ(data[15], 1);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    glDeleteProgram(program);
}

// Same as PackedEncoderDoesNotApplyToCustomizedUBONamedDefaultUniforms, except the SSBO
// data type is changed from uvec4 to vec4 to add more coverage
TEST_P(UniformTestES31, PackedEncoderDoesNotApplyToCustomizedUBONamedDefaultUniformsV2)
{
    setWindowWidth(1);
    setWindowHeight(1);

    constexpr char kFragShader[] = R"(#version 310 es
        precision mediump float;
        layout(std140, binding=0) buffer debugBlock {
            vec4 data[];
        };
        uniform vec3 vec3Uniform1;
        uniform vec3 vec3Uniform2;
        layout(std140) uniform defaultUniforms {
            vec3 vec3Uniform1;
            vec3 vec3Uniform2;
        } defaultUniformsBufferInstance;

        out highp vec4 my_color;
        void main() {
            data[0] = vec4(defaultUniformsBufferInstance.vec3Uniform1.x, defaultUniformsBufferInstance.vec3Uniform1.y, defaultUniformsBufferInstance.vec3Uniform1.z, 1.0);
            data[1] = vec4(defaultUniformsBufferInstance.vec3Uniform2.x, defaultUniformsBufferInstance.vec3Uniform2.y, defaultUniformsBufferInstance.vec3Uniform2.z, 1.0);
            data[2] = vec4(vec3Uniform1.x, vec3Uniform1.y, vec3Uniform1.z, 1.0);
            data[3] = vec4(vec3Uniform2.x, vec3Uniform2.y, vec3Uniform2.z, 1.0);
            my_color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLint vec3Uniform1Location = glGetUniformLocation(program, "vec3Uniform1");
    ASSERT_NE(vec3Uniform1Location, -1);
    GLfloat vec3Uniform1Value[3] = {11.0f, 12.0f, 13.0f};
    glUniform3fv(vec3Uniform1Location, 1, vec3Uniform1Value);

    GLint vec3Uniform2Location = glGetUniformLocation(program, "vec3Uniform2");
    ASSERT_NE(vec3Uniform2Location, -1);
    GLfloat vec3Uniform2Value[3] = {14.0f, 15.0f, 16.0f};
    glUniform3fv(vec3Uniform2Location, 1, vec3Uniform2Value);

    std::vector<float> uboData;
    uboData.insert(uboData.end(), {1.0f, 2.0f, 3.0f, 0.0f});
    uboData.insert(uboData.end(), {4.0f, 5.0f, 6.0f, 0.0f});
    GLBuffer ubo;
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);
    glBufferData(GL_UNIFORM_BUFFER, uboData.size() * sizeof(float), uboData.data(), GL_STATIC_DRAW);
    ASSERT_GL_NO_ERROR();

    GLuint uboIndex = glGetUniformBlockIndex(program, "defaultUniforms");
    EXPECT_NE(GL_INVALID_INDEX, uboIndex);
    glBindBufferRange(GL_UNIFORM_BUFFER, 0, ubo, 0, uboData.size() * sizeof(float));
    glUniformBlockBinding(program, uboIndex, 0);

    constexpr GLint kBufferSize = 4 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    float *data = static_cast<float *>(ptr);

    ASSERT_EQ(data[0], 1);
    ASSERT_EQ(data[1], 2);
    ASSERT_EQ(data[2], 3);
    ASSERT_EQ(data[3], 1);
    ASSERT_EQ(data[4], 4);
    ASSERT_EQ(data[5], 5);
    ASSERT_EQ(data[6], 6);
    ASSERT_EQ(data[7], 1);
    ASSERT_EQ(data[8], 11);
    ASSERT_EQ(data[9], 12);
    ASSERT_EQ(data[10], 13);
    ASSERT_EQ(data[11], 1);
    ASSERT_EQ(data[12], 14);
    ASSERT_EQ(data[13], 15);
    ASSERT_EQ(data[14], 16);
    ASSERT_EQ(data[15], 1);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    glDeleteProgram(program);
}

// Test PackedSPIRVBlockEncoder works properly for array of struct default uniforms
TEST_P(UniformTestES31, PackedEncoderWorksForArrayOfStructs)
{
    setWindowWidth(1);
    setWindowHeight(1);

    constexpr char kFragShader[] = R"(#version 310 es
        precision mediump float;
        layout(std140, binding=0) buffer debugBlock {
            vec4 data[];
        };

        struct UniformStruct {
            float floatMember;
            vec2 vec2Member;
            vec3 vec3Member;
            vec4 vec4Member;
        };

        uniform UniformStruct uniformStructArray[3];

        out highp vec4 my_color;
        void main() {
            for (int i = 0; i < 3; ++i) {
                int offset = i * 3;
                data[offset + 0] = vec4(uniformStructArray[i].floatMember, uniformStructArray[i].vec2Member, 0.0);
                data[offset + 1] = vec4(uniformStructArray[i].vec3Member, 0.0);
                data[offset + 2] = uniformStructArray[i].vec4Member;
            }
            my_color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLfloat expectedData[36] = {
        0.0f,  1.0f,  2.0f,  0.0f, 3.0f,  4.0f,  5.0f,  0.0f, 6.0f,  7.0f,  8.0f,  9.0f,

        10.0f, 11.0f, 12.0f, 0.0f, 13.0f, 14.0f, 15.0f, 0.0f, 16.0f, 17.0f, 18.0f, 19.0f,

        20.0f, 21.0f, 22.0f, 0.0f, 23.0f, 24.0f, 25.0f, 0.0f, 26.0f, 27.0f, 28.0f, 29.0f,
    };

    for (int i = 0; i < 3; ++i)
    {
        std::string base = "uniformStructArray[" + std::to_string(i) + "].";
        int indexOffset  = i * 12;

        GLfloat floatVal          = expectedData[indexOffset];
        GLint floatMemberLocation = glGetUniformLocation(program, (base + "floatMember").c_str());
        ASSERT_NE(floatMemberLocation, -1);
        glUniform1f(floatMemberLocation, floatVal);

        GLfloat vec2Val[2]       = {expectedData[indexOffset + 1], expectedData[indexOffset + 2]};
        GLint vec2MemberLocation = glGetUniformLocation(program, (base + "vec2Member").c_str());
        ASSERT_NE(vec2MemberLocation, -1);
        glUniform2fv(vec2MemberLocation, 1, vec2Val);

        GLfloat vec3Val[3]       = {expectedData[indexOffset + 4], expectedData[indexOffset + 5],
                                    expectedData[indexOffset + 6]};
        GLint vec3MemberLocation = glGetUniformLocation(program, (base + "vec3Member").c_str());
        ASSERT_NE(vec3MemberLocation, -1);
        glUniform3fv(vec3MemberLocation, 1, vec3Val);

        GLfloat vec4Val[4]       = {expectedData[indexOffset + 8], expectedData[indexOffset + 9],
                                    expectedData[indexOffset + 10], expectedData[indexOffset + 11]};
        GLint vec4MemberLocation = glGetUniformLocation(program, (base + "vec4Member").c_str());
        glUniform4fv(vec4MemberLocation, 1, vec4Val);
    }

    constexpr GLint kBufferSize = 9 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    float *data = static_cast<float *>(ptr);

    for (int i = 0; i < 36; ++i)
    {
        ASSERT_EQ(data[i], expectedData[i]);
    }

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    glDeleteProgram(program);
}

// Test PackedSPIRVBlockEncoder works properly for array of nested struct default uniforms
TEST_P(UniformTestES31, PackedEncoderWorksForArrayOfNestedStructs)
{
    setWindowWidth(1);
    setWindowHeight(1);

    constexpr char kFragShader[] = R"(#version 310 es
        precision mediump float;
        layout(std140, binding=0) buffer debugBlock {
            vec4 data[];
        };

        struct InnerStruct {
            float innerFloatMember;
            vec2 innerVec2Member;
            vec3 innerVec3Member;
            vec4 innerVec4Member;
        };

        struct OuterStruct {
            InnerStruct innerStructMember;
            float outerFloatMember;
            vec2 outerVec2Member;
            vec3 outerVec3Member;
            vec4 outerVec4Member;
        };

        uniform OuterStruct uniformNestedStructArray[3];

        out highp vec4 my_color;
        void main() {
            for (int i = 0; i < 3; ++i) {
                int offset = i * 6;
                data[offset + 0] = vec4(uniformNestedStructArray[i].innerStructMember.innerFloatMember, uniformNestedStructArray[i].innerStructMember.innerVec2Member, 0.0);
                data[offset + 1] = vec4(uniformNestedStructArray[i].innerStructMember.innerVec3Member, 0.0);
                data[offset + 2] = uniformNestedStructArray[i].innerStructMember.innerVec4Member;
                data[offset + 3] = vec4(uniformNestedStructArray[i].outerFloatMember, uniformNestedStructArray[i].outerVec2Member, 0.0);
                data[offset + 4] = vec4(uniformNestedStructArray[i].outerVec3Member, 0.0);
                data[offset + 5] = uniformNestedStructArray[i].outerVec4Member;
            }
            my_color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    GLuint program = CompileProgram(essl31_shaders::vs::Simple(), kFragShader);
    ASSERT_NE(program, 0u);
    glUseProgram(program);

    GLfloat expectedData[72] = {
        0.0f,  1.0f,  2.0f,  0.0f, 3.0f,  4.0f,  5.0f,  0.0f, 6.0f,  7.0f,  8.0f,  9.0f,

        10.0f, 11.0f, 12.0f, 0.0f, 13.0f, 14.0f, 15.0f, 0.0f, 16.0f, 17.0f, 18.0f, 19.0f,

        20.0f, 21.0f, 22.0f, 0.0f, 23.0f, 24.0f, 25.0f, 0.0f, 26.0f, 27.0f, 28.0f, 29.0f,

        30.0f, 31.0f, 32.0f, 0.0f, 33.0f, 34.0f, 35.0f, 0.0f, 36.0f, 37.0f, 38.0f, 39.0f,

        40.0f, 41.0f, 42.0f, 0.0f, 43.0f, 44.0f, 45.0f, 0.0f, 46.0f, 47.0f, 48.0f, 49.0f,

        50.0f, 51.0f, 52.0f, 0.0f, 53.0f, 54.0f, 55.0f, 0.0f, 56.0f, 57.0f, 58.0f, 59.0f,
    };

    for (int i = 0; i < 3; ++i)
    {
        std::string base = "uniformNestedStructArray[" + std::to_string(i) + "].";
        int indexOffset  = i * 24;

        GLfloat innerStructFloatVal = expectedData[indexOffset];
        GLint innerStructFloatLocation =
            glGetUniformLocation(program, (base + "innerStructMember.innerFloatMember").c_str());
        ASSERT_NE(innerStructFloatLocation, -1);
        glUniform1f(innerStructFloatLocation, innerStructFloatVal);

        GLfloat innerStructVec2Val[2] = {expectedData[indexOffset + 1],
                                         expectedData[indexOffset + 2]};
        GLint innerStructVec2Location =
            glGetUniformLocation(program, (base + "innerStructMember.innerVec2Member").c_str());
        ASSERT_NE(innerStructVec2Location, -1);
        glUniform2fv(innerStructVec2Location, 1, innerStructVec2Val);

        GLfloat innerStructVec3Val[3] = {expectedData[indexOffset + 4],
                                         expectedData[indexOffset + 5],
                                         expectedData[indexOffset + 6]};
        GLint innerStructVec3Location =
            glGetUniformLocation(program, (base + "innerStructMember.innerVec3Member").c_str());
        ASSERT_NE(innerStructVec3Location, -1);
        glUniform3fv(innerStructVec3Location, 1, innerStructVec3Val);

        GLfloat innerStructVec4Val[4] = {
            expectedData[indexOffset + 8], expectedData[indexOffset + 9],
            expectedData[indexOffset + 10], expectedData[indexOffset + 11]};
        GLint innerStructVec4Location =
            glGetUniformLocation(program, (base + "innerStructMember.innerVec4Member").c_str());
        ASSERT_NE(innerStructVec4Location, -1);
        glUniform4fv(innerStructVec4Location, 1, innerStructVec4Val);

        GLfloat floatVal = expectedData[indexOffset + 12];
        GLint floatMemberLocation =
            glGetUniformLocation(program, (base + "outerFloatMember").c_str());
        ASSERT_NE(floatMemberLocation, -1);
        glUniform1f(floatMemberLocation, floatVal);

        GLfloat vec2Val[2] = {expectedData[indexOffset + 13], expectedData[indexOffset + 14]};
        GLint vec2MemberLocation =
            glGetUniformLocation(program, (base + "outerVec2Member").c_str());
        ASSERT_NE(vec2MemberLocation, -1);
        glUniform2fv(vec2MemberLocation, 1, vec2Val);

        GLfloat vec3Val[3] = {expectedData[indexOffset + 16], expectedData[indexOffset + 17],
                              expectedData[indexOffset + 18]};
        GLint vec3MemberLocation =
            glGetUniformLocation(program, (base + "outerVec3Member").c_str());
        ASSERT_NE(vec3MemberLocation, -1);
        glUniform3fv(vec3MemberLocation, 1, vec3Val);

        GLfloat vec4Val[4] = {expectedData[indexOffset + 20], expectedData[indexOffset + 21],
                              expectedData[indexOffset + 22], expectedData[indexOffset + 23]};
        GLint vec4MemberLocation =
            glGetUniformLocation(program, (base + "outerVec4Member").c_str());
        glUniform4fv(vec4MemberLocation, 1, vec4Val);
    }

    constexpr GLint kBufferSize = 9 * 4 * sizeof(GLuint);
    GLBuffer buffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, kBufferSize, nullptr, GL_STATIC_DRAW);
    glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, buffer, 0, kBufferSize);

    glClearColor(1.0, 1.0, 1.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();

    void *ptr = glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, kBufferSize, GL_MAP_READ_BIT);
    ASSERT_GL_NO_ERROR();
    ASSERT(ptr);
    float *data = static_cast<float *>(ptr);

    for (int i = 0; i < 36; ++i)
    {
        ASSERT_EQ(data[i], expectedData[i]);
    }

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    glDeleteProgram(program);
}

// Test a uniform struct containing a non-square matrix and a boolean.
// Minimal test case for a bug revealed by dEQP tests.
TEST_P(UniformTestES3, StructWithNonSquareMatrixAndBool)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "out highp vec4 my_color;\n"
        "struct S\n"
        "{\n"
        "    mat2x4 m;\n"
        "    bool b;\n"
        "};\n"
        "uniform S uni;\n"
        "void main()\n"
        "{\n"
        "    my_color = vec4(1.0);\n"
        "    if (!uni.b) { my_color.g = 0.0; }"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);

    glUseProgram(program);

    GLint location = glGetUniformLocation(program, "uni.b");
    ASSERT_NE(-1, location);
    glUniform1i(location, 1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that matrix uniform upload is correct.
TEST_P(UniformTestES3, MatrixUniformUpload)
{
    constexpr size_t kMinDims = 2;
    constexpr size_t kMaxDims = 4;

    GLfloat matrixValues[kMaxDims * kMaxDims];

    for (size_t i = 0; i < kMaxDims * kMaxDims; ++i)
    {
        matrixValues[i] = static_cast<GLfloat>(i);
    }

    using UniformMatrixCxRfv = decltype(glUniformMatrix2fv);
    UniformMatrixCxRfv uniformMatrixCxRfv[kMaxDims + 1][kMaxDims + 1] = {
        {nullptr, nullptr, nullptr, nullptr, nullptr},
        {nullptr, nullptr, nullptr, nullptr, nullptr},
        {nullptr, nullptr, glUniformMatrix2fv, glUniformMatrix2x3fv, glUniformMatrix2x4fv},
        {nullptr, nullptr, glUniformMatrix3x2fv, glUniformMatrix3fv, glUniformMatrix3x4fv},
        {nullptr, nullptr, glUniformMatrix4x2fv, glUniformMatrix4x3fv, glUniformMatrix4fv},
    };

    for (int transpose = 0; transpose < 2; ++transpose)
    {
        for (size_t cols = kMinDims; cols <= kMaxDims; ++cols)
        {
            for (size_t rows = kMinDims; rows <= kMaxDims; ++rows)
            {
                std::ostringstream shader;
                shader << "#version 300 es\n"
                          "precision highp float;\n"
                          "out highp vec4 colorOut;\n"
                          "uniform mat"
                       << cols << 'x' << rows
                       << " unused;\n"
                          "uniform mat"
                       << cols << 'x' << rows
                       << " m;\n"
                          "void main()\n"
                          "{\n"
                          "  bool isCorrect =";

                for (size_t col = 0; col < cols; ++col)
                {
                    for (size_t row = 0; row < rows; ++row)
                    {
                        size_t value;
                        if (!transpose)
                        {
                            // Matrix data is uploaded column-major.
                            value = col * rows + row;
                        }
                        else
                        {
                            // Matrix data is uploaded row-major.
                            value = row * cols + col;
                        }

                        if (value != 0)
                        {
                            shader << "&&\n    ";
                        }

                        shader << "(m[" << col << "][" << row << "] == " << value << ".0)";
                    }
                }

                shader << ";\n  colorOut = vec4(isCorrect);\n"
                          "}\n";

                ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), shader.str().c_str());

                glUseProgram(program);

                GLint location = glGetUniformLocation(program, "m");
                ASSERT_NE(-1, location);

                uniformMatrixCxRfv[cols][rows](location, 1, transpose != 0, matrixValues);
                ASSERT_GL_NO_ERROR();

                drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);

                ASSERT_GL_NO_ERROR();
                EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white)
                    << " transpose = " << transpose << ", cols = " << cols << ", rows = " << rows;
            }
        }
    }
}

// Test that uniforms with reserved OpenGL names that aren't reserved in GL ES 2 work correctly.
TEST_P(UniformTest, UniformWithReservedOpenGLName)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "uniform float buffer;"
        "void main() {\n"
        "    gl_FragColor = vec4(buffer);\n"
        "}";

    mProgram = CompileProgram(essl1_shaders::vs::Simple(), kFS);
    ASSERT_NE(mProgram, 0u);

    GLint location = glGetUniformLocation(mProgram, "buffer");
    ASSERT_NE(-1, location);

    glUseProgram(mProgram);
    glUniform1f(location, 1.0f);

    drawQuad(mProgram, essl1_shaders::PositionAttrib(), 0.0f);

    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that unused sampler array elements do not corrupt used sampler array elements. Checks for a
// bug where unused samplers in an array would mark the whole array unused.
TEST_P(UniformTest, UnusedUniformsInSamplerArray)
{
    constexpr char kVS[] = R"(precision highp float;
attribute vec4 position;
varying vec2 texcoord;
void main()
{
    gl_Position = position;
    texcoord = (position.xy * 0.5) + 0.5;
})";
    constexpr char kFS[] = R"(precision highp float;
uniform sampler2D tex[3];
varying vec2 texcoord;
void main()
{
    gl_FragColor = texture2D(tex[0], texcoord);
})";

    mProgram = CompileProgram(kVS, kFS);

    ASSERT_NE(mProgram, 0u);
    GLint texLocation = glGetUniformLocation(mProgram, "tex[0]");
    ASSERT_NE(-1, texLocation);
    glUseProgram(mProgram);
    glUniform1i(texLocation, 0);
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    constexpr GLsizei kTextureSize = 2;
    std::vector<GLColor> textureData(kTextureSize * kTextureSize, GLColor::green);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, kTextureSize, kTextureSize, 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, textureData.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    drawQuad(mProgram, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

TEST_P(UniformTest, UnusedStructInlineUniform)
{
    constexpr char kVS[] = R"(precision highp float;
attribute vec4 position;
void main()
{
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision highp float;
uniform struct {
  vec3  aVec3;
  vec2 aVec2;
}aUniform;
varying vec2 texcoord;
void main()
{
    gl_FragColor = vec4(0,1,0,1);
})";

    mProgram = CompileProgram(kVS, kFS);
    ASSERT_NE(mProgram, 0u);
    drawQuad(mProgram, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

TEST_P(UniformTest, UnusedStructInlineUniformWithSampler)
{
    constexpr char kVS[] = R"(precision highp float;
attribute vec4 position;
void main()
{
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision highp float;
uniform struct {
  sampler2D  aSampler;
  vec3 aVec3;
}aUniform;
varying vec2 texcoord;
void main()
{
    gl_FragColor = vec4(0,1,0,1);
})";

    mProgram = CompileProgram(kVS, kFS);
    ASSERT_NE(mProgram, 0u);
    drawQuad(mProgram, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Bug: chromium:4210448 : Ensure programs properly
// compiles and renders where the uniforms form
// a struct with an alignment not matched with
// the actual size of the individual members.
// (Metal)
TEST_P(UniformTest, Vec4Vec2SizeAlignment)
{
    constexpr char kVS[] = R"(precision highp float;
attribute vec4 position;
uniform vec4 uniformA;
uniform vec4 uniformB;
uniform vec2 uniformC;
void main()
{
    gl_Position = position+uniformA +
    uniformB + vec4(uniformC.x, uniformC.y, 0, 0);
})";
    constexpr char kFS[] = R"(precision highp float;
void main()
{
    gl_FragColor = vec4(0,1,0,1);
})";
    mProgram             = CompileProgram(kVS, kFS);
    ASSERT_NE(mProgram, 0u);
    drawQuad(mProgram, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Regression test for D3D11 packing of 3x3 matrices followed by three floats. The setting of the
// matrix would overwrite the floats which is packed right after. http://anglebug.com/42266878,
// http://crbug.com/345525082
TEST_P(UniformTestES3, ExpandedFloatMatrix3Packing)
{
    constexpr char vs[] = R"(precision highp float;
attribute vec4 position;
void main()
{
    gl_Position = position;
})";

    constexpr char fs[] = R"(precision mediump float;
struct s
{
    mat3 umat3;
    float ufloat1;
    float ufloat2;
    float ufloat3;
};
uniform s u;
void main() {
    gl_FragColor = vec4(u.umat3[0][0], u.ufloat1, u.ufloat2, u.ufloat3);
})";

    ANGLE_GL_PROGRAM(program, vs, fs);
    glUseProgram(program);

    GLint umat3Location = glGetUniformLocation(program, "u.umat3");
    ASSERT_NE(umat3Location, -1);

    GLint ufloat1Location = glGetUniformLocation(program, "u.ufloat1");
    ASSERT_NE(ufloat1Location, -1);

    GLint ufloat2Location = glGetUniformLocation(program, "u.ufloat2");
    ASSERT_NE(ufloat2Location, -1);

    GLint ufloat3Location = glGetUniformLocation(program, "u.ufloat3");
    ASSERT_NE(ufloat3Location, -1);

    constexpr GLfloat mat3[9] = {
        0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
    };

    glUniform1f(ufloat1Location, 1.0f);
    glUniform1f(ufloat2Location, 1.0f);
    glUniform1f(ufloat3Location, 1.0f);
    glUniformMatrix3fv(umat3Location, 1, GL_FALSE, mat3);
    drawQuad(program, "position", 0.5f);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(0, 255, 255, 255));
}

// 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(SimpleUniformTest);

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(UniformTest);
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(BasicUniformUsageTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(UniformTestES3);
ANGLE_INSTANTIATE_TEST_ES3(UniformTestES3);
ANGLE_INSTANTIATE_TEST_ES3(SimpleUniformTestES3);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(UniformTestES31);
ANGLE_INSTANTIATE_TEST_ES31(UniformTestES31);

}  // namespace
