//
// 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/gl_raii.h"
#include "util/shader_utils.h"

using namespace angle;

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

    std::string GenerateVaryingType(GLint vectorSize)
    {
        char varyingType[10];

        if (vectorSize == 1)
        {
            snprintf(varyingType, sizeof(varyingType), "float");
        }
        else
        {
            snprintf(varyingType, sizeof(varyingType), "vec%d", vectorSize);
        }

        return std::string(varyingType);
    }

    std::string GenerateVectorVaryingDeclaration(GLint vectorSize, GLint arraySize, GLint id)
    {
        char buff[100];

        if (arraySize == 1)
        {
            snprintf(buff, sizeof(buff), "varying %s v%d;\n",
                     GenerateVaryingType(vectorSize).c_str(), id);
        }
        else
        {
            snprintf(buff, sizeof(buff), "varying %s v%d[%d];\n",
                     GenerateVaryingType(vectorSize).c_str(), id, arraySize);
        }

        return std::string(buff);
    }

    std::string GenerateVectorVaryingSettingCode(GLint vectorSize, GLint arraySize, GLint id)
    {
        std::string returnString;
        char buff[100];

        if (arraySize == 1)
        {
            snprintf(buff, sizeof(buff), "\t v%d = %s(1.0);\n", id,
                     GenerateVaryingType(vectorSize).c_str());
            returnString += buff;
        }
        else
        {
            for (int i = 0; i < arraySize; i++)
            {
                snprintf(buff, sizeof(buff), "\t v%d[%d] = %s(1.0);\n", id, i,
                         GenerateVaryingType(vectorSize).c_str());
                returnString += buff;
            }
        }

        return returnString;
    }

    std::string GenerateVectorVaryingUseCode(GLint arraySize, GLint id)
    {
        if (arraySize == 1)
        {
            char buff[100];
            snprintf(buff, sizeof(buff), "v%d + ", id);
            return std::string(buff);
        }
        else
        {
            std::string returnString;
            for (int i = 0; i < arraySize; i++)
            {
                char buff[100];
                snprintf(buff, sizeof(buff), "v%d[%d] + ", id, i);
                returnString += buff;
            }
            return returnString;
        }
    }

    void GenerateGLSLWithVaryings(GLint floatCount,
                                  GLint floatArrayCount,
                                  GLint vec2Count,
                                  GLint vec2ArrayCount,
                                  GLint vec3Count,
                                  GLint vec3ArrayCount,
                                  GLint vec4Count,
                                  GLint vec4ArrayCount,
                                  bool useFragCoord,
                                  bool usePointCoord,
                                  bool usePointSize,
                                  std::string *fragmentShader,
                                  std::string *vertexShader)
    {
        // Generate a string declaring the varyings, to share between the fragment shader and the
        // vertex shader.
        std::string varyingDeclaration;

        unsigned int varyingCount = 0;

        for (GLint i = 0; i < floatCount; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(1, 1, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < floatArrayCount; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(1, 2, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < vec2Count; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(2, 1, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < vec2ArrayCount; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(2, 2, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < vec3Count; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(3, 1, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < vec3ArrayCount; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(3, 2, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < vec4Count; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(4, 1, varyingCount);
            varyingCount += 1;
        }

        for (GLint i = 0; i < vec4ArrayCount; i++)
        {
            varyingDeclaration += GenerateVectorVaryingDeclaration(4, 2, varyingCount);
            varyingCount += 1;
        }

        // Generate the vertex shader
        vertexShader->clear();
        vertexShader->append(varyingDeclaration);
        vertexShader->append("\nvoid main()\n{\n");

        unsigned int currentVSVarying = 0;

        for (GLint i = 0; i < floatCount; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(1, 1, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < floatArrayCount; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(1, 2, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < vec2Count; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(2, 1, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < vec2ArrayCount; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(2, 2, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < vec3Count; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(3, 1, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < vec3ArrayCount; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(3, 2, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < vec4Count; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(4, 1, currentVSVarying));
            currentVSVarying += 1;
        }

        for (GLint i = 0; i < vec4ArrayCount; i++)
        {
            vertexShader->append(GenerateVectorVaryingSettingCode(4, 2, currentVSVarying));
            currentVSVarying += 1;
        }

        if (usePointSize)
        {
            vertexShader->append("gl_PointSize = 1.0;\n");
        }

        vertexShader->append("}\n");

        // Generate the fragment shader
        fragmentShader->clear();
        fragmentShader->append("precision highp float;\n");
        fragmentShader->append(varyingDeclaration);
        fragmentShader->append("\nvoid main() \n{ \n\tvec4 retColor = vec4(0,0,0,0);\n");

        unsigned int currentFSVarying = 0;

        // Make use of the float varyings
        fragmentShader->append("\tretColor += vec4(");

        for (GLint i = 0; i < floatCount; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(1, currentFSVarying));
            currentFSVarying += 1;
        }

        for (GLint i = 0; i < floatArrayCount; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(2, currentFSVarying));
            currentFSVarying += 1;
        }

        fragmentShader->append("0.0, 0.0, 0.0, 0.0);\n");

        // Make use of the vec2 varyings
        fragmentShader->append("\tretColor += vec4(");

        for (GLint i = 0; i < vec2Count; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(1, currentFSVarying));
            currentFSVarying += 1;
        }

        for (GLint i = 0; i < vec2ArrayCount; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(2, currentFSVarying));
            currentFSVarying += 1;
        }

        fragmentShader->append("vec2(0.0, 0.0), 0.0, 0.0);\n");

        // Make use of the vec3 varyings
        fragmentShader->append("\tretColor += vec4(");

        for (GLint i = 0; i < vec3Count; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(1, currentFSVarying));
            currentFSVarying += 1;
        }

        for (GLint i = 0; i < vec3ArrayCount; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(2, currentFSVarying));
            currentFSVarying += 1;
        }

        fragmentShader->append("vec3(0.0, 0.0, 0.0), 0.0);\n");

        // Make use of the vec4 varyings
        fragmentShader->append("\tretColor += ");

        for (GLint i = 0; i < vec4Count; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(1, currentFSVarying));
            currentFSVarying += 1;
        }

        for (GLint i = 0; i < vec4ArrayCount; i++)
        {
            fragmentShader->append(GenerateVectorVaryingUseCode(2, currentFSVarying));
            currentFSVarying += 1;
        }

        fragmentShader->append("vec4(0.0, 0.0, 0.0, 0.0);\n");

        // Set gl_FragColor, and use special variables if requested
        fragmentShader->append("\tgl_FragColor = retColor");

        if (useFragCoord)
        {
            fragmentShader->append(" + gl_FragCoord");
        }

        if (usePointCoord)
        {
            fragmentShader->append(" + vec4(gl_PointCoord, 0.0, 0.0)");
        }

        fragmentShader->append(";\n}");
    }

    void VaryingTestBase(GLint floatCount,
                         GLint floatArrayCount,
                         GLint vec2Count,
                         GLint vec2ArrayCount,
                         GLint vec3Count,
                         GLint vec3ArrayCount,
                         GLint vec4Count,
                         GLint vec4ArrayCount,
                         bool useFragCoord,
                         bool usePointCoord,
                         bool usePointSize,
                         bool expectSuccess)
    {
        std::string fragmentShaderSource;
        std::string vertexShaderSource;

        GenerateGLSLWithVaryings(floatCount, floatArrayCount, vec2Count, vec2ArrayCount, vec3Count,
                                 vec3ArrayCount, vec4Count, vec4ArrayCount, useFragCoord,
                                 usePointCoord, usePointSize, &fragmentShaderSource,
                                 &vertexShaderSource);

        GLuint program = CompileProgram(vertexShaderSource.c_str(), fragmentShaderSource.c_str());

        if (expectSuccess)
        {
            EXPECT_NE(0u, program);
        }
        else
        {
            EXPECT_EQ(0u, program);
        }
    }

    void CompileGLSLWithUniformsAndSamplers(GLint vertexUniformCount,
                                            GLint fragmentUniformCount,
                                            GLint vertexSamplersCount,
                                            GLint fragmentSamplersCount,
                                            bool expectSuccess)
    {
        std::stringstream vertexShader;
        std::stringstream fragmentShader;

        // Generate the vertex shader
        vertexShader << "precision mediump float;\n";

        for (int i = 0; i < vertexUniformCount; i++)
        {
            vertexShader << "uniform vec4 v" << i << ";\n";
        }

        for (int i = 0; i < vertexSamplersCount; i++)
        {
            vertexShader << "uniform sampler2D s" << i << ";\n";
        }

        vertexShader << "void main()\n{\n";

        for (int i = 0; i < vertexUniformCount; i++)
        {
            vertexShader << "    gl_Position +=  v" << i << ";\n";
        }

        for (int i = 0; i < vertexSamplersCount; i++)
        {
            vertexShader << "    gl_Position +=  texture2D(s" << i << ", vec2(0.0, 0.0));\n";
        }

        if (vertexUniformCount == 0 && vertexSamplersCount == 0)
        {
            vertexShader << "   gl_Position = vec4(0.0);\n";
        }

        vertexShader << "}\n";

        // Generate the fragment shader
        fragmentShader << "precision mediump float;\n";

        for (int i = 0; i < fragmentUniformCount; i++)
        {
            fragmentShader << "uniform vec4 v" << i << ";\n";
        }

        for (int i = 0; i < fragmentSamplersCount; i++)
        {
            fragmentShader << "uniform sampler2D s" << i << ";\n";
        }

        fragmentShader << "void main()\n{\n";

        for (int i = 0; i < fragmentUniformCount; i++)
        {
            fragmentShader << "    gl_FragColor +=  v" << i << ";\n";
        }

        for (int i = 0; i < fragmentSamplersCount; i++)
        {
            fragmentShader << "    gl_FragColor +=  texture2D(s" << i << ", vec2(0.0, 0.0));\n";
        }

        if (fragmentUniformCount == 0 && fragmentSamplersCount == 0)
        {
            fragmentShader << "    gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n";
        }

        fragmentShader << "}\n";

        GLuint program = CompileProgram(vertexShader.str().c_str(), fragmentShader.str().c_str());

        if (expectSuccess)
        {
            EXPECT_NE(0u, program);
        }
        else
        {
            EXPECT_EQ(0u, program);
        }
    }

    std::string QueryErrorMessage(GLuint program)
    {
        GLint infoLogLength;
        glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength);
        EXPECT_GL_NO_ERROR();

        if (infoLogLength >= 1)
        {
            std::vector<GLchar> infoLog(infoLogLength);
            glGetProgramInfoLog(program, static_cast<GLsizei>(infoLog.size()), nullptr,
                                infoLog.data());
            EXPECT_GL_NO_ERROR();
            return infoLog.data();
        }

        return "";
    }

    void validateComponentsInErrorMessage(const char *vertexShader,
                                          const char *fragmentShader,
                                          const char *expectedErrorType,
                                          const char *expectedVariableFullName)
    {
        GLuint vs = CompileShader(GL_VERTEX_SHADER, vertexShader);
        GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fragmentShader);

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

        glDetachShader(program, vs);
        glDetachShader(program, fs);
        glDeleteShader(vs);
        glDeleteShader(fs);

        const std::string errorMessage = QueryErrorMessage(program);
        printf("%s\n", errorMessage.c_str());

        EXPECT_NE(std::string::npos, errorMessage.find(expectedErrorType));
        EXPECT_NE(std::string::npos, errorMessage.find(expectedVariableFullName));

        glDeleteProgram(program);
        ASSERT_GL_NO_ERROR();
    }

    void verifyAttachment2DColor(unsigned int index,
                                 GLuint textureName,
                                 GLenum target,
                                 GLint level,
                                 GLColor color)
    {
        glReadBuffer(GL_COLOR_ATTACHMENT0 + index);
        ASSERT_GL_NO_ERROR();

        EXPECT_PIXEL_COLOR_EQ(getWindowWidth() / 2, getWindowHeight() / 2, color)
            << "index " << index;
    }

    std::string ExpectedExtensionMacros(std::vector<std::string> expected)
    {
        std::string shader;
        for (const auto &ext : expected)
        {
            if (IsGLExtensionEnabled(ext))
            {
                shader += "\n#ifndef " + ext + "\n#error !defined(" + ext + ")\n#endif\n";
            }
        }
        return shader;
    }

    std::string UnexpectedExtensionMacros(std::vector<std::string> unexpected)
    {
        std::string shader;
        for (const auto &ext : unexpected)
        {
            shader += "\n#ifdef " + ext + "\n#error defined(" + ext + ")\n#endif\n";
        }
        return shader;
    }
};

class GLSLTestNoValidation : public GLSLTest
{
  public:
    GLSLTestNoValidation() { setNoErrorEnabled(true); }
};

class GLSLTest_ClampPointSize : public GLSLTest
{};

class GLSLTest_ES3 : public GLSLTest
{};

class GLSLPrecisionTest_ES3 : public GLSLTest
{};

class GLSLTest_ES31 : public GLSLTest
{
  protected:
    void testArrayOfArrayOfSamplerDynamicIndex(const APIExtensionVersion usedExtension);
    void testTessellationTextureBufferAccess(const APIExtensionVersion usedExtension);
};

// Tests the "init output variables" ANGLE shader translator option.
class GLSLTest_ES3_InitShaderVariables : public GLSLTest
{};
class GLSLTest_ES31_InitShaderVariables : public GLSLTest
{};

// Selecting a field of a vector that's the result of dynamic indexing a constant array should work.
TEST_P(GLSLTest_ES3, ShaderSelectingFieldOfVectorIndexedFromArray)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        uniform int i;
        void main() {
            float f = vec2[1](vec2(0.5, 0.1))[i].x;
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
    ASSERT_GL_NO_ERROR();
}

// Passing an array into a function and then passing a value from that array into another function
// should work. This is a regression test for a bug where the mangled name of a TType was not
// properly updated when determining the type resulting from array indexing.
TEST_P(GLSLTest, ArrayValueFromFunctionParameterAsParameter)
{
    constexpr char kFS[] = R"(precision mediump float;
        uniform float u;
        float foo(float f) { return f * 2.0 + 0.1; }
        float bar(float[2] f) { return foo(f[0]); }
        void main() {
            float arr[2];
            arr[0] = u;
            gl_FragColor = vec4(bar(arr));
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(26, 26, 26, 26), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that vector field selection from a value taken from an array constructor is accepted as a
// constant expression.
TEST_P(GLSLTest_ES3, FieldSelectionFromVectorArrayConstructorIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            const float f = vec2[1](vec2(0.5, 1.0))[0].x;
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that structure field selection from a value taken from an array constructor is accepted as a
// constant expression.
TEST_P(GLSLTest_ES3, FieldSelectionFromStructArrayConstructorIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        struct S { float member; };
        void main() {
            const float f = S[1](S(0.5))[0].member;
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that a reference to a const array is accepted as a constant expression.
TEST_P(GLSLTest_ES3, ArraySymbolIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            const float[2] arr = float[2](0.5, 1.0);
            const float f = arr[0];
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that using an array constructor in a parameter to a built-in function is accepted as a
// constant expression.
TEST_P(GLSLTest_ES3, BuiltInFunctionAppliedToArrayConstructorIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            const float f = sin(float[2](0.5, 1.0)[0]);
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(122, 122, 122, 122), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that using an array constructor in a parameter to a built-in function is accepted as a
// constant expression.
TEST_P(GLSLTest_ES3, BuiltInFunctionWithMultipleParametersAppliedToArrayConstructorIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            const float f = pow(1.0, float[2](0.5, 1.0)[0]);
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    ASSERT_GL_NO_ERROR();
}

// Test that using an array constructor in a parameter to a constructor is accepted as a constant
// expression.
TEST_P(GLSLTest_ES3, ConstructorWithMultipleParametersAppliedToArrayConstructorIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            const vec2 f = vec2(1.0, float[2](0.5, 1.0)[0]);
            my_FragColor = vec4(f.x);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    ASSERT_GL_NO_ERROR();
}

// Test that using an array constructor in an operand of the ternary selection operator is accepted
// as a constant expression.
TEST_P(GLSLTest_ES3, TernaryOperatorAppliedToArrayConstructorIsConst)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            const float f = true ? float[2](0.5, 1.0)[0] : 1.0;
            my_FragColor = vec4(f);
        })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(128, 128, 128, 128), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that a shader passing a struct into a constructor of array of structs with 1 element works.
TEST_P(GLSLTest_ES3, SingleStructArrayConstructor)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         uniform float u;
         struct S { float member; };
         void main()
         {
             S[1] sarr = S[1](S(u + 0.5));
             my_FragColor = vec4(sarr[0].member);
      })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(128, 128, 128, 128), 1);
    ASSERT_GL_NO_ERROR();
}

// Built-in functions can be overloaded in ESSL 1.00.
TEST_P(GLSLTest, ESSL100BuiltInFunctionOverload)
{
    constexpr char kFS[] = R"(precision mediump float;
         int sin(int x)
         {
             return int(sin(float(x)));
         }
         void main() {
            gl_FragColor = vec4(sin(0.5));
    })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(122, 122, 122, 122), 1);
    ASSERT_GL_NO_ERROR();
}

// Multiplying a matrix with 2 columns and 4 rows with a 2x2 matrix should work.
TEST_P(GLSLTest_ES3, CompoundMultiplyMatrixValidNonSquareDimensions)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         void main() {
            mat2x4 foo = mat2x4(0.25);
            foo *= mat2x2(4.0);
            my_FragColor = vec4(foo);
    })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 0, 0, 0));
    ASSERT_GL_NO_ERROR();
}
// Tests a shader from conformance.olges/GL/build/build_017_to_024
// This shader uses chained assign-equals ops with swizzle, often reusing the same variable
// as part of a swizzle.
TEST_P(GLSLTest, SwizzledChainedAssignIncrement)
{
    constexpr char kFS[] =
        R"(
        precision mediump float;
        void main() {
            vec2 v = vec2(1,5);
            // at the end of next statement, values in
            // v.x = 12, v.y = 12
            v.xy += v.yx += v.xy;
            // v1 and v2, both are initialized with (12,12)
            vec2 v1 = v, v2 = v;
            v1.xy += v2.yx += ++(v.xy);  // v1 = 37, v2 = 25 each
            v1.xy += v2.yx += (v.xy)++;  // v1 = 75, v2 = 38 each
            gl_FragColor = vec4(v1,v2)/255.;  // 75, 75, 38, 38
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(75, 75, 38, 38));
}

// Same test as above without unary increments.
TEST_P(GLSLTest, SwizzledChainedAssign)
{
    constexpr char kFS[] =
        R"(
        precision mediump float;
        void main() {
            vec2 v = vec2(1,5);
            // at the end of next statement, values in
            // v.x = 12, v.y = 12
            v.xy += v.yx += v.xy;
            // v1 and v2, both are initialized with (12,12)
            vec2 v1 = v, v2 = v;
            v1.xy += v2.yx += (v.xy) += 1.0;  // v1 = 37, v2 = 25 each
            gl_FragColor = vec4(v1,v2)/255.;  // 37, 37, 25, 25
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(37, 37, 25, 25));
}

TEST_P(GLSLTest, NamelessScopedStructs)
{
    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    struct
    {
        float q;
    } b;

    gl_FragColor = vec4(1, 0, 0, 1);
    gl_FragColor.a += b.q;
})";

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

TEST_P(GLSLTest_ES3, CompareEqualityOfArrayOfVectors)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 fragColor;
uniform vec3 a[3];
uniform vec3 b[3];
void main() {
  bool same = a == b;
  fragColor = vec4(0);
  if (same) {
    fragColor = vec4(1);
  }
}
)";

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

    GLint aLocation = glGetUniformLocation(program, "a");
    GLint bLocation = glGetUniformLocation(program, "b");
    EXPECT_NE(aLocation, -1);
    EXPECT_NE(bLocation, -1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    static float almostZeros[] = {0, 0, 0, 0, 0, 0, 0, 1, 0};
    glUniform3fv(bLocation, 9, almostZeros);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    glUniform3fv(aLocation, 9, almostZeros);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    ASSERT_GL_NO_ERROR();
}

TEST_P(GLSLTest_ES3, CompareEqualityOfArrayOfMatrices)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 fragColor;
uniform mat3 a[3];
uniform mat3 b[3];
void main() {
  bool same = a == b;
  fragColor = vec4(0);
  if (same) {
    fragColor = vec4(1);
  }
}
)";

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

    GLint aLocation = glGetUniformLocation(program, "a");
    GLint bLocation = glGetUniformLocation(program, "b");
    EXPECT_NE(aLocation, -1);
    EXPECT_NE(bLocation, -1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    static float almostZeros[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    };
    glUniformMatrix3fv(bLocation, 27, false, almostZeros);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    glUniformMatrix3fv(aLocation, 27, false, almostZeros);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    ASSERT_GL_NO_ERROR();
}

TEST_P(GLSLTest_ES3, CompareEqualityOfArrayOfFloats)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 fragColor;
uniform float a[3];
uniform float b[3];
void main() {
  bool same = a == b;
  fragColor = vec4(0);
  if (same) {
    fragColor = vec4(1);
  }
}
)";

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

    GLint aLocation = glGetUniformLocation(program, "a");
    GLint bLocation = glGetUniformLocation(program, "b");
    EXPECT_NE(aLocation, -1);
    EXPECT_NE(bLocation, -1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    static float almostZeros[] = {
        0,
        0,
        1,
    };
    glUniform1fv(bLocation, 3, almostZeros);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    glUniform1fv(aLocation, 3, almostZeros);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    ASSERT_GL_NO_ERROR();
}

TEST_P(GLSLTest_ES3, CompareInequalityOfArrayOfVectors)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 fragColor;
uniform vec3 a[3];
uniform vec3 b[3];
void main() {
  bool notSame = a != b;
  fragColor = vec4(0);
  if (notSame) {
    fragColor = vec4(1);
  }
}
)";

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

    GLint aLocation = glGetUniformLocation(program, "a");
    GLint bLocation = glGetUniformLocation(program, "b");
    EXPECT_NE(aLocation, -1);
    EXPECT_NE(bLocation, -1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    static float almostZeros[] = {0, 0, 0, 0, 0, 0, 0, 1, 0};
    glUniform3fv(bLocation, 9, almostZeros);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    glUniform3fv(aLocation, 9, almostZeros);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    ASSERT_GL_NO_ERROR();
}

TEST_P(GLSLTest_ES3, CompareInequalityOfArrayOfMatrices)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 fragColor;
uniform mat3 a[3];
uniform mat3 b[3];
void main() {
  bool notSame = a != b;
  fragColor = vec4(0);
  if (notSame) {
    fragColor = vec4(1);
  }
}
)";

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

    GLint aLocation = glGetUniformLocation(program, "a");
    GLint bLocation = glGetUniformLocation(program, "b");
    EXPECT_NE(aLocation, -1);
    EXPECT_NE(bLocation, -1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    static float almostZeros[] = {
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    };
    glUniformMatrix3fv(bLocation, 27, false, almostZeros);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    glUniformMatrix3fv(aLocation, 27, false, almostZeros);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    ASSERT_GL_NO_ERROR();
}

TEST_P(GLSLTest_ES3, CompareInequalityOfArrayOfFloats)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 fragColor;
uniform float a[3];
uniform float b[3];
void main() {
  bool notSame = a != b;
  fragColor = vec4(0);
  if (notSame) {
    fragColor = vec4(1);
  }
}
)";

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

    GLint aLocation = glGetUniformLocation(program, "a");
    GLint bLocation = glGetUniformLocation(program, "b");
    EXPECT_NE(aLocation, -1);
    EXPECT_NE(bLocation, -1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    static float almostZeros[] = {
        0,
        0,
        1,
    };
    glUniform1fv(bLocation, 3, almostZeros);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);

    glUniform1fv(aLocation, 3, almostZeros);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);

    ASSERT_GL_NO_ERROR();
}

// Test that array of fragment shader outputs is processed properly and draws
// E.g. was issue with "out vec4 frag_color[4];"
TEST_P(GLSLTest_ES3, FragmentShaderOutputArray)
{
    GLuint fbo;
    glGenFramebuffers(1, &fbo);
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    GLuint textures[4];
    glGenTextures(4, textures);

    for (size_t texIndex = 0; texIndex < ArraySize(textures); texIndex++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    }

    GLint maxDrawBuffers;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ASSERT_GE(maxDrawBuffers, 4);

    GLuint readFramebuffer;
    glGenFramebuffers(1, &readFramebuffer);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);

    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 frag_color[4];

void main()
{
    frag_color[0] = vec4(1.0, 0.0, 0.0, 1.0);
    frag_color[1] = vec4(0.0, 1.0, 0.0, 1.0);
    frag_color[2] = vec4(0.0, 0.0, 1.0, 1.0);
    frag_color[3] = vec4(1.0, 1.0, 1.0, 1.0);
}
)";

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

    GLenum allBufs[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2,
                         GL_COLOR_ATTACHMENT3};

    constexpr GLuint kMaxBuffers = 4;

    // Enable all draw buffers.
    for (GLuint texIndex = 0; texIndex < kMaxBuffers; texIndex++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + texIndex, GL_TEXTURE_2D,
                               textures[texIndex], 0);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + texIndex, GL_TEXTURE_2D,
                               textures[texIndex], 0);
    }
    glDrawBuffers(kMaxBuffers, allBufs);

    // Draw with simple program.
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();

    verifyAttachment2DColor(0, textures[0], GL_TEXTURE_2D, 0, GLColor::red);
    verifyAttachment2DColor(1, textures[1], GL_TEXTURE_2D, 0, GLColor::green);
    verifyAttachment2DColor(2, textures[2], GL_TEXTURE_2D, 0, GLColor::blue);
    verifyAttachment2DColor(3, textures[3], GL_TEXTURE_2D, 0, GLColor::white);
}

// Test that inactive fragment shader outputs don't cause a crash.
TEST_P(GLSLTest_ES3, InactiveFragmentShaderOutput)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

// Make color0 inactive but specify color1 first.  The Vulkan backend assigns bogus locations when
// compiling and fixes it up in SPIR-V.  If color0's location is not fixed, it will return location
// 1 (aliasing color1).  This will lead to a Vulkan validation warning about attachment 0 not being
// written to, which shouldn't be fatal.
layout(location = 1) out vec4 color1;
layout(location = 0) out vec4 color0;

void main()
{
    color1 = vec4(0.0, 1.0, 0.0, 1.0);
}
)";

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

    constexpr GLint kDrawBufferCount = 2;

    GLint maxDrawBuffers;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ASSERT_GE(maxDrawBuffers, kDrawBufferCount);

    GLTexture textures[kDrawBufferCount];

    for (GLint texIndex = 0; texIndex < kDrawBufferCount; ++texIndex)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    }

    GLenum allBufs[kDrawBufferCount] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1};

    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    // Enable all draw buffers.
    for (GLint texIndex = 0; texIndex < kDrawBufferCount; ++texIndex)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + texIndex, GL_TEXTURE_2D,
                               textures[texIndex], 0);
    }
    glDrawBuffers(kDrawBufferCount, allBufs);

    // Draw with simple program.
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
}

TEST_P(GLSLTest, ScopedStructsOrderBug)
{
    constexpr char kFS[] = R"(precision mediump float;

struct T
{
    float f;
};

void main()
{
    T a;

    struct T
    {
        float q;
    };

    T b;

    gl_FragColor = vec4(1, 0, 0, 1);
    gl_FragColor.a += a.f;
    gl_FragColor.a += b.q;
})";

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

// Test that defining a struct together with an inactive uniform, then using it in a scope that has
// another struct with the same name declared works.
TEST_P(GLSLTest, ScopedStructsOrderBug2)
{
    constexpr char kFS[] = R"(precision mediump float;

uniform struct T
{
    float f;
} x;

void main()
{
    T a;

    struct T
    {
        float q;
    };

    T b;

    gl_FragColor = vec4(1, 0, 0, 1);
    gl_FragColor.a += a.f;
    gl_FragColor.a += b.q;
})";

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

// Test that defining a global struct with an "id" suffix does not collide with a local one without
// such a suffix.
TEST_P(GLSLTest, ScopedStructsOrderBug3)
{
    // Try IDs between 25 to 35 for IR ids, and 3000 to 3020 for AST ids.
    // For IR, the first 27 or so type ids are reserved, so user ids start at that value.
    // For AST, user ids start at 3000.
    for (uint32_t id = 25; id <= 3020; ++id)
    {
        std::ostringstream fs;
        fs << R"(precision mediump float;

struct T_)" << id
           << R"(
{
    float f;
};

void main()
{
    T_)" << id
           << R"( a;

    struct T
    {
        float q;
    };

    T b;

    gl_FragColor = vec4(1, 0, 0, 1);
    gl_FragColor.a += a.f;
    gl_FragColor.a += b.q;
})";

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.str().c_str());

        if (id == 35)
        {
            id = 2999;
        }
    }
}

// Test that defining a UBO with an "id" suffix does not collide with a struct declaration without
// such a suffix.
TEST_P(GLSLTest_ES3, UBOVsStructsNameCollision)
{
    // Try IDs between 25 to 35 for IR ids, and 3000 to 3020 for AST ids.  Also try 0, which is used
    // by both to suffix global struct names.
    // For IR, the first 27 or so type ids are reserved, so user ids start at that value.
    // For AST, user ids start at 3000.
    for (uint32_t id = 0; id <= 3020; ++id)
    {
        std::ostringstream fs;
        fs << R"(#version 300 es
precision mediump float;

uniform T_)"
           << id << R"(
{
    float f;
};

struct T
{
    float f;
};

out vec4 color;

void main()
{
    T a;

    struct T
    {
        float q;
    };

    T b;

    color = vec4(1, 0, 0, 1);
    color.a += a.f;
    color.a += b.q;
})";

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

        if (id == 0)
        {
            id = 24;
        }
        else if (id == 35)
        {
            id = 2999;
        }
    }
}

// Test that defining an SSBO with an "id" suffix does not collide with a struct declaration without
// such a suffix.
TEST_P(GLSLTest_ES31, SSBOVsStructsNameCollision)
{
    // Try IDs between 25 to 35 for IR ids, and 3000 to 3020 for AST ids.  Also try 0, which is used
    // by both to suffix global struct names.
    // For IR, the first 27 or so type ids are reserved, so user ids start at that value.
    // For AST, user ids start at 3000.
    for (uint32_t id = 0; id <= 3020; ++id)
    {
        std::ostringstream fs;
        fs << R"(#version 310 es
precision mediump float;

layout(std140) buffer T_)"
           << id << R"(
{
    float f;
};

struct T
{
    float f;
};

out vec4 color;

void main()
{
    T a;

    struct T
    {
        float q;
    };

    T b;

    color = vec4(1, 0, 0, 1);
    color.a += a.f;
    color.a += b.q;
})";

        ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), fs.str().c_str());

        if (id == 0)
        {
            id = 24;
        }
        else if (id == 35)
        {
            id = 2999;
        }
    }
}

// Test that defining an I/O block with an "id" suffix does not collide with a struct declaration
// without such a suffix.
TEST_P(GLSLTest_ES31, IOBlockVsStructsNameCollision)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    // Try IDs between 25 to 35 for IR ids, and 3000 to 3020 for AST ids.  Also try 0, which is used
    // by both to suffix global struct names.
    // For IR, the first 27 or so type ids are reserved, so user ids start at that value.
    // For AST, user ids start at 3000.
    for (uint32_t id = 0; id <= 3020; ++id)
    {
        std::ostringstream fs;
        fs << R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

in T_)" << id
           << R"(
{
    float f;
};

struct T
{
    float f;
};

out vec4 color;

void main()
{
    T a;

    struct T
    {
        float q;
    };

    T b;

    color = vec4(1, 0, 0, 1);
    color.a += a.f;
    color.a += b.q;
})";

        ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), fs.str().c_str());

        if (id == 0)
        {
            id = 24;
        }
        else if (id == 35)
        {
            id = 2999;
        }
    }
}

// Test that inactive uniforms of struct type don't cause any errors.
TEST_P(GLSLTest, InactiveStructUniform)
{
    constexpr char kVS[] = R"(
uniform struct
{
    vec4 c;
} s;
void main()
{
})";

    ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Red());
}

// Test that struct with same name can be declared in inner scope.
TEST_P(GLSLTest, SameNameStructInInnerScope)
{
    constexpr char kVS[] = R"(
void main() {
    gl_Position = vec4(0);
})";

    constexpr char kFS[] = R"(
struct S
{
    mediump float f;
};
void main()
{
    struct S
    {
        S n;
    };
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Regression test based on WebGL's conformance/glsl/misc/empty-declaration.html
TEST_P(GLSLTest, StructEmptyDeclaratorBug)
{
    constexpr char kVS[] = R"(
struct S {
    float member;
}, a;
void main() {
    a.member = 0.0;
    gl_Position = vec4(a.member);
})";

    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Regression test based on WebGL's conformance/ogles/GL/build/build_001_to_008.html
TEST_P(GLSLTest, StructConstantFoldingBug)
{
    constexpr char kVS[] = R"(
void main()
{

   const struct s2 {
       int i;
       vec3 v3;
       bvec4 bv4;
   } s22  = s2(8, vec3(9, 10, 11), bvec4(true, false, true, false));

   struct s4 {
       int ii;
       vec4 v4;
      };

   const struct s1 {
      s2 ss;
      int i;
      float f;
      mat4 m;
      s4 s44;
   } s11 = s1(s22, 2, 4.0, mat4(5), s4(6, vec4(7, 8, 9, 10))) ;

  const int field3 = s11.i * s11.ss.i;  // constant folding (int * int)
  const vec4 field4 = s11.s44.v4 * s11.s44.v4; // constant folding (vec4 * vec4)
 // 49, 64, 81, 100
  const vec4 v4 = vec4(s11.ss.v3.y, s11.m[3][3], field3, field4[2]);  // 10.0, 5.0, 16.0, 81.0
  gl_Position = v4;
})";

    constexpr char kFS[] = R"(precision mediump float;
precision mediump float;
void main()
{
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that constant folding doesn't remove struct declaration.
TEST_P(GLSLTest, StructConstantFoldingBug2)
{
    constexpr char kVS[] = R"(
uniform vec4 u;

void main()
{

   const struct s2 {
       int i;
       vec3 v3;
       bvec4 bv4;
   } s22  = s2(8, vec3(9, 10, 11), bvec4(true, false, true, false));

   s2 x;
   x.v3 = u.xyz;

   gl_Position = vec4(x.v3, float(s22.i));
})";

    constexpr char kFS[] = R"(precision mediump float;
precision mediump float;
void main()
{
    gl_FragColor = vec4(1.0,0.0,0.0,1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

TEST_P(GLSLTest, ScopedStructsBug)
{
    constexpr char kFS[] = R"(precision mediump float;

struct T_0
{
    float f;
};

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);

    struct T
    {
        vec2 v;
    };

    T_0 a;
    T b;

    gl_FragColor.a += a.f;
    gl_FragColor.a += b.v.x;
})";

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

TEST_P(GLSLTest, DxPositionBug)
{
    constexpr char kVS[] = R"(attribute vec4 inputAttribute;
varying float dx_Position;
void main()
{
    gl_Position = vec4(inputAttribute);
    dx_Position = 0.0;
})";

    constexpr char kFS[] = R"(precision mediump float;

varying float dx_Position;

void main()
{
    gl_FragColor = vec4(dx_Position, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Draw an array of points with the first vertex offset at 0 using gl_VertexID
TEST_P(GLSLTest_ES3, GLVertexIDOffsetZeroDrawArray)
{
    constexpr int kStartIndex  = 0;
    constexpr int kArrayLength = 5;
    constexpr char kVS[]       = R"(#version 300 es
precision highp float;
void main() {
    gl_Position = vec4(float(gl_VertexID)/10.0, 0, 0, 1);
    gl_PointSize = 3.0;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;
void main() {
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    glUseProgram(program);
    glDrawArrays(GL_POINTS, kStartIndex, kArrayLength);

    double pointCenterX = static_cast<double>(getWindowWidth()) / 2.0;
    double pointCenterY = static_cast<double>(getWindowHeight()) / 2.0;
    for (int i = kStartIndex; i < kStartIndex + kArrayLength; i++)
    {
        double pointOffsetX = static_cast<double>(i * getWindowWidth()) / 20.0;
        EXPECT_PIXEL_COLOR_EQ(static_cast<int>(pointCenterX + pointOffsetX),
                              static_cast<int>(pointCenterY), GLColor::red);
    }
}

GLint GetFirstIntPixelRedValue()
{
    GLint pixel[4];
    glReadPixels(0, 0, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel);
    return pixel[0];
}

TEST_P(GLSLTest_ES3, GLVertexIDIntegerTextureDrawElements)
{
    constexpr char kVS[] = R"(#version 300 es
    flat out highp int vVertexID;

    void main() {
        vVertexID = gl_VertexID;
        gl_PointSize = 1.0;
        gl_Position = vec4(0,0,0,1);
    })";

    constexpr char kFS[] = R"(#version 300 es
    flat in highp int vVertexID;
    out highp int oVertexID;
    void main() {
        oVertexID = vVertexID;
    })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glViewport(0, 0, 1, 1);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, 1, 1);
    GLFramebuffer fb;
    glBindFramebuffer(GL_FRAMEBUFFER, fb);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    EXPECT_GL_NO_ERROR();

    GLint clearData[4] = {42};
    glClearBufferiv(GL_COLOR, 0, clearData);
    EXPECT_EQ(42, GetFirstIntPixelRedValue());

    const int kIndexDataSize = 5;
    GLushort indexData[]     = {1, 2, 5, 3, 10000};
    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData, GL_STATIC_DRAW);

    for (size_t first = 0; first < kIndexDataSize; ++first)
    {
        for (size_t count = 1; first + count <= kIndexDataSize; ++count)
        {
            glDrawElements(GL_POINTS, count, GL_UNSIGNED_SHORT,
                           reinterpret_cast<const void *>(first * 2u));
            GLint expected = indexData[first + count - 1];
            GLint actual   = GetFirstIntPixelRedValue();
            EXPECT_EQ(expected, actual);
        }
    }
    EXPECT_GL_NO_ERROR();
}

TEST_P(GLSLTest_ES3, GLVertexIDIntegerTextureDrawElementsU8)
{
    constexpr char kVS[] = R"(#version 300 es
    flat out highp int vVertexID;

    void main() {
        vVertexID = gl_VertexID;
        gl_PointSize = 1.0;
        gl_Position = vec4(0,0,0,1);
    })";

    constexpr char kFS[] = R"(#version 300 es
    flat in highp int vVertexID;
    out highp int oVertexID;
    void main() {
        oVertexID = vVertexID;
    })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glViewport(0, 0, 1, 1);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, 1, 1);
    GLFramebuffer fb;
    glBindFramebuffer(GL_FRAMEBUFFER, fb);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    EXPECT_GL_NO_ERROR();

    GLint clearData[4] = {42};
    glClearBufferiv(GL_COLOR, 0, clearData);
    EXPECT_EQ(42, GetFirstIntPixelRedValue());

    const int kIndexDataSize = 5;
    GLubyte indexData[]      = {1, 2, 5, 3, 100};
    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData, GL_STATIC_DRAW);

    for (size_t first = 0; first < kIndexDataSize; ++first)
    {
        for (size_t count = 1; first + count <= kIndexDataSize; ++count)
        {
            glDrawElements(GL_POINTS, count, GL_UNSIGNED_BYTE,
                           reinterpret_cast<const void *>(first));
            GLint expected = indexData[first + count - 1];
            GLint actual   = GetFirstIntPixelRedValue();
            EXPECT_EQ(expected, actual);
        }
    }
    EXPECT_GL_NO_ERROR();
}

void GLVertexIDIntegerTextureDrawElementsU8Line_Helper(size_t first, const GLubyte *indices)
{
    glDrawElements(GL_LINES, 2, GL_UNSIGNED_BYTE, reinterpret_cast<const void *>(first));

    GLint pixels[8];
    glReadPixels(0, 0, 2, 1, GL_RGBA_INTEGER, GL_INT, pixels);

    GLint expected = indices[first + 1];
    EXPECT_EQ(expected, pixels[0]);
    EXPECT_EQ(expected, pixels[4]);
}

TEST_P(GLSLTest_ES3, GLVertexIDIntegerTextureDrawElementsU8Line)
{
    constexpr char kVS[] = R"(#version 300 es
    flat out highp int vVertexID;
    layout(location = 0) in vec4 position;

    void main() {
        vVertexID = gl_VertexID;
        gl_Position = position;
    })";

    constexpr char kFS[] = R"(#version 300 es
    flat in highp int vVertexID;
    out highp int oVertexID;
    void main() {
        oVertexID = vVertexID;
    })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glViewport(0, 0, 2, 1);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, 2, 1);
    GLFramebuffer fb;
    glBindFramebuffer(GL_FRAMEBUFFER, fb);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    EXPECT_GL_NO_ERROR();

    struct LR
    {
        LR() : X0(-1.0f), X1(1.0f) {}
        float X0;
        float X1;
    };
    constexpr int kNumVertices = 100;
    LR vertData[kNumVertices];
    GLBuffer buf;
    glBindBuffer(GL_ARRAY_BUFFER, buf);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertData), vertData, GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, nullptr);

    GLint clearData[4] = {42};
    glClearBufferiv(GL_COLOR, 0, clearData);
    EXPECT_EQ(42, GetFirstIntPixelRedValue());

    GLubyte indexData[] = {1, 4, 5, 2, 50, 61};
    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData, GL_STATIC_DRAW);

    GLVertexIDIntegerTextureDrawElementsU8Line_Helper(0, indexData);
    GLVertexIDIntegerTextureDrawElementsU8Line_Helper(1, indexData);
    GLVertexIDIntegerTextureDrawElementsU8Line_Helper(2, indexData);
    GLVertexIDIntegerTextureDrawElementsU8Line_Helper(4, indexData);

    EXPECT_GL_NO_ERROR();
}

// Test gl_VertexID works with lines
TEST_P(GLSLTest_ES3, GLVertexIDIntegerTextureDrawElementsU8LineIds)
{
    // Draws lines via indices (glDrawElements). Each pair of indices
    // draws the next consecutive pixel. For 2 points, because we're
    // using int attributes, they must be "flat" and so the spec
    // says for a given line the value should come from the second
    // of the 2 points. (see: OpenGL ES 3.0.2 spec Table 2.12)
    // Each line is only 1 pixel long so every other pixel should
    // be the default value.
    constexpr char kVS[] = R"(#version 300 es
    flat out highp int vVertexID;
    layout(location = 0) in float position;
    uniform float width;

    void main() {
        vVertexID = gl_VertexID;
        gl_Position = vec4(position / width * 2.0 - 1.0, 0, 0, 1);
    })";

    constexpr char kFS[] = R"(#version 300 es
    flat in highp int vVertexID;
    out highp int oVertexID;
    void main() {
        oVertexID = vVertexID;
    })";

    GLubyte indexData[]          = {1, 4, 5, 2, 50, 61, 32, 33};
    constexpr size_t kNumIndices = sizeof(indexData) / sizeof(indexData[0]);
    GLBuffer indexBuffer;
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indexData), indexData, GL_STATIC_DRAW);

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glUniform1f(glGetUniformLocation(program, "width"), kNumIndices);
    glViewport(0, 0, kNumIndices, 1);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, kNumIndices, 1);
    GLFramebuffer fb;
    glBindFramebuffer(GL_FRAMEBUFFER, fb);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);

    constexpr int kNumVertices = 100;
    std::vector<float> vertData(kNumVertices, -1.0f);
    {
        int i = 0;
        for (GLubyte ndx : indexData)
        {
            vertData[ndx] = i++;
        }
    }
    GLBuffer buf;
    glBindBuffer(GL_ARRAY_BUFFER, buf);
    glBufferData(GL_ARRAY_BUFFER, vertData.size() * sizeof(float), vertData.data(), GL_STATIC_DRAW);
    glEnableVertexAttribArray(0);
    glVertexAttribPointer(0, 1, GL_FLOAT, GL_FALSE, 0, nullptr);

    GLint kDefaultValue = 42;
    GLint clearData[4]  = {kDefaultValue};
    glClearBufferiv(GL_COLOR, 0, clearData);
    EXPECT_EQ(kDefaultValue, GetFirstIntPixelRedValue());

    EXPECT_GL_NO_ERROR();

    glDrawElements(GL_LINES, kNumIndices, GL_UNSIGNED_BYTE, 0);

    GLint pixels[kNumIndices * 4];
    glReadPixels(0, 0, kNumIndices, 1, GL_RGBA_INTEGER, GL_INT, pixels);

    for (size_t i = 0; i < kNumIndices; ++i)
    {
        const int expected = i % 2 ? kDefaultValue : indexData[i + 1];
        const int actual   = pixels[i * 4];
        EXPECT_EQ(expected, actual);
    }

    EXPECT_GL_NO_ERROR();
}

// Helper function for the GLVertexIDIntegerTextureDrawArrays test
void GLVertexIDIntegerTextureDrawArrays_helper(int first, int count, GLenum err)
{
    glDrawArrays(GL_POINTS, first, count);

    int pixel[4];
    glReadPixels(0, 0, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel);
    // If we call this function with err as GL_NO_ERROR, then we expect no error and check the
    // pixels.
    if (err == static_cast<GLenum>(GL_NO_ERROR))
    {
        EXPECT_GL_NO_ERROR();
        EXPECT_EQ(pixel[0], first + count - 1);
    }
    else
    {
        // If we call this function with err set, we will allow the error, but check the pixels if
        // the error hasn't occurred.
        GLenum glError = glGetError();
        if (glError == err || glError == static_cast<GLenum>(GL_NO_ERROR))
        {
            EXPECT_EQ(pixel[0], first + count - 1);
        }
    }
}

// Ensure gl_VertexID gets passed to an integer texture properly when drawArrays is called. This
// is based off the WebGL test:
// https://github.com/KhronosGroup/WebGL/blob/master/sdk/tests/conformance2/rendering/vertex-id.html
TEST_P(GLSLTest_ES3, GLVertexIDIntegerTextureDrawArrays)
{
    constexpr char kVS[] = R"(#version 300 es
flat out highp int vVertexID;
void main() {
    vVertexID = gl_VertexID;
    gl_Position = vec4(0,0,0,1);
    gl_PointSize = 1.0;
})";

    constexpr char kFS[] = R"(#version 300 es
flat in highp int vVertexID;
out highp int oVertexID;
void main() {
    oVertexID = vVertexID;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glViewport(0, 0, 1, 1);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, 1, 1);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture, 0);
    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));
    EXPECT_GL_NO_ERROR();

    // Clear the texture to 42 to ensure the first test case doesn't accidentally pass
    GLint val[4] = {42};
    glClearBufferiv(GL_COLOR, 0, val);
    int pixel[4];
    glReadPixels(0, 0, 1, 1, GL_RGBA_INTEGER, GL_INT, pixel);
    EXPECT_EQ(pixel[0], val[0]);

    GLVertexIDIntegerTextureDrawArrays_helper(0, 1, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(1, 1, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(10000, 1, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(100000, 1, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(1000000, 1, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(0, 2, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(1, 2, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(10000, 2, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(100000, 2, GL_NO_ERROR);
    GLVertexIDIntegerTextureDrawArrays_helper(1000000, 2, GL_NO_ERROR);

    int32_t int32Max = 0x7FFFFFFF;
    GLVertexIDIntegerTextureDrawArrays_helper(int32Max - 2, 1, GL_OUT_OF_MEMORY);
    GLVertexIDIntegerTextureDrawArrays_helper(int32Max - 1, 1, GL_OUT_OF_MEMORY);
}

// Draw an array of points with the first vertex offset at 5 using gl_VertexID
TEST_P(GLSLTest_ES3, GLVertexIDOffsetFiveDrawArray)
{
    // Bug in Nexus drivers, offset does not work. (anglebug.com/42261941)
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsOpenGLES());

    constexpr int kStartIndex  = 5;
    constexpr int kArrayLength = 5;
    constexpr char kVS[]       = R"(#version 300 es
precision highp float;
void main() {
    gl_Position = vec4(float(gl_VertexID)/10.0, 0, 0, 1);
    gl_PointSize = 3.0;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;
void main() {
    outColor = vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    glUseProgram(program);
    glDrawArrays(GL_POINTS, kStartIndex, kArrayLength);

    double pointCenterX = static_cast<double>(getWindowWidth()) / 2.0;
    double pointCenterY = static_cast<double>(getWindowHeight()) / 2.0;
    for (int i = kStartIndex; i < kStartIndex + kArrayLength; i++)
    {
        double pointOffsetX = static_cast<double>(i * getWindowWidth()) / 20.0;
        EXPECT_PIXEL_COLOR_EQ(static_cast<int>(pointCenterX + pointOffsetX),
                              static_cast<int>(pointCenterY), GLColor::red);
    }
}

TEST_P(GLSLTest, ElseIfRewriting)
{
    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "varying float v;\n"
        "void main() {\n"
        "  gl_Position = a_position;\n"
        "  v = 1.0;\n"
        "  if (a_position.x <= 0.5) {\n"
        "    v = 0.0;\n"
        "  } else if (a_position.x >= 0.5) {\n"
        "    v = 2.0;\n"
        "  }\n"
        "}\n";

    constexpr char kFS[] =
        "precision highp float;\n"
        "varying float v;\n"
        "void main() {\n"
        "  vec4 color = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "  if (v >= 1.0) color = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "  if (v >= 2.0) color = vec4(0.0, 0.0, 1.0, 1.0);\n"
        "  gl_FragColor = color;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    drawQuad(program, "a_position", 0.5f);

    EXPECT_PIXEL_EQ(0, 0, 255, 0, 0, 255);
    EXPECT_PIXEL_EQ(getWindowWidth() - 1, 0, 0, 255, 0, 255);
}

TEST_P(GLSLTest, TwoElseIfRewriting)
{
    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "varying float v;\n"
        "void main() {\n"
        "  gl_Position = a_position;\n"
        "  if (a_position.x == 0.0) {\n"
        "    v = 1.0;\n"
        "  } else if (a_position.x > 0.5) {\n"
        "    v = 0.0;\n"
        "  } else if (a_position.x > 0.75) {\n"
        "    v = 0.5;\n"
        "  }\n"
        "}\n";

    constexpr char kFS[] =
        "precision highp float;\n"
        "varying float v;\n"
        "void main() {\n"
        "  gl_FragColor = vec4(v, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

TEST_P(GLSLTest, FrontFacingAndVarying)
{
    constexpr char kVS[] = R"(attribute vec4 a_position;
varying float v_varying;
void main()
{
    v_varying = a_position.x;
    gl_Position = a_position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying float v_varying;
void main()
{
    vec4 c;

    if (gl_FrontFacing)
    {
        c = vec4(v_varying, 0, 0, 1.0);
    }
    else
    {
        c = vec4(0, v_varying, 0, 1.0);
    }
    gl_FragColor = c;
})";

    GLuint program = CompileProgram(kVS, kFS);

    // Compilation should succeed
    EXPECT_NE(0u, program);
}

// Test that we can release the shader compiler and still compile things properly.
TEST_P(GLSLTest, ReleaseCompilerThenCompile)
{
    // Draw with the first program.
    ANGLE_GL_PROGRAM(program1, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program1, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Clear and release shader compiler.
    glClearColor(0.0f, 1.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    glReleaseShaderCompiler();
    ASSERT_GL_NO_ERROR();

    // Draw with a second program.
    ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red());
    drawQuad(program2, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Verify that linking shaders declaring different shading language versions fails.
TEST_P(GLSLTest_ES3, VersionMismatch)
{
    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), essl1_shaders::fs::Red());
    EXPECT_EQ(0u, program);

    program = CompileProgram(essl1_shaders::vs::Simple(), essl3_shaders::fs::Red());
    EXPECT_EQ(0u, program);
}

// Verify that declaring varying as invariant only in vertex shader fails in ESSL 1.00.
TEST_P(GLSLTest, InvariantVaryingOut)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "invariant varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that declaring varying as invariant only in vertex shader succeeds in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantVaryingOut)
{
    // TODO: ESSL 3.00 -> GLSL 1.20 translation should add "invariant" in fragment shader
    // for varyings which are invariant in vertex shader (http://anglebug.com/40096344)
    ANGLE_SKIP_TEST_IF(IsDesktopOpenGL());

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

    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 a_position;\n"
        "invariant out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Verify that declaring varying as invariant only in fragment shader fails in ESSL 1.00.
TEST_P(GLSLTest, InvariantVaryingIn)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "invariant varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that declaring varying as invariant only in fragment shader fails in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantVaryingIn)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "invariant in float v_varying;\n"
        "out vec4 my_FragColor;\n"
        "void main() { my_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 a_position;\n"
        "out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that declaring varying as invariant in both shaders succeeds in ESSL 1.00.
TEST_P(GLSLTest, InvariantVaryingBoth)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "invariant varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "invariant varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Verify that declaring varying as invariant in both shaders fails in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantVaryingBoth)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "invariant in float v_varying;\n"
        "out vec4 my_FragColor;\n"
        "void main() { my_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 a_position;\n"
        "invariant out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that declaring gl_Position as invariant succeeds in ESSL 1.00.
TEST_P(GLSLTest, InvariantGLPosition)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "invariant gl_Position;\n"
        "varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Verify that declaring gl_Position as invariant succeeds in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantGLPosition)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "in float v_varying;\n"
        "out vec4 my_FragColor;\n"
        "void main() { my_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 a_position;\n"
        "invariant gl_Position;\n"
        "out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Verify that using invariant(all) in both shaders fails in ESSL 1.00.
TEST_P(GLSLTest, InvariantAllBoth)
{
    constexpr char kFS[] =
        "#pragma STDGL invariant(all)\n"
        "precision mediump float;\n"
        "varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#pragma STDGL invariant(all)\n"
        "attribute vec4 a_position;\n"
        "varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that using invariant(all) in both shaders fails in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantAllBoth)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "#pragma STDGL invariant(all)\n"
        "precision mediump float;\n"
        "in float v_varying;\n"
        "out vec4 my_FragColor;\n"
        "void main() { my_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#version 300 es\n"
        "#pragma STDGL invariant(all)\n"
        "in vec4 a_position;\n"
        "out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that using invariant(all) only in fragment shader succeeds in ESSL 1.00.
TEST_P(GLSLTest, InvariantAllIn)
{
    constexpr char kFS[] =
        "#pragma STDGL invariant(all)\n"
        "precision mediump float;\n"
        "varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "attribute vec4 a_position;\n"
        "varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Verify that using invariant(all) only in fragment shader fails in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantAllIn)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "#pragma STDGL invariant(all)\n"
        "precision mediump float;\n"
        "in float v_varying;\n"
        "out vec4 my_FragColor;\n"
        "void main() { my_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 a_position;\n"
        "out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that using invariant(all) only in vertex shader fails in ESSL 1.00.
TEST_P(GLSLTest, InvariantAllOut)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying float v_varying;\n"
        "void main() { gl_FragColor = vec4(v_varying, 0, 0, 1.0); }\n";

    constexpr char kVS[] =
        "#pragma STDGL invariant(all)\n"
        "attribute vec4 a_position;\n"
        "varying float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

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

// Verify that using invariant(all) only in vertex shader succeeds in ESSL 3.00.
TEST_P(GLSLTest_ES3, InvariantAllOut)
{
    // TODO: ESSL 3.00 -> GLSL 1.20 translation should add "invariant" in fragment shader
    // for varyings which are invariant in vertex shader,
    // because of invariant(all) being used in vertex shader (http://anglebug.com/40096344)
    ANGLE_SKIP_TEST_IF(IsDesktopOpenGL());

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

    constexpr char kVS[] =
        "#version 300 es\n"
        "#pragma STDGL invariant(all)\n"
        "in vec4 a_position;\n"
        "out float v_varying;\n"
        "void main() { v_varying = a_position.x; gl_Position = a_position; }\n";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

TEST_P(GLSLTest, MaxVaryingVec4)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 0, 0, 0, 0, maxVaryings, 0, false, false, false, true);
}

// Verify we can pack registers with one builtin varying.
TEST_P(GLSLTest, MaxVaryingVec4_OneBuiltin)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Generate shader code that uses gl_FragCoord.
    VaryingTestBase(0, 0, 0, 0, 0, 0, maxVaryings - 1, 0, true, false, false, true);
}

// Verify we can pack registers with two builtin varyings.
TEST_P(GLSLTest, MaxVaryingVec4_TwoBuiltins)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Generate shader code that uses gl_FragCoord and gl_PointCoord.
    VaryingTestBase(0, 0, 0, 0, 0, 0, maxVaryings - 2, 0, true, true, false, true);
}

// Verify we can pack registers with three builtin varyings.
TEST_P(GLSLTest, MaxVaryingVec4_ThreeBuiltins)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Generate shader code that uses gl_FragCoord, gl_PointCoord and gl_PointSize.
    VaryingTestBase(0, 0, 0, 0, 0, 0, maxVaryings - 3, 0, true, true, true, true);
}

// Test that max vec2 varyings + gl_FragCoord works
TEST_P(GLSLTest, MaxMinusTwoVaryingVec2PlusOneSpecialVariable)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Generate shader code that uses gl_FragCoord.
    VaryingTestBase(0, 0, maxVaryings, 0, 0, 0, 0, 0, true, false, false, true);
}

// Test that max vec3 varyings works
TEST_P(GLSLTest, MaxVaryingVec3)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 0, 0, maxVaryings, 0, 0, 0, false, false, false, true);
}

// Test that max vec3 array varyings works
TEST_P(GLSLTest, MaxVaryingVec3Array)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 0, 0, 0, maxVaryings / 2, 0, 0, false, false, false, true);
}

// Test that max vec3 varyings + a single float works
TEST_P(GLSLTest, MaxVaryingVec3AndOneFloat)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(1, 0, 0, 0, maxVaryings, 0, 0, 0, false, false, false, true);
}

// Test that max vec3 varyings + a single float array works
TEST_P(GLSLTest, MaxVaryingVec3ArrayAndOneFloatArray)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 1, 0, 0, 0, maxVaryings / 2, 0, 0, false, false, false, true);
}

// Test that 2x max vec2 varyings works, as they can get packed into max vec4 varyings
TEST_P(GLSLTest, TwiceMaxVaryingVec2)
{
    // TODO(geofflang): Figure out why this fails on NVIDIA's GLES driver
    // (http://anglebug.com/42262492)
    ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGLES());

    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 2 * maxVaryings, 0, 0, 0, 0, 0, false, false, false, true);
}

// Test that max vec2 array varyings works
TEST_P(GLSLTest, MaxVaryingVec2Arrays)
{
    // TODO(geofflang): Figure out why this fails on NVIDIA's GLES driver
    ANGLE_SKIP_TEST_IF(IsOpenGLES());

    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Special case: because arrays of mat2 are packed as small grids of two rows by two columns,
    // we should be aware that when we're packing into an odd number of varying registers the
    // last row will be empty and can not fit the final vec2 arrary.
    GLint maxVec2Arrays = (maxVaryings >> 1) << 1;

    VaryingTestBase(0, 0, 0, maxVec2Arrays, 0, 0, 0, 0, false, false, false, true);
}

// Verify max varying with feedback and gl_line enabled
TEST_P(GLSLTest_ES3, MaxVaryingWithFeedbackAndGLline)
{
    // (http://anglebug.com/42263058)
    ANGLE_SKIP_TEST_IF(IsAMD() && IsWindows() && IsVulkan());

    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    std::stringstream vertexShaderSource;
    std::stringstream fragmentShaderSource;

    // substract 1 here for gl_PointSize
    const GLint vec4Count     = maxVaryings - 1;
    unsigned int varyingCount = 0;
    std::string varyingDeclaration;
    for (GLint i = 0; i < vec4Count; i++)
    {
        varyingDeclaration += GenerateVectorVaryingDeclaration(4, 1, varyingCount);
        varyingCount += 1;
    }
    // Generate the vertex shader
    vertexShaderSource.clear();
    vertexShaderSource << varyingDeclaration;
    vertexShaderSource << "\nattribute vec4 a_position;\n";
    vertexShaderSource << "\nvoid main()\n{\n";
    unsigned int currentVSVarying = 0;
    for (GLint i = 0; i < vec4Count; i++)
    {
        vertexShaderSource << GenerateVectorVaryingSettingCode(4, 1, currentVSVarying);
        currentVSVarying += 1;
    }
    vertexShaderSource << "\tgl_Position = vec4(a_position.rgb, 1);\n";
    vertexShaderSource << "\tgl_PointSize = 1.0;\n";
    vertexShaderSource << "}\n";

    // Generate the fragment shader
    fragmentShaderSource.clear();
    fragmentShaderSource << "precision highp float;\n";
    fragmentShaderSource << varyingDeclaration;
    fragmentShaderSource << "\nvoid main() \n{ \n\tvec4 retColor = vec4(0,0,0,0);\n";
    unsigned int currentFSVarying = 0;
    // Make use of the vec4 varyings
    fragmentShaderSource << "\tretColor += ";
    for (GLint i = 0; i < vec4Count; i++)
    {
        fragmentShaderSource << GenerateVectorVaryingUseCode(1, currentFSVarying);
        currentFSVarying += 1;
    }
    fragmentShaderSource << "vec4(0.0, 0.0, 0.0, 0.0);\n";
    constexpr GLuint testValue = 234;
    fragmentShaderSource << "\tgl_FragColor = (retColor/vec4(" << std::to_string(currentFSVarying)
                         << ")) /255.0*" << std::to_string(testValue) << ".0;\n";
    fragmentShaderSource << "}\n";

    std::vector<std::string> tfVaryings = {"gl_Position", "gl_PointSize"};
    ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(program1, vertexShaderSource.str().c_str(),
                                        fragmentShaderSource.str().c_str(), tfVaryings,
                                        GL_INTERLEAVED_ATTRIBS);

    GLBuffer xfbBuffer;
    glBindBuffer(GL_TRANSFORM_FEEDBACK_BUFFER, xfbBuffer);
    glBufferData(GL_TRANSFORM_FEEDBACK_BUFFER, 6 * (sizeof(float[4]) + sizeof(float)), nullptr,
                 GL_STATIC_DRAW);

    GLTransformFeedback xfb;
    glBindTransformFeedback(GL_TRANSFORM_FEEDBACK, xfb);
    glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, xfbBuffer);

    glUseProgram(program1);

    const GLint positionLocation = glGetAttribLocation(program1, essl1_shaders::PositionAttrib());
    GLBuffer vertexBuffer;
    // need to shift half pixel to make sure the line covers the center of the pixel
    const Vector3 vertices[2] = {
        {-1.0f, -1.0f + 0.5f / static_cast<float>(getWindowHeight()), 0.0f},
        {1.0f, -1.0f + 0.5f / static_cast<float>(getWindowHeight()), 0.0f}};
    glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(*vertices) * 2, vertices, GL_STATIC_DRAW);
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
    glEnableVertexAttribArray(positionLocation);

    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    glBeginTransformFeedback(GL_LINES);
    glDrawArrays(GL_LINES, 0, 2);
    glEndTransformFeedback();

    ASSERT_GL_NO_ERROR();

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

// Test that structs declaration followed directly by an initialization is translated correctly.
TEST_P(GLSLTest, StructWithInitializer)
{
    constexpr char kFS[] = R"(precision mediump float;

struct S { float a; } s = S(1.0);

void main()
{
    gl_FragColor = vec4(0, 0, 0, 1);
    gl_FragColor.r += s.a;
})";

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

    // Test drawing, should be red.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test that structs without initializer, followed by a uniform usage works as expected.
TEST_P(GLSLTest, UniformStructWithoutInitializer)
{
    constexpr char kFS[] = R"(precision mediump float;

struct S { float a; };
uniform S u_s;

void main()
{
    gl_FragColor = vec4(u_s.a);
})";

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

    // Test drawing, should be red.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::transparentBlack);
    EXPECT_GL_NO_ERROR();
}

// Test that structs declaration followed directly by an initialization in a uniform.
TEST_P(GLSLTest, StructWithUniformInitializer)
{
    constexpr char kFS[] = R"(precision mediump float;

struct S { float a; } s = S(1.0);
uniform S us;

void main()
{
    gl_FragColor = vec4(0, 0, 0, 1);
    gl_FragColor.r += s.a;
    gl_FragColor.g += us.a;
})";

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

    // Test drawing, should be red.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test that gl_DepthRange is not stored as a uniform location. Since uniforms
// beginning with "gl_" are filtered out by our validation logic, we must
// bypass the validation to test the behaviour of the implementation.
// (note this test is still Impl-independent)
TEST_P(GLSLTestNoValidation, DepthRangeUniforms)
{
    constexpr char kFS[] = R"(precision mediump float;

void main()
{
    gl_FragColor = vec4(gl_DepthRange.near, gl_DepthRange.far, gl_DepthRange.diff, 1);
})";

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

    // We need to bypass validation for this call.
    GLint nearIndex = glGetUniformLocation(program, "gl_DepthRange.near");
    EXPECT_EQ(-1, nearIndex);

    // Test drawing does not throw an exception.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_GL_NO_ERROR();
}

std::string GenerateSmallPowShader(double base, double exponent)
{
    std::stringstream stream;

    stream.precision(8);

    double result = pow(base, exponent);

    stream << "precision highp float;\n"
           << "float fun(float arg)\n"
           << "{\n"
           << "    return pow(arg, " << std::fixed << exponent << ");\n"
           << "}\n"
           << "\n"
           << "void main()\n"
           << "{\n"
           << "    const float a = " << std::scientific << base << ";\n"
           << "    float b = fun(a);\n"
           << "    if (abs(" << result << " - b) < " << std::abs(result * 0.001) << ")\n"
           << "    {\n"
           << "        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
           << "    }\n"
           << "    else\n"
           << "    {\n"
           << "        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
           << "    }\n"
           << "}\n";

    return stream.str();
}

// Covers the WebGL test 'glsl/bugs/pow-of-small-constant-in-user-defined-function'
// See http://anglebug.com/40096900
TEST_P(GLSLTest, PowOfSmallConstant)
{
    // Test with problematic exponents that are close to an integer.
    std::vector<double> testExponents;
    std::array<double, 5> epsilonMultipliers = {-100.0, -1.0, 0.0, 1.0, 100.0};
    for (double epsilonMultiplier : epsilonMultipliers)
    {
        for (int i = -4; i <= 5; ++i)
        {
            if (i >= -1 && i <= 1)
                continue;
            const double epsilon = 1.0e-8;
            double bad           = static_cast<double>(i) + epsilonMultiplier * epsilon;
            testExponents.push_back(bad);
        }
    }

    // Also test with a few exponents that are not close to an integer.
    testExponents.push_back(3.6);
    testExponents.push_back(3.4);

    for (double testExponent : testExponents)
    {
        const std::string &fragmentShaderSource = GenerateSmallPowShader(1.0e-6, testExponent);

        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fragmentShaderSource.c_str());

        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

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

// Test fragment shaders which contain non-constant loop indexers
TEST_P(GLSLTest, LoopIndexingValidation)
{
    constexpr char kFS[] = R"(precision mediump float;

uniform float loopMax;

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    for (float l = 0.0; l < loopMax; l++)
    {
        if (loopMax > 3.0)
        {
            gl_FragColor.a += 0.1;
        }
    }
})";

    GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);

    const char *sourceArray[1] = {kFS};
    glShaderSource(shader, 1, sourceArray, nullptr);
    glCompileShader(shader);

    GLint compileResult;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);

    EXPECT_NE(0, compileResult);

    if (shader != 0)
    {
        glDeleteShader(shader);
    }
}

// Tests that the maximum uniforms count returned from querying GL_MAX_VERTEX_UNIFORM_VECTORS
// can actually be used.
TEST_P(GLSLTest, VerifyMaxVertexUniformVectors)
{
    // crbug.com/680631
    ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel());

    int maxUniforms = 10000;
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &maxUniforms);
    EXPECT_GL_NO_ERROR();
    std::cout << "Validating GL_MAX_VERTEX_UNIFORM_VECTORS = " << maxUniforms << std::endl;

    CompileGLSLWithUniformsAndSamplers(maxUniforms, 0, 0, 0, true);
}

// Tests that the maximum uniforms count returned from querying GL_MAX_VERTEX_UNIFORM_VECTORS
// can actually be used along with the maximum number of texture samplers.
TEST_P(GLSLTest, VerifyMaxVertexUniformVectorsWithSamplers)
{
    ANGLE_SKIP_TEST_IF(IsOpenGL() || IsOpenGLES());

    // Times out on D3D11 on test infra. http://anglebug.com/42263645
    ANGLE_SKIP_TEST_IF(IsD3D11() && IsIntel());

    int maxUniforms = 10000;
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &maxUniforms);
    EXPECT_GL_NO_ERROR();
    std::cout << "Validating GL_MAX_VERTEX_UNIFORM_VECTORS = " << maxUniforms << std::endl;

    int maxTextureImageUnits = 0;
    glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);

    CompileGLSLWithUniformsAndSamplers(maxUniforms, 0, maxTextureImageUnits, 0, true);
}

// Tests that the maximum uniforms count + 1 from querying GL_MAX_VERTEX_UNIFORM_VECTORS
// fails shader compilation.
TEST_P(GLSLTest, VerifyMaxVertexUniformVectorsExceeded)
{
    int maxUniforms = 10000;
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, &maxUniforms);
    EXPECT_GL_NO_ERROR();
    std::cout << "Validating GL_MAX_VERTEX_UNIFORM_VECTORS + 1 = " << maxUniforms + 1 << std::endl;

    CompileGLSLWithUniformsAndSamplers(maxUniforms + 1, 0, 0, 0, false);
}

// Tests that the maximum uniforms count returned from querying GL_MAX_FRAGMENT_UNIFORM_VECTORS
// can actually be used.
TEST_P(GLSLTest, VerifyMaxFragmentUniformVectors)
{
    // crbug.com/680631
    ANGLE_SKIP_TEST_IF(IsOzone() && IsIntel());

    int maxUniforms = 10000;
    glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &maxUniforms);
    EXPECT_GL_NO_ERROR();
    std::cout << "Validating GL_MAX_FRAGMENT_UNIFORM_VECTORS = " << maxUniforms << std::endl;

    CompileGLSLWithUniformsAndSamplers(0, maxUniforms, 0, 0, true);
}

// Tests that the maximum uniforms count returned from querying GL_MAX_FRAGMENT_UNIFORM_VECTORS
// can actually be used along with the maximum number of texture samplers.
TEST_P(GLSLTest, VerifyMaxFragmentUniformVectorsWithSamplers)
{
    ANGLE_SKIP_TEST_IF(IsOpenGL() || IsOpenGLES());

    int maxUniforms = 10000;
    glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &maxUniforms);
    EXPECT_GL_NO_ERROR();

    int maxTextureImageUnits = 0;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);

    CompileGLSLWithUniformsAndSamplers(0, maxUniforms, 0, maxTextureImageUnits, true);
}

// Tests that the maximum uniforms count + 1 from querying GL_MAX_FRAGMENT_UNIFORM_VECTORS
// fails shader compilation.
TEST_P(GLSLTest, VerifyMaxFragmentUniformVectorsExceeded)
{
    int maxUniforms = 10000;
    glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, &maxUniforms);
    EXPECT_GL_NO_ERROR();
    std::cout << "Validating GL_MAX_FRAGMENT_UNIFORM_VECTORS + 1 = " << maxUniforms + 1
              << std::endl;

    CompileGLSLWithUniformsAndSamplers(0, maxUniforms + 1, 0, 0, false);
}

// HLSL generates extra lod0 variants of functions. There was a bug that incorrectly reworte
// function calls to use them in vertex shaders.  http://anglebug.com/42262136
TEST_P(GLSLTest, TextureLODRewriteInVertexShader)
{
    constexpr char kVS[] = R"(
  precision highp float;
  uniform int uni;
  uniform sampler2D texture;

  vec4 A();

  vec4 B() {
    vec4 a;
    for(int r=0; r<14; r++){
      if (r < uni) return vec4(0.0);
      a = A();
    }
    return a;
  }

  vec4 A() {
    return texture2D(texture, vec2(0.0, 0.0));
  }

  void main() {
    gl_Position = B();
  })";

    constexpr char kFS[] = R"(
void main() { gl_FragColor = vec4(gl_FragCoord.x / 640.0, gl_FragCoord.y / 480.0, 0, 1); }
)";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test to verify the a shader can have a sampler unused in a vertex shader
// but used in the fragment shader.
TEST_P(GLSLTest, VerifySamplerInBothVertexAndFragmentShaders)
{
    constexpr char kVS[] = R"(
attribute vec2 position;
varying mediump vec2 texCoord;
uniform sampler2D tex;
void main()
{
    gl_Position = vec4(position, 0, 1);
    texCoord = position * 0.5 + vec2(0.5);
})";

    constexpr char kFS[] = R"(
varying mediump vec2 texCoord;
uniform sampler2D tex;
void main()
{
    gl_FragColor = texture2D(tex, texCoord);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    // Initialize basic red texture.
    const std::vector<GLColor> redColors(4, GLColor::red);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, redColors.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    drawQuad(program, "position", 0.0f);

    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::red);
}

// Test that array of structs containing array of samplers work as expected.
TEST_P(GLSLTest, ArrayOfStructContainingArrayOfSamplers)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "struct Data { mediump sampler2D data[2]; };\n"
        "uniform Data test[2];\n"
        "void main() {\n"
        "    gl_FragColor = vec4(texture2D(test[1].data[1], vec2(0.0, 0.0)).r,\n"
        "                        texture2D(test[1].data[0], vec2(0.0, 0.0)).r,\n"
        "                        texture2D(test[0].data[1], vec2(0.0, 0.0)).r,\n"
        "                        texture2D(test[0].data[0], vec2(0.0, 0.0)).r);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[4];
    GLColor expected = MakeGLColor(32, 64, 96, 255);
    GLubyte data[8]  = {};  // 4 bytes of padding, so that texture can be initialized with 4 bytes
    memcpy(data, expected.data(), sizeof(expected));
    for (int i = 0; i < 4; i++)
    {
        int outerIdx = i % 2;
        int innerIdx = i / 2;
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, textures[i]);
        // Each element provides two components.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + i);
        std::stringstream uniformName;
        uniformName << "test[" << innerIdx << "].data[" << outerIdx << "]";
        // Then send it as a uniform
        GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
        // The uniform should be active.
        EXPECT_NE(uniformLocation, -1);

        glUniform1i(uniformLocation, 3 - i);
    }
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, expected);
}

// Test that constructing matrices from non-float types works.
TEST_P(GLSLTest_ES3, ConstructMatrixFromNonFloat)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

uniform int i;
uniform uint u;
uniform bool b;

void main()
{
    mat3x2 mi = mat3x2(i);
    mat4 mu = mat4(u);
    mat2x4 mb = mat2x4(b);

    mat3x2 m = mat3x2(ivec2(i), uvec2(u), bvec2(b));

    color = vec4(mi[0][0] == -123.0 ? 1 : 0,
                 mu[2][2] == 456.0 ? 1 : 0,
                 mb[1][1] == 1.0 ? 1 : 0,
                 m[0][1] == -123.0 && m[1][0] == 456.0 && m[2][0] == 1.0 ? 1 : 0);
})";

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

    GLint iloc = glGetUniformLocation(program, "i");
    GLint uloc = glGetUniformLocation(program, "u");
    GLint bloc = glGetUniformLocation(program, "b");
    ASSERT_NE(iloc, -1);
    ASSERT_NE(uloc, -1);
    ASSERT_NE(bloc, -1);
    glUniform1i(iloc, -123);
    glUniform1ui(uloc, 456);
    glUniform1ui(bloc, 1);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that constructing vectors from non-float types works.
TEST_P(GLSLTest_ES3, ConstructVectorFromNonFloat)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

uniform ivec2 i;
uniform uvec2 u;
uniform bvec2 b;

void main()
{
    vec2 v2 = vec2(i.x, b);
    vec3 v3 = vec3(b, u);
    vec4 v4 = vec4(i, u);

    color = vec4(v2.x == -123.0 && v2.y == 1.0 ? 1 : 0,
                 v3.x == 1.0 && v3.y == 0.0 && v3.z == 456.0 ? 1 : 0,
                 v4.x == -123.0 && v4.y == -23.0 && v4.z == 456.0 && v4.w == 76.0 ? 1 : 0,
                 1);
})";

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

    GLint iloc = glGetUniformLocation(program, "i");
    GLint uloc = glGetUniformLocation(program, "u");
    GLint bloc = glGetUniformLocation(program, "b");
    ASSERT_NE(iloc, -1);
    ASSERT_NE(uloc, -1);
    ASSERT_NE(bloc, -1);
    glUniform2i(iloc, -123, -23);
    glUniform2ui(uloc, 456, 76);
    glUniform2ui(bloc, 1, 0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that constructing non-float vectors from matrix types works.
TEST_P(GLSLTest_ES3, ConstructNonFloatVectorFromMatrix)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

uniform float f;

void main()
{
    mat4 m = mat4(f);
    ivec3 vi = ivec3(m);
    uvec2 vu = uvec2(m);
    bvec4 vb = bvec4(m);
    bvec2 vb2 = bvec2(vi.x, m);

    color = vec4(vi.x == int(f) ? 1 : 0,
                 vu.x == uint(f) ? 1 : 0,
                 vb.x == bool(f) ? 1 : 0,
                 vb2.x == bool(f) && vb2.y == bool(f) ? 1 : 0);
})";

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

    GLint floc = glGetUniformLocation(program, "f");
    ASSERT_NE(floc, -1);
    glUniform1f(floc, 123);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that == and != for vector and matrix types work.
TEST_P(GLSLTest_ES3, NonScalarEqualOperator)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

uniform float f;
uniform int i;
uniform uint u;

void main()
{
    mat3x2 m32_1 = mat3x2(vec2(f), vec2(i), vec2(u));
    mat3x2 m32_2 = mat3x2(m32_1);
    mat3x2 m32_3 = mat3x2(vec2(i), vec2(u), vec2(f));
    mat2x3 m23_1 = mat2x3(vec3(f), vec3(i));
    mat2x3 m23_2 = mat2x3(m23_1);
    mat2x3 m23_3 = mat2x3(vec3(i), vec3(u));
    vec2 v2_1 = m32_1[0];
    vec2 v2_2 = m32_2[0];
    ivec3 v3_1 = ivec3(transpose(m32_1)[0]);
    ivec3 v3_2 = ivec3(transpose(m32_2)[0]);
    uvec4 v4_1 = uvec4(m32_1[1], m32_1[2]);
    uvec4 v4_2 = uvec4(m32_2[1], m32_2[2]);

    color = vec4((m32_1 == m32_2 ? 0.5 : 0.0) + (m23_1 == m23_2 ? 0.5 : 0.0),
                 v2_1 == v2_2 ? 1 : 0,
                 (v3_1 == v3_2 ? 0.5 : 0.0) +
                    (v4_1 == v4_2 ? 0.5 : 0.0),
                 (m32_1 != m32_3 ? 0.125 : 0.0) +
                    (m23_1 != m23_3 ? 0.125 : 0.0) +
                    (v2_1 != vec2(v3_2) ? 0.25 : 0.0) +
                    (v3_1 != ivec3(v4_2) ? 0.25 : 0.0) +
                    (v4_1 != uvec4(v2_1, v2_2) ? 0.25 : 0.0));
})";

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

    GLint floc = glGetUniformLocation(program, "f");
    GLint iloc = glGetUniformLocation(program, "i");
    GLint uloc = glGetUniformLocation(program, "u");
    ASSERT_NE(floc, -1);
    ASSERT_NE(iloc, -1);
    ASSERT_NE(uloc, -1);
    glUniform1f(floc, 1.5);
    glUniform1i(iloc, -123);
    glUniform1ui(uloc, 456);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that textureLod in a discontinuous loop does not generate HLSL loop unrolling errors.
TEST_P(GLSLTest_ES3, TextureLODShadowSamplerInDiscontinuousLoop)
{
    constexpr char kVS[] =
        R"(#version 300 es
out float vLoop;
void main()
{
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    vLoop = 10.0;
})";

    constexpr char kFS0[] =
        R"(#version 300 es
precision highp float;
precision highp sampler2DShadow;

uniform sampler2DShadow shadowMap;
in float vLoop;
out vec4 fragColor;

void main()
{
    int loopCount = int(vLoop); /* Each fragment may have a different value here.
                                   This makes it invalid to use gradient functions
                                   inside the loop! */

    float shadowSum = 0.0;
    int i = 0;
    while (i < loopCount)
    {
        float x = float(i) + 1.0; /* Makes sure sample operation cannot be optimized
                                     to be outside of the loop */
        shadowSum += textureLod(shadowMap, vec3(1.0 / x, 1.0 / x, 0.5), 0.0);
        ++i;
    }

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

    ANGLE_GL_PROGRAM(program0, kVS, kFS0);
    ASSERT_TRUE(program0.valid());

    constexpr char kFS1[] =
        R"(#version 300 es
precision highp float;
precision highp sampler2DShadow;

uniform sampler2DShadow u_testSampler;

out vec4 fragOut4f;

void main() {
    float a = texture(u_testSampler, vec3(10.0, 10.0, 0.5));
    for(;;) {
        if (a != 10.0) {
            a = textureLod(u_testSampler, vec3(4.0, 4.0, 0.1), 0.0);
            break;
        }
        if (a != 4.0) {
            a = textureLod(u_testSampler, vec3(10.0, 10.0, 0.4), 0.0);
            break;
        }
        fragOut4f = vec4(0.0);
        return;
    }
    fragOut4f = vec4(a);
})";

    ANGLE_GL_PROGRAM(program1, kVS, kFS1);
    ASSERT_TRUE(program1.valid());
}

// Test linking and using a program where a uniform is referenced only as a side-effect-free
// argument of an array constructor that is used as a statement.  The other constructor argument
// has a side effect that the program output depends on.
TEST_P(GLSLTest_ES3, UniformReferencedOnlyInArrayConstructorStatement)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform int u_zero;
out vec4 my_FragColor;
void main()
{
    int i = 0;
    int[2](u_zero, i++);
    my_FragColor = (i == 1) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that == and != for structs and array types work.
TEST_P(GLSLTest_ES31, StructAndArrayEqualOperator)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
out vec4 color;

uniform float f;
uniform int i;
uniform uint u;

struct S
{
    float f;
    int i;
    uint u;
    vec4 v;
    ivec3 iv;
    uvec2 uv;
    mat3x2 m32;
    mat2x3 m23;
    float fa[3][4][5];
    int ia[4];
    uint ua[6][2];
};

struct T
{
    S s1;
    S s2[3][2];
};

void main()
{
    float fa[5] = float[5](f, f, f, f, f);
    int ia[4] = int[4](i, i, i, i);
    uint ua[2] = uint[2](u, u);

    S s1 = S(f, i, u, vec4(f), ivec3(i), uvec2(u),
             mat3x2(vec2(f), vec2(i), vec2(u)),
             mat2x3(vec3(f), vec3(i)),
             float[3][4][5](
                            float[4][5](fa, fa, fa, fa),
                            float[4][5](fa, fa, fa, fa),
                            float[4][5](fa, fa, fa, fa)),
             ia,
             uint[6][2](ua, ua, ua, ua, ua, ua));

    S s2[2] = S[2](s1, s1);
    s2[1].fa[0][1][2] = float(i);

    T t1 = T(s1, S[3][2](s2, s2, s2));
    T t2 = T(s2[1], S[3][2](s2, s2, s2));

    T ta1[2] = T[2](t1, t2);
    T ta2[2] = T[2](t1, t2);
    T ta3[2] = T[2](t2, t1);

    color = vec4((s1 == s2[0] ? 0.5 : 0.0) + (s1 != s2[1] ? 0.5 : 0.0),
                 (s1.fa[0] == s2[0].fa[0] ? 0.5 : 0.0) + (s1.fa[0] != s2[1].fa[0] ? 0.5 : 0.0),
                 (ta1[0] == t1 ? 0.5 : 0.0) + (ta1[1] != t1 ? 0.5 : 0.0),
                 (ta1 == ta2 ? 0.5 : 0.0) + (ta1 != ta3 ? 0.5 : 0.0));
})";

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

    GLint floc = glGetUniformLocation(program, "f");
    GLint iloc = glGetUniformLocation(program, "i");
    GLint uloc = glGetUniformLocation(program, "u");
    ASSERT_NE(floc, -1);
    ASSERT_NE(iloc, -1);
    ASSERT_NE(uloc, -1);
    glUniform1f(floc, 1.5);
    glUniform1i(iloc, -123);
    glUniform1ui(uloc, 456);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that constant global matrix array with an initializer compiles.
TEST_P(GLSLTest_ES3, InitConstantMatrixArray)
{
    constexpr char kFS[] = R"(#version 300 es
        precision highp float;
        uniform int index;

        const mat4 matrix = mat4(1.0);
        const mat4 array[1] = mat4[1](matrix);
        out vec4 my_FragColor;
        void main() {
            my_FragColor = vec4(array[index][1].rgb, 1.0);
        })";

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

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

// Sequence operator evaluates operands from left to right (ESSL 3.00 section 5.9).
// The function call that returns the array needs to be evaluated after ++j for the expression to
// return the correct value (true).
TEST_P(GLSLTest_ES3, SequenceOperatorEvaluationOrderArray)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 my_FragColor;
int[2] func(int param) {
    return int[2](param, param);
}
void main() {
    int a[2];
    for (int i = 0; i < 2; ++i) {
        a[i] = 1;
    }
    int j = 0;
    bool result = ((++j), (a == func(j)));
    my_FragColor = vec4(0.0, (result ? 1.0 : 0.0), 0.0, 1.0);
})";

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

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

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

// Sequence operator evaluates operands from left to right (ESSL 3.00 section 5.9).
// The short-circuiting expression needs to be evaluated after ++j for the expression to return the
// correct value (true).
TEST_P(GLSLTest_ES3, SequenceOperatorEvaluationOrderShortCircuit)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 my_FragColor;
void main() {
    int j = 0;
    bool result = ((++j), (j == 1 ? true : (++j == 3)));
    my_FragColor = vec4(0.0, ((result && j == 1) ? 1.0 : 0.0), 0.0, 1.0);
})";

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

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

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

// Sequence operator evaluates operands from left to right (ESSL 3.00 section 5.9).
// Indexing the vector needs to be evaluated after func() for the right result.
TEST_P(GLSLTest_ES3, SequenceOperatorEvaluationOrderDynamicVectorIndexingInLValue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 my_FragColor;
uniform int u_zero;
int sideEffectCount = 0;
float func() {
    ++sideEffectCount;
    return -1.0;
}
void main() {
    vec4 v = vec4(0.0, 2.0, 4.0, 6.0);
    float f = (func(), (++v[u_zero + sideEffectCount]));
    bool green = abs(f - 3.0) < 0.01 && abs(v[1] - 3.0) < 0.01 && sideEffectCount == 1;
    my_FragColor = vec4(0.0, (green ? 1.0 : 0.0), 0.0, 1.0);
})";

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

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

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

// Test that using gl_PointCoord with GL_TRIANGLES doesn't produce a link error.
// From WebGL test conformance/rendering/point-specific-shader-variables.html
// See http://anglebug.com/42260376
TEST_P(GLSLTest, RenderTrisWithPointCoord)
{
    constexpr char kVS[] = R"(attribute vec2 aPosition;
void main()
{
    gl_Position = vec4(aPosition, 0, 1);
    gl_PointSize = 1.0;
})";
    constexpr char kFS[] = R"(void main()
{
    gl_FragColor = vec4(gl_PointCoord.xy, 0, 1);
    gl_FragColor = vec4(0, 1, 0, 1);
})";

    ANGLE_GL_PROGRAM(prog, kVS, kFS);
    drawQuad(prog, "aPosition", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Convers a bug with the integer pow statement workaround.
TEST_P(GLSLTest, NestedPowStatements)
{
    // https://crbug.com/1127866 - possible NVIDIA driver issue
    ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsVulkan() && IsWindows());

    constexpr char kFS[] = R"(precision mediump float;
float func(float v)
{
   float f1 = pow(v, 2.0);
   return pow(f1 + v, 2.0);
}
void main()
{
    float v = func(2.0);
    gl_FragColor = abs(v - 36.0) < 0.001 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// This test covers a crash seen in an application during SPIR-V compilation
TEST_P(GLSLTest_ES3, NestedPowFromUniform)
{
    constexpr char kVS[] = R"(#version 300 es
in vec2 position;
void main()
{
    gl_Position = vec4(position, 0, 1);
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
precision mediump int;

uniform highp vec4 scale;
out mediump vec4 out_FragColor;
void main()
{
    highp vec4 v0;
    v0 = scale;
    highp vec3 v1;
    v1.xyz = v0.xyz;
    if ((v0.y!=1.0))
    {
        vec3 v3;
        v3.xyz = pow(v0.xyz,v0.xxx);
        float h0;
        if ((v3.x < 3.13))
        {
            h0 = (v3.x * 1.29);
        }
        else
        {
            h0 = ((pow(v3.x,4.16)*1.055)+-5.5);
        }
        float h1;
        if ((v3.y<3.13))
        {
            h1 = (v3.y*1.29);
        }
        else
        {
            h1 = ((pow(v3.y,4.16)*1.055)+-5.5);
        }
        float h2;
        if ((v3.z<3.13))
        {
            h2 = (v3.z*1.29);
        }
        else
        {
            h2 = ((pow(v3.z,4.16)*1.055)+-5.5);
        }
        v1.xyz = vec3(h0, h1, h2);
    }
    out_FragColor = vec4(v1, v0.w);
}
)";

    ANGLE_GL_PROGRAM(prog, kVS, kFS);

    GLint scaleIndex = glGetUniformLocation(prog, "scale");
    ASSERT_NE(-1, scaleIndex);

    glUseProgram(prog);
    glUniform4f(scaleIndex, 0.5, 0.5, 0.5, 0.5);

    // Don't crash
    drawQuad(prog, "position", 0.5f);
}

// Test that -float calculation is correct.
TEST_P(GLSLTest_ES3, UnaryMinusOperatorFloat)
{
    constexpr char kFS[] = R"(#version 300 es
out highp vec4 o_color;
void main() {
    highp float f = -1.0;
    // atan(tan(0.5), -f) should be 0.5.
    highp float v = atan(tan(0.5), -f);
    o_color = abs(v - 0.5) < 0.001 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl3_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that atan(vec2, vec2) calculation is correct.
TEST_P(GLSLTest_ES3, AtanVec2)
{
    constexpr char kFS[] = R"(#version 300 es
out highp vec4 o_color;
void main() {
    highp float f = 1.0;
    // atan(tan(0.5), f) should be 0.5.
    highp vec2 v = atan(vec2(tan(0.5)), vec2(f));
    o_color = (abs(v[0] - 0.5) < 0.001 && abs(v[1] - 0.5) < 0.001) ?
                vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl3_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Convers a bug with the unary minus operator on signed integer workaround.
TEST_P(GLSLTest_ES3, UnaryMinusOperatorSignedInt)
{
    constexpr char kVS[] = R"(#version 300 es
in highp vec4 position;
out mediump vec4 v_color;
uniform int ui_one;
uniform int ui_two;
uniform int ui_three;
void main() {
    int s[3];
    s[0] = ui_one;
    s[1] = -(-(-ui_two + 1) + 1);  // s[1] = -ui_two
    s[2] = ui_three;
    int result = 0;
    for (int i = 0; i < ui_three; i++) {
        result += s[i];
    }
    v_color = (result == 2) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
    gl_Position = position;
})";
    constexpr char kFS[] = R"(#version 300 es
in mediump vec4 v_color;
layout(location=0) out mediump vec4 o_color;
void main() {
    o_color = v_color;
})";

    ANGLE_GL_PROGRAM(prog, kVS, kFS);

    GLint oneIndex = glGetUniformLocation(prog, "ui_one");
    ASSERT_NE(-1, oneIndex);
    GLint twoIndex = glGetUniformLocation(prog, "ui_two");
    ASSERT_NE(-1, twoIndex);
    GLint threeIndex = glGetUniformLocation(prog, "ui_three");
    ASSERT_NE(-1, threeIndex);
    glUseProgram(prog);
    glUniform1i(oneIndex, 1);
    glUniform1i(twoIndex, 2);
    glUniform1i(threeIndex, 3);

    drawQuad(prog, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Convers a bug with the unary minus operator on unsigned integer workaround.
TEST_P(GLSLTest_ES3, UnaryMinusOperatorUnsignedInt)
{
    constexpr char kVS[] = R"(#version 300 es
in highp vec4 position;
out mediump vec4 v_color;
uniform uint ui_one;
uniform uint ui_two;
uniform uint ui_three;
void main() {
    uint s[3];
    s[0] = ui_one;
    s[1] = -(-(-ui_two + 1u) + 1u);  // s[1] = -ui_two
    s[2] = ui_three;
    uint result = 0u;
    for (uint i = 0u; i < ui_three; i++) {
        result += s[i];
    }
    v_color = (result == 2u) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
    gl_Position = position;
})";
    constexpr char kFS[] = R"(#version 300 es
in mediump vec4 v_color;
layout(location=0) out mediump vec4 o_color;
void main() {
    o_color = v_color;
})";

    ANGLE_GL_PROGRAM(prog, kVS, kFS);

    GLint oneIndex = glGetUniformLocation(prog, "ui_one");
    ASSERT_NE(-1, oneIndex);
    GLint twoIndex = glGetUniformLocation(prog, "ui_two");
    ASSERT_NE(-1, twoIndex);
    GLint threeIndex = glGetUniformLocation(prog, "ui_three");
    ASSERT_NE(-1, threeIndex);
    glUseProgram(prog);
    glUniform1ui(oneIndex, 1u);
    glUniform1ui(twoIndex, 2u);
    glUniform1ui(threeIndex, 3u);

    drawQuad(prog, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test ternaries with a void type.  This is not allowed in WebGL2 (tested in
// glsl3/forbidden-operators.html) but is allowed in GLES.
TEST_P(GLSLTest_ES3, TernaryVoidType)
{
    // Most backends mishandle this shader.  Some OpenGL drivers do not accept the shader either.
    // The test is run only with IR.
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr));

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform bool falseBool;
vec4 global;
out vec4 color;

void f() { global = vec4(1, 0, 0, 1); }
void g() { global = vec4(0, 1, 0, 1); }
void main() {
    falseBool ? f() : g();
    color = global;
})";

    ANGLE_GL_PROGRAM(prog, essl3_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test a nested sequence operator with a ternary operator inside. The ternary operator is
// intended to be such that it gets converted to an if statement on the HLSL backend.
TEST_P(GLSLTest, NestedSequenceOperatorWithTernaryInside)
{
    // Note that the uniform keep_flop_positive doesn't need to be set - the test expects it to have
    // its default value false.
    constexpr char kFS[] = R"(precision mediump float;
uniform bool keep_flop_positive;
float flop;
void main() {
    flop = -1.0,
    (flop *= -1.0,
    keep_flop_positive ? 0.0 : flop *= -1.0),
    gl_FragColor = vec4(0, -flop, 0, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests that function arguments are evaluated left to right.
// See GLSL ES 3.0 spec, 6.1.1 Function Calling Conventions:
// "All arguments are evaluated at call time, exactly once, in order, from left to right."
TEST_P(GLSLTest, FunctionArgumentEvalOrder)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform bool u;

int outVar1, outVar2;

void f (int arg1, int arg2) {
    outVar1 = arg1;
    outVar2 = arg2;
}

void main()
{
    int a = u ? 1 : 2;      // will be 2
    f (a, a++);

    // Verify that both args were 2 but a was incremented afterwards.
    gl_FragColor = vec4(outVar1 == 2, outVar2 == 2, a == 3, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Same as FunctionArgumentEvalOrder, but with f as an overloaded function.
TEST_P(GLSLTest, FunctionArgumentEvalOrderOverloaded)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform bool u;

int outVar1, outVar2;

void f (in int arg1, in int arg2) {
    outVar1 = arg1;
    outVar2 = arg2;
}

void f (out int arg1, in float arg2) {
    arg1 = int(arg2);
}

void main()
{
    int a = u ? 1 : 2;      // will be 2
    f (a, a++);
    f (a, float(a++)); // Should NOT modify `a` because the first arg is assigned the current
                // value of `a` (before it is incremented as part of the side effects
                // of evaluating the second argument).

    // Verify that both args were 2 but a was incremented afterwards.
    gl_FragColor = vec4(outVar1 == 2, outVar2 == 2, a == 3, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that function arguments are evaluated left to right.
TEST_P(GLSLTest, FunctionArgumentEvalOrderIncrement)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform bool u;

int outVar1, outVar2;

void f (int arg1, int arg2) {
    outVar1 = arg1;
    outVar2 = arg2;
}

void main()
{
    int a = u ? 1 : 2;      // will be 2
    f (a++, a);

    // Verify that the first arg was still 2 but its evaluation set a == 3 for the second
    // argument.
    gl_FragColor = vec4(outVar1 == 2, outVar2 == 3, a == 3, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that function arguments are evaluated left to right, specifically when one argument is a
// ternary that modifies a variable used by an earlier function argument.
TEST_P(GLSLTest, TernaryModifiesVariableInSameStatement)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform bool u;

int outVar1, outVar2;

void f (int arg1, int arg2) {
    outVar1 = arg1;
    outVar2 = arg2;
}

void main()
{
    int a = u ? 1 : 2;      // will be 2
    f (a, a == 2 ? a++ : a + 1); // Ternary causes `a` to be incremented.

    // Verify that both args were 2 but a was incremented afterwards.
    gl_FragColor = vec4(outVar1 == 2, outVar2 == 2, a == 3, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that short circuit doesn't evaluate out of bounds expressions.
TEST_P(GLSLTest_ES31, ShortCircuitOutOfBoundsAccess)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));

    // Note that the uniform doesn't need to be set, and will contain the default value of false.
    constexpr char kCS[] = R"(#version 310 es
#extension GL_OES_texture_buffer : require
layout(local_size_x = 128, local_size_y = 1, local_size_z = 1) in;

uniform bool falseValue;
uniform uint zero;

layout(binding = 0) readonly buffer Input
{
    uint inData[];
};

layout(binding = 1, std430) writeonly buffer Output
{
    uint outData[];
};

layout(r32ui, binding = 0) uniform highp uimageBuffer image;

void main()
{
    outData[0] = falseValue ? inData[123456] : 1u;
    outData[1] = falseValue ? inData[~zero] : 2u;
    outData[2] = falseValue ? inData[123456] : 3u;
    outData[3] = !falseValue ? 4u : inData[~zero];
    outData[4] = !falseValue ? 5u : imageLoad(image, 1234567).x;
    outData[5] = falseValue ? imageLoad(image, int(~zero)).x : 6u;

    bool eval6 = falseValue && bool(inData[123456]);
    bool eval7 = falseValue && bool(inData[~zero]);
    bool eval8 = !falseValue || bool(inData[123456]);
    bool eval9 = !falseValue || bool(inData[~zero]);
    bool eval10 = falseValue && bool(imageLoad(image, 1234567).x);
    bool eval11 = !falseValue || bool(imageLoad(image, int(~zero)).x);

    outData[6] = eval6 ? 1234u : 7u;
    outData[7] = eval7 ? 2345u : 8u;
    outData[8] = eval8 ? 9u : 3456u;
    outData[9] = eval9 ? 10u : 4567u;
    outData[10] = eval10 ? 5678u : 11u;
    outData[11] = eval11 ? 12u : 6789u;
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);

    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(uint32_t), nullptr, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);

    constexpr std::array<uint32_t, 12> kInitialData = {};

    GLBuffer output;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, output);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kInitialData), kInitialData.data(),
                 GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, output);

    GLBuffer imageBufferStorage;
    GLTexture imageBuffer;

    glBindBuffer(GL_TEXTURE_BUFFER, imageBufferStorage);
    glBufferData(GL_TEXTURE_BUFFER, sizeof(uint32_t), nullptr, GL_STATIC_DRAW);

    glBindTexture(GL_TEXTURE_BUFFER, imageBuffer);
    glTexBufferEXT(GL_TEXTURE_BUFFER, GL_R32UI, imageBufferStorage);
    glBindImageTexture(0, imageBuffer, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32UI);
    ASSERT_GL_NO_ERROR();

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
    glFinish();

    std::array<uint32_t, 12> readback = {};
    void *mapped =
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kInitialData), GL_MAP_READ_BIT);
    memcpy(readback.data(), mapped, sizeof(kInitialData));
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
    ASSERT_GL_NO_ERROR();

    for (size_t i = 0; i < kInitialData.size(); ++i)
    {
        EXPECT_EQ(readback[i], i + 1) << i;
    }
}

// Test that short circuiting works correctly even if the right hand side has no side effects but
// is otherwise unsafe to execute (e.g. contains an out-of-bounds array access).
// This is a regression test for a bug where the HLSL backend would not unfold short-circuit
// expressions if the right hand side had no side effects.
TEST_P(GLSLTest_ES3, ShortCircuitUnsafeExpressionNoSideEffects)
{
    // Fragment shader based on a reproduction case for a bug where short-circuit unfolding
    // was gated by hasSideEffects().
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform int idx;
uniform bool safe;
out vec4 color;
void main() {
  float data[4] = float[4](0.25, 0.5, 0.75, 1.0);
  // hasSideEffects() == false on the RHS -> should be unfolded because it's unsafe.
  bool hit = safe && (data[idx] > 0.0);
  // If short-circuiting works, 'hit' must be false when 'safe' is false,
  // regardless of 'idx'.
  color = vec4(hit ? 1.0 : 0.0, 0.0, 0.0, 1.0);
})";

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

    GLint idxLocation  = glGetUniformLocation(program, "idx");
    GLint safeLocation = glGetUniformLocation(program, "safe");

    // Baseline: safe=true, idx=0. hit should be true.
    glUniform1i(safeLocation, 1);
    glUniform1i(idxLocation, 0);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    // Test: safe=false, idx=100 (OOB). hit should be false.
    // On some drivers/backends, if it's NOT short-circuited, this might crash or
    // return an unexpected value (e.g. if OOB read returns something > 0.0).
    glUniform1i(safeLocation, 0);
    glUniform1i(idxLocation, 100);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);

    ASSERT_GL_NO_ERROR();
}

// Test that nesting ternary and short-circuitting operators work.
TEST_P(GLSLTest, NestedTernaryAndShortCircuit)
{
    // Note that the uniform doesn't need to be set, and will contain the default value of false.
    constexpr char kFS[] = R"(
precision mediump float;
uniform bool u;
void main()
{
    int a = u ? 12345 : 2;      // will be 2
    int b = u ? 12345 : 4;      // will be 4
    int c = u ? 12345 : 0;      // will be 0

    if (a == 2                  // true path is taken
        ? (b == 3               // false path is taken
            ? (a=0) != 0
            : b != 0            // true
          ) && (                // short-circuit evaluates RHS
            (a=7) == 7          // true, modifies a
            ||                  // short-circuit doesn't evaluate RHS
            (b=8) == 8
          )
        : (a == 0 && b == 0
            ? (c += int((a=0) == 0 && (b=0) == 0)) != 0
            : (c += int((a=0) != 0 && (b=0) != 0)) != 0))
    {
        c += 15;                // will execute
    }

    // Verify that a is 7, b is 4 and c is 15.
    gl_FragColor = vec4(a == 7, b == 4, c == 15, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that nesting ternary and short-circuitting operators work, but using function calls
// instead of assignments for side effects.
TEST_P(GLSLTest, NestedTernaryAndShortCircuitWithFunctionCalls)
{
    // Note that the uniform doesn't need to be set, and will contain the default value of false.
    constexpr char kFS[] = R"(
precision mediump float;
uniform bool u;

int globVar = 0;

int f () {
    globVar++;
    return globVar;
}

void main()
{
    int a = u ? 12345 : 2;      // will be 2
    int b = u ? 12345 : 4;      // will be 4
    int c = u ? 12345 : 0;      // will be 0

    if (a == 2                  // true path is taken
        ? (b == 3               // false path is taken
            ? f() != 0
            : b != 0            // true
          ) && (                // short-circuit evaluates RHS
            f() == 1          // true, modifies globVar
            ||                  // short-circuit doesn't evaluate RHS
            f() == 8
          )
        : (a == 0 && b == 0
            ? (int(f() == 2 && f() == 0)) != 0
            : (int(f() != 2 && f() != 0)) != 0))
    {
        c += 15;                // will execute
    }

    // Verify that a is 7, b is 4 and c is 15.
    gl_FragColor = vec4(a == 2, b == 4, c == 15, globVar == 1);
})";

    ANGLE_GL_PROGRAM(prog, essl1_shaders::vs::Simple(), kFS);
    drawQuad(prog, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that uniform bvecN passed to functions work.
TEST_P(GLSLTest_ES3, UniformBoolVectorPassedToFunctions)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform bvec4 u;
out vec4 color;

bool f(bvec4 bv)
{
    return all(bv.xz) && !any(bv.yw);
}

void main() {
    color = f(u) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(prog, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(prog);

    GLint uloc = glGetUniformLocation(prog, "u");
    ASSERT_NE(uloc, -1);
    glUniform4ui(uloc, true, false, true, false);

    drawQuad(prog, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that bvecN in storage buffer passed to functions work.
TEST_P(GLSLTest_ES31, StorageBufferBoolVectorPassedToFunctions)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0, std430) buffer Output {
    bvec4 b;
    bool valid;
} outbuf;

bool f_in(bvec4 bv)
{
    return all(bv.xz) && !any(bv.yw);
}

bool f_inout(inout bvec4 bv)
{
    bool ok = all(bv.xz) && !any(bv.yw);
    bv.xw = bvec2(false, true);
    return ok;
}

void f_out(out bvec4 bv)
{
    bv = bvec4(false, true, false, true);
}

void main() {
    bool valid = f_in(outbuf.b);
    valid = f_inout(outbuf.b) && valid;
    f_out(outbuf.b);
    outbuf.valid = valid;
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    glUseProgram(program);

    constexpr std::array<GLuint, 5> kOutputInitData = {true, false, true, false, false};
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kOutputInitData), kOutputInitData.data(),
                 GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, outputBuffer);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kOutputInitData), GL_MAP_READ_BIT));
    EXPECT_FALSE(ptr[0]);
    EXPECT_TRUE(ptr[1]);
    EXPECT_FALSE(ptr[2]);
    EXPECT_TRUE(ptr[3]);
    EXPECT_TRUE(ptr[4]);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that using a sampler2D and samplerExternalOES in the same shader works
// (anglebug.com/42260512)
TEST_P(GLSLTest, ExternalAnd2DSampler)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_EGL_image_external"));

    constexpr char kFS[] = R"(#extension GL_OES_EGL_image_external : enable
precision mediump float;
uniform samplerExternalOES tex0;
uniform sampler2D tex1;
void main(void)
{
    vec2 uv = vec2(0.0, 0.0);
    gl_FragColor = texture2D(tex0, uv) + texture2D(tex1, uv);
})";

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

// Tests that monomorphizing functions does not crash if there is a main prototype.
TEST_P(GLSLTest, MonomorphizeMainPrototypeNoCrash)
{
    constexpr char kFS[] = R"(precision mediump float;
struct S { sampler2D source; };
vec4 f(S s)
{
    return texture2D(s.source, vec2(5));
}
uniform S green;
void main();
void main() {
    f(green);
})";
    CompileShader(GL_FRAGMENT_SHADER, kFS);
    ASSERT_GL_NO_ERROR();
}

// Tests that a vertex shader retrieving values from a 2D texture to
// use as an offset for gl_PointSize successfully compiles.
// https://issues.angleproject.org/444653099
TEST_P(GLSLTest_ClampPointSize, MonomorphizeMainPrototypeNoCrash)
{
    constexpr char kVS[] = R"(
attribute vec2 a_texCoord;
struct Wrapper{
    sampler2D sampler;
};
float getRedValue(Wrapper wrapper){
    return texture2D(wrapper.sampler, a_texCoord).r;
}
uniform Wrapper u_wrappedSampler;

void main();
void main(){
    float offset = getRedValue(u_wrappedSampler);
    gl_PointSize = 10.0 + offset;
    return;
}
)";
    CompileShader(GL_VERTEX_SHADER, kVS);
    ASSERT_GL_NO_ERROR();
}

// Tests that monomorphizing a function with switch with value-case and
// default-case does not crash.
TEST_P(GLSLTest, MonomorphizeSwitchCaseAndDefaultNoCrash)
{
    // Switch requires ES3.
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() < 3);

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 o;
struct S { sampler2D source; };
vec4 f(S s)
{
    switch(0) {
      case 0: return texture(s.source, vec2(0,0));
      default: break;
    }
    return vec4(1,0,0,1);
}
uniform S green;
void main() {
    o = f(green);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    // Create a green texture to sample from.
    glActiveTexture(GL_TEXTURE0);
    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &GLColor::green);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Test that using a varying matrix is supported.
TEST_P(GLSLTest, VaryingMatrix)
{
    constexpr char kVS[] =
        "uniform vec2 u_a1;\n"
        "attribute vec4 a_position;\n"
        "varying mat2 v_mat;\n"
        "void main() {\n"
        "    v_mat = mat2(u_a1, 0.0, 0.0);\n"
        "    gl_Position = a_position;\n"
        "}";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying mat2 v_mat;\n"
        "void main(void)\n"
        "{\n"
        "    gl_FragColor = vec4(v_mat[0].x, v_mat[0].y, v_mat[0].x, 1.0);\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint oneIndex = glGetUniformLocation(program, "u_a1");
    ASSERT_NE(-1, oneIndex);
    glUseProgram(program);
    glUniform2f(oneIndex, 0.25f, 0.5f);

    drawQuad(program, "a_position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(63, 128, 63, 255), 1.0);
}

// Test that using a varying matrix array is supported.
TEST_P(GLSLTest, VaryingMatrixArray)
{
    constexpr char kVS[] =
        "uniform vec2 u_a1;\n"
        "uniform vec2 u_a2;\n"
        "attribute vec4 a_position;\n"
        "varying mat2 v_mat[2];\n"
        "void main() {\n"
        "    v_mat[0] = mat2(u_a1, u_a2);\n"
        "    v_mat[1] = mat2(1.0 - u_a2, 1.0 - u_a1);\n"
        "    gl_Position = a_position;\n"
        "}";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying mat2 v_mat[2];\n"
        "void main(void)\n"
        "{\n"
        "    gl_FragColor = vec4(v_mat[0][0].x, v_mat[0][0].y, v_mat[1][0].x, 1.0);\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint oneIndex = glGetUniformLocation(program, "u_a1");
    ASSERT_NE(-1, oneIndex);
    GLint twoIndex = glGetUniformLocation(program, "u_a2");
    ASSERT_NE(-1, twoIndex);
    glUseProgram(program);
    glUniform2f(oneIndex, 1, 0.5f);
    glUniform2f(twoIndex, 0.25f, 0.125f);

    drawQuad(program, "a_position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 127, 255 - 63, 255), 1.0);
}

// Test that using a centroid varying matrix array is supported.
TEST_P(GLSLTest_ES3, CentroidVaryingMatrixArray)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "uniform vec2 u_a1;\n"
        "uniform vec2 u_a2;\n"
        "in vec4 a_position;\n"
        "centroid out mat3x2 v_mat[2];\n"
        "void main() {\n"
        "    v_mat[0] = mat3x2(u_a1, u_a2, vec2(0.0));\n"
        "    v_mat[1] = mat3x2(vec2(0.0), 1.0 - u_a2, 1.0 - u_a1);\n"
        "    gl_Position = a_position;\n"
        "}";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "centroid in mat3x2 v_mat[2];\n"
        "layout(location = 0) out vec4 out_color;\n"
        "void main(void)\n"
        "{\n"
        "    out_color = vec4(v_mat[0][0].x, v_mat[0][0].y, v_mat[1][1].x, 1.0);\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint oneIndex = glGetUniformLocation(program, "u_a1");
    ASSERT_NE(-1, oneIndex);
    GLint twoIndex = glGetUniformLocation(program, "u_a2");
    ASSERT_NE(-1, twoIndex);
    glUseProgram(program);
    glUniform2f(oneIndex, 1, 0.5f);
    glUniform2f(twoIndex, 0.25f, 0.125f);

    drawQuad(program, "a_position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 127, 255 - 63, 255), 1.0);
}

// Test that using a flat varying matrix array is supported.
TEST_P(GLSLTest_ES3, FlatVaryingMatrixArray)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "uniform vec2 u_a1;\n"
        "uniform vec2 u_a2;\n"
        "in vec4 a_position;\n"
        "flat out mat2 v_mat[2];\n"
        "void main() {\n"
        "    v_mat[0] = mat2(u_a1, u_a2);\n"
        "    v_mat[1] = mat2(u_a2, u_a1);\n"
        "    gl_Position = a_position;\n"
        "}";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "flat in mat2 v_mat[2];\n"
        "layout(location = 0) out vec4 out_color;\n"
        "void main(void)\n"
        "{\n"
        "    out_color = vec4(v_mat[0][0].x, v_mat[0][0].y, v_mat[1][0].x, 1.0);\n"
        "}";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint oneIndex = glGetUniformLocation(program, "u_a1");
    ASSERT_NE(-1, oneIndex);
    GLint twoIndex = glGetUniformLocation(program, "u_a2");
    ASSERT_NE(-1, twoIndex);
    glUseProgram(program);
    glUniform2f(oneIndex, 1, 0.5f);
    glUniform2f(twoIndex, 0.25f, 0.125f);

    drawQuad(program, "a_position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 127, 63, 255), 1.0);
}

// Test that using a varying matrix array works with a particular input names scheme.
// Test that having "mat3 a[*]" and "mat2 a_0" does not cause internal shader compile failures.
// For a buggy naming scheme both would expand into a_0_0 and cause problems.
TEST_P(GLSLTest_ES3, VaryingMatrixArrayNaming2)
{
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;
uniform mat3 r0;
uniform mat2 r1;
in vec4 a_position;
out mat2 a_0;
out mat3 a[2];
void main() {
    a[0] = r0;
    a[1] = r0 + mat3(1, 1, 1, 1, 1, 1, 1, 1, 1);
    a_0 = r1;
    gl_Position = a_position;
})";
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in mat2 a_0;
in mat3 a[2];
layout(location = 0) out vec4 o;
void main(void) {
    mat3 diff0 = a[0] - mat3(0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8);
    mat3 diff1 = a[1] - mat3(1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8);
    mat2 diff2 = a_0 - mat2(3.0, 3.1, 3.2, 3.3);
    o.r = all(lessThan(abs(diff0[0]) + abs(diff0[1]) + abs(diff0[2]), vec3(0.01))) ? 1.0 : 0.0;
    o.g = all(lessThan(abs(diff1[0]) + abs(diff1[1]) + abs(diff1[2]), vec3(0.01))) ? 1.0 : 0.0;
    o.b = all(lessThan(abs(diff2[0]) + abs(diff2[1]), vec2(0.01))) ? 1.0 : 0.0;
    o.a = 1.0;
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint r0 = glGetUniformLocation(program, "r0");
    ASSERT_NE(-1, r0);
    GLint r1 = glGetUniformLocation(program, "r1");
    ASSERT_NE(-1, r1);
    glUseProgram(program);
    float r0v[] = {0.0f, 0.1f, 0.2f, 0.3f, 0.4f, 0.5f, 0.6f, 0.7f, 0.8f};
    glUniformMatrix3fv(r0, 1, false, r0v);
    float r1v[] = {3.0f, 3.1f, 3.2f, 3.3f};
    glUniformMatrix2fv(r1, 1, false, r1v);
    drawQuad(program, "a_position", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 255, 255, 255), 1.0);
}

// Test that literal infinity can be written out from the shader translator.
// A similar test can't be made for NaNs, since ESSL 3.00.6 requirements for NaNs are very loose.
TEST_P(GLSLTest_ES3, LiteralInfinityOutput)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 out_color;\n"
        "uniform float u;\n"
        "void main()\n"
        "{\n"
        "   float infVar = 1.0e40 - u;\n"
        "   bool correct = isinf(infVar) && infVar > 0.0;\n"
        "   out_color = correct ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that literal negative infinity can be written out from the shader translator.
// A similar test can't be made for NaNs, since ESSL 3.00.6 requirements for NaNs are very loose.
TEST_P(GLSLTest_ES3, LiteralNegativeInfinityOutput)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 out_color;\n"
        "uniform float u;\n"
        "void main()\n"
        "{\n"
        "   float infVar = -1.0e40 + u;\n"
        "   bool correct = isinf(infVar) && infVar < 0.0;\n"
        "   out_color = correct ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// The following MultipleDeclaration* tests are testing TranslatorHLSL specific simplification
// passes. Because the interaction of multiple passes must be tested, it is difficult to write
// a unittest for them. Instead we add the tests as end2end so will in particular test
// TranslatorHLSL when run on Windows.

// Test that passes splitting multiple declarations and comma operators are correctly ordered.
TEST_P(GLSLTest_ES3, MultipleDeclarationWithCommaOperator)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

uniform float u;
float c = 0.0;
float sideEffect()
{
    c = u;
    return c;
}

void main(void)
{
    float a = 0.0, b = ((gl_FragCoord.x < 0.5 ? a : sideEffect()), a);
    color = vec4(b + c);
})";

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

// Test that passes splitting multiple declarations and comma operators and for loops are
// correctly ordered.
TEST_P(GLSLTest_ES3, MultipleDeclarationWithCommaOperatorInForLoop)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

uniform float u;
float c = 0.0;
float sideEffect()
{
    c = u;
    return c;
}

void main(void)
{
    for(float a = 0.0, b = ((gl_FragCoord.x < 0.5 ? a : sideEffect()), a); a < 10.0; a++)
    {
        b += 1.0;
        color = vec4(b);
    }
})";

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

// Test that splitting multiple declaration in for loops works with no loop condition
TEST_P(GLSLTest_ES3, MultipleDeclarationInForLoopEmptyCondition)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 color;\n"
        "void main(void)\n"
        "{\n"
        " for(float a = 0.0, b = 1.0;; a++)\n"
        " {\n"
        "  b += 1.0;\n"
        "  if (a > 10.0) {break;}\n"
        "  color = vec4(b);\n"
        " }\n"
        "}\n";

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

// Test that splitting multiple declaration in for loops works with no loop expression
TEST_P(GLSLTest_ES3, MultipleDeclarationInForLoopEmptyExpression)
{
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 color;\n"
        "void main(void)\n"
        "{\n"
        " for(float a = 0.0, b = 1.0; a < 10.0;)\n"
        " {\n"
        "  b += 1.0;\n"
        "  a += 1.0;\n"
        "  color = vec4(b);\n"
        " }\n"
        "}\n";

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

// Test that dynamic indexing of constant vectors work.
TEST_P(GLSLTest_ES3, DynamicIndexingConstantVector)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform int zero;

void main() {
    float r = vec3(1, 0.5, 0)[zero + 1];
    const vec3 c = vec3(0.1, 0.3, 0.25);
    float g = c[zero + 2];
    color = vec4(r, g, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 63, 0, 255), 1);
}

// Test that dynamic indexing of a matrix inside a dynamic indexing of a vector in an l-value works
// correctly.
TEST_P(GLSLTest_ES3, NestedDynamicIndexingInLValue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 my_FragColor;
uniform int u_zero;
void main() {
    mat2 m = mat2(0.0, 0.0, 0.0, 0.0);
    m[u_zero + 1][u_zero + 1] = float(u_zero + 1);
    float f = m[1][1];
    my_FragColor = vec4(1.0 - f, f, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

class WebGLGLSLTest : public GLSLTest
{
  protected:
    WebGLGLSLTest() { setWebGLCompatibilityEnabled(true); }
};

class WebGL2GLSLTest : public GLSLTest
{
  protected:
    WebGL2GLSLTest() { setWebGLCompatibilityEnabled(true); }
};

TEST_P(WebGLGLSLTest, MaxVaryingVec4PlusFragCoord)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Generate shader code that uses gl_FragCoord, a special fragment shader variables.
    // This test should fail, since we are really using (maxVaryings + 1) varyings.
    VaryingTestBase(0, 0, 0, 0, 0, 0, maxVaryings, 0, true, false, false, false);
}

TEST_P(WebGLGLSLTest, MaxVaryingVec4PlusPointCoord)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    // Generate shader code that uses gl_FragCoord, a special fragment shader variables.
    // This test should fail, since we are really using (maxVaryings + 1) varyings.
    VaryingTestBase(0, 0, 0, 0, 0, 0, maxVaryings, 0, false, true, false, false);
}

TEST_P(WebGLGLSLTest, MaxPlusOneVaryingVec3)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 0, 0, maxVaryings + 1, 0, 0, 0, false, false, false, false);
}

TEST_P(WebGLGLSLTest, MaxPlusOneVaryingVec3Array)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 0, 0, 0, maxVaryings / 2 + 1, 0, 0, false, false, false, false);
}

TEST_P(WebGLGLSLTest, MaxVaryingVec3AndOneVec2)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 1, 0, maxVaryings, 0, 0, 0, false, false, false, false);
}

TEST_P(WebGLGLSLTest, MaxPlusOneVaryingVec2)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, 0, 2 * maxVaryings + 1, 0, 0, 0, 0, 0, false, false, false, false);
}

TEST_P(WebGLGLSLTest, MaxVaryingVec3ArrayAndMaxPlusOneFloatArray)
{
    GLint maxVaryings = 0;
    glGetIntegerv(GL_MAX_VARYING_VECTORS, &maxVaryings);

    VaryingTestBase(0, maxVaryings / 2 + 1, 0, 0, 0, 0, 0, maxVaryings / 2, false, false, false,
                    false);
}

// Test that FindLSB and FindMSB return correct values in their corner cases.
TEST_P(GLSLTest_ES31, FindMSBAndFindLSBCornerCases)
{
    // Suspecting AMD driver bug - failure seen on bots running on AMD R5 230.
    ANGLE_SKIP_TEST_IF(IsAMD() && IsOpenGL() && IsLinux());

    // Failing on N5X Oreo http://anglebug.com/42261013
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsAdreno() && IsOpenGLES());

    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out vec4 my_FragColor;\n"
        "uniform int u_zero;\n"
        "void main() {\n"
        "    if (findLSB(u_zero) == -1 && findMSB(u_zero) == -1 && findMSB(u_zero - 1) == -1)\n"
        "    {\n"
        "        my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "    }\n"
        "    else\n"
        "    {\n"
        "        my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "    }\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a comma expression does not add any redundant statements
// during shader translation rewrite steps. The test will verify the
// correct color is drawn as a result of the called functions being
// executed in the correct order and only once each.
TEST_P(GLSLTest, CommaTestNoRedundantStatements)
{
    const char kFS[] = R"(
precision mediump float;
int g = 1;
void F() { g *= 3; }
void G() { g += 5; }
void main() {
    F(), G();
    if (g == 8)
    {
        gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0);
    }
    else
    {
        gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that reading from a swizzled vector that is dynamically indexed succeeds.
TEST_P(GLSLTest_ES3, ReadFromDynamicIndexingOfSwizzledVector)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

uniform int index;
uniform vec4 data;

out vec4 color;
void main() {
    color = vec4(vec4(data.x, data.y, data.z, data.w).zyxw[index], 0, 0, 1);
})";

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

    GLint dataLoc = glGetUniformLocation(program, "data");
    glUniform4f(dataLoc, 0.2, 0.4, 0.6, 0.8);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);

    EXPECT_PIXEL_NEAR(0, 0, 153, 0, 0, 255, 1);
}

// Test that writing into a swizzled vector that is dynamically indexed succeeds.
TEST_P(GLSLTest_ES3, WriteIntoDynamicIndexingOfSwizzledVector)
{
    // http://anglebug.com/40644616
    ANGLE_SKIP_TEST_IF(IsOpenGL());

    // The shader first assigns v.x to v.z (1.0)
    // Then v.y to v.y (2.0)
    // Then v.z to v.x (1.0)
    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 my_FragColor;\n"
        "void main() {\n"
        "    vec3 v = vec3(1.0, 2.0, 3.0);\n"
        "    for (int i = 0; i < 3; i++) {\n"
        "        v.zyx[i] = v[i];\n"
        "    }\n"
        "    my_FragColor = distance(v, vec3(1.0, 2.0, 1.0)) < 0.01 ? vec4(0, 1, 0, 1) : vec4(1, "
        "0, 0, 1);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test including Ternary using a uniform block is correctly
// expanded.
TEST_P(GLSLTest_ES3, NamelessUniformBlockTernary)
{
    const char kVS[] = R"(#version 300 es
    precision highp float;
    out vec4 color_interp;
    void  main()
    {
        color_interp = vec4(0.0);
    }
)";
    const char kFS[] = R"(#version 300 es
    precision highp float;
    out vec4 fragColor;
    in vec4 color_interp;
layout(std140) uniform TestData {
    int a;
    int b;
};
void main()
{
    int c, a1;
    a1 += c > 0 ? a : b;
    fragColor = vec4(a1,a1,a1,1.0);
}
)";
    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
}

// Test that uniform block variables work as comma expression results.
TEST_P(GLSLTest_ES3, UniformBlockCommaExpressionResult)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
layout (std140) uniform C {
    float u;
    float v;
};
out vec4 o;
void main() {
    vec2 z = vec2(1.0 - u, v);
    vec2 b = vec2((z=z, u)); // Being tested.
    o = vec4(b.x, z.x, b.x, 1.0);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    constexpr GLfloat kInput1Data[2] = {1.f, 0.f};
    GLBuffer input1;
    glBindBuffer(GL_UNIFORM_BUFFER, input1);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat) * 2, &kInput1Data, GL_STATIC_COPY);
    const GLuint kInput1Index = glGetUniformBlockIndex(program, "C");
    glUniformBlockBinding(program, kInput1Index, 1);
    glBindBufferBase(GL_UNIFORM_BUFFER, 1, input1);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
    ASSERT_GL_NO_ERROR();
}

// Test that the length() method is correctly translated in Vulkan atomic counter buffer emulation.
TEST_P(GLSLTest_ES31, AtomicCounterArrayLength)
{
    // Crashes on an assertion.  The driver reports no atomic counter buffers when queried from the
    // program, but ANGLE believes there to be one.
    //
    // This is likely due to the fact that ANGLE generates the following code, as a side effect of
    // the code on which .length() is being called:
    //
    //     _uac1[(_uvalue = _utestSideEffectValue)];
    //
    // The driver is optimizing the subscription out, and calling the atomic counter inactive.  This
    // was observed on nvidia, mesa and amd/windows.
    //
    // The fix would be for ANGLE to skip uniforms it believes should exist, but when queried, the
    // driver says don't.
    //
    // http://anglebug.com/42262426
    ANGLE_SKIP_TEST_IF(IsOpenGL());

    constexpr char kCS[] = R"(#version 310 es
precision mediump float;
layout(local_size_x=1) in;

layout(binding = 0) uniform atomic_uint ac1[2][3][4];
uniform uint testSideEffectValue;

layout(binding = 1, std140) buffer Result
{
    uint value;
} result;

void main() {
    bool passed = true;
    if (ac1.length() != 2)
    {
        passed = false;
    }
    uint value = 0u;
    if (ac1[value = testSideEffectValue].length() != 3)
    {
        passed = false;
    }
    if (value != testSideEffectValue)
    {
        passed = false;
    }
    if (ac1[1][value = testSideEffectValue + 1u].length() != 4)
    {
        passed = false;
    }
    if (value != testSideEffectValue + 1u)
    {
        passed = false;
    }
    result.value = passed ? 255u : 127u;
})";

    constexpr unsigned int kUniformTestValue     = 17;
    constexpr unsigned int kExpectedSuccessValue = 255;
    constexpr unsigned int kAtomicCounterRows    = 2;
    constexpr unsigned int kAtomicCounterCols    = 3;

    GLint maxAtomicCounters = 0;
    glGetIntegerv(GL_MAX_COMPUTE_ATOMIC_COUNTERS, &maxAtomicCounters);
    EXPECT_GL_NO_ERROR();

    // Required minimum is 8 by the spec
    EXPECT_GE(maxAtomicCounters, 8);
    ANGLE_SKIP_TEST_IF(static_cast<uint32_t>(maxAtomicCounters) <
                       kAtomicCounterRows * kAtomicCounterCols);

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    glUseProgram(program);

    constexpr unsigned int kBufferData[kAtomicCounterRows * kAtomicCounterCols] = {};
    GLBuffer atomicCounterBuffer;
    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
    glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(kBufferData), kBufferData, GL_STATIC_DRAW);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);

    constexpr unsigned int kOutputInitValue = 0;
    GLBuffer shaderStorageBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, shaderStorageBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kOutputInitValue), &kOutputInitValue,
                 GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, shaderStorageBuffer);

    GLint uniformLocation = glGetUniformLocation(program, "testSideEffectValue");
    EXPECT_NE(uniformLocation, -1);
    glUniform1ui(uniformLocation, kUniformTestValue);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint), GL_MAP_READ_BIT));
    EXPECT_EQ(*ptr, kExpectedSuccessValue);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that inactive images don't cause any errors.
TEST_P(GLSLTest_ES31, InactiveImages)
{
    ANGLE_SKIP_TEST_IF(IsD3D11());

    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(rgba32ui) uniform highp readonly uimage2D image1;
layout(rgba32ui) uniform highp readonly uimage2D image2[4];
void main()
{
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    // Verify that the images are indeed inactive.
    GLuint index = glGetProgramResourceIndex(program, GL_UNIFORM, "image1");
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(GL_INVALID_INDEX, index);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "image2");
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(GL_INVALID_INDEX, index);
}

// Test that inactive atomic counters don't cause any errors.
TEST_P(GLSLTest_ES31, InactiveAtomicCounters)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
layout(binding = 0, offset = 0) uniform atomic_uint ac1;
layout(binding = 0, offset = 4) uniform atomic_uint ac2[5];
void main()
{
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    // Verify that the atomic counters are indeed inactive.
    GLuint index = glGetProgramResourceIndex(program, GL_UNIFORM, "ac1");
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(GL_INVALID_INDEX, index);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "ac2");
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(GL_INVALID_INDEX, index);
}

// Test that inactive samplers in structs don't cause any errors.
TEST_P(GLSLTest_ES31, InactiveSamplersInStructCS)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1, local_size_y=1, local_size_z=1) in;
struct S
{
    vec4 v;
    sampler2D t[10];
};
uniform S s;
void main()
{
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();
}

// Test that array indices for arrays of arrays of basic types work as expected.
TEST_P(GLSLTest_ES31, ArraysOfArraysBasicType)
{
    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out vec4 my_FragColor;\n"
        "uniform ivec2 test[2][2];\n"
        "void main() {\n"
        "    bool passed = true;\n"
        "    for (int i = 0; i < 2; i++) {\n"
        "        for (int j = 0; j < 2; j++) {\n"
        "            if (test[i][j] != ivec2(i + 1, j + 1)) {\n"
        "                passed = false;\n"
        "            }\n"
        "        }\n"
        "    }\n"
        "    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            std::stringstream uniformName;
            uniformName << "test[" << i << "][" << j << "]";
            GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
            // All array indices should be used.
            EXPECT_NE(uniformLocation, -1);
            glUniform2i(uniformLocation, i + 1, j + 1);
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that array indices for arrays of arrays of basic types work as expected
// inside blocks.
TEST_P(GLSLTest_ES31, ArraysOfArraysBlockBasicType)
{
    // anglebug.com/42262465 - fails on AMD Windows
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsOpenGL());
    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out vec4 my_FragColor;\n"
        "layout(packed) uniform UBO { ivec2 test[2][2]; } ubo_data;\n"
        "void main() {\n"
        "    bool passed = true;\n"
        "    for (int i = 0; i < 2; i++) {\n"
        "        for (int j = 0; j < 2; j++) {\n"
        "            if (ubo_data.test[i][j] != ivec2(i + 1, j + 1)) {\n"
        "                passed = false;\n"
        "            }\n"
        "        }\n"
        "    }\n"
        "    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    // Use interface queries to determine buffer size and offset
    GLuint uboBlockIndex   = glGetProgramResourceIndex(program, GL_UNIFORM_BLOCK, "UBO");
    GLenum uboDataSizeProp = GL_BUFFER_DATA_SIZE;
    GLint uboDataSize;
    glGetProgramResourceiv(program, GL_UNIFORM_BLOCK, uboBlockIndex, 1, &uboDataSizeProp, 1,
                           nullptr, &uboDataSize);
    std::unique_ptr<char[]> uboData(new char[uboDataSize]);
    for (int i = 0; i < 2; i++)
    {
        std::stringstream resourceName;
        resourceName << "UBO.test[" << i << "][0]";
        GLenum resourceProps[] = {GL_ARRAY_STRIDE, GL_OFFSET};
        struct
        {
            GLint stride;
            GLint offset;
        } values;
        GLuint resourceIndex =
            glGetProgramResourceIndex(program, GL_UNIFORM, resourceName.str().c_str());
        ASSERT_NE(resourceIndex, GL_INVALID_INDEX);
        glGetProgramResourceiv(program, GL_UNIFORM, resourceIndex, 2, &resourceProps[0], 2, nullptr,
                               &values.stride);
        for (int j = 0; j < 2; j++)
        {
            GLint(&dataPtr)[2] =
                *reinterpret_cast<GLint(*)[2]>(&uboData[values.offset + j * values.stride]);
            dataPtr[0] = i + 1;
            dataPtr[1] = j + 1;
        }
    }
    GLBuffer ubo;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, uboDataSize, &uboData[0], GL_STATIC_DRAW);
    GLuint ubo_index = glGetUniformBlockIndex(program, "UBO");
    ASSERT_NE(ubo_index, GL_INVALID_INDEX);
    glUniformBlockBinding(program, ubo_index, 5);
    glBindBufferBase(GL_UNIFORM_BUFFER, 5, ubo);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that arrays of arrays of samplers work as expected.
TEST_P(GLSLTest_ES31, ArraysOfArraysSampler)
{
    constexpr char kFS[] =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out vec4 my_FragColor;\n"
        "uniform mediump isampler2D test[2][2];\n"
        "void main() {\n"
        "    bool passed = true;\n"
        "#define DO_CHECK(i,j) \\\n"
        "    if (texture(test[i][j], vec2(0.0, 0.0)) != ivec4(i + 1, j + 1, 0, 1)) { \\\n"
        "        passed = false; \\\n"
        "    }\n"
        "    DO_CHECK(0, 0)\n"
        "    DO_CHECK(0, 1)\n"
        "    DO_CHECK(1, 0)\n"
        "    DO_CHECK(1, 1)\n"
        "    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2][2];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            // First generate the texture
            int textureUnit = i * 2 + j;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[i][j]);
            GLint texData[2] = {i + 1, j + 1};
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32I, 1, 1, 0, GL_RG_INTEGER, GL_INT, &texData[0]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            // Then send it as a uniform
            std::stringstream uniformName;
            uniformName << "test[" << i << "][" << j << "]";
            GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
            // All array indices should be used.
            EXPECT_NE(uniformLocation, -1);
            glUniform1i(uniformLocation, textureUnit);
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that arrays of arrays of images work as expected.
TEST_P(GLSLTest_ES31, ArraysOfArraysImage)
{
    // http://anglebug.com/42263641
    ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());

    // Fails on D3D due to mistranslation.
    ANGLE_SKIP_TEST_IF(IsD3D());

    // Fails on Android on GLES.
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    GLint maxTextures, maxComputeImageUniforms;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextures);
    glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms);
    ANGLE_SKIP_TEST_IF(maxTextures < 1 * 2 * 3);
    ANGLE_SKIP_TEST_IF(maxComputeImageUniforms < 1 * 2 * 3);

    constexpr char kComputeShader[] = R"(#version 310 es
        layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
        layout(binding = 0, r32ui) uniform highp readonly uimage2D image[1][2][3];
        layout(binding = 1, std430) buffer Output {
            uint image_value;
        } outbuf;

        void main(void)
        {
            outbuf.image_value = uint(0.0);
            outbuf.image_value += imageLoad(image[0][0][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image[0][0][1], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image[0][0][2], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image[0][1][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image[0][1][1], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image[0][1][2], ivec2(0, 0)).x;
        })";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    GLuint outputInitData[1] = {10};
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    GLuint imageData = 200u;
    GLTexture images[1][2][3];
    for (int i = 0; i < 1; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            for (int k = 0; k < 3; k++)
            {
                glBindTexture(GL_TEXTURE_2D, images[i][j][k]);
                glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
                                &imageData);
                glBindImageTexture(i * 6 + j * 3 + k, images[i][j][k], 0, GL_FALSE, 0, GL_READ_ONLY,
                                   GL_R32UI);
                EXPECT_GL_NO_ERROR();
            }
        }
    }

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    memcpy(outputInitData, ptr, sizeof(outputInitData));
    EXPECT_EQ(outputInitData[0], imageData * 1 * 2 * 3);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that multiple arrays of arrays of images work as expected.
TEST_P(GLSLTest_ES31, ConsecutiveArraysOfArraysImage)
{
    // http://anglebug.com/42263641
    ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());

    // Fails on D3D due to mistranslation.
    ANGLE_SKIP_TEST_IF(IsD3D());

    constexpr GLsizei kImage1Layers = 3;
    constexpr GLsizei kImage1Rows   = 2;
    constexpr GLsizei kImage1Cols   = 1;
    constexpr GLsizei kImage2Rows   = 2;
    constexpr GLsizei kImage2Cols   = 4;

    constexpr GLsizei kImage1Units = kImage1Layers * kImage1Rows * kImage1Cols;
    constexpr GLsizei kImage2Units = kImage2Rows * kImage2Cols;
    constexpr GLsizei kImage3Units = 1;

    constexpr GLsizei kTotalImageCount = kImage1Units + kImage2Units + kImage3Units;

    GLint maxTextures, maxComputeImageUniforms;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextures);
    glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms);
    ANGLE_SKIP_TEST_IF(maxTextures < kTotalImageCount);
    ANGLE_SKIP_TEST_IF(maxComputeImageUniforms < kTotalImageCount);

    constexpr char kComputeShader[] = R"(#version 310 es
        layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
        layout(binding = 0, r32ui) uniform highp readonly uimage2D image1[3][2][1];
        layout(binding = 6, r32ui) uniform highp readonly uimage2D image2[2][4];
        layout(binding = 14, r32ui) uniform highp readonly uimage2D image3;
        layout(binding = 0, std430) buffer Output {
            uint image_value;
        } outbuf;

        void main(void)
        {
            outbuf.image_value = uint(0.0);

            outbuf.image_value += imageLoad(image1[0][0][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image1[0][1][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image1[1][0][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image1[1][1][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image1[2][0][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image1[2][1][0], ivec2(0, 0)).x;

            outbuf.image_value += imageLoad(image2[0][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[0][1], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[0][2], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[0][3], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[1][0], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[1][1], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[1][2], ivec2(0, 0)).x;
            outbuf.image_value += imageLoad(image2[1][3], ivec2(0, 0)).x;

            outbuf.image_value += imageLoad(image3, ivec2(0, 0)).x;
        })";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    constexpr GLuint kOutputInitData = 10;
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kOutputInitData), &kOutputInitData,
                 GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, outputBuffer);
    EXPECT_GL_NO_ERROR();

    constexpr GLsizei kImage1Binding = 0;
    constexpr GLsizei kImage2Binding = kImage1Binding + kImage1Units;
    constexpr GLsizei kImage3Binding = kImage2Binding + kImage2Units;

    constexpr GLuint kImage1Data = 13;
    GLTexture images1[kImage1Layers][kImage1Rows][kImage1Cols];
    for (int layer = 0; layer < kImage1Layers; layer++)
    {
        for (int row = 0; row < kImage1Rows; row++)
        {
            for (int col = 0; col < kImage1Cols; col++)
            {
                glBindTexture(GL_TEXTURE_2D, images1[layer][row][col]);
                glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
                glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
                                &kImage1Data);
                glBindImageTexture(kImage1Binding + (layer * kImage1Rows + row) * kImage1Cols + col,
                                   images1[layer][row][col], 0, GL_FALSE, 0, GL_READ_ONLY,
                                   GL_R32UI);
                EXPECT_GL_NO_ERROR();
            }
        }
    }

    constexpr GLuint kImage2Data = 17;
    GLTexture images2[kImage2Rows][kImage2Cols];
    for (int row = 0; row < kImage2Rows; row++)
    {
        for (int col = 0; col < kImage2Cols; col++)
        {
            glBindTexture(GL_TEXTURE_2D, images2[row][col]);
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT,
                            &kImage2Data);
            glBindImageTexture(kImage2Binding + row * kImage2Cols + col, images2[row][col], 0,
                               GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
            EXPECT_GL_NO_ERROR();
        }
    }

    constexpr GLuint kImage3Data = 19;
    GLTexture image3;
    glBindTexture(GL_TEXTURE_2D, image3);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &kImage3Data);
    glBindImageTexture(kImage3Binding, image3, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kOutputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(*ptr,
              kImage1Data * kImage1Units + kImage2Data * kImage2Units + kImage3Data * kImage3Units);
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that arrays of arrays of images of r32f format work when passed to functions.
TEST_P(GLSLTest_ES31, ArraysOfArraysOfR32fImages)
{
    // Skip if GL_OES_shader_image_atomic is not enabled.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_shader_image_atomic"));

    // http://anglebug.com/42263641
    ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());

    // Fails on D3D due to mistranslation.
    ANGLE_SKIP_TEST_IF(IsD3D());

    // Fails on Android on GLES.
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    // http://anglebug.com/42263895
    ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL());

    GLint maxComputeImageUniforms;
    glGetIntegerv(GL_MAX_COMPUTE_IMAGE_UNIFORMS, &maxComputeImageUniforms);
    ANGLE_SKIP_TEST_IF(maxComputeImageUniforms < 7);

    constexpr char kComputeShader[] = R"(#version 310 es
#extension GL_OES_shader_image_atomic : require

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0, r32f) uniform highp image2D image1[2][3];
layout(binding = 6, r32f) uniform highp image2D image2;

void testFunction(highp image2D imageOut[2][3])
{
    // image1 is an array of 1x1 images.
    // image2 is a 1x4 image with the following data:
    //
    // (0, 0): 234.5
    // (0, 1): 4.0
    // (0, 2): 456.0
    // (0, 3): 987.0


    // Write to [0][0]
    imageStore(imageOut[0][0], ivec2(0, 0), vec4(1234.5));

    // Write to [0][1]
    imageStore(imageOut[0][1], ivec2(0, 0), imageLoad(image2, ivec2(0, 0)));

    // Write to [0][2]
    imageStore(imageOut[0][2], ivec2(0, 0), vec4(imageSize(image2).y));

    // Write to [1][0]
    imageStore(imageOut[1][0], ivec2(0,
                 imageSize(image2).y - int(imageLoad(image2, ivec2(0, 1)).x)
                ), vec4(678.0));

    // Write to [1][1]
    imageStore(imageOut[1][1], ivec2(0, 0),
                vec4(imageAtomicExchange(image2, ivec2(0, 2), 135.0)));

    // Write to [1][2]
    imageStore(imageOut[1][2], ivec2(0, 0),
                    imageLoad(image2, ivec2(imageSize(image2).x - 1, 3)));
}

void main(void)
{
    testFunction(image1);
})";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    constexpr GLsizei kImageRows = 2;
    constexpr GLsizei kImageCols = 3;
    constexpr GLfloat kImageData = 0;
    GLTexture images[kImageRows][kImageCols];
    for (size_t row = 0; row < kImageRows; row++)
    {
        for (size_t col = 0; col < kImageCols; col++)
        {
            glBindTexture(GL_TEXTURE_2D, images[row][col]);
            glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32F, 1, 1);
            glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED, GL_FLOAT, &kImageData);
            glBindImageTexture(row * kImageCols + col, images[row][col], 0, GL_FALSE, 0,
                               GL_READ_WRITE, GL_R32F);
            EXPECT_GL_NO_ERROR();
        }
    }

    constexpr GLsizei kImage2Size                          = 4;
    constexpr std::array<GLfloat, kImage2Size> kImage2Data = {
        234.5f,
        4.0f,
        456.0f,
        987.0f,
    };
    GLTexture image2;
    glBindTexture(GL_TEXTURE_2D, image2);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32F, 1, kImage2Size);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, kImage2Size, GL_RED, GL_FLOAT, kImage2Data.data());
    glBindImageTexture(6, image2, 0, GL_FALSE, 0, GL_READ_WRITE, GL_R32F);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    // Verify the previous dispatch with another dispatch
    constexpr char kVerifyShader[] = R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 0, r32f) uniform highp readonly image2D image1[2][3];
layout(binding = 6, r32f) uniform highp readonly image2D image2;
layout(binding = 0, std430) buffer Output {
    float image2Data[4];
    float image1Data[6];
} outbuf;

void main(void)
{
    for (int i = 0; i < 4; ++i)
    {
        outbuf.image2Data[i] = imageLoad(image2, ivec2(0, i)).x;
    }
    outbuf.image1Data[0] = imageLoad(image1[0][0], ivec2(0, 0)).x;
    outbuf.image1Data[1] = imageLoad(image1[0][1], ivec2(0, 0)).x;
    outbuf.image1Data[2] = imageLoad(image1[0][2], ivec2(0, 0)).x;
    outbuf.image1Data[3] = imageLoad(image1[1][0], ivec2(0, 0)).x;
    outbuf.image1Data[4] = imageLoad(image1[1][1], ivec2(0, 0)).x;
    outbuf.image1Data[5] = imageLoad(image1[1][2], ivec2(0, 0)).x;
})";
    ANGLE_GL_COMPUTE_PROGRAM(verifyProgram, kVerifyShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(verifyProgram);

    constexpr std::array<GLfloat, kImage2Size + kImageRows * kImageCols> kOutputInitData = {};
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(kOutputInitData), kOutputInitData.data(),
                 GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, outputBuffer);
    EXPECT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // Verify
    const GLfloat *ptr = reinterpret_cast<const GLfloat *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(kOutputInitData), GL_MAP_READ_BIT));

    EXPECT_EQ(ptr[0], kImage2Data[0]);
    EXPECT_EQ(ptr[1], kImage2Data[1]);
    EXPECT_NEAR(ptr[2], 135.0f, 0.0001f);
    EXPECT_EQ(ptr[3], kImage2Data[3]);

    EXPECT_NEAR(ptr[4], 1234.5f, 0.0001f);
    EXPECT_NEAR(ptr[5], kImage2Data[0], 0.0001f);
    EXPECT_NEAR(ptr[6], kImage2Size, 0.0001f);
    EXPECT_NEAR(ptr[7], 678.0f, 0.0001f);
    EXPECT_NEAR(ptr[8], kImage2Data[2], 0.0001f);
    EXPECT_NEAR(ptr[9], kImage2Data[3], 0.0001f);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Check that imageLoad gives the correct color after clearing the texture -- anglebug.com/42265826
TEST_P(GLSLTest_ES31, ImageLoadAfterClear)
{
    ANGLE_GL_PROGRAM(program,
                     R"(#version 310 es
precision highp float;
void main()
{
    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})",

                     R"(#version 310 es
precision highp float;
layout(binding=0, rgba8) readonly highp uniform image2D img;
out vec4 fragColor;
void main()
{
    ivec2 imgcoord = ivec2(floor(gl_FragCoord.xy));
    fragColor = vec4(1, 0, 0, 0) + imageLoad(img, imgcoord);
})");
    ASSERT_TRUE(program.valid());
    glUseProgram(program);

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth(), getWindowHeight());
    glBindImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);

    // Clear the texture to green.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glClearColor(0, 1, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT);

    // Draw the texture via imageLoad, plus red, into the main framebuffer. Make sure the texture
    // was still green. (green + red == yellow.)
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::yellow);
    ASSERT_GL_NO_ERROR();
}

// Check that writeonly image2D handles can be passed as function args.
TEST_P(GLSLTest_ES31, WriteOnlyImage2DAsFunctionArg)
{
    // Create an image.
    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth(), getWindowHeight());

    // Clear the image to red.
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

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

    const char kVS[] = R"(#version 310 es
precision highp float;
void main()
{
    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})";

    const char kFS[] = R"(#version 310 es
precision highp float;
layout(binding=0, rgba8) writeonly highp uniform image2D uniformImage;
void store(writeonly highp image2D img, vec4 color)
{
    ivec2 imgcoord = ivec2(floor(gl_FragCoord.xy));
    imageStore(img, imgcoord, color);
}
void main()
{
    store(uniformImage, vec4(1, 1, 0, 1));
})";

    // Store yellow to the image.
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindImageTexture(0, tex, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA8);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT);

    // Check that the image is yellow.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::yellow);
    ASSERT_GL_NO_ERROR();
}

// Check that readonly image2D handles can be passed as function args.
TEST_P(GLSLTest_ES31, ReadOnlyImage2DAsFunctionArg)
{
    const int w = getWindowWidth();
    const int h = getWindowHeight();

    // Create an image.
    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);

    const std::vector<GLColor> kInitData(w * h, GLColor::red);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_RGBA, GL_UNSIGNED_BYTE, kInitData.data());

    // Create a framebuffer.
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Initialize the framebuffer with the contents of the texture.
    glClearColor(0, 0, 0, 0);
    glClear(GL_COLOR_BUFFER_BIT);

    const char kVS[] = R"(#version 310 es
precision highp float;
void main()
{
    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})";

    const char kFS[] = R"(#version 310 es
precision highp float;
layout(binding=0, rgba8) readonly highp uniform image2D uniformImage;
out vec4 color;
vec4 load(readonly highp image2D img)
{
    ivec2 imgcoord = ivec2(floor(gl_FragCoord.xy));
    return imageLoad(img, imgcoord);
}
void main()
{
    color = load(uniformImage);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    glBindImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA8);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

    // Check that the framebuffer is red.
    EXPECT_PIXEL_RECT_EQ(0, 0, w, h, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Check that the volatile keyword combined with memoryBarrierImage() allow load/store from
// different aliases of the same image -- anglebug.com/42265813
//
// ES 3.1 requires most image formats to be either readonly or writeonly. (It appears that this
// limitation exists due to atomics, since we still have the volatile keyword and the built-in
// memoryBarrierImage(), which ought to allow us to load and store different aliases of the same
// image.) To test this, we create two aliases of the same image -- one for reading and one for
// writing.
TEST_P(GLSLTest_ES31, AliasedLoadStore)
{

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, getWindowWidth(), getWindowHeight());

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    glClearColor(1, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glMemoryBarrier(GL_FRAMEBUFFER_BARRIER_BIT | GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    ANGLE_GL_PROGRAM(program,

                     R"(#version 310 es
precision highp float;
void main()
{
    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})",

                     R"(#version 310 es
precision highp float;
layout(binding=0, rgba8) volatile readonly highp uniform image2D img_r;
layout(binding=0, rgba8) volatile writeonly highp uniform image2D img_w;
uniform vec4 drawColor;
void main()
{
    ivec2 coord = ivec2(floor(gl_FragCoord.xy));
    vec4 oldval = imageLoad(img_r, coord);
    memoryBarrierImage();
    imageStore(img_w, coord, oldval + drawColor);
})");

    ASSERT_TRUE(program.valid());
    glUseProgram(program);
    GLint drawColorLocation = glGetUniformLocation(program, "drawColor");

    // Tell the driver the binding is GL_READ_WRITE, since it will be referenced by two image2Ds:
    // one readeonly and one writeonly.
    glBindImageTexture(0, tex, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA8);

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glUniform4f(drawColorLocation, 0, 1, 0, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    // Ensure the cleared color was loaded before we stored.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::yellow);

    ASSERT_GL_NO_ERROR();

    // Now make two draws to ensure the imageStore is coherent.
    glClear(GL_COLOR_BUFFER_BIT);
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glUniform4f(drawColorLocation, 0, 1, 0, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);
    glUniform4f(drawColorLocation, 0, 0, 1, 0);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT);

    // Ensure the first imageStore was loaded by the second imageLoad.
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::white);

    ASSERT_GL_NO_ERROR();
}

// Test that structs containing arrays of samplers work as expected.
TEST_P(GLSLTest_ES31, StructArraySampler)
{
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump sampler2D data[2]; };
uniform Data test;
void main() {
    my_FragColor = vec4(texture(test.data[0], vec2(0.0, 0.0)).rg,
                        texture(test.data[1], vec2(0.0, 0.0)).rg);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2];
    GLColor expected = MakeGLColor(32, 64, 96, 255);
    GLubyte data[6]  = {};  // Two bytes of padding, so that texture can be initialized with 4 bytes
    memcpy(data, expected.data(), sizeof(expected));
    for (int i = 0; i < 2; i++)
    {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, textures[i]);
        // Each element provides two components.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + 2 * i);
        std::stringstream uniformName;
        uniformName << "test.data[" << i << "]";
        // Then send it as a uniform
        GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
        // The uniform should be active.
        EXPECT_NE(uniformLocation, -1);
        glUniform1i(uniformLocation, i);
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, expected);
}

// Test that structs containing arrays of samplers work as expected if the index has side effect
// that shouldn't execute, when the sampler is passed to a function.
TEST_P(GLSLTest_ES31, StructArraySamplerWithShortCircuitedSideEffectInIndex)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_gpu_shader5"));

    // The monomorphization AST pass run by multiple backends does not correctly handle side effects
    // in the presence of short-circuit.
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr));

    constexpr char kFS[] = R"(#version 310 es
#extension GL_OES_gpu_shader5 : require
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump sampler2D data; };
uniform Data test[2];

vec4 sampleFromTexture(Data d) {
    return texture(d.data, vec2(0.0, 0.0));
}

void main() {
    float red = 0.5;
    float green = 1.0;
    if (sampleFromTexture(test[0]).r > 0.5 && // false, the next expression shouldn't run
        sampleFromTexture(test[int(red = 1.0)]).r > 0.5)
    {
        green = 0.7;
    }
    my_FragColor = vec4(red, green, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2];
    GLColor expected = MakeGLColor(32, 64, 96, 255);
    GLubyte data[6]  = {};  // Two bytes of padding, so that texture can be initialized with 4 bytes
    memcpy(data, expected.data(), sizeof(expected));
    for (int i = 0; i < 2; i++)
    {
        glActiveTexture(GL_TEXTURE0 + i);
        glBindTexture(GL_TEXTURE_2D, textures[i]);
        // Each element provides two components.
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data + 2 * i);
        std::stringstream uniformName;
        uniformName << "test[" << i << "].data";
        // Then send it as a uniform
        GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
        // The uniform should be active.
        EXPECT_NE(uniformLocation, -1);
        glUniform1i(uniformLocation, i);
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_NEAR(0, 0, 127, 255, 0, 255, 1);
}

// Test that arrays of arrays of samplers inside structs work as expected.
TEST_P(GLSLTest_ES31, StructArrayArraySampler)
{
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump isampler2D data[2][2]; };
uniform Data test;
void main() {
    bool passed = true;
#define DO_CHECK(i,j) \
    if (texture(test.data[i][j], vec2(0.0, 0.0)) != ivec4(i + 1, j + 1, 0, 1)) { \
        passed = false; \
    }
    DO_CHECK(0, 0)
    DO_CHECK(0, 1)
    DO_CHECK(1, 0)
    DO_CHECK(1, 1)
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2][2];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            // First generate the texture
            int textureUnit = i * 2 + j;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[i][j]);
            GLint texData[2] = {i + 1, j + 1};
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32I, 1, 1, 0, GL_RG_INTEGER, GL_INT, &texData[0]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            // Then send it as a uniform
            std::stringstream uniformName;
            uniformName << "test.data[" << i << "][" << j << "]";
            GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
            // All array indices should be used.
            EXPECT_NE(uniformLocation, -1);
            glUniform1i(uniformLocation, textureUnit);
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that an array of structs with arrays of arrays of samplers works.
TEST_P(GLSLTest_ES31, ArrayStructArrayArraySampler)
{
    GLint numTextures;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTextures);
    ANGLE_SKIP_TEST_IF(numTextures < 2 * (2 * 2 + 2 * 2));
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump isampler2D data0[2][2]; mediump isampler2D data1[2][2]; };
uniform Data test[2];
void main() {
    bool passed = true;
#define DO_CHECK_ikl(i,k,l) \
    if (texture(test[i].data0[k][l], vec2(0.0, 0.0)) != ivec4(i, 0, k, l)+1) { \
        passed = false; \
    } \
    if (texture(test[i].data1[k][l], vec2(0.0, 0.0)) != ivec4(i, 1, k, l)+1) { \
        passed = false; \
    }
#define DO_CHECK_ik(i,k) \
    DO_CHECK_ikl(i, k, 0) \
    DO_CHECK_ikl(i, k, 1)
#define DO_CHECK_i(i) \
    DO_CHECK_ik(i, 0) \
    DO_CHECK_ik(i, 1)
    DO_CHECK_i(0)
    DO_CHECK_i(1)
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2][2][2][2];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            for (int k = 0; k < 2; k++)
            {
                for (int l = 0; l < 2; l++)
                {
                    // First generate the texture
                    int textureUnit = l + 2 * (k + 2 * (j + 2 * i));
                    glActiveTexture(GL_TEXTURE0 + textureUnit);
                    glBindTexture(GL_TEXTURE_2D, textures[i][j][k][l]);
                    GLint texData[4] = {i + 1, j + 1, k + 1, l + 1};
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, 1, 1, 0, GL_RGBA_INTEGER, GL_INT,
                                 &texData[0]);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                    // Then send it as a uniform
                    std::stringstream uniformName;
                    uniformName << "test[" << i << "].data" << j << "[" << k << "][" << l << "]";
                    GLint uniformLocation =
                        glGetUniformLocation(program, uniformName.str().c_str());
                    // All array indices should be used.
                    EXPECT_NE(uniformLocation, -1);
                    glUniform1i(uniformLocation, textureUnit);
                }
            }
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a complex chain of structs and arrays of samplers works as expected.
TEST_P(GLSLTest_ES31, ComplexStructArraySampler)
{
    GLint numTextures;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTextures);
    ANGLE_SKIP_TEST_IF(numTextures < 2 * 3 * (2 + 3));
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump isampler2D data0[2]; mediump isampler2D data1[3]; };
uniform Data test[2][3];
const vec2 ZERO = vec2(0.0, 0.0);
void main() {
    bool passed = true;
#define DO_CHECK_INNER0(i,j,l) \
    if (texture(test[i][j].data0[l], ZERO) != ivec4(i, j, 0, l) + 1) { \
        passed = false; \
    }
#define DO_CHECK_INNER1(i,j,l) \
    if (texture(test[i][j].data1[l], ZERO) != ivec4(i, j, 1, l) + 1) { \
        passed = false; \
    }
#define DO_CHECK(i,j) \
    DO_CHECK_INNER0(i, j, 0) \
    DO_CHECK_INNER0(i, j, 1) \
    DO_CHECK_INNER1(i, j, 0) \
    DO_CHECK_INNER1(i, j, 1) \
    DO_CHECK_INNER1(i, j, 2)
    DO_CHECK(0, 0)
    DO_CHECK(0, 1)
    DO_CHECK(0, 2)
    DO_CHECK(1, 0)
    DO_CHECK(1, 1)
    DO_CHECK(1, 2)
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    struct Data
    {
        GLTexture data1[2];
        GLTexture data2[3];
    };
    Data textures[2][3];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            GLTexture *arrays[]     = {&textures[i][j].data1[0], &textures[i][j].data2[0]};
            size_t arrayLengths[]   = {2, 3};
            size_t arrayOffsets[]   = {0, 2};
            size_t totalArrayLength = 5;
            for (int k = 0; k < 2; k++)
            {
                GLTexture *array   = arrays[k];
                size_t arrayLength = arrayLengths[k];
                size_t arrayOffset = arrayOffsets[k];
                for (int l = 0; l < static_cast<int>(arrayLength); l++)
                {
                    // First generate the texture
                    int textureUnit = arrayOffset + l + totalArrayLength * (j + 3 * i);
                    glActiveTexture(GL_TEXTURE0 + textureUnit);
                    glBindTexture(GL_TEXTURE_2D, array[l]);
                    GLint texData[4] = {i + 1, j + 1, k + 1, l + 1};
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, 1, 1, 0, GL_RGBA_INTEGER, GL_INT,
                                 &texData[0]);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                    // Then send it as a uniform
                    std::stringstream uniformName;
                    uniformName << "test[" << i << "][" << j << "].data" << k << "[" << l << "]";
                    GLint uniformLocation =
                        glGetUniformLocation(program, uniformName.str().c_str());
                    // All array indices should be used.
                    EXPECT_NE(uniformLocation, -1);
                    glUniform1i(uniformLocation, textureUnit);
                }
            }
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

TEST_P(GLSLTest_ES31, ArraysOfArraysStructDifferentTypesSampler)
{
    GLint numTextures;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTextures);
    ANGLE_SKIP_TEST_IF(numTextures < 3 * (2 + 2));
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump isampler2D data0[2]; mediump sampler2D data1[2]; };
uniform Data test[3];
ivec4 f2i(vec4 x) { return ivec4(x * 4.0 + 0.5); }
void main() {
    bool passed = true;
#define DO_CHECK_ik(i,k) \
    if (texture(test[i].data0[k], vec2(0.0, 0.0)) != ivec4(i, 0, k, 0)+1) { \
        passed = false; \
    } \
    if (f2i(texture(test[i].data1[k], vec2(0.0, 0.0))) != ivec4(i, 1, k, 0)+1) { \
        passed = false; \
    }
#define DO_CHECK_i(i) \
    DO_CHECK_ik(i, 0) \
    DO_CHECK_ik(i, 1)
    DO_CHECK_i(0)
    DO_CHECK_i(1)
    DO_CHECK_i(2)
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[3][2][2];
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            for (int k = 0; k < 2; k++)
            {
                // First generate the texture
                int textureUnit = k + 2 * (j + 2 * i);
                glActiveTexture(GL_TEXTURE0 + textureUnit);
                glBindTexture(GL_TEXTURE_2D, textures[i][j][k]);
                GLint texData[4]        = {i + 1, j + 1, k + 1, 1};
                GLubyte texDataFloat[4] = {static_cast<GLubyte>((i + 1) * 64 - 1),
                                           static_cast<GLubyte>((j + 1) * 64 - 1),
                                           static_cast<GLubyte>((k + 1) * 64 - 1), 64};
                if (j == 0)
                {
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, 1, 1, 0, GL_RGBA_INTEGER, GL_INT,
                                 &texData[0]);
                }
                else
                {
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                                 &texDataFloat[0]);
                }
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                // Then send it as a uniform
                std::stringstream uniformName;
                uniformName << "test[" << i << "].data" << j << "[" << k << "]";
                GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
                // All array indices should be used.
                EXPECT_NE(uniformLocation, -1);
                glUniform1i(uniformLocation, textureUnit);
            }
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that arrays of arrays of samplers as parameters works as expected.
TEST_P(GLSLTest_ES31, ParameterArraysOfArraysSampler)
{
    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
uniform mediump isampler2D test[2][3];
const vec2 ZERO = vec2(0.0, 0.0);

bool check(mediump isampler2D data[2][3]);
bool check(mediump isampler2D data[2][3]) {
#define DO_CHECK(i,j) \
    if (texture(data[i][j], ZERO) != ivec4(i+1, j+1, 0, 1)) { \
        return false; \
    }
    DO_CHECK(0, 0)
    DO_CHECK(0, 1)
    DO_CHECK(0, 2)
    DO_CHECK(1, 0)
    DO_CHECK(1, 1)
    DO_CHECK(1, 2)
    return true;
}
void main() {
    bool passed = check(test);
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2][3];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            // First generate the texture
            int textureUnit = i * 3 + j;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[i][j]);
            GLint texData[2] = {i + 1, j + 1};
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32I, 1, 1, 0, GL_RG_INTEGER, GL_INT, &texData[0]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            // Then send it as a uniform
            std::stringstream uniformName;
            uniformName << "test[" << i << "][" << j << "]";
            GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
            // All array indices should be used.
            EXPECT_NE(uniformLocation, -1);
            glUniform1i(uniformLocation, textureUnit);
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that structs with arrays of arrays of samplers as parameters works as expected.
TEST_P(GLSLTest_ES31, ParameterStructArrayArraySampler)
{
    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump isampler2D data[2][3]; };
uniform Data test;
const vec2 ZERO = vec2(0.0, 0.0);

bool check(Data data) {
#define DO_CHECK(i,j) \
    if (texture(data.data[i][j], ZERO) != ivec4(i+1, j+1, 0, 1)) { \
        return false; \
    }
    DO_CHECK(0, 0)
    DO_CHECK(0, 1)
    DO_CHECK(0, 2)
    DO_CHECK(1, 0)
    DO_CHECK(1, 1)
    DO_CHECK(1, 2)
    return true;
}
void main() {
    bool passed = check(test);
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[2][3];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            // First generate the texture
            int textureUnit = i * 3 + j;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[i][j]);
            GLint texData[2] = {i + 1, j + 1};
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RG32I, 1, 1, 0, GL_RG_INTEGER, GL_INT, &texData[0]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
            // Then send it as a uniform
            std::stringstream uniformName;
            uniformName << "test.data[" << i << "][" << j << "]";
            GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
            // All array indices should be used.
            EXPECT_NE(uniformLocation, -1);
            glUniform1i(uniformLocation, textureUnit);
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that arrays of arrays of structs with arrays of arrays of samplers
// as parameters works as expected.
TEST_P(GLSLTest_ES31, ParameterArrayArrayStructArrayArraySampler)
{
    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    GLint numTextures;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTextures);
    ANGLE_SKIP_TEST_IF(numTextures < 3 * 2 * 2 * 2);
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
struct Data { mediump isampler2D data[2][2]; };
uniform Data test[3][2];
const vec2 ZERO = vec2(0.0, 0.0);

bool check(Data data[3][2]) {
#define DO_CHECK_ijkl(i,j,k,l) \
    if (texture(data[i][j].data[k][l], ZERO) != ivec4(i, j, k, l) + 1) { \
        return false; \
    }
#define DO_CHECK_ij(i,j) \
    DO_CHECK_ijkl(i, j, 0, 0) \
    DO_CHECK_ijkl(i, j, 0, 1) \
    DO_CHECK_ijkl(i, j, 1, 0) \
    DO_CHECK_ijkl(i, j, 1, 1)
    DO_CHECK_ij(0, 0)
    DO_CHECK_ij(1, 0)
    DO_CHECK_ij(2, 0)
    DO_CHECK_ij(0, 1)
    DO_CHECK_ij(1, 1)
    DO_CHECK_ij(2, 1)
    return true;
}
void main() {
    bool passed = check(test);
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures[3][2][2][2];
    for (int i = 0; i < 3; i++)
    {
        for (int j = 0; j < 2; j++)
        {
            for (int k = 0; k < 2; k++)
            {
                for (int l = 0; l < 2; l++)
                {
                    // First generate the texture
                    int textureUnit = l + 2 * (k + 2 * (j + 2 * i));
                    glActiveTexture(GL_TEXTURE0 + textureUnit);
                    glBindTexture(GL_TEXTURE_2D, textures[i][j][k][l]);
                    GLint texData[4] = {i + 1, j + 1, k + 1, l + 1};
                    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32I, 1, 1, 0, GL_RGBA_INTEGER, GL_INT,
                                 &texData[0]);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                    // Then send it as a uniform
                    std::stringstream uniformName;
                    uniformName << "test[" << i << "][" << j << "].data[" << k << "][" << l << "]";
                    GLint uniformLocation =
                        glGetUniformLocation(program, uniformName.str().c_str());
                    // All array indices should be used.
                    EXPECT_NE(uniformLocation, -1);
                    glUniform1i(uniformLocation, textureUnit);
                }
            }
        }
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that 3D arrays with sub-arrays passed as parameters works as expected.
TEST_P(GLSLTest_ES31, ParameterArrayArrayArraySampler)
{
    GLint numTextures;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTextures);
    ANGLE_SKIP_TEST_IF(numTextures < 2 * 3 * 4 + 4);

    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    // http://anglebug.com/42264082
    ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsOpenGL());

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 my_FragColor;
uniform mediump isampler2D test[2][3][4];
uniform mediump isampler2D test2[4];
const vec2 ZERO = vec2(0.0, 0.0);

bool check1D(mediump isampler2D arr[4], int x, int y) {
    if (texture(arr[0], ZERO) != ivec4(x, y, 0, 0)+1) return false;
    if (texture(arr[1], ZERO) != ivec4(x, y, 1, 0)+1) return false;
    if (texture(arr[2], ZERO) != ivec4(x, y, 2, 0)+1) return false;
    if (texture(arr[3], ZERO) != ivec4(x, y, 3, 0)+1) return false;
    return true;
}
bool check2D(mediump isampler2D arr[3][4], int x) {
    if (!check1D(arr[0], x, 0)) return false;
    if (!check1D(arr[1], x, 1)) return false;
    if (!check1D(arr[2], x, 2)) return false;
    return true;
}
bool check3D(mediump isampler2D arr[2][3][4]) {
    if (!check2D(arr[0], 0)) return false;
    if (!check2D(arr[1], 1)) return false;
    return true;
}
void main() {
    bool passed = check3D(test) && check1D(test2, 7, 8);
    my_FragColor = passed ? vec4(0.0, 1.0, 0.0, 1.0) : vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture textures1[2][3][4];
    GLTexture textures2[4];
    for (int i = 0; i < 2; i++)
    {
        for (int j = 0; j < 3; j++)
        {
            for (int k = 0; k < 4; k++)
            {
                // First generate the texture
                int textureUnit = k + 4 * (j + 3 * i);
                glActiveTexture(GL_TEXTURE0 + textureUnit);
                glBindTexture(GL_TEXTURE_2D, textures1[i][j][k]);
                GLint texData[3] = {i + 1, j + 1, k + 1};
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32I, 1, 1, 0, GL_RGB_INTEGER, GL_INT,
                             &texData[0]);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
                // Then send it as a uniform
                std::stringstream uniformName;
                uniformName << "test[" << i << "][" << j << "][" << k << "]";
                GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
                // All array indices should be used.
                EXPECT_NE(uniformLocation, -1);
                glUniform1i(uniformLocation, textureUnit);
            }
        }
    }
    for (int k = 0; k < 4; k++)
    {
        // First generate the texture
        int textureUnit = 2 * 3 * 4 + k;
        glActiveTexture(GL_TEXTURE0 + textureUnit);
        glBindTexture(GL_TEXTURE_2D, textures2[k]);
        GLint texData[3] = {7 + 1, 8 + 1, k + 1};
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB32I, 1, 1, 0, GL_RGB_INTEGER, GL_INT, &texData[0]);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
        // Then send it as a uniform
        std::stringstream uniformName;
        uniformName << "test2[" << k << "]";
        GLint uniformLocation = glGetUniformLocation(program, uniformName.str().c_str());
        // All array indices should be used.
        EXPECT_NE(uniformLocation, -1);
        glUniform1i(uniformLocation, textureUnit);
    }
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that names do not collide when translating arrays of arrays of samplers.
TEST_P(GLSLTest_ES31, ArraysOfArraysNameCollisionSampler)
{
    ANGLE_SKIP_TEST_IF(IsVulkan());  // anglebug.com/42262269 - rewriter can create name collisions
    GLint numTextures;
    glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &numTextures);
    ANGLE_SKIP_TEST_IF(numTextures < 2 * 2 + 3 * 3 + 4 * 4);
    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());
    constexpr char kFS[] = R"(#version 310 es
precision mediump sampler2D;
precision mediump float;
uniform sampler2D test_field1_field2[2][2];
struct S1 { sampler2D field2[3][3]; }; uniform S1 test_field1;
struct S2 { sampler2D field1_field2[4][4]; }; uniform S2 test;
vec4 func1(sampler2D param_field1_field2[2][2],
           int param_field1_field2_offset,
           S1 param_field1,
           S2 param) {
    return vec4(0.0, 1.0, 0.0, 0.0);
}
out vec4 my_FragColor;
void main() {
    my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
    my_FragColor += func1(test_field1_field2, 0, test_field1, test);
    vec2 uv = vec2(0.0);
    my_FragColor += texture(test_field1_field2[0][0], uv) +
                    texture(test_field1.field2[0][0], uv) +
                    texture(test.field1_field2[0][0], uv);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glActiveTexture(GL_TEXTURE0);
    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    GLint zero = 0;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &zero);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that regular arrays are unmodified.
TEST_P(GLSLTest_ES31, BasicTypeArrayAndArrayOfSampler)
{
    constexpr char kFS[] = R"(#version 310 es
precision mediump sampler2D;
precision mediump float;
uniform sampler2D sampler_array[2][2];
uniform int array[3][2];
vec4 func1(int param[2],
           int param2[3]) {
    return vec4(0.0, 1.0, 0.0, 0.0);
}
out vec4 my_FragColor;
void main() {
    my_FragColor = texture(sampler_array[0][0], vec2(0.0));
    my_FragColor += func1(array[1], int[](1, 2, 3));
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glActiveTexture(GL_TEXTURE0);
    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    GLint zero = 0;
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, 1, 1, 0, GL_RED, GL_UNSIGNED_BYTE, &zero);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// This test covers a bug (and associated workaround) with nested sampling operations in the HLSL
// compiler DLL.
TEST_P(GLSLTest_ES3, NestedSamplingOperation)
{
    // This seems to be bugged on some version of Android. Might not affect the newest versions.
    // TODO(jmadill): Lift suppression when Chromium bots are upgraded.
    // Test skipped on Android because of bug with Nexus 5X.
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kVS[] = R"(#version 300 es
out vec2 texCoord;
in vec2 position;
void main()
{
    gl_Position = vec4(position, 0, 1);
    texCoord = position * 0.5 + vec2(0.5);
})";

    constexpr char kSimpleFS[] = R"(#version 300 es
in mediump vec2 texCoord;
out mediump vec4 fragColor;
void main()
{
    fragColor = vec4(texCoord, 0, 1);
})";

    constexpr char kNestedFS[] = R"(#version 300 es
uniform mediump sampler2D samplerA;
uniform mediump sampler2D samplerB;
in mediump vec2 texCoord;
out mediump vec4 fragColor;
void main ()
{
    fragColor = texture(samplerB, texture(samplerA, texCoord).xy);
})";

    ANGLE_GL_PROGRAM(initProg, kVS, kSimpleFS);
    ANGLE_GL_PROGRAM(nestedProg, kVS, kNestedFS);

    // Initialize a first texture with default texCoord data.
    GLTexture texA;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, texA);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texA, 0);

    drawQuad(initProg, "position", 0.5f);
    ASSERT_GL_NO_ERROR();

    // Initialize a second texture with a simple color pattern.
    GLTexture texB;
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, texB);

    std::array<GLColor, 4> simpleColors = {
        {GLColor::red, GLColor::green, GLColor::blue, GLColor::yellow}};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 simpleColors.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

    // Draw with the nested program, using the first texture to index the second.
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glUseProgram(nestedProg);
    GLint samplerALoc = glGetUniformLocation(nestedProg, "samplerA");
    ASSERT_NE(-1, samplerALoc);
    glUniform1i(samplerALoc, 0);
    GLint samplerBLoc = glGetUniformLocation(nestedProg, "samplerB");
    ASSERT_NE(-1, samplerBLoc);
    glUniform1i(samplerBLoc, 1);

    drawQuad(nestedProg, "position", 0.5f);
    ASSERT_GL_NO_ERROR();

    // Compute four texel centers.
    Vector2 windowSize(getWindowWidth(), getWindowHeight());
    Vector2 quarterWindowSize = windowSize / 4;
    Vector2 ul                = quarterWindowSize;
    Vector2 ur(windowSize.x() - quarterWindowSize.x(), quarterWindowSize.y());
    Vector2 ll(quarterWindowSize.x(), windowSize.y() - quarterWindowSize.y());
    Vector2 lr = windowSize - quarterWindowSize;

    EXPECT_PIXEL_COLOR_EQ_VEC2(ul, simpleColors[0]);
    EXPECT_PIXEL_COLOR_EQ_VEC2(ur, simpleColors[1]);
    EXPECT_PIXEL_COLOR_EQ_VEC2(ll, simpleColors[2]);
    EXPECT_PIXEL_COLOR_EQ_VEC2(lr, simpleColors[3]);
}

// Test that side effect in for loop expressions are retained even if loop body is empty.
TEST_P(GLSLTest, EmptyForLoopWithSideEffect)
{
    constexpr char kFS1[] = R"(void main()
{
    int j = 0;
    for (int i = ++j; i < 1;) {}
    gl_FragColor = vec4(j, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS1);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    constexpr char kFS2[] = R"(void main()
{
    int i;
    for (i = 0; i++ < 5;) {}
    gl_FragColor = vec4(0, float(i) / 12.0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program2, essl1_shaders::vs::Simple(), kFS2);
    drawQuad(program2, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0, 127, 0, 255), 1);

    constexpr char kFS3[] = R"(void main()
{
    int i;
    for (i = 0; i < 5; i++) {}
    gl_FragColor = vec4(0, 0, float(i) / 10.0, 1);
})";

    ANGLE_GL_PROGRAM(program3, essl1_shaders::vs::Simple(), kFS3);
    drawQuad(program3, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0, 0, 127, 255), 1);
}

// Test that for loops with vec variable work
TEST_P(GLSLTest_ES3, ForLoopWithVecVariable)
{
    constexpr char kFS1[] = R"(#version 300 es
precision mediump float;
out vec4 color;
void main()
{
    color = vec4(0, 0, 0, 1);
    for (vec4 i = vec4(0); i != vec4(3); i += vec4(1))
    {
        color.x += 0.25;
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS1);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(191, 0, 0, 255), 1);
}

// Test that uninitialized local variables are initialized to 0.
TEST_P(WebGL2GLSLTest, InitUninitializedLocals)
{
    // Test skipped on Android GLES because local variable initialization is disabled.
    // http://anglebug.com/40096454
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 my_FragColor;
int result = 0;
void main()
{
    int u;
    result += u;
    int k = 0;
    for (int i[2], j = i[0] + 1; k < 2; ++k)
    {
        result += j;
    }
    if (result == 2)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

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

    // [WebGL 1.0]
    // DrawArrays or drawElements will generate an INVALID_OPERATION error
    // if a vertex attribute is enabled as an array via enableVertexAttribArray
    // but no buffer is bound to that attribute.
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that uninitialized structs containing arrays of structs are initialized to 0. This
// specifically tests with two different struct variables declared in the same block.
TEST_P(WebGL2GLSLTest, InitUninitializedStructContainingArrays)
{
    // Test skipped on Android GLES because local variable initialization is disabled.
    // http://anglebug.com/40096454
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kFS[] = R"(precision mediump float;
struct T
{
    int a[2];
};
struct S
{
    T t[2];
};
void main()
{
    S s;
    S s2;
    if (s.t[1].a[1] == 0 && s2.t[1].a[1] == 0)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        gl_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Verify that two shaders with the same uniform name and members but different structure names will
// not link.
TEST_P(GLSLTest, StructureNameMatchingTest)
{
    constexpr char kVS[] = R"(
// Structures must have the same name, sequence of type names, and
// type definitions, and field names to be considered the same type.
// GLSL 1.017 4.2.4
precision mediump float;
struct info {
  vec4 pos;
  vec4 color;
};

uniform info uni;
void main()
{
    gl_Position = uni.pos;
})";

    GLuint vs = CompileShader(GL_VERTEX_SHADER, kVS);
    ASSERT_NE(0u, vs);
    glDeleteShader(vs);

    constexpr char kFS[] = R"(
// Structures must have the same name, sequence of type names, and
// type definitions, and field names to be considered the same type.
// GLSL 1.017 4.2.4
precision mediump float;
struct info1 {
  vec4 pos;
  vec4 color;
};

uniform info1 uni;
void main()
{
    gl_FragColor = uni.color;
})";

    GLuint fs = CompileShader(GL_FRAGMENT_SHADER, kFS);
    ASSERT_NE(0u, fs);
    glDeleteShader(fs);

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

// Test that an uninitialized nameless struct inside a for loop init statement works.
TEST_P(WebGL2GLSLTest, UninitializedNamelessStructInForInitStatement)
{
    // Test skipped on Android GLES because local variable initialization is disabled.
    // http://anglebug.com/40096454
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    for (struct { float q; } b; b.q < 2.0; b.q++) {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that uninitialized global variables are initialized to 0.
TEST_P(WebGLGLSLTest, InitUninitializedGlobals)
{
    // http://anglebug.com/42261561
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsAdreno() && IsOpenGLES());

    constexpr char kFS[] = R"(precision mediump float;
int result;
int i[2], j = i[0] + 1;
void main()
{
    result += j;
    if (result == 1)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        gl_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that an uninitialized nameless struct in the global scope works.
TEST_P(WebGLGLSLTest, UninitializedNamelessStructInGlobalScope)
{
    constexpr char kFS[] = R"(precision mediump float;
struct { float q; } b;
void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    if (b.q == 0.0)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that uninitialized output arguments are initialized to 0.
TEST_P(WebGL2GLSLTest, InitOutputParams)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;

struct S { float a; };

out vec4 color;

float f(out float, out vec2 o1, out S o2[2], out float o3[3])
{
    float uninitialized_local;

    // leave o1 uninitialized
    // leave o2 partially uninitialized
    o2[0].a = 1.0;

    // leave o3 partially uninitialized
    o3[1] = 0.5;

    return uninitialized_local;
}

void main()
{
    float v0 = 345.;
    vec2 v1 = vec2(123., 234.);
    S v2[2] = S[2](S(-1111.), S(55.));
    float v3[3] = float[3](20., 30., 40.);
    float v4 = f(v0, v1, v2, v3);

    // Everything should be 0 now except for v2[0].a and v3[1] which should be 1.0 and 0.5
    // respectively.
    color = vec4(v0 + v1.x + v2[0].a + v3[0],  // 1.0
                 v1.y + v2[1].a + v3[1],  // 0.5
                 v3[2] + v4,              // 0
                 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 255, 127, 0, 255, 1);
}

// Verify that functions without return statements return zero-initialized vec4
TEST_P(WebGL2GLSLTest, MissingReturnZeroInitVec4)
{
    constexpr char kFS[] = R"(precision highp float;
uniform float u0;
uniform float u1;
vec4 foo(float u)
{
    if (u > 0.0) { return vec4(1.0); }
}
void main()
{
    if (foo(u0) == vec4(0.0) && foo(u1) == vec4(1.0))
        gl_FragColor = vec4(0, 1, 0, 1);
    else
        gl_FragColor = vec4(1, 0, 0, 1);
})";

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

    GLint loc = glGetUniformLocation(program, "u1");
    ASSERT_NE(-1, loc);
    glUniform1f(loc, 1.0f);

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

// Verify that functions without return statements return zero-initialized struct
TEST_P(WebGL2GLSLTest, MissingReturnZeroInitStruct)
{
    constexpr char kFS[] = R"(precision highp float;
struct S { float a; int b; };
uniform float u0;
uniform float u1;
S foo(float u)
{
    if (u > 0.0) { return S(1.0, 1); }
}
void main()
{
    S r0 = foo(u0);
    S r1 = foo(u1);
    if (r0.a == 0.0 && r0.b == 0 && r1.a == 1.0 && r1.b == 1)
        gl_FragColor = vec4(0, 1, 0, 1);
    else
        gl_FragColor = vec4(1, 0, 0, 1);
})";

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

    GLint loc = glGetUniformLocation(program, "u1");
    ASSERT_NE(-1, loc);
    glUniform1f(loc, 1.0f);

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

// Tests nameless struct uniforms.
TEST_P(GLSLTest, EmbeddedStructUniform)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform struct { float q; } b;
void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    if (b.q == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint uniLoc = glGetUniformLocation(program, "b.q");
    ASSERT_NE(-1, uniLoc);
    glUniform1f(uniLoc, 0.5f);

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

// Tests nameless struct uniform arrays.
TEST_P(GLSLTest, EmbeddedStructUniformArray)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform struct { float q; } b[2];
void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    if (b[0].q == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint uniLoc = glGetUniformLocation(program, "b[0].q");
    ASSERT_NE(-1, uniLoc);
    glUniform1f(uniLoc, 0.5f);

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

// Test that samplers in structs can be used on the right-hand side of a comma, where the expression
// has side effect.
TEST_P(GLSLTest_ES3, SamplerInStructRHSOfCommaWithSideEffect)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform struct {
    sampler2D n;
    vec2 c;
} s[4];
ivec4 global = ivec4(0);
out vec4 color;
void main()
{
    int i = 0;
    (s, s[i += 1].n), (global.x = 10);
    s[i += 2], s[i].c, (s[i - 1].n, (global.y = 20));
    s[global.w = 80, i += 4].c, (global.z = 40);

    int c11 = ((s, s[i += 8].n), (global.x += 1));
    int c22 = (s[i += 16], s[i].c, (s[i - 1].n, (global.y += 2)));
    int c43 = (s[global.w += 4, i += 32].c, (global.z += 3));
    vec4 allOnes = vec4(((i += 64, s), 1.0));

    color = vec4(i == 127,
                 global.x == 11 && global.y == 22,
                 global.z == 43 && global.w == 84,
                 c11 == 11 && c22 == 22 && c43 == 43) * allOnes;
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that samplers in structs can be used on the right-hand side of a comma, where the expression
// has side effect, and that the struct field can be selected on the comma expression.
TEST_P(GLSLTest_ES3, SamplerInStructRHSOfCommaWithSideEffectWithSelectField)
{
    // Only correctly handled by the IR.
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr));

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform struct {
    sampler2D n;
    vec2 c;
} s[4];
out vec4 color;
void main()
{
    int i = 0;
    vec4 zero = vec4(texture((s[i += 1], s[0]).n, vec2(0)).xyz, 0);
    vec4 zero2 = vec4(texture(((s[i += 2], i += 4), s[0]).n, vec2(0)).xyz, 0);

    color = vec4(i == 7, 0, 0, 1) - zero - zero2;
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that samplers in structs can be extracted if the first reference to the struct does not
// select an attribute.
TEST_P(GLSLTest, SamplerInStructNoMemberIndexing)
{
    constexpr char kVS[] = R"(
uniform struct {
    sampler2D n;
    vec2 c;
} s;
void main()
{
    s;
})";

    constexpr char kFS[] = R"(void main()
{
    gl_FragColor = vec4(1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Similar test to SamplerInStructNoMemberIndexing, but the struct variable is an array.
TEST_P(GLSLTest, SamplerInStructArrayNoMemberIndexing)
{
    constexpr char kVS[] = R"(
uniform struct
{
    sampler2D K;
    vec4 c;
} s[6];
void main()
{
    s[0];
})";

    constexpr char kFS[] = R"(void main()
{
    gl_FragColor = vec4(1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Tests that rewriting samplers in structs doesn't mess up indexing.
TEST_P(GLSLTest, SamplerInStructMemberIndexing)
{
    const char kVertexShader[] = R"(attribute vec2 position;
varying vec2 texCoord;
void main()
{
    gl_Position = vec4(position, 0, 1);
    texCoord = position * 0.5 + vec2(0.5);
})";

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samp; bool b; };
uniform S uni;
varying vec2 texCoord;
void main()
{
    uni;
    if (uni.b)
    {
        gl_FragColor = texture2D(uni.samp, texCoord);
    }
    else
    {
        gl_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVertexShader, kFragmentShader);
    glUseProgram(program);

    GLint bLoc = glGetUniformLocation(program, "uni.b");
    ASSERT_NE(-1, bLoc);
    GLint sampLoc = glGetUniformLocation(program, "uni.samp");
    ASSERT_NE(-1, sampLoc);

    glUniform1i(bLoc, 1);

    std::array<GLColor, 4> kGreenPixels = {
        {GLColor::green, GLColor::green, GLColor::green, GLColor::green}};

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 kGreenPixels.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    drawQuad(program, "position", 0.5f);
    ASSERT_GL_NO_ERROR();

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

// Test that nested structs with samplers work when the nested struct is not the last element.
TEST_P(GLSLTest_ES3, NestedStructWithSamplers)
{
    const char kFS[] = R"(#version 300 es
precision mediump float;

struct Inner
{
    sampler2D s;
    float a;
};

uniform struct Outer
{
    Inner i;
    float b;
} u;

out vec4 color;

void main()
{
    color = texture(u.i.s, vec2(0)) + vec4(u.i.a, u.b, 0, 1);
})";

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

    GLint uniLocA = glGetUniformLocation(program, "u.i.a");
    ASSERT_NE(-1, uniLocA);
    glUniform1f(uniLocA, 0.5f);

    GLint uniLocB = glGetUniformLocation(program, "u.b");
    ASSERT_NE(-1, uniLocB);
    glUniform1f(uniLocB, 0.75f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 191, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that nested structs with samplers work when the nested struct is not the last element and
// includes nothing but samplers.
TEST_P(GLSLTest_ES3, NestedStructWithOnlySamplers)
{
    const char kFS[] = R"(#version 300 es
precision mediump float;

struct Inner
{
    sampler2D s;
};

uniform struct Outer
{
    Inner i;
    float a;
} u;

out vec4 color;

void main()
{
    color = texture(u.i.s, vec2(0)) + vec4(u.a, 0, 0, 1);
})";

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

    GLint uniLocA = glGetUniformLocation(program, "u.a");
    ASSERT_NE(-1, uniLocA);
    glUniform1f(uniLocA, 0.5f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 0, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Tests two nameless struct uniforms.
TEST_P(GLSLTest, TwoEmbeddedStructUniforms)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform struct { float q; } b, c;
void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    if (b.q == 0.5 && c.q == 1.0)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

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

    GLint uniLocB = glGetUniformLocation(program, "b.q");
    ASSERT_NE(-1, uniLocB);
    glUniform1f(uniLocB, 0.5f);

    GLint uniLocC = glGetUniformLocation(program, "c.q");
    ASSERT_NE(-1, uniLocC);
    glUniform1f(uniLocC, 1.0f);

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

// Test that a loop condition that has an initializer declares a variable.
TEST_P(GLSLTest_ES3, ConditionInitializerDeclaresVariable)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    float i = 0.0;
    while (bool foo = (i < 1.5))
    {
        if (!foo)
        {
            ++i;
        }
        if (i > 3.5)
        {
            break;
        }
        ++i;
    }
    my_FragColor = vec4(i * 0.5 - 1.0, i * 0.5, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a variable hides a user-defined function with the same name after its initializer.
// GLSL ES 1.00.17 section 4.2.2: "A variable declaration is visible immediately following the
// initializer if present, otherwise immediately following the identifier"
TEST_P(GLSLTest, VariableHidesUserDefinedFunctionAfterInitializer)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "uniform vec4 u;\n"
        "vec4 foo()\n"
        "{\n"
        "    return u;\n"
        "}\n"
        "void main()\n"
        "{\n"
        "    vec4 foo = foo();\n"
        "    gl_FragColor = foo + vec4(0, 1, 0, 1);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that structs with identical members are not ambiguous as function arguments.
TEST_P(GLSLTest, StructsWithSameMembersDisambiguatedByName)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "uniform float u_zero;\n"
        "struct S { float foo; };\n"
        "struct S2 { float foo; };\n"
        "float get(S s) { return s.foo + u_zero; }\n"
        "float get(S2 s2) { return 0.25 + s2.foo + u_zero; }\n"
        "void main()\n"
        "{\n"
        "    S s;\n"
        "    s.foo = 0.5;\n"
        "    S2 s2;\n"
        "    s2.foo = 0.25;\n"
        "    gl_FragColor = vec4(0.0, get(s) + get(s2), 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that an inactive varying in vertex shader but used in fragment shader can be linked
// successfully.
TEST_P(GLSLTest, InactiveVaryingInVertexActiveInFragment)
{
    constexpr char kVS[] =
        "attribute vec4 inputAttribute;\n"
        "varying vec4 varColor;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = inputAttribute;\n"
        "}\n";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying vec4 varColor;\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = varColor;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    ASSERT_GL_NO_ERROR();
}

// Test that standard derivatives work as expected with FBOs since the render target
// might have flipped viewport orientation.
TEST_P(GLSLTest, ScreenFlipCauseStandardDerivativesWrong)
{
    constexpr char kFS[] = R"(#extension GL_OES_standard_derivatives : enable
precision mediump float;

void main()
{
    gl_FragColor = vec4(
        dFdx(gl_FragCoord.x),
        dFdy(gl_FragCoord.y),
        0.0, 1.0
    );
})";

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    ASSERT_GL_NO_ERROR();

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::yellow);
}

// Test that derivative works in a loop that ends in continue or break.
TEST_P(GLSLTest_ES3, DerivativeInLoopWithUniformBranch)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    color = vec4(0, 0, 0, 1);
    for (int i = 0; i < 1; ++i)
    {
        color.x += dFdx(gl_FragCoord.x);
        continue;
    }
    for (int i = 0; i < 10; ++i)
    {
        color.y += dFdy(gl_FragCoord.y);
        break;
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::yellow);
}

// Test that a varying struct that's not statically used in the fragment shader works.
// GLSL ES 3.00.6 section 4.3.10.
TEST_P(GLSLTest_ES3, VaryingStructNotStaticallyUsedInFragmentShader)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "out S varStruct;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(1.0);\n"
        "    varStruct.field = vec4(0.0, 0.5, 0.0, 0.0);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "in S varStruct;\n"
        "out vec4 col;\n"
        "void main()\n"
        "{\n"
        "    col = vec4(1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that inactive shader IO block varying are ok.
TEST_P(GLSLTest_ES31, InactiveVaryingIOBlock)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] =
        R"(#version 310 es
        #extension GL_EXT_shader_io_blocks : require

        precision highp float;
        in vec4 inputAttribute;
        out Block { vec4 v; };
        out Inactive1 { vec4 value; };
        out Inactive2 { vec4 value; } named;

        void main()
        {
            gl_Position    = inputAttribute;
            v = vec4(0);
        })";

    constexpr char kFS[] =
        R"(#version 310 es
        #extension GL_EXT_shader_io_blocks : require

        precision highp float;

        in Block { vec4 v; };
        in Inactive3 { vec4 value; };
        in Inactive4 { vec4 value; } named2;

        layout(location = 0) out mediump vec4 color;
        void main()
        {
            color = vec4(1, v.xy, 1);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    ASSERT_GL_NO_ERROR();

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

// Test that a shader IO block varying that's not declared in the fragment shader links
// successfully.
TEST_P(GLSLTest_ES31, VaryingIOBlockNotDeclaredInFragmentShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] =
        R"(#version 310 es
        #extension GL_EXT_shader_io_blocks : require

        precision highp float;
        in vec4 inputAttribute;
        out Block_inout { vec4 value; } user_out;

        void main()
        {
            gl_Position    = inputAttribute;
            user_out.value = vec4(4.0, 5.0, 6.0, 7.0);
        })";

    constexpr char kFS[] =
        R"(#version 310 es
        #extension GL_EXT_shader_io_blocks : require

        precision highp float;
        layout(location = 0) out mediump vec4 color;
        void main()
        {
            color = vec4(1, 0, 0, 1);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    ASSERT_GL_NO_ERROR();

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

// Test that a shader IO block varying that's not declared in the vertex shader links
// successfully.
TEST_P(GLSLTest_ES31, VaryingIOBlockNotDeclaredInVertexShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] =
        R"(#version 310 es
        #extension GL_EXT_shader_io_blocks : require

        precision highp float;
        in vec4 inputAttribute;

        void main()
        {
            gl_Position = inputAttribute;
        })";

    constexpr char kFS[] =
        R"(#version 310 es
        #extension GL_EXT_shader_io_blocks : require

        precision highp float;
        in Block_inout { vec4 value; } user_in;
        layout(location = 0) out mediump vec4 color;

        void main()
        {
            color = vec4(1, 0, 0, 1);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    ASSERT_GL_NO_ERROR();

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

// Test that a shader with sample in / sample out can be linked successfully.
TEST_P(GLSLTest_ES31, VaryingTessellationSampleInAndOut)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_shader_multisample_interpolation"));

    constexpr char kVS[] =
        R"(#version 310 es
        #extension GL_OES_shader_multisample_interpolation : require

        precision highp float;
        in vec4 inputAttribute;

        sample out mediump float tc_in;
        void main()
        {
            tc_in = inputAttribute[0];
            gl_Position = inputAttribute;
        })";

    constexpr char kTCS[] =
        R"(#version 310 es
        #extension GL_EXT_tessellation_shader : require
        #extension GL_OES_shader_multisample_interpolation : require
        layout (vertices=3) out;

        sample in mediump float tc_in[];
        sample out mediump float tc_out[];
        void main()
        {
            tc_out[gl_InvocationID] = tc_in[gl_InvocationID];
            gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
            gl_TessLevelInner[0] = 2.0;
            gl_TessLevelInner[1] = 2.0;
            gl_TessLevelOuter[0] = 2.0;
            gl_TessLevelOuter[1] = 2.0;
            gl_TessLevelOuter[2] = 2.0;
            gl_TessLevelOuter[3] = 2.0;
        })";

    constexpr char kTES[] =
        R"(#version 310 es
        #extension GL_EXT_tessellation_shader : require
        #extension GL_OES_shader_multisample_interpolation : require
        layout (triangles) in;

        sample in mediump float tc_out[];
        sample out mediump float te_out;
        void main()
        {
            te_out = tc_out[2];
            gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position;
        })";

    constexpr char kFS[] =
        R"(#version 310 es
        #extension GL_OES_shader_multisample_interpolation : require

        precision highp float;
        sample in mediump float te_out;
        layout(location = 0) out mediump vec4 color;

        void main()
        {
            float out0 = te_out;
            color = vec4(1, 0, 0, 1);
        })";

    ANGLE_GL_PROGRAM_WITH_TESS(program, kVS, kTCS, kTES, kFS);
    drawPatches(program, "inputAttribute", 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();
}

// Test that GetProgramiv with tessellation parameters generates an INVALID_OPERATION error when
// there is no tessellation shader.
TEST_P(GLSLTest_ES31, ValidateGetProgramivNoTessellationShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kVS[] =
        R"(#version 310 es

        precision highp float;
        in vec4 inputAttribute;

        void main()
        {
            gl_Position = inputAttribute;
        })";

    constexpr char kFS[] =
        R"(#version 310 es

        precision highp float;
        layout(location = 0) out mediump vec4 color;

        void main()
        {
            color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    GLint params = 0;

    glGetProgramiv(program, GL_TESS_CONTROL_OUTPUT_VERTICES_EXT, &params);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glGetProgramiv(program, GL_TESS_GEN_MODE, &params);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glGetProgramiv(program, GL_TESS_GEN_SPACING, &params);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Test that GetProgramiv with tessellation parameters generates an INVALID_OPERATION error when
// there is no tessellation control shader.
TEST_P(GLSLTest_ES31, ValidateGetProgramivNoTessellationControlShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kVS[] =
        R"(#version 310 es

        precision highp float;
        in vec4 inputAttribute;

        void main()
        {
            gl_Position = inputAttribute;
        })";

    static const char kTES[] =
        "#version 310 es\n"
        "#extension GL_EXT_tessellation_shader : require\n"
        "layout(triangles, fractional_even_spacing, cw, point_mode) in;\n"
        "void main(void)\n"
        "{}";

    constexpr char kFS[] =
        R"(#version 310 es

        precision highp float;
        layout(location = 0) out mediump vec4 color;

        void main()
        {
            color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    GLuint vs      = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint tes     = CompileShader(GL_TESS_EVALUATION_SHADER, kTES);
    GLuint fs      = CompileShader(GL_FRAGMENT_SHADER, kFS);
    GLuint program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, tes);
    glAttachShader(program, fs);
    glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
    glLinkProgram(program);
    ASSERT_GL_NO_ERROR();

    GLint params = 0;

    glGetProgramiv(program, GL_TESS_CONTROL_OUTPUT_VERTICES_EXT, &params);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glGetProgramiv(program, GL_TESS_GEN_MODE, &params);
    EXPECT_EQ(static_cast<GLint>(GL_TRIANGLES), params);
    glGetProgramiv(program, GL_TESS_GEN_SPACING, &params);
    EXPECT_EQ(static_cast<GLint>(GL_FRACTIONAL_EVEN), params);

    glDetachShader(program, vs);
    glDetachShader(program, tes);
    glDetachShader(program, fs);
    glDeleteShader(vs);
    glDeleteShader(tes);
    glDeleteShader(fs);
    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();
}

// Test that GetProgramiv with tessellation parameters generates an INVALID_OPERATION error when
// there is no tessellation evaluation shader.
TEST_P(GLSLTest_ES31, ValidateGetProgramivNoTessellationEvaluationShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kVS[] =
        R"(#version 310 es

        precision highp float;
        in vec4 inputAttribute;

        void main()
        {
            gl_Position = inputAttribute;
        })";

    static const char kTCS[] =
        "#version 310 es\n"
        "#extension GL_EXT_tessellation_shader : require\n"
        "layout(vertices = 3) out;\n"
        "void main(void)\n"
        "{}";

    constexpr char kFS[] =
        R"(#version 310 es

        precision highp float;
        layout(location = 0) out mediump vec4 color;

        void main()
        {
            color = vec4(1.0, 0.0, 0.0, 1.0);
        })";

    GLuint vs      = CompileShader(GL_VERTEX_SHADER, kVS);
    GLuint tcs     = CompileShader(GL_TESS_CONTROL_SHADER, kTCS);
    GLuint fs      = CompileShader(GL_FRAGMENT_SHADER, kFS);
    GLuint program = glCreateProgram();
    glAttachShader(program, vs);
    glAttachShader(program, tcs);
    glAttachShader(program, fs);
    glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
    glLinkProgram(program);
    ASSERT_GL_NO_ERROR();

    GLint params = 0;
    glGetProgramiv(program, GL_TESS_CONTROL_OUTPUT_VERTICES_EXT, &params);
    EXPECT_EQ(3, params);

    glGetProgramiv(program, GL_TESS_GEN_MODE, &params);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glGetProgramiv(program, GL_TESS_GEN_SPACING, &params);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);

    glDetachShader(program, vs);
    glDetachShader(program, tcs);
    glDetachShader(program, fs);
    glDeleteShader(vs);
    glDeleteShader(tcs);
    glDeleteShader(fs);
    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();
}

// Test that `smooth sample` and `flat sample` pass the validation.
TEST_P(GLSLTest_ES3, AliasedSampleQualifiers)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_shader_multisample_interpolation"));

    constexpr char kVS[] =
        R"(#version 300 es
        #extension GL_OES_shader_multisample_interpolation : require

        smooth sample out mediump float f;
        flat sample out mediump int i;
        void main()
        {
            f = 1.0;
            i = 1;
            gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
        })";

    constexpr char kFS[] =
        R"(#version 300 es
        #extension GL_OES_shader_multisample_interpolation : require

        smooth sample in mediump float f;
        flat sample in mediump int i;
        out mediump vec4 color;
        void main()
        {
            color = vec4(f, float(i), 0, 1);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that `noperspective centroid` passes the validation and compiles.
TEST_P(GLSLTest_ES3, NoPerspectiveCentroid)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_shader_noperspective_interpolation"));

    constexpr char kVS[] =
        R"(#version 300 es
        #extension GL_NV_shader_noperspective_interpolation : require

        noperspective centroid out mediump float f;
        void main()
        {
            f = 1.0;
            gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
        })";

    constexpr char kFS[] =
        R"(#version 300 es
        #extension GL_NV_shader_noperspective_interpolation : require

        noperspective centroid in mediump float f;
        out mediump vec4 color;
        void main()
        {
            color = vec4(f, 0.0, 0.0, 1.0);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that `noperspective sample` passes the validation and compiles.
TEST_P(GLSLTest_ES3, NoPerspectiveSample)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_shader_multisample_interpolation"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_NV_shader_noperspective_interpolation"));

    constexpr char kVS[] =
        R"(#version 300 es
        #extension GL_OES_shader_multisample_interpolation : require
        #extension GL_NV_shader_noperspective_interpolation : require

        noperspective sample out mediump float f;
        void main()
        {
            f = 1.0;
            gl_Position = vec4(f, 0.0, 0.0, 1.0);
        })";

    constexpr char kFS[] =
        R"(#version 300 es
        #extension GL_OES_shader_multisample_interpolation : require
        #extension GL_NV_shader_noperspective_interpolation : require

        noperspective sample in mediump float f;
        out mediump vec4 color;
        void main()
        {
            color = vec4(f, 0.0, 0.0, 1.0);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that a shader with sample in / sample out can be used successfully when the varying
// precision is different between VS and FS.
TEST_P(GLSLTest_ES31, VaryingSampleInAndOutDifferentPrecision)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_shader_multisample_interpolation"));

    constexpr char kVS[] =
        R"(#version 310 es
        #extension GL_OES_shader_multisample_interpolation : require

        precision highp float;
        in vec4 inputAttribute;

        sample out highp float v;
        void main()
        {
            v = inputAttribute[0];
            gl_Position = inputAttribute;
        })";

    constexpr char kFS[] =
        R"(#version 310 es
        #extension GL_OES_shader_multisample_interpolation : require

        precision highp float;
        sample in mediump float v;
        layout(location = 0) out mediump vec4 color;

        void main()
        {
            color = vec4(round((v + 1.) / 2. * 5.) / 5., 0, 0, 1);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::red);
}

// Test that a shader IO block varying whose block name is declared multiple(in/out) time links
// successfully.
TEST_P(GLSLTest_ES31, VaryingIOBlockDeclaredAsInAndOut)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
    #extension GL_EXT_shader_io_blocks : require
    precision highp float;
    in vec4 inputAttribute;
    out Vertex
    {
        vec4 fv;
    } outVertex;
    void main()
    {
        gl_Position = inputAttribute;
        outVertex.fv = gl_Position;
    })";

    constexpr char kTCS[] = R"(#version 310 es
    #extension GL_EXT_tessellation_shader : require
    #extension GL_EXT_shader_io_blocks : require
    precision mediump float;
    in Vertex
    {
        vec4 fv;
    } inVertex[];
    layout(vertices = 2) out;
    out Vertex
    {
        vec4 fv;
    } outVertex[];

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

    constexpr char kTES[] = R"(#version 310 es
    #extension GL_EXT_tessellation_shader : require
    #extension GL_EXT_shader_io_blocks : require
    precision mediump float;
    layout (isolines, point_mode) in;
    in Vertex
    {
        vec4 fv;
    } inVertex[];
    out vec4 result_fv;

    void main()
    {
        gl_Position = gl_in[0].gl_Position;
        result_fv = inVertex[0].fv;
    })";

    constexpr char kFS[] = R"(#version 310 es
    precision mediump float;

    layout(location = 0) out mediump vec4 color;

    void main()
    {
        // Output solid green
        color = vec4(0, 1.0, 0, 1.0);
    })";

    ANGLE_GL_PROGRAM_WITH_TESS(program, kVS, kTCS, kTES, kFS);
    drawPatches(program, "inputAttribute", 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();
}

void GLSLTest_ES31::testTessellationTextureBufferAccess(const APIExtensionVersion usedExtension)
{
    ASSERT(usedExtension == APIExtensionVersion::EXT || usedExtension == APIExtensionVersion::OES);

    // Vertex shader
    constexpr char kVS[] = R"(#version 310 es
precision highp float;
in vec4 inputAttribute;

void main()
{
gl_Position = inputAttribute;
})";

    // Tessellation shaders
    constexpr char kGLSLVersion[] = R"(#version 310 es
)";
    constexpr char kTessEXT[]     = R"(#extension GL_EXT_tessellation_shader : require
)";
    constexpr char kTessOES[]     = R"(#extension GL_OES_tessellation_shader : require
)";
    constexpr char kTexBufEXT[]   = R"(#extension GL_EXT_texture_buffer : require
)";
    constexpr char kTexBufOES[]   = R"(#extension GL_OES_texture_buffer : require
)";

    std::string tcs;
    std::string tes;

    tcs.append(kGLSLVersion);
    tes.append(kGLSLVersion);

    if (usedExtension == APIExtensionVersion::EXT)
    {
        tcs.append(kTessEXT);
        tes.append(kTessEXT);
        tes.append(kTexBufEXT);
    }
    else
    {
        tcs.append(kTessOES);
        tes.append(kTessOES);
        tes.append(kTexBufOES);
    }

    constexpr char kTCSBody[] = R"(precision mediump float;
layout(vertices = 2) out;

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

    constexpr char kTESBody[] = R"(precision mediump float;
layout (isolines, point_mode) in;

uniform highp samplerBuffer tex;

out vec4 tex_color;

void main()
{
tex_color = texelFetch(tex, 0);
gl_Position = gl_in[0].gl_Position;
})";
    tes.append(kTESBody);

    // Fragment shader
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
layout(location = 0) out mediump vec4 color;

in vec4 tex_color;

void main()
{
color = tex_color;
})";

    constexpr GLint kBufferSize = 4;
    GLubyte texData[]           = {0u, 255u, 0u, 255u};

    GLTexture texture;
    glBindTexture(GL_TEXTURE_BUFFER, texture);

    GLBuffer buffer;
    glBindBuffer(GL_TEXTURE_BUFFER, buffer);
    glBufferData(GL_TEXTURE_BUFFER, kBufferSize, texData, GL_STATIC_DRAW);
    glTexBufferEXT(GL_TEXTURE_BUFFER, GL_RGBA8, buffer);
    ASSERT_GL_NO_ERROR();

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

    ANGLE_GL_PROGRAM_WITH_TESS(program, kVS, tcs.c_str(), tes.c_str(), kFS);
    drawPatches(program, "inputAttribute", 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();
}

// Test that texture buffers can be accessed in a tessellation stage (using EXT)
TEST_P(GLSLTest_ES31, TessellationTextureBufferAccessEXT)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_buffer"));
    testTessellationTextureBufferAccess(APIExtensionVersion::EXT);
}

// Test that texture buffers can be accessed in a tessellation stage (using OES)
TEST_P(GLSLTest_ES31, TessellationTextureBufferAccessOES)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_texture_buffer"));
    testTessellationTextureBufferAccess(APIExtensionVersion::OES);
}

// Test that a varying struct that's not declared in the fragment shader links successfully.
// GLSL ES 3.00.6 section 4.3.10.
TEST_P(GLSLTest_ES3, VaryingStructNotDeclaredInFragmentShader)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "out S varStruct;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(1.0);\n"
        "    varStruct.field = vec4(0.0, 0.5, 0.0, 0.0);\n"
        "}\n";

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

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that a varying struct that's not declared in the vertex shader, and is unused in the
// fragment shader links successfully.
TEST_P(GLSLTest_ES3, VaryingStructNotDeclaredInVertexShader)
{
    // GLSL ES allows the vertex shader to not declare a varying if the fragment shader is not
    // going to use it.  See section 9.1 in
    // https://www.khronos.org/registry/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.pdf or
    // section 4.3.5 in https://www.khronos.org/files/opengles_shading_language.pdf
    //
    // However, nvidia OpenGL ES drivers fail to link this program.
    //
    // http://anglebug.com/42262078
    ANGLE_SKIP_TEST_IF(IsOpenGLES() && IsNVIDIA());

    constexpr char kVS[] =
        "#version 300 es\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 col;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "in S varStruct;\n"
        "void main()\n"
        "{\n"
        "    col = vec4(1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that a varying struct that's not initialized in the vertex shader links successfully.
TEST_P(WebGL2GLSLTest, VaryingStructNotInitializedInVertexShader)
{
    // GLSL ES allows the vertex shader to declare but not initialize a varying (with a
    // specification that the varying values are undefined in the fragment stage).  See section 9.1
    // in https://www.khronos.org/registry/OpenGL/specs/es/3.2/GLSL_ES_Specification_3.20.pdf
    // or section 4.3.5 in https://www.khronos.org/files/opengles_shading_language.pdf
    //
    // However, windows drivers fail to link this program.  With a message like:
    //
    // > Input of fragment shader 'varStruct' not written by vertex shader
    //
    // http://anglebug.com/42262078
    ANGLE_SKIP_TEST_IF(IsDesktopOpenGL() && IsWindows() && !IsNVIDIA());

    constexpr char kVS[] =
        "#version 300 es\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "out S varStruct;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = vec4(1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 col;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "in S varStruct;\n"
        "void main()\n"
        "{\n"
        "    col = varStruct.field;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that a varying struct that gets used in the fragment shader works.
TEST_P(GLSLTest_ES3, VaryingStructUsedInFragmentShader)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 inputAttribute;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "out S varStruct;\n"
        "out S varStruct2;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = inputAttribute;\n"
        "    varStruct.field = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "    varStruct2.field = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 col;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "in S varStruct;\n"
        "in S varStruct2;\n"
        "void main()\n"
        "{\n"
        "    col = varStruct.field + varStruct2.field;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// This is a regression test to make sure a red quad is rendered without issues
// when a passthrough function with a vec3 input parameter is used in the fragment shader.
TEST_P(GLSLTest_ES31, SamplerPassthroughFailedLink)
{
    constexpr char kVS[] =
        "precision mediump float;\n"
        "attribute vec4 inputAttribute;\n"
        "varying mediump vec2 texCoord;\n"
        "void main() {\n"
        "    texCoord = inputAttribute.xy;\n"
        "    gl_Position = vec4(inputAttribute.x, inputAttribute.y, 0.0, 1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying mediump vec2 texCoord;\n"
        "uniform sampler2D testSampler;\n"
        "vec3 passthrough(vec3 c) {\n"
        "    return c;\n"
        "}\n"
        "void main() {\n"
        "    gl_FragColor = vec4(passthrough(texture2D(testSampler, texCoord).rgb), 1.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, kVS, kFS);

    // Initialize basic red texture.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::red.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    drawQuad(program, "inputAttribute", 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// This is a regression test to make sure a red quad is rendered without issues
// when a passthrough function with a vec4 input parameter is used in the fragment shader.
TEST_P(GLSLTest_ES31, SamplerPassthroughIncorrectColor)
{
    constexpr char kVS[] =
        "precision mediump float;\n"
        "attribute vec4 inputAttribute;\n"
        "varying mediump vec2 texCoord;\n"
        "void main() {\n"
        "    texCoord = inputAttribute.xy;\n"
        "    gl_Position = vec4(inputAttribute.x, inputAttribute.y, 0.0, 1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "precision mediump float;\n"
        "varying mediump vec2 texCoord;\n"
        "uniform sampler2D testSampler;\n"
        "vec4 passthrough(vec4 c) {\n"
        "    return c;\n"
        "}\n"
        "void main() {\n"
        "    gl_FragColor = vec4(passthrough(texture2D(testSampler, texCoord)));\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, kVS, kFS);

    // Initialize basic red texture.
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 GLColor::red.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    drawQuad(program, "inputAttribute", 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that multiple multi-field varying structs that get used in the fragment shader work.
TEST_P(GLSLTest_ES3, ComplexVaryingStructsUsedInFragmentShader)
{
    // TODO(syoussefi): fails on android with:
    //
    // > Internal Vulkan error: A return array was too small for the result
    //
    // http://anglebug.com/42261898
    ANGLE_SKIP_TEST_IF(IsVulkan() && IsAndroid());

    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 inputAttribute;\n"
        "struct S {\n"
        "    vec4 field1;\n"
        "    vec4 field2;\n"
        "};\n"
        "out S varStruct;\n"
        "out S varStruct2;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = inputAttribute;\n"
        "    varStruct.field1 = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "    varStruct.field2 = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "    varStruct2.field1 = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "    varStruct2.field2 = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 col;\n"
        "struct S {\n"
        "    vec4 field1;\n"
        "    vec4 field2;\n"
        "};\n"
        "in S varStruct;\n"
        "in S varStruct2;\n"
        "void main()\n"
        "{\n"
        "    col = varStruct.field1 + varStruct2.field2;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that an inactive varying array that doesn't get used in the fragment shader works.
TEST_P(GLSLTest_ES3, InactiveVaryingArrayUnusedInFragmentShader)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 inputAttribute;\n"
        "out vec4 varArray[4];\n"
        "void main()\n"
        "{\n"
        "    gl_Position = inputAttribute;\n"
        "    varArray[0] = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "    varArray[1] = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "    varArray[2] = vec4(0.0, 0.0, 1.0, 1.0);\n"
        "    varArray[3] = vec4(1.0, 1.0, 0.0, 1.0);\n"
        "}\n";

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

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
}

// Test that an inactive varying struct that doesn't get used in the fragment shader works.
TEST_P(GLSLTest_ES3, InactiveVaryingStructUnusedInFragmentShader)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 inputAttribute;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "out S varStruct;\n"
        "out S varStruct2;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = inputAttribute;\n"
        "    varStruct.field = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "    varStruct2.field = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 col;\n"
        "struct S {\n"
        "    vec4 field;\n"
        "};\n"
        "in S varStruct;\n"
        "in S varStruct2;\n"
        "void main()\n"
        "{\n"
        "    col = varStruct.field;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that multiple varying matrices that get used in the fragment shader work.
TEST_P(GLSLTest_ES3, VaryingMatrices)
{
    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 inputAttribute;\n"
        "out mat2x2 varMat;\n"
        "out mat2x2 varMat2;\n"
        "out mat4x3 varMat3;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = inputAttribute;\n"
        "    varMat[0] = vec2(1, 1);\n"
        "    varMat[1] = vec2(1, 1);\n"
        "    varMat2[0] = vec2(0.5, 0.5);\n"
        "    varMat2[1] = vec2(0.5, 0.5);\n"
        "    varMat3[0] = vec3(0.75, 0.75, 0.75);\n"
        "    varMat3[1] = vec3(0.75, 0.75, 0.75);\n"
        "    varMat3[2] = vec3(0.75, 0.75, 0.75);\n"
        "    varMat3[3] = vec3(0.75, 0.75, 0.75);\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 col;\n"
        "in mat2x2 varMat;\n"
        "in mat2x2 varMat2;\n"
        "in mat4x3 varMat3;\n"
        "void main()\n"
        "{\n"
        "    col = vec4(varMat[0].x, varMat2[1].y, varMat3[2].z, 1);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 127, 191, 255), 1);
}

// This test covers passing a struct containing a sampler as a function argument.
TEST_P(GLSLTest, StructsWithSamplersAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samplerMember; };
uniform S uStruct;
uniform vec2 uTexCoord;
vec4 foo(S structVar)
{
    return texture2D(structVar.samplerMember, uTexCoord);
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

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

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

// This test covers passing a struct containing a sampler as a function argument.
TEST_P(GLSLTest, StructsWithSamplersAsFunctionArgWithPrototype)
{
    // Shader failed to compile on Android. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samplerMember; };
uniform S uStruct;
uniform vec2 uTexCoord;
vec4 foo(S structVar);
vec4 foo(S structVar)
{
    return texture2D(structVar.samplerMember, uTexCoord);
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

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

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

// This test covers passing a struct containing a sampler as a function argument, where the function
// has non-return branch statements.
TEST_P(GLSLTest_ES3, StructsWithSamplersAsFunctionArgWithBranch)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samplerMember; };
uniform S uStruct;
uniform vec2 uTexCoord;
vec4 foo(S structVar)
{
    vec4 result;
    while (true)
    {
        result = texture2D(structVar.samplerMember, uTexCoord);
        if (result.x == 12345.)
        {
            continue;
        }
        break;
    }
    return result;
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

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

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

// This test covers passing an array of structs containing samplers as a function argument.
TEST_P(GLSLTest, ArrayOfStructsWithSamplersAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    constexpr char kFS[] =
        "precision mediump float;\n"
        "struct S\n"
        "{\n"
        "    sampler2D samplerMember; \n"
        "};\n"
        "uniform S uStructs[2];\n"
        "uniform vec2 uTexCoord;\n"
        "\n"
        "vec4 foo(S[2] structs)\n"
        "{\n"
        "    return texture2D(structs[0].samplerMember, uTexCoord);\n"
        "}\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = foo(uStructs);\n"
        "}\n";

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

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStructs[0].samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

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

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

// This test covers passing a struct containing an array of samplers as a function argument.
TEST_P(GLSLTest, StructWithSamplerArrayAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    constexpr char kFS[] =
        "precision mediump float;\n"
        "struct S\n"
        "{\n"
        "    sampler2D samplerMembers[2];\n"
        "};\n"
        "uniform S uStruct;\n"
        "uniform vec2 uTexCoord;\n"
        "\n"
        "vec4 foo(S str)\n"
        "{\n"
        "    return texture2D(str.samplerMembers[0], uTexCoord);\n"
        "}\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = foo(uStruct);\n"
        "}\n";

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

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.samplerMembers[0]");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

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

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

// This test covers passing nested structs containing a sampler as a function argument.
TEST_P(GLSLTest, NestedStructsWithSamplersAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samplerMember; };
struct T { S nest; };
uniform T uStruct;
uniform vec2 uTexCoord;
vec4 foo2(S structVar)
{
    return texture2D(structVar.samplerMember, uTexCoord);
}
vec4 foo(T structVar)
{
    return foo2(structVar.nest);
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.nest.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

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

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

// This test covers passing a compound structs containing a sampler as a function argument.
TEST_P(GLSLTest, CompoundStructsWithSamplersAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samplerMember; bool b; };
uniform S uStruct;
uniform vec2 uTexCoord;
vec4 foo(S structVar)
{
    if (structVar.b)
        return texture2D(structVar.samplerMember, uTexCoord);
    else
        return vec4(1, 0, 0, 1);
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);
    GLint bLoc = glGetUniformLocation(program, "uStruct.b");
    ASSERT_NE(-1, bLoc);
    glUniform1i(bLoc, 1);

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

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

// This test covers passing nested compound structs containing a sampler as a function argument.
TEST_P(GLSLTest, NestedCompoundStructsWithSamplersAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { sampler2D samplerMember; bool b; };
struct T { S nest; bool b; };
uniform T uStruct;
uniform vec2 uTexCoord;
vec4 foo2(S structVar)
{
    if (structVar.b)
        return texture2D(structVar.samplerMember, uTexCoord);
    else
        return vec4(1, 0, 0, 1);
}
vec4 foo(T structVar)
{
    if (structVar.b)
        return foo2(structVar.nest);
    else
        return vec4(1, 0, 0, 1);
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.nest.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

    GLint bLoc = glGetUniformLocation(program, "uStruct.b");
    ASSERT_NE(-1, bLoc);
    glUniform1i(bLoc, 1);

    GLint nestbLoc = glGetUniformLocation(program, "uStruct.nest.b");
    ASSERT_NE(-1, nestbLoc);
    glUniform1i(nestbLoc, 1);

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

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

// Same as the prior test but with reordered struct members.
TEST_P(GLSLTest, MoreNestedCompoundStructsWithSamplersAsFunctionArg)
{
    // Shader failed to compile on Nexus devices. http://anglebug.com/42260860
    ANGLE_SKIP_TEST_IF(IsNexus5X() && IsAdreno() && IsOpenGLES());

    const char kFragmentShader[] = R"(precision mediump float;
struct S { bool b; sampler2D samplerMember; };
struct T { bool b; S nest; };
uniform T uStruct;
uniform vec2 uTexCoord;
vec4 foo2(S structVar)
{
    if (structVar.b)
        return texture2D(structVar.samplerMember, uTexCoord);
    else
        return vec4(1, 0, 0, 1);
}
vec4 foo(T structVar)
{
    if (structVar.b)
        return foo2(structVar.nest);
    else
        return vec4(1, 0, 0, 1);
}
void main()
{
    gl_FragColor = foo(uStruct);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);

    // Initialize the texture with green.
    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    GLubyte texData[] = {0u, 255u, 0u, 255u};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    // Draw
    glUseProgram(program);
    GLint samplerMemberLoc = glGetUniformLocation(program, "uStruct.nest.samplerMember");
    ASSERT_NE(-1, samplerMemberLoc);
    glUniform1i(samplerMemberLoc, 0);
    GLint texCoordLoc = glGetUniformLocation(program, "uTexCoord");
    ASSERT_NE(-1, texCoordLoc);
    glUniform2f(texCoordLoc, 0.5f, 0.5f);

    GLint bLoc = glGetUniformLocation(program, "uStruct.b");
    ASSERT_NE(-1, bLoc);
    glUniform1i(bLoc, 1);

    GLint nestbLoc = glGetUniformLocation(program, "uStruct.nest.b");
    ASSERT_NE(-1, nestbLoc);
    glUniform1i(nestbLoc, 1);

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

    EXPECT_PIXEL_COLOR_EQ(1, 1, GLColor::green);
}
// Test that a global variable declared after main() works. This is a regression test for an issue
// in global variable initialization.
TEST_P(WebGLGLSLTest, GlobalVariableDeclaredAfterMain)
{
    constexpr char kFS[] =
        "precision mediump float;\n"
        "int getFoo();\n"
        "uniform int u_zero;\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = vec4(1, 0, 0, 1);\n"
        "    if (getFoo() == 0)\n"
        "    {\n"
        "        gl_FragColor = vec4(0, 1, 0, 1);\n"
        "    }\n"
        "}\n"
        "int foo;\n"
        "int getFoo()\n"
        "{\n"
        "    foo = u_zero;\n"
        "    return foo;\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test calling array length() with a "this" expression having side effects inside a loop condition.
// The spec says that sequence operator operands need to run in sequence.
TEST_P(GLSLTest_ES3, ArrayLengthOnExpressionWithSideEffectsInLoopCondition)
{
    // "a" gets doubled three times in the below program.
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
uniform int u_zero;
int a;
int[2] doubleA()
{
    a *= 2;
    return int[2](a, a);
}
void main()
{
    a = u_zero + 1;
    for (int i = 0; i < doubleA().length(); ++i)
    {}
    if (a == 8)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test calling array length() with a "this" expression having side effects that interact with side
// effects of another operand of the same sequence operator. The spec says that sequence operator
// operands need to run in order from left to right (ESSL 3.00.6 section 5.9).
TEST_P(GLSLTest_ES3, ArrayLengthOnExpressionWithSideEffectsInSequence)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
uniform int u_zero;
int a;
int[3] doubleA()
{
    a *= 2;
    return int[3](a, a, a);
}
void main()
{
    a = u_zero;
    int b = (a++, doubleA().length());
    if (b == 3 && a == 2)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test calling array length() with a "this" expression that also contains a call of array length().
// Both "this" expressions also have side effects.
TEST_P(GLSLTest_ES3, NestedArrayLengthMethodsWithSideEffects)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
uniform int u_zero;
int a;
int[3] multiplyA(int multiplier)
{
    a *= multiplier;
    return int[3](a, a, a);
}
void main()
{
    a = u_zero + 1;
    int b = multiplyA(multiplyA(2).length()).length();
    if (b == 3 && a == 6)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test calling array length() with a "this" expression having side effects inside an if condition.
// This is an issue if the the side effect can be short circuited.
TEST_P(GLSLTest_ES3, ArrayLengthOnShortCircuitedExpressionWithSideEffectsInIfCondition)
{
    // Bug in the shader translator.  http://anglebug.com/42262472
    ANGLE_SKIP_TEST_IF(true);

    // "a" shouldn't get modified by this shader.
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
uniform int u_zero;
int a;
int[2] doubleA()
{
    a *= 2;
    return int[2](a, a);
}
void main()
{
    a = u_zero + 1;
    if (u_zero != 0 && doubleA().length() == 2)
    {
        ++a;
    }
    if (a == 1)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test calling array length() with a "this" expression having side effects in a statement where the
// side effect can be short circuited.
TEST_P(GLSLTest_ES3, ArrayLengthOnShortCircuitedExpressionWithSideEffectsInStatement)
{
    // Bug in the shader translator.  http://anglebug.com/42262472
    ANGLE_SKIP_TEST_IF(true);

    // "a" shouldn't get modified by this shader.
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
uniform int u_zero;
int a;
int[2] doubleA()
{
    a *= 2;
    return int[2](a, a);
}
void main()
{
    a = u_zero + 1;
    bool test = u_zero != 0 && doubleA().length() == 2;
    if (a == 1)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that array length inside vector constructor works.
TEST_P(GLSLTest_ES3, ArrayLengthInVectorConstructor)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
flat out uvec4 v;

int[1] f0()
{
    return int[1](1);
}
void main()
{
    v = uvec4(vec4(f0().length()));

    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
flat in uvec4 v;
out vec4 color;

bool isEq(uint a, float b) { return abs(float(a) - b) < 0.01; }

void main()
{
    if (isEq(v[0], 1.) &&
        isEq(v[1], 1.) &&
        isEq(v[2], 1.) &&
        isEq(v[3], 1.))
    {
        color = vec4(0, 1, 0, 1);
    }
    else
    {
        color = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that array length inside vector constructor works in complex expression.
TEST_P(GLSLTest_ES3, ArrayLengthInVectorConstructorComplex)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
out vec4 v;

int[1] f0()
{
    return int[1](1);
}
void main()
{
    v = vec4(float(uint(f0().length()) + 1u) / 4.);

    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
in vec4 v;
out vec4 color;

bool isEq(float a, float b) { return abs(float(a) - b) < 0.01; }

void main()
{
    if (isEq(v[0], 0.5) &&
        isEq(v[1], 0.5) &&
        isEq(v[2], 0.5) &&
        isEq(v[3], 0.5))
    {
        color = vec4(0, 1, 0, 1);
    }
    else
    {
        color = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that array length inside matrix constructor works.
TEST_P(GLSLTest_ES3, ArrayLengthInMatrixConstructor)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
out mat2x2 v;

int[1] f0()
{
    return int[1](1);
}
void main()
{
    v = mat2x2(f0().length());

    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
in mat2x2 v;
out vec4 color;

bool isEq(float a, float b) { return abs(a - b) < 0.01; }

void main()
{
    if (isEq(v[0][0], 1.) &&
        isEq(v[0][1], 0.) &&
        isEq(v[1][0], 0.) &&
        isEq(v[1][1], 1.))
    {
        color = vec4(0, 1, 0, 1);
    }
    else
    {
        color = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that array length inside vector constructor inside matrix constructor works.
TEST_P(GLSLTest_ES3, ArrayLengthInVectorInMatrixConstructor)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
out mat2x2 v;

int[1] f0()
{
    return int[1](1);
}
void main()
{
    v = mat2x2(vec2(f0().length()), f0().length(), 0);

    gl_Position.x = ((gl_VertexID & 1) == 0 ? -1.0 : 1.0);
    gl_Position.y = ((gl_VertexID & 2) == 0 ? -1.0 : 1.0);
    gl_Position.zw = vec2(0, 1);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
in mat2x2 v;
out vec4 color;

bool isEq(float a, float b) { return abs(a - b) < 0.01; }

void main()
{
    if (isEq(v[0][0], 1.) &&
        isEq(v[0][1], 1.) &&
        isEq(v[1][0], 1.) &&
        isEq(v[1][1], 0.))
    {
        color = vec4(0, 1, 0, 1);
    }
    else
    {
        color = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that statements inside switch() get translated to correct HLSL.
TEST_P(GLSLTest_ES3, DifferentStatementsInsideSwitch)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform int u;
void main()
{
    switch (u)
    {
        case 0:
            ivec2 i;
            i.yx;
    }
})";

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

// Test that switch fall-through works correctly.
// This is a regression test for http://anglebug.com/40644631
TEST_P(GLSLTest_ES3, SwitchFallThroughCodeDuplication)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
uniform int u_zero;

void main()
{
    int i = 0;
    // switch should fall through both cases.
    switch(u_zero)
    {
        case 0:
            i += 1;
        case 1:
            i += 2;
    }
    if (i == 3)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
    else
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test switch/case where default is last.
TEST_P(GLSLTest_ES3, SwitchWithDefaultAtTheEnd)
{
    constexpr char kFS[] = R"(#version 300 es

precision highp float;
out vec4 my_FragColor;

uniform int u_zero;

void main()
{
    switch (u_zero)
    {
        case 1:
            my_FragColor = vec4(1, 0, 0, 1);
            break;
        default:
            my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a switch statement with an empty block inside as a final statement compiles.
TEST_P(GLSLTest_ES3, SwitchFinalCaseHasEmptyBlock)
{
    constexpr char kFS[] = R"(#version 300 es

precision mediump float;
uniform int i;
void main()
{
    switch (i)
    {
        case 0:
            break;
        default:
            {}
    }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
}

// Test that a switch statement with an empty declaration inside as a final statement compiles.
TEST_P(GLSLTest_ES3, SwitchFinalCaseHasEmptyDeclaration)
{
    constexpr char kFS[] = R"(#version 300 es

precision mediump float;
uniform int i;
void main()
{
    switch (i)
    {
        case 0:
            break;
        default:
            float;
    }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
}

// Test switch/case where break/return statements are within blocks.
TEST_P(GLSLTest_ES3, SwitchBreakOrReturnInsideBlocks)
{
    constexpr char kFS[] = R"(#version 300 es

precision highp float;

uniform int u_zero;
out vec4 my_FragColor;

bool test(int n)
{
    switch(n) {
        case 0:
        {
            {
                break;
            }
        }
        case 1:
        {
            return true;
        }
        case 2:
        {
            n++;
        }
    }
    return false;
}

void main()
{
    my_FragColor = test(u_zero + 1) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test switch/case where a variable is declared inside one of the cases and is accessed by a
// subsequent case.
TEST_P(GLSLTest_ES3, SwitchWithVariableDeclarationInside)
{
    constexpr char kFS[] = R"(#version 300 es

precision highp float;
out vec4 my_FragColor;

uniform int u_zero;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    switch (u_zero)
    {
        case 0:
            ivec2 i;
            i = ivec2(1, 0);
        default:
            my_FragColor = vec4(0, i[0], 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test nested switch/case where a variable is declared inside one of the cases and is accessed by a
// subsequent case.
TEST_P(GLSLTest_ES3, NestedSwitchWithVariableDeclarationInside)
{
    constexpr char kFS[] = R"(#version 300 es

precision highp float;
out vec4 my_FragColor;

uniform int u_zero;
uniform int u_zero2;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    switch (u_zero)
    {
        case 0:
            ivec2 i;
            i = ivec2(1, 0);
            switch (u_zero2)
            {
                case 0:
                    int j;
                default:
                    j = 1;
                    i *= j;
            }
        default:
            my_FragColor = vec4(0, i[0], 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that an empty switch/case statement is translated in a way that compiles and executes the
// init-statement.
TEST_P(GLSLTest_ES3, EmptySwitch)
{
    constexpr char kFS[] = R"(#version 300 es

precision highp float;

uniform int u_zero;
out vec4 my_FragColor;

void main()
{
    int i = u_zero;
    switch(++i) {}
    my_FragColor = (i == 1) ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that an switch over a constant with mismatching cases works.
TEST_P(GLSLTest_ES3, SwitchWithConstantExpr)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    switch(10)
    {
        case 44:
            r = 0.5;
        case 50:
            break;
    }

    switch(20)
    {
        case 198:
            g = 0.5;
        default:
            g -= 1.;
            break;
    }

    switch(30)
    {
        default:
            b = 0.5;
        case 4:
            b += 0.5;
            break;
    }

    color = vec4(r, g, b, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
}

// Basic test that loops are not mistakenly considered infinite loops.
TEST_P(WebGL2GLSLTest, NotInfiniteLoop)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

float f()
{
    // Not infinite loop
    while (true)
    {
        // Not infinite loop
        for (int i = 0; true; ++i)
        {
            if (zero < 10u)
            {
                switch (zero)
                {
                    case 0u:
                        // Loops are not infinite because of this `return`.
                        return 0.7;
                    default:
                        break;
                }
            }
        }
    }
}

void writeFalse(out bool v)
{
    v = false;
}

bool globalConstantVariable = true;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    bool localConstantVariable = true;
    bool localVariable = true;
    bool localVariable2 = true;

    if (zero != 0u)
    {
        localVariable = false;
    }

    // Not infinite loop because of break
    while (localConstantVariable)
    {
        b += 0.3;

        if (g > 0.4) { break; }
    }

    // Not infinite loop because of break
    while (globalConstantVariable)
    {
        b += 0.2;

        if (g > 0.4) { break; }
    }

    // Not infinite loop because variable gets modified.
    while (localVariable)
    {
        b += 0.2;
        localVariable = !localVariable;
    }

    // Not infinite loop because variable gets modified.
    while (localVariable2)
    {
        b += 0.1;
        writeFalse(localVariable2);
    }

    r = f();

    color = vec4(r, g, b, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 178, 255, 204, 255, 1);
}

// Test that while(global) loops are not rejected if global is modified in a function call whose
// definition is after the loop.
TEST_P(WebGL2GLSLTest, NotInfiniteLoopIfGlobalIsModified)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

bool globalVariable = true;

// resetGlobal modifies globalVariable, but its definition is not visible while parsing the loop
void resetGlobal();

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    while (globalVariable)
    {
        r += 0.5;
        resetGlobal();
    }

    color = vec4(r, g, b, 1);
}

void resetGlobal()
{
    globalVariable = false;
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 127, 255, 0, 255, 1);
}

// Test that while(true) loops with break or return are not rejected
TEST_P(WebGL2GLSLTest, NotInfiniteLoopIfBreakOrReturn)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    while (true)
    {
        r += 0.1;
        if (r > 0.4)
        {
            break;
        }
    }

    for (;;)
    {
        g -= 0.1;

        switch (zero)
        {
            case 0u:
                g -= 0.6;
                color = vec4(r, g, b, 1);
                return;
            default:
                r = 0.2;
                break;
        }
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 127, 76, 0, 255, 1);
}

// Test that a constant struct inside an expression is handled correctly.
TEST_P(GLSLTest_ES3, ConstStructInsideExpression)
{
    // Incorrect output color was seen on Android. http://anglebug.com/42260946
    ANGLE_SKIP_TEST_IF(IsAndroid() && !IsNVIDIA() && IsOpenGLES());

    constexpr char kFS[] = R"(#version 300 es

precision highp float;
out vec4 my_FragColor;

uniform float u_zero;

struct S
{
    float field;
};

void main()
{
    const S constS = S(1.0);
    S nonConstS = constS;
    nonConstS.field = u_zero;
    bool fail = (constS == nonConstS);
    my_FragColor = vec4(0, 1, 0, 1);
    if (fail)
    {
        my_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a varying struct that's defined as a part of the declaration is handled correctly.
TEST_P(GLSLTest_ES3, VaryingStructWithInlineDefinition)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;

flat out struct S
{
    int field;
} v_s;

void main()
{
    v_s.field = 1;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es

precision highp float;
out vec4 my_FragColor;

flat in struct S
{
    int field;
} v_s;

void main()
{
    bool success = (v_s.field == 1);
    my_FragColor = vec4(1, 0, 0, 1);
    if (success)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that multi variables struct should not crash in separated struct expressions.
TEST_P(GLSLTest_ES3, VaryingStructWithInlineDefinition2)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
flat out struct A
{
    int a;
} z1, z2;
void main()
{
    z1.a = 1;
    z2.a = 2;
    gl_Position = inputAttribute;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
flat in struct A
{
    int a;
} z1, z2;
void main()
{
    bool success = (z1.a == 1 && z2.a == 2);
    my_FragColor = vec4(1, 0, 0, 1);
    if (success)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program.get(), "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a varying anonymous struct that is defined as a part of the declaration is handled
// correctly.
TEST_P(GLSLTest_ES3, VaryingAnonymousStructWithInlineDefinition)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
flat out struct
{
    int field;
} v_s;

void main()
{
    v_s.field = 1;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
flat in struct
{
    int field;
} v_s;
void main()
{
    bool success = (v_s.field == 1);
    my_FragColor = vec4(1, 0, 0, 1);
    if (success)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program.get(), "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a varying anonymous structs that are defined as a part of the declaration is handled
// correctly.
TEST_P(GLSLTest_ES3, VaryingAnonymousStructWithInlineDefinition2)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
flat out struct
{
    int field;
} v_s0, v_s1;
void main()
{
    v_s0.field = 1;
    v_s1.field = 2;
    gl_Position = inputAttribute;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
flat in struct
{
    int field;
} v_s0, v_s1;
void main()
{
    bool success = (v_s0.field == 1 && v_s1.field == 2);
    my_FragColor = vec4(1, 0, 0, 1);
    if (success)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program.get(), "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a varying anonymous structs that are defined as a part of the declaration is handled
// in a specific way. Highlights ambiguity of ES "Chapter 9. Shader Interface Matching":
//  "When linking shaders, the type of declared vertex outputs and fragment inputs with the same
//  name must match"
TEST_P(GLSLTest_ES3, VaryingAnonymousStructWithInlineDefinition3)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
flat out struct
{
    int field;
} v_s0;
flat out struct
{
    int field;
} v_s1;
flat out struct
{
    int field;
} v_s2, v_s3;
void main()
{
    v_s0.field = 1;
    v_s1.field = 2;
    v_s2.field = 3;
    v_s3.field = 4;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
flat in struct
{
    int field;
} v_s0, v_s1, v_s2, v_s3;
void main()
{
    bool success = v_s0.field == 1 && v_s1.field == 2 && v_s2.field == 3 && v_s3.field == 4;
    my_FragColor = vec4(1, 0, 0, 1);
    if (success)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program.get(), "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a varying anonymous structs can be compared for equality.
TEST_P(GLSLTest_ES3, VaryingAnonymousStructEquality)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
flat out struct
{
    int field;
} v_s0;
flat out struct
{
    int field;
} v_s1;
flat out struct
{
    int field;
} v_s2, v_s3;

void main()
{
    v_s0.field = 1;
    v_s1.field = 2;
    v_s2.field = 3;
    v_s3.field = 4;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
flat in struct
{
    int field;
} v_s0, v_s1, v_s2, v_s3;
void main()
{
    bool success = v_s0 != v_s1 && v_s0 != v_s2 && v_s0 != v_s3 && v_s1 != v_s2 && v_s1 != v_s3 && v_s2 != v_s3;
    success = success && v_s0.field == 1 && v_s1.field == 2 && v_s2.field == 3 && v_s3.field == 4;
    my_FragColor = vec4(1, 0, 0, 1);
    if (success)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program.get(), "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionFloat)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 position;
uniform highp float inVal;
out highp float myVarying;

void main()
{
    myVarying = inVal;
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in mediump float myVarying;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    if (myVarying > 1.0)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glUseProgram(program);
    GLint positionLocation              = glGetAttribLocation(program, "position");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    for (Vector3 &vertex : quadVertices)
    {
        vertex.z() = 0.5f;
    }
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(positionLocation);

    GLint inValLoc = glGetUniformLocation(program, "inVal");
    ASSERT_NE(-1, inValLoc);
    glUniform1f(inValLoc, static_cast<GLfloat>(1.003));

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionlowpFloat)
{
    // Note: SPIRV only has relaxed precision so both lowp and mediump turn into "relaxed
    // precision", thus this is the same test as MismatchPrecisionFloat but including it for
    // completeness in case something changes.
    constexpr char kVS[] = R"(#version 300 es
in vec4 position;
uniform highp float inVal;
out highp float myVarying;

void main()
{
    myVarying = inVal;
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in lowp float myVarying;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    if (myVarying > 1.0)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glUseProgram(program);
    GLint positionLocation              = glGetAttribLocation(program, "position");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    for (Vector3 &vertex : quadVertices)
    {
        vertex.z() = 0.5f;
    }
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(positionLocation);

    GLint inValLoc = glGetUniformLocation(program, "inVal");
    ASSERT_NE(-1, inValLoc);
    glUniform1f(inValLoc, static_cast<GLfloat>(1.003));

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionVec2UnusedVarying)
{
    constexpr char kVS[] = R"(#version 300 es
in vec2 position;
uniform highp float inVal;
out highp float myVarying;
out highp vec2 texCoord;

void main()
{
    myVarying = inVal;
    gl_Position = vec4(position, 0, 1);
    texCoord = position * 0.5 + vec2(0.5);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in mediump float myVarying;
in mediump vec2 texCoord;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    if (myVarying > 1.0)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glUseProgram(program);
    GLint positionLocation              = glGetAttribLocation(program, "position");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    for (Vector3 &vertex : quadVertices)
    {
        vertex.z() = 0.5f;
    }
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(positionLocation);

    GLint inValLoc = glGetUniformLocation(program, "inVal");
    ASSERT_NE(-1, inValLoc);
    glUniform1f(inValLoc, static_cast<GLfloat>(1.003));

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test mismatched precision in varying is handled correctly.
TEST_P(GLSLTest_ES3, MismatchPrecisionMedToHigh)
{
    constexpr char kVS[] = R"(#version 300 es
in vec2 position;
uniform highp float inVal;
out mediump float myVarying;

void main()
{
    myVarying = inVal;
    gl_Position = vec4(position, 0, 1);
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
in highp float myVarying;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
    if (myVarying > 1.0)
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glUseProgram(program);
    GLint positionLocation              = glGetAttribLocation(program, "position");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    for (Vector3 &vertex : quadVertices)
    {
        vertex.z() = 0.5f;
    }
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(positionLocation);

    GLint inValLoc = glGetUniformLocation(program, "inVal");
    ASSERT_NE(-1, inValLoc);
    glUniform1f(inValLoc, static_cast<GLfloat>(1.003));

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test vector/scalar arithmetic (in this case multiplication and addition).
TEST_P(GLSLTest, VectorScalarMultiplyAndAddInLoop)
{
    constexpr char kFS[] = R"(precision mediump float;

void main() {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
    for (int i = 0; i < 2; i++)
    {
        gl_FragColor += (2.0 * gl_FragCoord.x);
    }
    if (gl_FragColor.g == gl_FragColor.r &&
        gl_FragColor.b == gl_FragColor.r &&
        gl_FragColor.a == gl_FragColor.r)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test vector/scalar arithmetic (in this case compound division and addition).
TEST_P(GLSLTest, VectorScalarDivideAndAddInLoop)
{
    constexpr char kFS[] = R"(precision mediump float;

void main() {
    gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
    for (int i = 0; i < 2; i++)
    {
        float x = gl_FragCoord.x;
        gl_FragColor = gl_FragColor + (x /= 2.0);
    }
    if (gl_FragColor.g == gl_FragColor.r &&
        gl_FragColor.b == gl_FragColor.r &&
        gl_FragColor.a == gl_FragColor.r)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that a varying with a flat qualifier that is used as an operand of a folded ternary operator
// is handled correctly.
TEST_P(GLSLTest_ES3, FlatVaryingUsedInFoldedTernary)
{
    constexpr char kVS[] = R"(#version 300 es

in vec4 inputAttribute;

flat out int v;

void main()
{
    v = 1;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es

precision highp float;
out vec4 my_FragColor;

flat in int v;

void main()
{
    my_FragColor = vec4(0, (true ? v : 0), 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "inputAttribute", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Verify that the link error message from last link failure is cleared when the new link is
// finished.
TEST_P(GLSLTest, ClearLinkErrorLog)
{
    constexpr char kVS[] = R"(attribute vec4 vert_in;
varying vec4 vert_out;
void main()
{
    gl_Position = vert_in;
    vert_out = vert_in;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec4 frag_in;
void main()
{
    gl_FragColor = frag_in;
})";

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

    GLuint program = glCreateProgram();

    // The first time the program link fails because of lack of fragment shader.
    glAttachShader(program, vs);
    glLinkProgram(program);
    GLint linkStatus = GL_TRUE;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    ASSERT_FALSE(linkStatus);

    const std::string lackOfFragmentShader = QueryErrorMessage(program);
    EXPECT_TRUE(lackOfFragmentShader != "");

    // The second time the program link fails because of the mismatch of the varying types.
    glAttachShader(program, fs);
    glLinkProgram(program);
    linkStatus = GL_TRUE;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    ASSERT_FALSE(linkStatus);

    const std::string varyingTypeMismatch = QueryErrorMessage(program);
    EXPECT_TRUE(varyingTypeMismatch != "");

    EXPECT_EQ(std::string::npos, varyingTypeMismatch.find(lackOfFragmentShader));

    glDetachShader(program, vs);
    glDetachShader(program, fs);
    glDeleteShader(vs);
    glDeleteShader(fs);
    glDeleteProgram(program);

    ASSERT_GL_NO_ERROR();
}

// Verify that a valid program still draws correctly after a shader link error
TEST_P(GLSLTest, DrawAfterShaderLinkError)
{
    constexpr char kVS[]    = R"(attribute vec4 position;
        varying vec4 vColor;
        void main()
        {
            vColor = vec4(0.0, 1.0, 0.0, 1.0);
            gl_Position = position;
        })";
    constexpr char kFS[]    = R"(precision mediump float;
        varying vec4 vColor;
        void main()
        {
            gl_FragColor = vColor;
        })";
    constexpr char kBadFS[] = R"(WILL NOT COMPILE;)";

    GLuint fsBad = glCreateShader(GL_FRAGMENT_SHADER);

    // Create bad fragment shader
    {
        const char *sourceArray[1] = {kBadFS};
        glShaderSource(fsBad, 1, sourceArray, nullptr);
        glCompileShader(fsBad);

        GLint compileResult;
        glGetShaderiv(fsBad, GL_COMPILE_STATUS, &compileResult);
        ASSERT_FALSE(compileResult);
    }

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    GLuint fs = GetProgramShader(program, GL_FRAGMENT_SHADER);

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glUseProgram(program);
    GLint positionLocation              = glGetAttribLocation(program, "position");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    for (Vector3 &vertex : quadVertices)
    {
        vertex.z() = 0.5f;
    }
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(positionLocation);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);

    glDetachShader(program, fs);
    glAttachShader(program, fsBad);
    glLinkProgram(program);
    GLint linkStatus = GL_TRUE;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    ASSERT_FALSE(linkStatus);

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Validate error messages when the link mismatch occurs on the type of a non-struct varying.
TEST_P(GLSLTest, ErrorMessageOfVaryingMismatch)
{
    constexpr char kVS[] = R"(attribute vec4 inputAttribute;
varying vec4 vertex_out;
void main()
{
    vertex_out = inputAttribute;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying float vertex_out;
void main()
{
    gl_FragColor = vec4(vertex_out, 0.0, 0.0, 1.0);
})";

    validateComponentsInErrorMessage(kVS, kFS, "Types", "varying 'vertex_out'");
}

// Validate error messages when the link mismatch occurs on the name of a varying field.
TEST_P(GLSLTest_ES3, ErrorMessageOfVaryingStructFieldNameMismatch)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
struct S {
    float val1;
    vec4 val2;
};
out S vertex_out;
void main()
{
    vertex_out.val2 = inputAttribute;
    vertex_out.val1 = inputAttribute[0];
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
struct S {
    float val1;
    vec4 val3;
};
in S vertex_out;
layout (location = 0) out vec4 frag_out;
void main()
{
    frag_out = vec4(vertex_out.val1, 0.0, 0.0, 1.0);
})";

    validateComponentsInErrorMessage(kVS, kFS, "Field names", "varying 'vertex_out'");
}

// Validate error messages when the link mismatch occurs on the type of a varying field.
TEST_P(GLSLTest_ES3, ErrorMessageOfVaryingStructFieldMismatch)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 inputAttribute;
struct S {
    float val1;
    vec4 val2;
};
out S vertex_out;
void main()
{
    vertex_out.val2 = inputAttribute;
    vertex_out.val1 = inputAttribute[0];
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
struct S {
    float val1;
    vec2 val2;
};
in S vertex_out;
layout (location = 0) out vec4 frag_out;
void main()
{
    frag_out = vec4(vertex_out.val1, 0.0, 0.0, 1.0);
})";

    validateComponentsInErrorMessage(kVS, kFS, "Types",
                                     "varying 'vertex_out' member 'vertex_out.val2'");
}

// Validate error messages when the link mismatch occurs on the name of a struct member of a uniform
// field.
TEST_P(GLSLTest, ErrorMessageOfLinkUniformStructFieldNameMismatch)
{
    constexpr char kVS[] = R"(
struct T
{
    vec2 t1;
    vec3 t2;
};
struct S {
    T val1;
    vec4 val2;
};
uniform S uni;

attribute vec4 inputAttribute;
varying vec4 vertex_out;
void main()
{
    vertex_out = uni.val2;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(precision highp float;
struct T
{
    vec2 t1;
    vec3 t3;
};
struct S {
    T val1;
    vec4 val2;
};
uniform S uni;

varying vec4 vertex_out;
void main()
{
    gl_FragColor = vec4(uni.val1.t1[0], 0.0, 0.0, 1.0);
})";

    validateComponentsInErrorMessage(kVS, kFS, "Field names", "uniform 'uni' member 'uni.val1'");
}

// Validate error messages  when the link mismatch occurs on the type of a non-struct uniform block
// field.
TEST_P(GLSLTest_ES3, ErrorMessageOfLinkInterfaceBlockFieldMismatch)
{
    constexpr char kVS[] = R"(#version 300 es
uniform S {
    vec2 val1;
    vec4 val2;
} uni;

in vec4 inputAttribute;
out vec4 vertex_out;
void main()
{
    vertex_out = uni.val2;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform S {
    vec2 val1;
    vec3 val2;
} uni;

in vec4 vertex_out;
layout (location = 0) out vec4 frag_out;
void main()
{
    frag_out = vec4(uni.val1[0], 0.0, 0.0, 1.0);
})";

    validateComponentsInErrorMessage(kVS, kFS, "Types", "uniform block 'S' member 'S.val2'");
}

// Validate error messages  when the link mismatch occurs on the type of a member of a uniform block
// struct field.
TEST_P(GLSLTest_ES3, ErrorMessageOfLinkInterfaceBlockStructFieldMismatch)
{
    constexpr char kVS[] = R"(#version 300 es
struct T
{
    vec2 t1;
    vec3 t2;
};
uniform S {
    T val1;
    vec4 val2;
} uni;

in vec4 inputAttribute;
out vec4 vertex_out;
void main()
{
    vertex_out = uni.val2;
    gl_Position = inputAttribute;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
struct T
{
    vec2 t1;
    vec4 t2;
};
uniform S {
    T val1;
    vec4 val2;
} uni;

in vec4 vertex_out;
layout (location = 0) out vec4 frag_out;
void main()
{
    frag_out = vec4(uni.val1.t1[0], 0.0, 0.0, 1.0);
})";

    validateComponentsInErrorMessage(kVS, kFS, "Types", "uniform block 'S' member 'S.val1.t2'");
}

// Test a vertex shader that doesn't declare any varyings with a fragment shader that statically
// uses a varying, but in a statement that gets trivially optimized out by the compiler.
TEST_P(GLSLTest_ES3, FragmentShaderStaticallyUsesVaryingMissingFromVertex)
{
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;

void main()
{
    gl_Position = vec4(0, 1, 0, 1);
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in float foo;
out vec4 my_FragColor;

void main()
{
    if (false)
    {
        float unreferenced = foo;
    }
    my_FragColor = vec4(0, 1, 0, 1);
})";

    validateComponentsInErrorMessage(kVS, kFS, "does not match any", "foo");
}

// Test a varying that is statically used but not active in the fragment shader.
TEST_P(GLSLTest_ES3, VaryingStaticallyUsedButNotActiveInFragmentShader)
{
    constexpr char kVS[] = R"(#version 300 es
precision mediump float;
in vec4 iv;
out vec4 v;
void main()
{
    gl_Position = iv;
    v = iv;
})";

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in vec4 v;
out vec4 color;
void main()
{
    color = true ? vec4(0.0) : v;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that linking varyings by location works.
TEST_P(GLSLTest_ES31, LinkVaryingsByLocation)
{
    constexpr char kVS[] = R"(#version 310 es
precision highp float;
in vec4 position;
layout(location = 1) out vec4 shaderOutput;
void main() {
    gl_Position = position;
    shaderOutput = vec4(0.0, 1.0, 0.0, 1.0);
})";

    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(location = 1) in vec4 shaderInput;
out vec4 outColor;
void main() {
    outColor = shaderInput;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test nesting floor() calls with a large multiplier inside.
TEST_P(GLSLTest_ES3, NestedFloorWithLargeMultiplierInside)
{
    // D3D11 seems to ignore the floor() calls in this particular case, so one of the corners ends
    // up red. http://crbug.com/838885
    ANGLE_SKIP_TEST_IF(IsD3D11());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main()
{
    vec2 coord = gl_FragCoord.xy / 500.0;
    my_FragColor = vec4(1, 0, 0, 1);
    if (coord.y + 0.1 > floor(1e-6 * floor(coord.x*4e5)))
    {
        my_FragColor = vec4(0, 1, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    // Verify that all the corners of the rendered result are green.
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, getWindowHeight() - 1, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(getWindowWidth() - 1, 0, GLColor::green);
    EXPECT_PIXEL_COLOR_EQ(0, getWindowHeight() - 1, GLColor::green);
}

// Verify that a link error is generated when the sum of the number of active image uniforms and
// active shader storage blocks in a rendering pipeline exceeds
// GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES.
TEST_P(GLSLTest_ES31, ExceedCombinedShaderOutputResourcesInVSAndFS)
{
    // TODO(jiawei.shao@intel.com): enable this test when shader storage buffer is supported on
    // D3D11 back-ends.
    ANGLE_SKIP_TEST_IF(IsD3D11());

    GLint maxVertexShaderStorageBlocks;
    GLint maxVertexImageUniforms;
    GLint maxFragmentShaderStorageBlocks;
    GLint maxFragmentImageUniforms;
    GLint maxCombinedShaderStorageBlocks;
    GLint maxCombinedImageUniforms;
    glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexShaderStorageBlocks);
    glGetIntegerv(GL_MAX_VERTEX_IMAGE_UNIFORMS, &maxVertexImageUniforms);
    glGetIntegerv(GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS, &maxFragmentShaderStorageBlocks);
    glGetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImageUniforms);
    glGetIntegerv(GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS, &maxCombinedShaderStorageBlocks);
    glGetIntegerv(GL_MAX_COMBINED_IMAGE_UNIFORMS, &maxCombinedImageUniforms);

    ASSERT_GE(maxCombinedShaderStorageBlocks, maxVertexShaderStorageBlocks);
    ASSERT_GE(maxCombinedShaderStorageBlocks, maxFragmentShaderStorageBlocks);
    ASSERT_GE(maxCombinedImageUniforms, maxVertexImageUniforms);
    ASSERT_GE(maxCombinedImageUniforms, maxFragmentImageUniforms);

    GLint vertexSSBOs   = maxVertexShaderStorageBlocks;
    GLint fragmentSSBOs = maxFragmentShaderStorageBlocks;
    // Limit the sum of ssbos in vertex and fragment shaders to maxCombinedShaderStorageBlocks.
    if (vertexSSBOs + fragmentSSBOs > maxCombinedShaderStorageBlocks)
    {
        fragmentSSBOs = maxCombinedShaderStorageBlocks - vertexSSBOs;
    }

    GLint vertexImages   = maxVertexImageUniforms;
    GLint fragmentImages = maxFragmentImageUniforms;
    // Limit the sum of images in vertex and fragment shaders to maxCombinedImageUniforms.
    if (vertexImages + fragmentImages > maxCombinedImageUniforms)
    {
        vertexImages = maxCombinedImageUniforms - fragmentImages;
    }

    GLint maxDrawBuffers;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);

    GLint maxCombinedShaderOutputResources;
    glGetIntegerv(GL_MAX_COMBINED_SHADER_OUTPUT_RESOURCES, &maxCombinedShaderOutputResources);
    ASSERT_GL_NO_ERROR();

    ANGLE_SKIP_TEST_IF(vertexSSBOs + fragmentSSBOs + vertexImages + fragmentImages +
                           maxDrawBuffers <=
                       maxCombinedShaderOutputResources);

    std::ostringstream vertexStream;
    vertexStream << "#version 310 es\n";
    for (int i = 0; i < vertexSSBOs; ++i)
    {
        vertexStream << "layout(shared, binding = " << i << ") buffer blockName" << i
                     << "{\n"
                        "    float data;\n"
                        "} ssbo"
                     << i << ";\n";
    }
    vertexStream << "layout(r32f, binding = 0) uniform highp image2D imageArray[" << vertexImages
                 << "];\n";
    vertexStream << "void main()\n"
                    "{\n"
                    "    float val = 0.1;\n"
                    "    vec4 val2 = vec4(0.0);\n";
    for (int i = 0; i < vertexSSBOs; ++i)
    {
        vertexStream << "    val += ssbo" << i << ".data; \n";
    }
    for (int i = 0; i < vertexImages; ++i)
    {
        vertexStream << "    val2 += imageLoad(imageArray[" << i << "], ivec2(0, 0)); \n";
    }
    vertexStream << "    gl_Position = vec4(val, val2);\n"
                    "}\n";

    std::ostringstream fragmentStream;
    fragmentStream << "#version 310 es\n" << "precision highp float;\n";
    for (int i = 0; i < fragmentSSBOs; ++i)
    {
        fragmentStream << "layout(shared, binding = " << i << ") buffer blockName" << i
                       << "{\n"
                          "    float data;\n"
                          "} ssbo"
                       << i << ";\n";
    }
    fragmentStream << "layout(r32f, binding = 0) uniform highp image2D imageArray["
                   << fragmentImages << "];\n";
    fragmentStream << "layout (location = 0) out vec4 foutput[" << maxDrawBuffers << "];\n";

    fragmentStream << "void main()\n"
                      "{\n"
                      "    float val = 0.1;\n"
                      "    vec4 val2 = vec4(0.0);\n";
    for (int i = 0; i < fragmentSSBOs; ++i)
    {
        fragmentStream << "    val += ssbo" << i << ".data; \n";
    }
    for (int i = 0; i < fragmentImages; ++i)
    {
        fragmentStream << "    val2 += imageLoad(imageArray[" << i << "], ivec2(0, 0)); \n";
    }
    for (int i = 0; i < maxDrawBuffers; ++i)
    {
        fragmentStream << "    foutput[" << i << "] = vec4(val, val2);\n";
    }
    fragmentStream << "}\n";

    GLuint program = CompileProgram(vertexStream.str().c_str(), fragmentStream.str().c_str());
    EXPECT_EQ(0u, program);

    ASSERT_GL_NO_ERROR();
}

// Test that assigning an assignment expression to a swizzled vector field in a user-defined
// function works correctly.
TEST_P(GLSLTest_ES3, AssignToSwizzled)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;

uniform float uzero;

vec3 fun(float s, float v)
{
    vec3 r = vec3(0);
    if (s < 1.0) {
        r.x = r.y = r.z = v;
        return r;
    }
    return r;
}

void main()
{
    my_FragColor.a = 1.0;
    my_FragColor.rgb = fun(uzero, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Similar to AssignToSwizzled, but uses other assignment operators than `=`.
TEST_P(GLSLTest_ES3, AssignToSwizzled2)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;

uniform float uzero;

vec3 fun(float s, float v)
{
    vec3 r = vec3(0.125, 0.5, 0.);
    if (s < 1.0) {
        r.x /= r.y *= r.z += v;
        return r;
    }
    return r;
}

void main()
{
    my_FragColor.a = 1.0;
    my_FragColor.rgb = fun(uzero, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_NEAR(0, 0, 63, 127, 255, 255, 1);
}

// Test that swizzled vector to bool cast works correctly.
TEST_P(GLSLTest_ES3, SwizzledToBoolCoercion)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 o;
uniform vec2 u;
void main()
{
    bvec2 b = bvec2(u.yx);
    if (b.x&&!b.y)
        o = vec4(1.0);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLint uloc = glGetUniformLocation(program, "u");
    ASSERT_NE(uloc, -1);
    glUniform2f(uloc, 0, 1);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test a fragment shader that returns inside if (that being the only branch that actually gets
// executed). Regression test for http://anglebug.com/42261034
TEST_P(GLSLTest, IfElseIfAndReturn)
{
    constexpr char kVS[] = R"(attribute vec4 a_position;
varying vec2 vPos;
void main()
{
    gl_Position = a_position;
    vPos = a_position.xy;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 vPos;
void main()
{
    if (vPos.x < 1.0) // This colors the whole canvas green
    {
        gl_FragColor = vec4(0, 1, 0, 1);
        return;
    }
    else if (vPos.x < 1.1) // This should have no effect
    {
        gl_FragColor = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "a_position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that if-else blocks whose contents get pruned due to compile-time constant conditions work.
TEST_P(GLSLTest, IfElsePrunedBlocks)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform float u;
void main()
{
    // if with only a pruned true block
    if (u > 0.0)
        if (false) discard;

    // if with a pruned true block and a false block
    if (u > 0.0)
    {
        if (false) discard;
    }
    else
        ;

    // if with a true block and a pruned false block
    if (u > 0.0)
        ;
    else
        if (false) discard;

    // if with a pruned true block and a pruned false block
    if (u > 0.0)
    {
        if (false) discard;
    }
    else
        if (false) discard;

    gl_FragColor = vec4(0, 1, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests that PointCoord behaves the same betweeen a user FBO and the back buffer.
TEST_P(GLSLTest, PointCoordConsistency)
{
    constexpr char kPointCoordVS[] = R"(attribute vec2 position;
uniform vec2 viewportSize;
void main()
{
   gl_Position = vec4(position, 0, 1);
   gl_PointSize = viewportSize.x;
})";

    constexpr char kPointCoordFS[] = R"(void main()
{
    gl_FragColor = vec4(gl_PointCoord.xy, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kPointCoordVS, kPointCoordFS);
    glUseProgram(program);

    GLint uniLoc = glGetUniformLocation(program, "viewportSize");
    ASSERT_NE(-1, uniLoc);
    glUniform2f(uniLoc, static_cast<GLfloat>(getWindowWidth()),
                static_cast<GLfloat>(getWindowHeight()));

    // Draw to backbuffer.
    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_POINTS, 0, 1);
    ASSERT_GL_NO_ERROR();

    std::vector<GLColor> backbufferData(getWindowWidth() * getWindowHeight());
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 backbufferData.data());

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    ASSERT_GL_NO_ERROR();
    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

    // Draw to user FBO.
    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_POINTS, 0, 1);
    ASSERT_GL_NO_ERROR();

    std::vector<GLColor> userFBOData(getWindowWidth() * getWindowHeight());
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 userFBOData.data());

    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(userFBOData.size(), backbufferData.size());
    EXPECT_EQ(userFBOData, backbufferData);
}

// Test gl_PointCoord used before gl_FragCoord when dithering might be emulated.
TEST_P(GLSLTest_ES3, PointCoordBeforeFragCoord)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 colorOut;
void main()
{
    vec2 p = gl_PointCoord;
    colorOut = vec4(abs(p) + vec2(1, 1), gl_FragCoord.x * 0.000001, 1.0);
})";

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLTexture tex565;
    glBindTexture(GL_TEXTURE_2D, tex565);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGB565, 10, 20);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex565, 0);

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor::yellow, 1);
    ASSERT_GL_NO_ERROR();
}

bool SubrectEquals(const std::vector<GLColor> &bigArray,
                   const std::vector<GLColor> &smallArray,
                   int bigSize,
                   int offset,
                   int smallSize)
{
    int badPixels = 0;
    for (int y = 0; y < smallSize; y++)
    {
        for (int x = 0; x < smallSize; x++)
        {
            int bigOffset   = (y + offset) * bigSize + x + offset;
            int smallOffset = y * smallSize + x;
            if (bigArray[bigOffset] != smallArray[smallOffset])
                badPixels++;
        }
    }
    return badPixels == 0;
}

// Tests that FragCoord behaves the same between a user FBO and the back buffer.
TEST_P(GLSLTest, FragCoordConsistency)
{
    constexpr char kFragCoordShader[] = R"(uniform mediump vec2 viewportSize;
void main()
{
    gl_FragColor = vec4(gl_FragCoord.xy / viewportSize, 0, 1);
})";

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

    GLint uniLoc = glGetUniformLocation(program, "viewportSize");
    ASSERT_NE(-1, uniLoc);
    glUniform2f(uniLoc, static_cast<GLfloat>(getWindowWidth()),
                static_cast<GLfloat>(getWindowHeight()));

    // Draw to backbuffer.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5);
    ASSERT_GL_NO_ERROR();

    std::vector<GLColor> backbufferData(getWindowWidth() * getWindowHeight());
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 backbufferData.data());

    GLTexture tex;
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                 GL_UNSIGNED_BYTE, nullptr);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
    ASSERT_GL_NO_ERROR();
    ASSERT_GLENUM_EQ(GL_FRAMEBUFFER_COMPLETE, glCheckFramebufferStatus(GL_FRAMEBUFFER));

    // Draw to user FBO.
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5);
    ASSERT_GL_NO_ERROR();

    std::vector<GLColor> userFBOData(getWindowWidth() * getWindowHeight());
    glReadPixels(0, 0, getWindowWidth(), getWindowHeight(), GL_RGBA, GL_UNSIGNED_BYTE,
                 userFBOData.data());

    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(userFBOData.size(), backbufferData.size());
    EXPECT_EQ(userFBOData, backbufferData)
        << "FragCoord should be the same to default and user FBO";

    // Repeat the same test but with a smaller viewport.
    ASSERT_EQ(getWindowHeight(), getWindowWidth());
    const int kQuarterSize = getWindowWidth() >> 2;
    glViewport(kQuarterSize, kQuarterSize, kQuarterSize * 2, kQuarterSize * 2);

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

    std::vector<GLColor> userFBOViewportData(kQuarterSize * kQuarterSize * 4);
    glReadPixels(kQuarterSize, kQuarterSize, kQuarterSize * 2, kQuarterSize * 2, GL_RGBA,
                 GL_UNSIGNED_BYTE, userFBOViewportData.data());

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glClear(GL_COLOR_BUFFER_BIT);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5);

    std::vector<GLColor> defaultFBOViewportData(kQuarterSize * kQuarterSize * 4);
    glReadPixels(kQuarterSize, kQuarterSize, kQuarterSize * 2, kQuarterSize * 2, GL_RGBA,
                 GL_UNSIGNED_BYTE, defaultFBOViewportData.data());
    ASSERT_GL_NO_ERROR();
    EXPECT_EQ(userFBOViewportData, defaultFBOViewportData)
        << "FragCoord should be the same to default and user FBO even with a custom viewport";

    // Check that the subrectangles are the same between the viewport and non-viewport modes.
    EXPECT_TRUE(SubrectEquals(userFBOData, userFBOViewportData, getWindowWidth(), kQuarterSize,
                              kQuarterSize * 2));
    EXPECT_TRUE(SubrectEquals(backbufferData, defaultFBOViewportData, getWindowWidth(),
                              kQuarterSize, kQuarterSize * 2));
}

// Ensure that using defined in a macro works in this simple case. This mirrors a dEQP test.
TEST_P(GLSLTest, DefinedInMacroSucceeds)
{
    constexpr char kVS[] = R"(precision mediump float;
attribute highp vec4 position;
varying vec2 out0;

void main()
{
#define AAA defined(BBB)

#if !AAA
    out0 = vec2(0.0, 1.0);
#else
    out0 = vec2(1.0, 0.0);
#endif
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 out0;
void main()
{
    gl_FragColor = vec4(out0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Validate the defined operator is evaluated when the macro is called, not when defined.
TEST_P(GLSLTest, DefinedInMacroWithUndef)
{
    constexpr char kVS[] = R"(precision mediump float;
attribute highp vec4 position;
varying vec2 out0;

void main()
{
#define BBB 1
#define AAA defined(BBB)
#undef BBB

#if AAA
    out0 = vec2(1.0, 0.0);
#else
    out0 = vec2(0.0, 1.0);
#endif
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 out0;
void main()
{
    gl_FragColor = vec4(out0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Validate the defined operator is evaluated when the macro is called, not when defined.
TEST_P(GLSLTest, DefinedAfterMacroUsage)
{
    constexpr char kVS[] = R"(precision mediump float;
attribute highp vec4 position;
varying vec2 out0;

void main()
{
#define AAA defined(BBB)
#define BBB 1

#if AAA
    out0 = vec2(0.0, 1.0);
#else
    out0 = vec2(1.0, 0.0);
#endif
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 out0;
void main()
{
    gl_FragColor = vec4(out0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "position", 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test generating "defined" by concatenation when a macro is called. This is not allowed.
TEST_P(GLSLTest, DefinedInMacroConcatenationNotAllowed)
{
    constexpr char kVS[] = R"(precision mediump float;
attribute highp vec4 position;
varying vec2 out0;

void main()
{
#define BBB 1
#define AAA(defi, ned) defi ## ned(BBB)

#if AAA(defi, ned)
    out0 = vec2(0.0, 1.0);
#else
    out0 = vec2(1.0, 0.0);
#endif
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 out0;
void main()
{
    gl_FragColor = vec4(out0, 0, 1);
})";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_EQ(0u, program);
    glDeleteProgram(program);
}

// Test using defined in a macro parameter name. This is not allowed.
TEST_P(GLSLTest, DefinedAsParameterNameNotAllowed)
{
    constexpr char kVS[] = R"(precision mediump float;
attribute highp vec4 position;
varying vec2 out0;

void main()
{
#define BBB 1
#define AAA(defined) defined(BBB)

#if AAA(defined)
    out0 = vec2(0.0, 1.0);
#else
    out0 = vec2(1.0, 0.0);
#endif
    gl_Position = position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 out0;
void main()
{
    gl_FragColor = vec4(out0, 0, 1);
})";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_EQ(0u, program);
    glDeleteProgram(program);
}

// Ensure that defined in a macro is no accepted in WebGL.
TEST_P(WebGLGLSLTest, DefinedInMacroFails)
{
    constexpr char kVS[] = R"(precision mediump float;
attribute highp vec4 position;
varying float out0;

void main()
{
#define AAA defined(BBB)

#if !AAA
    out0 = 1.0;
#else
    out0 = 0.0;
#endif
    gl_Position = dEQP_Position;
})";

    constexpr char kFS[] = R"(precision mediump float;
varying float out0;
void main()
{
    gl_FragColor = vec4(out0, 0, 0, 1);
})";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_EQ(0u, program);
    glDeleteProgram(program);
}

// Simple test using a define macro in WebGL.
TEST_P(WebGLGLSLTest, DefinedGLESSymbol)
{
    constexpr char kVS[] = R"(void main()
{
    gl_Position = vec4(1, 0, 0, 1);
})";

    constexpr char kFS[] = R"(#if defined(GL_ES)
precision mediump float;
void main()
{
    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
}
#else
foo
#endif
)";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that clamp applied on non-literal indices is correct on es 100 shaders.
TEST_P(GLSLTest, ValidIndexClampES100)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform int u;
uniform mat4 m[2];
void main()
{
    gl_FragColor = vec4(m[u][1].xyz, 1);
}
)";

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

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

    GLint matrixLocation = glGetUniformLocation(program, "m");
    ASSERT_NE(matrixLocation, -1);
    const std::array<GLfloat, 32> mValue = {{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f,
                                             0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 0.5f,
                                             1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
                                             0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f}};
    glUniformMatrix4fv(matrixLocation, 2, false, mValue.data());

    glUniform1i(uniformLocation, 1);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that clamp applied on non-literal indices is correct on es 300 shaders.
TEST_P(GLSLTest_ES3, ValidIndexClampES300)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform int u;
mat4 m[4] = mat4[4](mat4(0.25), mat4(0.5), mat4(1), mat4(0.75));
void main()
{
    color = vec4(m[u][2].xyz, 1);
}
)";

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

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

    glUniform1i(uniformLocation, 2);
    EXPECT_GL_NO_ERROR();
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Tests constant folding of non-square 'matrixCompMult'.
TEST_P(GLSLTest_ES3, NonSquareMatrixCompMult)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;

const mat4x2 matA = mat4x2(2.0, 4.0, 8.0, 16.0, 32.0, 64.0, 128.0, 256.0);
const mat4x2 matB = mat4x2(1.0/2.0, 1.0/4.0, 1.0/8.0, 1.0/16.0, 1.0/32.0, 1.0/64.0, 1.0/128.0, 1.0/256.0);

out vec4 color;

void main()
{
    mat4x2 result = matrixCompMult(matA, matB);
    vec2 vresult = result * vec4(1.0, 1.0, 1.0, 1.0);
    if (vresult == vec2(4.0, 4.0))
    {
        color = vec4(0.0, 1.0, 0.0, 1.0);
    }
    else
    {
        color = vec4(1.0, 0.0, 0.0, 1.0);
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test initializing an array with the same name of previously declared array
TEST_P(GLSLTest_ES3, InitSameNameArray)
{
    constexpr char kFS[] = R"(#version 300 es
      precision highp float;
      out vec4 my_FragColor;

      void main()
      {
          float arr[2] = float[2](1.0, 1.0);
          {
              float arr[2] = arr;
              my_FragColor = vec4(0.0, arr[0], 0.0, arr[1]);
          }
      })";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests using gl_FragData[0] instead of gl_FragColor.
TEST_P(GLSLTest, FragData)
{
    constexpr char kFS[] = R"(void main() { gl_FragData[0] = vec4(1, 0, 0, 1); })";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Tests passing gl_FragData to function.
TEST_P(GLSLTest, FragDataPassedToFunction)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
precision mediump float;
vec4 f(vec4 fragData[gl_MaxDrawBuffers])
{
    vec4 original = fragData[1];
    fragData[1] = vec4(0, 1, 0, 0);
    return original + fragData[1];
}
void main()
{
    gl_FragData[1] = vec4(1, 0, 0, 1);
    gl_FragData[0] = f(gl_FragData);
})";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

// Tests passing gl_FragData to function without EXT_draw_buffers.
TEST_P(GLSLTest, FragDataPassedToFunctionNoDrawBuffers)
{
    constexpr char kFS[] = R"(precision mediump float;
vec4 f(vec4 fragData[gl_MaxDrawBuffers])
{
    return fragData[0] + vec4(0, 1, 0, 0);
}
void main()
{
    gl_FragData[0] = vec4(1, 0, 0, 1);
    gl_FragData[0] = f(gl_FragData);
})";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

// Tests passing gl_FragData to function as out parameter.
TEST_P(GLSLTest_ES3, FragDataPassedToFunctionOut)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
precision mediump float;
void f(out vec4 fragData[gl_MaxDrawBuffers])
{
    fragData[0] = vec4(1, 0, 0, 1);
    fragData[1] = vec4(0, 1, 0, 1);
    fragData[2] = vec4(0, 0, 1, 1);
    fragData[3] = vec4(1, 1, 0, 1);
}
void main()
{
    f(gl_FragData);
})";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);

    GLTexture textures[4];
    for (size_t texIndex = 0; texIndex < ArraySize(textures); texIndex++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + texIndex, GL_TEXTURE_2D,
                               textures[texIndex], 0);
    }

    GLint maxDrawBuffers;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ASSERT_GE(maxDrawBuffers, 4);

    constexpr GLenum kAllBufs[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1,
                                    GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3};
    glDrawBuffers(ArraySize(kAllBufs), kAllBufs);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    glReadBuffer(GL_COLOR_ATTACHMENT0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    glReadBuffer(GL_COLOR_ATTACHMENT1);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    glReadBuffer(GL_COLOR_ATTACHMENT2);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    glReadBuffer(GL_COLOR_ATTACHMENT3);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
    EXPECT_GL_NO_ERROR();
}

// Tests passing gl_FragData to function as out parameter without EXT_draw_buffers.
TEST_P(GLSLTest, FragDataPassedToFunctionOutNoDrawBuffers)
{
    constexpr char kFS[] = R"(precision mediump float;
void f(out vec4 fragData[gl_MaxDrawBuffers])
{
    fragData[0] = vec4(1, 0, 0, 1);
}
void main()
{
    f(gl_FragData);
})";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Tests using gl_FragData[0] instead of gl_FragColor with GL_SAMPLE_ALPHA_TO_COVERAGE
// Regression test for https://github.com/KhronosGroup/Vulkan-ValidationLayers/issues/5520
TEST_P(GLSLTest, FragData_AlphaToCoverage)
{
    constexpr char kFS[] = R"(void main() { gl_FragData[0] = vec4(1, 0, 0, 1); })";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glEnable(GL_SAMPLE_ALPHA_TO_COVERAGE);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that inactive samplers in structs don't cause any errors.
TEST_P(GLSLTest, InactiveSamplersInStruct)
{
    constexpr char kVS[] = R"(attribute vec4 a_position;
void main() {
  gl_Position = a_position;
})";

    constexpr char kFS[] = R"(precision highp float;
struct S
{
    vec4 v;
    sampler2D t[10];
};
uniform S s;
void main() {
  gl_FragColor = s.v;
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);

    drawQuad(program, "a_position", 0.5f);
}

// Helper functions for MixedRowAndColumnMajorMatrices* tests

// Round up to alignment, assuming it's a power of 2
uint32_t RoundUpPow2(uint32_t value, uint32_t alignment)
{
    return (value + alignment - 1) & ~(alignment - 1);
}

void CreateOutputBuffer(GLBuffer *buffer, uint32_t binding)
{
    unsigned int outputInitData = 0;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, *buffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), &outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, binding, *buffer);
    EXPECT_GL_NO_ERROR();
}

// Fill provided buffer with matrices based on the given dimensions.  The buffer should be large
// enough to accomodate the data.
uint32_t FillBuffer(const std::pair<uint32_t, uint32_t> matrixDims[],
                    const bool matrixIsColMajor[],
                    size_t matrixCount,
                    float data[],
                    bool isStd430,
                    bool isTransposed)
{
    size_t offset = 0;
    for (size_t m = 0; m < matrixCount; ++m)
    {
        uint32_t cols   = matrixDims[m].first;
        uint32_t rows   = matrixDims[m].second;
        bool isColMajor = matrixIsColMajor[m] != isTransposed;

        uint32_t arraySize              = isColMajor ? cols : rows;
        uint32_t arrayElementComponents = isColMajor ? rows : cols;
        // Note: stride is generally 4 with std140, except for scalar and gvec2 types (which
        // MixedRowAndColumnMajorMatrices* tests don't use).  With std430, small matrices can have
        // a stride of 2 between rows/columns.
        uint32_t stride = isStd430 ? RoundUpPow2(arrayElementComponents, 2) : 4;

        offset = RoundUpPow2(offset, stride);

        for (uint32_t i = 0; i < arraySize; ++i)
        {
            for (uint32_t c = 0; c < arrayElementComponents; ++c)
            {
                uint32_t row = isColMajor ? c : i;
                uint32_t col = isColMajor ? i : c;

                data[offset + i * stride + c] = col * 4 + row;
            }
        }

        offset += arraySize * stride;
    }
    return offset;
}

// Initialize and bind the buffer.
template <typename T>
void InitBuffer(GLuint program,
                const char *name,
                GLuint buffer,
                uint32_t bindingIndex,
                const T data[],
                uint32_t dataSize,
                bool isUniform)
{
    GLenum bindPoint = isUniform ? GL_UNIFORM_BUFFER : GL_SHADER_STORAGE_BUFFER;

    glBindBufferBase(bindPoint, bindingIndex, buffer);
    glBufferData(bindPoint, dataSize * sizeof(*data), data, GL_STATIC_DRAW);

    if (isUniform)
    {
        GLint blockIndex = glGetUniformBlockIndex(program, name);
        glUniformBlockBinding(program, blockIndex, bindingIndex);
    }
}

// Verify that buffer data is written by the shader as expected.
template <typename T>
bool VerifyBuffer(GLuint buffer, const T data[], uint32_t dataSize)
{
    uint32_t sizeInBytes = dataSize * sizeof(*data);

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer);

    const T *ptr = reinterpret_cast<const T *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeInBytes, GL_MAP_READ_BIT));

    bool isCorrect = memcmp(ptr, data, sizeInBytes) == 0;
    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);

    return isCorrect;
}

// Verify that the success output of the shader is as expected.
bool VerifySuccess(GLuint buffer)
{
    uint32_t success = 1;
    return VerifyBuffer(buffer, reinterpret_cast<const float *>(&success), 1);
}

// Test reading from UBOs and SSBOs and writing to SSBOs with mixed row- and colum-major layouts in
// both std140 and std430 layouts.  Tests many combinations of std140 vs std430, struct being used
// as row- or column-major in different UBOs, reading from UBOs and SSBOs and writing to SSBOs,
// nested structs, matrix arrays, inout parameters etc.
//
// Some very specific corner cases that are not covered here are tested in the subsequent tests.
TEST_P(GLSLTest_ES31, MixedRowAndColumnMajorMatrices)
{
    GLint maxComputeShaderStorageBlocks;
    glGetIntegerv(GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS, &maxComputeShaderStorageBlocks);

    // The test uses 9 SSBOs.  Skip if not that many are supported.
    ANGLE_SKIP_TEST_IF(maxComputeShaderStorageBlocks < 9);

    // Fails on Nvidia because having |Matrices| qualified as row-major in one UBO makes the other
    // UBO also see it as row-major despite explicit column-major qualifier.
    // http://anglebug.com/42262474
    ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL());

    // Fails on mesa because in the first UBO which is qualified as column-major, |Matrices| is
    // read column-major despite explicit row-major qualifier.  http://anglebug.com/42262481
    ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());

    // Fails on windows AMD on GL: http://anglebug.com/42262482
    ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());

    // Fails to compile the shader on Android.  http://anglebug.com/42262483
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGL());

    // Fails on assertion in translation to D3D.  http://anglebug.com/42262486
    ANGLE_SKIP_TEST_IF(IsD3D11());

    // Fails on SSBO validation on Android/Vulkan.  http://anglebug.com/42262485
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsVulkan());

    // Fails input verification as well as std140 SSBO validation.  http://anglebug.com/42262489
    ANGLE_SKIP_TEST_IF(IsWindows() && IsAMD() && IsVulkan());

    // Fails on ARM on Vulkan.  http://anglebug.com/42263107
    ANGLE_SKIP_TEST_IF(IsARM() && IsVulkan());

    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

struct Inner
{
    mat3x4 m3c4r;
    mat4x3 m4c3r;
};

struct Matrices
{
    mat2 m2c2r;
    mat2x3 m2c3r[2];
    mat3x2 m3c2r;
    Inner inner;
};

// For simplicity, the layouts are either of:
// - col-major mat4, row-major rest
// - row-major mat4, col-major rest
//
// The former is tagged with c, the latter with r.
layout(std140, column_major) uniform Ubo140c
{
    mat4 m4c4r;
    layout(row_major) Matrices m;
} ubo140cIn;

layout(std140, row_major) uniform Ubo140r
{
    mat4 m4c4r;
    layout(column_major) Matrices m;
} ubo140rIn;

layout(std140, row_major, binding = 0) buffer Ssbo140c
{
    layout(column_major) mat4 m4c4r;
    Matrices m;
} ssbo140cIn;

layout(std140, column_major, binding = 1) buffer Ssbo140r
{
    layout(row_major) mat4 m4c4r;
    Matrices m;
} ssbo140rIn;

layout(std430, column_major, binding = 2) buffer Ssbo430c
{
    mat4 m4c4r;
    layout(row_major) Matrices m;
} ssbo430cIn;

layout(std430, row_major, binding = 3) buffer Ssbo430r
{
    mat4 m4c4r;
    layout(column_major) Matrices m;
} ssbo430rIn;

layout(std140, row_major, binding = 4) buffer Ssbo140cOut
{
    layout(column_major) mat4 m4c4r;
    Matrices m;
} ssbo140cOut;

layout(std140, column_major, binding = 5) buffer Ssbo140rOut
{
    layout(row_major) mat4 m4c4r;
    Matrices m;
} ssbo140rOut;

layout(std430, column_major, binding = 6) buffer Ssbo430cOut
{
    mat4 m4c4r;
    layout(row_major) Matrices m;
} ssbo430cOut;

layout(std430, row_major, binding = 7) buffer Ssbo430rOut
{
    mat4 m4c4r;
    layout(column_major) Matrices m;
} ssbo430rOut;

layout(std140, binding = 8) buffer Result
{
    uint success;
} resultOut;

#define EXPECT(result, expression, value) if ((expression) != value) { result = false; }
#define EXPECTV(result, expression, value) if (any(notEqual(expression, value))) { result = false; }

#define VERIFY_IN(result, mat, cols, rows)                  \
    EXPECT(result, mat[0].x, 0.0);                          \
    EXPECT(result, mat[0][1], 1.0);                         \
    EXPECTV(result, mat[0].xy, vec2(0, 1));                 \
    EXPECTV(result, mat[1].xy, vec2(4, 5));                 \
    for (int c = 0; c < cols; ++c)                          \
    {                                                       \
        for (int r = 0; r < rows; ++r)                      \
        {                                                   \
            EXPECT(result, mat[c][r], float(c * 4 + r));    \
        }                                                   \
    }

#define COPY(matIn, matOut, cols, rows)     \
    matOut = matOut + matIn;                \
    /* random operations for testing */     \
    matOut[0].x += matIn[0].x + matIn[1].x; \
    matOut[0].x -= matIn[1].x;              \
    matOut[0][1] += matIn[0][1];            \
    matOut[1] += matIn[1];                  \
    matOut[1].xy -= matIn[1].xy;            \
    /* undo the above to get back matIn */  \
    matOut[0].x -= matIn[0].x;              \
    matOut[0][1] -= matIn[0][1];            \
    matOut[1] -= matIn[1];                  \
    matOut[1].xy += matIn[1].xy;

bool verifyMatrices(in Matrices m)
{
    bool result = true;
    VERIFY_IN(result, m.m2c2r, 2, 2);
    VERIFY_IN(result, m.m2c3r[0], 2, 3);
    VERIFY_IN(result, m.m2c3r[1], 2, 3);
    VERIFY_IN(result, m.m3c2r, 3, 2);
    VERIFY_IN(result, m.inner.m3c4r, 3, 4);
    VERIFY_IN(result, m.inner.m4c3r, 4, 3);
    return result;
}

mat4 copyMat4(in mat4 m)
{
    return m;
}

void copyMatrices(in Matrices mIn, inout Matrices mOut)
{
    COPY(mIn.m2c2r, mOut.m2c2r, 2, 2);
    COPY(mIn.m2c3r[0], mOut.m2c3r[0], 2, 3);
    COPY(mIn.m2c3r[1], mOut.m2c3r[1], 2, 3);
    COPY(mIn.m3c2r, mOut.m3c2r, 3, 2);
    COPY(mIn.inner.m3c4r, mOut.inner.m3c4r, 3, 4);
    COPY(mIn.inner.m4c3r, mOut.inner.m4c3r, 4, 3);
}

void main()
{
    bool result = true;

    VERIFY_IN(result, ubo140cIn.m4c4r, 4, 4);
    VERIFY_IN(result, ubo140cIn.m.m2c3r[0], 2, 3);
    EXPECT(result, verifyMatrices(ubo140cIn.m), true);

    VERIFY_IN(result, ubo140rIn.m4c4r, 4, 4);
    VERIFY_IN(result, ubo140rIn.m.m2c2r, 2, 2);
    EXPECT(result, verifyMatrices(ubo140rIn.m), true);

    VERIFY_IN(result, ssbo140cIn.m4c4r, 4, 4);
    VERIFY_IN(result, ssbo140cIn.m.m3c2r, 3, 2);
    EXPECT(result, verifyMatrices(ssbo140cIn.m), true);

    VERIFY_IN(result, ssbo140rIn.m4c4r, 4, 4);
    VERIFY_IN(result, ssbo140rIn.m.inner.m4c3r, 4, 3);
    EXPECT(result, verifyMatrices(ssbo140rIn.m), true);

    VERIFY_IN(result, ssbo430cIn.m4c4r, 4, 4);
    VERIFY_IN(result, ssbo430cIn.m.m2c3r[1], 2, 3);
    EXPECT(result, verifyMatrices(ssbo430cIn.m), true);

    VERIFY_IN(result, ssbo430rIn.m4c4r, 4, 4);
    VERIFY_IN(result, ssbo430rIn.m.inner.m3c4r, 3, 4);
    EXPECT(result, verifyMatrices(ssbo430rIn.m), true);

    // Only assign to SSBO from a single invocation.
    if (gl_GlobalInvocationID.x == 0u)
    {
        ssbo140cOut.m4c4r = copyMat4(ssbo140cIn.m4c4r);
        copyMatrices(ssbo430cIn.m, ssbo140cOut.m);
        ssbo140cOut.m.m2c3r[1] = mat2x3(0);
        COPY(ssbo430cIn.m.m2c3r[1], ssbo140cOut.m.m2c3r[1], 2, 3);

        ssbo140rOut.m4c4r = copyMat4(ssbo140rIn.m4c4r);
        copyMatrices(ssbo430rIn.m, ssbo140rOut.m);
        ssbo140rOut.m.inner.m3c4r = mat3x4(0);
        COPY(ssbo430rIn.m.inner.m3c4r, ssbo140rOut.m.inner.m3c4r, 3, 4);

        ssbo430cOut.m4c4r = copyMat4(ssbo430cIn.m4c4r);
        copyMatrices(ssbo140cIn.m, ssbo430cOut.m);
        ssbo430cOut.m.m3c2r = mat3x2(0);
        COPY(ssbo430cIn.m.m3c2r, ssbo430cOut.m.m3c2r, 3, 2);

        ssbo430rOut.m4c4r = copyMat4(ssbo430rIn.m4c4r);
        copyMatrices(ssbo140rIn.m, ssbo430rOut.m);
        ssbo430rOut.m.inner.m4c3r = mat4x3(0);
        COPY(ssbo430rIn.m.inner.m4c3r, ssbo430rOut.m.inner.m4c3r, 4, 3);

        resultOut.success = uint(result);
    }
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    constexpr size_t kMatrixCount                                     = 7;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4}, {2, 2}, {2, 3}, {2, 3}, {3, 2}, {3, 4}, {4, 3},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        true, false, false, false, false, false, false,
    };

    float dataStd140ColMajor[kMatrixCount * 4 * 4] = {};
    float dataStd140RowMajor[kMatrixCount * 4 * 4] = {};
    float dataStd430ColMajor[kMatrixCount * 4 * 4] = {};
    float dataStd430RowMajor[kMatrixCount * 4 * 4] = {};
    float dataZeros[kMatrixCount * 4 * 4]          = {};

    const uint32_t sizeStd140ColMajor =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, dataStd140ColMajor, false, false);
    const uint32_t sizeStd140RowMajor =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, dataStd140RowMajor, false, true);
    const uint32_t sizeStd430ColMajor =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, dataStd430ColMajor, true, false);
    const uint32_t sizeStd430RowMajor =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, dataStd430RowMajor, true, true);

    GLBuffer uboStd140ColMajor, uboStd140RowMajor;
    GLBuffer ssboStd140ColMajor, ssboStd140RowMajor;
    GLBuffer ssboStd430ColMajor, ssboStd430RowMajor;
    GLBuffer ssboStd140ColMajorOut, ssboStd140RowMajorOut;
    GLBuffer ssboStd430ColMajorOut, ssboStd430RowMajorOut;

    InitBuffer(program, "Ubo140c", uboStd140ColMajor, 0, dataStd140ColMajor, sizeStd140ColMajor,
               true);
    InitBuffer(program, "Ubo140r", uboStd140RowMajor, 1, dataStd140RowMajor, sizeStd140RowMajor,
               true);
    InitBuffer(program, "Ssbo140c", ssboStd140ColMajor, 0, dataStd140ColMajor, sizeStd140ColMajor,
               false);
    InitBuffer(program, "Ssbo140r", ssboStd140RowMajor, 1, dataStd140RowMajor, sizeStd140RowMajor,
               false);
    InitBuffer(program, "Ssbo430c", ssboStd430ColMajor, 2, dataStd430ColMajor, sizeStd430ColMajor,
               false);
    InitBuffer(program, "Ssbo430r", ssboStd430RowMajor, 3, dataStd430RowMajor, sizeStd430RowMajor,
               false);
    InitBuffer(program, "Ssbo140cOut", ssboStd140ColMajorOut, 4, dataZeros, sizeStd140ColMajor,
               false);
    InitBuffer(program, "Ssbo140rOut", ssboStd140RowMajorOut, 5, dataZeros, sizeStd140RowMajor,
               false);
    InitBuffer(program, "Ssbo430cOut", ssboStd430ColMajorOut, 6, dataZeros, sizeStd430ColMajor,
               false);
    InitBuffer(program, "Ssbo430rOut", ssboStd430RowMajorOut, 7, dataZeros, sizeStd430RowMajor,
               false);
    EXPECT_GL_NO_ERROR();

    GLBuffer outputBuffer;
    CreateOutputBuffer(&outputBuffer, 8);

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();
    EXPECT_TRUE(VerifySuccess(outputBuffer));

    EXPECT_TRUE(VerifyBuffer(ssboStd140ColMajorOut, dataStd140ColMajor, sizeStd140ColMajor));
    EXPECT_TRUE(VerifyBuffer(ssboStd140RowMajorOut, dataStd140RowMajor, sizeStd140RowMajor));
    EXPECT_TRUE(VerifyBuffer(ssboStd430ColMajorOut, dataStd430ColMajor, sizeStd430ColMajor));
    EXPECT_TRUE(VerifyBuffer(ssboStd430RowMajorOut, dataStd430RowMajor, sizeStd430RowMajor));
}

// Test that array UBOs are transformed correctly.
TEST_P(GLSLTest_ES3, RowMajorMatrix_ReadMat4Test)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, row_major) uniform Ubo
{
    mat4 m1;
};

void main()
{
    outColor = m1[3] / 255.0;
})";

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

    constexpr size_t kMatrixCount                                     = 1;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        false,
    };

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubos;

    InitBuffer(program, "Ubo", ubos, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 12, 13, 14, 15, 0);
}

// Test that array UBOs are transformed correctly.
TEST_P(GLSLTest_ES3, RowMajorMatrix_ReadMat2x3Test)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, row_major) uniform Ubo
{
    mat2x3 m1;
};

void main()
{
    outColor = vec4(m1[1], 0) / 255.0;
})";

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

    constexpr size_t kMatrixCount                                     = 1;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {2, 3},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        false,
    };

    float data[kMatrixCount * 3 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubos;

    InitBuffer(program, "Ubo", ubos, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 4, 5, 6, 0, 0);
}

TEST_P(GLSLTest_ES3, RowMajorMatrix_ReadMat3x2Test)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, row_major) uniform Ubo
{
    mat3x2 m1;
};

void main()
{
    outColor = vec4(m1[2], 0, 0) / 255.0;
})";

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

    constexpr size_t kMatrixCount                                     = 1;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {3, 2},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        false,
    };

    float data[kMatrixCount * 2 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubos;

    InitBuffer(program, "Ubo", ubos, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 8, 9, 0, 0, 0);
}

TEST_P(GLSLTest_ES3, RowMajorMatrix_NestedExpression)
{
    // Many OpenGL drivers seem to fail this
    ANGLE_SKIP_TEST_IF(IsLinux() && IsOpenGL());

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;

uniform Ubo {
  layout(row_major) mat4 u_mat[3];
  layout(row_major) mat4 u_ndx[3];
} stuff;

precision highp float;
out vec4 outColor;

void main() {
  outColor = stuff.u_mat[int(stuff.u_ndx[1][1][3])][2] / 255.0;
}
)";

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

    typedef float vec4[4];
    typedef vec4 mat4[4];

    constexpr size_t kMatrixCount = 6;
    mat4 data[]                   = {
        {
            {0, 1, 2, 3},      //
            {4, 5, 6, 7},      //
            {8, 9, 10, 11},    //
            {12, 13, 14, 15},  //
        },
        {
            //     +-- we should be looking up this column
            //     V
            {0, 4, 8, 12},   //
            {1, 5, 9, 13},   //
            {2, 6, 10, 14},  //
            {3, 7, 11, 15},  //
        },
        {
            {0, 2, 4, 6},      //
            {8, 10, 12, 14},   //
            {16, 18, 20, 22},  //
            {24, 26, 28, 30},  //
        },
        {
            {0, 0, 0, 0},  //
            {0, 0, 0, 0},  //
            {0, 0, 0, 0},  //
            {0, 0, 0, 0},  //
        },
        {
            {0, 0, 0, 0},  //
            {0, 0, 0, 2},  //
            {0, 0, 0, 0},  //
            {0, 1, 0, 0},
            //  ^
            //  +-- we should be using this element
        },
        {
            {0, 0, 0, 0},  //
            {0, 0, 0, 0},  //
            {0, 0, 0, 0},  //
            {0, 0, 0, 0},  //
        },
    };

    GLBuffer ubos;
    InitBuffer(program, "Ubo", ubos, 0, data, kMatrixCount, true);
    EXPECT_GL_NO_ERROR();

    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_NEAR(0, 0, 8, 9, 10, 11, 0);
}

// Test that array UBOs are transformed correctly.
TEST_P(GLSLTest_ES3, MixedRowAndColumnMajorMatrices_ArrayBufferDeclaration)
{
    // Fails to compile the shader on Android: http://anglebug.com/42262483
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGL());

    // http://anglebug.com/42262481
    ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());

    // Fails on windows AMD on GL: http://anglebug.com/42262482
    ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());

    // Fails on D3D due to mistranslation: http://anglebug.com/42262486
    ANGLE_SKIP_TEST_IF(IsD3D11());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2;
} ubo[3];

#define EXPECT(result, expression, value) if ((expression) != value) { result = false; }

#define VERIFY_IN(result, mat, cols, rows)                  \
    for (int c = 0; c < cols; ++c)                          \
    {                                                       \
        for (int r = 0; r < rows; ++r)                      \
        {                                                   \
            EXPECT(result, mat[c][r], float(c * 4 + r));    \
        }                                                   \
    }

void main()
{
    bool result = true;

    VERIFY_IN(result, ubo[0].m1, 4, 4);
    VERIFY_IN(result, ubo[0].m2, 4, 4);

    VERIFY_IN(result, ubo[1].m1, 4, 4);
    VERIFY_IN(result, ubo[1].m2, 4, 4);

    VERIFY_IN(result, ubo[2].m1, 4, 4);
    VERIFY_IN(result, ubo[2].m2, 4, 4);

    outColor = result ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    constexpr size_t kMatrixCount                                     = 2;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        true,
        false,
    };

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubos[3];

    InitBuffer(program, "Ubo[0]", ubos[0], 0, data, size, true);
    InitBuffer(program, "Ubo[1]", ubos[1], 0, data, size, true);
    InitBuffer(program, "Ubo[2]", ubos[2], 0, data, size, true);

    EXPECT_GL_NO_ERROR();

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

// Test that side effects when transforming read operations are preserved.
TEST_P(GLSLTest_ES3, MixedRowAndColumnMajorMatrices_ReadSideEffect)
{
    // Fails on D3D due to mistranslation: http://anglebug.com/42262486
    ANGLE_SKIP_TEST_IF(IsD3D11());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

struct S
{
    mat2x3 m2[3];
};

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) S s[2];
} ubo;

#define EXPECT(result, expression, value) if ((expression) != value) { result = false; }

#define VERIFY_IN(result, mat, cols, rows)                  \
    for (int c = 0; c < cols; ++c)                          \
    {                                                       \
        for (int r = 0; r < rows; ++r)                      \
        {                                                   \
            EXPECT(result, mat[c][r], float(c * 4 + r));    \
        }                                                   \
    }

bool verify2x3(mat2x3 mat)
{
    bool result = true;

    for (int c = 0; c < 2; ++c)
    {
        for (int r = 0; r < 3; ++r)
        {
            EXPECT(result, mat[c][r], float(c * 4 + r));
        }
    }

    return result;
}

void main()
{
    bool result = true;

    int sideEffect = 0;
    VERIFY_IN(result, ubo.m1, 4, 4);
    EXPECT(result, verify2x3(ubo.s[0].m2[0]), true);
    EXPECT(result, verify2x3(ubo.s[0].m2[sideEffect += 1]), true);
    EXPECT(result, verify2x3(ubo.s[0].m2[sideEffect += 1]), true);

    EXPECT(result, sideEffect, 2);

    EXPECT(result, verify2x3(ubo.s[sideEffect = 1].m2[0]), true);
    EXPECT(result, verify2x3(ubo.s[1].m2[(sideEffect = 4) - 3]), true);
    EXPECT(result, verify2x3(ubo.s[1].m2[sideEffect - 2]), true);

    EXPECT(result, sideEffect, 4);

    outColor = result ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    constexpr size_t kMatrixCount                                     = 7;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4}, {2, 3}, {2, 3}, {2, 3}, {2, 3}, {2, 3}, {2, 3},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        true, false, false, false, false, false, false,
    };

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo;
    InitBuffer(program, "Ubo", ubo, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

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

// Test that side effects respect the order of logical expression operands.
TEST_P(GLSLTest_ES3, MixedRowAndColumnMajorMatrices_ReadSideEffectOrder)
{
    // http://anglebug.com/42262481
    ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2[2];
} ubo;

void main()
{
    bool result = true;

    int x = 0;
    if (x == 0 && ubo.m2[x = 1][1][1] == 5.0)
    {
        result = true;
    }
    else
    {
        result = false;
    }

    outColor = result ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    constexpr size_t kMatrixCount                                     = 3;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
        {4, 4},
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {true, false, false};

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo;
    InitBuffer(program, "Ubo", ubo, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

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

TEST_P(GLSLTest_ES3, MixedRowAndColumnMajorMatrices_ReadSideEffectOrderSurroundedByLoop)
{
    // http://anglebug.com/42262481
    ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2[2];
} ubo;

void main()
{
    bool result = false;

    for(int x = 0; x < 1; ++x)
    {
        if (x == 0 && ubo.m2[x = 1][1][1] == 5.0) {
          result = true;
        }
    }
    outColor = result ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    constexpr size_t kMatrixCount                                     = 3;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
        {4, 4},
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {true, false, false};

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo;
    InitBuffer(program, "Ubo", ubo, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

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

TEST_P(GLSLTest_ES3, MixedRowAndColumnMajorMatrices_ReadSideEffectOrderInALoop)
{
    // http://anglebug.com/42262481
    ANGLE_SKIP_TEST_IF(IsLinux() && IsIntel() && IsOpenGL());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2[2];
} ubo;

void main()
{
    bool result = false;

    for(int x = 0; x == 0 && ubo.m2[x = 1][1][1] == 5.0;)
    {
        result = true;
    }
    outColor = result ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    constexpr size_t kMatrixCount                                     = 3;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
        {4, 4},
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {true, false, false};

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo;
    InitBuffer(program, "Ubo", ubo, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

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

// Test that side effects respect short-circuit.
TEST_P(GLSLTest_ES3, MixedRowAndColumnMajorMatrices_ReadSideEffectShortCircuit)
{
    // Fails on Android: http://anglebug.com/42262483
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGL());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 outColor;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2[2];
} ubo;

void main()
{
    bool result = true;

    int x = 0;
    if (x == 1 && ubo.m2[x = 1][1][1] == 5.0)
    {
        // First x == 1 should prevent the side effect of the second expression (x = 1) from
        // being executed.  If x = 1 is run before the if, the condition of the if would be true,
        // which is a failure.
        result = false;
    }
    if (x == 1)
    {
        result = false;
    }

    outColor = result ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    constexpr size_t kMatrixCount                                     = 3;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
        {4, 4},
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {true, false, false};

    float data[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo;
    InitBuffer(program, "Ubo", ubo, 0, data, size, true);

    EXPECT_GL_NO_ERROR();

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

// Test that indexing l-value swizzles work
TEST_P(GLSLTest_ES3, IndexingOfSwizzledLValuesShouldWork)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 oColor;

bool do_test() {
    highp vec3 expected = vec3(3.0, 2.0, 1.0);
    highp vec3 vec;

    vec.yzx[2] = 3.0;
    vec.yzx[1] = 1.0;
    vec.yzx[0] = 2.0;

    return vec == expected;
}

void main()
{
    oColor = vec4(do_test(), 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Test that indexing r-value swizzles work
TEST_P(GLSLTest_ES3, IndexingOfSwizzledRValuesShouldWork)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 oColor;

bool do_test() {
    highp vec3 expected = vec3(3.0, 2.0, 1.0);
    highp vec3 vecA = vec3(1.0, 3.0, 2.0);
    highp vec3 vecB;

    vecB.x = vecA.zxy[2];
    vecB.y = vecA.zxy[0];
    vecB.z = vecA.zxy[1];

    return vecB == expected;
}

void main()
{
    oColor = vec4(do_test(), 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Test that dynamic indexing of swizzled l-values should work.
// A simple porting of sdk/tests/conformance2/glsl3/vector-dynamic-indexing-swizzled-lvalue.html
TEST_P(GLSLTest_ES3, DynamicIndexingOfSwizzledLValuesShouldWork)
{
    // The shader first assigns v.x to v.z (1.0)
    // Then v.y to v.y (2.0)
    // Then v.z to v.x (1.0)
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
void main() {
    vec3 v = vec3(1.0, 2.0, 3.0);
    for (int i = 0; i < 3; i++) {
        v.zyx[i] = v[i];
    }
    my_FragColor = distance(v, vec3(1.0, 2.0, 1.0)) < 0.01 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    EXPECT_GL_NO_ERROR();
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Another test for dynamic indexing of swizzled l-values.
TEST_P(GLSLTest_ES3, DynamicIndexingOfSwizzledLValuesShouldWork2)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 oColor;

bool do_test() {
    highp vec3 expected = vec3(3.0, 2.0, 1.0);
    highp vec3 vec;

    for (int i = 0; i < 3; ++i)
    {
        vec.zyx[i] = float(1 + i);
    }

    return vec == expected;
}

void main()
{
    oColor = vec4(do_test(), 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    EXPECT_GL_NO_ERROR();
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that dead code after discard, return, continue and branch are pruned.
TEST_P(GLSLTest_ES3, DeadCodeIsPruned)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

vec4 f(vec4 c)
{
    return c;
    // dead code
    c = vec4(0, 0, 1, 1);
    return c;
}

void main()
{
    vec4 result = vec4(0, 0.5, 0, 1);
    int var = int(result.y * 2.2);

    {
        if (result.x > 1.0)
        {
            discard;
            // dead code
            result = vec4(1, 0, 0, 1);
        }
        for (int i = 0; i < 3; ++i)
        {
            if (i < 2)
            {
                result = f(result);
                continue;
                // dead code
                result = vec4(1, 0, 1, 1);
            }
            result = f(result);
            break;
            // dead code
            result = vec4(1, 0, 1, 0);
        }
        while (true)
        {
            if (result.x > -1.0)
            {
                {
                    result = f(result);
                    {
                        break;
                        // dead code
                        result = vec4(1, 0, 0, 0);
                    }
                    // dead code
                    for (int j = 0; j < 3; ++j)
                    {
                        if (j > 1) continue;
                        result = vec4(0, 0, 1, 0);
                        color = vec4(0.5, 0, 0.5, 0.5);
                        return;
                    }
                }
                // dead code
                result = vec4(0.5, 0, 0, 0);
            }
        }
        switch (var)
        {
        case 2:
            return;
            // dead code
            color = vec4(0.25, 0, 0.25, 0.25);
        case 1:
            {
                // Make sure this path is not pruned due to the return in the previous case.
                result.y += 0.5;
                break;
                // dead code
                color = vec4(0.25, 0, 0, 0);
            }
            // dead code
            color = vec4(0, 0, 0.25, 0);
            break;
        default:
            break;
        }

        color = result;
        return;
        // dead code
        color = vec4(0, 0, 0.5, 0);
    }
    // dead code
    color = vec4(0, 0, 0, 0.5);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test nested dead code with `if`
TEST_P(GLSLTest_ES3, NestedIfDeadCodeIsPruned)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    vec4 result = vec4(0, 0.5, 0, 1);

    for (int i = 0; i < 3; ++i)
    {
        if (i < 1)
        {
            result.x += 0.5;
            continue;
            // dead code
            if (result.y > 0.)
            {
                break;
                result.z += 0.5;
                // dead code
                if (false)
                {
                    result.w -= 0.5;
                }
            }
            // dead code
            if (result.x > 0.)
            {
                result.w -= 0.5;
            }
        }

        break;
        // dead code
        if (result.x > 0.)
        {
            return;
            if (result.y > 0.)
            {
                result.w -= 0.5;
            }
        }
    }

    color = result;
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
}

// Test that dead code elimination can handle ternary
TEST_P(GLSLTest_ES31, DeadCodeTernary)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

uniform int zero;
uniform int inactive1;
uniform int inactive2;
uniform int inactive3[3];
uniform struct Inactive {
    int v[5];
} inactive4;

void main()
{
    inactive1 == 0 ? inactive2 : inactive3[inactive2];

    inactive4.v[0] == 0
        ? inactive2 == 0
            ? float(inactive3[1]) + vec4(inactive4.v[1]).y
            : float(inactive1) * 3.0
        : inactive1 == 0
            ? inactive2 == 0
                ? sin(float(inactive1)) * faceforward(mat2(inactive2)[0], vec2(1), vec2(-1)).x
                : clamp(float(inactive3[1]), 0., 1.)
            : 1.;

    // The condition of a ternary is a ternary itself
    (inactive1 == 0 ? inactive2 == 0 : inactive3[0] == 0) ? inactive1 : 0;

    zero == 0 ? 1.0 : 0.0;

    color = zero != 0 ? vec4(1, 0, 0, 1) : vec4(0, 1, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

    GLint activeUniforms = 0;
    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms);
    EXPECT_GL_NO_ERROR();
    EXPECT_EQ(activeUniforms, 1);

    GLuint index = glGetProgramResourceIndex(program, GL_UNIFORM, "zero");
    EXPECT_NE(index, GL_INVALID_INDEX);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "inactive1");
    EXPECT_EQ(index, GL_INVALID_INDEX);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "inactive2");
    EXPECT_EQ(index, GL_INVALID_INDEX);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "inactive3");
    EXPECT_EQ(index, GL_INVALID_INDEX);

    index = glGetProgramResourceIndex(program, GL_UNIFORM, "inactive4");
    EXPECT_EQ(index, GL_INVALID_INDEX);
}

// Regression test based on fuzzer issue.  If a case has statements that are pruned, and those
// pruned statements in turn have branches, and another case follows, a prior implementation of
// dead-code elimination doubly pruned some statements.
TEST_P(GLSLTest_ES3, DeadCodeBranchInPrunedStatementsInCaseBeforeAnotherCase)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
void main()
{
    color = vec4(0, 1, 0, 1);
    switch(0)
    {
    case 0:
        break;
        break;
        color = vec4(1, 0, 0, 1);   // The bug was pruning this statement twice
    default:
        color = vec4(0, 0, 1, 1);
        break;
    }
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test dead code after constant folded control flow structures
TEST_P(GLSLTest_ES3, DeadCodeInConstFoldControlFlow)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

vec4 constantIf()
{
    vec4 ret = vec4(0, 0.1, 0, 0);
    if (true)
    {
        return ret;
    }
    ret = vec4(1, 0, 0, 0);
    return ret;
}

vec4 constantFor()
{
    vec4 ret = vec4(0.1, 0, 0, 0);
    for (int i = 0; i < 10; ++i)
    {
        ret.r += 0.1;
        break;
    }
    ret.a += 0.1;
    return ret;
}

vec4 constantWhileFalse()
{
    vec4 ret = vec4(0, 0, 0.1, 0);
    while (false)
    {
        ret.g = 1.0;
        return ret;
    }
    ret.b += 0.1;
    return ret;
}

vec4 constantWhileTrue()
{
    vec4 ret = vec4(0, 0.1, 0, 0);
    while (true)
    {
        ret.g += 0.3;
        break;
    }
    ret.g += 0.1;
    return ret;
}

vec4 constantDoWhileFalse()
{
    vec4 ret = vec4(0, 0, 0.1, 0);
    do
    {
        ret.b += 0.1;
        return ret;
    } while (false);

    ret.a = 1.;
    return ret;
}

vec4 constantDoWhileTrue()
{
    vec4 ret = vec4(0, 0, 0.1, 0);
    do
    {
        ret.b += 0.1;
        return ret;
    } while (true);

    ret.r = 1.;
    return ret;
}

vec4 constantSwitch()
{
    vec4 ret = vec4(0, 0, 0.1, 0);
    switch (4)
    {
    case 1:
        ret.r = 1.;
        break;
    case 3:
        ret.b = 1.;
        // fallthrough
    case 4:
        ret.a += 0.1;
        // fallthrough
    case 5:
        return ret;
    default:
        ret.g = 1.;
    }
    ret.r = 1.;
    return ret;
}

void main()
{
    vec4 result = constantIf();
    result += constantFor();
    result += constantWhileFalse();
    result += constantWhileTrue();
    result += constantDoWhileFalse();
    result += constantDoWhileTrue();
    result += constantSwitch();

    color = result;
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 153, 179, 51), 1);
}

// Test that blocks with only variable declarations inside are handled correctly
TEST_P(GLSLTest_ES3, BlocksWithOnlyVariableDeclaration)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform float zero;

float global = 1.0;
int iglobal = 100;

void main()
{
    float local = 0.5;
    int ilocal = 200;

    if (false)
    {
        float a = 0.;
        int b = 1;
    }

    if (zero < 1.)
    {
        float c = 0.;
        int d = 1;
    }

    for (int i = 0; i < 2; ++i)
    {
        break;
        // dead code
        if (zero < 1.)
        {
            float e = 0.;
            int f = 1;
        }
    }

    if (false)
    {
        float g = global + 1.;
    }
    if (zero < 1.)
    {
        float h = global + 1.;
    }

    if (false)
    {
        float i = local + 1.;
    }
    if (zero < 1.)
    {
        float j = local + 1.;
    }

    if (false)
    {
        int j = ++iglobal;
    }
    if (zero < 1.)
    {
        int k = ++iglobal;
    }

    if (false)
    {
        int l = ++ilocal;
    }
    if (zero < 1.)
    {
        int m = ++ilocal;
    }

    color = vec4(global, local, iglobal == 101 ? 0.5 : 0., ilocal == 201 ? 1.0 : 0.);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 127, 127, 255), 1);
}

// Test shader with all resources (default uniform, UBO, SSBO, image, sampler and atomic counter) to
// make sure they are all linked ok.  The front-end sorts these resources and traverses the list of
// "uniforms" to find the range for each resource.  A bug there was causing some resource ranges to
// be empty in the presence of other resources.
TEST_P(GLSLTest_ES31, MixOfAllResources)
{
    // http://anglebug.com/42263641
    ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());

    constexpr char kComputeShader[] = R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
layout(binding = 1, std430) buffer Output {
  uint ubo_value;
  uint default_value;
  uint sampler_value;
  uint ac_value;
  uint image_value;
} outbuf;
uniform Input {
  uint input_value;
} inbuf;
uniform uint default_uniform;
uniform sampler2D smplr;
layout(binding=0) uniform atomic_uint ac;
layout(r32ui) uniform highp readonly uimage2D image;

void main(void)
{
  outbuf.ubo_value = inbuf.input_value;
  outbuf.default_value = default_uniform;
  outbuf.sampler_value = uint(texture(smplr, vec2(0.5, 0.5)).x * 255.0);
  outbuf.ac_value = atomicCounterIncrement(ac);
  outbuf.image_value = imageLoad(image, ivec2(0, 0)).x;
}
)";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    unsigned int inputData = 89u;
    GLBuffer inputBuffer;
    glBindBuffer(GL_UNIFORM_BUFFER, inputBuffer);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(inputData), &inputData, GL_STATIC_DRAW);
    GLuint inputBufferIndex = glGetUniformBlockIndex(program, "Input");
    ASSERT_NE(inputBufferIndex, GL_INVALID_INDEX);
    glUniformBlockBinding(program, inputBufferIndex, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, inputBuffer);

    unsigned int outputInitData[5] = {0x12345678u, 0x09ABCDEFu, 0x56789ABCu, 0x0DEF1234u,
                                      0x13579BDFu};
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    unsigned int uniformData = 456u;
    GLint uniformLocation    = glGetUniformLocation(program, "default_uniform");
    ASSERT_NE(uniformLocation, -1);
    glUniform1ui(uniformLocation, uniformData);

    unsigned int acData = 2u;
    GLBuffer atomicCounterBuffer;
    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
    glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(acData), &acData, GL_STATIC_DRAW);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
    EXPECT_GL_NO_ERROR();

    unsigned int imageData = 33u;
    GLTexture image;
    glBindTexture(GL_TEXTURE_2D, image);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, &imageData);
    glBindImageTexture(0, image, 0, GL_FALSE, 0, GL_READ_ONLY, GL_R32UI);
    EXPECT_GL_NO_ERROR();

    GLColor textureData(127, 18, 189, 211);
    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &textureData);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(ptr[0], inputData);
    EXPECT_EQ(ptr[1], uniformData);
    EXPECT_NEAR(ptr[2], textureData.R, 1.0);
    EXPECT_EQ(ptr[3], acData);
    EXPECT_EQ(ptr[4], imageData);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that sending mixture of resources to functions works.
TEST_P(GLSLTest_ES31, MixOfResourcesAsFunctionArgs)
{
    // http://anglebug.com/42264082
    ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsOpenGL());

    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kComputeShader[] = R"(#version 310 es
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout(binding = 1, std430) buffer Output {
  uint success;
} outbuf;

uniform uint initialAcValue;
uniform sampler2D smplr[2][3];
layout(binding=0) uniform atomic_uint ac;

bool sampler1DAndAtomicCounter(uvec3 sExpect, in sampler2D s[3], in atomic_uint a, uint aExpect)
{
    uvec3 sResult = uvec3(uint(texture(s[0], vec2(0.5, 0.5)).x * 255.0),
                          uint(texture(s[1], vec2(0.5, 0.5)).x * 255.0),
                          uint(texture(s[2], vec2(0.5, 0.5)).x * 255.0));
    uint aResult = atomicCounterIncrement(a);

    return sExpect == sResult && aExpect == aResult;
}

bool sampler2DAndAtomicCounter(in sampler2D s[2][3], uint aInitial, in atomic_uint a)
{
    bool success = true;
    success = sampler1DAndAtomicCounter(uvec3(0, 127, 255), s[0], a, aInitial) && success;
    success = sampler1DAndAtomicCounter(uvec3(31, 63, 191), s[1], a, aInitial + 1u) && success;
    return success;
}

void main(void)
{
    outbuf.success = uint(sampler2DAndAtomicCounter(smplr, initialAcValue, ac));
}
)";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    unsigned int outputInitData = 0x12345678u;
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), &outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    unsigned int acData   = 2u;
    GLint uniformLocation = glGetUniformLocation(program, "initialAcValue");
    ASSERT_NE(uniformLocation, -1);
    glUniform1ui(uniformLocation, acData);

    GLBuffer atomicCounterBuffer;
    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
    glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(acData), &acData, GL_STATIC_DRAW);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
    EXPECT_GL_NO_ERROR();

    const std::array<GLColor, 6> kTextureData = {
        GLColor(0, 0, 0, 0),  GLColor(127, 0, 0, 0), GLColor(255, 0, 0, 0),
        GLColor(31, 0, 0, 0), GLColor(63, 0, 0, 0),  GLColor(191, 0, 0, 0),
    };
    GLTexture textures[2][3];

    for (int dim1 = 0; dim1 < 2; ++dim1)
    {
        for (int dim2 = 0; dim2 < 3; ++dim2)
        {
            int textureUnit = dim1 * 3 + dim2;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[dim1][dim2]);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         &kTextureData[textureUnit]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

            std::stringstream uniformName;
            uniformName << "smplr[" << dim1 << "][" << dim2 << "]";
            GLint samplerLocation = glGetUniformLocation(program, uniformName.str().c_str());
            EXPECT_NE(samplerLocation, -1);
            glUniform1i(samplerLocation, textureUnit);
        }
    }
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(ptr[0], 1u);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that array of array of samplers used as function parameter with an index that has a
// side-effect works.
TEST_P(GLSLTest_ES31, ArrayOfArrayOfSamplerAsFunctionParameterIndexedWithSideEffect)
{
    // http://anglebug.com/42264082
    ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsOpenGL());

    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    // Skip if EXT_gpu_shader5 is not enabled.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));

    constexpr char kComputeShader[] = R"(#version 310 es
#extension GL_EXT_gpu_shader5 : require

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout(binding = 1, std430) buffer Output {
  uint success;
} outbuf;

uniform sampler2D smplr[2][3];
layout(binding=0) uniform atomic_uint ac;

bool sampler1DAndAtomicCounter(uvec3 sExpect, in sampler2D s[3], in atomic_uint a, uint aExpect)
{
    uvec3 sResult = uvec3(uint(texture(s[0], vec2(0.5, 0.5)).x * 255.0),
                          uint(texture(s[1], vec2(0.5, 0.5)).x * 255.0),
                          uint(texture(s[2], vec2(0.5, 0.5)).x * 255.0));
    uint aResult = atomicCounter(a);

    return sExpect == sResult && aExpect == aResult;
}

bool sampler2DAndAtomicCounter(in sampler2D s[2][3], uint aInitial, in atomic_uint a)
{
    bool success = true;
    success = sampler1DAndAtomicCounter(uvec3(0, 127, 255),
                    s[atomicCounterIncrement(ac)], a, aInitial + 1u) && success;
    success = sampler1DAndAtomicCounter(uvec3(31, 63, 191),
                    s[atomicCounterIncrement(ac)], a, aInitial + 2u) && success;
    return success;
}

void main(void)
{
    outbuf.success = uint(sampler2DAndAtomicCounter(smplr, 0u, ac));
}
)";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    unsigned int outputInitData = 0x12345678u;
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), &outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    unsigned int acData = 0u;
    GLBuffer atomicCounterBuffer;
    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
    glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(acData), &acData, GL_STATIC_DRAW);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
    EXPECT_GL_NO_ERROR();

    const std::array<GLColor, 6> kTextureData = {
        GLColor(0, 0, 0, 0),  GLColor(127, 0, 0, 0), GLColor(255, 0, 0, 0),
        GLColor(31, 0, 0, 0), GLColor(63, 0, 0, 0),  GLColor(191, 0, 0, 0),
    };
    GLTexture textures[2][3];

    for (int dim1 = 0; dim1 < 2; ++dim1)
    {
        for (int dim2 = 0; dim2 < 3; ++dim2)
        {
            int textureUnit = dim1 * 3 + dim2;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[dim1][dim2]);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         &kTextureData[textureUnit]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

            std::stringstream uniformName;
            uniformName << "smplr[" << dim1 << "][" << dim2 << "]";
            GLint samplerLocation = glGetUniformLocation(program, uniformName.str().c_str());
            EXPECT_NE(samplerLocation, -1);
            glUniform1i(samplerLocation, textureUnit);
        }
    }
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(ptr[0], 1u);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

void GLSLTest_ES31::testArrayOfArrayOfSamplerDynamicIndex(const APIExtensionVersion usedExtension)
{
    ASSERT(usedExtension == APIExtensionVersion::EXT || usedExtension == APIExtensionVersion::OES);

    int maxTextureImageUnits = 0;
    glGetIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);
    ANGLE_SKIP_TEST_IF(maxTextureImageUnits < 24);

    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    // http://anglebug.com/42264082
    ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsOpenGL());

    std::string computeShader;
    constexpr char kGLSLVersion[]  = R"(#version 310 es
)";
    constexpr char kGPUShaderEXT[] = R"(#extension GL_EXT_gpu_shader5 : require
)";
    constexpr char kGPUShaderOES[] = R"(#extension GL_OES_gpu_shader5 : require
)";

    computeShader.append(kGLSLVersion);
    if (usedExtension == APIExtensionVersion::EXT)
    {
        computeShader.append(kGPUShaderEXT);
    }
    else
    {
        computeShader.append(kGPUShaderOES);
    }

    constexpr char kComputeShaderBody[] = R"(
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout(binding = 1, std430) buffer Output {
uint success;
} outbuf;

uniform sampler2D smplr[2][3][4];
layout(binding=0) uniform atomic_uint ac;

bool sampler1DAndAtomicCounter(uvec4 sExpect, in sampler2D s[4], in atomic_uint a, uint aExpect)
{
uvec4 sResult = uvec4(uint(texture(s[0], vec2(0.5, 0.5)).x * 255.0),
                      uint(texture(s[1], vec2(0.5, 0.5)).x * 255.0),
                      uint(texture(s[2], vec2(0.5, 0.5)).x * 255.0),
                      uint(texture(s[3], vec2(0.5, 0.5)).x * 255.0));
uint aResult = atomicCounter(a);

return sExpect == sResult && aExpect == aResult;
}

bool sampler3DAndAtomicCounter(in sampler2D s[2][3][4], uint aInitial, in atomic_uint a)
{
bool success = true;
// [0][0]
success = sampler1DAndAtomicCounter(uvec4(0, 8, 16, 24),
                s[atomicCounterIncrement(ac)][0], a, aInitial + 1u) && success;
// [1][0]
success = sampler1DAndAtomicCounter(uvec4(96, 104, 112, 120),
                s[atomicCounterIncrement(ac)][0], a, aInitial + 2u) && success;
// [0][1]
success = sampler1DAndAtomicCounter(uvec4(32, 40, 48, 56),
                s[0][atomicCounterIncrement(ac) - 1u], a, aInitial + 3u) && success;
// [0][2]
success = sampler1DAndAtomicCounter(uvec4(64, 72, 80, 88),
                s[0][atomicCounterIncrement(ac) - 1u], a, aInitial + 4u) && success;
// [1][1]
success = sampler1DAndAtomicCounter(uvec4(128, 136, 144, 152),
                s[1][atomicCounterIncrement(ac) - 3u], a, aInitial + 5u) && success;
// [1][2]
uint acValue = atomicCounterIncrement(ac);  // Returns 5
success = sampler1DAndAtomicCounter(uvec4(160, 168, 176, 184),
                s[acValue - 4u][atomicCounterIncrement(ac) - 4u], a, aInitial + 7u) && success;

return success;
}

void main(void)
{
outbuf.success = uint(sampler3DAndAtomicCounter(smplr, 0u, ac));
}
)";
    computeShader.append(kComputeShaderBody);

    ANGLE_GL_COMPUTE_PROGRAM(program, computeShader.c_str());
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    unsigned int outputInitData = 0x12345678u;
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), &outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    unsigned int acData = 0u;
    GLBuffer atomicCounterBuffer;
    glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, atomicCounterBuffer);
    glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(acData), &acData, GL_STATIC_DRAW);
    glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, atomicCounterBuffer);
    EXPECT_GL_NO_ERROR();

    const std::array<GLColor, 24> kTextureData = {
        GLColor(0, 0, 0, 0),   GLColor(8, 0, 0, 0),   GLColor(16, 0, 0, 0),  GLColor(24, 0, 0, 0),
        GLColor(32, 0, 0, 0),  GLColor(40, 0, 0, 0),  GLColor(48, 0, 0, 0),  GLColor(56, 0, 0, 0),
        GLColor(64, 0, 0, 0),  GLColor(72, 0, 0, 0),  GLColor(80, 0, 0, 0),  GLColor(88, 0, 0, 0),
        GLColor(96, 0, 0, 0),  GLColor(104, 0, 0, 0), GLColor(112, 0, 0, 0), GLColor(120, 0, 0, 0),
        GLColor(128, 0, 0, 0), GLColor(136, 0, 0, 0), GLColor(144, 0, 0, 0), GLColor(152, 0, 0, 0),
        GLColor(160, 0, 0, 0), GLColor(168, 0, 0, 0), GLColor(176, 0, 0, 0), GLColor(184, 0, 0, 0),
    };
    GLTexture textures[2][3][4];

    for (int dim1 = 0; dim1 < 2; ++dim1)
    {
        for (int dim2 = 0; dim2 < 3; ++dim2)
        {
            for (int dim3 = 0; dim3 < 4; ++dim3)
            {
                int textureUnit = (dim1 * 3 + dim2) * 4 + dim3;
                glActiveTexture(GL_TEXTURE0 + textureUnit);
                glBindTexture(GL_TEXTURE_2D, textures[dim1][dim2][dim3]);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                             &kTextureData[textureUnit]);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

                std::stringstream uniformName;
                uniformName << "smplr[" << dim1 << "][" << dim2 << "][" << dim3 << "]";
                GLint samplerLocation = glGetUniformLocation(program, uniformName.str().c_str());
                EXPECT_NE(samplerLocation, -1);
                glUniform1i(samplerLocation, textureUnit);
            }
        }
    }
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(ptr[0], 1u);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that array of array of samplers can be indexed correctly with dynamic indices.
TEST_P(GLSLTest_ES31, ArrayOfArrayOfSamplerDynamicIndexEXT)
{
    // Skip if EXT_gpu_shader5 is not enabled.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));
    testArrayOfArrayOfSamplerDynamicIndex(APIExtensionVersion::EXT);
}

// Test that array of array of samplers can be indexed correctly with dynamic indices.
TEST_P(GLSLTest_ES31, ArrayOfArrayOfSamplerDynamicIndexOES)
{
    // Skip if OES_gpu_shader5 is not enabled.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_gpu_shader5"));
    testArrayOfArrayOfSamplerDynamicIndex(APIExtensionVersion::OES);
}

// Test that array of array of samplers is handled correctly with the comma operator.
TEST_P(GLSLTest, ArrayOfArrayOfSamplerVsComma)
{
    int maxTextureImageUnits = 0;
    glGetIntegerv(GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);

    ANGLE_SKIP_TEST_IF(maxTextureImageUnits < 6);

    constexpr char kVS[] = R"(uniform struct {
  sampler2D s1, s2[3];
} s[2];

void main()
{
    ++gl_Position, s[1].s1;
})";
    ANGLE_GL_PROGRAM(program, kVS, essl1_shaders::fs::Red());
    EXPECT_GL_NO_ERROR();
}

// Test that array of array of samplers can be indexed correctly with dynamic indices.  Uses
// samplers in structs.
TEST_P(GLSLTest_ES31, ArrayOfArrayOfSamplerInStructDynamicIndex)
{
    // Skip if EXT_gpu_shader5 is not enabled.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));

    int maxTextureImageUnits = 0;
    glGetIntegerv(GL_MAX_COMPUTE_TEXTURE_IMAGE_UNITS, &maxTextureImageUnits);
    ANGLE_SKIP_TEST_IF(maxTextureImageUnits < 24);

    // http://anglebug.com/42263641
    ANGLE_SKIP_TEST_IF(IsIntel() && IsLinux() && IsOpenGL());

    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    // http://anglebug.com/42264082
    ANGLE_SKIP_TEST_IF(IsWindows() && IsIntel() && IsOpenGL());

    constexpr char kComputeShader[] = R"(#version 310 es
#extension GL_EXT_gpu_shader5 : require

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout(binding = 1, std430) buffer Output {
  uint success;
} outbuf;

struct I
{
    uint index;
};

struct S
{
    sampler2D smplr[4];
    I nested;
};

struct T
{
    S nested[3];
    uint tIndex;
};

uniform T u[2];

uint getValue(in sampler2D s)
{
    return uint(texture(s, vec2(0.5, 0.5)).x * 255.0);
}

bool sampler1DTest(uvec4 sExpect, in sampler2D s[4])
{
    uvec4 sResult = uvec4(getValue(s[0]), getValue(s[1]),
                          getValue(s[2]), getValue(s[3]));

    return sExpect == sResult;
}

bool samplerTest(T t, uint N)
{
    // u[N].tIndex == 0 + N*4
    // u[N].nested[0].nested.index == 1 + N*4
    // u[N].nested[1].nested.index == 2 + N*4
    // u[N].nested[2].nested.index == 3 + N*4

    uvec4 colorOffset = N * 3u * 4u * uvec4(8);

    bool success = true;
    // [N][0]
    success = sampler1DTest(uvec4(0, 8, 16, 24) + colorOffset,
                    t.nested[t.nested[0].nested.index - t.tIndex - 1u].smplr) && success;
    // [N][1]
    success = sampler1DTest(uvec4(32, 40, 48, 56) + colorOffset,
                    t.nested[t.nested[1].nested.index - t.tIndex - 1u].smplr) && success;
    // [N][2]
    success = sampler1DTest(uvec4(64, 72, 80, 88) + colorOffset,
                    t.nested[t.nested[2].nested.index - t.tIndex - 1u].smplr) && success;

    return success;
}

bool uniformTest(T t, uint N)
{
    // Also verify that expressions that involve structs-with-samplers are correct when not
    // referecing the sampler.

    bool success = true;
    success = (t.nested[0].nested.index - t.tIndex == 1u) && success;
    success = (t.nested[1].nested.index - t.tIndex == 2u) && success;
    success = (t.nested[2].nested.index - t.tIndex == 3u) && success;

    success = (t.nested[t.nested[0].nested.index - t.tIndex - 1u].nested.index - t.tIndex == 1u)
                && success;
    success = (t.nested[t.nested[0].nested.index - t.tIndex     ].nested.index - t.tIndex == 2u)
                && success;
    success = (t.nested[t.nested[0].nested.index - t.tIndex + 1u].nested.index - t.tIndex == 3u)
                && success;

    success = (t.nested[
                          t.nested[
                                     t.nested[2].nested.index - t.tIndex - 1u  // 2
                                  ].nested.index - t.tIndex - 2u               // 1
                       ].nested.index - t.tIndex                               // 2
                == 2u) && success;

    return success;
}

void main(void)
{
    bool success = samplerTest(u[0], 0u) && samplerTest(u[1], 1u)
                    && uniformTest(u[0], 0u) && uniformTest(u[1], 1u);
    outbuf.success = uint(success);
}
)";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    unsigned int outputInitData = 0x12345678u;
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), &outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    const std::array<GLColor, 24> kTextureData = {
        GLColor(0, 0, 0, 0),   GLColor(8, 0, 0, 0),   GLColor(16, 0, 0, 0),  GLColor(24, 0, 0, 0),
        GLColor(32, 0, 0, 0),  GLColor(40, 0, 0, 0),  GLColor(48, 0, 0, 0),  GLColor(56, 0, 0, 0),
        GLColor(64, 0, 0, 0),  GLColor(72, 0, 0, 0),  GLColor(80, 0, 0, 0),  GLColor(88, 0, 0, 0),
        GLColor(96, 0, 0, 0),  GLColor(104, 0, 0, 0), GLColor(112, 0, 0, 0), GLColor(120, 0, 0, 0),
        GLColor(128, 0, 0, 0), GLColor(136, 0, 0, 0), GLColor(144, 0, 0, 0), GLColor(152, 0, 0, 0),
        GLColor(160, 0, 0, 0), GLColor(168, 0, 0, 0), GLColor(176, 0, 0, 0), GLColor(184, 0, 0, 0),
    };
    GLTexture textures[2][3][4];

    for (int dim1 = 0; dim1 < 2; ++dim1)
    {
        for (int dim2 = 0; dim2 < 3; ++dim2)
        {
            for (int dim3 = 0; dim3 < 4; ++dim3)
            {
                int textureUnit = (dim1 * 3 + dim2) * 4 + dim3;
                glActiveTexture(GL_TEXTURE0 + textureUnit);
                glBindTexture(GL_TEXTURE_2D, textures[dim1][dim2][dim3]);
                glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                             &kTextureData[textureUnit]);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
                glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

                std::stringstream uniformName;
                uniformName << "u[" << dim1 << "].nested[" << dim2 << "].smplr[" << dim3 << "]";
                GLint samplerLocation = glGetUniformLocation(program, uniformName.str().c_str());
                EXPECT_NE(samplerLocation, -1);
                glUniform1i(samplerLocation, textureUnit);
            }

            std::stringstream uniformName;
            uniformName << "u[" << dim1 << "].nested[" << dim2 << "].nested.index";
            GLint nestedIndexLocation = glGetUniformLocation(program, uniformName.str().c_str());
            EXPECT_NE(nestedIndexLocation, -1);
            glUniform1ui(nestedIndexLocation, dim1 * 4 + dim2 + 1);
        }

        std::stringstream uniformName;
        uniformName << "u[" << dim1 << "].tIndex";
        GLint indexLocation = glGetUniformLocation(program, uniformName.str().c_str());
        EXPECT_NE(indexLocation, -1);
        glUniform1ui(indexLocation, dim1 * 4);
    }
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(ptr[0], 1u);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that array of array of samplers work when indexed with an expression that's derived from an
// array of array of samplers.
TEST_P(GLSLTest_ES31, ArrayOfArrayOfSamplerIndexedWithArrayOfArrayOfSamplers)
{
    // Skip if EXT_gpu_shader5 is not enabled.
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));

    // anglebug.com/42262476 - no sampler array params on Android
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsOpenGLES());

    constexpr char kComputeShader[] = R"(#version 310 es
#extension GL_EXT_gpu_shader5 : require

layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

layout(binding = 1, std430) buffer Output {
  uint success;
} outbuf;

uniform sampler2D smplr[2][3];

uint getValue(in sampler2D s)
{
    return uint(texture(s, vec2(0.5, 0.5)).x * 255.0);
}

bool runTest(in sampler2D s[2][3])
{
    // s[0][0] should contain 2
    // s[0][1] should contain 0
    // s[0][2] should contain 1
    // s[1][0] should contain 1
    // s[1][1] should contain 2
    // s[1][2] should contain 0

    uint result = getValue(
                       s[
                           getValue(
                                s[
                                    getValue(s[0][1])   // 0
                                ][
                                    getValue(s[0][0])   // 2
                                ]
                           )                      // s[0][2] -> 1
                       ][
                           getValue(
                                s[
                                    getValue(s[1][0])   // 1
                                ][
                                    getValue(s[1][1])   // 2
                                ]
                           )                      // s[1][2] -> 0
                       ]
                  );                      // s[1][0] -> 1

    return result == 1u;
}

void main(void)
{
    outbuf.success = uint(runTest(smplr));
}
)";
    ANGLE_GL_COMPUTE_PROGRAM(program, kComputeShader);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);

    unsigned int outputInitData = 0x12345678u;
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(outputInitData), &outputInitData, GL_STATIC_DRAW);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, outputBuffer);
    EXPECT_GL_NO_ERROR();

    const std::array<GLColor, 6> kTextureData = {
        GLColor(2, 0, 0, 0), GLColor(0, 0, 0, 0), GLColor(1, 0, 0, 0),
        GLColor(1, 0, 0, 0), GLColor(2, 0, 0, 0), GLColor(0, 0, 0, 0),
    };
    GLTexture textures[2][3];

    for (int dim1 = 0; dim1 < 2; ++dim1)
    {
        for (int dim2 = 0; dim2 < 3; ++dim2)
        {
            int textureUnit = dim1 * 3 + dim2;
            glActiveTexture(GL_TEXTURE0 + textureUnit);
            glBindTexture(GL_TEXTURE_2D, textures[dim1][dim2]);
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                         &kTextureData[textureUnit]);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);

            std::stringstream uniformName;
            uniformName << "smplr[" << dim1 << "][" << dim2 << "]";
            GLint samplerLocation = glGetUniformLocation(program, uniformName.str().c_str());
            EXPECT_NE(samplerLocation, -1);
            glUniform1i(samplerLocation, textureUnit);
        }
    }
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    // read back
    const GLuint *ptr = reinterpret_cast<const GLuint *>(
        glMapBufferRange(GL_SHADER_STORAGE_BUFFER, 0, sizeof(outputInitData), GL_MAP_READ_BIT));
    EXPECT_EQ(ptr[0], 1u);

    glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
}

// Test that multiple nested assignments are handled correctly.
TEST_P(GLSLTest_ES31, MixedRowAndColumnMajorMatrices_WriteSideEffect)
{
    // http://anglebug.com/42262475
    ANGLE_SKIP_TEST_IF(IsNVIDIA() && IsOpenGL());

    // Fails on windows AMD on GL: http://anglebug.com/42262482
    ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());
    // http://anglebug.com/42263924
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsDesktopOpenGL());

    // Fails on D3D due to mistranslation: http://anglebug.com/42262486
    ANGLE_SKIP_TEST_IF(IsD3D11());

    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2;
} ubo;

layout(std140, row_major, binding = 0) buffer Ssbo
{
    layout(column_major) mat4 m1;
    mat4 m2;
} ssbo;

layout(std140, binding = 1) buffer Result
{
    uint success;
} resultOut;

void main()
{
    bool result = true;

    // Only assign to SSBO from a single invocation.
    if (gl_GlobalInvocationID.x == 0u)
    {
        if ((ssbo.m2 = ssbo.m1 = ubo.m1) != ubo.m2)
        {
            result = false;
        }

        resultOut.success = uint(result);
    }
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    constexpr size_t kMatrixCount                                     = 2;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4},
        {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        true,
        false,
    };

    float data[kMatrixCount * 4 * 4]  = {};
    float zeros[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo, ssbo;

    InitBuffer(program, "Ubo", ubo, 0, data, size, true);
    InitBuffer(program, "Ssbo", ssbo, 0, zeros, size, false);
    EXPECT_GL_NO_ERROR();

    GLBuffer outputBuffer;
    CreateOutputBuffer(&outputBuffer, 1);

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();
    EXPECT_TRUE(VerifySuccess(outputBuffer));

    EXPECT_TRUE(VerifyBuffer(ssbo, data, size));
}

// Test that assignments to array of array of matrices are handled correctly.
TEST_P(GLSLTest_ES31, MixedRowAndColumnMajorMatrices_WriteArrayOfArray)
{
    // Fails on windows AMD on GL: http://anglebug.com/42262482
    ANGLE_SKIP_TEST_IF(IsWindows() && IsOpenGL() && IsAMD());
    // http://anglebug.com/42263924
    ANGLE_SKIP_TEST_IF(IsLinux() && IsAMD() && IsDesktopOpenGL());

    // Fails on D3D due to mistranslation: http://anglebug.com/42262486
    ANGLE_SKIP_TEST_IF(IsD3D11());

    // Fails compiling shader on Android/Vulkan.  http://anglebug.com/42262919
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsVulkan());

    // Fails on ARM on Vulkan.  http://anglebug.com/42263107
    ANGLE_SKIP_TEST_IF(IsARM() && IsVulkan());

    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

layout(std140, column_major) uniform Ubo
{
    mat4 m1;
    layout(row_major) mat4 m2[2][3];
} ubo;

layout(std140, row_major, binding = 0) buffer Ssbo
{
    layout(column_major) mat4 m1;
    mat4 m2[2][3];
} ssbo;

layout(std140, binding = 1) buffer Result
{
    uint success;
} resultOut;

void main()
{
    bool result = true;

    // Only assign to SSBO from a single invocation.
    if (gl_GlobalInvocationID.x == 0u)
    {
        ssbo.m1 = ubo.m1;
        ssbo.m2 = ubo.m2;

        resultOut.success = uint(result);
    }
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    constexpr size_t kMatrixCount                                     = 7;
    constexpr std::pair<uint32_t, uint32_t> kMatrixDims[kMatrixCount] = {
        {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4}, {4, 4},
    };
    constexpr bool kMatrixIsColMajor[kMatrixCount] = {
        true, false, false, false, false, false, false,
    };

    float data[kMatrixCount * 4 * 4]  = {};
    float zeros[kMatrixCount * 4 * 4] = {};

    const uint32_t size =
        FillBuffer(kMatrixDims, kMatrixIsColMajor, kMatrixCount, data, false, false);

    GLBuffer ubo, ssbo;

    InitBuffer(program, "Ubo", ubo, 0, data, size, true);
    InitBuffer(program, "Ssbo", ssbo, 0, zeros, size, false);
    EXPECT_GL_NO_ERROR();

    GLBuffer outputBuffer;
    CreateOutputBuffer(&outputBuffer, 1);

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();
    EXPECT_TRUE(VerifySuccess(outputBuffer));

    EXPECT_TRUE(VerifyBuffer(ssbo, data, size));
}

// Verify that types used differently (in different block storages, differently qualified etc) work
// when copied around.
TEST_P(GLSLTest_ES31, TypesUsedInDifferentBlockStorages)
{
    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

struct Inner
{
    mat3x2 m;
    float f[3];
    uvec2 u[2][4];
    ivec3 i;
    mat2x3 m2[3][2];
};

struct Outer
{
    Inner i[2];
};

layout(std140, column_major) uniform Ubo140c
{
    mat2 m;
    layout(row_major) Outer o;
} ubo140cIn;

layout(std430, row_major, binding = 0) buffer Ubo430r
{
    mat2 m;
    layout(column_major) Outer o;
} ubo430rIn;

layout(std140, column_major, binding = 1) buffer Ssbo140c
{
    layout(row_major) mat2 m[2];
    Outer o;
    layout(row_major) Inner i;
} ssbo140cOut;

layout(std430, row_major, binding = 2) buffer Ssbo430r
{
    layout(column_major) mat2 m[2];
    Outer o;
    layout(column_major) Inner i;
} ssbo430rOut;

void writeArgToStd140(uvec2 u[2][4], int innerIndex)
{
    ssbo140cOut.o.i[innerIndex].u = u;
}

void writeBlockArgToStd140(Inner i, int innerIndex)
{
    ssbo140cOut.o.i[innerIndex] = i;
}

mat2x3[3][2] readFromStd140(int innerIndex)
{
    return ubo140cIn.o.i[0].m2;
}

Inner readBlockFromStd430(int innerIndex)
{
    return ubo430rIn.o.i[innerIndex];
}

void copyFromStd140(out Inner i)
{
    i = ubo140cIn.o.i[1];
}

void main(){
    // Directly copy from one layout to another.
    ssbo140cOut.m[0] = ubo140cIn.m;
    ssbo140cOut.m[1] = ubo430rIn.m;
    ssbo140cOut.o.i[0].m = ubo140cIn.o.i[0].m;
    ssbo140cOut.o.i[0].f = ubo140cIn.o.i[0].f;
    ssbo140cOut.o.i[0].i = ubo140cIn.o.i[0].i;

    // Read from block and pass to function.
    writeArgToStd140(ubo140cIn.o.i[0].u, 0);
    writeBlockArgToStd140(ubo430rIn.o.i[0], 1);

    // Have function return value read from block.
    ssbo140cOut.o.i[0].m2 = readFromStd140(0);

    // Have function fill in value as out parameter.
    copyFromStd140(ssbo140cOut.i);

    // Initialize local variable.
    mat2 mStd140 = ubo140cIn.m;

    // Copy to variable, through multiple assignments.
    mat2 mStd430, temp;
    mStd430 = temp = ubo430rIn.m;

    // Copy from local variable
    ssbo430rOut.m[0] = mStd140;
    ssbo430rOut.m[1] = mStd430;

    // Construct from struct.
    Inner iStd140 = ubo140cIn.o.i[1];
    Outer oStd140 = Outer(Inner[2](iStd140, ubo430rIn.o.i[1]));

    // Copy struct from local variable.
    ssbo430rOut.o = oStd140;

    // Construct from arrays
    Inner iStd430 = Inner(ubo430rIn.o.i[1].m,
                          ubo430rIn.o.i[1].f,
                          ubo430rIn.o.i[1].u,
                          ubo430rIn.o.i[1].i,
                          ubo430rIn.o.i[1].m2);
    ssbo430rOut.i = iStd430;
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    // Test data, laid out with padding (0) based on std140/std430 rules.
    // clang-format off
    const std::vector<float> ubo140cData = {
        // m (mat2, column-major)
        1, 2, 0, 0,     3, 4, 0, 0,

        // o.i[0].m (mat3x2, row-major)
        5, 7, 9, 0,     6, 8, 10, 0,
        // o.i[0].f (float[3])
        12, 0, 0, 0,    13, 0, 0, 0,    14, 0, 0, 0,
        // o.i[0].u (uvec2[2][4])
        15, 16, 0, 0,   17, 18, 0, 0,   19, 20, 0, 0,   21, 22, 0, 0,
        23, 24, 0, 0,   25, 26, 0, 0,   27, 28, 0, 0,   29, 30, 0, 0,
        // o.i[0].i (ivec3)
        31, 32, 33, 0,
        // o.i[0].m2 (mat2x3[3][2], row-major)
        34, 37, 0, 0,   35, 38, 0, 0,   36, 39, 0, 0,
        40, 43, 0, 0,   41, 44, 0, 0,   42, 45, 0, 0,
        46, 49, 0, 0,   47, 50, 0, 0,   48, 51, 0, 0,
        52, 55, 0, 0,   53, 56, 0, 0,   54, 57, 0, 0,
        58, 61, 0, 0,   59, 62, 0, 0,   60, 63, 0, 0,
        64, 67, 0, 0,   65, 68, 0, 0,   66, 69, 0, 0,

        // o.i[1].m (mat3x2, row-major)
        70, 72, 74, 0,     71, 73, 75, 0,
        // o.i[1].f (float[3])
        77, 0, 0, 0,    78, 0, 0, 0,    79, 0, 0, 0,
        // o.i[1].u (uvec2[2][4])
        80, 81, 0, 0,   82, 83, 0, 0,   84, 85, 0, 0,   86, 87, 0, 0,
        88, 89, 0, 0,   90, 91, 0, 0,   92, 93, 0, 0,   94, 95, 0, 0,
        // o.i[1].i (ivec3)
        96, 97, 98, 0,
        // o.i[1].m2 (mat2x3[3][2], row-major)
         99, 102, 0, 0,  100, 103, 0, 0,   101, 104, 0, 0,
        105, 108, 0, 0,  106, 109, 0, 0,   107, 110, 0, 0,
        111, 114, 0, 0,  112, 115, 0, 0,   113, 116, 0, 0,
        117, 120, 0, 0,  118, 121, 0, 0,   119, 122, 0, 0,
        123, 126, 0, 0,  124, 127, 0, 0,   125, 128, 0, 0,
        129, 132, 0, 0,  130, 133, 0, 0,   131, 134, 0, 0,
    };
    const std::vector<float> ubo430rData = {
        // m (mat2, row-major)
        135, 137,         136, 138,

        // o.i[0].m (mat3x2, column-major)
        139, 140,         141, 142,         143, 144,
        // o.i[0].f (float[3])
        146, 147, 148, 0,
        // o.i[0].u (uvec2[2][4])
        149, 150,         151, 152,         153, 154,         155, 156,
        157, 158,         159, 160,         161, 162,         163, 164, 0, 0,
        // o.i[0].i (ivec3)
        165, 166, 167, 0,
        // o.i[0].m2 (mat2x3[3][2], column-major)
        168, 169, 170, 0,   171, 172, 173, 0,
        174, 175, 176, 0,   177, 178, 179, 0,
        180, 181, 182, 0,   183, 184, 185, 0,
        186, 187, 188, 0,   189, 190, 191, 0,
        192, 193, 194, 0,   195, 196, 197, 0,
        198, 199, 200, 0,   201, 202, 203, 0,

        // o.i[1].m (mat3x2, column-major)
        204, 205,         206, 207,         208, 209,
        // o.i[1].f (float[3])
        211, 212, 213, 0,
        // o.i[1].u (uvec2[2][4])
        214, 215,         216, 217,         218, 219,         220, 221,
        222, 223,         224, 225,         226, 227,         228, 229, 0, 0,
        // o.i[1].i (ivec3)
        230, 231, 232, 0,
        // o.i[1].m2 (mat2x3[3][2], column-major)
        233, 234, 235, 0,   236, 237, 238, 0,
        239, 240, 241, 0,   242, 243, 244, 0,
        245, 246, 247, 0,   248, 249, 250, 0,
        251, 252, 253, 0,   254, 255, 256, 0,
        257, 258, 259, 0,   260, 261, 262, 0,
        263, 264, 265, 0,   266, 267, 268, 0,
    };
    const std::vector<float> ssbo140cExpect = {
        // m (mat2[2], row-major), m[0] copied from ubo140cIn.m, m[1] from ubo430rIn.m
        1, 3, 0, 0,     2, 4, 0, 0,
        135, 137, 0, 0, 136, 138, 0, 0,

        // o.i[0].m (mat3x2, column-major), copied from ubo140cIn.o.i[0].m
        5, 6, 0, 0,     7, 8, 0, 0,     9, 10, 0, 0,
        // o.i[0].f (float[3]), copied from ubo140cIn.o.i[0].f
        12, 0, 0, 0,    13, 0, 0, 0,    14, 0, 0, 0,
        // o.i[0].u (uvec2[2][4]), copied from ubo140cIn.o.i[0].u
        15, 16, 0, 0,   17, 18, 0, 0,   19, 20, 0, 0,   21, 22, 0, 0,
        23, 24, 0, 0,   25, 26, 0, 0,   27, 28, 0, 0,   29, 30, 0, 0,
        // o.i[0].i (ivec3), copied from ubo140cIn.o.i[0].i
        31, 32, 33, 0,
        // o.i[0].m2 (mat2x3[3][2], column-major), copied from ubo140cIn.o.i[0].m2
        34, 35, 36, 0,  37, 38, 39, 0,
        40, 41, 42, 0,  43, 44, 45, 0,
        46, 47, 48, 0,  49, 50, 51, 0,
        52, 53, 54, 0,  55, 56, 57, 0,
        58, 59, 60, 0,  61, 62, 63, 0,
        64, 65, 66, 0,  67, 68, 69, 0,

        // o.i[1].m (mat3x2, column-major), copied from ubo430rIn.o.i[0].m
        139, 140, 0, 0,   141, 142, 0, 0,   143, 144, 0, 0,
        // o.i[1].f (float[3]), copied from ubo430rIn.o.i[0].f
        146, 0, 0, 0,     147, 0, 0, 0,     148, 0, 0, 0,
        // o.i[1].u (uvec2[2][4]), copied from ubo430rIn.o.i[0].u
        149, 150, 0, 0,   151, 152, 0, 0,   153, 154, 0, 0,   155, 156, 0, 0,
        157, 158, 0, 0,   159, 160, 0, 0,   161, 162, 0, 0,   163, 164, 0, 0,
        // o.i[1].i (ivec3), copied from ubo430rIn.o.i[0].i
        165, 166, 167, 0,
        // o.i[1].m2 (mat2x3[3][2], column-major), copied from ubo430rIn.o.i[0].m2
        168, 169, 170, 0,   171, 172, 173, 0,
        174, 175, 176, 0,   177, 178, 179, 0,
        180, 181, 182, 0,   183, 184, 185, 0,
        186, 187, 188, 0,   189, 190, 191, 0,
        192, 193, 194, 0,   195, 196, 197, 0,
        198, 199, 200, 0,   201, 202, 203, 0,

        // i.m (mat3x2, row-major), copied from ubo140cIn.o.i[1].m
        70, 72, 74, 0,     71, 73, 75, 0,
        // i.f (float[3]), copied from ubo140cIn.o.i[1].f
        77, 0, 0, 0,    78, 0, 0, 0,    79, 0, 0, 0,
        // i.u (uvec2[2][4]), copied from ubo430rIn.o.i[1].u
        80, 81, 0, 0,   82, 83, 0, 0,   84, 85, 0, 0,   86, 87, 0, 0,
        88, 89, 0, 0,   90, 91, 0, 0,   92, 93, 0, 0,   94, 95, 0, 0,
        // i.i (ivec3), copied from ubo140cIn.o.i[1].i
        96, 97, 98, 0,
        // i.m2 (mat2x3[3][2], row-major), copied from ubo140cIn.o.i[1].m2
         99, 102, 0, 0,  100, 103, 0, 0,   101, 104, 0, 0,
        105, 108, 0, 0,  106, 109, 0, 0,   107, 110, 0, 0,
        111, 114, 0, 0,  112, 115, 0, 0,   113, 116, 0, 0,
        117, 120, 0, 0,  118, 121, 0, 0,   119, 122, 0, 0,
        123, 126, 0, 0,  124, 127, 0, 0,   125, 128, 0, 0,
        129, 132, 0, 0,  130, 133, 0, 0,   131, 134, 0, 0,
    };
    const std::vector<float> ssbo430rExpect = {
        // m (mat2[2], column-major), m[0] copied from ubo140cIn.m, m[1] from ubo430rIn.m
        1, 2,           3, 4,
        135, 136,       137, 138,

        // o.i[0].m (mat3x2, row-major), copied from ubo140cIn.o.i[1].m
        70, 72, 74, 0,  71, 73, 75, 0,
        // o.i[0].f (float[3]), copied from ubo140cIn.o.i[1].f
        77, 78, 79, 0,
        // o.i[0].u (uvec2[2][4]), copied from ubo140cIn.o.i[1].u
        80, 81,         82, 83,         84, 85,         86, 87,
        88, 89,         90, 91,         92, 93,         94, 95,
        // o.i[0].i (ivec3), copied from ubo140cIn.o.i[1].i
        96, 97, 98, 0,
        // o.i[0].m2 (mat2x3[3][2], row-major), copied from ubo140cIn.o.i[1].m2
         99, 102,        100, 103,         101, 104,
        105, 108,        106, 109,         107, 110,
        111, 114,        112, 115,         113, 116,
        117, 120,        118, 121,         119, 122,
        123, 126,        124, 127,         125, 128,
        129, 132,        130, 133,         131, 134,

        // o.i[1].m (mat3x2, row-major), copied from ubo430rIn.o.i[1].m
        204, 206, 208, 0,  205, 207, 209, 0,
        // o.i[1].f (float[3]), copied from ubo430rIn.o.i[1].f
        211, 212, 213, 0,
        // o.i[1].u (uvec2[2][4]), copied from ubo430rIn.o.i[1].u
        214, 215,         216, 217,         218, 219,         220, 221,
        222, 223,         224, 225,         226, 227,         228, 229,
        // o.i[1].i (ivec3), copied from ubo430rIn.o.i[1].i
        230, 231, 232, 0,
        // o.i[1].m2 (mat2x3[3][2], row-major), copied from ubo430rIn.o.i[1].m2
        233, 236,         234, 237,         235, 238,
        239, 242,         240, 243,         241, 244,
        245, 248,         246, 249,         247, 250,
        251, 254,         252, 255,         253, 256,
        257, 260,         258, 261,         259, 262,
        263, 266,         264, 267,         265, 268,

        // i.m (mat3x2, column-major), copied from ubo430rIn.o.i[1].m
        204, 205,          206, 207,         208, 209,
        // i.f (float[3]), copied from ubo430rIn.o.i[1].f
        211, 212, 213, 0,
        // i.u (uvec2[2][4]), copied from ubo430rIn.o.i[1].u
        214, 215,         216, 217,         218, 219,         220, 221,
        222, 223,         224, 225,         226, 227,         228, 229, 0, 0,
        // i.i (ivec3), copied from ubo430rIn.o.i[1].i
        230, 231, 232, 0,
        // i.m2 (mat2x3[3][2], column-major), copied from ubo430rIn.o.i[1].m2
        233, 234, 235, 0,   236, 237, 238, 0,
        239, 240, 241, 0,   242, 243, 244, 0,
        245, 246, 247, 0,   248, 249, 250, 0,
        251, 252, 253, 0,   254, 255, 256, 0,
        257, 258, 259, 0,   260, 261, 262, 0,
        263, 264, 265, 0,   266, 267, 268, 0,
    };
    const std::vector<float> zeros(std::max(ssbo140cExpect.size(), ssbo430rExpect.size()), 0);
    // clang-format on

    GLBuffer uboStd140ColMajor, uboStd430RowMajor;
    GLBuffer ssboStd140ColMajor, ssboStd430RowMajor;

    InitBuffer(program, "Ubo140c", uboStd140ColMajor, 0, ubo140cData.data(),
               static_cast<uint32_t>(ubo140cData.size()), true);
    InitBuffer(program, "Ubo430r", uboStd430RowMajor, 0, ubo430rData.data(),
               static_cast<uint32_t>(ubo430rData.size()), false);
    InitBuffer(program, "Ssbo140c", ssboStd140ColMajor, 1, zeros.data(),
               static_cast<uint32_t>(ssbo140cExpect.size()), false);
    InitBuffer(program, "Ssbo430r", ssboStd430RowMajor, 2, zeros.data(),
               static_cast<uint32_t>(ssbo430rExpect.size()), false);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    EXPECT_TRUE(VerifyBuffer(ssboStd140ColMajor, ssbo140cExpect.data(),
                             static_cast<uint32_t>(ssbo140cExpect.size())));
    EXPECT_TRUE(VerifyBuffer(ssboStd430RowMajor, ssbo430rExpect.data(),
                             static_cast<uint32_t>(ssbo430rExpect.size())));
}

// Verify that bool in interface blocks work.
TEST_P(GLSLTest_ES31, BoolInInterfaceBlocks)
{
    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

struct Inner
{
    bool b;
    bvec2 b2;
    bvec3 b3;
    bvec4 b4;
    bool ba[5];
    bvec2 b2a[2][3];
};

struct Outer
{
    Inner i[2];
};

layout(std140) uniform Ubo140
{
    Outer o;
};

layout(std430, binding = 0) buffer Ubo430
{
    Outer o;
} ubo430In;

layout(std140, binding = 1) buffer Ssbo140
{
    bool valid;
    Inner i;
} ssbo140Out;

layout(std430, binding = 2) buffer Ssbo430
{
    bool valid;
    Inner i;
};

void writeArgToStd430(bool ba[5])
{
    i.ba = ba;
}

bool[5] readFromStd430(uint innerIndex)
{
    return ubo430In.o.i[innerIndex].ba;
}

void copyFromStd430(out bvec2 b2a[2][3])
{
    b2a = ubo430In.o.i[0].b2a;
}

bool destroyContent(inout Inner iOut)
{
    iOut.b = true;
    iOut.b2 = bvec2(true);
    iOut.b3 = bvec3(true);
    iOut.b4 = bvec4(true);
    iOut.ba = bool[5](true, true, true, true, true);
    bvec2 true3[3] = bvec2[3](iOut.b2, iOut.b2, iOut.b2);
    iOut.b2a = bvec2[2][3](true3, true3);
    return true;
}

void main(){
    // Directly copy from one layout to another.
    i.b = o.i[0].b;
    i.b2 = o.i[0].b2;
    i.b2a = o.i[0].b2a;

    // Copy to temp with swizzle.
    bvec4 t1 = o.i[0].b3.yxzy;
    bvec4 t2 = o.i[0].b4.xxyy;
    bvec4 t3 = o.i[0].b4.zzww;

    // Copy from temp with swizzle.
    i.b3 = t1.ywz;
    i.b4.yz = bvec2(t2.z, t3.y);
    i.b4.wx = bvec2(t3.w, t2.x);

    // Copy by passing argument to function.
    writeArgToStd430(o.i[0].ba);

    // Copy by return value.
    ssbo140Out.i.ba = readFromStd430(0u);

    // Copy by out parameter.
    copyFromStd430(ssbo140Out.i.b2a);

    // Logical operations
    uvec4 t4 = ubo430In.o.i[0].b ? uvec4(0) : uvec4(1);
    ssbo140Out.i.b = all(equal(t4, uvec4(1))) && (ubo430In.o.i[0].b ? false : true);
    ssbo140Out.i.b2 = not(ubo430In.o.i[0].b2);
    ssbo140Out.i.b3 = bvec3(all(ubo430In.o.i[0].b3), any(ubo430In.o.i[0].b3), any(ubo430In.o.i[0].b3.yx));
    ssbo140Out.i.b4 = equal(ubo430In.o.i[0].b4, bvec4(true, false, true, false));

    ssbo140Out.valid = true;
    ssbo140Out.valid = ssbo140Out.valid && all(equal(bvec3(o.i[1].b, o.i[1].b2), o.i[1].b3));
    ssbo140Out.valid = ssbo140Out.valid &&
            all(notEqual(o.i[1].b4, bvec4(o.i[1].ba[0], o.i[1].ba[1], o.i[1].ba[2], o.i[1].ba[3])));
    ssbo140Out.valid = ssbo140Out.valid && uint(o.i[1].ba[4]) == 1u;
    for (int x = 0; x < o.i[1].b2a.length(); ++x)
    {
        for (int y = 0; y < o.i[1].b2a[x].length(); ++y)
        {
            ssbo140Out.valid = ssbo140Out.valid && all(equal(uvec2(o.i[1].b2a[x][y]), uvec2(x % 2, y % 2)));
        }
    }

    valid = o.i[1] == ubo430In.o.i[1];

    // Make sure short-circuiting behavior is correct.
    bool falseVar = !valid && destroyContent(i);
    if (falseVar && destroyContent(ssbo140Out.i))
    {
        valid = false;
    }

    if (valid || o.i[uint((i.ba = bool[5](true, true, true, true, true))[1])].b)
    {
    }
    else
    {
        ssbo140Out.valid = false;
    }
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    // Test data, laid out with padding (0) based on std140/std430 rules.
    // clang-format off
    const std::vector<uint32_t> ubo140Data = {
        // o.i[0].b (bool)
        true, 0,
        // o.i[0].b2 (bvec2)
        true, false,
        // o.i[0].b3 (bvec3)
        true, true, false, 0,
        // o.i[0].b4 (bvec4)
        false, true, false, true,
        // o.i[0].ba (bool[5])
        true, 0, 0, 0,
        false, 0, 0, 0,
        false, 0, 0, 0,
        true, 0, 0, 0,
        true, 0, 0, 0,
        // o.i[0].b2a (bool[2][3])
        false, true, 0, 0,  true, true, 0, 0,    true, false, 0, 0,
        true, false, 0, 0,  false, false, 0, 0,  true, true, 0, 0,

        // o.i[1].b (bool)
        false, 0,
        // o.i[1].b2 (bvec2)
        true, true,
        // o.i[1].b3 (bvec3), expected to be equal to (b, b2)
        false, true, true, 0,
        // o.i[1].b4 (bvec4)
        true, false, true, true,
        // o.i[1].ba (bool[5]), expected to be equal to (not(b4), 1)
        false, 0, 0, 0,
        true, 0, 0, 0,
        false, 0, 0, 0,
        false, 0, 0, 0,
        true, 0, 0, 0,
        // o.i[1].b2a (bvec2[2][3]), [x][y] expected to equal (x%2,y%2)
        false, false, 0, 0,  false, true, 0, 0,  false, false, 0, 0,
        true, false, 0, 0,   true, true, 0, 0,   true, false, 0, 0,
    };
    const std::vector<uint32_t> ubo430Data = {
        // o.i[0].b (bool)
        false, 0,
        // o.i[0].b2 (bvec2)
        true, true,
        // o.i[0].b3 (bvec3)
        false, false, true, 0,
        // o.i[0].b4 (bvec4)
        true, false, true, true,
        // o.i[0].ba (bool[5])
        false, false, false, true, false, 0,
        // o.i[0].b2a (bool[2][3])
        true, false,  true, false,  true, true,
        false, true,  true, true,   false, false, 0, 0,

        // o.i[1] expected to be equal to ubo140In.o.i[1]
        // o.i[1].b (bool)
        false, 0,
        // o.i[1].b2 (bvec2)
        true, true,
        // o.i[1].b3 (bvec3)
        false, true, true, 0,
        // o.i[1].b4 (bvec4)
        true, false, true, true,
        // o.i[1].ba (bool[5])
        false, true, false, false, true, 0,
        // o.i[1].b2a (bvec2[2][3])
        false, false,  false, true,  false, false,
        true, false,   true, true,   true, false,
    };
    const std::vector<uint32_t> ssbo140Expect = {
        // valid, expected to be true
        true, 0, 0, 0,

        // i.b (bool), ubo430In.o.i[0].b ? false : true
        true, 0,
        // i.b2 (bvec2), not(ubo430In.o.i[0].b2)
        false, false,
        // i.b3 (bvec3), all(ubo430In.o.i[0].b3), any(...b3), any(...b3.yx)
        false, true, false, 0,
        // i.b4 (bvec4), ubo430In.o.i[0].b4 == (true, false, true, false)
        true, true, true, false,
        // i.ba (bool[5]), copied from ubo430In.o.i[0].ba
        false, 0, 0, 0,
        false, 0, 0, 0,
        false, 0, 0, 0,
        true, 0, 0, 0,
        false, 0, 0, 0,
        // i.b2a (bool[2][3]), copied from ubo430In.o.i[0].b2a
        true, false, 0, 0,  true, false, 0, 0,   true, true, 0, 0,
        false, true, 0, 0,  true, true, 0, 0,    false, false, 0, 0,
    };
    const std::vector<uint32_t> ssbo430Expect = {
        // valid, expected to be true
        true, 0, 0, 0,

        // o.i[0].b (bool), copied from (Ubo140::)o.i[0].b
        true, 0,
        // o.i[0].b2 (bvec2), copied from (Ubo140::)o.i[0].b2
        true, false,
        // o.i[0].b3 (bvec3), copied from (Ubo140::)o.i[0].b3
        true, true, false, 0,
        // o.i[0].b4 (bvec4), copied from (Ubo140::)o.i[0].b4
        false, true, false, true,
        // o.i[0].ba (bool[5]), copied from (Ubo140::)o.i[0].ba
        true, false, false, true, true, 0,
        // o.i[0].b2a (bool[2][3]), copied from (Ubo140::)o.i[0].b2a
        false, true,  true, true,    true, false,
        true, false,  false, false,  true, true, 0, 0,
    };
    const std::vector<uint32_t> zeros(std::max(ssbo140Expect.size(), ssbo430Expect.size()), 0);
    // clang-format on

    GLBuffer uboStd140, uboStd430;
    GLBuffer ssboStd140, ssboStd430;

    InitBuffer(program, "Ubo140", uboStd140, 0, ubo140Data.data(),
               static_cast<uint32_t>(ubo140Data.size()), true);
    InitBuffer(program, "Ubo430", uboStd430, 0, ubo430Data.data(),
               static_cast<uint32_t>(ubo430Data.size()), false);
    InitBuffer(program, "Ssbo140", ssboStd140, 1, zeros.data(),
               static_cast<uint32_t>(ssbo140Expect.size()), false);
    InitBuffer(program, "Ssbo430", ssboStd430, 2, zeros.data(),
               static_cast<uint32_t>(ssbo430Expect.size()), false);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    EXPECT_TRUE(VerifyBuffer(ssboStd140, ssbo140Expect.data(),
                             static_cast<uint32_t>(ssbo140Expect.size())));
    EXPECT_TRUE(VerifyBuffer(ssboStd430, ssbo430Expect.data(),
                             static_cast<uint32_t>(ssbo430Expect.size())));
}

// Verify that ternary operator works when the operands are matrices used in different block
// storage.
TEST_P(GLSLTest_ES31, TernaryOnMatricesInDifferentBlockStorages)
{
    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

layout(std140, column_major) uniform Ubo140c
{
    uint u;
    layout(row_major) mat3x2 m;
} ubo140cIn;

layout(std430, row_major, binding = 0) buffer Ubo430r
{
    uint u;
    layout(column_major) mat3x2 m;
} ubo430rIn;

layout(std140, column_major, binding = 1) buffer Ssbo140c
{
    uint u;
    mat3x2 m;
} ssbo140cIn;

layout(std430, row_major, binding = 2) buffer Ssbo430r
{
    mat3x2 m1;
    mat3x2 m2;
} ssbo430rOut;

void main(){
    ssbo430rOut.m1 = ubo140cIn.u > ubo430rIn.u ? ubo140cIn.m : ubo430rIn.m;
    ssbo430rOut.m2 = ssbo140cIn.u > ubo140cIn.u ? ssbo140cIn.m : ubo140cIn.m;

    mat3x2 m = mat3x2(0);

    ssbo430rOut.m1 = ubo140cIn.u == 0u ? m : ssbo430rOut.m1;
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    // Test data, laid out with padding (0) based on std140/std430 rules.
    // clang-format off
    const std::vector<float> ubo140cData = {
        // u (uint)
        1, 0, 0, 0,

        // m (mat3x2, row-major)
        5, 7, 9, 0,     6, 8, 10, 0,
    };
    const std::vector<float> ubo430rData = {
        // u (uint)
        135, 0,

        // m (mat3x2, column-major)
        139, 140,         141, 142,         143, 144,
    };
    const std::vector<float> ssbo140cData = {
        // u (uint)
        204, 0, 0, 0,

        // m (mat3x2, column-major)
        205, 206, 0, 0,  207, 208, 0, 0,  209, 210, 0, 0,
    };
    const std::vector<float> ssbo430rExpect = {
        // m1 (mat3x2, row-major), copied from ubo430rIn.m
        139, 141, 143, 0,  140, 142, 144, 0,

        // m2 (mat3x2, row-major), copied from ssbo140cIn.m
        205, 207, 209, 0,  206, 208, 210, 0,
    };
    const std::vector<float> zeros(ssbo430rExpect.size(), 0);
    // clang-format on

    GLBuffer uboStd140ColMajor, uboStd430RowMajor;
    GLBuffer ssboStd140ColMajor, ssboStd430RowMajor;

    InitBuffer(program, "Ubo140c", uboStd140ColMajor, 0, ubo140cData.data(),
               static_cast<uint32_t>(ubo140cData.size()), true);
    InitBuffer(program, "Ubo430r", uboStd430RowMajor, 0, ubo430rData.data(),
               static_cast<uint32_t>(ubo430rData.size()), false);
    InitBuffer(program, "Ssbo140c", ssboStd140ColMajor, 1, ssbo140cData.data(),
               static_cast<uint32_t>(ssbo140cData.size()), false);
    InitBuffer(program, "Ssbo430r", ssboStd430RowMajor, 2, zeros.data(),
               static_cast<uint32_t>(ssbo430rExpect.size()), false);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    EXPECT_TRUE(VerifyBuffer(ssboStd430RowMajor, ssbo430rExpect.data(),
                             static_cast<uint32_t>(ssbo430rExpect.size())));
}

// Verify that ternary operator works when the operands are structs used in different block
// storage.
TEST_P(GLSLTest_ES31, TernaryOnStructsInDifferentBlockStorages)
{
    constexpr char kCS[] = R"(#version 310 es
precision highp float;
layout(local_size_x=1) in;

struct S
{
    mat3x2 m[2];
};

layout(std140, column_major) uniform Ubo140c
{
    uint u;
    layout(row_major) S s;
} ubo140cIn;

layout(std430, row_major, binding = 0) buffer Ubo430r
{
    uint u;
    layout(column_major) S s;
} ubo430rIn;

layout(std140, column_major, binding = 1) buffer Ssbo140c
{
    uint u;
    S s;
} ssbo140cIn;

layout(std430, row_major, binding = 2) buffer Ssbo430r
{
    S s1;
    S s2;
} ssbo430rOut;

void main(){
    ssbo430rOut.s1 = ubo140cIn.u > ubo430rIn.u ? ubo140cIn.s : ubo430rIn.s;
    ssbo430rOut.s2 = ssbo140cIn.u > ubo140cIn.u ? ssbo140cIn.s : ubo140cIn.s;

    S s;
    s.m[0] = mat3x2(0);
    s.m[1] = mat3x2(0);

    ssbo430rOut.s1 = ubo140cIn.u == 0u ? s : ssbo430rOut.s1;
})";

    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    EXPECT_GL_NO_ERROR();

    // Test data, laid out with padding (0) based on std140/std430 rules.
    // clang-format off
    const std::vector<float> ubo140cData = {
        // u (uint)
        1, 0, 0, 0,

        // s.m[0] (mat3x2, row-major)
        5, 7, 9, 0,     6, 8, 10, 0,
        // s.m[1] (mat3x2, row-major)
        25, 27, 29, 0,  26, 28, 30, 0,
    };
    const std::vector<float> ubo430rData = {
        // u (uint)
        135, 0,

        // s.m[0] (mat3x2, column-major)
        139, 140,         141, 142,         143, 144,
        // s.m[1] (mat3x2, column-major)
        189, 190,         191, 192,         193, 194,
    };
    const std::vector<float> ssbo140cData = {
        // u (uint)
        204, 0, 0, 0,

        // s.m[0] (mat3x2, column-major)
        205, 206, 0, 0,  207, 208, 0, 0,  209, 210, 0, 0,
        // s.m[1] (mat3x2, column-major)
        245, 246, 0, 0,  247, 248, 0, 0,  249, 250, 0, 0,
    };
    const std::vector<float> ssbo430rExpect = {
        // s1.m[0] (mat3x2, row-major), copied from ubo430rIn.s.m[0]
        139, 141, 143, 0,  140, 142, 144, 0,
        // s1.m[1] (mat3x2, row-major), copied from ubo430rIn.s.m[0]
        189, 191, 193, 0,  190, 192, 194, 0,

        // s2.m[0] (mat3x2, row-major), copied from ssbo140cIn.m
        205, 207, 209, 0,  206, 208, 210, 0,
        // s2.m[1] (mat3x2, row-major), copied from ssbo140cIn.m
        245, 247, 249, 0,  246, 248, 250, 0,
    };
    const std::vector<float> zeros(ssbo430rExpect.size(), 0);
    // clang-format on

    GLBuffer uboStd140ColMajor, uboStd430RowMajor;
    GLBuffer ssboStd140ColMajor, ssboStd430RowMajor;

    InitBuffer(program, "Ubo140c", uboStd140ColMajor, 0, ubo140cData.data(),
               static_cast<uint32_t>(ubo140cData.size()), true);
    InitBuffer(program, "Ubo430r", uboStd430RowMajor, 0, ubo430rData.data(),
               static_cast<uint32_t>(ubo430rData.size()), false);
    InitBuffer(program, "Ssbo140c", ssboStd140ColMajor, 1, ssbo140cData.data(),
               static_cast<uint32_t>(ssbo140cData.size()), false);
    InitBuffer(program, "Ssbo430r", ssboStd430RowMajor, 2, zeros.data(),
               static_cast<uint32_t>(ssbo430rExpect.size()), false);
    EXPECT_GL_NO_ERROR();

    glUseProgram(program);
    glDispatchCompute(1, 1, 1);
    EXPECT_GL_NO_ERROR();

    EXPECT_TRUE(VerifyBuffer(ssboStd430RowMajor, ssbo430rExpect.data(),
                             static_cast<uint32_t>(ssbo430rExpect.size())));
}

// Verify that uint in interface block cast to bool works.
TEST_P(GLSLTest_ES3, UintCastToBoolFromInterfaceBlocks)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;

uniform uvec4 uv4;
uniform uvec2 uv2;
uniform uint u1;
uniform uint u2;

out vec4 colorOut;

void main()
{
    bvec4 bv4 = bvec4(uv4);
    bvec2 bv2 = bvec2(uv2);
    bool b1 = bool(u1);
    bool b2 = bool(u2);

    vec4 vv4 = mix(vec4(0), vec4(0.4), bv4);
    vec2 vv2 = mix(vec2(0), vec2(0.7), bv2);
    float v1 = b1 ? 1.0 : 0.0;
    float v2 = b2 ? 0.0 : 1.0;

    colorOut = vec4(vv4.x - vv4.y + vv4.z + vv4.w,
                        (vv2.y - vv2.x) * 1.5,
                        v1,
                        v2);
})";

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

    glUseProgram(program);
    GLint uv4 = glGetUniformLocation(program, "uv4");
    GLint uv2 = glGetUniformLocation(program, "uv2");
    GLint u1  = glGetUniformLocation(program, "u1");
    GLint u2  = glGetUniformLocation(program, "u2");
    ASSERT_NE(uv4, -1);
    ASSERT_NE(uv2, -1);
    ASSERT_NE(u1, -1);
    ASSERT_NE(u2, -1);

    glUniform4ui(uv4, 123, 0, 9, 8297312);
    glUniform2ui(uv2, 0, 90812);
    glUniform1ui(u1, 8979421);
    glUniform1ui(u2, 0);

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that the precise keyword is not reserved before ES3.1.
TEST_P(GLSLTest_ES3, PreciseNotReserved)
{
    // Skip in ES3.1+ as the precise keyword is reserved/core.
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() > 3 ||
                       (getClientMajorVersion() == 3 && getClientMinorVersion() >= 1));

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
in float precise;
out vec4 my_FragColor;
void main() { my_FragColor = vec4(precise, 0, 0, 1.0); })";

    constexpr char kVS[] = R"(#version 300 es
in vec4 a_position;
out float precise;
void main() { precise = a_position.x; gl_Position = a_position; })";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Test that the precise keyword is reserved on ES3.0 without GL_EXT_gpu_shader5.
TEST_P(GLSLTest_ES31, PreciseReservedWithoutExtension)
{
    // Skip if EXT_gpu_shader5 is enabled.
    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_EXT_gpu_shader5"));
    // Skip in ES3.2+ as the precise keyword is core.
    ANGLE_SKIP_TEST_IF(getClientMajorVersion() > 3 ||
                       (getClientMajorVersion() == 3 && getClientMinorVersion() >= 2));

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
in float v_varying;
out vec4 my_FragColor;
void main() { my_FragColor = vec4(v_varying, 0, 0, 1.0); })";

    constexpr char kVS[] = R"(#version 310 es
in vec4 a_position;
precise out float v_varying;
void main() { v_varying = a_position.x; gl_Position = a_position; })";

    // Should fail, as precise is a reserved keyword when the extension is not enabled.
    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_EQ(0u, program);
}

// Regression test for a bug with precise in combination with constructor, swizzle and dynamic
// index.
TEST_P(GLSLTest_ES31, PreciseVsVectorConstructorSwizzleAndIndex)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_gpu_shader5 : require

uniform highp float u;

void main()
{
    precise float p = vec4(u, u, u, u).xyz[int(u)];
    gl_Position = vec4(p);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 oColor;
void main()
{
    oColor = vec4(1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Regression test for a bug with precise in combination with matrix constructor and column index.
TEST_P(GLSLTest_ES31, PreciseVsMatrixConstructorAndIndex)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_gpu_shader5 : require

uniform highp vec4 u;

void main()
{
    precise vec4 p = mat4(u,vec4(0),vec4(0),vec4(0))[0];
    gl_Position = p;
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 oColor;
void main()
{
    oColor = vec4(1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Regression test for a bug with precise in combination with struct constructor and field
// selection.
TEST_P(GLSLTest_ES31, PreciseVsStructConstructorAndFieldSelection)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_gpu_shader5"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_gpu_shader5 : require

struct S
{
    float a;
    float b;
};

uniform highp float u;

void main()
{
    precise float p = S(u, u).b;
    gl_Position = vec4(p);
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 oColor;
void main()
{
    oColor = vec4(1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that reusing the same variable name for different uses across stages links fine.  The SPIR-V
// transformation should ignore all names for non-shader-interface variables and not get confused by
// them.
TEST_P(GLSLTest_ES31, VariableNameReuseAcrossStages)
{
    constexpr char kVS[] = R"(#version 310 es
precision mediump float;
uniform highp vec4 a;
in highp vec4 b;
in highp vec4 c;
in highp vec4 d;
out highp vec4 e;

vec4 f(vec4 a)
{
    return a;
}

vec4 g(vec4 f)
{
    return f + f;
}

void main() {
    e = f(b) + a;
    gl_Position = g(c) + f(d);
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
in highp vec4 e;
uniform sampler2D f;
layout(rgba8) uniform highp readonly image2D g;
uniform A
{
    vec4 x;
} c;
layout(std140, binding=0) buffer B
{
    vec4 x;
} d[2];
out vec4 col;

vec4 h(vec4 c)
{
    return texture(f, c.xy) + imageLoad(g, ivec2(c.zw));
}

vec4 i(vec4 x, vec4 y)
{
    return vec4(x.xy, y.zw);
}

void main() {
    col = h(e) + i(c.x, d[0].x) + d[1].x;
}
)";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Test that reusing the same uniform variable name for different uses across stages links fine.
TEST_P(GLSLTest_ES31, UniformVariableNameReuseAcrossStages)
{
    constexpr char kVS[] = R"(#version 310 es
precision mediump float;
in highp vec4 variableWithSameName;

void main() {
    gl_Position = variableWithSameName;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
uniform vec4 variableWithSameName;
out vec4 col;

void main() {
    col = vec4(variableWithSameName);
}
)";

    GLuint program = CompileProgram(kVS, kFS);
    EXPECT_NE(0u, program);
}

// Verify that precision match validation of uniforms is performed only if they are statically used
TEST_P(GLSLTest_ES31, UniformPrecisionMatchValidation)
{
    // Nvidia driver bug: http://anglebug.com/42263793
    ANGLE_SKIP_TEST_IF(IsOpenGL() && IsWindows() && IsNVIDIA());

    constexpr char kVSUnused[] = R"(#version 300 es
precision highp float;
uniform highp vec4 positionIn;

void main()
{
    gl_Position = vec4(1, 0, 0, 1);
})";

    constexpr char kVSStaticUse[] = R"(#version 300 es
precision highp float;
uniform highp vec4 positionIn;

void main()
{
    gl_Position = positionIn;
})";

    constexpr char kFSUnused[] = R"(#version 300 es
precision highp float;
uniform highp vec4 positionIn;
out vec4 my_FragColor;

void main()
{
    my_FragColor = vec4(1, 0, 0, 1);
})";

    constexpr char kFSStaticUse[] = R"(#version 300 es
precision highp float;
uniform mediump vec4 positionIn;
out vec4 my_FragColor;

void main()
{
    my_FragColor = vec4(1, 0, 0, positionIn.z);
})";

    GLuint program = 0;

    program = CompileProgram(kVSUnused, kFSUnused);
    EXPECT_NE(0u, program);

    program = CompileProgram(kVSUnused, kFSStaticUse);
    EXPECT_NE(0u, program);

    program = CompileProgram(kVSStaticUse, kFSUnused);
    EXPECT_NE(0u, program);

    program = CompileProgram(kVSStaticUse, kFSStaticUse);
    EXPECT_EQ(0u, program);
}

// Validate that link fails when two instanceless interface blocks with different block names but
// same field names are present.
TEST_P(GLSLTest_ES31, AmbiguousInstancelessInterfaceBlockFields)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
in highp vec4 position;
layout(binding = 0) buffer BlockA { mediump float a; };
void main()
{
    a = 0.0;
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
layout(location = 0) out mediump vec4 color;
uniform BlockB { float a; };
void main()
{
    color = vec4(a, a, a, 1.0);
})";

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

// Verify I/O block locations
TEST_P(GLSLTest_ES31, IOBlockLocations)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

in highp vec4 position;

layout(location = 0) out vec4 aOut;

layout(location = 6) out VSBlock
{
    vec4 b;     // location 6
    vec4 c;     // location 7
    layout(location = 1) vec4 d;
    vec4 e;     // location 2
    vec4 f[2];  // locations 3 and 4
} blockOut;

layout(location = 5) out vec4 gOut;

void main()
{
    aOut = vec4(0.03, 0.06, 0.09, 0.12);
    blockOut.b = vec4(0.15, 0.18, 0.21, 0.24);
    blockOut.c = vec4(0.27, 0.30, 0.33, 0.36);
    blockOut.d = vec4(0.39, 0.42, 0.45, 0.48);
    blockOut.e = vec4(0.51, 0.54, 0.57, 0.6);
    blockOut.f[0] = vec4(0.63, 0.66, 0.66, 0.69);
    blockOut.f[1] = vec4(0.72, 0.75, 0.78, 0.81);
    gOut = vec4(0.84, 0.87, 0.9, 0.93);
    gl_Position = position;
})";

    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;

// Input varyings
layout(location = 0) in vec4 aIn[];

layout(location = 6) in VSBlock
{
    vec4 b;
    vec4 c;
    layout(location = 1) vec4 d;
    vec4 e;
    vec4 f[2];
} blockIn[];

layout(location = 5) in vec4 gIn[];

// Output varyings
layout(location = 1) out vec4 aOut;

layout(location = 0) out GSBlock
{
    vec4 b;     // location 0
    layout(location = 3) vec4 c;
    layout(location = 7) vec4 d;
    layout(location = 5) vec4 e[2];
    layout(location = 4) vec4 f;
} blockOut;

layout(location = 2) out vec4 gOut;

void main()
{
    int n;
    for (n = 0; n < gl_in.length(); n++)
    {
        gl_Position = gl_in[n].gl_Position;

        aOut = aIn[n];
        blockOut.b = blockIn[n].b;
        blockOut.c = blockIn[n].c;
        blockOut.d = blockIn[n].d;
        blockOut.e[0] = blockIn[n].e;
        blockOut.e[1] = blockIn[n].f[0];
        blockOut.f = blockIn[n].f[1];
        gOut = gIn[n];

        EmitVertex();
    }
    EndPrimitive();
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

layout(location = 0) out mediump vec4 color;

layout(location = 1) in vec4 aIn;

layout(location = 0) in GSBlock
{
    vec4 b;
    layout(location = 3) vec4 c;
    layout(location = 7) vec4 d;
    layout(location = 5) vec4 e[2];
    layout(location = 4) vec4 f;
} blockIn;

layout(location = 2) in vec4 gIn;

bool isEq(vec4 a, vec4 b) { return all(lessThan(abs(a-b), vec4(0.001))); }

void main()
{
    bool passR = isEq(aIn, vec4(0.03, 0.06, 0.09, 0.12));
    bool passG = isEq(blockIn.b, vec4(0.15, 0.18, 0.21, 0.24)) &&
                 isEq(blockIn.c, vec4(0.27, 0.30, 0.33, 0.36)) &&
                 isEq(blockIn.d, vec4(0.39, 0.42, 0.45, 0.48)) &&
                 isEq(blockIn.e[0], vec4(0.51, 0.54, 0.57, 0.6)) &&
                 isEq(blockIn.e[1], vec4(0.63, 0.66, 0.66, 0.69)) &&
                 isEq(blockIn.f, vec4(0.72, 0.75, 0.78, 0.81));
    bool passB = isEq(gIn, vec4(0.84, 0.87, 0.9, 0.93));

    color = vec4(passR, passG, passB, 1.0);
})";

    ANGLE_GL_PROGRAM_WITH_GS(program, kVS, kGS, kFS);
    EXPECT_GL_NO_ERROR();

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

    drawQuad(program, "position", 0);

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

// Verify I/O block array locations
TEST_P(GLSLTest_ES31, IOBlockArrayLocations)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

in highp vec4 position;

layout(location = 0) out vec4 a;

// This should consume locations [3, 8]
layout(location = 3) out Block
{
    vec4 b;
    vec4 c;
} block[3];

layout(location = 1) out vec4 d[2];
layout(location = 9) out vec4 e[4];

void main()
{
    a = vec4(0.03, 0.06, 0.09, 0.12);
    block[0].b = vec4(0.15, 0.18, 0.21, 0.24);
    block[0].c = vec4(0.27, 0.30, 0.33, 0.36);
    block[1].b = vec4(0.39, 0.42, 0.45, 0.48);
    block[1].c = vec4(0.51, 0.54, 0.57, 0.6);
    block[2].b = vec4(0.63, 0.66, 0.66, 0.69);
    block[2].c = vec4(0.72, 0.75, 0.78, 0.81);
    d[0] = vec4(0.84, 0.87, 0.9, 0.93);
    d[1] = vec4(0.96, 0.99, 0.94, 0.89);
    e[0] = vec4(0.84, 0.79, 0.74, 0.69);
    e[1] = vec4(0.64, 0.59, 0.54, 0.49);
    e[2] = vec4(0.44, 0.39, 0.34, 0.29);
    e[3] = vec4(0.24, 0.19, 0.14, 0.09);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

layout(location = 0) out mediump vec4 color;

layout(location = 0) in vec4 a;

layout(location = 3) in Block
{
    vec4 b;
    vec4 c;
} block[3];

layout(location = 1) in vec4 d[2];
layout(location = 9) in vec4 e[4];

bool isEq(vec4 a, vec4 b) { return all(lessThan(abs(a-b), vec4(0.001))); }

void main()
{
    bool passR = isEq(a, vec4(0.03, 0.06, 0.09, 0.12));
    bool passG = isEq(block[0].b, vec4(0.15, 0.18, 0.21, 0.24)) &&
                 isEq(block[0].c, vec4(0.27, 0.30, 0.33, 0.36)) &&
                 isEq(block[1].b, vec4(0.39, 0.42, 0.45, 0.48)) &&
                 isEq(block[1].c, vec4(0.51, 0.54, 0.57, 0.6)) &&
                 isEq(block[2].b, vec4(0.63, 0.66, 0.66, 0.69)) &&
                 isEq(block[2].c, vec4(0.72, 0.75, 0.78, 0.81));
    bool passB = isEq(d[0], vec4(0.84, 0.87, 0.9, 0.93)) &&
                 isEq(d[1], vec4(0.96, 0.99, 0.94, 0.89));
    bool passA = isEq(e[0], vec4(0.84, 0.79, 0.74, 0.69)) &&
                 isEq(e[1], vec4(0.64, 0.59, 0.54, 0.49)) &&
                 isEq(e[2], vec4(0.44, 0.39, 0.34, 0.29)) &&
                 isEq(e[3], vec4(0.24, 0.19, 0.14, 0.09));

    color = vec4(passR, passG, passB, passA);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    EXPECT_GL_NO_ERROR();

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

    drawQuad(program, "position", 0);

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

// Verify I/O block locations match when one shader has implicit locations and another has explicit
// locations
TEST_P(GLSLTest_ES31, IOBlockLocationsImplicitVsExplicit)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

in highp vec4 position;

layout(location = 0) out vec4 aOut;

layout(location = 6) out VSBlock
{
    vec4 b;     // location 6
    layout(location = 1) vec4 c;
    vec4 d;     // location 2
    vec4 e[2];  // locations 3 and 4
    vec4 f;     // location 5
} blockOut;

void main()
{
    aOut = vec4(0.03, 0.06, 0.09, 0.12);
    blockOut.b = vec4(0.15, 0.18, 0.21, 0.24);
    blockOut.c = vec4(0.27, 0.30, 0.33, 0.36);
    blockOut.d = vec4(0.39, 0.42, 0.45, 0.48);
    blockOut.e[0] = vec4(0.51, 0.54, 0.57, 0.6);
    blockOut.e[1] = vec4(0.63, 0.66, 0.66, 0.69);
    blockOut.f = vec4(0.72, 0.75, 0.78, 0.81);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

layout(location = 0) out mediump vec4 color;

layout(location = 0) in vec4 aIn;

layout(location = 6) in VSBlock
{
    layout(location = 6) vec4 b;
    layout(location = 1) vec4 c;
    layout(location = 2) vec4 d;
    layout(location = 3) vec4 e[2];
    layout(location = 5) vec4 f;
} blockIn;

bool isEq(vec4 a, vec4 b) { return all(lessThan(abs(a-b), vec4(0.001))); }

void main()
{
    bool passR = isEq(aIn, vec4(0.03, 0.06, 0.09, 0.12));
    bool passG = isEq(blockIn.b, vec4(0.15, 0.18, 0.21, 0.24)) &&
                 isEq(blockIn.c, vec4(0.27, 0.30, 0.33, 0.36)) &&
                 isEq(blockIn.d, vec4(0.39, 0.42, 0.45, 0.48)) &&
                 isEq(blockIn.e[0], vec4(0.51, 0.54, 0.57, 0.6)) &&
                 isEq(blockIn.e[1], vec4(0.63, 0.66, 0.66, 0.69)) &&
                 isEq(blockIn.f, vec4(0.72, 0.75, 0.78, 0.81));

    color = vec4(passR, passG, 0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    EXPECT_GL_NO_ERROR();

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

    drawQuad(program, "position", 0);

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

// Test using builtins that can only be redefined with gl_PerVertex
TEST_P(GLSLTest_ES31, PerVertexRedefinition)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));

    constexpr char kVS[] = R"(#version 310 es
void main()
{
    gl_Position = vec4(1.0, 0.0, 0.0, 1.0);
})";

    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
#extension GL_EXT_clip_cull_distance : require

layout(lines_adjacency, invocations = 3) in;
layout(points, max_vertices = 16) out;

out gl_PerVertex {
    vec4 gl_Position;
    float gl_ClipDistance[4];
    float gl_CullDistance[4];
};

void main()
{
    for (int n = 0; n < 16; ++n)
    {
        gl_Position = vec4(n, 0.0, 0.0, 1.0);
        EmitVertex();
    }

    EndPrimitive();
})";

    constexpr char kFS[] = R"(#version 310 es
precision highp float;

out vec4 result;

void main()
{
    result = vec4(1.0);
})";

    ANGLE_GL_PROGRAM_WITH_GS(program, kVS, kGS, kFS);
    EXPECT_NE(0u, program);
    EXPECT_GL_NO_ERROR();
}

// Test pragma STDGL invariant all with I/O blocks
TEST_P(GLSLTest_ES31, IOBlockInvariantAll)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

#pragma STDGL invariant(all)

in highp vec4 position;

out VSBlock
{
    vec4 a;
    vec4 b[2];
} blockOut;

void main()
{
    blockOut.a = vec4(0.15, 0.18, 0.21, 0.24);
    blockOut.b[0] = vec4(0.27, 0.30, 0.33, 0.36);
    blockOut.b[1] = vec4(0.39, 0.42, 0.45, 0.48);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

layout(location = 0) out mediump vec4 color;

in VSBlock
{
    vec4 a;
    vec4 b[2];
} blockIn;

bool isEq(vec4 a, vec4 b) { return all(lessThan(abs(a-b), vec4(0.001))); }

void main()
{
    bool passR = isEq(blockIn.a, vec4(0.15, 0.18, 0.21, 0.24));
    bool passG = isEq(blockIn.b[0], vec4(0.27, 0.30, 0.33, 0.36)) &&
                 isEq(blockIn.b[1], vec4(0.39, 0.42, 0.45, 0.48));

    color = vec4(passR, passG, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    EXPECT_GL_NO_ERROR();

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

    drawQuad(program, "position", 0);

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

// Test varying packing in presence of multiple I/O blocks
TEST_P(GLSLTest_ES31, MultipleIOBlocks)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

in highp vec4 position;

out VSBlock1
{
    vec4 a;
    vec4 b[2];
} blockOut1;

out VSBlock2
{
    vec4 c[2];
    vec4 d;
} blockOut2;

void main()
{
    blockOut1.a = vec4(0.15, 0.18, 0.21, 0.24);
    blockOut1.b[0] = vec4(0.27, 0.30, 0.33, 0.36);
    blockOut1.b[1] = vec4(0.39, 0.42, 0.45, 0.48);
    blockOut2.c[0] = vec4(0.51, 0.54, 0.57, 0.6);
    blockOut2.c[1] = vec4(0.63, 0.66, 0.66, 0.69);
    blockOut2.d = vec4(0.72, 0.75, 0.78, 0.81);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

layout(location = 0) out mediump vec4 color;

in VSBlock1
{
    vec4 a;
    vec4 b[2];
} blockIn1;

in VSBlock2
{
    vec4 c[2];
    vec4 d;
} blockIn2;

bool isEq(vec4 a, vec4 b) { return all(lessThan(abs(a-b), vec4(0.001))); }

void main()
{
    bool passR = isEq(blockIn1.a, vec4(0.15, 0.18, 0.21, 0.24));
    bool passG = isEq(blockIn1.b[0], vec4(0.27, 0.30, 0.33, 0.36)) &&
                 isEq(blockIn1.b[1], vec4(0.39, 0.42, 0.45, 0.48));
    bool passB = isEq(blockIn2.c[0], vec4(0.51, 0.54, 0.57, 0.6)) &&
                 isEq(blockIn2.c[1], vec4(0.63, 0.66, 0.66, 0.69));
    bool passA = isEq(blockIn2.d, vec4(0.72, 0.75, 0.78, 0.81));

    color = vec4(passR, passG, passB, passA);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    EXPECT_GL_NO_ERROR();

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

    drawQuad(program, "position", 0);

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

// Test varying packing in presence of I/O block arrays
TEST_P(GLSLTest_ES31, IOBlockArray)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

in highp vec4 position;

out VSBlock1
{
    vec4 b[2];
} blockOut1[2];

out VSBlock2
{
    vec4 d;
} blockOut2[3];

void main()
{
    blockOut1[0].b[0] = vec4(0.15, 0.18, 0.21, 0.24);
    blockOut1[0].b[1] = vec4(0.27, 0.30, 0.33, 0.36);
    blockOut1[1].b[0] = vec4(0.39, 0.42, 0.45, 0.48);
    blockOut1[1].b[1] = vec4(0.51, 0.54, 0.57, 0.6);
    blockOut2[0].d = vec4(0.63, 0.66, 0.66, 0.69);
    blockOut2[1].d = vec4(0.72, 0.75, 0.78, 0.81);
    blockOut2[2].d = vec4(0.84, 0.87, 0.9, 0.93);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;

layout(location = 0) out mediump vec4 color;

in VSBlock1
{
    vec4 b[2];
} blockIn1[2];

in VSBlock2
{
    vec4 d;
} blockIn2[3];

bool isEq(vec4 a, vec4 b) { return all(lessThan(abs(a-b), vec4(0.001))); }

void main()
{
    bool passR = isEq(blockIn1[0].b[0], vec4(0.15, 0.18, 0.21, 0.24)) &&
                 isEq(blockIn1[0].b[1], vec4(0.27, 0.30, 0.33, 0.36));
    bool passG = isEq(blockIn1[1].b[0], vec4(0.39, 0.42, 0.45, 0.48)) &&
                 isEq(blockIn1[1].b[1], vec4(0.51, 0.54, 0.57, 0.6));
    bool passB = isEq(blockIn2[0].d, vec4(0.63, 0.66, 0.66, 0.69));
    bool passA = isEq(blockIn2[1].d, vec4(0.72, 0.75, 0.78, 0.81)) &&
                 isEq(blockIn2[2].d, vec4(0.84, 0.87, 0.9, 0.93));

    color = vec4(passR, passG, passB, passA);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    EXPECT_GL_NO_ERROR();

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 1, 1);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);

    drawQuad(program, "position", 0);

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

// Validate that link fails with I/O block member name mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberNameMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
out VSBlock { vec4 a; vec4 b[2]; } blockOut1;
void main()
{
    blockOut1.a = vec4(0);
    blockOut1.b[0] = vec4(0);
    blockOut1.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
in VSBlock { vec4 c; vec4 b[2]; } blockIn1;
void main()
{
    color = vec4(blockIn1.c.x, blockIn1.b[0].y, blockIn1.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member array size mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberArraySizeMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
out VSBlock { vec4 a; vec4 b[2]; } blockOut1;
void main()
{
    blockOut1.a = vec4(0);
    blockOut1.b[0] = vec4(0);
    blockOut1.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
in VSBlock { vec4 a; vec4 b[3]; } blockIn1;
void main()
{
    color = vec4(blockIn1.a.x, blockIn1.b[0].y, blockIn1.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member type mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberTypeMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
out VSBlock { vec4 a; vec4 b[2]; } blockOut1;
void main()
{
    blockOut1.a = vec4(0);
    blockOut1.b[0] = vec4(0);
    blockOut1.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
in VSBlock { vec3 a; vec4 b[2]; } blockIn1;
void main()
{
    color = vec4(blockIn1.a.x, blockIn1.b[0].y, blockIn1.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block location mismatches
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkLocationMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
layout(location = 2) out VSBlock { vec4 a; vec4 b[2]; } blockOut1;
void main()
{
    blockOut1.a = vec4(0);
    blockOut1.b[0] = vec4(0);
    blockOut1.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
layout(location = 1) in VSBlock { vec4 a; vec4 b[2]; } blockIn1;
void main()
{
    color = vec4(blockIn1.a.x, blockIn1.b[0].y, blockIn1.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member location mismatches
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberLocationMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
out VSBlock { vec4 a; layout(location = 2) vec4 b[2]; } blockOut1;
void main()
{
    blockOut1.a = vec4(0);
    blockOut1.b[0] = vec4(0);
    blockOut1.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
in VSBlock { vec4 a; layout(location = 3) vec4 b[2]; } blockIn1;
void main()
{
    color = vec4(blockIn1.a.x, blockIn1.b[0].y, blockIn1.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member struct name mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberStructNameMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
struct S1 { vec4 a; vec4 b[2]; };
out VSBlock { S1 s; } blockOut1;
void main()
{
    blockOut1.s.a = vec4(0);
    blockOut1.s.b[0] = vec4(0);
    blockOut1.s.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
struct S2 { vec4 a; vec4 b[2]; };
in VSBlock { S2 s; } blockIn1;
void main()
{
    color = vec4(blockIn1.s.a.x, blockIn1.s.b[0].y, blockIn1.s.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member struct member name mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberStructMemberNameMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
struct S { vec4 c; vec4 b[2]; };
out VSBlock { S s; } blockOut1;
void main()
{
    blockOut1.s.c = vec4(0);
    blockOut1.s.b[0] = vec4(0);
    blockOut1.s.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
struct S { vec4 a; vec4 b[2]; };
in VSBlock { S s; } blockIn1;
void main()
{
    color = vec4(blockIn1.s.a.x, blockIn1.s.b[0].y, blockIn1.s.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member struct member type mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberStructMemberTypeMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
struct S { vec4 a; vec4 b[2]; };
out VSBlock { S s; } blockOut1;
void main()
{
    blockOut1.s.a = vec4(0);
    blockOut1.s.b[0] = vec4(0);
    blockOut1.s.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
struct S { vec3 a; vec4 b[2]; };
in VSBlock { S s; } blockIn1;
void main()
{
    color = vec4(blockIn1.s.a.x, blockIn1.s.b[0].y, blockIn1.s.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member struct member array size mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberStructMemberArraySizeMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
struct S { vec4 a; vec4 b[3]; };
out VSBlock { S s; } blockOut1;
void main()
{
    blockOut1.s.a = vec4(0);
    blockOut1.s.b[0] = vec4(0);
    blockOut1.s.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
struct S { vec4 a; vec4 b[2]; };
in VSBlock { S s; } blockIn1;
void main()
{
    color = vec4(blockIn1.s.a.x, blockIn1.s.b[0].y, blockIn1.s.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member struct member count mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberStructMemberCountMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
struct S { vec4 a; vec4 b[2]; vec4 c; };
out VSBlock { S s; } blockOut1;
void main()
{
    blockOut1.s.c = vec4(0);
    blockOut1.s.b[0] = vec4(0);
    blockOut1.s.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
struct S { vec4 a; vec4 b[2]; };
in VSBlock { S s; } blockIn1;
void main()
{
    color = vec4(blockIn1.s.a.x, blockIn1.s.b[0].y, blockIn1.s.b[1].z, 1.0);
})";

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

// Validate that link fails with I/O block member nested struct mismatches.
TEST_P(GLSLTest_ES31, NegativeIOBlocksLinkMemberNestedStructMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in highp vec4 position;
struct S1 { vec4 c; vec4 b[2]; };
struct S2 { S1 s; };
struct S3 { S2 s; };
out VSBlock { S3 s; } blockOut1;
void main()
{
    blockOut1.s.s.s.c = vec4(0);
    blockOut1.s.s.s.b[0] = vec4(0);
    blockOut1.s.s.s.b[1] = vec4(0);
    gl_Position = position;
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
layout(location = 0) out mediump vec4 color;
struct S1 { vec4 a; vec4 b[2]; };
struct S2 { S1 s; };
struct S3 { S2 s; };
in VSBlock { S3 s; } blockIn1;
void main()
{
    color = vec4(blockIn1.s.s.s.a.x, blockIn1.s.s.s.b[0].y, blockIn1.s.s.s.b[1].z, 1.0);
})";

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

// Test separation of struct declarations, case where separated struct is used as a member of
// another struct.
TEST_P(GLSLTest, SeparateStructDeclaratorStructInStruct)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform vec4 u;
struct S1 { vec4 v; } a;
void main()
{
    struct S2 { S1 s1; } b;
    a.v = u;
    b.s1 = a;
    gl_FragColor = b.s1.v + vec4(0, 0, 0, 1);
}
)";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint u = glGetUniformLocation(program, "u");
    glUniform4f(u, 0, 1, 0, 0);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that separable program with multiple shaders with mismatching shader interface works.
TEST_P(GLSLTest_ES31, SeparableProgramWithMismatchingShaderInterface)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kVS[] =
        "#version 300 es\n"
        "in vec4 a_position;\n"
        "out float out_val;\n"
        "void main()\n"
        "{\n"
        "    gl_Position = a_position;\n"
        "}\n";

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 fragColor;\n"
        "void main()\n"
        "{\n"
        "    fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

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

    glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
    glLinkProgram(program);
    EXPECT_GL_NO_ERROR();

    GLuint pipeline = 0;
    glGenProgramPipelines(1, &pipeline);
    glBindProgramPipeline(pipeline);
    glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, program);
    glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, program);

    std::array<Vector3, 6> quadVertices = ANGLETestBase::GetQuadVertices();
    GLint positionLocation              = glGetAttribLocation(program, "a_position");
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glVertexAttribPointer(positionLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(positionLocation);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glDisableVertexAttribArray(positionLocation);
    glVertexAttribPointer(positionLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();

    glDetachShader(program, vs);
    glDetachShader(program, fs);
    glDeleteShader(vs);
    glDeleteShader(fs);
    glDeleteProgram(program);
    glDeleteProgramPipelines(1, &pipeline);

    ASSERT_GL_NO_ERROR();
}

// Test constructing matrix from vectors
TEST_P(GLSLTest, MatrixConstructorFromVectors)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform vec2 uvec2;
void main()
{
    mat2 m = mat2(uvec2, uvec2.yx);
    gl_FragColor = vec4(m * uvec2, uvec2);
})";

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

    GLint uloc = glGetUniformLocation(program, "uvec2");
    ASSERT_NE(uloc, -1);
    glUniform2f(uloc, 0.5, 0.8);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(227, 204, 127, 204), 1);
}

// Test that constructing vector and matrix inside multiple declarations preserves the correct order
// of operations.
TEST_P(GLSLTest, ConstructorinSequenceOperator)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform vec2 u;
void main()
{
    vec2 v = u;
    mat2 m = (v[0] += 1.0, mat2(v, v[1], -v[0]));
    gl_FragColor = vec4(m[0], m[1]);
})";

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

    GLint uloc = glGetUniformLocation(program, "u");
    ASSERT_NE(uloc, -1);
    glUniform2f(uloc, -0.5, 1.0);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 255, 255, 0), 1);
}

// Test that constructing vectors inside multiple declarations preserves the correct order
// of operations.
TEST_P(GLSLTest, VectorConstructorsInMultiDeclaration)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform vec2 u;
void main()
{
    vec2 v = vec2(u[0]),
         w = mat2(v, v) * u;
    gl_FragColor = vec4(v, w);
})";

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

    GLint uloc = glGetUniformLocation(program, "u");
    ASSERT_NE(uloc, -1);
    glUniform2f(uloc, 0.5, 0.8);

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 166, 166), 1);
}

// Test complex constructor usage.
TEST_P(GLSLTest_ES3, ComplexConstructor)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform vec2 u; // = vec2(0.5, 0.8)
uniform vec2 v; // = vec2(-0.2, 1.0)

out vec4 color;

bool f(mat2 m)
{
    return m[0][0] > 0.;
}

bool isEqual(float a, float b)
{
    return abs(a - b) < 0.01;
}

void main()
{
    int shouldRemainZero = 0;

    // Test side effects inside constructor args after short-circuit
    if (u.x < 0. && f(mat2(shouldRemainZero += 1, u, v)))
    {
        shouldRemainZero += 2;
    }

    int shouldBecomeFive = 0;

    // Test directly nested constructors
    mat4x3 m = mat4x3(mat2(shouldBecomeFive += 5, v, u));

    // Test indirectly nested constructors
    mat2 m2 = mat2(f(mat2(u, v)), f(mat2(v, u)), f(mat2(f(mat2(1.)))), -1.);

    // Verify
    bool sideEffectsOk = shouldRemainZero == 0 && shouldBecomeFive == 5;

    bool mOk = isEqual(m[0][0], 5.) && isEqual(m[0][1], -0.2) && isEqual(m[0][2], 0.) &&
               isEqual(m[1][0], 1.) && isEqual(m[1][1], 0.5) && isEqual(m[1][2], 0.) &&
               isEqual(m[2][0], 0.) && isEqual(m[2][1], 0.) && isEqual(m[2][2], 1.) &&
               isEqual(m[3][0], 0.) && isEqual(m[3][1], 0.) && isEqual(m[3][2], 0.);

    bool m2Ok = isEqual(m2[0][0], 1.) && isEqual(m2[0][1], 0.) &&
               isEqual(m2[1][0], 1.) && isEqual(m2[1][1], -1.);

    color = vec4(sideEffectsOk ? 1 : 0, mOk ? 1 : 0, m2Ok ? 1 : 0, 1);
})";

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

    GLint uloc = glGetUniformLocation(program, "u");
    GLint vloc = glGetUniformLocation(program, "v");
    ASSERT_NE(uloc, -1);
    ASSERT_NE(vloc, -1);
    glUniform2f(uloc, 0.5, 0.8);
    glUniform2f(vloc, -0.2, 1.0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that scalar(nonScalar) constructors work.
TEST_P(GLSLTest_ES3, ScalarConstructor)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform vec4 u;
out vec4 color;
void main()
{
    float f1 = float(u);
    mat3 m = mat3(u, u, u);
    int i = int(m);
    color = vec4(f1, float(i), 0, 1);
})";

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

    GLint uloc = glGetUniformLocation(program, "u");
    ASSERT_NE(uloc, -1);
    glUniform4f(uloc, 1.0, 0.4, 0.2, 0.7);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test when a constant constructor is nested inside a constructor of a different type, where the
// outer constructor itself is not a constant.
TEST_P(GLSLTest, ConstantConstructorNestedInConstructorOfDifferentType)
{
    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    float e = 1.;
    gl_FragColor.xyz = vec3(ivec2(1, 0),e);
    gl_FragColor.a = 1.;
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::magenta);
    ASSERT_GL_NO_ERROR();
}

// Test that initializing global variables with complex constants work
TEST_P(GLSLTest_ES3, InitGlobalComplexConstant)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

struct T
{
    float f;
};

struct S
{
    vec4 v;
    mat3x4 m[2];
    T t;
};

const float five = 5.;
const mat3x4 m = mat3x4(
                         vec4(T(4.).f, five, 6, 7),
                         vec4(8, 9, 10, 11),
                         vec4(12, 13, 14, 15)
                  );
S s = S(
        vec4(0, 1, 2, 3),
        mat3x4[2](
                  m,
                  mat3x4(
                         vec4(16, 17, 18, 19),
                         vec4(20, 21, 22, 23),
                         vec4(24, 25, 26, 27)
                  )
        ),
        T(28.0)
       );

void main()
{
    vec4 result = vec4(0, 1, 0, 1);

    if (s.v != vec4(0, 1, 2, 3))
        result = vec4(1, 0, 0, 0);

    for (int index = 0; index < 2; ++index)
    {
        for (int column = 0; column < 3; ++column)
        {
            int expect = index * 12 + column * 4 + 4;
            if (s.m[index][column] != vec4(expect, expect + 1, expect + 2, expect + 3))
                result = vec4(float(index + 1) / 2.0, 0, float(column + 1) / 3.0, 1);
        }
    }

    if (s.t.f != 28.0)
        result = vec4(0, 0, 1, 0);

    color = result;
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that initializing global variables with complex constants work when the constant is
// qualified with a `const`.
TEST_P(GLSLTest_ES3, InitGlobalComplexConstConstant)
{
    // The SPIR-V generator does not handle |s.m| correctly in the shader below where |s| is const,
    // expecting it to be constant folded but it isn't because it's an array type.
    //
    // The GLES output seems to generate an incorrect number of arguments to the constructor, but
    // somehow only the Qualcomm and Imagination drivers complain about it.
    //
    // On Metal, the test fails with GL_INVALID_OPERATION for some reason.
    //
    // There are no failures with the IR.
    ANGLE_SKIP_TEST_IF((IsVulkan() || IsOpenGLES() || IsMetal()) &&
                       !getEGLWindow()->isFeatureEnabled(Feature::UseIr));

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;

struct T
{
    float f;
};

struct S
{
    vec4 v;
    mat3x4 m[2];
    T t;
};

const float five = 5.;
const mat3x4 m = mat3x4(
                         vec4(T(4.).f, five, 6, 7),
                         vec4(8, 9, 10, 11),
                         vec4(12, 13, 14, 15)
                  );
const S s = S(
        vec4(0, 1, 2, 3),
        mat3x4[2](
                  m,
                  mat3x4(
                         vec4(16, 17, 18, 19),
                         vec4(20, 21, 22, 23),
                         vec4(24, 25, 26, 27)
                  )
        ),
        T(28.0)
       );

void main()
{
    vec4 result = vec4(0, 1, 0, 1);

    if (s.v != vec4(0, 1, 2, 3))
        result = vec4(1, 0, 0, 0);

    for (int index = 0; index < 2; ++index)
    {
        for (int column = 0; column < 3; ++column)
        {
            int expect = index * 12 + column * 4 + 4;
            if (s.m[index][column] != vec4(expect, expect + 1, expect + 2, expect + 3))
                result = vec4(float(index + 1) / 2.0, 0, float(column + 1) / 3.0, 1);
        }
    }

    if (s.t.f != 28.0)
        result = vec4(0, 0, 1, 0);

    color = result;
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that built-ins with out parameters work
TEST_P(GLSLTest_ES31, BuiltInsWithOutParameters)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
precision highp int;

out vec4 color;

uniform float f;    // = 3.41
uniform uvec4 u1;   // = 0xFEDCBA98, 0x13579BDF, 0xFEDCBA98, 0x13579BDF
uniform uvec4 u2;   // = 0xECA86420, 0x12345678, 0x12345678, 0xECA86420

struct S
{
    float fvalue;
    int ivalues[2];
    uvec4 uvalues[3];
};

struct T
{
    S s[2];
};

void main()
{
    float integer;
    float fraction = modf(f, integer);

    T t;

    t.s[0].fvalue     = frexp(f, t.s[0].ivalues[0]);
    float significand = t.s[0].fvalue;
    int exponent      = t.s[0].ivalues[0];

    t.s[0].uvalues[0] = uaddCarry(u1, u2, t.s[0].uvalues[1].yxwz);
    uvec4 addResult   = t.s[0].uvalues[0];
    uvec4 addCarry    = t.s[0].uvalues[1].yxwz;

    t.s[0].uvalues[2].wx = usubBorrow(u1.wx, u2.wx, t.s[1].uvalues[0].wx);
    uvec2 subResult      = t.s[0].uvalues[2].wx;
    uvec2 subBorrow      = t.s[1].uvalues[0].wx;

    umulExtended(u1, u2, t.s[1].uvalues[1], t.s[1].uvalues[2]);
    uvec4 mulMsb = t.s[1].uvalues[1];
    uvec4 mulLsb = t.s[1].uvalues[2];

    ivec2 imulMsb, imulLsb;
    imulExtended(ivec2(u1.wz), ivec2(u2.wz), imulMsb.yx, imulLsb.yx);

    bool modfPassed = abs(fraction - 0.41) < 0.0001 && integer == 3.0;
    bool frexpPassed = abs(significand - 0.8525) < 0.0001 && exponent == 2;
    bool addPassed =
        addResult == uvec4(0xEB851EB8, 0x258BF257, 0x11111110, 0xFFFFFFFF) &&
        addCarry == uvec4(1, 0, 1, 0);
    bool subPassed = subResult == uvec2(0x26AF37BF, 0x12345678) && subBorrow == uvec2(1, 0);
    bool mulPassed =
        mulMsb == uvec4(0xEB9B208C, 0x01601D49, 0x121FA00A, 0x11E17CC0) &&
        mulLsb == uvec4(0xA83AB300, 0xD6B9FA88, 0x35068740, 0x822E97E0);
    bool imulPassed =
        imulMsb == ivec2(0xFFEB4992, 0xFE89E0E1) &&
        imulLsb == ivec2(0x35068740, 0x822E97E0);

    color = vec4(modfPassed ? 1 : 0,
                 frexpPassed ? 1 : 0,
                 (addPassed ? 0.4 : 0.0) + (subPassed ? 0.6 : 0.0),
                 (mulPassed ? 0.4 : 0.0) + (imulPassed ? 0.6 : 0.0));
})";

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

    GLint floc  = glGetUniformLocation(program, "f");
    GLint u1loc = glGetUniformLocation(program, "u1");
    GLint u2loc = glGetUniformLocation(program, "u2");
    ASSERT_NE(floc, -1);
    ASSERT_NE(u1loc, -1);
    ASSERT_NE(u2loc, -1);
    glUniform1f(floc, 3.41);
    glUniform4ui(u1loc, 0xFEDCBA98u, 0x13579BDFu, 0xFEDCBA98u, 0x13579BDFu);
    glUniform4ui(u2loc, 0xECA86420u, 0x12345678u, 0x12345678u, 0xECA86420u);

    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that case fallthrough after diverging control flow works.
TEST_P(GLSLTest_ES3, CaseFallThroughAfterIf)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform int zero;

void main()
{
    int result = 0;
    switch (zero)
    {
        case 1:
            result += 100;
            // fallthrough
        default:
            result += 1000;
            if (result == 1000)
            {
                result += 10000;
            }
            // fallthrough
        case 0:
            ++result;
            if (result == 2)
            {
                result += 100000;
                break;
            }
            // fallthrough
        case 3:
            result += 10;
            if (result == 2)
            {
                result += 1000000;
            }
            break;
        case 5:
            result += 10000000;
    }

    color = result == 11 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test simple switch constant folding
TEST_P(GLSLTest_ES3, SwitchConstantFold)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform int zero;

void main()
{
    int result = 0;
    switch (10)
    {
    }

    switch (11)
    {
    case 11:
        result++;
    }

    switch (12)
    {
    default:
        result += 10;
    }

    switch (123)
    {
        case 1:
            result += 100;
            break;
        default:
            result += 10000;
            // fallthrough
        case 123:
            result += 2;
            // fallthrough
        case 3:
            result += 4;
            break;
        case 5:
            result += 100000;
    }

    // After dead code:
    while (true)
    {
        break;
        // dead code
        switch (123)
        {
            default:
                result = 123456;
                // fallthrough
            case 123:
                result = 234567;
                // fallthrough
            case 3:
                result = 345678;
                break;
        }
    }

    color = result == 17 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test switch constant folding with variable definition in unreachable cases.
TEST_P(GLSLTest_ES3, SwitchConstantFoldWithVariables)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform int zero;

void main()
{
    int result = 0;
    switch (10)
    {
    default:
        int a;
    }

    switch (11)
    {
    case 11:
        int b = result++;
    }

    switch (12)
    {
    default:
        int c = result += 10;
    }

    switch (123)
    {
        case 1:
            result += 100;
            break;
        default:
            result += 10000;
            int d = 31;
            // fallthrough
        case 123:
            result += 2 + d;
            // fallthrough
        case 3:
            result += 4;
            break;
        case 5:
            result += 100000;
    }

    color = result == 48 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test complex situations with switch constant folding
TEST_P(GLSLTest_ES3, SwitchConstantFoldComplex)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
uniform int zero;

void main()
{
    int result = 0;
    switch (123)
    {
        case 1:
            result += 100;
            if (false)
            {
                result += 1000;
            }
            break;
        default:
            result += 10000;
            if (zero == 0)
            {
                result += 100000;
            }
            // fallthrough
        case 123:
            ++result;
            if (result == 2)
            {
                result += 1000000;
                break;
            }
            // fallthrough
        case 3:
            result += 10;
            if (result == 2)
            {
                result += 10000000;
            }
            break;
        case 5:
            result += 100000000;
    }

    color = result == 11 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

class GLSLTestLoops : public GLSLTest
{
  protected:
    void runTest(const char *fs)
    {
        ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs);

        drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
        EXPECT_GL_NO_ERROR();

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

class GLSLTestLoops_ES31 : public GLSLTest_ES31
{};

// Test basic for loops
TEST_P(GLSLTestLoops, BasicFor)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; j < 8; ++j)
        {
            for (int k = 0; k < 2; ++k, ++j) ++result;
            for (int k = 0; k < 3; ++k)      ++result;
            for (int k = 0; k < 0; ++k)      ++result;
        }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with a continue expression that is void.
TEST_P(GLSLTestLoops, ForVoidContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void f()
{
}

void main()
{
    int result = 0;
    for (int i = 0; i < 10; f())
    {
        ++result;
        ++i;
    }

    color = result == 10 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop without condition
TEST_P(GLSLTestLoops, ForNoCondition)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; ; ++j)
        {
            for (int k = 0; k < 2; ++k, ++j) ++result;
            for (int k = 0; k < 3; ++k)      ++result;
            for (int k = 0; k < 0; ++k)      ++result;

            if (j >= 8)
                break;
        }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop without init and expression
TEST_P(GLSLTestLoops, ForNoInitConditionOrExpression)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
    {
        int j = 0;
        for (;;)
        {
            for (int k = 0; k < 2; ++k, ++j) ++result;
            for (int k = 0; k < 3; ++k)      ++result;
            for (int k = 0; k < 0; ++k)      ++result;

            if (j >= 8)
                break;
            ++j;
        }
    }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with continue
TEST_P(GLSLTestLoops, ForContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; j < 8; ++j)
        {
            for (int k = 0; k < 2; ++k, ++j) ++result;
            for (int k = 0; k < 3; ++k)      ++result;
            if (i > 3)
                continue;
            for (int k = 0; k < 0; ++k)      ++result;
        }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with continue at the end of block
TEST_P(GLSLTestLoops, ForUnconditionalContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; j < 8; ++j)
        {
            for (int k = 0; k < 2; ++k, ++j) ++result;
            for (int k = 0; k < 3; ++k)      ++result;
            for (int k = 0; k < 0; ++k)      ++result;
            continue;
        }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with break at the end of block
TEST_P(GLSLTestLoops, ForUnconditionalBreak)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; j < 8; ++j)
        {
            for (int k = 0; k < 2; ++k, ++j) ++result;
            for (int k = 0; k < 3; ++k)      ++result;
            for (int k = 0; k < 0; ++k)      ++result;
            break;
        }

    color = result == 50 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with break and continue
TEST_P(GLSLTestLoops, ForBreakContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; j < 8; ++j)
        {
            if (j < 2) continue;
            if (j > 6) break;
            if (i < 3) continue;
            if (i > 8) break;
            ++result;
        }

    color = result == 30 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test basic while loops
TEST_P(GLSLTestLoops, BasicWhile)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    while (i < 10)
    {
        int j = 0;
        while (j < 8)
        {
            int k = 0;
            while (k < 2) { ++result; ++k; ++j; }
            while (k < 5) { ++result; ++k; }
            while (k < 4) { ++result; }
            ++j;
        }
        ++i;
    }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test while loops with continue
TEST_P(GLSLTestLoops, WhileContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    while (i < 10)
    {
        int j = 0;
        while (j < 8)
        {
            int k = 0;
            while (k < 2) { ++result; ++k; ++j; }
            while (k < 5) { ++result; ++k; }
            if (i > 3)
            {
                ++j;
                continue;
            }
            while (k < 4) { ++result; }
            ++j;
        }
        ++i;
    }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test while loops with continue at the end of block
TEST_P(GLSLTestLoops, WhileUnconditionalContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    while (i < 10)
    {
        int j = 0;
        while (j < 8)
        {
            int k = 0;
            while (k < 2) { ++result; ++k; ++j; }
            while (k < 5) { ++result; ++k; }
            while (k < 4) { ++result; }
            ++j;
            continue;
        }
        ++i;
    }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test while loops with break
TEST_P(GLSLTestLoops, WhileBreak)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    while (i < 10)
    {
        int j = 0;
        while (true)
        {
            int k = 0;
            while (k < 2) { ++result; ++k; ++j; }
            while (k < 5) { ++result; ++k; }
            while (k < 4) { ++result; }
            ++j;
            if (j >= 8)
                break;
        }
        ++i;
    }

    color = result == 150 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test while loops with continue at the end of block
TEST_P(GLSLTestLoops, WhileUnconditionalBreak)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    while (i < 10)
    {
        int j = 0;
        while (j < 8)
        {
            int k = 0;
            while (k < 2) { ++result; ++k; ++j; }
            while (k < 5) { ++result; ++k; }
            while (k < 4) { ++result; }
            ++j;
            break;
        }
        ++i;
    }

    color = result == 50 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test basic do-while loops
TEST_P(GLSLTestLoops, BasicDoWhile)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    do
    {
        int j = 0;
        do
        {
            int k = 0;
            do { ++result; ++k; ++j; } while (k < 2);
            do { ++result; ++k;      } while (k < 5);
            do { ++result;           } while (k < 3);
            ++j;
        } while (j < 8);
        ++i;
    } while (i < 10);

    color = result == 180 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test do-while loops with continue
TEST_P(GLSLTestLoops, DoWhileContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    do
    {
        int j = 0;
        do
        {
            int k = 0;
            do { ++result; ++k; ++j; } while (k < 2);
            if (i > 3)
            {
                ++j;
                continue;
            }
            do { ++result; ++k;      } while (k < 5);
            do { ++result;           } while (k < 3);
            ++j;
        } while (j < 8);
        ++i;
    } while (i < 10);

    color = result == 108 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test do-while loops with continue at the end of block
TEST_P(GLSLTestLoops, DoWhileUnconditionalContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    do
    {
        int j = 0;
        do
        {
            int k = 0;
            do { ++result; ++k; ++j; continue; } while (k < 2);
            do { ++result; ++k;      continue; } while (k < 5);
            do { ++result;           continue; } while (k < 3);
            ++j;
        } while (j < 8);
        ++i;
    } while (i < 10);

    color = result == 180 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test do-while loops with break
TEST_P(GLSLTestLoops, DoWhileBreak)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    do
    {
        int j = 0;
        do
        {
            int k = 0;
            do { ++result; ++k; ++j; } while (k < 2);
            do { ++result; ++k;      } while (k < 5);
            do { ++result;           } while (k < 3);
            ++j;
            if (j >= 8)
                break;
        } while (true);
        ++i;
    } while (i < 10);

    color = result == 180 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test do-while loops with break at the end of block
TEST_P(GLSLTestLoops, DoWhileUnconditionalBreak)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    do
    {
        int j = 0;
        do
        {
            int k = 0;
            do { ++result; ++k; ++j; break; } while (k < 2);
            do { ++result; ++k;      break; } while (k < 5);
            do { ++result;           break; } while (k < 3);
            ++j;
        } while (j < 8);
        ++i;
    } while (i < 10);

    color = result == 120 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with continue inside switch.
TEST_P(GLSLTestLoops, ForContinueInSwitch)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
        for (int j = 0; j < 8; ++j)
        {
            switch (j)
            {
                case 2:
                case 3:
                case 4:
                    ++result;
                    // fallthrough
                case 5:
                case 6:
                    ++result;
                    break;
                default:
                    continue;
            }
        }

    color = result == 80 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with continue inside constant-foldable switch.
TEST_P(GLSLTestLoops, ForContinueInConstSwitch)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
    {
        switch (10)
        {
            case 2:
                result += 100;
                continue;
                // dead code
                result += 1000;
                // fallthrough
            case 9:
                result += 10000;
                // fallthrough
            case 10:
                ++result;
                continue;
            default:
                result += 100000;
                break;
        }
        result += 1000000;
    }

    color = result == 10 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loop with continue inside nested switches, with side effects after continue.
TEST_P(GLSLTestLoops, ForContinueInSwitchComplex)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    for (int i = 0; i < 10; ++i)
    {
        for (int j = 0; j < 8; ++j)
        {
            switch (j)
            {
                case 2:
                    switch (i)
                    {
                        case 3:
                        case 4:
                        case 5:
                            result += 100;
                            continue;
                        default:
                            result += 10000;
                            continue;
                    }
                    result = 123456789;
                    // fallthrough
                case 3:
                case 4:
                    ++result;
                    // fallthrough
                case 5:
                case 6:
                    ++result;
                    break;
                default:
                    continue;
            }
            result += 3;
        }
    }

    // j |     result
    // 0 | 0 (default continues)
    // 1 | 0 (default continues)
    // 2 | 3x100 (cases 3-5) + 7x10000 (default)
    // 3 | 10x1 (cases 3,4) + 10x1 (cases 5,6) + 10x3 (after switch)
    // 4 | 10x1 (cases 3,4) + 10x1 (cases 5,6) + 10x3 (after switch)
    // 5 | 10x1 (cases 5,6) + 10x3 (after switch)
    // 6 | 10x1 (cases 5,6) + 10x3 (after switch)
    // 7 | 0 (default continues)
    color = result == 70480 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test while loop with continue inside switch
TEST_P(GLSLTestLoops, WhileContinueInSwitch)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    while (i < 10)
    {
        int j = 0;
        while (j < 8)
        {
            switch (j)
            {
                case 2:
                default:
                case 3:
                case 4:
                    ++j;
                    ++result;
                    continue;
                case 0:
                case 1:
                case 7:
                    break;
            }
            ++j;
        }
        ++i;
    }

    color = result == 50 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test do-while loops with continue in switch
TEST_P(GLSLTestLoops, DoWhileContinueInSwitch)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int result = 0;
    int i = 0;
    do
    {
        int j = 0;
        do
        {
            switch (j)
            {
                case 0:
                    ++j;
                    continue;
                default:
                case 2:
                case 3:
                case 4:
                    ++j;
                    ++result;
                    if (j >= 2 && j <= 6)
                        break;
                    else
                        continue;
            }
            ++result;
        } while (j < 8);
        ++i;
    } while (i < 10);

    color = result == 120 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test prune-able loop with side effect in statements.
TEST_P(GLSLTestLoops, SideEffectsInPrunableFor)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

void main()
{
    int a = 4;
    float b = 0.;
    for (int c = a++; (b += float(c) / 8.) < 0.; b += 0.3)
    {
        if (2 == 0);
    }
    int c = a - 4;

    // Expect c to be 1 and b to be 0.5
    color = c == 1 && abs(b - 0.5) < 0.001 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

    runTest(kFS);
}

// Test for loops where the continue block is never executed
TEST_P(GLSLTestLoops_ES31, ForLoopWithDeadContinue)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

uniform int u1;
uniform int u2;
uniform int u3;
uniform int u4;

void main()
{
    int a = 0;
    // ++a is never executed:
    for (int b = 0; b < 1; ++a)
    {
        break;
    }
    // a += u1 + 2 is never executed:
    for (int b = 0; b < 1; a += u1 + 2)
    {
        // Always false
        if (u3 == 1)
        {
            return;
        }
        else
        {
            break;
        }
        discard;
    }
    // a += u2 + 4 is never executed:
    for (int b = 0; b < 1; a += u2 + 4)
    {
        // Always false
        if (u3 == 1)
        {
            return;
        }
        // a += 100 **is** executed once
        for (int c = 0; c < 1; ++c, a += 100)
        {
            // Always true
            if (u3 == 0)
            {
                continue;
            }
            return;
        }
        // Always false
        if (u4 == 1)
        {
            discard;
        }
        else
        {
            // Always true
            if (u4 == 0)
            {
                break;
            }
        }
        discard;
    }

    // `a` should be 100.  `u1` and `u2` are effectively inactive uniforms
    color = a == 100 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

    // `u1` and `u2` can be inactive, which is only detected by the IR
    if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
    {
        GLuint index = glGetProgramResourceIndex(program, GL_UNIFORM, "u1");
        EXPECT_EQ(index, GL_INVALID_INDEX);

        index = glGetProgramResourceIndex(program, GL_UNIFORM, "u2");
        EXPECT_EQ(index, GL_INVALID_INDEX);
    }
}

// Test for loops where the continue block should be executed
TEST_P(GLSLTestLoops_ES31, ForLoopWithLiveContinue)
{
    // The test fails in the AST path, but only on NVIDIA/GL
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr) && IsOpenGL() &&
                       IsNVIDIA());

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

uniform int u1;
uniform int u2;
uniform int u3;
uniform int u4;
uniform int u5;

void main()
{
    int a = 0;
    // ++a is executed once:
    for (int b = 0; b < 1; ++b, ++a)
    {
        continue;
    }
    // a += u1 + 2 is executed once:
    for (int b = 0; b < 1; ++b, a += u1 + 2)
    {
        // Always false
        if (u3 == 1)
        {
            return;
        }
        else
        {
            continue;
        }
        discard;
    }
    // a += u2 + 4 is executed once:
    for (int b = 0; b < 1; ++b, a += u2 + 4)
    {
        // Always false
        if (u3 == 1)
        {
            return;
        }
        switch (u3)
        {
            // Never matches
            case 1:
                a += u3 + 8;
                break;
            // Never matches
            default:
                a += u3 + 16;
                break;
            // Always matches
            case 0:
                a += u3 + 32;
                continue;
        }
        // Always false
        if (u4 == 1)
        {
            discard;
        }
        else
        {
            // Always true, but the `continue` in `switch` makes this never execute
            if (u4 == 0)
            {
                a = 10000;
                break;
            }
        }
        discard;
    }
    // a += u5 + 64 is never executed, but the compiler cannot know that:
    for (int b = 0; b < 1; ++b, a += u5 + 64)
    {
        switch (u3)
        {
            // Never matches
            case 1:
                a += u3 + 128;
                continue;
            // Always matches
            default:
                a += u3 + 256;
                break;
        }
        // Always false
        if (u4 == 1)
        {
            discard;
        }
        else
        {
            // Always true
            if (u4 == 0)
            {
                break;
            }
        }
        discard;
    }

    // `a` should be 1+2+4+32+256
    color = a == 295 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";

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

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

    // Even though `u5` is never really accessed, the compiler wouldn't know that.
    // I.e. the `a += u5 + 64` code cannot be dead-code-eliminated
    GLuint index = glGetProgramResourceIndex(program, GL_UNIFORM, "u5");
    EXPECT_NE(index, GL_INVALID_INDEX);
}

// Test that precision is retained for constants (which are constant folded).  Adapted from a WebGL
// test.
TEST_P(GLSLTest, ConstantFoldedConstantsRetainPrecision)
{
    constexpr char kFS[] = R"(
// It is assumed that uTest is set to 0. It's here to make the expression not constant.
uniform mediump float uTest;
void main() {
    // exact representation of 4096.5 requires 13 bits of relative precision.
    const highp float c = 4096.5;
    mediump float a = 0.0;
    // Below, addition should be evaluated at highp, since one of the operands has the highp qualifier.
    // Thus fract should also be evaluated at highp.
    // See OpenGL ES Shading Language spec section 4.5.2.
    // This should make the result 0.5, since highp provides at least 16 bits of relative precision.
    // (exceptions for operation precision are allowed for a small number of computationally
    // intensive built-in functions, but it is reasonable to think that fract is not one of those).
    // However, if fract() is incorrectly evaluated at minimum precision fulfilling mediump criteria,
    // or at IEEE half float precision, the result is 0.0.
    a = fract(c + uTest);
    // Multiply by 2.0 to make the color green.
    gl_FragColor = vec4(0.0, 2.0 * a, 0.0, 1.0);
})";

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

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

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

// Test that precision is derived for constants in various expressions.  Note that constants don't
// inherently have a precision.  Their precision in the expression they are used are derived from
// the adjacent operands.
// This test mostly relies on the translator's internal validation to catch missing propagations.
TEST_P(GLSLTest_ES31, ConstantPrecisionPropagation)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_shader_multisample_interpolation"));

    constexpr char kVS[] = R"(#version 310 es
in vec4 position;
out vec2 interpolant;
void main()
{
    gl_Position = position;
    interpolant = vec2(1, 2);
})";

    constexpr char kFS[] = R"(#version 310 es
#extension GL_OES_shader_multisample_interpolation: require

in mediump vec2 interpolant;
out highp vec4 color;

uniform bool always_false1;
uniform bool always_false2;
uniform bool always_false3;

highp float f(mediump float v)
{
    return v;
}

struct S1
{
    bool b;
    lowp float f;
};

struct S2
{
    highp uint i;
    S1 s1;
};

layout(binding = 0, std430) buffer B {
    uint b;
} b;

mediump uniform sampler2D samp;

void main()
{
    mediump vec4 v1 = always_false1
                        ? vec4(0, 0, 1, 0)
                        : always_false2 ? vec4(0, 1, 0, 0) : vec4(1, 0, 0, 0);
    mediump vec4 v2 = always_false1
                        ? always_false2 ? vec4(0, 1, 0, 0) : vec4(1, 0, 0, 0)
                        : vec4(0, 0, 1, 0);
    mediump vec4 v3 = always_false1
                        ? vec4(0, 0, 1, 0)
                        : ((always_false2 ? vec4(0, 1, 0, 0) : vec4(1, 0, 0, 0)) + vec4(0, 0, 0, 1));
    lowp vec4 v4 = always_false1
                        ? (always_false2 ? vec4(0, 1, 0, 0) : vec4(1, 0, 0, 0)) + vec4(0, 0, 0, 1)
                        : vec4(0, 0, 1, 0);
    highp vec4 v5 = vec4(0, 0, 0, 1) + (always_false1 ? vec4(0, 1, 0, 0) : vec4(0, 0, 1, 0));
    mediump ivec2 v6 = (always_false1 ? ivec2(100, 200) : ivec2(300, 400))
                     + (always_false2 ? ivec2(1000, 2000) : ivec2(3000, 4000));
    highp ivec2 v7 = (always_false1 ? ivec2(100, 200) : ivec2(300, 400))
                  + (always_false2 ? ivec3(1000, 2000, 3000) : ivec3(4000, 5000, 6000)).yx;
    lowp uvec3 v8 = uvec3((always_false1 ? 10 : 20)
                         +(always_false2 ? 30 : 60));
    mediump float v9 = f(always_false1 ? 10. : 20.);
    S2 v10 = S2((always_false1 ? 1u : 2u), S1(false, (always_false2 ? 3. : 4.)));
    bool v11 = (always_false1 ? 1. : 2.) < (always_false2 ? 3. : 4.);
    bool v12 = (always_false1 ? v4.w : 2.) < (always_false2 ? 3. : 4.);
    bvec3 v13 = greaterThan((always_false1 ? vec3(-1, -2, -3) : vec3(1, 2, 3))
                          + (always_false2 ? vec3(-4, -5, -6) : vec3(4, 5, 6)),
                        vec3(3, 4, 10));

    mediump uint array[5] = uint[5](5u, 7u, 9u, 11u, 13u);
    highp uint v14 = array[(always_false1 ? 5 : 4) - (always_false2 ? 5 : 3) + 1];

    atomicAdd(b.b, (always_false1 ? 4u : 5u));
    highp float v15 = clamp((always_false1 ? 5. : 4.), (always_false2 ? 3. : 2.), (always_false3 ? 7. : 3.5));
    highp uint v16 = packHalf2x16((always_false1 ? vec2(8., 16.) : vec2(32., 64.))
                                + (always_false2 ? vec2(1., 2.) : vec2(4., 128.)));
    lowp vec2 v17 = interpolateAtOffset(interpolant,
                                        always_false2 ? vec2(1000., 2000.) : vec2(3000., 4000.));
    lowp ivec2 v18 = textureSize(samp, always_false1 ? 1 : 2);
    mediump float v19 = 0.;
    switch ((always_false1 ? 2 : 1) + (always_false1 ? 3 : 2) + 4)
    {
      case 6: v19 = 0.25; break;
      case 7: v19 = 1.; break;
      case 8: v19 = 0.75; break;
      default: v19 = 0.5; break;
    }

    if (!all(lessThan(abs(v1 - vec4(1, 0, 0, 0)), vec4(0.01))))         color = vec4(0.1, 0, 0, 0);
    else if (!all(lessThan(abs(v2 - vec4(0, 0, 1, 0)), vec4(0.01))))    color = vec4(0.2, 0, 0, 0);
    else if (!all(lessThan(abs(v3 - vec4(1, 0, 0, 1)), vec4(0.01))))    color = vec4(0.3, 0, 0, 0);
    else if (!all(lessThan(abs(v4 - vec4(0, 0, 1, 0)), vec4(0.01))))    color = vec4(0.4, 0, 0, 0);
    else if (!all(lessThan(abs(v5 - vec4(0, 0, 1, 1)), vec4(0.01))))    color = vec4(0.5, 0, 0, 0);
    else if (!all(equal(v6, ivec2(3300, 4400))))                        color = vec4(0.6, 0, 0, 0);
    else if (!all(equal(v7, ivec2(5300, 4400))))                        color = vec4(0.7, 0, 0, 0);
    else if (!all(equal(v8, uvec3(80, 80, 80))))                        color = vec4(0.8, 0, 0, 0);
    else if (abs(v9 - 20.) > 0.01)                                      color = vec4(0.9, 0, 0, 0);
    else if (v10.i != 2u)                                               color = vec4(1, 0, 0, 0);
    else if (abs(v10.s1.f - 4.0) > 0.01)                                color = vec4(0, 0.1, 0, 0);
    else if (!v11)                                                      color = vec4(0, 0.2, 0, 0);
    else if (!v12)                                                      color = vec4(0, 0.3, 0, 0);
    else if (!v13.x || !v13.y || v13.z)                                 color = vec4(0, 0.4, 0, 0);
    else if (v14 != 9u)                                                 color = vec4(0, 0.5, 0, 0);
    else if (abs(v15 - 3.5) > 0.01)                                     color = vec4(0, 0.6, 0, 0);
    else if (v16 != 0x5A005080u)                                        color = vec4(0, 0.7, 0, 0);
    else if (!all(lessThan(abs(v17 - vec2(1, 2)), vec2(0.01))))         color = vec4(0, 0.8, 0, 0);
    else if (abs(v19 - 1.) > 0.01)                                      color = vec4(0, 0.9, 0, 0);
    else                                                                color = vec4(0, 0, 1, 1);
}
)";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "position", 0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    ASSERT_GL_NO_ERROR();
}

// Test that vector and matrix scalarization does not affect rendering.
TEST_P(GLSLTest, VectorAndMatrixScalarizationDoesNotAffectRendering)
{
    constexpr char kFS[] = R"(
precision mediump float;

varying vec2 v_texCoord;

float a = 0.;
#define A 0.

#define r(a)    mat2( cos( a + vec4(0,-1.5708,1.5708,0) ) )
vec2 c;
#define f(U,a)  ( c = (U) * r(a) , sin(10.*c.x) )

void main() {
    vec2 U = v_texCoord;

    gl_FragColor = U.y > .5
        ? vec4( f(U,a) , f(U*4.,a) , 0,1.0)   // top
        : vec4( f(U,A) , f(U*4.,A) , 0,1.0);  // bottom
}

)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Texture2D(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    // Make sure we rendered something.
    EXPECT_PIXEL_NE(0, 0, 0, 0, 0, 0);

    // Comapare one line in top half to one line in bottom half.
    int compareWidth  = getWindowWidth();
    int compareHeight = getWindowHeight() / 4;

    ASSERT_GE(compareWidth, 2);
    ASSERT_GE(compareHeight, 2);

    GLubyte pixelValue[4];
    constexpr int tolerance = 12;

    for (int x = 0; x < compareWidth; ++x)
    {
        glReadPixels(x, compareHeight, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, pixelValue);
        EXPECT_PIXEL_NEAR(x, getWindowHeight() - compareHeight, pixelValue[0], pixelValue[1],
                          pixelValue[2], pixelValue[3], tolerance);
    }
    EXPECT_GL_NO_ERROR();
}

// Tests initializing a shader IO block using the shader translator option.
TEST_P(GLSLTest_ES31_InitShaderVariables, InitIOBlock)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    const char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in vec4 position;
out BlockType {
    vec4 blockMember;
} BlockTypeOut;

void main()
{
    gl_Position = position;
})";

    const char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
out vec4 colorOut;
in BlockType {
    vec4 blockMember;
} BlockTypeOut;

void main()
{
    if (BlockTypeOut.blockMember == vec4(0)) {
        colorOut = vec4(0, 1, 0, 1);
    } else {
        colorOut = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
    drawQuad(testProgram, "position", 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests initializing a nameless shader IO block using the shader translator option.
TEST_P(GLSLTest_ES31_InitShaderVariables, InitIOBlockNameless)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    const char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in vec4 position;
out BlockType {
    vec4 blockMember;
};

void main()
{
    gl_Position = position;
})";

    const char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
out vec4 colorOut;
in BlockType {
    vec4 blockMember;
};

void main()
{
    if (blockMember == vec4(0)) {
        colorOut = vec4(0, 1, 0, 1);
    } else {
        colorOut = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
    drawQuad(testProgram, "position", 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests initializing a shader IO block with an array using the shader translator option.
TEST_P(GLSLTest_ES31_InitShaderVariables, InitIOBlockWithArray)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    const char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in vec4 position;
out BlockType {
    vec4 blockMember[2];
} BlockTypeOut;

void main()
{
    gl_Position = position;
})";

    const char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
out vec4 colorOut;
in BlockType {
    vec4 blockMember[2];
} BlockTypeOut;

void main()
{
    if (BlockTypeOut.blockMember[0] == vec4(0) &&
        BlockTypeOut.blockMember[1] == vec4(0)) {
        colorOut = vec4(0, 1, 0, 1);
    } else {
        colorOut = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
    drawQuad(testProgram, "position", 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests initializing a shader IO block array using the shader translator option.
TEST_P(GLSLTest_ES31_InitShaderVariables, InitIOBlockArray)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    const char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in vec4 position;
out BlockType {
    vec4 blockMember;
} BlockTypeOut[2];

void main()
{
    gl_Position = position;
})";

    const char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
out vec4 colorOut;
in BlockType {
    vec4 blockMember;
} BlockTypeOut[2];

void main()
{
    if (BlockTypeOut[0].blockMember == vec4(0) &&
        BlockTypeOut[1].blockMember == vec4(0)) {
        colorOut = vec4(0, 1, 0, 1);
    } else {
        colorOut = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
    drawQuad(testProgram, "position", 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests initializing a shader IO block with a struct using the shader translator option.
TEST_P(GLSLTest_ES31_InitShaderVariables, InitIOBlockWithStruct)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    const char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in vec4 position;
struct s {
    float f;
    vec2 v;
};
out BlockType {
    s blockMember;
} BlockTypeOut;

void main()
{
    gl_Position = position;
})";

    const char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
out vec4 colorOut;
struct s {
    float f;
    vec2 v;
};
in BlockType {
    s blockMember;
} BlockTypeOut;

void main()
{
    if (BlockTypeOut.blockMember.f == 0.0 &&
        BlockTypeOut.blockMember.v == vec2(0)) {
        colorOut = vec4(0, 1, 0, 1);
    } else {
        colorOut = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
    drawQuad(testProgram, "position", 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests initializing an IO block with a complicated set of types, using the shader translator.
TEST_P(GLSLTest_ES31_InitShaderVariables, InitIOBlockWithComplexTypes)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    const char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
in vec4 position;
struct s {
    float f;
    vec2 v;
};
out BlockType {
    vec4 v;
    s s1;
    s s2[2];
} BlockTypeOut;

void main()
{
    gl_Position = position;
})";

    const char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
precision mediump float;
out vec4 colorOut;
struct s {
    float f;
    vec2 v;
};
in BlockType {
    vec4 v;
    s s1;
    s s2[2];
} BlockTypeOut;

void main()
{
    s sz = s(0.0, vec2(0));
    if (BlockTypeOut.v == vec4(0) &&
        BlockTypeOut.s1 == sz &&
        BlockTypeOut.s2[0] == sz &&
        BlockTypeOut.s2[1] == sz) {
        colorOut = vec4(0, 1, 0, 1);
    } else {
        colorOut = vec4(1, 0, 0, 1);
    }
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
    drawQuad(testProgram, "position", 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Tests an unsuccessful re-link using glBindAttribLocation.
TEST_P(GLSLTest_ES3, UnsuccessfulRelinkWithBindAttribLocation)
{
    // Make a simple program.
    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    // Install the executable.
    glUseProgram(testProgram);

    // Re-link with a bad XFB varying and a bound attrib location.
    const char *tfVaryings = "gl_FragColor";
    glTransformFeedbackVaryings(testProgram, 1, &tfVaryings, GL_SEPARATE_ATTRIBS);
    glBindAttribLocation(testProgram, 8, essl1_shaders::PositionAttrib());
    glLinkProgram(testProgram);
    GLint linkStatus = 999;
    glGetProgramiv(testProgram, GL_LINK_STATUS, &linkStatus);
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(linkStatus, GL_FALSE);

    // Under normal GL this is not an error.
    glDrawArrays(GL_TRIANGLES, 79, 16);
    EXPECT_GL_NO_ERROR();
}

// Regression test for an unsuccessful link with a varying followed by another link
TEST_P(GLSLTest_ES3, UnsuccessfulLinkFollowedByAnotherLink)
{
    // Make a simple program.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    // Install the executable.
    glUseProgram(program);

    // Re-link with a bad XFB varying and a bound attrib location.
    const char *tfVaryings = "gl_FragColor";
    glTransformFeedbackVaryings(program, 1, &tfVaryings, GL_SEPARATE_ATTRIBS);
    glBindAttribLocation(program, 8, essl1_shaders::PositionAttrib());
    glLinkProgram(program);
    GLint linkStatus = 999;
    glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
    ASSERT_GL_NO_ERROR();
    // Link expected to fail on the first program
    ASSERT_EQ(linkStatus, GL_FALSE);

    // Another program with the same shaders but without the varying is expected to link
    ANGLE_GL_PROGRAM(anotherProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());
    ASSERT_NE(program, anotherProgram);
    glUseProgram(anotherProgram);
    glLinkProgram(anotherProgram);
    ASSERT_GL_NO_ERROR();
    ASSERT_NE(CheckLinkStatusAndReturnProgram(anotherProgram, true), 0u);
}

// Tests an unsuccessful re-link using glBindAttribLocation under WebGL.
TEST_P(WebGL2GLSLTest, UnsuccessfulRelinkWithBindAttribLocation)
{
    // Make a simple program.
    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), essl1_shaders::fs::Green());

    // Install the executable.
    glUseProgram(testProgram);

    // Re-link with a bad XFB varying and a bound attrib location.
    const char *tfVaryings = "gl_FragColor";
    glTransformFeedbackVaryings(testProgram, 1, &tfVaryings, GL_SEPARATE_ATTRIBS);
    glBindAttribLocation(testProgram, 8, essl1_shaders::PositionAttrib());
    glLinkProgram(testProgram);
    GLint linkStatus = 999;
    glGetProgramiv(testProgram, GL_LINK_STATUS, &linkStatus);
    ASSERT_GL_NO_ERROR();
    ASSERT_EQ(linkStatus, GL_FALSE);

    // Under WebGL this is an error.
    glDrawArrays(GL_TRIANGLES, 79, 16);
    EXPECT_GL_ERROR(GL_INVALID_OPERATION);
}

// Covers a HLSL compiler bug.
TEST_P(GLSLTest_ES3, ComplexCrossExpression)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
vec3 a = vec3(0.0);
out vec4 color;
void main()
{
    cross(max(vec3(0.0), reflect(dot(a, vec3(0.0)), 0.0)), vec3(0.0));
})";

    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
}

// Test that unused local variable is dead code eliminated in IR
TEST_P(GLSLTest_ES3, UnusedLocalVariableEliminatedInIR)
{
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr));
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
vec3 a = vec3(0.0);
out vec4 color;
void main()
{
    vec3 unusedVariable;
    cross(max(vec3(0.0), reflect(dot(a, vec3(0.0)), 0.0)), vec3(0.0));
})";

    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
}

// Test robustness of out-of-bounds lod in texelFetch
TEST_P(WebGL2GLSLTest, TexelFetchLodOutOfBounds)
{
    constexpr char kVS[] = R"(#version 300 es
in vec4 vertexPosition;
void main() {
    gl_Position = vertexPosition;
})";

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform highp sampler2DArray textureArray;
uniform int textureLod;
out vec4 fragColor;
void main() {
    fragColor = texelFetch(textureArray, ivec3(gl_FragCoord.xy, 0), textureLod);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);
    const GLint lodLoc = glGetUniformLocation(program, "textureLod");
    EXPECT_NE(lodLoc, -1);
    const GLint textureLoc = glGetUniformLocation(program, "textureArray");
    EXPECT_NE(textureLoc, -1);

    const GLint attribLocation = glGetAttribLocation(program, "vertexPosition");
    GLBuffer buffer;
    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    constexpr float vertices[12] = {
        -1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1,
    };
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
    glEnableVertexAttribArray(attribLocation);
    glVertexAttribPointer(attribLocation, 2, GL_FLOAT, false, 0, 0);

    GLTexture texture;
    glBindTexture(GL_TEXTURE_2D_ARRAY, texture);
    glTexStorage3D(GL_TEXTURE_2D_ARRAY, 5, GL_RGBA8, 16, 16, 3);
    glUniform1i(textureLoc, 0);

    // Test LOD too large
    glUniform1i(lodLoc, 0x7FFF);
    glDrawArrays(GL_TRIANGLES, 0, 6);

    // Test LOD negative
    glUniform1i(lodLoc, -1);
    glDrawArrays(GL_TRIANGLES, 0, 6);
}

// Test that aliasing function out parameters work.  The GLSL spec says:
//
// > Because the function works with local copies of parameters, there are no issues regarding
// > aliasing of variables within a function.
//
// In the test below, while the value of x is unknown after the function call, the result of the
// function must deterministically be true.
TEST_P(GLSLTest, AliasingFunctionOutParams)
{
    constexpr char kFS[] = R"(precision highp float;

const vec4 colorGreen = vec4(0.,1.,0.,1.);
const vec4 colorRed   = vec4(1.,0.,0.,1.);

bool outParametersAreDistinct(out float x, out float y) {
    x = 1.0;
    y = 2.0;
    return x == 1.0 && y == 2.0;
}
void main() {
    float x = 0.0;
    gl_FragColor = outParametersAreDistinct(x, x) ? colorGreen : colorRed;
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Test that aliasing function out parameters work even when multiple params are aliased.
TEST_P(GLSLTest, AliasingFunctionOutParamsMultiple)
{
    constexpr char kFS[] = R"(precision highp float;

const vec4 colorGreen = vec4(0.,1.,0.,1.);
const vec4 colorRed   = vec4(1.,0.,0.,1.);

bool outParametersAreDistinct(out float x, out float y, out float z, out float a) {
    x = 1.0;
    y = 2.0;
    z = 3.0;
    a = 4.0;
    return x == 1.0 && y == 2.0 && z == 3.0 && a == 4.0;
}
void main() {
    float x = 0.0;
    float y = 0.0;
    gl_FragColor = outParametersAreDistinct(x, x, y, y) ? colorGreen : colorRed;
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Test that aliasing function inout parameters work.
TEST_P(GLSLTest, AliasingFunctionInOutParams)
{
    constexpr char kFS[] = R"(precision highp float;

const vec4 colorGreen = vec4(0.,1.,0.,1.);
const vec4 colorRed   = vec4(1.,0.,0.,1.);

bool inoutParametersAreDistinct(inout float x, inout float y) {
    x = 1.0;
    y = 2.0;
    return x == 1.0 && y == 2.0;
}
void main() {
    float x = 0.0;
    gl_FragColor = inoutParametersAreDistinct(x, x) ? colorGreen : colorRed;
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Test coverage of some matrix/scalar ops which Metal translation was missing.
TEST_P(GLSLTest, MatrixScalarOps)
{
    constexpr char kFS[] = R"(precision highp float;
void main() {
    float scalar = 0.5;
    mat3 matrix = mat3(vec3(0.1), vec3(0.1), vec3(0.1));

    mat3 m0 = scalar / matrix;
    mat3 m1 = scalar * matrix;
    mat3 m2 = scalar + matrix;
    mat3 m3 = scalar - matrix;

    gl_FragColor = vec4(m0[0][0], m1[0][0], m2[0][0], m3[0][0]);
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();

    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 13, 153, 102), 1.0);
    ASSERT_GL_NO_ERROR();
}

// Test coverage of some matrix ops which Metal translation was missing.
TEST_P(GLSLTest, MatrixNegate)
{
    constexpr char kFS[] = R"(precision highp float;
void main() {
    mat3 matrix = mat3(vec3(-0.1), vec3(-0.1), vec3(-0.1));

    mat3 m0 = -matrix;

    gl_FragColor = vec4(m0[0][0], 0, 0, 1);
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();

    drawQuad(testProgram, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(26, 0, 0, 255), 1.0);
    ASSERT_GL_NO_ERROR();
}

// Test coverage of the mix(float, float, bool) overload which was missing in Metal translation
TEST_P(GLSLTest_ES3, MixFloatFloatBool)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 fragColor;
void main() {
    vec4 testData = vec4(0.0, 1.0, 0.5, 0.25);
    float scalar = mix(testData.x, testData.y, testData.x < 0.5);
    vec2 vector = mix(testData.xy, testData.xw, bvec2(testData.x < 0.5, testData.y < 0.5));
    fragColor = vec4(scalar, vector.x, vector.y, 1);
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();

    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 0, 255, 255));
    ASSERT_GL_NO_ERROR();
}

// Test mix(float, float, bool) is not affected by an invalid value not selected.
TEST_P(GLSLTest_ES3, MixFloatFloatBoolInvalid)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform vec2 xy;
uniform bool a;
out vec4 fragColor;
void main() {
    fragColor = vec4(mix(xy.x, xy.y, a) == xy.y, 0.0, 0.0, 1.0);
}
)";
    ANGLE_GL_PROGRAM(testProgram, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(testProgram);
    auto xy = glGetUniformLocation(testProgram, "xy");
    auto a  = glGetUniformLocation(testProgram, "a");
    ASSERT_GL_NO_ERROR();
    glUniform2f(xy, NAN, 0.5f);
    glUniform1i(a, 1);
    ASSERT_GL_NO_ERROR();

    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 0, 0, 255));
    ASSERT_GL_NO_ERROR();
}

// Test coverage of the mix(uint, uint, bool) overload which was missing in D3D11 translation
TEST_P(GLSLTest_ES31, MixUintUintBool)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
out vec4 fragColor;
void main() {
    uvec4 testData1 = uvec4(0, 1, 2, 3);
    uvec4 testData2 = uvec4(4, 5, 6, 7);
    uint scalar = mix(testData1.x, testData2.x, true);
    uvec4 vector = mix(testData1, testData2, bvec4(false, true, true, false));
    fragColor = vec4(scalar == 4u ? 1.0 : 0.0, vector == uvec4(0, 5, 6, 3) ? 1.0 : 0.0, 0.0, 1.0);
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();

    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 0, 255));
    ASSERT_GL_NO_ERROR();
}

// Test coverage of the mix(int, int, bool) overload which was missing in D3D11 translation
TEST_P(GLSLTest_ES31, MixIntIntBool)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
out vec4 fragColor;
void main() {
    ivec4 testData1 = ivec4(-4, -3, -2, -1);
    ivec4 testData2 = ivec4(4, 5, 6, 7);
    int scalar = mix(testData1.x, testData2.x, true);
    ivec4 vector = mix(testData1, testData2, bvec4(false, true, true, false));
    fragColor = vec4(scalar == 4 ? 1.0 : 0.0, vector == ivec4(-4, 5, 6, -1) ? 1.0 : 0.0, 0.0, 1.0);
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();

    drawQuad(testProgram, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(255, 255, 0, 255));
    ASSERT_GL_NO_ERROR();
}

// Test that aliasing function inout parameters work when more than one param is aliased.
TEST_P(GLSLTest, AliasingFunctionInOutParamsMultiple)
{
    constexpr char kFS[] = R"(precision highp float;

const vec4 colorGreen = vec4(0.,1.,0.,1.);
const vec4 colorRed   = vec4(1.,0.,0.,1.);

bool inoutParametersAreDistinct(inout float x, inout float y, inout float z, inout float a) {
    x = 1.0;
    y = 2.0;
    z = 3.0;
    a = 4.0;
    return x == 1.0 && y == 2.0 && z == 3.0 && a == 4.0;
}
void main() {
    float x = 0.0;
    float y = 0.0;
    gl_FragColor = inoutParametersAreDistinct(x, x, y, y) ? colorGreen : colorRed;
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Test that aliasing function out parameter with a global works.
TEST_P(GLSLTest, AliasingFunctionOutParamAndGlobal)
{
    constexpr char kFS[] = R"(precision highp float;

const vec4 colorGreen = vec4(0.,1.,0.,1.);
const vec4 colorRed   = vec4(1.,0.,0.,1.);

float x = 1.0;
bool outParametersAreDistinctFromGlobal(out float y) {
    y = 2.0;
    return x == 1.0 && y == 2.0;
}
void main() {
    gl_FragColor = outParametersAreDistinctFromGlobal(x) ? colorGreen : colorRed;
}
)";

    ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Simple(), kFS);
    drawQuad(testProgram, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Make sure const sampler parameters work.
TEST_P(GLSLTest, ConstSamplerParameter)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform sampler2D samp;

vec4 sampleConstSampler(const sampler2D s) {
    return texture2D(s, vec2(0));
}

void main() {
    gl_FragColor = sampleConstSampler(samp);
}
)";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture texture;
    GLColor expected = MakeGLColor(32, 64, 96, 255);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, expected.data());
    GLint u = glGetUniformLocation(program, "samp");
    EXPECT_NE(u, -1);
    glUniform1i(u, 0);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, expected);
    ASSERT_GL_NO_ERROR();
}

// Make sure const sampler parameters work.
TEST_P(GLSLTest, ConstInSamplerParameter)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform sampler2D u;
vec4 sampleConstSampler(const in sampler2D s) {
    return texture2D(s, vec2(0));
}
void main() {
    gl_FragColor = sampleConstSampler(u);
}
)";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture texture;
    GLColor expected = MakeGLColor(32, 64, 96, 255);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, expected.data());
    GLint u = glGetUniformLocation(program, "u");
    EXPECT_NE(u, -1);
    glUniform1i(u, 0);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, expected);
    ASSERT_GL_NO_ERROR();
}

// Make sure passing const sampler parameters to another function work.
TEST_P(GLSLTest, ConstSamplerParameterAsArgument)
{
    constexpr char kFS[] = R"(precision mediump float;

uniform sampler2D samp;

vec4 sampleSampler(sampler2D s) {
    return texture2D(s, vec2(0));
}

vec4 sampleConstSampler(const sampler2D s) {
    return sampleSampler(s);
}

void main() {
    gl_FragColor = sampleConstSampler(samp);
}
)";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    GLTexture texture;
    GLColor expected = MakeGLColor(32, 64, 96, 255);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, expected.data());
    GLint u = glGetUniformLocation(program, "samp");
    EXPECT_NE(u, -1);
    glUniform1i(u, 0);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, expected);
    ASSERT_GL_NO_ERROR();
}

// Test for a driver bug with matrix multiplication in the tessellation control shader.
TEST_P(GLSLTest_ES31, TessellationControlShaderMatrixMultiplicationBug)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : enable
layout(vertices = 1) out;
precision highp float;

patch out mat4 x;

void main()
{
    x = mat4(
        0.53455, 0.47307, 0.34935, 0.28717,
        0.67195, 0.59992, 0.48213, 0.43678,
        0.76376, 0.6772, 0.55361, 0.5165,
        0.77996, 0.68862, 0.56187, 0.52611
    );

    const mat4 m = mat4(
        vec4( -1.0, 3.0,-3.0, 1.0),
        vec4(  3.0,-6.0, 3.0, 0.0),
        vec4( -3.0, 3.0, 0.0, 0.0),
        vec4(  1.0, 0.0, 0.0, 0.0)
    );

    x = m * x;

    gl_TessLevelInner[0u] = 1.;
    gl_TessLevelInner[1u] = 1.;
    gl_TessLevelOuter[0u] = 1.;
    gl_TessLevelOuter[1u] = 1.;
    gl_TessLevelOuter[2u] = 1.;
    gl_TessLevelOuter[3u] = 1.;
})";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : enable
layout(quads, cw, fractional_odd_spacing) in;
precision highp float;

patch in mat4 x;

out mat4 x_fs;

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

    constexpr char kFS[] = R"(#version 310 es
precision highp float;

in mat4 x_fs;
out vec4 color;

void main()
{
    // Note: on the failing driver, .w of every column has the same value as .x of the same column.

    const mat4 expect = mat4(
        0.12378, -0.18672, -0.18444, 0.53455,
        0.1182, -0.13728, -0.21609, 0.67195,
        0.12351, -0.11109, -0.25968, 0.76376,
        0.1264, -0.10623, -0.27402, 0.77996
    );

    color = vec4(all(lessThan(abs(x_fs[0] - expect[0]), vec4(0.01))),
                 all(lessThan(abs(x_fs[1] - expect[1]), vec4(0.01))),
                 all(lessThan(abs(x_fs[2] - expect[2]), vec4(0.01))),
                 all(lessThan(abs(x_fs[3] - expect[3]), vec4(0.01))));
})";

    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    ASSERT_GL_NO_ERROR();
}

// Test for a driver bug with matrix copy in the tessellation control shader.
TEST_P(GLSLTest_ES31, TessellationControlShaderMatrixCopyBug)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : enable
layout(vertices = 1) out;
precision highp float;

patch out mat4 x;
patch out vec4 col0;

void main()
{
    // Note: if |x| is not an |out| varying, the test passes.
    x = mat4(
        0.53455, 0.47307, 0.34935, 0.28717,
        0.67195, 0.59992, 0.48213, 0.43678,
        0.76376, 0.6772, 0.55361, 0.5165,
        0.77996, 0.68862, 0.56187, 0.52611
    );

    const mat4 m = mat4(
        vec4( -1.0, 3.0,-3.0, 1.0),
        vec4(  3.0,-6.0, 3.0, 0.0),
        vec4( -3.0, 3.0, 0.0, 0.0),
        vec4(  1.0, 0.0, 0.0, 0.0)
    );

    mat4 temp = x;

    // Note: On the failing driver, commenting this line makes the test pass.
    // However, the output being tested is |temp|, assigned above, not |x|.
    x = m * x;

    col0 = temp[0];

    gl_TessLevelInner[0u] = 1.;
    gl_TessLevelInner[1u] = 1.;
    gl_TessLevelOuter[0u] = 1.;
    gl_TessLevelOuter[1u] = 1.;
    gl_TessLevelOuter[2u] = 1.;
    gl_TessLevelOuter[3u] = 1.;
})";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : enable
layout(quads, cw, fractional_odd_spacing) in;
precision highp float;

patch in vec4 col0;

out vec4 col0_fs;

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

    constexpr char kFS[] = R"(#version 310 es
precision highp float;

in vec4 col0_fs;
out vec4 color;

void main()
{
    // Note: on the failing driver, |col0| has the value of |m * x|, not |temp|.
    color = vec4(abs(col0_fs.x - 0.53455) < 0.01,
                abs(col0_fs.y - 0.47307) < 0.01,
                abs(col0_fs.z - 0.34935) < 0.01,
                abs(col0_fs.w - 0.28717) < 0.01);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
    ASSERT_GL_NO_ERROR();
}

// Tests the generation of HLSL functions with uint/int parameters that may be ambiguous.
TEST_P(GLSLTest_ES3, AmbiguousHLSLIntegerFunctionParameters)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
void main()
{
    gl_Position = vec4(0, 0, 0, 0);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
void main()
{
    // Ensure that both uint and int to float constructors are generated before the ambiguous usage.
    int i = int(gl_FragCoord.x);
    float f1 = float(i);
    color.r = f1;

    uint ui = uint(gl_FragCoord.x);
    float f2 = float(i);
    color.g = f2;

    // Ambiguous call
    float f3 = float(1u << (2u * ui));
    color.b = f3;
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
}

// Tests adding a struct definition inline in a shader.
// Metal backend contains a pass that separates struct definition and declaration.
TEST_P(GLSLTest_ES3, StructInShader)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
void main(void)
{
    struct structMain {
        float i;
    } testStruct;

    testStruct.i = 5.0 ;
    gl_Position = vec4(testStruct.i - 4.0, 0, 0, 1);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
void main()
{
    color = vec4(0,1,0,0);
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
}

// Issue: A while loop's expression, and a branch
// condition with EOpContinue were being deep
// copied as part of monomorphize functions,
// causing a crash, as they were not null-checked.
// Tests transforming a function that will be monomorphized.
TEST_P(GLSLTest_ES3, MonomorphizeForAndContinue)
{

    constexpr char kFS[] =
        R"(#version 300 es

        precision mediump float;
        out vec4 fragOut;
        struct aParam
        {
            sampler2D sampler;
        };
        uniform aParam theParam;

        float monomorphizedFunction(aParam a)
        {
            int i = 0;
            vec4 j = vec4(0);
            for(;;)
            {
                if(i++ < 10)
                {
                    j += texture(a.sampler, vec2(0.0f,0.0f));
                    continue;
                }
                break;
            }
            return j.a;
        }
        void main()
        {
            fragOut.a = monomorphizedFunction(theParam);
        }
)";
    CompileShader(GL_FRAGMENT_SHADER, kFS);
    ASSERT_GL_NO_ERROR();
}

// Tests inout parameters with array references.
TEST_P(GLSLTest_ES3, InoutWithArrayRefs)
{
    const char kVS[] = R"(#version 300 es
precision highp float;
void swap(inout float a, inout float b)
{
    float tmp = a;
    a = b;
    b = tmp;
}

void main(void)
{
    vec3 testVec = vec3(0.0, 1.0, 1.0);
    swap(testVec[0], testVec[1]);
    gl_Position = vec4(testVec[0], testVec[1], testVec[2], 1.0);
})";

    const char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
void main()
{
    color = vec4(0,1,0,0);
})";

    ANGLE_GL_PROGRAM(testProgram, kVS, kFS);
}

// Test that sample variables compile.
TEST_P(GLSLTest_ES3, SampleVariables)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));

    const char kFS[] = R"(#version 300 es
#extension GL_OES_sample_variables : require
precision highp float;
out vec4 color;
void main()
{
    gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555;
    color = vec4(gl_SamplePosition.yx, float(gl_SampleID), float(gl_MaxSamples + gl_NumSamples));
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
}

// Test that gl_SampleID can be used before gl_SampleMaskIn.
TEST_P(GLSLTest_ES3, SampleIDBeforeSampleMaskIn)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));

    const char kFS[] = R"(#version 300 es
#extension GL_OES_sample_variables : require
precision highp float;
out vec4 color;
void main()
{
    int sampleId = gl_SampleID;
    gl_SampleMask[0] = gl_SampleMaskIn[0] & 0x55555555;
    color = vec4(gl_SamplePosition.yx, float(sampleId), float(gl_MaxSamples + gl_NumSamples));
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 0, 255), 1);
}

// Test that gl_FragCoord can be used after gl_SampleID.
TEST_P(GLSLTest_ES3, SampleIDBeforeFragCoord)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));

    const char kFS[] = R"(#version 300 es
#extension GL_OES_sample_variables : require
precision highp float;
uniform vec2 dim;
out vec4 color;
void main()
{
    vec2 useSampleIDFirst = vec2(gl_SampleID);
    vec2 coords = gl_FragCoord.xy / dim + useSampleIDFirst;
    color = vec4(coords.x > 0.5, coords.y > 0.5, 0, 1);
})";

    const int w  = getWindowWidth();
    const int h  = getWindowHeight();
    const int w2 = w / 2;
    const int h2 = h / 2;

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform2f(glGetUniformLocation(program, "dim"), w, h);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);

    EXPECT_PIXEL_RECT_EQ(0, 0, w2 - 1, h2 - 1, GLColor::black);
    EXPECT_PIXEL_RECT_EQ(w2 + 1, 0, w - (w2 + 1), h2 - 1, GLColor::red);
    EXPECT_PIXEL_RECT_EQ(0, h2 + 1, w2 - 1, h - (h2 + 1), GLColor::green);
    EXPECT_PIXEL_RECT_EQ(w2 + 1, h2 + 1, w - (w2 + 1), h - (h2 + 1), GLColor::yellow);
}

// Test that shader caching maintains uniforms across compute shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheComputeWithUniform)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kCS[] = R"(#version 310 es
layout (local_size_x = 2, local_size_y = 3, local_size_z = 1) in;

uniform uint inputs[6];

layout (binding = 0, std430) buffer OutputBuffer {
    uint outputs[6];
};

void main() {
    outputs[gl_LocalInvocationIndex] = inputs[gl_LocalInvocationIndex];
}
)";

    ANGLE_GL_COMPUTE_PROGRAM(unusedProgram, kCS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr std::array<GLuint, 6> kInputUniform = {1, 2, 3, 4, 5, 6};
    for (int i = 0; i < static_cast<int>(kInputUniform.size()); ++i)
    {
        const std::string uniformName =
            std::string("inputs[") + std::to_string(i) + std::string("]");
        int inputLocation =
            glGetUniformLocation(program, static_cast<const GLchar *>(uniformName.c_str()));
        glUniform1ui(inputLocation, kInputUniform[i]);
        ASSERT_GL_NO_ERROR();
    }

    constexpr std::array<GLuint, 6> kOutputInitData = {0, 0, 0, 0, 0, 0};
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLuint) * kOutputInitData.size(),
                 kOutputInitData.data(), GL_STATIC_READ);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, outputBuffer);

    glDispatchCompute(1, 1, 1);
    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);

    auto outputData = static_cast<const GLuint *>(glMapBufferRange(
        GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint) * kOutputInitData.size(), GL_MAP_READ_BIT));
    for (int i = 0; i < static_cast<int>(kInputUniform.size()); ++i)
    {
        EXPECT_EQ(kInputUniform[i], outputData[i]);
    }
}

// Test that shader caching maintains uniform blocks across shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheComputeWithUniformBlocks)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kCS[] = R"(#version 310 es
layout (local_size_x = 2, local_size_y = 3, local_size_z = 1) in;

layout (std140) uniform Input1 {
    uint input1;
};

layout (std140) uniform Input2 {
    uint input2;
};

layout (binding = 0, std430) buffer OutputBuffer {
    uint outputs[6];
};

void main() {
    if (gl_LocalInvocationIndex < uint(3))
    {
        outputs[gl_LocalInvocationIndex] = input1;
    }
    else
    {
        outputs[gl_LocalInvocationIndex] = input2;
    }
}
)";

    ANGLE_GL_COMPUTE_PROGRAM(unusedProgram, kCS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_COMPUTE_PROGRAM(program, kCS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLuint kInput1Data = 1;
    GLBuffer input1;
    glBindBuffer(GL_UNIFORM_BUFFER, input1);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLuint), &kInput1Data, GL_STATIC_COPY);
    const GLuint kInput1Index = glGetUniformBlockIndex(program, "Input1");
    glUniformBlockBinding(program, kInput1Index, 1);
    glBindBufferBase(GL_UNIFORM_BUFFER, 1, input1);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    constexpr GLuint kInput2Data = 2;
    GLBuffer input2;
    glBindBuffer(GL_UNIFORM_BUFFER, input2);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLuint), &kInput2Data, GL_STATIC_COPY);
    const GLuint kInput2Index = glGetUniformBlockIndex(program, "Input2");
    glUniformBlockBinding(program, kInput2Index, 2);
    glBindBufferBase(GL_UNIFORM_BUFFER, 2, input2);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    constexpr std::array<GLuint, 6> kOutputInitData = {0, 0, 0, 0, 0, 0};
    GLBuffer outputBuffer;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, outputBuffer);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLuint) * kOutputInitData.size(),
                 kOutputInitData.data(), GL_STATIC_READ);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, outputBuffer);
    ASSERT_GL_NO_ERROR();

    glDispatchCompute(1, 1, 1);
    glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT);
    glDeleteProgram(program);
    ASSERT_GL_NO_ERROR();

    auto outputData                       = static_cast<const GLuint *>(glMapBufferRange(
        GL_SHADER_STORAGE_BUFFER, 0, sizeof(GLuint) * kOutputInitData.size(), GL_MAP_READ_BIT));
    constexpr std::array<GLuint, 6> kWant = {kInput1Data, kInput1Data, kInput1Data,
                                             kInput2Data, kInput2Data, kInput2Data};
    for (int i = 0; i < static_cast<int>(kWant.size()); ++i)
    {
        EXPECT_EQ(kWant[i], outputData[i]);
    }
}

// Test that shader caching maintains uniforms across vertex shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheVertexWithUniform)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kVS[] = R"(#version 310 es

precision mediump float;

layout (location = 0) in vec4 a_position;

uniform float redInput;

out float redValue;

void main() {
    gl_Position = a_position;
    redValue = redInput;
})";

    constexpr char kFS[] = R"(#version 310 es

precision mediump float;

in float redValue;

out vec4 fragColor;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, kVS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kRedValue = 1.0f;
    int redInputLocation        = glGetUniformLocation(program, "redInput");
    glUniform1f(redInputLocation, kRedValue);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniform blocks across vertex shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheVertexWithUniformBlock)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kVS[] = R"(#version 310 es

precision mediump float;

layout (location = 0) in vec4 a_position;

layout (std140) uniform Input {
    float redInput;
};

out float redValue;

void main() {
    gl_Position = a_position;
    redValue = redInput;
})";

    constexpr char kFS[] = R"(#version 310 es

precision mediump float;

in float redValue;

out vec4 fragColor;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, kVS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_UNIFORM_BUFFER, input);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    const GLuint kInputIndex = glGetUniformBlockIndex(program, "Input");
    glUniformBlockBinding(program, kInputIndex, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, input);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains SSBOs across vertex shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheVertexWithSSBO)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    // Check that GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS is at least 1.
    GLint maxVertexShaderStorageBlocks;
    glGetIntegerv(GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS, &maxVertexShaderStorageBlocks);
    ANGLE_SKIP_TEST_IF(maxVertexShaderStorageBlocks == 0);
    constexpr char kVS[] = R"(#version 310 es

precision mediump float;

layout (location = 0) in vec4 a_position;

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

out float redValue;

void main() {
    gl_Position = a_position;
    redValue = redInput;
})";

    constexpr char kFS[] = R"(#version 310 es

precision mediump float;

in float redValue;

out vec4 fragColor;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, kVS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniforms across vertex shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheFragmentWithUniform)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kFS[] = R"(#version 310 es

precision mediump float;

uniform float redValue;

out vec4 fragColor;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    GLfloat redValue     = 1.0f;
    int redInputLocation = glGetUniformLocation(program, "redValue");
    glUniform1f(redInputLocation, redValue);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniform blocks across vertex shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheFragmentWithUniformBlock)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kFS[] = R"(#version 310 es

precision mediump float;

layout (std140) uniform Input {
    float redValue;
};

out vec4 fragColor;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_UNIFORM_BUFFER, input);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    const GLuint kInputIndex = glGetUniformBlockIndex(program, "Input");
    glUniformBlockBinding(program, kInputIndex, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, input);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains SSBOs across vertex shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheFragmentWithSSBO)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    constexpr char kFS[] = R"(#version 310 es

precision mediump float;

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

out vec4 fragColor;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains whether GL_ARB_sample_shading is enabled across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheFragmentWithARBSampleShading)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARB_sample_shading"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kFS[] = R"(#version 310 es
#extension GL_ARB_sample_shading : enable

precision mediump float;

out vec4 fragColor;

void main()
{
#ifdef GL_ARB_sample_shading
    fragColor = vec4(1., 0., 0., 1.);
#else
    fragColor = vec4(0.);
#endif
})";

    ANGLE_GL_PROGRAM(unusedProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

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

// Test that shader caching maintains which advanced blending equations (provided by
// GL_KHR_blend_equation_advanced) are used across shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheFragmentWithKHRAdvancedBlendEquations)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_KHR_blend_equation_advanced"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kFS[] = R"(#version 310 es
#extension GL_KHR_blend_equation_advanced : require

layout (blend_support_multiply) out;

precision mediump float;

out vec4 fragColor;

void main()
{
    fragColor = vec4(1., 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM(unusedProgram, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

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

// Test that shader caching maintains uniforms in geometry shaders across shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheGeometryWithUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
precision mediump float;

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

uniform float redInput;

out float redValue;

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

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

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

    EndPrimitive();
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_GS(unusedProgram, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_GS(program, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kRedValue = 1.0f;
    int redInputLocation        = glGetUniformLocation(program, "redInput");
    glUniform1f(redInputLocation, kRedValue);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniform blocks in geometry shaders across shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheGeometryWithUniformBlock)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
precision mediump float;

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

layout (std140) uniform Input {
    float redInput;
};

out float redValue;

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

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

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

    EndPrimitive();
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_GS(unusedProgram, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_GS(program, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_UNIFORM_BUFFER, input);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    const GLuint kInputIndex = glGetUniformBlockIndex(program, "Input");
    glUniformBlockBinding(program, kInputIndex, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, input);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains SSBO in geometry shaders across shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheGeometryWithSSBO)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    GLint maxGeometryShaderStorageBlocks = 0;
    glGetIntegerv(GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS_EXT, &maxGeometryShaderStorageBlocks);
    ANGLE_SKIP_TEST_IF(maxGeometryShaderStorageBlocks == 0);

    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
precision mediump float;

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

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

out float redValue;

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

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

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

    EndPrimitive();
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_GS(unusedProgram, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_GS(program, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains the number of invocations in geometry shaders across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheGeometryWithInvocations)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
precision mediump float;

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

out float redValue;

void main() {
    float redOut = 0.;
    if (gl_InvocationID == 1) {
        redOut = 1.;
    }

    gl_Position = gl_in[0].gl_Position;
    redValue = redOut;
    EmitVertex();

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

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

    EndPrimitive();
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_GS(unusedProgram, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_GS(program, essl31_shaders::vs::Simple(), kGS, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

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

// Test that shader caching maintains uniforms in tessellation control shaders across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheTessellationControlWithUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

uniform float redInput;

patch out float redValueCS;

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

    redValueCS = redInput;
}

)";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

patch in float redValueCS;

out float redValue;

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

    redValue = redValueCS;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(unusedProgram, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kRedValue = 1.0f;
    int redInputLocation        = glGetUniformLocation(program, "redInput");
    glUniform1f(redInputLocation, kRedValue);
    ASSERT_GL_NO_ERROR();

    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniform blocks in tessellation control shaders across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheTessellationControlWithUniformBlock)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

layout (std140) uniform Input {
    float redInput;
};

patch out float redValueCS;

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

    redValueCS = redInput;
}

)";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

patch in float redValueCS;

out float redValue;

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

    redValue = redValueCS;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(unusedProgram, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_UNIFORM_BUFFER, input);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    const GLuint kInputIndex = glGetUniformBlockIndex(program, "Input");
    glUniformBlockBinding(program, kInputIndex, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, input);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains SSBOs in tessellation control shaders across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheTessellationControlWithSSBO)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    GLint maxTessControlShaderStorageBlocks;
    glGetIntegerv(GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS_EXT,
                  &maxTessControlShaderStorageBlocks);
    ANGLE_SKIP_TEST_IF(maxTessControlShaderStorageBlocks == 0);

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

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

patch out float redValueCS;

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

    redValueCS = redInput;
}

)";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

patch in float redValueCS;

out float redValue;

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

    redValue = redValueCS;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(unusedProgram, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniforms in tessellation evaluation shaders across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheTessellationEvalWithUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

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

)";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

uniform float redInput;

out float redValue;

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

    redValue = redInput;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(unusedProgram, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kRedValue = 1.0f;
    int redInputLocation        = glGetUniformLocation(program, "redInput");
    glUniform1f(redInputLocation, kRedValue);
    ASSERT_GL_NO_ERROR();

    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains uniform blocks in tessellation evaluation shaders across
// shader compilations.
TEST_P(GLSLTest_ES31, ShaderCacheTessellationEvalWithUniformBlock)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

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

)";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

layout (std140) uniform Input {
    float redInput;
};

out float redValue;

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

    redValue = redInput;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(unusedProgram, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_UNIFORM_BUFFER, input);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    const GLuint kInputIndex = glGetUniformBlockIndex(program, "Input");
    glUniformBlockBinding(program, kInputIndex, 0);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, input);
    glBindBuffer(GL_UNIFORM_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

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

// Test that shader caching maintains SSBOs in tessellation evaluation shaders across shader
// compilations.
TEST_P(GLSLTest_ES31, ShaderCacheTessellationEvalWithSSBO)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsVulkan());
    GLint maxTessEvalShaderStorageBlocks;
    glGetIntegerv(GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS_EXT,
                  &maxTessEvalShaderStorageBlocks);
    ANGLE_SKIP_TEST_IF(maxTessEvalShaderStorageBlocks == 0);

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (vertices = 1) out;

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

)";

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
precision mediump float;

layout (quads, cw, fractional_odd_spacing) in;

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

out float redValue;

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

    redValue = redInput;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;

out vec4 fragColor;

in float redValue;

void main()
{
    fragColor = vec4(redValue, 0., 0., 1.);
})";

    ANGLE_GL_PROGRAM_WITH_TESS(unusedProgram, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    // Delete the shader and recompile to fetch from cache.
    glDeleteProgram(unusedProgram);
    ANGLE_GL_PROGRAM_WITH_TESS(program, essl31_shaders::vs::Simple(), kTCS, kTES, kFS);
    ASSERT_GL_NO_ERROR();
    glUseProgram(program);

    constexpr GLfloat kInputData = 1.0f;
    GLBuffer input;
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, input);
    glBufferData(GL_SHADER_STORAGE_BUFFER, sizeof(GLfloat), &kInputData, GL_STATIC_COPY);
    glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, input);
    glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
    ASSERT_GL_NO_ERROR();

    drawPatches(program, essl31_shaders::PositionAttrib(), 0.5f, 1.0f, GL_FALSE);
    ASSERT_GL_NO_ERROR();

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

// Test that only macros for ESSL 1.0 compatible extensions are defined
TEST_P(GLSLTest, ESSL1ExtensionMacros)
{
    std::string fs = essl1_shaders::fs::Red();
    fs += ExpectedExtensionMacros({
        "GL_ANGLE_multi_draw",
        "GL_APPLE_clip_distance",
        "GL_ARB_texture_rectangle",
        "GL_ARM_shader_framebuffer_fetch",
        "GL_EXT_blend_func_extended",
        "GL_EXT_draw_buffers",
        "GL_EXT_frag_depth",
        "GL_EXT_separate_shader_objects",
        "GL_EXT_shader_framebuffer_fetch_non_coherent",
        "GL_EXT_shader_framebuffer_fetch",
        "GL_EXT_shader_non_constant_global_initializers",
        "GL_EXT_shader_texture_lod",
        "GL_EXT_shadow_samplers",
        "GL_KHR_blend_equation_advanced",
        "GL_NV_EGL_stream_consumer_external",
        "GL_OES_EGL_image_external",
        "GL_OES_standard_derivatives",
        "GL_OES_texture_3D",
    });
    fs += UnexpectedExtensionMacros({
        "GL_ANDROID_extension_pack_es31a",
        "GL_ANGLE_base_vertex_base_instance_shader_builtin",
        "GL_ANGLE_clip_cull_distance",
        "GL_ANGLE_shader_pixel_local_storage",
        "GL_ANGLE_texture_multisample",
        "GL_EXT_clip_cull_distance",
        "GL_EXT_geometry_shader",
        "GL_EXT_gpu_shader5",
        "GL_EXT_primitive_bounding_box",
        "GL_EXT_shader_io_blocks",
        "GL_EXT_tessellation_shader",
        "GL_EXT_texture_buffer",
        "GL_EXT_texture_cube_map_array",
        "GL_EXT_YUV_target",
        "GL_NV_shader_noperspective_interpolation",
        "GL_OES_EGL_image_external_essl3",
        "GL_OES_geometry_shader",
        "GL_OES_primitive_bounding_box",
        "GL_OES_sample_variables",
        "GL_OES_shader_image_atomic",
        "GL_OES_shader_io_blocks",
        "GL_OES_shader_multisample_interpolation",
        "GL_OES_texture_buffer",
        "GL_OES_texture_cube_map_array",
        "GL_OES_texture_storage_multisample_2d_array",
        "GL_OVR_multiview",
        "GL_OVR_multiview2",
    });
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.c_str());
    ASSERT_GL_NO_ERROR();
}

// Test that only macros for ESSL 3.0 compatible extensions are defined
TEST_P(GLSLTest_ES3, ESSL3ExtensionMacros)
{
    std::string fs = essl3_shaders::fs::Red();
    fs += ExpectedExtensionMacros({
        "GL_ANGLE_base_vertex_base_instance_shader_builtin",
        "GL_ANGLE_clip_cull_distance",
        "GL_ANGLE_multi_draw",
        "GL_ANGLE_shader_pixel_local_storage",
        "GL_ANGLE_texture_multisample",
        "GL_APPLE_clip_distance",
        "GL_ARB_texture_rectangle",
        "GL_ARM_shader_framebuffer_fetch",
        "GL_EXT_blend_func_extended",
        "GL_EXT_clip_cull_distance",
        "GL_EXT_separate_shader_objects",
        "GL_EXT_shader_framebuffer_fetch_non_coherent",
        "GL_EXT_shader_framebuffer_fetch",
        "GL_EXT_shader_non_constant_global_initializers",
        "GL_EXT_YUV_target",
        "GL_KHR_blend_equation_advanced",
        "GL_NV_EGL_stream_consumer_external",
        "GL_NV_shader_noperspective_interpolation",
        // Enabled on ESSL 3+ to workaround app bug. http://issuetracker.google.com/285871779
        "GL_OES_EGL_image_external",
        "GL_OES_EGL_image_external_essl3",
        // Enabled on ESSL 3+ to workaround app bug. http://issuetracker.google.com/285871779
        "GL_OES_texture_3D",
        "GL_OES_sample_variables",
        "GL_OES_shader_multisample_interpolation",
        // Enabled on ESSL 3+ because ANGLE can support multisample textures with ES 3.0 contexts.
        "GL_OES_texture_storage_multisample_2d_array",
        "GL_OVR_multiview",
        "GL_OVR_multiview2",
    });
    fs += UnexpectedExtensionMacros({
        "GL_ANDROID_extension_pack_es31a",
        "GL_EXT_draw_buffers",
        "GL_EXT_frag_depth",
        "GL_EXT_geometry_shader",
        "GL_EXT_gpu_shader5",
        "GL_EXT_primitive_bounding_box",
        "GL_EXT_shader_io_blocks",
        "GL_EXT_shader_texture_lod",
        "GL_EXT_shadow_samplers",
        "GL_EXT_tessellation_shader",
        "GL_EXT_texture_buffer",
        "GL_EXT_texture_cube_map_array",
        "GL_OES_geometry_shader",
        "GL_OES_primitive_bounding_box",
        "GL_OES_shader_image_atomic",
        "GL_OES_shader_io_blocks",
        "GL_OES_standard_derivatives",
        "GL_OES_texture_buffer",
        "GL_OES_texture_cube_map_array",
    });
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    ASSERT_GL_NO_ERROR();
}

// Test that only macros for ESSL 3.1 compatible extensions are defined
TEST_P(GLSLTest_ES31, ESSL31ExtensionMacros)
{
    std::string fs = essl31_shaders::fs::Red();
    fs += ExpectedExtensionMacros({
        "GL_ANDROID_extension_pack_es31a",
        "GL_ANGLE_base_vertex_base_instance_shader_builtin",
        "GL_ANGLE_clip_cull_distance",
        "GL_ANGLE_multi_draw",
        "GL_ANGLE_shader_pixel_local_storage",
        "GL_ANGLE_texture_multisample",
        "GL_APPLE_clip_distance",
        "GL_ARB_texture_rectangle",
        "GL_ARM_shader_framebuffer_fetch",
        "GL_EXT_blend_func_extended",
        "GL_EXT_clip_cull_distance",
        "GL_EXT_geometry_shader",
        "GL_EXT_gpu_shader5",
        "GL_EXT_primitive_bounding_box",
        "GL_EXT_separate_shader_objects",
        "GL_EXT_shader_framebuffer_fetch_non_coherent",
        "GL_EXT_shader_framebuffer_fetch",
        "GL_EXT_shader_io_blocks",
        "GL_EXT_shader_non_constant_global_initializers",
        "GL_EXT_tessellation_shader",
        "GL_EXT_texture_buffer",
        "GL_EXT_texture_cube_map_array",
        "GL_EXT_YUV_target",
        "GL_KHR_blend_equation_advanced",
        "GL_NV_EGL_stream_consumer_external",
        "GL_NV_shader_noperspective_interpolation",
        // Enabled on ESSL 3+ to workaround app bug. http://issuetracker.google.com/285871779
        "GL_OES_EGL_image_external",
        "GL_OES_EGL_image_external_essl3",
        // Enabled on ESSL 3+ to workaround app bug. http://issuetracker.google.com/285871779
        "GL_OES_texture_3D",
        "GL_OES_geometry_shader",
        "GL_OES_primitive_bounding_box",
        "GL_OES_sample_variables",
        "GL_OES_shader_image_atomic",
        "GL_OES_shader_io_blocks",
        "GL_OES_shader_multisample_interpolation",
        "GL_OES_texture_buffer",
        "GL_OES_texture_cube_map_array",
        "GL_OES_texture_storage_multisample_2d_array",
        "GL_OVR_multiview",
        "GL_OVR_multiview2",
    });
    fs += UnexpectedExtensionMacros({
        "GL_EXT_draw_buffers",
        "GL_EXT_frag_depth",
        "GL_EXT_shader_texture_lod",
        "GL_EXT_shadow_samplers",
        "GL_OES_standard_derivatives",
    });
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), fs.c_str());
    ASSERT_GL_NO_ERROR();
}

// Make sure names starting with webgl_ work.
TEST_P(GLSLTest_ES3, NameWithWebgl)
{
    constexpr char kFS[] = R"(#version 300 es
out highp vec4 webgl_color;
void main()
{
  webgl_color = vec4(0, 1, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Make sure webgl_FragColor works.
TEST_P(GLSLTest_ES3, NameWithWebglFragColor)
{
    constexpr char kFS[] = R"(#version 300 es
out highp vec4 webgl_FragColor;
void main()
{
  webgl_FragColor = vec4(0, 1, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that the ScalarizeVecAndMatConstructorArgs workaround works correctly with constructors that
// have no precision.  Regression test for a bug where the generated helper has no precision
// specified on the parameters and return value.
TEST_P(GLSLTest, ScalarizeVectorWorkaroundVsPrecisionlessConstructor)
{
    constexpr char kFS[] = R"(precision highp float;
void main() {
    bool b1 = true;
    float f1 = dot(vec4(b1 ? 1.0 : 0.0, 0.0, 0.0, 0.0), vec4(1.0));
    gl_FragColor = vec4(f1,0.0,0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that Metal compiler doesn't inline non-const globals
TEST_P(WebGLGLSLTest, InvalidGlobalsNotInlined)
{
    constexpr char kFS[] = R"(#version 100
  precision highp float;
  float v1 = 0.5;
  float v2 = v1;

  float f1() {
    return v2;
  }

  void main() {
    gl_FragColor = vec4(v1 + f1(),0.0,0.0, 1.0);
  })";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    ASSERT_GL_NO_ERROR();
}

// Test that a struct can have lots of fields.  Regression test for an inefficient O(n^2) check for
// fields having unique names.
TEST_P(GLSLTest_ES3, LotsOfFieldsInStruct)
{
    int maxUniformBlockSize = 0;
    glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
    ANGLE_SKIP_TEST_IF(maxUniformBlockSize < 16384 * 4);

    std::ostringstream fs;
    fs << R"(#version 300 es
precision highp float;
struct LotsOfFields
{
)";
    // Note: 16383 is the SPIR-V limit for struct member count.
    for (uint32_t i = 0; i < 16383; ++i)
    {
        fs << "    float field" << i << ";\n";
    }
    fs << R"(};
uniform B { LotsOfFields s; };
out vec4 color;
void main() {
    color = vec4(s.field0, 0.0, 0.0, 1.0);
})";

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

// Make sure an SSBO with a large array links successfully.
TEST_P(GLSLTest_ES31, LargeInterfaceBlockArray)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(std430) buffer Large { float a[65536]; };
out vec4 color;
void main() {
    color = vec4(a[0], 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
}

// Make sure an SSBO with a nested large array links successfully.
TEST_P(GLSLTest_ES31, LargeInterfaceBlockNestedArray)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
struct S { float a[65536]; };
layout(std430) buffer Large { S s; };
out vec4 color;
void main() {
    color = vec4(s.a[0], 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
}

// Regression test for const globals losing const qualifiers during MSL
// translation and exceeding available temporary registers on Apple GPUs.
TEST_P(GLSLTest_ES3, LargeConstGlobalArraysOfStructs)
{
    const int n = 128;
    std::stringstream fragmentShader;
    fragmentShader << "#version 300 es\n"
                   << "precision mediump float;\n"
                   << "uniform mediump int zero;\n"
                   << "out vec4 color;\n"
                   << "struct S { vec3 A; vec3 B; float C; };\n";
    for (int i = 0; i < 3; ++i)
    {
        fragmentShader << "const S s" << i << "[" << n << "] = S[" << n << "](\n";
        for (int j = 0; j < n; ++j)
        {
            fragmentShader << "  S(vec3(0., 1., 0.), vec3(" << j << "), 0.)"
                           << (j != n - 1 ? ",\n" : "\n");
        }
        fragmentShader << ");\n";
    }
    // To ensure that the array is not rescoped, it must be accessed from two functions.
    // To ensure that the array is not optimized out, it must be accessed with a dynamic index.
    fragmentShader << "vec4 foo() {\n"
                   << "  return vec4(s0[zero].A * s1[zero].A * s2[zero].A, 1.0);\n"
                   << "}\n"
                   << "void main() {\n"
                   << "  color = foo() * vec4(s0[zero].A * s1[zero].A * s2[zero].A, 1.0);\n"
                   << "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fragmentShader.str().c_str());

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

// Test that framebuffer fetch emulation does not add a user-visible uniform.
TEST_P(GLSLTest, FramebufferFetchDoesNotAddUniforms)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    static constexpr char kFS[] = R"(#version 100
#extension GL_EXT_shader_framebuffer_fetch : require
#extension GL_EXT_draw_buffers : require
uniform highp vec4 u_color;

void main (void)
{
    gl_FragData[0] = gl_LastFragData[0] + u_color;
    gl_FragData[1] = gl_LastFragData[1] + u_color;
    gl_FragData[2] = gl_LastFragData[2] + u_color;
    gl_FragData[3] = gl_LastFragData[3] + u_color;
})";

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

    GLint activeUniforms = 0, uniformsMaxLength = 0;
    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms);
    glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformsMaxLength);

    // There should be only one active uniform
    EXPECT_EQ(activeUniforms, 1);

    // And that is u_color
    GLsizei nameLen = uniformsMaxLength;
    std::vector<char> name(uniformsMaxLength);

    GLint size;
    GLenum type;

    glGetActiveUniform(program, 0, uniformsMaxLength, &nameLen, &size, &type, name.data());
    EXPECT_EQ(std::string(name.data()), "u_color");
    EXPECT_EQ(size, 1);
    EXPECT_EQ(type, static_cast<GLenum>(GL_FLOAT_VEC4));
}

// Test that framebuffer fetch emulation does not add a user-visible uniform.
TEST_P(GLSLTest_ES31, FramebufferFetchDoesNotAddUniforms)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));

    static constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_shader_framebuffer_fetch : require
layout(location = 0) inout highp vec4 o_color;

layout(std140, binding = 0) buffer outBlock {
    highp vec4 data[256];
};

uniform highp vec4 u_color;
void main (void)
{
    uint index = uint(gl_FragCoord.y) * 16u + uint(gl_FragCoord.x);
    data[index] = o_color;
    o_color += u_color;
})";

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

    GLint activeUniforms = 0, uniformsMaxLength = 0;
    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms);
    glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformsMaxLength);

    // There should be only one active uniform
    EXPECT_EQ(activeUniforms, 1);

    // And that is u_color
    GLsizei nameLen = uniformsMaxLength;
    std::vector<char> name(uniformsMaxLength);

    GLint size;
    GLenum type;

    glGetActiveUniform(program, 0, uniformsMaxLength, &nameLen, &size, &type, name.data());
    EXPECT_EQ(std::string(name.data()), "u_color");
    EXPECT_EQ(size, 1);
    EXPECT_EQ(type, static_cast<GLenum>(GL_FLOAT_VEC4));
}

// Test that advanced blend emulation does not add a user-visible uniform.
TEST_P(GLSLTest_ES31, AdvancedBlendEquationsDoesNotAddUniforms)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_KHR_blend_equation_advanced"));

    static constexpr char kFS[] = R"(#version 310 es
#extension GL_KHR_blend_equation_advanced : require

layout (blend_support_multiply) out;

out highp vec4 o_color;

layout(std140, binding = 0) buffer outBlock {
    highp vec4 data[256];
};

uniform highp vec4 u_color;
void main (void)
{
    o_color = u_color;
})";

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

    GLint activeUniforms = 0, uniformsMaxLength = 0;
    glGetProgramiv(program, GL_ACTIVE_UNIFORMS, &activeUniforms);
    glGetProgramiv(program, GL_ACTIVE_UNIFORM_MAX_LENGTH, &uniformsMaxLength);

    // There should be only one active uniform
    EXPECT_EQ(activeUniforms, 1);

    // And that is u_color
    GLsizei nameLen = uniformsMaxLength;
    std::vector<char> name(uniformsMaxLength);

    GLint size;
    GLenum type;

    glGetActiveUniform(program, 0, uniformsMaxLength, &nameLen, &size, &type, name.data());
    EXPECT_EQ(std::string(name.data()), "u_color");
    EXPECT_EQ(size, 1);
    EXPECT_EQ(type, static_cast<GLenum>(GL_FLOAT_VEC4));
}

// Test that with feature varyingsRequireMatchingPrecisionInSpirv enabled, and when tessellation
// control shader input and output varyings are of array type and they need precision adjustments,
// the spirv generated by ANGLE is correct. Adapted from
// dEQP-GLES31.functional.shaders.linkage.es31.tessellation.varying.rules.internal_different_precision
TEST_P(GLSLTest_ES31, PrecisionAdjustmentInTessellationControlShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader") &&
                       !IsGLExtensionEnabled("GL_OES_tessellation_shader"));
    constexpr char kVS[] = R"(#version 310 es
in highp vec4 pos;
in float in0;

out mediump float tc_in;
void main()
{
    tc_in = in0;
    gl_Position = pos;
})";

    constexpr char kTC[] = R"(#version 310 es
#extension GL_OES_tessellation_shader : require
layout (vertices=3) out;

in highp float tc_in[];
out highp float tc_out[];
void main()
{
    tc_out[gl_InvocationID] = tc_in[gl_InvocationID];
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
    gl_TessLevelInner[0] = 2.0;
    gl_TessLevelInner[1] = 2.0;
    gl_TessLevelOuter[0] = 2.0;
    gl_TessLevelOuter[1] = 2.0;
    gl_TessLevelOuter[2] = 2.0;
    gl_TessLevelOuter[3] = 2.0;
})";

    constexpr char kTE[] = R"(#version 310 es
#extension GL_OES_tessellation_shader : require
layout (triangles) in;

in lowp float tc_out[];
out mediump float te_out;
void main()
{
    te_out = tc_out[2];
    gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * gl_in[1].gl_Position + gl_TessCoord[2] * gl_in[2].gl_Position;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
bool isOk (float a, float b, float eps) { return (abs(a-b) <= (eps*abs(b) + eps)); }
layout(location = 0) out mediump vec4 fragColor;
uniform float ref_out0;
float out0;

in mediump float te_out;
void main()
{
    out0 = te_out;
    bool res = isOk(out0, ref_out0, 0.05);
    fragColor = vec4(res, res, res, 1.0);
}
)";

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    ANGLE_GL_PROGRAM_WITH_TESS(program, kVS, kTC, kTE, kFS);
    glUseProgram(program);
    glUniform1f(glGetUniformLocation(program, "ref_out0"), 1.0);
    GLint vertexPosLocation             = glGetAttribLocation(program, "pos");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    glVertexAttribPointer(vertexPosLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(vertexPosLocation);
    GLint vertexIn0Location          = glGetAttribLocation(program, "in0");
    std::array<float, 6> quadAttribs = {1.0, 1.0, 1.0, 1.0, 1.0, 1.0};
    glVertexAttribPointer(vertexIn0Location, 1, GL_FLOAT, GL_FALSE, 0, quadAttribs.data());
    glEnableVertexAttribArray(vertexIn0Location);
    glDrawArrays(GL_PATCHES, 0, 6);
    glDisableVertexAttribArray(vertexPosLocation);
    glVertexAttribPointer(vertexPosLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
    glDisableVertexAttribArray(vertexIn0Location);
    glVertexAttribPointer(vertexIn0Location, 1, GL_FLOAT, GL_FALSE, 0, nullptr);

    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::white);
}

// Similar to PrecisionAdjustmentInTessellationControlShader test, except that the tessellation
// control shader also read from tc_out written by other invocations. This is to test that with
// feature varyingsRequireMatchingPrecisionInSpirv enabled, write to tessellation control shader
// output are flushed after each tc_out[gl_InvocationID] = tc_in[gl_InvocationID], not at the end of
// the tessellation control shader, so that "reading from output written by other invocations" can
// work properly. This test also purposefully make below precisions in shader inputs and outputs:
// VS output: mediump
// TCS input: highp, TCS output: highp
// TES input: lowp
// In TCS, we do below conversions:
// multiply input with 50 to produce a number that is bigger than upper limit of mediump and write
// to output:
// output[gl_InvocationID] = input[gl_InvocationID] * 50.0;
// Read the output written by other invocations, and bring it down to a number that can be
// represented with lowp:
// output[gl_InvocationID] = resultFromOtherInvocation / (50.0 * 10000.0);
// This tests that with feature varyingsRequireMatchingPrecisionInSpirv enabled, if TCS output has
// higher precision than TES input, we are adjusting the precision of TES input, so that if we need
// to read values written by other invocations in the middle of TCS, they are not lost.
TEST_P(GLSLTest_ES31, PrecisionAdjustmentInTessellationControlShaderWithBarrier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader") &&
                       !IsGLExtensionEnabled("GL_OES_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(
        !getEGLWindow()->isFeatureEnabled(Feature::VaryingsRequireMatchingPrecisionInSpirv));
    constexpr char kVS[] = R"(#version 310 es
in highp vec4 pos;
in float in0;

out mediump float tc_in;
void main()
{
    tc_in = in0;
    gl_Position = pos;
})";

    constexpr char kTC[] = R"(#version 310 es
#extension GL_OES_tessellation_shader : require
layout (vertices=3) out;

in highp float tc_in[];
out highp float tc_out[];
void main()
{
    tc_out[gl_InvocationID] = tc_in[gl_InvocationID] * 50.0;
    barrier();
    float resultFromOtherInvocation = 0.0;
    if (gl_InvocationID==0) {
        resultFromOtherInvocation = tc_out[1];
    }
    if (gl_InvocationID==1) {
        resultFromOtherInvocation = tc_out[2];
    }
    if (gl_InvocationID==2) {
        resultFromOtherInvocation = tc_out[0];
    }
    barrier();
    tc_out[gl_InvocationID] = resultFromOtherInvocation / (50.0 * 10000.0);
    gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
    gl_TessLevelInner[0] = 2.0;
    gl_TessLevelInner[1] = 2.0;
    gl_TessLevelOuter[0] = 2.0;
    gl_TessLevelOuter[1] = 2.0;
    gl_TessLevelOuter[2] = 2.0;
    gl_TessLevelOuter[3] = 2.0;
})";

    constexpr char kTE[] = R"(#version 310 es
#extension GL_OES_tessellation_shader : require
layout (triangles) in;

in lowp float tc_out[];
out mediump float te_out;
void main()
{
    te_out = tc_out[2];
    gl_Position = gl_TessCoord[0] * gl_in[0].gl_Position + gl_TessCoord[1] * gl_in[1].gl_Position + gl_TessCoord[2] * gl_in[2].gl_Position;
}
)";

    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
bool isOk (float a, float b, float eps) { return (abs(a-b) <= (eps*abs(b) + eps)); }
layout(location = 0) out mediump vec4 fragColor;
uniform float ref_out0;
float out0;

in mediump float te_out;
void main()
{
    out0 = te_out;
    bool res = isOk(out0, ref_out0, 0.05);
    fragColor = vec4(res, res, res, 1.0);
}
)";

    glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    ANGLE_GL_PROGRAM_WITH_TESS(program, kVS, kTC, kTE, kFS);
    glUseProgram(program);
    glUniform1f(glGetUniformLocation(program, "ref_out0"), 3.0);
    GLint vertexPosLocation             = glGetAttribLocation(program, "pos");
    std::array<Vector3, 6> quadVertices = GetQuadVertices();
    glVertexAttribPointer(vertexPosLocation, 3, GL_FLOAT, GL_FALSE, 0, quadVertices.data());
    glEnableVertexAttribArray(vertexPosLocation);
    GLint vertexIn0Location          = glGetAttribLocation(program, "in0");
    std::array<float, 6> quadAttribs = {30000.0, 20000.0, 10000.0, 30000.0, 20000.0, 10000.0};
    glVertexAttribPointer(vertexIn0Location, 1, GL_FLOAT, GL_FALSE, 0, quadAttribs.data());
    glEnableVertexAttribArray(vertexIn0Location);
    glDrawArrays(GL_PATCHES, 0, 6);
    glDisableVertexAttribArray(vertexPosLocation);
    glVertexAttribPointer(vertexPosLocation, 4, GL_FLOAT, GL_FALSE, 0, nullptr);
    glDisableVertexAttribArray(vertexIn0Location);
    glVertexAttribPointer(vertexIn0Location, 1, GL_FLOAT, GL_FALSE, 0, nullptr);

    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::white);
}

// Tests struct in function return type.
TEST_P(GLSLTest, StructInFunctionDefinition)
{
    const char kFragmentShader[] = R"(precision mediump float;
struct Foo
{
    float v;
};

Foo foo()
{
    Foo f;
    f.v = 0.5;
    return f;
}

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    Foo f = foo();
    if (f.v == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

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

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

// Tests struct definition in function return type.
TEST_P(GLSLTest, StructDefinitionInFunctionDefinition)
{
    const char kFragmentShader[] = R"(precision mediump float;
struct Foo { float v; } foo()
{
    Foo f;
    f.v = 0.5;
    return f;
}

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    Foo f = foo();
    if (f.v == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

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

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

// Test struct definition in forward declaration of function return type.
TEST_P(GLSLTest, StructDefinitionInFunctionPrototype)
{
    const char kFragmentShader[] = R"(precision mediump float;
struct Foo { float v; } foo();

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    Foo f = foo();
    if (f.v == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
}

Foo foo()
{
    Foo f;
    f.v = 0.5;
    return f;
})";

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

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

// Test that struct declarations are introduced into the correct scope.
TEST_P(GLSLTest, StructDefinitionInFunctionPrototypeScope)
{
    const char kFragmentShader[] = R"(precision mediump float;

struct Foo { float v; } foo()
{
    Foo f;
    f.v = 0.5;
    return f;
}

struct Bar { Foo f; } bar()
{
    Bar b;
    b.f = foo();
    return b;
}

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    Bar b = bar();
    if (b.f.v == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
})";

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

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

// Test that a struct used without any variables instantiated out of it works.
TEST_P(GLSLTest, StructUsedWithoutVariable)
{
    const char kFragmentShader[] = R"(precision mediump float;

struct S
{
    float f;
    int i;
};

void main()
{
    float f = S(0.5, 2).f;
    gl_FragColor = vec4(
        f,
        S(0.75, 1).i,
        0, 1);
})";

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

    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 255, 0, 255), 1);
}

// Test that struct declarations are introduced into the correct scope.
TEST_P(GLSLTest, NestedReturnedStructs)
{
    const char kFragmentShader[] = R"(precision mediump float;
struct Foo { float v; } foo(float bar);

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    float v = foo(foo(0.5).v).v;
    if (v == 0.5)
    {
        gl_FragColor = vec4(0, 1, 0, 1);
    }
}

Foo foo(float bar)
{
    Foo f;
    f.v = bar;
    return f;
})";

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

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

// Test that vec equality works.
TEST_P(GLSLTest, VecEquality)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform vec4 u;
void main()
{
    gl_FragColor = vec4(0, 0, 0, 1);
    vec4 a = vec4(1.0, 2.0, 3.0, 4.0);
    if (a == u)
        gl_FragColor.g = 1.0;

    vec4 b = vec4(1.0) + u;
    if (b == u)
        gl_FragColor.r = 1.0;
}
)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint u = glGetUniformLocation(program, "u");
    glUniform4f(u, 1, 2, 3, 4);

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

// Test that mat equality works.
TEST_P(GLSLTest, MatEquality)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform vec4 u;
void main()
{
    gl_FragColor = vec4(0, 0, 0, 1);
    mat4 a = mat4(1,2,3,4,1,2,3,4,1,2,3,4,1,2,3,4);
    if (a == mat4(u, u, u, u))
        gl_FragColor.g = 1.0;
    mat4 b = mat4(1.0);
    if (b == mat4(u, u, u, u))
        gl_FragColor.r = 1.0;
}
)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint u = glGetUniformLocation(program, "u");
    glUniform4f(u, 1, 2, 3, 4);

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

// Test that struct equality works.
TEST_P(GLSLTest, StructEquality)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform vec4 u;
struct A {
    vec4 i;
};
void main()
{
    gl_FragColor = vec4(0, 0, 0, 1);
    A a, b;
    a.i = vec4(1,2,3,4);
    b.i = u;
    if (a == b)
        gl_FragColor.g = 1.0;
    b.i = vec4(1.0);
    if (a == b)
        gl_FragColor.r = 1.0;
}
)";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint u = glGetUniformLocation(program, "u");
    glUniform4f(u, 1, 2, 3, 4);

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

// Test that nested struct equality works.
TEST_P(GLSLTest, NestedStructEquality)
{
    const char kFragmentShader[] = R"(precision mediump float;
uniform vec4 u;
struct A {
    vec4 i;
};
struct B {
    A a;
};
void main()
{
    gl_FragColor = vec4(0, 0, 0, 1);
    B a, b;
    a.a.i = vec4(1,2,3,4);
    b.a.i = u;
    if (a == b)
        gl_FragColor.g = 1.0;
    b.a.i = vec4(1.0);
    if (a == b)
        gl_FragColor.r = 1.0;
}
)";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFragmentShader);
    glUseProgram(program);
    GLint u = glGetUniformLocation(program, "u");
    glUniform4f(u, 1, 2, 3, 4);

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

// Test that double underscores user defined name is allowed
TEST_P(GLSLTest_ES3, DoubleUnderscoresName)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 oColor;
uniform struct __Data {float red;} data;
void main() {oColor=vec4(data.red,0,1,1);})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    // populate uniform
    GLint uniformLocation = glGetUniformLocation(program, "data.red");
    EXPECT_NE(uniformLocation, -1);
    glUniform1f(uniformLocation, 0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    ASSERT_GL_NO_ERROR();
}

// Test that user defined name starts with "ANGLE" or "ANGLE_"
TEST_P(GLSLTest_ES3, VariableNameStartsWithANGLE)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 oColor;
uniform struct ANGLEData{float red;} data;
uniform struct ANGLE_Data{float green;} _data;
void main() {oColor=vec4(data.red,_data.green,1,1);})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    // populate uniform
    GLint uniformRedLocation   = glGetUniformLocation(program, "data.red");
    GLint uniformGreenLocation = glGetUniformLocation(program, "_data.green");
    EXPECT_NE(uniformRedLocation, -1);
    EXPECT_NE(uniformGreenLocation, -1);
    glUniform1f(uniformRedLocation, 0);
    glUniform1f(uniformGreenLocation, 0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    ASSERT_GL_NO_ERROR();
}

// Test that underscores in array names work with out arrays.
TEST_P(GLSLTest_ES3, UnderscoresWorkWithOutArrays)
{
    GLFramebuffer fbo;
    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo);

    GLTexture textures[4];
    for (size_t texIndex = 0; texIndex < ArraySize(textures); texIndex++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWindowWidth(), getWindowHeight(), 0, GL_RGBA,
                     GL_UNSIGNED_BYTE, nullptr);
    }

    GLint maxDrawBuffers;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ASSERT_GE(maxDrawBuffers, 4);

    GLFramebuffer readFramebuffer;
    glBindFramebuffer(GL_READ_FRAMEBUFFER, readFramebuffer);

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 _e[4];
void main()
{
    _e[0] = vec4(1.0, 0.0, 0.0, 1.0);
    _e[1] = vec4(0.0, 1.0, 0.0, 1.0);
    _e[2] = vec4(0.0, 0.0, 1.0, 1.0);
    _e[3] = vec4(1.0, 1.0, 1.0, 1.0);
}
)";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    constexpr GLenum kAllBufs[4] = {GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1,
                                    GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3};
    constexpr GLuint kMaxBuffers = 4;
    // Enable all draw buffers.
    for (GLuint texIndex = 0; texIndex < kMaxBuffers; texIndex++)
    {
        glBindTexture(GL_TEXTURE_2D, textures[texIndex]);
        glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + texIndex, GL_TEXTURE_2D,
                               textures[texIndex], 0);
        glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + texIndex, GL_TEXTURE_2D,
                               textures[texIndex], 0);
    }
    glDrawBuffers(kMaxBuffers, kAllBufs);

    // Draw with simple program.
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    ASSERT_GL_NO_ERROR();
    verifyAttachment2DColor(0, textures[0], GL_TEXTURE_2D, 0, GLColor::red);
    verifyAttachment2DColor(1, textures[1], GL_TEXTURE_2D, 0, GLColor::green);
    verifyAttachment2DColor(2, textures[2], GL_TEXTURE_2D, 0, GLColor::blue);
    verifyAttachment2DColor(3, textures[3], GL_TEXTURE_2D, 0, GLColor::white);
}

// Make sure there is no name look up clash when initializing output variables
TEST_P(GLSLTest_ES3_InitShaderVariables, NameLookup)
{
    constexpr char kFS[] = R"(#version 300 es
out highp vec4 color;

void make_color_active()
{
    color.zw = vec2(1.0);
}

void main()
{
    highp vec4 color;
    color.x = 1.0;
    make_color_active();
}
)";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    ASSERT_GL_NO_ERROR();
}

// Make sure unnamed `out` function parameters are initialized correctly.
TEST_P(GLSLTest_ES3_InitShaderVariables, UnnamedOutParamInPrototype)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 o;
void f(out float, out float);
void main()
{
    o = vec4(0.5);
    // o.g is reset to 0 because of the forceInitShaderVariables feature
    f(o.r, o.g);
}
void f(out float r, out float)
{
    r = 1.0;
}
)";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(255, 0, 127, 127), 1);
}

// Same as UnnamedOutParamInPrototype, but with nested calls.
TEST_P(GLSLTest_ES3_InitShaderVariables, UnnamedOutParamInPrototype2)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 o;
void f(out float, out float);
void g(out float a, out float b)
{
    f(a, b);
}
void main()
{
    o = vec4(0.75);
    // o.g is reset to 0 because of the forceInitShaderVariables feature
    g(o.r, o.g);
}
void f(out float r, out float)
{
    r = 0.5;
}
)";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 0, 191, 191), 1);
}

// Same as UnnamedOutParamInPrototype, but with a non-void function.
TEST_P(GLSLTest_ES3_InitShaderVariables, UnnamedOutParamInPrototype3)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 o;
float f(out float r, out float)
{
    r = 0.75;
    return 0.25;
}
void main()
{
    o = vec4(0.5);
    float z = f(o.r, o.g);
    o.z = z;
}
)";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(191, 0, 63, 127), 1);
}

// Test that lowp and mediump varyings can be correctly matched between VS and FS.
TEST_P(GLSLTest, LowpMediumpVarying)
{
    const char kVS[] = R"(varying lowp float lowpVarying;
attribute vec4 position;
void main ()
{
  lowpVarying = 1.;
  gl_Position = position;
})";

    const char kFS[] = R"(varying mediump float lowpVarying;
void main ()
{
  gl_FragColor = vec4(lowpVarying, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    drawQuad(program, "position", 0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that a mismatched varying that is unused in fragshader compiles.
TEST_P(GLSLTest, MismatchedInactiveVarying)
{
    const char kVS[] = R"(precision mediump float;

attribute vec2 a_position_0;
attribute vec4 a_color_0;

varying vec4 vertexColor;

void main()
{
    vertexColor = a_color_0;
    gl_Position = vec4(a_position_0, 0.0, 1.0);
})";

    const char kFS[] = R"(precision highp float;

uniform vec2 resolution;
uniform vec2 fragOffset;
uniform vec2 fragScale;

varying vec4 vertexColor;
varying vec2 texCoord;

void main()
{
    vec2 uv = (fragScale * gl_FragCoord.xy + fragOffset) / resolution;
    gl_FragColor = vec4(uv, 1.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that denorm float values in GLSL are preserved
TEST_P(GLSLTest_ES3, DenormFloatsToIntValues)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan());

    constexpr char kFS[] =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 out_color;\n"
        "uniform float u;\n"
        "void main()\n"
        "{\n"
        "   float smallDenormFloat = 1.40129846e-45;\n"
        "   int smallBits = floatBitsToInt(smallDenormFloat);\n"
        "   bool smallCorrect = smallBits == 1;\n"
        "\n"
        "   float largeDenormFloat = 1.1754942107e-38f;\n"
        "   int largeBits = floatBitsToInt(largeDenormFloat);\n"
        "   bool largeCorrect = largeBits == 0x007FFFFF;\n"
        "\n"
        "   out_color = (smallCorrect && largeCorrect)\n"
        "             ? vec4(0.0, 1.0, 0.0, 1.0)\n"
        "             : vec4(1.0, 0.0, 0.0, 1.0);\n"
        "}\n";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test output initialization vs. fragment output arrays
TEST_P(WebGL2GLSLTest, FragmentOutputArray)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
layout(location = 0) out vec4 activeColor;
layout(location = 1) out vec4 inactive[3];
void main() {
    // Make activeColor active without fully initializing it.
    activeColor.x += 0.0001;
})";

    glClearColor(100, 200, 50, 150);
    glClear(GL_COLOR_BUFFER_BIT);

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f, 1.0f, true);

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

// Tests that outparams work when used with globals.
TEST_P(GLSLTest, OutparamWorksGlobal)
{
    constexpr char kFS[] =
        R"(
        precision mediump float;
        float globalX = 0.5;
        void f(inout float x) {
            x += x;
        }
        void main() {
            f (globalX);
            gl_FragColor = vec4(globalX, 1.0, 1.0, 1.0); // white
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that outparams work when used with temporary arrays.
TEST_P(GLSLTest, OutparamWorksTempArray)
{
    constexpr char kFS[] =
        R"(
        precision mediump float;
        void f(inout float x, out float y, in float val) {
            y = x;
            x = val;
        }
        void main() {
            float arr[3];
            arr[0] = 1.0;
            arr[1] = 0.25;
            arr[2] = 127.0/255.0;
            f (arr[0], arr[1], arr[2]);
            // arr[1] = arr[0] = 1.0;
            // arr[0] = arr[2] = 0.5;
            gl_FragColor = vec4(arr[0], arr[1], arr[2], 1.0);
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(127u, 255u, 127u, 255u));
}

// Checks (add-)assigning to a multielement-swizzle where both the LHS and RHS have side effects.
// Doesn't test the LHS side effects.
TEST_P(GLSLTest, ComplicatedMultielementSwizzle1)
{

    constexpr char kFS[] =
        R"(
        precision mediump float;
        void main() {
            int i = 0;
            float a = 0.0;
            vec4 vecs[2];
            vecs[0] = vec4(0.0);
            vecs[1] = vec4(1.0);
            vecs[++i].yz += vec2(a++, a++); // vecs[1].yz += vec2(0.0, 1.0);

            gl_FragColor = vecs[1]/255.0;  // 1, 1, 2, 1
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(1, 1, 2, 1));
}

// Checks (add-)assigning to a multielement-swizzle where both the LHS and RHS have side effects.
// See GLSL ES 3.0 Spec. 5.8 Assignments:
// "Expressions on the left of an assignment are evaluated before expressions on the right of the
// assignment."
TEST_P(GLSLTest, ComplicatedMultielementSwizzle2)
{

    constexpr char kFS[] =
        R"(
        precision mediump float;
        void main() {
            int i = 0;
            float a = 0.0;
            vec4 vecs[2];
            vecs[0] = vec4(0.0);
            vecs[1] = vec4(1.0);
            // Essentially vecs[0].yz = (vecs[1].xy += vec2(0.0, 1.0));
            // Resulting in:
            // vecs[1] = vec4(1.0, 2.0, 1.0, 1.0);
            // So vecs[1].xy = vec2(1.0, 2.0);
            // So vecs[0] = vec4(0.0, 1.0, 2.0, 0.0);
            vecs[i++].yz = (vecs[i++].xy += vec2(a++, a++));

            gl_FragColor = vec4(vecs[0].yz, vecs[1].zw)/255.;  // 1, 2, 1, 1
        })";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(1, 2, 1, 1));
}

// Checks that ternaries work with params.
TEST_P(GLSLTest, TernariesWithParams1)
{
    constexpr char kFS[] =
        R"(precision mediump float;

float func (in float a, in float b)
{
    return a > b ? -1.0 : 1.0;
}

void main()
{
    float x = 1.0;
    float m = func(x, x); // returns 1.0
    gl_FragColor = vec4(m, m, m, 1.0);
}

)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Checks that ternaries work with params and the return value is passed to an outparam.
TEST_P(GLSLTest, TernariesWithParams2)
{
    constexpr char kFS[] =
        R"(precision mediump float;

void func (in float a, in float b, out float ret)
{
    ret = a > b ? -1.0 : 1.0;
}

void main()
{
    float x = 1.0;
    float m;
    func(x, x, m); // returns 1.0
    gl_FragColor = vec4(m, m, m, 1.0);
}

)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Checks that ternaries work with params where one is an inout param. The inout param is read from
// and written to.
TEST_P(GLSLTest, TernariesWithParams3)
{
    constexpr char kFS[] =
        R"(precision mediump float;

void func (in float a, inout float b)
{
    b = a > b ? -1.0 : 1.0;
}

void main()
{
    float x = 1.0;
    func(x, x); // x = 1.0
    gl_FragColor = vec4(x, x, x, 1.0);
}

)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that calling a function with an in parameter as an inout argument works.
TEST_P(GLSLTest, NestedInoutVars1)
{

    constexpr char kFS[] =
        R"(precision mediump float;

void negate (inout float x);

float func (in float a, in float b)
{
    negate(a);
    if (abs(a-b) < 0.1) {
        return -1.0;
    } else {
        return 1.0;
    }
}

void main()
{
    float x = 1.0;
    float m = func(x, x); // returns 1.0
    gl_FragColor = vec4(m, m, m, 1.0);
}

void negate (inout float x) { x = -x; }

)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that calling a function with inout parameters as in, out, and inout arguments works.
TEST_P(GLSLTest, NestedInoutVars2)
{
    constexpr char kFS[] =
        R"(precision mediump float;

    float get (in float x);
    void set (out float x, in float val);
    void negate (inout float x);

    float func (inout float a, inout float b)
    {
        negate(a);
        return abs(a - b) < 0.1 ? -1.0 : 1.0;
    }

    void main()
    {
        float x;
        set(x, 1.0);
        float m = func(x, x); // returns 1.0
        gl_FragColor = vec4(m, m, m, 1.0);
    }

    float get (in float x) { return x; }
    void set (out float x, in float val) { x = val; }
    void negate (inout float x) { set(x, -get(x)); }

    )";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Tests that calling a function with out parameters as out arguments works.
TEST_P(GLSLTest, NestedInoutVars3)
{
    constexpr char kFS[] =
        R"(precision mediump float;

    float globVar;

    void g (out float y, out float x) {
        x = 127.0/255.0;
        y = 1.0;
    }

    void f (out float x) {
        g(x, x);
    }

    void main()
    {
        f (globVar);
        gl_FragColor = vec4(globVar, 0.0, 0.0, 1.0);
    }

    )";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    ASSERT_GL_NO_ERROR();

    // According to the GLES spec, it's undefined which order outparams are copied out, so globVar
    // could have ended with either 0.5 or 1.0.
    GLColor color = angle::ReadColor(0, 0);
    ASSERT_GL_NO_ERROR();
    if (color != GLColor::red && color != GLColor(127u, 0u, 0u, 255u))
    {
        ADD_FAILURE() << "Got " << color << ", expected white or grey";
    }
}

// Test that nesting rgb_2_yuv and yuv_2_rgb works.
TEST_P(GLSLTest_ES3, rgbYuvBuiltInNesting1)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    const char kFS[] = R"(#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
const yuvCscStandardEXT conv = itu_601;

out vec4 color;

void main()
{
    vec3 rgb = yuv_2_rgb(rgb_2_yuv(vec3(0.2, 0.3, 0.4), conv), conv);
    color = vec4(rgb, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 77, 102, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that nesting rgb_2_yuv and yuv_2_rgb works.
TEST_P(GLSLTest_ES3, rgbYuvBuiltInNesting2)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    const char kFS[] = R"(#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
const yuvCscStandardEXT conv = itu_601_full_range;

out vec4 color;

void main()
{
    vec3 rgb = yuv_2_rgb(vec3(0.1, 0.2, 0.3) + rgb_2_yuv(vec3(0.15, 0.25, 0.35), conv), conv);
    color = vec4(rgb, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(171, 17, 205, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that nesting rgb_2_yuv and yuv_2_rgb works.
TEST_P(GLSLTest_ES3, rgbYuvBuiltInNesting3)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    const char kFS[] = R"(#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
const yuvCscStandardEXT conv = itu_709;

out vec4 color;

vec3 f(vec3 v) { return v + vec3(0.1); }

void main()
{
    vec3 rgb = yuv_2_rgb(f(rgb_2_yuv(vec3(0.1, 0.2, 0.3), conv)), conv);
    color = vec4(rgb, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(102, 62, 161, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that nesting rgb_2_yuv and yuv_2_rgb works.
TEST_P(GLSLTest_ES3, rgbYuvBuiltInNesting4)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    const char kFS[] = R"(#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
const yuvCscStandardEXT conv = itu_601;

out vec4 color;

vec3 f(vec3 v) { return v + vec3(0.1); }

void main()
{
    vec3 rgb = f(yuv_2_rgb(f(rgb_2_yuv(vec3(0.1, 0.2, 0.3), conv)), conv));
    color = vec4(rgb, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(122, 75, 183, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that using rgb_2_yuv and yuv_2_rgb with different precisions work.
TEST_P(GLSLTest_ES3, rgbYuvBuiltInUsedWithDifferentPrecisions)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    const char kFS[] = R"(#version 300 es
#extension GL_EXT_YUV_target: require
precision mediump float;
const yuvCscStandardEXT conv = itu_601;

out vec4 color;

void main()
{
    lowp vec3 rgbLow = vec3(0.1);
    mediump vec3 rgbMedium = vec3(0.2);
    highp vec3 rgbHigh = vec3(0.3);

    lowp vec3 yuvLow = vec3(0.4);
    mediump vec3 yuvMedium = vec3(0.5);
    highp vec3 yuvHigh = vec3(0.6);

    color = vec4(
            rgb_2_yuv(rgbLow, conv) - rgb_2_yuv(rgbMedium, conv) + rgb_2_yuv(rgbHigh, conv) -
            yuv_2_rgb(yuvLow, conv) - yuv_2_rgb(yuvMedium, conv) + yuv_2_rgb(yuvHigh, conv),
        1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(72, 0, 161, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that link fails if UBO size is larger than MAX.
TEST_P(GLSLTest_ES3, UBOExceedsMaxSize)
{
    int maxUniformBlockSize = 0;
    glGetIntegerv(GL_MAX_UNIFORM_BLOCK_SIZE, &maxUniformBlockSize);
    // ANGLE limits the UBO size to 64KB, so in practice the test always runs.
    ANGLE_SKIP_TEST_IF(maxUniformBlockSize > 65536);

    const char kFS[] = R"(#version 300 es
precision mediump float;
struct S64
{
    vec4 f[64];
};

layout(std140) uniform Block {
    S64 arr[256];
};

out vec4 color;

void main()
{
    color = vec4(arr[10].f[15]);
})";

    GLuint program = CompileProgram(essl3_shaders::vs::Simple(), kFS);
    EXPECT_EQ(0u, program);
}

// Test that link fails if SSBO size is larger than MAX.
TEST_P(GLSLTest_ES31, SSBOExceedsMaxSize)
{
    int maxShaderStorageBlockSize = 0;
    glGetIntegerv(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &maxShaderStorageBlockSize);
    // ANGLE limits the SSBO size to INT_MAX, so in practice the test always runs.
    ANGLE_SKIP_TEST_IF(maxShaderStorageBlockSize > 0x7FFF'FFFF);

    const char kFS[] = R"(#version 310 es
precision mediump float;
struct S
{
    vec4 f[8192];
};

layout(std140) buffer Block {
    S arr[32768];
};

out vec4 color;

void main()
{
    color = vec4(arr[10].f[15]);
})";

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

// Test that an unused gl_LastFragDepthARM does not lead to errors
TEST_P(GLSLTest_ES31, UnusedLastFragDepth)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));

    const char kFS[] = R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil:require
mediump float gl_LastFragDepthARM;
void main()
{
    gl_FragColor = vec4(0, 1, 0, 1);
}
)";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    ASSERT_GL_NO_ERROR();
}

// Test that inactive fragment inout variable compiles fine, with inout locations start from
// non-zero
TEST_P(GLSLTest_ES31, InactiveFragmentInout)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));
    const char kVS[] = R"(#version 310 es
        in vec3 POSITION;
        void main()
        {
            gl_Position = vec4(POSITION, 1.0);
        })";
    const char kFS[] = R"(#version 310 es
        #extension GL_EXT_shader_framebuffer_fetch : require
        precision mediump float;
        precision highp int;
        layout(location = 1) inout highp vec4 activeVar;
        layout(location = 2) inout highp vec4 inactiveVar;
        void main()
        {
            activeVar = vec4(0.0, 0.0, 0.0, 1.0);
        })";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that inactive fragment inout variable used in an unused function compiles fine, with inout
// locations start from non-zero
TEST_P(GLSLTest_ES31, InactiveFragmentInoutReferencedInUnusedFunction)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));
    const char kVS[] = R"(#version 310 es
        in vec3 POSITION;
        void main()
        {
            gl_Position = vec4(POSITION, 1.0);
        })";
    const char kFS[] = R"(#version 310 es
        #extension GL_EXT_shader_framebuffer_fetch : require
        precision mediump float;
        precision highp int;
        layout(location = 1) inout highp vec4 activeVar;
        layout(location = 2) inout highp vec4 inactiveVar;

        void unusedFunction() {
            vec4 unusedVar = inactiveVar;
        }

        void main()
        {
            activeVar = vec4(0.0, 0.0, 0.0, 1.0);
        })";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
}

// Test that if the inout variable is inactive, its' location attachment content is preserved
TEST_P(GLSLTest_ES31, testInactiveInoutPreservesAttachmentContent)
{
    const unsigned int kWidth  = 2;
    const unsigned int kHeight = 2;

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));
    const char kFS[] = R"(#version 310 es
        #extension GL_EXT_shader_framebuffer_fetch : require
        precision mediump float;
        precision highp int;
        layout(location = 1) inout highp vec4 activeVar;
        layout(location = 2) inout highp vec4 inactiveVar;

        void main()
        {
            activeVar = vec4(0.0, 1.0, 0.0, 1.0);
        })";
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);

    GLTexture texture1;
    glBindTexture(GL_TEXTURE_2D, texture1);
    // Fill texture1 with red color
    const std::array<GLColor, kHeight * kHeight> texture1Data = {GLColor::red, GLColor::red,
                                                                 GLColor::red, GLColor::red};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 &texture1Data[0]);
    GLTexture texture2;
    glBindTexture(GL_TEXTURE_2D, texture2);
    // Fill texture2 with blue color
    const std::array<GLColor, kHeight * kHeight> texture2Data = {GLColor::blue, GLColor::blue,
                                                                 GLColor::blue, GLColor::blue};
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kHeight, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
                 &texture2Data[0]);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT1, GL_TEXTURE_2D, texture1, 0);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT2, GL_TEXTURE_2D, texture2, 0);

    GLenum drawBuffers[] = {GL_NONE, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2};
    glDrawBuffers(3, drawBuffers);
    glUseProgram(program);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);

    // Verify that texture1 changes to green, texture2 remains unchanged.
    GLFramebuffer verifyTextureFBO;
    glBindFramebuffer(GL_FRAMEBUFFER, verifyTextureFBO);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture1, 0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture2, 0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
}

// Tests pack/unpack emulation in the shader translator.
class GLSLTest_ES3_PackUnpackEmulation : public GLSLTest_ES3
{
  protected:
    void testUnsignedInt(GLuint program, uint32_t expect);
    void testFloat(GLuint program, float r, float g, float b, float a);
};

void GLSLTest_ES3_PackUnpackEmulation::testUnsignedInt(GLuint program, uint32_t expect)
{
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32I, 1, 1);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_GL_NO_ERROR();

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_EQ(expect, static_cast<uint32_t>(GetFirstIntPixelRedValue()));
    EXPECT_GL_NO_ERROR();
}

void GLSLTest_ES3_PackUnpackEmulation::testFloat(GLuint program, float r, float g, float b, float a)
{
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, 1, 1);
    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    EXPECT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
    EXPECT_GL_NO_ERROR();

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_32F_NEAR(0, 0, r, g, b, a, 1e-6);
    EXPECT_GL_NO_ERROR();
}

// Verify correct emulation of packSnorm2x16
TEST_P(GLSLTest_ES3_PackUnpackEmulation, PackSnorm2x16)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp int;
uniform vec2 u;
out uvec4 color;
void main()
{
   uint v = packSnorm2x16(u);
   color = uvec4(v, 0, 0, 0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform2f(glGetUniformLocation(program, "u"), 4096.0 / 32767.0, -8192.0 / 32767.0);
    testUnsignedInt(program, 0xE000'1000);
}

// Verify correct emulation of unpackSnorm2x16
TEST_P(GLSLTest_ES3_PackUnpackEmulation, UnpackSnorm2x16)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp int;
uniform uint u;
out vec4 color;
void main()
{
   vec2 v = unpackSnorm2x16(u);
   color = vec4(v, 0.0, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform1ui(glGetUniformLocation(program, "u"), 0xE0001000);
    testFloat(program, 4096.0 / 32767.0, -8192.0 / 32767.0, 0, 0);
}

// Verify correct emulation of packUnorm2x16
TEST_P(GLSLTest_ES3_PackUnpackEmulation, PackUnorm2x16)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp int;
uniform vec2 u;
out uvec4 color;
void main()
{
   uint v = packUnorm2x16(u);
   color = uvec4(v, 0, 0, 0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform2f(glGetUniformLocation(program, "u"), 64.0 / 65535.0, 16384.0 / 65535.0);
    testUnsignedInt(program, 0x4000'0040);
}

// Verify correct emulation of unpackSnorm2x16
TEST_P(GLSLTest_ES3_PackUnpackEmulation, UnpackUnorm2x16)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp int;
uniform uint u;
out vec4 color;
void main()
{
   vec2 v = unpackUnorm2x16(u);
   color = vec4(v, 0.0, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform1ui(glGetUniformLocation(program, "u"), 0x4000'0040);
    testFloat(program, 64.0 / 65535.0, 16384.0 / 65535.0, 0, 0);
}

// Verify correct emulation of packHalf2x16
TEST_P(GLSLTest_ES3_PackUnpackEmulation, PackHalf2x16)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp int;
uniform vec2 u;
out uvec4 color;
void main()
{
    uint v = packHalf2x16(u);
    color = uvec4(v, 0, 0, 0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform2f(glGetUniformLocation(program, "u"), 47.03125, -20.6875);
    testUnsignedInt(program, 0xCD2C'51E1);
}

// Verify correct emulation of unpackHalf2x16
TEST_P(GLSLTest_ES3_PackUnpackEmulation, UnpackHalf2x16)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision highp int;
uniform uint u;
out vec4 color;
void main()
{
    vec2 v = unpackHalf2x16(u);
    color = vec4(v, 0.0, 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform1ui(glGetUniformLocation(program, "u"), 0xCD2C'51E1);
    testFloat(program, 47.03125, -20.6875, 0, 0);
}

// Regression test for a bug where the translator assumed the last case can be simply pruned because
// it is no-op.
TEST_P(GLSLTest_ES3, EmptyLastCaseInSwitch)
{
    constexpr char kFS[] = R"(#version 300 es
uniform int ui;
out mediump vec4 color;

void main(void)
{
    color = vec4(1, 0, 0, 1);

    int i = ui;
    switch (i)
    {
        default:
            // This shouldn't run, because `ui` is zero.
            color = vec4(0, 1, 0, 1);
            break;
        case 0:
            int j;
            1;
    }
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Regression test for codegen errors when the last case of a switch is dead-code-eliminated.
TEST_P(GLSLTest_ES3, EmptyLastCaseInSwitch2)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int a, b;
out vec4 color;
void main() {
    float r = 0.25;
    switch(a) {
        case 0:
            switch(b) {
                default:
                    r = 0.75;
                case 0:
                    switch(b) { }
            }
            break;
        default:
            r = 0.5;
    }
    color = vec4(r, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(63, 0, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that if the uniform struct specifier is referenced by non-uniform variables, the
// sortUniforms() step does not reorder the uniform struct specifier and place it after where it is
// referenced.
TEST_P(GLSLTest_ES3, UniformStructSpecifierIsReferencedByNonUniforms)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform struct b{vec2 S;}t;
struct{b x;};
out vec4 color;
void main()
{
    color = vec4(1.0, 0.0, 0.0, 1.0);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but used in the next case.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode1)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    switch(u0){
        case 0:
            break;
            vec4 d = vec4(0);
        default:
            d.a = .0;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but used in the next case.
// The dead code is after divergence and reconvergence.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode2)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    switch(u0){
        case 0:
            vec4 c = vec4(0);
            if(u0 == 0)
            {
                fragOut = vec4(0, 1, 0, 1);
            }
            break;
            vec4 d = vec4(0);
        default:
            c.r = .1;
            d.a = .0;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but used in the next case.
// The dead code itself has divergence and reconvergence.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode3)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    switch(u0){
        case 0:
            if(u0 == 0)
            {
                fragOut = vec4(0, 1, 0, 1);
            }
            break;
            vec4 c = vec4(0);
            if(u0 == 0)
            {
                fragOut = vec4(0, 1, 0, 1);
            }
            vec4 d = vec4(0);
        default:
            c.r = .1;
            d.a = .0;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but nested inside a block.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode4)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    switch(u0){
        case 0:
            if(u0 == 0)
            {
                fragOut = vec4(0, 1, 0, 1);
            }
            break;
            if(u0 == 0)
            {
                vec4 d = vec4(0);
                d.a = .0;
            }
        default:
            break;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but nested inside a block.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode5)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    switch(u0){
        case 0:
            if(u0 == 0)
            {
                fragOut = vec4(0, 1, 0, 1);
            }
            if(u0 == 0)
            {
                break;
                vec4 d = vec4(0);
                d.a = .0;
            }
        default:
            break;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but used in the next case.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode6)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    vec4 d = vec4(1);
    switch(u0){
        case 0:
            break;
            vec4 d = vec4(0);
        default:
            d.a = .0;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but used in the next case.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode7)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    vec4 d = vec4(1);
    switch(u0){
        case 0:
            break;
            vec4 d = vec4(0), d1 = vec4(0);
            vec4 d2 = vec4(0);
        default:
            d.a = .0;
            d1.a = .0;
            d2.a = .0;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test variable declaration in switch case after dead code, but used in the next case.
TEST_P(GLSLTest_ES3, VariableDeclaredInCaseDeadCode8)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
uniform int u0;
out vec4 fragOut;
void main(){
    switch(u0){
        case 0:
            break;
            switch(u0){
                case 0:
                    break;
                    vec4 d = vec4(0);
                default:
                    d.a = .0;
            }
        default:
            break;
    }
    fragOut = vec4(1, 0, 0, 1);
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test that a for loop initializer can be a variable with a struct declaration.
TEST_P(GLSLTest_ES31, StructDeclarationInForLoop)
{
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 color;
void main()
{
    color = vec4(0, 1, 0, 1);
    for (struct S { int i; } i = S(0); i.i < 1; i.i++)
    {
        color = vec4(1, 0, 0, 1);
    }
})";
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    EXPECT_GL_NO_ERROR();
}

// Test that a for loop initializer can be a struct declaration.
TEST_P(GLSLTest_ES31, StructDeclarationInForLoop2)
{
    constexpr char kFS[] = R"(#version 310 es
precision mediump float;
out vec4 color;
void main()
{
    color = vec4(0, 1, 0, 1);
    for (struct S { int i; }; color.y < 0.5; color.x = 1.0)
    {
        S s = S(0);
        color = vec4(1, 0, s.i, 1);
    }
})";
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.0);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
    EXPECT_GL_NO_ERROR();
}

// Test that texture derivatives and gl_HelperInvocation after `discard` work.  This is not actually
// correct per the GLSL spec:
//
// > The discard keyword .... causes the fragment to be discarded and no updates to the framebuffer
// will occur. ... subsequent implicit or explicit derivatives are undefined when this control flow
// is non-uniform
//
// However, it's a guarantee we provide with the Vulkan backend for apps because they commonly
// expect it to work.
TEST_P(GLSLTest_ES31, HelperInvocationsAfterDiscard)
{
    ANGLE_SKIP_TEST_IF(!IsVulkan() || !getEGLWindow()->isFeatureEnabled(
                                          Feature::SupportsShaderDemoteToHelperInvocation));

    // Bind a framebuffer whose size is two times an odd number.  This is so that dividing the
    // framebuffer gives odd quarters, where the 2x2 quads on the edge are split between them.
    constexpr uint32_t kWidth  = 38;
    constexpr uint32_t kHeight = 54;
    static_assert(kWidth % 4 == 2);
    static_assert(kHeight % 4 == 2);

    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kWidth, kHeight);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // A 2-mip texture is used to test the derivatives.  If the derivatives are incorrect, the wrong
    // texture level is selected.
    const std::vector<GLColor> kMip0Data(kWidth * kHeight * 4, GLColor::green);
    const std::vector<GLColor> kMip1Data(kWidth * kHeight, GLColor::red);

    GLTexture tex;
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, tex);
    glTexStorage2D(GL_TEXTURE_2D, 2, GL_RGBA8, kWidth * 2, kHeight * 2);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kWidth * 2, kHeight * 2, GL_RGBA, GL_UNSIGNED_BYTE,
                    kMip0Data.data());
    glTexSubImage2D(GL_TEXTURE_2D, 1, 0, 0, kWidth, kHeight, GL_RGBA, GL_UNSIGNED_BYTE,
                    kMip1Data.data());
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);

    // Clear the framebuffer to blue, then draw to it.  Each quarter is shaded differently:
    //
    // * The top-left quarter is discarded entirely, which should remain blue.
    // * The top-right quarter samples from the texture.  The size of the viewport matches mip 1 of
    //   the texture, so it should be red, including at the edges (where the 2x2 quads are half
    //   turned into helper lanes).  The green channel is set to gl_HelperInvocation (a boolean),
    //   which should remain 0.
    // * The bottom-left quarter has lines horizontally discarded where the row number is odd.  This
    //   way, every 2x2 quad has half of it discarded (either the bottom or top half).
    //   dFdx(gl_HelperInvocation) and dFdy(gl_HelperInvocation) is used to output to red and green
    //   channels; the result should always be green.
    // * The bottom-right quarter is similarly divided but with vertical lines.  The result should
    //   always be red.
    std::ostringstream fs;
    fs << R"(#version 310 es
precision mediump float;
uniform sampler2D tex;
out vec4 color;
void main()
{
    ivec2 coord = ivec2(gl_FragCoord.xy);
    bool isLeft = coord.x < )"
       << kWidth / 2 << R"(;
    bool isTop = coord.y < )"
       << kHeight / 2 << R"(;
    if (isTop)
    {
        if (isLeft)
        {
            discard;
        }
        else
        {
            color = texture(tex, gl_FragCoord.xy / vec2()"
       << kWidth << "," << kHeight << R"());
            color.y = float(gl_HelperInvocation);
        }
    }
    else
    {
        int index = isLeft ? coord.y : coord.x;
        if (index % 2 == 1)
        {
            discard;
        }
        color = vec4(abs(dFdx(float(gl_HelperInvocation))),
                     abs(dFdy(float(gl_HelperInvocation))), 0, 1);
    }
})";

    // Draw a single triangle.  drawQuad() draws two triangles, with the edge of the triangle being
    // problematic.
    constexpr char kVS[] = R"(#version 310 es
void main()
{
    vec2 pos = vec2(0.0);
    switch (gl_VertexID) {
        case 0: pos = vec2(-1.0, -1.0); break;
        case 1: pos = vec2(3.0, -1.0); break;
        case 2: pos = vec2(-1.0, 3.0); break;
    };
    gl_Position = vec4(pos, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, kVS, fs.str().c_str());
    glUseProgram(program);
    const GLint texLoc = glGetUniformLocation(program, "tex");
    EXPECT_NE(texLoc, -1);
    glUniform1i(texLoc, 0);

    glClearColor(0, 0, 1, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    glViewport(0, 0, kWidth, kHeight);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    // Top-left, should stay blue.
    // Ignore the top, left and bottom edges for simplicity.  The right edge (with the red quarter)
    // should be accurate.
    EXPECT_PIXEL_RECT_EQ(1, 1, kWidth / 2 - 1, kHeight / 2 - 2, GLColor::blue);
    // Top-right, should be red.
    // Ignore the top, right and bottom edges for simplicity.  The left edge (with the blue quarter)
    // should be accurate.
    EXPECT_PIXEL_RECT_EQ(kWidth / 2, 1, kWidth / 2 - 1, kHeight / 2 - 2, GLColor::red);

    // The rest are stripes of blue and another color
    std::vector<GLColor> result(kWidth * kHeight / 2);
    glReadPixels(0, kHeight / 2, kWidth, kHeight / 2, GL_RGBA, GL_UNSIGNED_BYTE, result.data());

    // Bottom-right, horizontal stripes of blue (where discarded) and green.
    // Ignore the edges to simplify verification.
    for (uint32_t row = 1; row < kHeight / 2 - 1; ++row)
    {
        for (uint32_t col = 1; col < kWidth / 2 - 1; ++col)
        {
            // Expect rows with an odd index to remain blue.
            const uint32_t rowIndex   = row + kHeight / 2;
            const uint32_t colIndex   = col;
            const GLColor resultColor = result[row * kWidth + col];
            const GLColor expect      = rowIndex % 2 == 1 ? GLColor::blue : GLColor::green;

            EXPECT_EQ(resultColor, expect) << rowIndex << " " << colIndex;
        }
    }
    // Bottom-left, vertical stripes of blue (where discarded) and red.
    // Ignore the edges to simplify verification.
    for (uint32_t row = 1; row < kHeight / 2 - 1; ++row)
    {
        for (uint32_t col = 1; col < kWidth / 2 - 1; ++col)
        {
            // Expect columns with an odd index to remain blue.
            const uint32_t rowIndex   = row;
            const uint32_t colIndex   = col + kWidth / 2;
            const GLColor resultColor = result[row * kWidth + col + kWidth / 2];
            const GLColor expect      = colIndex % 2 == 1 ? GLColor::blue : GLColor::red;

            EXPECT_EQ(resultColor, expect) << rowIndex << " " << colIndex;
        }
    }
    ASSERT_GL_NO_ERROR();
}

// Test that complex expressions are evaluated correctly.  With the IR, these are broken up to be
// less complex.  With AST, the shader fails compilation instead.
TEST_P(WebGLGLSLTest, ComplexExpression)
{
    ANGLE_SKIP_TEST_IF(!getEGLWindow()->isFeatureEnabled(Feature::UseIr));

    std::ostringstream fs;
    fs << R"(precision highp float;
            uniform vec4 u_color;
            void main()
            {
                float f = u_color.x)";
    for (uint32_t i = 0; i < 1000; ++i)
    {
        fs << "+ " << i << ".0";
    }
    fs << R"(;
                // sum(0, 999) is 499500.  Divide by twice this amount and expect gray.
                f /= 499500. * 2.;
                gl_FragColor = vec4(f);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.str().c_str());
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
    ASSERT_GL_NO_ERROR();
}

// Regression test for a transformation bug where a function has |return| only in dead code.
TEST_P(GLSLTest_ES3, EmptyBodyAfterPrunedIfWithReturn)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;

int foo() {
    if (false) { return 1; }
}

void main() {
    color = vec4(float(foo()), 0, 1, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
    ASSERT_GL_NO_ERROR();
}

// Empty prunable nested switches should work
TEST_P(GLSLTest_ES3, PruneEmptySwitch1)
{
    constexpr char kFS[] = R"(#version 300 es
out mediump vec4 color;
void main() {
  switch (9) {
    case 8:
      switch (7) {
        case 6:
        default:
          break;
      }
    default:
      break;
  }
  color = vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Empty prunable nested switches should work
TEST_P(GLSLTest_ES3, PruneEmptySwitch2)
{
    constexpr char kFS[] = R"(#version 300 es
out mediump vec4 color;
void main() {
  switch (9) {
    case 8:
    {
      switch (7) {
        case 6:
        default:
          break;
      }
    }
    default:
      break;
  }
  color = vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Empty prunable nested switches should work
TEST_P(GLSLTest_ES3, PruneEmptySwitch3)
{
    constexpr char kFS[] = R"(#version 300 es
out mediump vec4 color;
void main() {
  switch (9) {
    case 8:
      switch (7) {
        case 6:
        default:
          break;
      }
      {
      }
    default:
      break;
  }
  color = vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Empty prunable nested switches should work
TEST_P(GLSLTest_ES3, PruneEmptySwitch4)
{
    constexpr char kFS[] = R"(#version 300 es
out mediump vec4 color;
void main() {
  switch (9) {
    case 8:
      switch (7) {
        case 6:
        default:
          break;
      }
      {
        break;
      }
    default:
      break;
  }
  color = vec4(1, 0, 0, 1);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

class GLSLTest_ES3_Blend : public GLSLTest_ES3
{};

// Test alpha blend where both the framebuffer and shader miss the alpha channel.  The spec says
// that:
//
// > If a color buffer has no A value, then A_d is taken to be 1.
//
// But it says nothing about what happens if the shader does not write to alpha and A_s.
TEST_P(GLSLTest_ES3_Blend, AlphaBlendNoAlphaChannelInSrcAndDst)
{
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, 1, 1);
    constexpr uint8_t kInitialValue = 0x10;
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED, GL_UNSIGNED_BYTE, &kInitialValue);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    ANGLE_SKIP_TEST_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE);

    constexpr char kFS[] = R"(#version 300 es
out mediump float color;
void main() {
    color = 0.2;
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    // A_d is implicitly one.  But A_s is undefined.
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    // The result cannot be known given A_s is undefined, however it must be at least 0x10 given the
    // additive blend and that A_d must act as 1.
    // For future reference, A_s has been observed to be 0, 1 and 0.2 with various drivers (0.2
    // being the value of the component that is present).
    GLColor value;
    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &value);
    EXPECT_GE(value.R, 0x10);
    ASSERT_GL_NO_ERROR();
}

// Test alpha blend where the framebuffer misses the alpha channel, but the shader writes to alpha.
TEST_P(GLSLTest_ES3_Blend, AlphaBlendNoAlphaChannelInDst)
{
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, 1, 1);
    constexpr uint8_t kInitialValue = 0x10;
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED, GL_UNSIGNED_BYTE, &kInitialValue);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    ANGLE_SKIP_TEST_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE);

    constexpr char kFS[] = R"(#version 300 es
// Use an array for extra testing
out mediump vec4 color[1];
void main() {
    color[0] = vec4(0.2, 0, 0, 0.5);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
    glBlendEquation(GL_FUNC_ADD);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0x10 + 255 / 10, 0, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test blend where the framebuffer misses the alpha channel.  Uses (GL_DST_COLOR, GL_ZERO) blend
// that hits an optimization path in the mesa/Radeon driver.
TEST_P(GLSLTest_ES3_Blend, ColorBlendNoAlphaChannelInDst)
{
    GLTexture color;
    glBindTexture(GL_TEXTURE_2D, color);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_R8, 1, 1);
    constexpr uint8_t kInitialValue = 0xC0;
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 1, 1, GL_RED, GL_UNSIGNED_BYTE, &kInitialValue);

    GLFramebuffer fbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, color, 0);
    ANGLE_SKIP_TEST_IF(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE);

    constexpr char kFS[] = R"(#version 300 es
// Declare the output after main for extra testing
void f();
void main() {
    f();
}
out mediump float color;
void f() {
    color = 0.2;
}
)";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glEnable(GL_BLEND);
    glBlendFunc(GL_DST_COLOR, GL_ZERO);
    glBlendEquation(GL_FUNC_ADD);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0xC0 / 5, 0, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Tests that clamping a lowp value from the VS and subtracting from it the lower limit of the clamp
// does not result in a negative value.
TEST_P(GLSLPrecisionTest_ES3, ClampLowpVaryingAndSubtractLowerLimit)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float u_zeroval;

out lowp float tempval;

void main() {
    // Full screen quad vertices
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);

    gl_Position = vec4(x, y, 0.0, 1.0);
    tempval = u_zeroval;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in lowp float tempval;
out vec4 fragColor;

void main() {
  float result = clamp(tempval, 0.4, 0.9) - 0.4;
  if (result < 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a lowp value from the VS and subtracting from it the lower limit of the clamp
// and rooting it does not result in an invalid value.
TEST_P(GLSLPrecisionTest_ES3, ClampLowpVaryingAndSubtractLowerLimitAndRoot)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float u_zeroval;

out lowp float tempval;

void main() {
    // Full screen quad vertices
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);

    gl_Position = vec4(x, y, 0.0, 1.0);
    tempval = u_zeroval;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in lowp float tempval;
out vec4 fragColor;

void main() {
  float result = sqrt(clamp(tempval, 0.4, 0.9) - 0.4);
  if (result > 0.0) {
    fragColor = vec4(result, 0.0, 0.5, 1.0);
  } else if (result == 0.0) {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.5, 0.0, result, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a lowp uniform and subtracting from it the lower limit of the clamp does not
// result in a negative value.
TEST_P(GLSLPrecisionTest_ES3, ClampLowpUniformAndSubtractLowerLimit)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 fragColor;

uniform lowp float u_zeroval;

void main() {
  float result = clamp(u_zeroval, 0.4, 0.9) - 0.4;
  if (result < 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a lowp uniform and subtracting from it the lower limit of the clamp and
// rooting it does not result in an invalid value.
TEST_P(GLSLPrecisionTest_ES3, ClampLowpUniformAndSubtractLowerLimitAndRoot)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 fragColor;

uniform lowp float u_zeroval;

void main() {
  float result = sqrt(clamp(u_zeroval, 0.4, 0.9) - 0.4);
  if (result > 0.0) {
    fragColor = vec4(result, 0.0, 0.5, 1.0);
  } else if (result == 0.0) {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.5, 0.0, result, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a highp value from the VS and subtracting from it the lower limit of the
// clamp does not result in a negative value.
TEST_P(GLSLPrecisionTest_ES3, ClampHighpVaryingAndSubtractLowerLimit)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float u_zeroval;

out float tempval;

void main() {
    // Full screen quad vertices
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);

    gl_Position = vec4(x, y, 0.0, 1.0);
    tempval = u_zeroval;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in float tempval;
out vec4 fragColor;

void main() {
  float result = clamp(tempval, 0.4, 0.9) - 0.4;
  if (result < 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a highp value from the VS and subtracting from it the lower limit of the
// clamp and rooting it does not result in an invalid value.
TEST_P(GLSLPrecisionTest_ES3, ClampHighpVaryingAndSubtractLowerLimitAndRoot)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float u_zeroval;

out float tempval;

void main() {
    // Full screen quad vertices
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);

    gl_Position = vec4(x, y, 0.0, 1.0);
    tempval = u_zeroval;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in float tempval;
out vec4 fragColor;

void main() {
  float result = sqrt(clamp(tempval, 0.4, 0.9) - 0.4);
  if (result > 0.0) {
    fragColor = vec4(result, 0.0, 0.5, 1.0);
  } else if (result == 0.0) {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.5, 0.0, result, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a highp uniform and subtracting from it the lower limit of the clamp does not
// result in a negative value.
TEST_P(GLSLPrecisionTest_ES3, ClampHighpUniformAndSubtractLowerLimit)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 fragColor;

uniform float u_zeroval;

void main() {
  float result = clamp(u_zeroval, 0.4, 0.9) - 0.4;
  if (result < 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that clamping a highp uniform and subtracting from it the lower limit of the clamp and
// rooting it does not result in an invalid value.
TEST_P(GLSLPrecisionTest_ES3, ClampHighpUniformAndSubtractLowerLimitAndRoot)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 fragColor;

uniform float u_zeroval;

void main() {
  float result = sqrt(clamp(u_zeroval, 0.4, 0.9) - 0.4);
  if (result > 0.0) {
    fragColor = vec4(result, 0.0, 0.5, 1.0);
  } else if (result == 0.0) {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.5, 0.0, result, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_zeroval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.0f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that subtracting a lowp value from the VS by its value becomes 0.
TEST_P(GLSLPrecisionTest_ES3, SubtractLowpVaryingByItsValue)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float u_posval;

out lowp float tempval;

void main() {
    // Full screen quad vertices
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);

    gl_Position = vec4(x, y, 0.0, 1.0);
    tempval = u_posval;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in lowp float tempval;
out vec4 fragColor;

void main() {
  lowp float result = tempval - 0.4;
  if (result != 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_posval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.4f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that subtracting a lowp uniform by its value becomes 0.
TEST_P(GLSLPrecisionTest_ES3, SubtractLowpUniformByItsValue)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 fragColor;

uniform lowp float u_posval;

void main() {
  lowp float result = u_posval - 0.4;
  if (result != 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_posval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.4f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that subtracting a highp value from the VS by its value becomes 0.
TEST_P(GLSLPrecisionTest_ES3, SubtractHighpVaryingByItsValue)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
uniform float u_posval;

out float tempval;

void main() {
    // Full screen quad vertices
    float x = -1.0 + float((gl_VertexID & 1) << 2);
    float y = -1.0 + float((gl_VertexID & 2) << 1);

    gl_Position = vec4(x, y, 0.0, 1.0);
    tempval = u_posval;
})";
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

in float tempval;
out vec4 fragColor;

void main() {
  float result = tempval - 0.4;
  if (result != 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_posval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.4f);

    glDrawArrays(GL_TRIANGLES, 0, 6);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// Tests that subtracting a highp uniform by its value becomes 0.
TEST_P(GLSLPrecisionTest_ES3, SubtractHighpUniformByItsValue)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

out vec4 fragColor;

uniform float u_posval;

void main() {
  float result = u_posval - 0.4;
  if (result != 0.0) {
    fragColor = vec4(1.0, 0.0, 0.0, 1.0);
  } else {
    fragColor = vec4(0.0, 1.0, 0.0, 1.0);
  }
})";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    glUseProgram(program);

    GLint zeroValLoc = glGetUniformLocation(program, "u_posval");
    EXPECT_NE(zeroValLoc, -1);
    glUniform1f(zeroValLoc, 0.4f);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_RECT_EQ(0, 0, getWindowWidth(), getWindowHeight(), GLColor::green);
}

// This test recreates a bug seen on some platforms regarding static
// reads of varying arrays, specifically on 565 configs
// http://issuetracker.google.com/456812545
TEST_P(GLSLTest_ES3, DynamicWriteStaticReadVaryingArray)
{
    constexpr char kVS[] =
        R"(#version 300 es
        in highp vec4 a_position;
        in highp vec4 a_coords;
        uniform mediump int ui_zero, ui_one, ui_two, ui_three;
        out mediump vec4 var[4];

        void main()
        {
            gl_Position = a_position;

            // Sum exactly to 1.0 when a_coords is 1.0
            var[ui_zero]  = vec4(a_coords) * 0.5;    // 0.5
            var[ui_one]   = vec4(a_coords) * 0.25;   // 0.25
            var[ui_two]   = vec4(a_coords) * 0.125;  // 0.125
            var[ui_three] = vec4(a_coords) * 0.125;  // 0.125
        })";

    constexpr char kFS[] =
        R"(#version 300 es
        precision mediump int;
        layout(location = 0) out mediump vec4 o_color;
        in mediump vec4 var[4];

        void main()
        {
            mediump vec4 res = vec4(0.0);

            // FAIL pattern: statically reading a dynamically written varying array
            res += var[0];
            res += var[1];
            res += var[2];
            res += var[3];

            o_color = vec4(res);
        })";

    ANGLE_GL_PROGRAM(program, kVS, kFS);
    glUseProgram(program);

    GLFramebuffer fbo;
    GLRenderbuffer rbo;
    glBindFramebuffer(GL_FRAMEBUFFER, fbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);

    // Allocate RGB565 storage matching the default test window size
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB565, getWindowWidth(), getWindowHeight());
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);
    ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);

    // Bind dynamic indices
    glUniform1i(glGetUniformLocation(program, "ui_zero"), 0);
    glUniform1i(glGetUniformLocation(program, "ui_one"), 1);
    glUniform1i(glGetUniformLocation(program, "ui_two"), 2);
    glUniform1i(glGetUniformLocation(program, "ui_three"), 3);

    // By passing 1.0, our expected sum is exactly 1.0, which perfectly
    // translates to 255 in 8-bit channels and 31/63 in 565 channels.
    GLint coordsLoc = glGetAttribLocation(program, "a_coords");
    ASSERT_NE(coordsLoc, -1);
    glVertexAttrib4f(coordsLoc, 1.0f, 1.0f, 1.0f, 1.0f);

    drawQuad(program, "a_position", 0.0f);
    ASSERT_GL_NO_ERROR();

    // If the read fails, the sum drops to <= 0.875, resulting in a dark pixel.
    // We use GLColor::white to verify it hit 1.0 across all channels.
    // (Note: 565 with no alpha, glReadPixels will pad alpha to 255).
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Make sure gl_FragColor can be marked invariant in presence of GL_EXT_draw_buffers.
TEST_P(GLSLTest, EmulateGLFragColorBroadcastInvariantFragColor)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
        invariant gl_FragColor;
        void main() {
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
        }
    )";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
    ASSERT_GL_NO_ERROR();
}

// Make sure gl_FragColor can be marked invariant in presence of GL_EXT_draw_buffers, even if
// gl_FragColor is unused.
TEST_P(GLSLTest, EmulateGLFragColorBroadcastInvariantFragColorUnused)
{
    ANGLE_SKIP_TEST_IF(!EnsureGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
        invariant gl_FragColor;
        void main() {
            // gl_FragColor is unused
        }
    )";

    // Verify compilation only, as output is not written to.
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
}

// Test that indirect indices to gl_FragData get clamped to the right bounds when
// GL_EXT_draw_buffers is not enabled.
//
// The same test for ES3 is not needed, because unlike gl_FragData in ESSL 100, it's not allowed to
// index a fragment output variable with a non-constant index in ESSL 300+.
TEST_P(WebGLGLSLTest, FragDataIndexClampWithoutDrawBuffers)
{
    constexpr char kFS[] = R"(precision mediump float;
void main() {
    // GL_EXT_draw_buffers is not enabled, which means only one output is valid.  Make sure all the
    // following writes in the loop end up writing to gl_FragData[0].
    gl_FragData[0] = vec4(1, 0, 0, 1);
    for (int i = 0; i < 8; i++) {
        gl_FragData[i] += vec4(-0.1, 0.05, 0.0, 0.0);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 102, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that indirect indices to gl_FragData get clamped to the right bounds when
// gl_SecondaryFragDataEXT is used.
//
// The same test for ES3 is not needed, because unlike gl_FragData in ESSL 100, it's not allowed to
// index a fragment output variable with a non-constant index in ESSL 300+.
TEST_P(WebGLGLSLTest, FragDataIndexClampWithSecondaryFragData)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    // gl_SecondaryFragDataEXT follows, which means only one output is valid.  Make sure all the
    // following writes in the loop end up writing to gl_FragData[0].
    gl_FragData[0] = vec4(1, 0, 0, 1);
    for (int i = 0; i < 8; i++) {
        gl_FragData[i] += vec4(-0.1, 0.05, 0.0, 0.0);
    }

    gl_SecondaryFragDataEXT[0] = vec4(1.0, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 102, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Same test as FragDataIndexClampWithSecondaryFragData, but gl_SecondaryFragDataEXT is not written
// to.
TEST_P(WebGLGLSLTest, FragDataIndexClampWithSecondaryFragDataRead)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_FragData[0] = vec4(1, 0, 0, 1);
    for (int i = 0; i < 8; i++) {
        gl_FragData[i] += vec4(-0.1, 0.05, 0.0, 0.0);
    }

    gl_FragData[0] += gl_SecondaryFragDataEXT[0];
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 102, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Same test as FragDataIndexClampWithSecondaryFragData, but gl_SecondaryFragDataEXT is only used in
// dead code.
TEST_P(WebGLGLSLTest, FragDataIndexClampWithSecondaryFragDataDeadCode)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_FragData[0] = vec4(1, 0, 0, 1);
    for (int i = 0; i < 8; i++) {
        gl_FragData[i] += vec4(-0.1, 0.05, 0.0, 0.0);
    }

    if (false)
    {
        gl_SecondaryFragDataEXT[0] = vec4(1.0, 0.0, 0.0, 1.0);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 102, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Same test as FragDataIndexClampWithSecondaryFragData, but gl_SecondaryFragDataEXT is not actually
// used (only the extension is enabled.
TEST_P(WebGLGLSLTest, FragDataIndexClampWithSecondaryFragDataUnused)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_FragData[0] = vec4(1, 0, 0, 1);
    for (int i = 0; i < 8; i++) {
        gl_FragData[i] += vec4(-0.1, 0.05, 0.0, 0.0);
    }
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(229, 13, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that indirect indices to gl_FragData get clamped to the right bounds when
// gl_SecondaryFragDataEXT is used.
TEST_P(WebGLGLSLTest, FragDataIndexClampWithSecondaryFragDataFunctionArg)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
vec4 f(vec4 fragData[gl_MaxDrawBuffers])
{
    vec4 original = fragData[0];
    fragData[0] = vec4(0, 1, 0, 0);
    return original + fragData[0];
}
void main() {
    gl_FragData[0] = vec4(1, 0, 0, 1);
    gl_FragData[0] = f(gl_FragData);
    for (int i = 0; i < 8; i++) {
        gl_FragData[i] += vec4(-0.1, -0.05, 0.0, 0.0);
    }

    gl_SecondaryFragDataEXT[0] = vec4(1.0, 0.0, 0.0, 1.0);

})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 153, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that indirect indices to gl_FragData get clamped to the right bounds when
// gl_SecondaryFragDataEXT is used.
TEST_P(WebGL2GLSLTest, FragDataIndexClampWithSecondaryFragDataFunctionArgOut)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void f(out vec4 fragData[gl_MaxDrawBuffers])
{
    fragData[0] = vec4(1, 1, 0, 1);
    for (int i = 0; i < 8; i++) {
        fragData[i] += vec4(-0.1, -0.05, 0.0, 0.0);
    }
}
void main()
{
    f(gl_FragData);
    gl_SecondaryFragDataEXT[0] = vec4(1.0, 0.0, 0.0, 1.0);
})";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f, true);
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(51, 153, 0, 255), 1);
    ASSERT_GL_NO_ERROR();
}

// Test that long symbols work
TEST_P(GLSLTest_ES3, LongIdentifiers)
{
    constexpr GLfloat kUBOValue = 0.4f;
    GLBuffer ubo;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, sizeof(kUBOValue), &kUBOValue, GL_STATIC_COPY);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);

    // If symbols are at least 1022 characters, they don't get prefixed.  If they are below 1022,
    // they do.  Either way, this test makes sure these symbols work.  This is particularly needed
    // given these symbols may get suffixed with `_id`.
    for (uint32_t len = 1020; len <= 1024; len += 2)
    {
        const std::string longUBO(len, 'b');
        const std::string longUniform(len, 'u');
        const std::string longGlobalStruct(len, 'S');
        const std::string longLocalStruct(len, 'L');
        const std::string longVariable(len, 'v');

        std::string shader = R"(#version 300 es
precision mediump float;
uniform )" + longUBO + R"({
    float u;
};
struct )" + longGlobalStruct +
                             R"({
    float f;
} g;
uniform float )" + longUniform +
                             R"(;
out vec4 color;

void main() {
    struct )" + longLocalStruct +
                             R"({
        float f2;
    } l;
    float )" + longVariable + R"( = 0.1 + u;
    g.f = )" + longUniform + R"(;
    l.f2 = g.f + 0.25;
    color = vec4()" + longVariable +
                             R"(, g.f, l.f2, 1.0);
})";

        ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), shader.c_str());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f, 1.0f);
        EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 0, 63, 255), 1);
        ASSERT_GL_NO_ERROR();
    }
}

// Regression test for a bug in the HLSL generator where the global variable names could collide
// with local variable names.  In particular, the local variables were suffixed with the symbol id,
// starting from 3000 (kFirstUserDefinedSymbolId) but the global variables weren't.
TEST_P(GLSLTest_ES3, HLSLGlobalNameCollisionWithLocalVar)
{
    // At the time this regression test was written, the ID of the local variable was 3003.  Try a
    // few IDs starting at kFirstUserDefinedSymbolId so the test is not sensitive to small
    // variations in the ID.
    for (uint32_t id = 3000; id < 3010; ++id)
    {
        std::ostringstream fs;
        fs << R"(precision highp float;
float _a)" << id
           << R"( = 0.5;
void main()
{
  float a = 0.2;
  a = _a)" << id
           << R"(;
  gl_FragColor = vec4(a);
})";
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.str().c_str());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 127, 127, 127), 1);
        ASSERT_GL_NO_ERROR();
    }
}

// Regression test for a bug in the HLSL generator where the function parameter names could collide
// with local variable names.  In particular, the local variables were suffixed with the symbol id,
// starting from 3000 (kFirstUserDefinedSymbolId) but the function parameters weren't.
TEST_P(GLSLTest_ES3, HLSLParameterNameCollisionWithLocalVar)
{
    // At the time this regression test was written, the ID of the local variable was 3003.  Try a
    // few IDs starting at kFirstUserDefinedSymbolId so the test is not sensitive to small
    // variations in the ID.
    for (uint32_t id = 3000; id < 3010; ++id)
    {
        std::ostringstream fs;
        fs << R"(precision highp float;
float f(float _a)"
           << id << R"()
{
    float a;
    a = _a)"
           << id << R"( + 0.5;
    return a;
}
void main()
{
  gl_FragColor = vec4(f(0.2));
})";
        ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.str().c_str());
        drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
        EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(178, 178, 178, 178), 1);
        ASSERT_GL_NO_ERROR();
    }
}

class GLSLTestPassthrough : public GLSLTest
{};

// Test that uniforms of nameless struct type work with the shader passthrough feature
TEST_P(GLSLTestPassthrough, StructUniformNameless)
{
    constexpr char kFS[] = R"(
uniform struct
{
    mediump vec4 c;
} s;
void main()
{
    gl_FragColor = s.c;
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform4f(glGetUniformLocation(program, "s.c"), 1, 0, 0, 1);

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

// Test that uniforms of named struct type work with the shader passthrough feature
TEST_P(GLSLTestPassthrough, StructUniformNamed)
{
    constexpr char kFS[] = R"(
uniform struct S
{
    mediump vec4 c;
} s;
void main()
{
    gl_FragColor = s.c;
})";

    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), kFS);
    glUseProgram(program);
    glUniform4f(glGetUniformLocation(program, "s.c"), 1, 0, 0, 1);

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

// Test that struct constructor arguments with array constants and mixed precision work.
TEST_P(GLSLTest_ES3, StructConstructorComplexExpressionWithArrayConstant)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
precision lowp int;
struct S {
    float f;
    mediump uint i[3];
};

out vec4 color;
void main()
{
    // Note: default precision of int is lowp, but the struct has mediump
    S s = S(gl_FragCoord.x, uint[3](10u, 20u, 30u));
    color = vec4(s.i[0] == 10u, s.i[1] == 20u, s.i[2] == 30u, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that struct constructor arguments are not evaluated if they are in a short-circuited
// expression.
TEST_P(GLSLTest_ES3, StructConstructorComplexExpressionShortCircuit)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
uniform bool ufalse;
struct S {
    int i;
};
void main()
{
    int a = 10;
    int b = 20;
    // |ufalse| is false by default, so a should be unmodified.
    if (ufalse && S(++a).i == 11)
    {
        b = 30;
    }

    color = vec4(a == 10, b == 20, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

// Test struct constructor with complex expression with mixed function and struct declarations.
TEST_P(GLSLTest_ES3, StructConstructorComplexExpressionMixedDecls)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
struct A {
    int i;
};
int f() {
    int a = 10;
    return A(++a).i;
}
struct B {
    float f;
};
void main()
{
    int a = f();
    float f = B(float(a)).f;
    color = vec4(a == 11, f == 11.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

// Test struct constructor with complex expression with local struct declarations.
TEST_P(GLSLTest_ES3, StructConstructorComplexExpressionLocalDecl)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
struct B {
    float f;
};
void main()
{
    struct A {
        int i;
    };
    int a = 10;
    int b = A(a++).i;
    color = vec4(a == 11, b == 10, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::yellow);
}

// Test struct constructor with complex expression with struct declaration in function return value.
TEST_P(GLSLTest_ES3, StructConstructorComplexExpressionDeclInReturn)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
struct A {
    int i;
} f() {
    int a = 10;
    return A(++a);
}
void main()
{
    A a = f();
    color = vec4(a.i == 11, 0.0, 0.0, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test nested struct constructor s with complex expression work.
TEST_P(GLSLTest_ES3, StructConstructorComplexExpressionNested)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 color;
struct B {
    int i;
};
struct A {
    int i;
    B b;
};
void main()
{
    int a = 10;
    int b = 20;
    // Directly nest B's constructor under A
    int c = A(a++, B(++b)).i;
    color = vec4(a == 11, b == 21, c == 10, 1.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that having dynamic indexing of a vector inside the right hand side of logical or works
// correctly.
TEST_P(GLSLTest_ES3, DynamicIndexingOfVectorOnRightSideOfLogicalOr)
{
    const std::string &fragShader =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 my_FragColor;\n"
        "uniform int u1;\n"
        "void main() {\n"
        "   bvec4 v = bvec4(true, true, true, false);\n"
        "   my_FragColor = vec4(v[u1 + 1] || v[u1]);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fragShader.c_str());
    glUseProgram(program);
    GLint u1Loc = glGetUniformLocation(program, "u1");
    ASSERT_NE(-1, u1Loc);
    glUniform1i(u1Loc, 2);  // v[3] || v[2] -> false || true -> true
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::white);
}

// Test that rewriting else blocks in a function that returns a struct doesn't use the struct name
// without a prefix.
TEST_P(GLSLTest, RewriteElseBlockReturningStruct)
{
    const std::string &vs =
        "attribute vec4 a_position;\n"
        "struct foo\n"
        "{\n"
        "    float member;\n"
        "};\n"
        "uniform bool b;\n"
        "varying float outMember;\n"
        "foo getFoo()\n"
        "{\n"
        "    if (b)\n"
        "    {\n"
        "        return foo(0.5);\n"
        "    }\n"
        "    else\n"
        "    {\n"
        "        return foo(1.0);\n"
        "    }\n"
        "}\n"
        "void main()\n"
        "{\n"
        "   gl_Position = a_position;\n"
        "   outMember = getFoo().member;\n"
        "}\n";
    const std::string &fs =
        "precision mediump float;\n"
        "varying float outMember;\n"
        "void main() {\n"
        "   gl_FragColor = vec4(outMember, 0.0, 0.0, 1.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, vs.c_str(), fs.c_str());
    glUseProgram(program);
    GLint bLoc = glGetUniformLocation(program, "b");
    ASSERT_NE(-1, bLoc);
    glUniform1i(bLoc, 1);
    drawQuad(program, "a_position", 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 0, 0, 255), 2);
}

// Regression test for RemoveDynamicIndexing transformation producing invalid AST, based on fuzzer
// test.
TEST_P(GLSLTest, RemoveDynamicingIndexIndexPrecisionBug)
{
    const char vs[] = R"(void main()
{
    mat3 tmp;
    vec3 res = vec3(0);
    for (int i = 0; res += 0., ivec3(0)[i], ivec3(tmp)[i], i < 0;);
    gl_Position = vec4(0.0);
})";
    ANGLE_GL_PROGRAM(program, vs, essl1_shaders::fs::Red());
    EXPECT_GL_NO_ERROR();
}

// Test that having an array constructor as a statement doesn't trigger an assert in compiler
// output. This test has a constant array constructor statement.
TEST_P(GLSLTest_ES3, ConstArrayConstructorStatement)
{
    const std::string &fs =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 outColor;\n"
        "void main()\n"
        "{\n"
        "    int[1](0);\n"
        "    outColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that having an array constructor as a statement doesn't trigger an assert in compiler
// output.
TEST_P(GLSLTest_ES3, ArrayConstructorStatement)
{
    const std::string &fs =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 outColor;\n"
        "void main()\n"
        "{\n"
        "    outColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
        "    float[1](outColor[1]++);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(0, 255, 0, 255));
}

// Test an array of arrays constructor as a statement.
TEST_P(GLSLTest_ES31, ArrayOfArraysStatement)
{
    const std::string &fs =
        "#version 310 es\n"
        "precision mediump float;\n"
        "out vec4 outColor;\n"
        "void main()\n"
        "{\n"
        "    outColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
        "    float[2][2](float[2](outColor[1]++, 0.0), float[2](1.0, 2.0));\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(0, 255, 0, 255));
}

// Test dynamic indexing of a vector. This makes sure that helper functions added for dynamic
// indexing have correct data that subsequent traversal steps rely on.
TEST_P(GLSLTest_ES3, VectorDynamicIndexing)
{
    const std::string &fs =
        "#version 300 es\n"
        "precision mediump float;\n"
        "out vec4 outColor;\n"
        "uniform int i;\n"
        "void main()\n"
        "{\n"
        "    vec4 foo = vec4(0.0, 0.5, 0.0, 1.0);\n"
        "    outColor = vec4(0.0, foo[i], 0.0, 1.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLint iLoc = glGetUniformLocation(program, "i");
    ASSERT_NE(-1, iLoc);
    glUniform1i(iLoc, 1);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0, 127, 0, 255), 2);
}

// Test returning an array from a user-defined function. This makes sure that function symbols are
// changed consistently when the user-defined function is changed to have an array out parameter.
TEST_P(GLSLTest_ES31, ArrayReturnValue)
{
    const std::string &fs =
        "#version 310 es\n"
        "precision highp float;\n"
        "uniform float u;\n"
        "out vec4 outColor;\n"
        "float[2] getArray(float f)\n"
        "{\n"
        "    return float[2](f, f + 0.5);\n"
        "}\n"
        "void main()\n"
        "{\n"
        "    float[2] arr = getArray(u);\n"
        "    outColor = vec4(arr[0], arr[1], 0.0, 1.0);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl31_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLint uLoc = glGetUniformLocation(program, "u");
    ASSERT_NE(-1, uLoc);
    glUniform1f(uLoc, 0.25f);
    drawQuad(program, essl31_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor(64, 191, 0, 255));
}

// Test that writing parameters without a name doesn't assert.
TEST_P(GLSLTest, ParameterWithNoName)
{
    const std::string &fs =
        "precision mediump float;\n"
        "uniform vec4 v;\n"
        "vec4 s(vec4)\n"
        "{\n"
        "    return v;\n"
        "}\n"
        "void main()\n"
        "{\n"
        "    gl_FragColor = s(v);\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLint vLoc = glGetUniformLocation(program, "v");
    ASSERT_NE(-1, vLoc);
    glUniform4f(vLoc, 0.0f, 1.0f, 0.0f, 1.0f);
    drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
}

// Test that array dimensions are written out correctly.
TEST_P(GLSLTest_ES3, ArrayDimensionsCompile)
{
    const std::string &fs =
        "#version 300 es\n"
        "precision mediump float;\n"
        "uniform float uf;\n"
        "out vec4 my_FragColor;\n"
        "void main()\n"
        "{\n"
        "    my_FragColor = vec4(0.0, 0.0, 0.0, 1.0);\n"
        "    float arr[2];\n"
        "    for (int i = 0; i < 2; ++i) {\n"
        "        arr[i] = uf * 0.25;\n"
        "        my_FragColor.x += arr[i];\n"
        "    }\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLint ufLoc = glGetUniformLocation(program, "uf");
    ASSERT_NE(-1, ufLoc);
    glUniform1f(ufLoc, 1.0f);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(127, 0, 0, 255), 2);
}

// Test that initializing array with previously declared array will not be overwritten
TEST_P(GLSLTest_ES3, SameNameArray)
{
    const std::string &fs =
        "#version 300 es\n"
        "precision highp float;\n"
        "out vec4 my_FragColor;\n"
        "void main()\n"
        "{\n"
        "  float arr[2] = float[2](0.5, 1.0);\n"
        "  {\n"
        "    float arr[2] = arr;\n"
        "    my_FragColor = vec4(0.0, arr[0], 0.0, arr[1]);\n"
        "  }\n"
        "}\n";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(0, 127, 0, 255), 2);
}

// Test that passing a non-struct member of a std140 structure to a function won't trigger the
// struct mapping.
TEST_P(GLSLTest_ES3, NonStructMemberAsFunctionArgument)
{
    const std::string &fs = R"(#version 300 es
    precision highp float;
    out vec4 my_FragColor;
    struct InstancingData
    {
        vec4 data;
    };
    layout(std140) uniform InstanceBlock
    {
        InstancingData instances[8];
    };
    void main()
    {
        // Restrict array indexing via '% 8' to avoid out-of-bounds array reads.
        int index = int(gl_FragCoord.x) % 8;
        // Multiply by vec4(0.25) so that dot(vec4(1.0), vec4(0.25)) evaluates exactly to 1.0 (unclamped)
        float result = dot(instances[index].data, vec4(0.25, 0.25, 0.25, 0.25));
        my_FragColor = vec4(result, 0.0, 0.0, 1.0);
    })";
    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLuint blockIndex = glGetUniformBlockIndex(program, "InstanceBlock");
    ASSERT_NE(GL_INVALID_INDEX, blockIndex);

    GLBuffer ubo;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    std::vector<float> data(8 * 4, 1.0f);
    glBufferData(GL_UNIFORM_BUFFER, data.size() * sizeof(float), data.data(), GL_STATIC_DRAW);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);
    glUniformBlockBinding(program, blockIndex, 0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();
    EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
}

// Test that a uniform block with a structure containing a mat3 (unsupported for StructuredBuffer
// optimization) compiles, links, and runs correctly by falling back to cbuffer.
TEST_P(GLSLTest_ES3, UniformBlockWithUnsupportedFieldStructuredBufferFallback)
{
    // The D3D11 backend translates uniform blocks to HLSL constant buffers (cbuffer) or structured
    // buffers (StructuredBuffer). An HLSL StructuredBuffer packs structures tightly, giving S (with
    // mat3) a stride of 52 or 56 bytes, whereas a standard cbuffer preserves std140's 64-byte
    // stride. We populate the buffer using std140 layout (64-byte stride) and read element i = 49
    // using a uniform index. The CPU sets:
    //   buf[49].a = (0.49, 0.0, 0.0, 1.0)
    //   buf[49].b[0] = (0.0, 0.49, 0.0)
    // The shader computes:
    //   fragColor = buf[u_index].a + vec4(buf[u_index].b[0], 0.0) = (0.49, 0.49, 0.0, 1.0)
    // This evaluates to GLColor(125, 125, 0, 255) since 0.49 * 255 = 124.95.
    // If translation incorrectly used StructuredBuffer, the GPU would read element 49 from offset
    // 49 * 52 = 2548 instead of the correct CPU offset 49 * 64 = 3136, reading garbage and
    // rendering a wrong color.
    const std::string &fs = R"(#version 300 es
precision highp float;
struct S {
    vec4 a;
    mat3 b;
};
layout(std140) uniform Block {
    S buf[50];
};
uniform int u_index;
out vec4 fragColor;
void main() {
    fragColor = buf[u_index].a + vec4(buf[u_index].b[0], 0.0);
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLuint blockIndex = glGetUniformBlockIndex(program, "Block");
    ASSERT_NE(GL_INVALID_INDEX, blockIndex);

    GLint indexLoc = glGetUniformLocation(program, "u_index");
    ASSERT_NE(-1, indexLoc);
    glUniform1i(indexLoc, 49);

    // S size in std140 is 16 (for vec4 a) + 48 (for mat3 b, 3 * vec4 aligned columns) = 64 bytes
    constexpr size_t kElementStride = 64;
    std::vector<uint8_t> bufferData(50 * kElementStride, 0);
    for (int i = 0; i < 50; ++i)
    {
        // buf[i].a is at offset 0
        float *aPtr = reinterpret_cast<float *>(&bufferData[i * kElementStride]);
        aPtr[0]     = 0.01f * i;
        aPtr[1]     = 0.0f;
        aPtr[2]     = 0.0f;
        aPtr[3]     = 1.0f;

        // buf[i].b[0] (column 0 of mat3) is at offset 16
        float *b0Ptr = reinterpret_cast<float *>(&bufferData[i * kElementStride + 16]);
        b0Ptr[0]     = 0.0f;
        b0Ptr[1]     = 0.01f * i;
        b0Ptr[2]     = 0.0f;
    }

    GLBuffer ubo;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_STATIC_DRAW);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);
    glUniformBlockBinding(program, blockIndex, 0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(125, 125, 0, 255), 2);
}

// Test that a uniform block with only supported field types in its structure is translated and
// rendered correctly.
TEST_P(GLSLTest_ES3, UniformBlockWithSupportedFieldsStructuredBuffer)
{
    const std::string &fs = R"(#version 300 es
precision highp float;
struct S {
    vec4 a;
    mat4 b;
};
layout(std140) uniform Block {
    S buf[50];
};
uniform int u_index;
out vec4 fragColor;
void main() {
    fragColor = buf[u_index].a + buf[u_index].b[0];
})";

    ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), fs.c_str());
    glUseProgram(program);
    GLuint blockIndex = glGetUniformBlockIndex(program, "Block");
    ASSERT_NE(GL_INVALID_INDEX, blockIndex);

    GLint indexLoc = glGetUniformLocation(program, "u_index");
    ASSERT_NE(-1, indexLoc);
    glUniform1i(indexLoc, 49);

    // S size in std140 is 16 (for vec4 a) + 64 (for mat4 b, 4 * vec4 aligned columns) = 80 bytes
    constexpr size_t kElementStride = 80;
    std::vector<uint8_t> bufferData(50 * kElementStride, 0);
    for (int i = 0; i < 50; ++i)
    {
        // buf[i].a is at offset 0
        float *aPtr = reinterpret_cast<float *>(&bufferData[i * kElementStride]);
        aPtr[0]     = 0.01f * i;
        aPtr[1]     = 0.0f;
        aPtr[2]     = 0.0f;
        aPtr[3]     = 1.0f;

        // buf[i].b[0] (column 0 of mat4) is at offset 16
        float *b0Ptr = reinterpret_cast<float *>(&bufferData[i * kElementStride + 16]);
        b0Ptr[0]     = 0.0f;
        b0Ptr[1]     = 0.01f * i;
        b0Ptr[2]     = 0.0f;
        b0Ptr[3]     = 0.0f;
    }

    GLBuffer ubo;
    glBindBuffer(GL_UNIFORM_BUFFER, ubo);
    glBufferData(GL_UNIFORM_BUFFER, bufferData.size(), bufferData.data(), GL_STATIC_DRAW);
    glBindBufferBase(GL_UNIFORM_BUFFER, 0, ubo);
    glUniformBlockBinding(program, blockIndex, 0);

    drawQuad(program, essl3_shaders::PositionAttrib(), 0.5f);
    EXPECT_GL_NO_ERROR();

    EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(125, 125, 0, 255), 2);
}

}  // anonymous namespace

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31_AND_ES32(
    GLSLTest,
    ES3_OPENGL().enable(Feature::ForceInitShaderVariables),
    ES3_OPENGL().enable(Feature::ScalarizeVecAndMatConstructorArgs),
    ES3_OPENGL().enable(Feature::AvoidComplexExpressionsInStructConstructor),
    ES3_OPENGLES().enable(Feature::ScalarizeVecAndMatConstructorArgs),
    ES3_OPENGLES().enable(Feature::AvoidComplexExpressionsInStructConstructor),
    ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
    ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
    ES3_VULKAN().disable(Feature::SupportsSPIRV14),
    ES2_VULKAN().enable(Feature::VaryingsRequireMatchingPrecisionInSpirv),
    ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
    ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
    ES3_VULKAN().enable(Feature::EmulatedPrerotation270));

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLTestNoValidation);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ES3);
ANGLE_INSTANTIATE_TEST_ES3_AND(
    GLSLTest_ES3,
    ES3_OPENGL().enable(Feature::ForceInitShaderVariables),
    ES3_OPENGL().enable(Feature::ScalarizeVecAndMatConstructorArgs),
    ES3_OPENGL().enable(Feature::AvoidComplexExpressionsInStructConstructor),
    ES3_OPENGLES().enable(Feature::ScalarizeVecAndMatConstructorArgs),
    ES3_OPENGLES().enable(Feature::AvoidComplexExpressionsInStructConstructor),
    ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
    ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
    ES3_VULKAN().disable(Feature::SupportsSPIRV14),
    ES3_VULKAN().enable(Feature::EmulatedPrerotation90),
    ES3_VULKAN().enable(Feature::EmulatedPrerotation180),
    ES3_VULKAN().enable(Feature::EmulatedPrerotation270));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLPrecisionTest_ES3);
ANGLE_INSTANTIATE_TEST_ES3_AND(
    GLSLPrecisionTest_ES3,
    ES3_OPENGL().enable(Feature::ForceInitShaderVariables),
    ES3_OPENGL().enable(Feature::ScalarizeVecAndMatConstructorArgs),
    ES3_OPENGLES().enable(Feature::ScalarizeVecAndMatConstructorArgs),
    ES3_VULKAN().enable(Feature::AvoidOpSelectWithMismatchingRelaxedPrecision),
    ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
    ES3_VULKAN().disable(Feature::SupportsSPIRV14));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTestLoops);
ANGLE_INSTANTIATE_TEST_ES3(GLSLTestLoops);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTestLoops_ES31);
ANGLE_INSTANTIATE_TEST_ES31(GLSLTestLoops_ES31);

ANGLE_INSTANTIATE_TEST_ES2(WebGLGLSLTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ClampPointSize);
ANGLE_INSTANTIATE_TEST_ES2_AND(GLSLTest_ClampPointSize,
                               ES2_OPENGL().enable(Feature::ClampPointSize),
                               ES2_VULKAN().enable(Feature::ClampPointSize));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WebGL2GLSLTest);
ANGLE_INSTANTIATE_TEST_ES3(WebGL2GLSLTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ES31);
ANGLE_INSTANTIATE_TEST_ES31_AND(
    GLSLTest_ES31,
    ES31_VULKAN().enable(Feature::ForceInitShaderVariables),
    ES31_VULKAN().enable(Feature::VaryingsRequireMatchingPrecisionInSpirv),
    ES31_VULKAN().disable(Feature::SupportsSPIRV14));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ES3_InitShaderVariables);
ANGLE_INSTANTIATE_TEST(
    GLSLTest_ES3_InitShaderVariables,
    ES3_D3D11().enable(Feature::ForceInitShaderVariables),
    ES3_OPENGL().enable(Feature::ForceInitShaderVariables),
    ES3_OPENGLES().enable(Feature::ForceInitShaderVariables),
    ES3_METAL().enable(Feature::ForceInitShaderVariables),
    ES3_VULKAN().enable(Feature::ForceInitShaderVariables),
    ES3_VULKAN().disable(Feature::SupportsSPIRV14).enable(Feature::ForceInitShaderVariables));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ES31_InitShaderVariables);
ANGLE_INSTANTIATE_TEST(
    GLSLTest_ES31_InitShaderVariables,
    ES31_VULKAN().enable(Feature::ForceInitShaderVariables),
    ES31_VULKAN().disable(Feature::SupportsSPIRV14).enable(Feature::ForceInitShaderVariables));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ES3_PackUnpackEmulation);
ANGLE_INSTANTIATE_TEST(GLSLTest_ES3_PackUnpackEmulation,
                       ES3_D3D11(),
                       ES3_OPENGL(),
                       ES3_OPENGL().enable(Feature::EmitMaxGlsl400ForTesting),
                       ES3_OPENGLES(),
                       ES3_METAL(),
                       ES3_VULKAN());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLTest_ES3_Blend);
ANGLE_INSTANTIATE_TEST_ES3_AND(GLSLTest_ES3_Blend,
                               ES3_OPENGL().enable(Feature::ExpandFragmentOutputsToVec4),
                               ES3_OPENGLES().enable(Feature::ExpandFragmentOutputsToVec4));

ANGLE_INSTANTIATE_TEST_ES2_AND(GLSLTestPassthrough,
                               ES2_OPENGLES().enable(Feature::ForcePassthroughShaders));
