//
// Copyright 2025 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//

#include "test_utils/CompilerTest.h"
#include "test_utils/angle_test_configs.h"

#include <sstream>

using namespace angle;

namespace
{
// Limits set in sh::InitBuiltInResources().  These are implementation limits that the tests need to
// be aware of.
constexpr uint32_t kMaxExpressionComplexity = 256;
constexpr uint32_t kMaxCallStackDepth       = 256;
constexpr uint32_t kMaxFunctionParameters   = 255;

class GLSLValidationTest : public CompilerTest
{
  protected:
    GLSLValidationTest() {}

    // Helper to create a shader, then verify that it fails to compile with the given reason.  It's
    // given:
    //
    // * The type of shader.
    // * The shader source itself.
    // * An error string to look for in the compile logs.
    void validateError(GLenum shaderType, const char *shaderSource, const char *expectedError)
    {
        const CompiledShader &shader = compile(shaderType, shaderSource);
        EXPECT_FALSE(shader.success());

        EXPECT_TRUE(shader.hasInfoLog(expectedError)) << expectedError;
        reset();
    }

    // Helper to create a shader, then verify that compilation succeeded.
    void validateSuccess(GLenum shaderType, const char *shaderSource)
    {
        const CompiledShader &shader = compile(shaderType, shaderSource);
        EXPECT_TRUE(shader.success());
        reset();
    }

    void validateWarning(GLenum shaderType, const char *shaderSource, const char *expectedWarning)
    {
        const CompiledShader &shader = compile(shaderType, shaderSource);
        EXPECT_TRUE(shader.success());

        EXPECT_TRUE(shader.hasInfoLog(expectedWarning)) << expectedWarning;
        reset();
    }
};

class GLSLValidationTest_ES3 : public GLSLValidationTest
{};

class GLSLValidationTest_ES31 : public GLSLValidationTest
{};

class GLSLValidationTestNoValidation : public GLSLValidationTest
{
  public:
    GLSLValidationTestNoValidation() { setNoErrorEnabled(true); }
};

class WebGLGLSLValidationTest : public GLSLValidationTest
{
  protected:
    WebGLGLSLValidationTest() { setWebGLCompatibilityEnabled(true); }
};

class WebGL2GLSLValidationTest : public GLSLValidationTest_ES3
{
  protected:
    WebGL2GLSLValidationTest() { setWebGLCompatibilityEnabled(true); }

    void testInfiniteLoop(const char *fs)
    {
        const CompiledShader &shader = compile(GL_FRAGMENT_SHADER, fs);
        EXPECT_FALSE(shader.success());
        reset();
    }
};

// Test that an empty shader fails to compile
TEST_P(GLSLValidationTest, EmptyShader)
{
    constexpr char kFS[] = "";
    validateError(GL_FRAGMENT_SHADER, kFS, "syntax error");
}

// Test that a shader with no main in it fails to compile
TEST_P(GLSLValidationTest, MissingMain)
{
    constexpr char kFS[] = R"(precision mediump float;)";
    validateError(GL_FRAGMENT_SHADER, kFS, "Missing main()");
}

// Test that a shader with only a main prototype in it fails to compile
TEST_P(GLSLValidationTest, MainPrototypeOnly)
{
    constexpr char kFS[] = R"(precision mediump float;
void main();
)";
    validateError(GL_FRAGMENT_SHADER, kFS, "Missing main()");
}

// Test relational operations between bools is rejected.
TEST_P(GLSLValidationTest, BoolLessThan)
{
    constexpr char kFS[] = R"(uniform mediump vec4 u;
void main() {
  bool a = bool(u.x);
  bool b = bool(u.y);
  bool c = a < b;
  gl_FragColor = vec4(c, !c, c, !c);
}
)";
    validateError(GL_FRAGMENT_SHADER, kFS, "'<' : comparison operator not defined for booleans");
}

// This is a test for a bug that used to exist in ANGLE:
// Calling a function with all parameters missing should not succeed.
TEST_P(GLSLValidationTest, FunctionParameterMismatch)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float fun(float a) {
            return a * 2.0;
        }
        void main() {
            float ff = fun();
            gl_FragColor = vec4(ff);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "'fun' : no matching overloaded function found");
}

// Functions can't be redeclared as variables in the same scope (ESSL 1.00 section 4.2.7)
TEST_P(GLSLValidationTest, RedeclaringFunctionAsVariable)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float fun(float a) {
            return a * 2.0;
        }

        float fun;
        void main() {
             gl_FragColor = vec4(0.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "'fun' : redefinition");
}

// Functions can't be redeclared as structs in the same scope (ESSL 1.00 section 4.2.7)
TEST_P(GLSLValidationTest, RedeclaringFunctionAsStruct)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float fun(float a) {
           return a * 2.0;
        }
        struct fun { float a; };
        void main() {
           gl_FragColor = vec4(0.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "'fun' : redefinition of a struct");
}

// Functions can't be redeclared with different qualifiers (ESSL 1.00 section 6.1.0)
TEST_P(GLSLValidationTest, RedeclaringFunctionWithDifferentQualifiers)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float fun(out float a);
        float fun(float a) {
           return a * 2.0;
        }
        void main() {
           gl_FragColor = vec4(0.0);
        }
    )";

    validateError(
        GL_FRAGMENT_SHADER, kFS,
        "'in' : function must have the same parameter qualifiers in all of its declarations");
}

// Auxiliary/interpolation qualifiers must always be paired with storage qualifiers.
TEST_P(GLSLValidationTest_ES3, NoAuxOrInterpQualifierWithoutStorageQualifier)
{
    {
        constexpr char kVS[] = R"(#version 300 es
precision mediump float;
centroid float invalid;
void main() { gl_Position = vec4(invalid); }
        )";

        validateError(GL_VERTEX_SHADER, kVS,
                      "'centroid' : qualifier can only be used with in and out variables");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
precision mediump float;
flat int invalid;
void main() { gl_Position = vec4(invalid); }
        )";

        validateError(GL_VERTEX_SHADER, kVS,
                      "'flat' : qualifier can only be used with in and out variables");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
precision mediump float;
smooth float invalid;
void main() { gl_Position = vec4(invalid); }
        )";

        validateError(GL_VERTEX_SHADER, kVS,
                      "'smooth' : qualifier can only be used with in and out variables");
    }

    if (IsGLExtensionEnabled("GL_NV_shader_noperspective_interpolation"))
    {
        constexpr char kVS[] = R"(#version 300 es
#extension GL_NV_shader_noperspective_interpolation : require
precision mediump float;
noperspective float invalid;
void main() { gl_Position = vec4(invalid); }
        )";

        validateError(GL_VERTEX_SHADER, kVS,
                      "'noperspective' : qualifier can only be used with in and out variables");
    }

    if (IsGLExtensionEnabled("GL_NV_shader_noperspective_interpolation"))
    {
        constexpr char kVS[] = R"(#version 300 es
#extension GL_NV_shader_noperspective_interpolation : require
precision mediump float;
noperspective centroid float invalid;
void main() { gl_Position = vec4(invalid); }
        )";

        validateError(
            GL_VERTEX_SHADER, kVS,
            "'noperspective centroid' : qualifier can only be used with in and out variables");
    }

    if (IsGLExtensionEnabled("GL_OES_shader_multisample_interpolation"))
    {
        constexpr char kFS[] = R"(#version 300 es
#extension GL_OES_shader_multisample_interpolation : require
precision mediump float;
sample float invalid;
out vec4 color;
void main() { color = vec4(invalid); }
        )";

        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'sample' : qualifier can only be used with in and out variables");
    }
}
// Assignment and equality are undefined for structures containing arrays (ESSL 1.00 section 5.7)
TEST_P(GLSLValidationTest, CompareStructsContainingArrays)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        struct s { float a[3]; };
        void main() {
           s a;
           s b;
           bool c = (a == b);
           gl_FragColor = vec4(c ? 1.0 : 0.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'==' : undefined operation for structs containing arrays");
}

// Assignment and equality are undefined for structures containing arrays (ESSL 1.00 section 5.7)
TEST_P(GLSLValidationTest, AssignStructsContainingArrays)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        struct s { float a[3]; };
        void main() {
           s a;
           s b;
           b.a[0] = 0.0;
           a = b;
           gl_FragColor = vec4(a.a[0]);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : undefined operation for structs containing arrays");
}

// Assignment and equality are undefined for structures containing samplers (ESSL 1.00 sections 5.7
// and 5.9)
TEST_P(GLSLValidationTest, CompareStructsContainingSamplers)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        struct s { sampler2D foo; };
        uniform s a;
        uniform s b;
        void main() {
           bool c = (a == b);
           gl_FragColor = vec4(c ? 1.0 : 0.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'==' : undefined operation for structs containing samplers");
}

// The ESSL 3.00 spec says that equality is supported for all types, but glslang does not accept
// equality between structs with samplers.  The GL workgroup clarified that the intention was to not
// allow comparison between structs with samplers.
TEST_P(GLSLValidationTest_ES3, CompareStructsContainingSamplersESSL300)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
struct S { sampler2D s; };
uniform S a;
uniform S b;
out vec4 c;
void main() {
  c = vec4(a == b ? 1.0 : 0.0);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'==' : undefined operation for structs containing samplers");
}

// https://crbug.com/499176133
TEST_P(GLSLValidationTest, LongIdentifierAtLimit_1024)
{
    std::string longName = "_u";
    longName.append(1024 - 2, 'a');
    std::string shader = R"(
void main() {
  precision mediump float;
  float )" + longName + R"( = 1.0;
})";

    std::string result =
        std::string("'") + longName +
        std::string("' : identifiers beginning with `_` must be < 1022 characters");

    validateError(GL_FRAGMENT_SHADER, shader.c_str(), result.c_str());
}
// https://crbug.com/499176133
TEST_P(GLSLValidationTest, LongIdentifierAtLimit_1023)
{
    std::string longName = "_b";
    longName.append(1023 - 2, 'a');
    std::string shader = R"(
void main() {
  precision mediump float;
  float )" + longName + R"( = 1.0;
})";

    std::string result =
        std::string("'") + longName +
        std::string("' : identifiers beginning with `_` must be < 1022 characters");

    validateError(GL_FRAGMENT_SHADER, shader.c_str(), result.c_str());
}
// https://crbug.com/499176133
TEST_P(GLSLValidationTest, LongIdentifierAtLimit_1022)
{
    std::string longName = "_u";
    longName.append(1022 - 2, 'a');
    std::string shader = R"(
void main() {
  precision mediump float;
  float )" + longName + R"( = 1.0;
})";

    std::string result =
        std::string("'") + longName +
        std::string("' : identifiers beginning with `_` must be < 1022 characters");

    validateError(GL_FRAGMENT_SHADER, shader.c_str(), result.c_str());
}
// https://crbug.com/499176133
TEST_P(GLSLValidationTest, LongIdentifierAtLimit_1021)
{
    std::string longName = "_u";
    longName.append(1021 - 2, 'a');
    std::string shader = R"(
void main() {
  precision mediump float;
  float )" + longName + R"( = 1.0;
})";

    validateSuccess(GL_FRAGMENT_SHADER, shader.c_str());
}

// Samplers are not allowed as l-values (ESSL 3.00 section 4.1.7), our interpretation is that this
// extends to structs containing samplers. ESSL 1.00 spec is clearer about this.
TEST_P(GLSLValidationTest_ES3, AssignStructsContainingSamplers)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        struct s { sampler2D foo; };
        uniform s a;
        out vec4 my_FragColor;
        void main() {
           s b;
           b = a;
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'structure' : structures must be uniform (structure contains a sampler)");
}

// This is a regression test for a particular bug that was in ANGLE.
// It also verifies that ESSL3 functionality doesn't leak to ESSL1.
TEST_P(GLSLValidationTest, ArrayWithNoSizeInInitializerList)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        void main() {
           float a[2], b[];
           gl_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  " '[]' : implicitly sized array supported in GLSL ES 3.00 and above only");
}

// Const variables need an initializer.
TEST_P(GLSLValidationTest_ES3, ConstVarNotInitialized)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           const float a;
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'a' : variables with qualifier 'const' must be initialized");
}

// Const variables need an initializer. In ESSL1 const structs containing
// arrays are not allowed at all since it's impossible to initialize them.
// Even though this test is for ESSL3 the only thing that's critical for
// ESSL1 is the non-initialization check that's used for both language versions.
// Whether ESSL1 compilation generates the most helpful error messages is a
// secondary concern.
TEST_P(GLSLValidationTest_ES3, ConstStructNotInitialized)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        struct S { float a[3]; };
        out vec4 my_FragColor;
        void main() {
           const S b;
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'b' : variables with qualifier 'const' must be initialized");
}

// Const variables need an initializer. In ESSL1 const arrays are not allowed
// at all since it's impossible to initialize them.
// Even though this test is for ESSL3 the only thing that's critical for
// ESSL1 is the non-initialization check that's used for both language versions.
// Whether ESSL1 compilation generates the most helpful error messages is a
// secondary concern.
TEST_P(GLSLValidationTest_ES3, ConstArrayNotInitialized)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           const float a[3];
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'a' : variables with qualifier 'const' must be initialized");
}

// Block layout qualifiers can't be used on non-block uniforms (ESSL 3.00 section 4.3.8.3)
TEST_P(GLSLValidationTest_ES3, BlockLayoutQualifierOnRegularUniform)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        layout(packed) uniform mat2 x;
        out vec4 my_FragColor;
        void main() {
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'packed' : layout qualifier only valid for interface blocks");
}

// Block layout qualifiers can't be used on non-block uniforms (ESSL 3.00 section 4.3.8.3)
TEST_P(GLSLValidationTest_ES3, BlockLayoutQualifierOnUniformWithEmptyDecl)
{
    // Yes, the comma in the declaration below is not a typo.
    // Empty declarations are allowed in GLSL.
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        layout(packed) uniform mat2, x;
        out vec4 my_FragColor;
        void main() {
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'packed' : layout qualifier only valid for interface blocks");
}

// Arrays of arrays are not allowed (ESSL 3.00 section 4.1.9)
TEST_P(GLSLValidationTest_ES3, ArraysOfArrays1)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           float[5] a[3];
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'mediump array[5] of float' : cannot declare arrays of arrays");
}

// Arrays of arrays are not allowed (ESSL 3.00 section 4.1.9)
TEST_P(GLSLValidationTest_ES3, ArraysOfArrays2)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           float[2] a, b[3];
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "mediump array[2] of float' : cannot declare arrays of arrays");
}

// Arrays of arrays are not allowed (ESSL 3.00 section 4.1.9). Test this in a struct.
TEST_P(GLSLValidationTest_ES3, ArraysOfArraysInStruct)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        struct S { float[2] foo[3]; };
        void main() { my_FragColor = vec4(1.0); }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'mediump array[2] of float' : cannot declare arrays of arrays");
}

// Test invalid dimensionality of implicitly sized array constructor arguments.
TEST_P(GLSLValidationTest_ES31,
       TooHighDimensionalityOfImplicitlySizedArrayOfArraysConstructorArguments)
{
    constexpr char kFS[] = R"(#version 310 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            float[][] a = float[][](float[1][1](float[1](1.0)), float[1][1](float[1](2.0)));
            my_FragColor = vec4(a[0][0]);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : constructing from a non-dereferenced array");
}

// Test invalid dimensionality of implicitly sized array constructor arguments.
TEST_P(GLSLValidationTest_ES31,
       TooLowDimensionalityOfImplicitlySizedArrayOfArraysConstructorArguments)
{
    constexpr char kFS[] = R"(#version 310 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
            float[][][] a = float[][][](float[2](1.0, 2.0), float[2](3.0, 4.0));
            my_FragColor = vec4(a[0][0][0]);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : implicitly sized array of arrays constructor argument "
                  "dimensionality is too low");
}

// Implicitly sized arrays need to be initialized (ESSL 3.00 section 4.1.9)
TEST_P(GLSLValidationTest_ES3, UninitializedImplicitArraySize)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           float[] a;
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'a' : implicitly sized arrays only allowed for tessellation shaders or geometry "
                  "shader inputs");
}

// An operator can only form a constant expression if all the operands are constant expressions
// - even operands of ternary operator that are never evaluated. (ESSL 3.00 section 4.3.3)
TEST_P(GLSLValidationTest_ES3, TernaryOperatorNotConstantExpression)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        uniform bool u;
        void main() {
           const bool a = true ? true : u;
           my_FragColor = vec4(1.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "'=' : assigning non-constant to 'const bool'");
}

// Ternary operator can operate on arrays (ESSL 3.00 section 5.7)
TEST_P(GLSLValidationTest_ES3, TernaryOperatorOnArrays)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           float[1] a = float[1](0.0);
           float[1] b = float[1](1.0);
           float[1] c = true ? a : b;
           my_FragColor = vec4(1.0);
        }
    )";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Ternary operator can operate on structs (ESSL 3.00 section 5.7)
TEST_P(GLSLValidationTest_ES3, TernaryOperatorOnStructs)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        struct S { float foo; };
        void main() {
           S a = S(0.0);
           S b = S(1.0);
           S c = true ? a : b;
           my_FragColor = vec4(1.0);
        }
    )";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Array length() returns a constant signed integral expression (ESSL 3.00 section 4.1.9)
// Assigning it to unsigned should result in an error.
TEST_P(GLSLValidationTest_ES3, AssignArrayLengthToUnsigned)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           int[1] arr;
           uint l = arr.length();
           my_FragColor = vec4(float(l));
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : cannot convert from 'const highp int' to 'mediump uint'");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with a varying should be an error.
TEST_P(GLSLValidationTest, AssignVaryingToGlobal)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        varying float a;
        float b = a * 2.0;
        void main() {
           gl_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 3.00 section 4.3)
// Initializing with an uniform should be an error.
TEST_P(GLSLValidationTest_ES3, AssignUniformToGlobalESSL3)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform float a;
        float b = a * 2.0;
        out vec4 my_FragColor;
        void main() {
           my_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with an uniform used to generate a warning on ESSL 1.00 because of legacy
// compatibility, but that causes dEQP to fail (which expects an error)
TEST_P(GLSLValidationTest, AssignUniformToGlobalESSL1)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        uniform float a;
        float b = a * 2.0;
        void main() {
           gl_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with an user-defined function call should be an error.
TEST_P(GLSLValidationTest, AssignFunctionCallToGlobal)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float foo() { return 1.0; }
        float b = foo();
        void main() {
           gl_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with an assignment to another global should be an error.
TEST_P(GLSLValidationTest, AssignAssignmentToGlobal)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float c = 1.0;
        float b = (c = 0.0);
        void main() {
           gl_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  " '=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with incrementing another global should be an error.
TEST_P(GLSLValidationTest, AssignIncrementToGlobal)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        float c = 1.0;
        float b = (c++);
        void main() {
           gl_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  " '=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with an assignment to another global should be an error.
TEST_P(GLSLValidationTest, AssignTexture2DToGlobal)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        uniform mediump sampler2D s;
        float b = texture2D(s, vec2(0.5, 0.5)).x;
        void main() {
           gl_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 3.00 section 4.3)
// Initializing with a non-constant global should be an error.
TEST_P(GLSLValidationTest_ES3, AssignNonConstGlobalToGlobal)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        float a = 1.0;
        float b = a * 2.0;
        out vec4 my_FragColor;
        void main() {
           my_FragColor = vec4(b);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'=' : global variable initializers must be constant expressions");
}

// Global variable initializers need to be constant expressions (ESSL 3.00 section 4.3)
// Initializing with a constant global should be fine.
TEST_P(GLSLValidationTest_ES3, AssignConstGlobalToGlobal)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        const float a = 1.0;
        float b = a * 2.0;
        out vec4 my_FragColor;
        void main() {
           my_FragColor = vec4(b);
        }
    )";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Statically assigning to both gl_FragData and gl_FragColor is forbidden (ESSL 1.00 section 7.2)
TEST_P(GLSLValidationTest, WriteBothFragDataAndFragColor)
{
    constexpr char kFS[] = R"(
        precision mediump float;
        void foo() {
           gl_FragData[0].a++;
        }
        void main() {
           gl_FragColor.x += 0.0;
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "cannot use both gl_FragData and gl_FragColor");
}

// Version directive must be on the first line (ESSL 3.00 section 3.3)
TEST_P(GLSLValidationTest_ES3, VersionOnSecondLine)
{
    constexpr char kFS[] = R"(
        #version 300 es
        precision mediump float;
        out vec4 my_FragColor;
        void main() {
           my_FragColor = vec4(0.0);
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "#version directive must occur on the first line of the shader");
}

// Layout qualifier can only appear in global scope (ESSL 3.00 section 4.3.8)
TEST_P(GLSLValidationTest_ES3, LayoutQualifierInCondition)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform vec4 u;
        out vec4 my_FragColor;
        void main() {
            int i = 0;
            for (int j = 0; layout(location = 0) bool b = false; ++j) {
                ++i;
            }
            my_FragColor = u;
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "'layout' : only allowed at global scope");
}

// Layout qualifier can only appear where specified (ESSL 3.00 section 4.3.8)
TEST_P(GLSLValidationTest_ES3, LayoutQualifierInFunctionReturnType)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform vec4 u;
        out vec4 my_FragColor;
        layout(location = 0) vec4 foo() {
            return u;
        }
        void main() {
            my_FragColor = foo();
        }
    )";

    validateError(GL_FRAGMENT_SHADER, kFS, "'layout' : no qualifiers allowed for function return");
}

// If there is more than one output, the location must be specified for all outputs.
// (ESSL 3.00.04 section 4.3.8.2)
TEST_P(GLSLValidationTest_ES3, TwoOutputsNoLayoutQualifiers)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform vec4 u;
        out vec4 my_FragColor;
        out vec4 my_SecondaryFragColor;
        void main() {
            my_FragColor = vec4(1.0);
            my_SecondaryFragColor = vec4(0.5);
        }
      )";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'my_FragColor' : when EXT_blend_func_extended extension is not enabled, must "
                  "explicitly specify all locations when using multiple fragment outputs");
}

// (ESSL 3.00.04 section 4.3.8.2)
TEST_P(GLSLValidationTest_ES3, TwoOutputsFirstLayoutQualifier)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform vec4 u;
        layout(location = 0) out vec4 my_FragColor;
        out vec4 my_SecondaryFragColor;
        void main() {
            my_FragColor = vec4(1.0);
            my_SecondaryFragColor = vec4(0.5);
        })";

    validateError(
        GL_FRAGMENT_SHADER, kFS,
        "'my_SecondaryFragColor' : when EXT_blend_func_extended extension is not enabled, must "
        "explicitly specify all locations when using multiple fragment outputs");
}

// (ESSL 3.00.04 section 4.3.8.2)
TEST_P(GLSLValidationTest_ES3, TwoOutputsSecondLayoutQualifier)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform vec4 u;
        out vec4 my_FragColor;
        layout(location = 0) out vec4 my_SecondaryFragColor;
        void main() {
            my_FragColor = vec4(1.0);
            my_SecondaryFragColor = vec4(0.5);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'my_FragColor' : when EXT_blend_func_extended extension is not enabled, must "
                  "explicitly specify all locations when using multiple fragment outputs");
}

// Uniforms can be arrays (ESSL 3.00 section 4.3.5)
TEST_P(GLSLValidationTest_ES3, UniformArray)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform vec4[2] u;
        out vec4 my_FragColor;
        void main() {
            my_FragColor = u[0];
      })";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Fragment shader input variables cannot be arrays of structs (ESSL 3.00 section 4.3.4)
TEST_P(GLSLValidationTest_ES3, FragmentInputArrayOfStructs)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        struct S {
            vec4 foo;
        };
        in S i[2];
        out vec4 my_FragColor;
        void main() {
            my_FragColor = i[0].foo;
      })";

    validateError(GL_FRAGMENT_SHADER, kFS, "cannot declare arrays of structs of this qualifier");
}

// Vertex shader inputs can't be arrays (ESSL 3.00 section 4.3.4)
// This test is testing the case where the array brackets are after the variable name, so
// the arrayness isn't known when the type and qualifiers are initially parsed.
TEST_P(GLSLValidationTest_ES3, VertexShaderInputArray)
{
    constexpr char kVS[] = R"(#version 300 es
        precision mediump float;
        in vec4 i[2];
        void main() {
            gl_Position = i[0];
        })";

    validateError(GL_VERTEX_SHADER, kVS, "'in' : cannot declare arrays of this qualifier");
}

// Vertex shader inputs can't be arrays (ESSL 3.00 section 4.3.4)
// This test is testing the case where the array brackets are after the type.
TEST_P(GLSLValidationTest_ES3, VertexShaderInputArrayType)
{
    constexpr char kVS[] = R"(#version 300 es
        precision mediump float;
        in vec4[2] i;
        void main() {
            gl_Position = i[0];
        })";

    validateError(GL_VERTEX_SHADER, kVS, "'in' : cannot be array");
}

// Fragment shader inputs can't contain booleans (ESSL 3.00 section 4.3.4)
TEST_P(GLSLValidationTest_ES3, FragmentShaderInputStructWithBool)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        struct S { bool foo; };
        in S s;
        out vec4 my_FragColor;
        void main() {
            my_FragColor = vec4(0.0);
        })";

    validateError(GL_FRAGMENT_SHADER, kFS, " 'in' : cannot be a structure containing a bool");
}

// Fragment shader inputs without a flat qualifier can't contain integers (ESSL 3.00 section 4.3.4)
TEST_P(GLSLValidationTest_ES3, FragmentShaderInputStructWithInt)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        struct S { int foo; };
        in S s;
        out vec4 my_FragColor;
        void main() {
            my_FragColor = vec4(0.0);
        })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'in' : must use 'flat' interpolation here");
}

// Test that out-of-range integer literal generates an error in ESSL 3.00.
TEST_P(GLSLValidationTest_ES3, OutOfRangeIntegerLiteral)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        precision highp int;
        out vec4 my_FragColor;
        void main() {
            my_FragColor = vec4(0x100000000);
        })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'0x100000000' : Integer overflow");
}

// Test that a ternary operator with one unevaluated non-constant operand is not a constant
// expression.
TEST_P(GLSLValidationTest, TernaryOperatorNonConstantOperand)
{
    constexpr char kFS[] = R"(precision mediump float;
        uniform float u;
        void main() {
            const float f = true ? 1.0 : u;
            gl_FragColor = vec4(f);
        })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'=' : assigning non-constant to 'const mediump float'");
}

// Test that a sampler can't be used in constructor argument list
TEST_P(GLSLValidationTest, SamplerInConstructorArguments)
{
    constexpr char kFS[] = R"(precision mediump float;
        uniform sampler2D s;
        void main()
        {
            vec2 v = vec2(0.0, s);
            gl_FragColor = vec4(v, 0.0, 0.0);
        })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : cannot convert a variable with type sampler2D");
}

// Test that a struct with sampler can't be constructed
TEST_P(GLSLValidationTest, ConstructorWithSampler)
{
    constexpr char kFS[] = R"(precision mediump float;
        struct S {
            sampler2D inStruct;
        };
        uniform sampler2D s;
        void main()
        {
            gl_FragColor = texture2D(S(s).inStruct, vec2(0));
        })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : cannot convert a variable with type sampler2D");
}

// Test that a struct-with-sampler can't be used in a constructor
TEST_P(GLSLValidationTest_ES3, ConstructorWithStructWithSampler)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        struct S {
            sampler2D inStruct;
        };
        uniform S s;
        out vec4 color;
        void main()
        {
            color = texture(S[2](s, s)[0].inStruct, vec2(0));
        })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : cannot convert a variable with struct type containing samplers");
}

// Test that void can't be used in constructor argument list
TEST_P(GLSLValidationTest, VoidInConstructorArguments)
{
    constexpr char kFS[] = R"(precision mediump float;
        void foo() {}
        void main()
        {
            vec2 v = vec2(0.0, foo());
            gl_FragColor = vec4(v, 0.0, 0.0);
        })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'constructor' : cannot convert a void");
}

// Test that a shader with empty constructor parameter list is not accepted.
TEST_P(GLSLValidationTest_ES3, EmptyArrayConstructor)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         uniform float u;
         const float[] f = float[]();
         void main() {
             my_FragColor = vec4(0.0);
      })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'[]' : implicitly sized array constructor must have at least one argument");
}

// Test that indexing fragment outputs with a non-constant expression is forbidden, even if ANGLE
// is able to constant fold the index expression. ESSL 3.00 section 4.3.6.
TEST_P(GLSLValidationTest_ES3, DynamicallyIndexedFragmentOutput)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         uniform int a;
         out vec4[2] my_FragData;
         void main()
         {
             my_FragData[true ? 0 : a] = vec4(0.0);
         }
    )";

    validateError(
        GL_FRAGMENT_SHADER, kFS,
        " '[' : array indexes for fragment outputs must be constant integral expressions");
}

// Test that indexing a uniform buffer array with a non-constant expression is forbidden, even if
// ANGLE is able to constant fold the index expression. ESSL 3.00 section 4.3.7.
TEST_P(GLSLValidationTest_ES3, DynamicallyIndexedUniformBuffer)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform int a;
        uniform B
        {
            vec4 f;
        }
        blocks[2];
        out vec4 my_FragColor;
        void main()
        {
            my_FragColor = blocks[true ? 0 : a].f;
        })";

    validateError(
        GL_FRAGMENT_SHADER, kFS,
        "'[' : array indexes for uniform block arrays must be constant integral expressions");
}

// Test that indexing a storage buffer array with a non-constant expression is forbidden, even if
// ANGLE is able to constant fold the index expression. ESSL 3.10 section 4.3.9.
TEST_P(GLSLValidationTest_ES31, DynamicallyIndexedStorageBuffer)
{
    constexpr char kFS[] = R"(#version 310 es
        precision mediump float;
        uniform int a;
        layout(std140) buffer B
        {
            vec4 f;
        }
        blocks[2];
        out vec4 my_FragColor;
        void main()
        {
            my_FragColor = blocks[true ? 0 : a].f;
        })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'[' : array indexes for shader storage block arrays must be constant integral "
                  "expressions");
}

// Test that indexing a sampler array with a non-constant expression is forbidden, even if ANGLE is
// able to constant fold the index expression. ESSL 3.00 section 4.1.7.1.
TEST_P(GLSLValidationTest_ES3, DynamicallyIndexedSampler)
{
    constexpr char kFS[] = R"(#version 300 es
        precision mediump float;
        uniform int a;
        uniform sampler2D s[2];
        out vec4 my_FragColor;
        void main()
        {
            my_FragColor = texture(s[true ? 0 : a], vec2(0));
        })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'[' : array index for samplers must be constant integral expressions");
}

// Test that indexing an image array with a non-constant expression is forbidden, even if ANGLE is
// able to constant fold the index expression. ESSL 3.10 section 4.1.7.2.
TEST_P(GLSLValidationTest_ES31, DynamicallyIndexedImage)
{
    constexpr char kFS[] = R"(#version 310 es
        precision mediump float;
        uniform int a;
        layout(rgba32f) uniform highp readonly image2D image[2];
        out vec4 my_FragColor;
        void main()
        {
            my_FragColor = imageLoad(image[true ? 0 : a], ivec2(0));
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  " '[' : array indexes for image arrays must be constant integral expressions");
}

// Test that a shader that uses a struct definition in place of a struct constructor does not
// compile. See GLSL ES 1.00 section 5.4.3.
TEST_P(GLSLValidationTest, StructConstructorWithStructDefinition)
{
    constexpr char kFS[] = R"(precision mediump float;
         void main() {
             struct s { float f; } (0.0);
             gl_FragColor = vec4(0.0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'structure' : constructor can't be a structure definition");
}

// Test that indexing gl_FragData with a non-constant expression is forbidden in WebGL 2.0, even
// when ANGLE is able to constant fold the index.
// WebGL 2.0 spec section 'GLSL ES 1.00 Fragment Shader Output'
TEST_P(WebGL2GLSLValidationTest, IndexFragDataWithNonConstant)
{

    constexpr char kFS[] = R"(precision mediump float;
         void main() {
             for (int i = 0; i < 2; ++i) {
                 gl_FragData[true ? 0 : i] = vec4(0.0);
             }
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'[' : array index for gl_FragData must be constant zero");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with an uniform should generate a warning
// (we don't generate an error on ESSL 1.00 because of WebGL compatibility)
TEST_P(WebGL2GLSLValidationTest, AssignUniformToGlobalESSL1)
{

    constexpr char kFS[] = R"(precision mediump float;
         uniform float a;
         float b = a * 2.0;
         void main() {
            gl_FragColor = vec4(b);
    })";

    validateWarning(GL_FRAGMENT_SHADER, kFS,
                    "'=' : global variable initializers should be constant expressions");
}

// Test that deferring global variable init works with an empty main().
TEST_P(WebGL2GLSLValidationTest, DeferGlobalVariableInitWithEmptyMain)
{
    constexpr char kFS[] = R"(precision mediump float;
         uniform float u;
         float foo = u;
         void main() {}
    )";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that a non-constant texture offset is not accepted for textureOffset.
// ESSL 3.00 section 8.8
TEST_P(GLSLValidationTest_ES3, TextureOffsetNonConst)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         uniform vec3 u_texCoord;
         uniform mediump sampler3D u_sampler;
         uniform int x;
         void main() {
            my_FragColor = textureOffset(u_sampler, u_texCoord, ivec3(x, 3, -8));
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'textureOffset' : Texture offset must be a constant expression");
}

// Test that a non-constant texture offset is not accepted for textureProjOffset with bias.
// ESSL 3.00 section 8.8
TEST_P(GLSLValidationTest_ES3, TextureProjOffsetNonConst)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         uniform vec4 u_texCoord;
         uniform mediump sampler3D u_sampler;
         uniform int x;
         void main() {
            my_FragColor = textureProjOffset(u_sampler, u_texCoord, ivec3(x, 3, -8), 0.0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'textureProjOffset' : Texture offset must be a constant expression");
}

// Test that an out-of-range texture offset is not accepted.
// GLES 3.0.4 section 3.8.10 specifies that out-of-range offset has undefined behavior.
TEST_P(GLSLValidationTest_ES3, TextureLodOffsetOutOfRange)
{

    GLint maxOffset = 0;

    glGetIntegerv(GL_MAX_PROGRAM_TEXEL_OFFSET, &maxOffset);

    const std::string kFS = R"(#version 300 es
    precision mediump float;
    out vec4 my_FragColor;
    uniform vec3 u_texCoord;
    uniform mediump sampler3D u_sampler;
    void main() {
        my_FragColor = textureLodOffset(u_sampler, u_texCoord, 0.0, ivec3(0, 0, )" +
                            std::to_string(maxOffset + 1) + R"());
    })";

    validateError(GL_FRAGMENT_SHADER, kFS.c_str(), "Texture offset value out of valid range");
}

// Test that default precision qualifier for uint is not accepted.
// ESSL 3.00.4 section 4.5.4: Only allowed for float, int and sampler types.
TEST_P(GLSLValidationTest_ES3, DefaultPrecisionUint)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         precision mediump uint;
         out vec4 my_FragColor;
         void main() {
            my_FragColor = vec4(0.0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'uint' : illegal type argument for default precision qualifier");
}

// Test that sampler3D needs to be precision qualified.
// ESSL 3.00.4 section 4.5.4: New ESSL 3.00 sampler types don't have predefined precision.
TEST_P(GLSLValidationTest_ES3, NoPrecisionSampler3D)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         uniform sampler3D s;
         out vec4 my_FragColor;
         void main() {
            my_FragColor = vec4(0.0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'sampler3D' : No precision specified");
}

// Test that using a non-constant expression in a for loop initializer is forbidden in WebGL 1.0,
// even when ANGLE is able to constant fold the initializer.
// ESSL 1.00 Appendix A.
TEST_P(WebGLGLSLValidationTest, NonConstantLoopIndex)
{
    constexpr char kFS[] = R"(precision mediump float;
         uniform int u;
         void main() {
             for (int i = (true ? 1 : u); i < 5; ++i) {
                 gl_FragColor = vec4(0.0);
             }
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'i' : Loop index cannot be initialized with non-constant expression");
}

// Global variable initializers need to be constant expressions (ESSL 1.00 section 4.3)
// Initializing with an uniform should generate a warning
// (we don't generate an error on ESSL 1.00 because of WebGL compatibility)
TEST_P(WebGLGLSLValidationTest, AssignUniformToGlobalESSL1)
{
    constexpr char kFS[] = R"(precision mediump float;
         uniform float a;
         float b = a * 2.0;
         void main() {
            gl_FragColor = vec4(b);
    })";

    validateWarning(GL_FRAGMENT_SHADER, kFS,
                    "'=' : global variable initializers should be constant expressions");
}

// Test that deferring global variable init works with an empty main().
TEST_P(WebGLGLSLValidationTest, DeferGlobalVariableInitWithEmptyMain)
{
    constexpr char kFS[] = R"(precision mediump float;
         uniform float u;
         float foo = u;
         void main() {}
    )";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Check that indices that are not integers are rejected.
// The check should be done even if ESSL 1.00 Appendix A limitations are not applied.
TEST_P(GLSLValidationTest, NonIntegerIndex)
{
    constexpr char kFS[] = R"(precision mediump float;
         void main() {
             float f[3];
             const float i = 2.0;
             gl_FragColor = vec4(f[i]);
      })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'[]' : integer expression required");
}

// ESSL1 shaders with a duplicate function prototype should be rejected.
// ESSL 1.00.17 section 4.2.7.
TEST_P(GLSLValidationTest, DuplicatePrototypeESSL1)
{
    constexpr char kFS[] = R"(precision mediump float;
         void foo();
         void foo();
         void foo() {}
         void main()
         {
             gl_FragColor = vec4(0.0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'function' : duplicate function prototype declarations are not allowed");
}

// ESSL3 shaders with a duplicate function prototype should be allowed.
// ESSL 3.00.4 section 4.2.3.
TEST_P(GLSLValidationTest_ES3, DuplicatePrototypeESSL3)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         void foo();
         void foo();
         void foo() {}
         void main() {
             my_FragColor = vec4(0.0);
    })";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Shaders with a local function prototype should be rejected.
// ESSL 3.00.4 section 4.2.4.
TEST_P(GLSLValidationTest_ES3, LocalFunctionPrototype)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         void main() {
             void foo();
             my_FragColor = vec4(0.0);
         })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  " 'function' : local function prototype declarations are not allowed");
}

// Built-in functions can not be overloaded in ESSL 3.00.
TEST_P(GLSLValidationTest_ES3, ESSL300BuiltInFunctionOverload)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         int sin(int x) {
             return int(sin(float(x)));
         }
         void main() {
            my_FragColor = vec4(sin(1));
      })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'sin' : Name of a built-in function cannot be redeclared as function");
}

// Multiplying a 4x2 matrix with a 4x2 matrix should not work.
TEST_P(GLSLValidationTest_ES3, CompoundMultiplyMatrixIdenticalNonSquareDimensions)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 my_FragColor;
         void main() {
            mat4x2 foo;
            foo *= mat4x2(4.0);
            my_FragColor = vec4(0.0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'assign' : cannot convert from 'const 4X2 matrix of float' to 'mediump 4X2 "
                  "matrix of float'");
}

// ESSL 3.00 fragment shaders can not use #pragma STDGL invariant(all).
// ESSL 3.00.4 section 4.6.1. Does not apply to other versions of ESSL.
TEST_P(GLSLValidationTest_ES3, ESSL300FragmentInvariantAll)
{
    constexpr char kFS[] = R"(#version 300 es
         #pragma STDGL invariant(all)
         precision mediump float;
         out vec4 my_FragColor;
         void main() {
             my_FragColor = vec4(0.0);
         })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'invariant' : #pragma STDGL invariant(all) can not be used in fragment shader");
}

// Covers a bug where we would set the incorrect result size on an out-of-bounds vector swizzle.
TEST_P(GLSLValidationTest, OutOfBoundsVectorSwizzle)
{
    constexpr char kFS[] = R"(
        void main() {
            vec2(0).qq;
    })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'qq' : vector field selection out of range");
}

// Covers a bug where strange preprocessor defines could trigger asserts.
TEST_P(GLSLValidationTest, DefineWithSemicolon)
{
    constexpr char kFS[] = R"(#define Def; highp
         uniform Def vec2 a;)";

    validateError(GL_FRAGMENT_SHADER, kFS, " '?' : Error during layout qualifier parsing.");
}

// Covers a bug in our parsing of malformed shift preprocessor expressions.
TEST_P(GLSLValidationTest, LineDirectiveUndefinedShift)
{
    constexpr char kFS[] = "#line x << y";

    validateError(GL_FRAGMENT_SHADER, kFS, "'x' : invalid line number");
}

// Covers a bug in our parsing of malformed shift preprocessor expressions.
TEST_P(GLSLValidationTest, LineDirectiveNegativeShift)
{
    constexpr char kFS[] = "#line x << -1";

    validateError(GL_FRAGMENT_SHADER, kFS, "'x' : invalid line number");
}

// gl_MaxImageUnits is only available in ES 3.1 shaders.
TEST_P(GLSLValidationTest_ES3, MaxImageUnitsInES3Shader)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 myOutput;
         void main() {
            float ff = float(gl_MaxImageUnits);
            myOutput = vec4(ff);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'gl_MaxImageUnits' : undeclared identifier");
}

// struct += struct is an invalid operation.
TEST_P(GLSLValidationTest_ES3, StructCompoundAssignStruct)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 myOutput;
         struct S { float foo; };
         void main() {
            S a, b;
            a += b;
            myOutput = vec4(0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS, "'+=' : Invalid operation for structs");
}

// struct == different struct is an invalid operation.
TEST_P(GLSLValidationTest_ES3, StructEqDifferentStruct)
{
    constexpr char kFS[] = R"(#version 300 es
         precision mediump float;
         out vec4 myOutput;
         struct S { float foo; };
         struct S2 { float foobar; };
         void main() {
            S a;
            S2 b;
            a == b;
            myOutput = vec4(0);
    })";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'==' : wrong operand types - no operation '==' exists that takes a left-hand "
                  "operand of type 'structure 'S'");
}

// Compute shaders are not supported in versions lower than 310.
TEST_P(GLSLValidationTest_ES31, Version100)
{
    constexpr char kCS[] = R"(void main()
        {
        })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "Compute shader is not supported in this shader version.");
}

// Compute shaders are not supported in versions lower than 310.
TEST_P(GLSLValidationTest_ES31, Version300)
{
    constexpr char kCS[] = R"(#version 300 es
        void main()
        {
        })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "Compute shader is not supported in this shader version.");
}

// Compute shaders should have work group size specified. However, it is not a compile time error
// to not have the size specified, but rather a link time one.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, NoWorkGroupSizeSpecified)
{
    constexpr char kCS[] = R"(#version 310 es
    void main() {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Test that workgroup size declaration doesn't accept variable declaration.
TEST_P(GLSLValidationTest_ES31, NoVariableDeclrationAfterWorkGroupSize)
{
    constexpr char kCS[] = R"(#version 310 es
        layout(local_size_x = 1) in vec4 x;
        void main()
        {
        })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// Work group size is less than 1. It should be at least 1.
// GLSL ES 3.10 Revision 4, 7.1.3 Compute Shader Special Variables
// The spec is not clear whether having a local size qualifier equal zero
// is correct.
// TODO (mradev): Ask people from Khronos to clarify the spec.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeTooSmallXdimension)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 0) in;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS, "'0' : out of range: local_size_x must be positive");
}

// Work group size is correct for the x and y dimensions, but not for the z dimension.
// GLSL ES 3.10 Revision 4, 7.1.3 Compute Shader Special Variables
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeTooSmallZDimension)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 4, local_size_y = 6, local_size_z = 0) in;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS, "'0' : out of range: local_size_z must be positive");
}

// Work group size is bigger than the maximum in the x dimension.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeTooBigXDimension)
{

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

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid value: Value must be at least 1 and no greater than");
}

// Work group size is bigger than the maximum in the y dimension.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeTooBigYDimension)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5, local_size_y = 9989899) in;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_y' : invalid value: Value must be at least 1 and no greater than");
}

// Work group size is definitely bigger than the maximum in the z dimension.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeTooBigZDimension)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5, local_size_y = 5, local_size_z = 9989899) in;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_z' : invalid value: Value must be at least 1 and no greater than");
}

// Work group size specified through macro expansion.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeMacro)
{
    constexpr char kCS[] = R"(#version 310 es
    #define MYDEF(x) x
    layout(local_size_x = MYDEF(127)) in;
    void main()
    {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Work group size specified as an unsigned integer.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeUnsignedInteger)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 123u) in;
    void main() {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Work group size specified in hexadecimal.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeHexadecimal)
{
    constexpr char kCS[] = R"(#version 310 es
         layout(local_size_x = 0x3A) in;
         void main()
         {
         })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// local_size_x is -1 in hexadecimal format.
// -1 is used as unspecified value in the TLayoutQualifier structure.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeMinusOneHexadecimal)
{
    constexpr char kCS[] = R"(#version 310 es
         layout(local_size_x = 0xFFFFFFFF) in;
         void main()
         {
         })";

    validateError(GL_COMPUTE_SHADER, kCS, "'-1' : out of range: local_size_x must be positive");
}

// Work group size specified in octal.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeOctal)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 013) in;
    void main() {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Work group size is negative. It is specified in hexadecimal.
TEST_P(GLSLValidationTest_ES31, WorkGroupSizeNegativeHexadecimal)
{
    constexpr char kCS[] = R"(#version 310 es
         layout(local_size_x = 0xFFFFFFEC) in;
         void main()
         {
         })";

    validateError(GL_COMPUTE_SHADER, kCS, "'-20' : out of range: local_size_x must be positive");
}

// Verify that a compile error is generated when the total size of shared memory exceeds
// GL_MAX_COMPUTE_SHARED_MEMORY_SIZE.
TEST_P(GLSLValidationTest_ES31, ExceedComputeSharedMemorySize)
{
    GLint maxComputeSharedMemorySize;
    glGetIntegerv(GL_MAX_COMPUTE_SHARED_MEMORY_SIZE, &maxComputeSharedMemorySize);

    // Using exactly GL_MAX_COMPUTE_SHARED_MEMORY_SIZE memory is ok
    {
        std::ostringstream cs;
        cs << R"(#version 310 es
layout (local_size_x = 1) in;
layout (r32ui, binding = 0) writeonly uniform highp uimage2D img;
shared uint temp[)"
           << (maxComputeSharedMemorySize / sizeof(uint32_t)) << R"(];
void main()
{
    temp[gl_LocalInvocationID.x] = 0u;
    groupMemoryBarrier();
    barrier();
    imageStore(img, ivec2(0), uvec4(temp[gl_LocalInvocationID.x + 1u]));
})";
        validateSuccess(GL_COMPUTE_SHADER, cs.str().c_str());
    }

    // Using any more than GL_MAX_COMPUTE_SHARED_MEMORY_SIZE memory is not ok
    {
        std::ostringstream cs;
        cs << R"(#version 310 es
layout (local_size_x = 1) in;
layout (r32ui, binding = 0) writeonly uniform highp uimage2D img;
shared uint temp[)"
           << (maxComputeSharedMemorySize / sizeof(uint32_t) + 1) << R"(];
void main()
{
    temp[gl_LocalInvocationID.x] = 0u;
    groupMemoryBarrier();
    barrier();
    imageStore(img, ivec2(0), uvec4(temp[gl_LocalInvocationID.x + 1u]));
})";

        validateError(GL_COMPUTE_SHADER, cs.str().c_str(),
                      "Shared memory size exceeds GL_MAX_COMPUTE_SHARED_MEMORY_SIZE");
    }

    // Using more than GL_MAX_COMPUTE_SHARED_MEMORY_SIZE split between multiple |shared| variables
    // is not ok.
    // This validation is not correctly implemented in the AST path.
    if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
    {
        const GLint maxSharedUintCount     = maxComputeSharedMemorySize / sizeof(uint32_t);
        const GLint halfMaxSharedUintCount = maxSharedUintCount / 2;

        std::ostringstream cs;
        cs << R"(#version 310 es
layout (local_size_x = 1) in;
layout (r32ui, binding = 0) writeonly uniform highp uimage2D img;
shared uint temp1[)"
           << halfMaxSharedUintCount << R"(];
shared uint temp2[)"
           << (maxSharedUintCount - halfMaxSharedUintCount) << R"(];
shared uint temp3;
void main()
{
    temp1[gl_LocalInvocationID.x] = temp2[gl_LocalInvocationID.x] = temp3 = 0u;
    groupMemoryBarrier();
    barrier();
    imageStore(img, ivec2(0), uvec4(temp1[gl_LocalInvocationID.x + 1u] + temp2[gl_LocalInvocationID.x + 1u] + temp3));
})";

        validateError(GL_COMPUTE_SHADER, cs.str().c_str(),
                      "Shared memory size exceeds GL_MAX_COMPUTE_SHARED_MEMORY_SIZE");
    }
}

// Multiple work group layout qualifiers with differing values.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, DifferingLayoutQualifiers)
{
    constexpr char kCS[] = R"(#version 310 es
         layout(local_size_x = 5, local_size_x = 6) in;
         void main()
         {
         })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : Cannot have multiple different work group size specifiers");
}

// Multiple work group input variables with differing local size values.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, MultipleInputVariablesDifferingLocalSize)
{
    constexpr char kCS[] = R"(#version 310 es
         layout(local_size_x = 5, local_size_y = 6) in;
         layout(local_size_x = 5, local_size_y = 7) in;
         void main()
         {
         }
    )";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'layout' : Work group size does not match the previous declaration");
}

// Multiple work group input variables with differing local size values.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, MultipleInputVariablesDifferingLocalSize2)
{
    constexpr char kCS[] = R"(#version 310 es
         layout(local_size_x = 5) in;
         layout(local_size_x = 5, local_size_y = 7) in;
         void main()
         {
         })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'layout' : Work group size does not match the previous declaration");
}

// Multiple work group input variables with the same local size values. It should compile.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, MultipleInputVariablesSameLocalSize)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5, local_size_y = 6) in;
    layout(local_size_x = 5, local_size_y = 6) in;
    void main() {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Multiple work group input variables with the same local size values. It should compile.
// Since the default value is 1, it should compile.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, MultipleInputVariablesSameLocalSize2)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5) in;
    layout(local_size_x = 5, local_size_y = 1) in;
    void main() {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Multiple work group input variables with the same local size values. It should compile.
// Since the default value is 1, it should compile.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, MultipleInputVariablesSameLocalSize3)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5, local_size_y = 1) in;
    layout(local_size_x = 5) in;
    void main() {
    })";

    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Specifying row_major qualifier in a work group size layout.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, RowMajorInComputeInputLayout)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5, row_major) in;
    void main()
    {
    })";

    validateError(GL_COMPUTE_SHADER, kCS, "'layout' : invalid layout qualifier combination");
}

// local size layout can be used only with compute input variables
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, UniformComputeInputLayout)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5) uniform;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// local size layout can be used only with compute input variables
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, UniformBufferComputeInputLayout)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5) uniform SomeBuffer { vec4 something; };
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// local size layout can be used only with compute input variables
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, StructComputeInputLayout)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5) struct SomeBuffer { vec4 something; };
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// local size layout can be used only with compute input variables
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, StructBodyComputeInputLayout)
{
    constexpr char kCS[] = R"(#version 310 es
    struct S {
      layout(local_size_x = 12) vec4 foo;
    };
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// local size layout can be used only with compute input variables
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, TypeComputeInputLayout)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 5) vec4;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// Invalid use of the out storage qualifier in a compute shader.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, InvalidOutStorageQualifier)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 15) in;
    out vec4 myOutput;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  " 'out' : storage qualifier isn't supported in compute shaders");
}

// Invalid use of the out storage qualifier in a compute shader.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, InvalidOutStorageQualifier2)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 15) in;
    out myOutput;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'out' : storage qualifier isn't supported in compute shaders");
}

// Invalid use of the in storage qualifier. Can be only used to describe the local block size.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, InvalidInStorageQualifier)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 15) in;
    in vec4 myInput;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS,
                  "'in' : 'in' can only be used to specify the local group size");
}

// Invalid use of the in storage qualifier. Can be only used to describe the local block size.
// The test checks a different part of the GLSL grammar than what InvalidInStorageQualifier
// checks.
// GLSL ES 3.10 Revision 4, 4.4.1.1 Compute Shader Inputs
TEST_P(GLSLValidationTest_ES31, InvalidInStorageQualifier2)
{
    constexpr char kCS[] = R"(#version 310 es
    layout(local_size_x = 15) in;
    in myInput;
    void main() {
    })";

    validateError(GL_COMPUTE_SHADER, kCS, "'myInput' : Expected invariant or precise");
}

// The local_size layout qualifier is only available in compute shaders.
TEST_P(GLSLValidationTest_ES31, VS_InvalidUseOfLocalSizeX)
{
    constexpr char kVS[] = R"(#version 310 es
    precision mediump float;
    layout(local_size_x = 15) in vec4 myInput;
    out vec4 myOutput;
    void main() {
        myOutput = myInput;
    })";

    validateError(GL_VERTEX_SHADER, kVS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}

// The local_size layout qualifier is only available in compute shaders.
TEST_P(GLSLValidationTest_ES31, FS_InvalidUseOfLocalSizeX)
{
    constexpr char kFS[] = R"(#version 310 es
    precision mediump float;
    layout(local_size_x = 15) in vec4 myInput;
    out vec4 myOutput;
    void main() {
      myOutput = myInput;
    })";

    validateError(GL_VERTEX_SHADER, kFS,
                  "'local_size_x' : invalid layout qualifier: only valid when used with 'in' in a "
                  "compute shader global layout declaration");
}
// Verify that using maximum size as atomic counter offset results in compilation failure.
TEST_P(GLSLValidationTest_ES31, CompileWithMaxAtomicCounterOffsetFails)
{
    GLint maxSize;
    glGetIntegerv(GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE, &maxSize);

    std::ostringstream fs;
    fs << R"(#version 310 es
layout(location = 0) out uvec4 color;
layout(binding = 0, offset = )"
       << maxSize << R"() uniform atomic_uint a_counter;
void main() {
color = uvec4(atomicCounterIncrement(a_counter));
})";
    validateError(
        GL_FRAGMENT_SHADER, fs.str().c_str(),
        "'atomic counter' : Offset must not exceed the maximum atomic counter buffer size");
}

// Check that having an invalid char after the "." doesn't cause an assert.
TEST_P(GLSLValidationTest, InvalidFieldFirstChar)
{
    constexpr char kVS[] = "void main() {vec4 x; x.}";
    validateError(GL_VERTEX_SHADER, kVS, ": '}' : Illegal character at fieldname start");
}

// Tests that bad index expressions don't crash ANGLE's translator.
// http://anglebug.com/42266998
TEST_P(GLSLValidationTest, BadIndexBugVec)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform vec4 uniformVec;
void main()
{
    gl_FragColor = vec4(uniformVec[int()]);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : constructor does not have any arguments");
}

// Tests that bad index expressions don't crash ANGLE's translator.
// http://anglebug.com/42266998
TEST_P(GLSLValidationTest, BadIndexBugMat)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform mat4 uniformMat;
void main()
{
    gl_FragColor = vec4(uniformMat[int()]);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : constructor does not have any arguments");
}

// Tests that bad index expressions don't crash ANGLE's translator.
// http://anglebug.com/42266998
TEST_P(GLSLValidationTest, BadIndexBugArray)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform vec4 uniformArray;
void main()
{
    gl_FragColor = vec4(uniformArray[int()]);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : constructor does not have any arguments");
}

// Test that GLSL error on gl_DepthRange does not crash.
TEST_P(GLSLValidationTestNoValidation, DepthRangeError)
{
    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    gl_DepthRange + 1;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'+' : Invalid operation for structs");
}

// Test that an inout value in a location beyond the MaxDrawBuffer limit when using the shader
// framebuffer fetch extension results in a compilation error.
// (Based on a fuzzer-discovered issue)
TEST_P(GLSLValidationTest_ES3, CompileFSWithInoutLocBeyondMaxDrawBuffers)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));

    GLint maxDrawBuffers;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);

    const std::string fs = R"(#version 300 es
#extension GL_EXT_shader_framebuffer_fetch : require
precision highp float;
layout(location = )" + std::to_string(maxDrawBuffers) +
                           R"() inout vec4 inoutArray[1];
void main()
{
    vec4 val = inoutArray[0];
    inoutArray[0] = val + vec4(0.1, 0.2, 0.3, 0.4);
})";
    validateError(GL_FRAGMENT_SHADER, fs.c_str(),
                  "'inoutArray' : output location must be < MAX_DRAW_BUFFERS");
}

// Test that structs with samplers are not allowed in interface blocks.  This is forbidden per
// GLES3:
//
// > Types and declarators are the same as for other uniform variable declarations outside blocks,
// > with these exceptions:
// > * opaque types are not allowed
TEST_P(GLSLValidationTest_ES3, StructWithSamplersDisallowedInInterfaceBlock)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
struct S { sampler2D samp; bool b; };

layout(std140) uniform Buffer { S s; } buffer;

out vec4 color;

void main()
{
    color = texture(buffer.s.samp, vec2(0));
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'Buffer' : Opaque types are not allowed in interface blocks");
}

// Test that *= on boolean vectors fails compilation
TEST_P(GLSLValidationTest, BVecMultiplyAssign)
{
    constexpr char kFS[] = R"(bvec4 c,s;void main(){s*=c;})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'assign' : cannot convert from '4-component vector of bool' to '4-component "
                  "vector of bool'");
}

// Test that gl_VertexID cannot be written to.
TEST_P(GLSLValidationTest_ES3, VertexIDNoWrite)
{
    constexpr char kVS[] = R"(#version 300 es
void main(){gl_VertexID=0;})";
    validateError(GL_VERTEX_SHADER, kVS,
                  "l-value required (can't modify gl_VertexID \"gl_VertexID\")");
}

// Test that gl_InstanceID cannot be written to.
TEST_P(GLSLValidationTest_ES3, InstanceIDNoWrite)
{
    constexpr char kVS[] = R"(#version 300 es
void main(){gl_InstanceID=0;})";
    validateError(GL_VERTEX_SHADER, kVS,
                  "l-value required (can't modify gl_InstanceID \"gl_InstanceID\")");
}

// Test that gl_BaseVertex cannot be written to.
TEST_P(GLSLValidationTest_ES3, BaseVertexNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin"));

    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
void main(){gl_BaseVertex=0;})";
    validateError(GL_VERTEX_SHADER, kVS,
                  "l-value required (can't modify gl_BaseVertex \"gl_BaseVertex\")");
}

// Test that gl_BaseInstance cannot be written to.
TEST_P(GLSLValidationTest_ES3, BaseInstanceNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin"));

    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
void main(){gl_BaseInstance=0;})";
    validateError(GL_VERTEX_SHADER, kVS,
                  "l-value required (can't modify gl_BaseInstance \"gl_BaseInstance\")");
}

// Test that gl_DrawID cannot be written to.
TEST_P(GLSLValidationTest_ES3, DrawIDNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_multi_draw"));

    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_multi_draw : require
void main(){gl_DrawID=0;})";
    validateError(GL_VERTEX_SHADER, kVS, "l-value required (can't modify gl_DrawID \"gl_DrawID\")");
}

// Test that gl_DepthRange cannot be written to.
TEST_P(GLSLValidationTest_ES3, DepthRangeNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_multi_draw"));

    constexpr char kFS[] = R"(void main(){gl_DepthRange.near=0.;})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "l-value required (can't modify gl_DepthRange \"gl_DepthRange\")");
}

// Test that gl_SampleMaskIn cannot be written to.
TEST_P(GLSLValidationTest_ES3, SampleMaskInNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));

    constexpr char kFS[] = R"(#version 300 es
#extension GL_OES_sample_variables : require
void main(){gl_SampleMaskIn[0]=0;})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "l-value required (can't modify gl_SampleMaskIn \"gl_SampleMaskIn\")");
}

// Test that gl_NumSamples cannot be written to.
TEST_P(GLSLValidationTest_ES3, NumSamplesNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OES_sample_variables"));

    constexpr char kFS[] = R"(#version 300 es
#extension GL_OES_sample_variables : require
void main(){gl_NumSamples=0;})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "l-value required (can't modify gl_NumSamples \"gl_NumSamples\")");
}

// Test that gl_PatchVerticesIn cannot be written to.
TEST_P(GLSLValidationTest_ES31, PatchVerticesInNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
void main(){gl_PatchVerticesIn=0;})";
    validateError(GL_TESS_CONTROL_SHADER, kTCS,
                  "l-value required (can't modify gl_PatchVerticesIn \"gl_PatchVerticesIn\")");
}

// Test that gl_TessCoord cannot be written to.
TEST_P(GLSLValidationTest_ES31, TessCoordNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
void main(){gl_TessCoord=0;})";
    validateError(GL_TESS_EVALUATION_SHADER, kTES,
                  "l-value required (can't modify gl_TessCoord \"gl_TessCoord\")");
}

// Test that patch in variables cannot be written to.
TEST_P(GLSLValidationTest_ES31, PatchInNoWrite)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
patch in float f;
void main(){f=0.;})";
    validateError(GL_TESS_EVALUATION_SHADER, kTES,
                  "l-value required (can't modify an input \"f\")");
}

// Test no mangling collision in structs
TEST_P(GLSLValidationTest, ManglingCollisionInStruct)
{
    constexpr char kFS[] = R"(precision mediump float;
struct A00B { vec4 y; };
struct A    { float x; vec4 y; };

void foo(A00B p);
void foo(A p) {}

void main() {
    A00B v = A00B(vec4(0));
    foo(v);
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "Function foo() called by main() is undefined");
}

// Test no mangling collision in function parameters
TEST_P(GLSLValidationTest, ManglingCollisionInFunctionParams)
{
    constexpr char kFS[] = R"(precision mediump float;
void fooA00B(vec4 y);
void foo(float x, vec4 y) {}

void main() {
    fooA00B(vec4(0));
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "Function fooA00B() called by main() is undefined");
}

// Test that infinite loop with while(true) is rejected
TEST_P(WebGL2GLSLValidationTest, InfiniteLoopWhileTrue)
{
    testInfiniteLoop(R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    // Infinite loop
    while (true)
    {
        r += 0.1;
        if (r > 0.)
        {
            continue;
        }
    }

    color = vec4(r, g, b, 1);
})");
}

// Test that infinite loop with for(;true;) is rejected
TEST_P(WebGL2GLSLValidationTest, InfiniteLoopForTrue)
{
    testInfiniteLoop(R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    // Infinite loop
    for (;!false;)
    {
        r += 0.1;
    }

    color = vec4(r, g, b, 1);
})");
}

// Test that infinite loop with do{} while(true) is rejected
TEST_P(WebGL2GLSLValidationTest, InfiniteLoopDoWhileTrue)
{
    testInfiniteLoop(R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    // Infinite loop
    do
    {
        r += 0.1;
        switch (uint(r))
        {
            case 0:
                g += 0.1;
                break;
            default:
                b += 0.1;
                continue;
        }
    } while (true);

    color = vec4(r, g, b, 1);
})");
}

// Test that infinite loop with constant local variable is rejected
TEST_P(WebGL2GLSLValidationTest, InfiniteLoopLocalVariable)
{
    testInfiniteLoop(R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    bool localConstTrue = true;

    // Infinite loop
    do
    {
        r += 0.1;
        switch (uint(r))
        {
            case 0:
                g += 0.1;
                break;
            default:
                b += 0.1;
                continue;
        }
    } while (localConstTrue);

    color = vec4(r, g, b, 1);
})");
}

// Test that infinite loop with global variable is rejected
TEST_P(WebGL2GLSLValidationTest, InfiniteLoopGlobalVariable)
{
    testInfiniteLoop(R"(#version 300 es
precision highp float;
uniform uint zero;
out vec4 color;

bool globalConstTrue = true;

void main()
{
    float r = 0.;
    float g = 1.;
    float b = 0.;

    // Infinite loop
    do
    {
        r += 0.1;
        switch (uint(r))
        {
            case 0:
                g += 0.1;
                break;
            default:
                b += 0.1;
                continue;
        }
    } while (globalConstTrue);

    color = vec4(r, g, b, 1);
})");
}

// Test that indexing swizzles out of bounds fails
TEST_P(GLSLValidationTest_ES3, OutOfBoundsIndexingOfSwizzle)
{
    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 colorOut;
uniform vec3 colorIn;

void main()
{
    colorOut = vec4(colorIn.yx[2], 0, 0, 1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'[]' : vector field selection out of range");
}

// Regression test for a validation bug in the translator where func(void, int) was accepted even
// though it's illegal, and the function was callable as if the void parameter isn't there.
TEST_P(GLSLValidationTest, NoParameterAfterVoid)
{
    constexpr char kVS[] = R"(void f(void, int a){}
void main(){f(1);})";
    validateError(GL_VERTEX_SHADER, kVS, "'void' : cannot be a parameter type except for '(void)'");
}

// Similar to NoParameterAfterVoid, but tests func(void, void).
TEST_P(GLSLValidationTest, NoParameterAfterVoid2)
{
    constexpr char kVS[] = R"(void f(void, void){}
void main(){f();})";
    validateError(GL_VERTEX_SHADER, kVS, "'void' : cannot be a parameter type except for '(void)'");
}

// Test that structs with too many fields are rejected.  In SPIR-V, the instruction that defines the
// struct lists the fields which means the length of the instruction is a function of the field
// count.  Since SPIR-V instruction sizes are limited to 16 bits, structs with more fields cannot be
// represented.
TEST_P(GLSLValidationTest_ES3, TooManyFieldsInStruct)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
precision highp float;
struct TooManyFields
{
)";
    for (uint32_t i = 0; i < (1 << 16); ++i)
    {
        fs << "    float field" << i << ";\n";
    }
    fs << R"(};
uniform B { TooManyFields s; };
out vec4 color;
void main() {
    color = vec4(s.field0, 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
                  "'TooManyFields' : Too many fields in the struct");
}

// Same as TooManyFieldsInStruct, but with samplers in the struct.
TEST_P(GLSLValidationTest_ES3, TooManySamplerFieldsInStruct)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
precision highp float;
struct TooManyFields
{
)";
    for (uint32_t i = 0; i < (1 << 16); ++i)
    {
        fs << "    sampler2D field" << i << ";\n";
    }
    fs << R"(};
uniform TooManyFields s;
out vec4 color;
void main() {
    color = texture(s.field0, vec2(0));
})";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
                  "'TooManyFields' : Too many fields in the struct");
}

// Test having many samplers in nested structs.
TEST_P(GLSLValidationTest_ES3, ManySamplerFieldsInStructComplex)
{
    // D3D and OpenGL may be more restrictive about this many samplers.
    ANGLE_SKIP_TEST_IF(IsD3D() || IsOpenGL());

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

struct X {
    mediump sampler2D a[0xf00];
    mediump sampler2D b[0xf00];
    mediump sampler2D c[0xf000];
    mediump sampler2D d[0xf00];
};

struct Y {
  X s1;
  mediump sampler2D a[0xf00];
  mediump sampler2D b[0xf000];
  mediump sampler2D c[0x14000];
};

struct S {
    Y s1;
};

struct structBuffer { S s; };

uniform structBuffer b;

out vec4 color;
void main()
{
    color = texture(b.s.s1.s1.c[0], vec2(0));
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Make sure a large array of samplers works.
TEST_P(GLSLValidationTest, ManySamplers)
{
    // D3D and OpenGL may be more restrictive about this many samplers.
    ANGLE_SKIP_TEST_IF(IsD3D() || IsOpenGL());

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

uniform mediump sampler2D c[0x12000];

void main()
{
    gl_FragColor = texture2D(c[0], vec2(0));
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Make sure a large array of samplers works when declared in a struct.
TEST_P(GLSLValidationTest, ManySamplersInStruct)
{
    // D3D and OpenGL may be more restrictive about this many samplers.
    ANGLE_SKIP_TEST_IF(IsD3D() || IsOpenGL());

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

struct X {
    mediump sampler2D c[0x12000];
};

uniform X x;

void main()
{
    gl_FragColor = texture2D(x.c[0], vec2(0));
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that passing large arrays to functions are compiled correctly.  Regression test for the
// SPIR-V generator that made a copy of the array to pass to the function, by decomposing and
// reconstructing it (in the absence of OpCopyLogical), but the reconstruction instruction has a
// length higher than can fit in SPIR-V.
TEST_P(WebGL2GLSLValidationTest, LargeInterfaceBlockArrayPassedToFunction)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform Large { float a[65536]; };
float f(float b[65536])
{
    b[0] = 1.0;
    return b[0] + b[1];
}
out vec4 color;
void main() {
    color = vec4(f(a), 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "Size of declared private variable exceeds implementation-defined limit");
}

// Similar to LargeInterfaceBlockArrayPassedToFunction, but the array is nested in a struct.
TEST_P(WebGL2GLSLValidationTest, LargeInterfaceBlockNestedArrayPassedToFunction)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
struct S { float a[65536]; };
uniform Large { S s; };
float f(float b[65536])
{
    b[0] = 1.0;
    return b[0] + b[1];
}
out vec4 color;
void main() {
    color = vec4(f(s.a), 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "Size of declared private variable exceeds implementation-defined limit");
}

// Similar to LargeInterfaceBlockArrayPassedToFunction, but the large array is copied to a local
// variable instead.
TEST_P(WebGL2GLSLValidationTest, LargeInterfaceBlockArrayCopiedToLocal)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
uniform Large { float a[65536]; };
out vec4 color;
void main() {
    float b[65536] = a;
    color = vec4(b[0], 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "Size of declared private variable exceeds implementation-defined limit");
}

// Similar to LargeInterfaceBlockArrayCopiedToLocal, but the array is nested in a struct
TEST_P(WebGL2GLSLValidationTest, LargeInterfaceBlockNestedArrayCopiedToLocal)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
struct S { float a[65536]; };
uniform Large { S s; };
out vec4 color;
void main() {
    S s2 = s;
    color = vec4(s2.a[0], 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "Size of declared private variable exceeds implementation-defined limit");
}

// Test that too large varyings are rejected.
TEST_P(WebGL2GLSLValidationTest, LargeArrayVarying)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
in float a[65536];
out vec4 color;
void main() {
    color = vec4(a[0], 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'a' : Size of declared private variable exceeds implementation-defined limit");
}

// Test that too large local variables are rejected after the same type passes validation as a
// uniform
TEST_P(WebGL2GLSLValidationTest, LargeArrayAfterUsedAsUniform)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
struct S
{
    float a[65536];
};
uniform S u;
out vec4 color;
void main() {
    S l = u;
    color = vec4(l.a[0], 0.0, 0.0, 1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'l' : Size of declared private variable exceeds implementation-defined limit");
}

// Test that repeated declarations are counted towards the total private variable limit
TEST_P(WebGL2GLSLValidationTest, RepeatedDeclarationsOfSameTimeTooLarge)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
precision highp float;
// Declare a struct that's within the 65536-byte limit
struct S
{
    float a[16384];
};
out vec4 color;
void main() {
    float f;
)";
    // Repeatedly declare a variable of this type, so that the total limit is reached, which is 256
    // times the per-variable limit.
    for (uint32_t i = 0; i < 257; ++i)
    {
        fs << "S s" << i << "; f += s" << i << ".a[0];\n";
    }
    fs << R"(
    color = vec4(f, 0.0, 0.0, 1.0);
})";
    validateError(
        GL_FRAGMENT_SHADER, fs.str().c_str(),
        "'' : Total size of declared private variables exceeds implementation-defined limit");
}

// Test that too large array, where cast to signed int would produce negative sizes, does not crash.
TEST_P(WebGL2GLSLValidationTest, LargeArrayUintMaxSize)
{
    constexpr char kFS[] = R"(#version 300 es
int rr[~1U];
out int o;
void main() {
    o = rr[1];
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'rr' : Size of declared variable exceeds implementation-defined limit");
}

// Test that too large array in UBO, where cast to signed int would produce negative sizes, does not
// crash.
TEST_P(WebGL2GLSLValidationTest, LargeArrayUintMaxSizeInUBO)
{
    constexpr char kFS[] = R"(#version 300 es
uniform Block
{
    int rr[~1U];
};
out int o;
void main() {
    o = rr[1];
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'Block' : Size of declared variable exceeds implementation-defined limit");
}

// Test that too large array in UBO fails after another UBO within limit is declared.
TEST_P(WebGL2GLSLValidationTest, LargeArrayUBOAfterSmallUBO)
{
    constexpr char kFS[] = R"(#version 300 es
uniform Small
{
    int i;
};
uniform Block
{
    int rr[~1U];
};
out int o;
void main() {
    o = rr[1] + i;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'Block' : Size of declared variable exceeds implementation-defined limit");
}

// Regression test for a 32-bit overflow bug when setting initializer for a large constant.
TEST_P(WebGL2GLSLValidationTest, LargeConstantVariableWithInitializer)
{
    const int N1 = 256;
    const int N2 = 256;
    const int N3 = 65537;

    std::ostringstream aInit;
    const char *delim = "";
    for (int i = 0; i < N1; i++)
    {
        aInit << delim << "1.5";
        delim = ",";
    }

    std::ostringstream bInit;
    delim = "";
    for (int i = 0; i < N2; i++)
    {
        bInit << delim << "sA";
        delim = ",";
    }

    std::ostringstream cInit;
    delim = "";
    for (int i = 0; i < N3; i++)
    {
        cInit << delim << "sB";
        delim = ",";
    }

    // Set up a shader with large arrays, overflowing 32-bit math.
    //
    // S: 256*sizeof(float) = 1024 bytes
    // S2: 256*sizeof(S) = 256KB
    // c: 65537*sizeof(S2) >= 4*4GB
    std::ostringstream fs;
    fs << "#version 300 es\n"
       << "precision highp float;\n"
       << "struct S { float a[" << N1 << "]; };\n"
       << "struct S2 { S b[" << N2 << "]; };\n"
       << "const float a[" << N1 << "] = float[" << N1 << "](" << aInit.str() << ");\n"
       << "const S sA = S(a);\n"
       << "const S b[" << N2 << "] = S[" << N2 << "](" << bInit.str() << ");\n"
       << "const S2 sB = S2(b);\n"
       << "const S2 c[" << N3 << "] = S2[" << N3 << "](" << cInit.str() << ");\n"
       << "void main(){}\n";

    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
                  "Size of declared private variable exceeds implementation-defined limit");
}

// Test using a large constant that is declared inline. Construction of such a large object, even if
// it may be constant folded is not allowed.
TEST_P(WebGL2GLSLValidationTest, InlineLargeConstant)
{
    const int N1 = 256;
    const int N2 = 32;
    const int N3 = 65536 * 8 + 1;

    std::ostringstream aInit;
    const char *delim = "";
    for (int i = 0; i < N1; i++)
    {
        aInit << delim << "1.5";
        delim = ",";
    }

    std::ostringstream bInit;
    delim = "";
    for (int i = 0; i < N2; i++)
    {
        bInit << delim << "sA";
        delim = ",";
    }

    std::ostringstream s2;
    s2 << "S2[" << N3 << "](";
    delim = "";
    for (int i = 0; i < N3; i++)
    {
        s2 << delim << "sB";
        delim = ",";
    }
    s2 << ")";

    // Set up a shader with large arrays, overflowing 32-bit math.
    //
    // S: 256*sizeof(float) = 1024 bytes
    // S2: 32*sizeof(S) = 32KB
    // constant: (65536 * 8 + 1)*sizeof(S2) >= 4*4GB
    std::ostringstream fs;
    fs << "#version 300 es\n"
       << "precision highp float;\n"
       << "struct S { float a[" << N1 << "]; };\n"
       << "struct S2 { S b[" << N2 << "]; };\n"
       << "const float a[" << N1 << "] = float[" << N1 << "](" << aInit.str() << ");\n"
       << "const S sA = S(a);\n"
       << "const S b[" << N2 << "] = S[" << N2 << "](" << bInit.str() << ");\n"
       << "const S2 sB = S2(b);\n"
       << "void main(){ " << s2.str() << "[0].b[0].a[0]; }\n";

    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
                  "'' : Size of declared variable exceeds implementation-defined limit");
}

// Validate that too-large structures cannot be instantiated as temporaries.
TEST_P(WebGL2GLSLValidationTest, LargeStructConstructorOnly)
{
    const int N1 = 1024;
    const int N2 = 1024;
    const int N3 = 64;

    std::ostringstream fs;
    fs << "#version 300 es\n";
    fs << "precision highp float;\n";
    fs << "struct S1 { mat4 m[" << N1 << "]; };\n";
    fs << "struct S2 {\n";
    for (int i = 0; i < N2; i++)
    {
        fs << "    S1 m" << i << ";\n";
    }
    fs << "};\n";
    fs << "struct S3 {\n";
    for (int i = 0; i < N3; i++)
    {
        fs << "    S2 m" << i << ";\n";
    }
    fs << "};\n";
    fs << "out vec4 color;\n";
    fs << "void main() {\n";
    fs << "    S1 s1;\n";
    fs << "    color = S3(";
    for (int i = 0; i < N3; i++)
    {
        fs << "S2(";
        for (int j = 0; j < N2; j++)
        {
            fs << "s1";
            if (j != N2 - 1)
            {
                fs << ",";
            }
        }
        fs << ")";
        if (i != N3 - 1)
        {
            fs << ",";
        }
    }
    fs << ").m0.m0.m[0][0];\n";
    fs << "}\n";

    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(),
                  "'' : Size of declared private variable exceeds implementation-defined limit");
}

// Validate that too-large structures cannot be used as function return types
TEST_P(WebGL2GLSLValidationTest, LargeVariableFunctionReturnType)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

struct largestruct {
    mat4 m[1000000000u];
};

largestruct func() {
    largestruct s;
    return s;
}

void main() {
    func();
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'s' : Size of declared variable exceeds implementation-defined limit");
}

// Test that too large color outputs are rejected
TEST_P(WebGL2GLSLValidationTest, LargeColorOutput)
{
    GLint maxDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ANGLE_SKIP_TEST_IF(maxDrawBuffers >= 32);

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 colorOut[32];

void main()
{
    colorOut[0] = vec4(0, 0, 0, 1);
    colorOut[31] = vec4(0, 0, 0, 1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'colorOut' : output array locations would exceed MAX_DRAW_BUFFERS");
}

// Test that too large color outputs are rejected
TEST_P(WebGL2GLSLValidationTest, LargeColorOutputWithLocation)
{
    GLint maxDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ANGLE_SKIP_TEST_IF(maxDrawBuffers >= 10);

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
layout(location = 0) out vec4 colorOut[4];
layout(location = 4) out vec4 colorOut2[6];

void main()
{
    colorOut[0] = vec4(0, 0, 0, 1);
    colorOut[3] = vec4(0, 0, 0, 1);
    colorOut2[0] = vec4(0, 0, 0, 1);
    colorOut2[5] = vec4(0, 0, 0, 1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'colorOut2' : output array locations would exceed MAX_DRAW_BUFFERS");
}

// Test that marking a built-in as invariant and then redeclaring it is an error.
TEST_P(GLSLValidationTest_ES3, FragDepthInvariantThenRedeclare)
{
    constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_conservative_depth:enable
precision mediump float;
invariant gl_FragDepth;
out float gl_FragDepth;
void main() {
    gl_FragDepth = 0.5;
}
)";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_FragDepth' : built-ins cannot be redeclared after being qualified as "
                  "invariant or precise");
}

// Make sure gl_PerVertex is not accepted other than as `out` and with no name in vertex shader
TEST_P(GLSLValidationTest_ES31, ValidatePerVertexVertexShader)
{
    {
        // Cannot use gl_PerVertex with attribute
        constexpr char kVS[] = R"(attribute gl_PerVertex{vec4 gl_Position;};
void main() {})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'gl_PerVertex' : interface blocks supported in GLSL ES 3.00 and above only");
    }

    {
        // Cannot use gl_PerVertex with a name (without EXT_shader_io_blocks)
        constexpr char kVS[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'out' : invalid qualifier: interface blocks must be uniform in version "
                      "lower than GLSL ES 3.10");
    }

    {
        // Cannot use gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kVS[] = R"(#version 310 es
out gl_PerVertex{vec4 gl_Position;};
void main() {})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'out' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    {
        // Cannot use gl_PerVertex with a name
        constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'name' : out gl_PerVertex instance name must be empty in this shader");
    }

    {
        // out gl_PerVertex without a name is ok.
        constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require
out gl_PerVertex{vec4 gl_Position;};
void main() {})";
        validateSuccess(GL_VERTEX_SHADER, kVS);
    }
}

// Make sure gl_PerVertex is not accepted other than as `out .. gl_out[]`, or `in .. gl_in[]` in
// tessellation control shader.
TEST_P(GLSLValidationTest_ES31, ValidatePerVertexTessellationControlShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    {
        // Cannot use tessellation control shaders in #version 300 shaders
        constexpr char kTCS[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS,
                      "Tessellation shaders are not supported in this shader version.");
    }

    {
        // Cannot use tessellation control shaders in #version 300 shaders
        constexpr char kTCS[] = R"(#version 300 es
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS,
                      "Tessellation shaders are not supported in this shader version.");
    }

    {
        // Cannot use out gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kTCS[] = R"(#version 310 es
out gl_PerVertex{vec4 gl_Position;} gl_out[];
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS,
                      "'out' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    {
        // Cannot use in gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kTCS[] = R"(#version 310 es
in gl_PerVertex{vec4 gl_Position;} gl_in[];
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS,
                      "'in' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    {
        // Cannot use out gl_PerVertex with a name
        constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (vertices=4) out;
out gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS,
                      "'name' : out gl_PerVertex instance name must be gl_out in this shader");
    }

    {
        // Cannot use in gl_PerVertex with a name
        constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (vertices=4) out;
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS,
                      "'name' : in gl_PerVertex instance name must be gl_in");
    }

    {
        // Cannot use out gl_PerVertex if not arrayed
        constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (vertices=4) out;
out gl_PerVertex{vec4 gl_Position;} gl_out;
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS, "'gl_PerVertex' : type must be an array");
    }

    {
        // Cannot use in gl_PerVertex if not arrayed
        constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (vertices=4) out;
in gl_PerVertex{vec4 gl_Position;} gl_in;
void main() {})";
        validateError(GL_TESS_CONTROL_SHADER, kTCS, "'gl_PerVertex' : type must be an array");
    }

    {
        // out gl_PerVertex with gl_out, and in gl_PerVertex with gl_in are ok.
        constexpr char kTCS[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (vertices=4) out;
out gl_PerVertex{vec4 gl_Position;} gl_out[];
in gl_PerVertex{vec4 gl_Position;} gl_in[];
void main() {})";
        validateSuccess(GL_TESS_CONTROL_SHADER, kTCS);
    }
}

// Make sure gl_PerVertex is not accepted other than as `out .. gl_out`, or `in .. gl_in[]` in
// tessellation evaluation shader.
TEST_P(GLSLValidationTest_ES31, ValidatePerVertexTessellationEvaluationShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));

    {
        // Cannot use tessellation evaluation shaders in #version 300 shaders
        constexpr char kTES[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "Tessellation shaders are not supported in this shader version.");
    }

    {
        // Cannot use tessellation evaluation shaders in #version 300 shaders
        constexpr char kTES[] = R"(#version 300 es
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "Tessellation shaders are not supported in this shader version.");
    }

    {
        // Cannot use out gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kTES[] = R"(#version 310 es
out gl_PerVertex{vec4 gl_Position;};
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "'out' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    {
        // Cannot use in gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kTES[] = R"(#version 310 es
in gl_PerVertex{vec4 gl_Position;} gl_in[];
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "'in' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    {
        // Cannot use out gl_PerVertex with a name
        constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (isolines, point_mode) in;
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "'name' : out gl_PerVertex instance name must be empty in this shader");
    }

    {
        // Cannot use in gl_PerVertex with a name
        constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (isolines, point_mode) in;
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "'name' : in gl_PerVertex instance name must be gl_in");
    }

    {
        // Cannot use out gl_PerVertex if arrayed
        constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (isolines, point_mode) in;
out gl_PerVertex{vec4 gl_Position;} gl_out[];
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES,
                      "'gl_out' : out gl_PerVertex instance name must be empty in this shader");
    }

    {
        // Cannot use in gl_PerVertex if not arrayed
        constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (isolines, point_mode) in;
in gl_PerVertex{vec4 gl_Position;} gl_in;
void main() {})";
        validateError(GL_TESS_EVALUATION_SHADER, kTES, "'gl_PerVertex' : type must be an array");
    }

    {
        // out gl_PerVertex without a name, and in gl_PerVertex with gl_in are ok.
        constexpr char kTES[] = R"(#version 310 es
#extension GL_EXT_tessellation_shader : require
layout (isolines, point_mode) in;
out gl_PerVertex{vec4 gl_Position;};
in gl_PerVertex{vec4 gl_Position;} gl_in[];
void main() {})";
        validateSuccess(GL_TESS_EVALUATION_SHADER, kTES);
    }
}

// Make sure gl_PerVertex is not accepted other than as `out .. gl_out`, or `in .. gl_in[]` in
// geometry shader.
TEST_P(GLSLValidationTest_ES31, ValidatePerVertexGeometryShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));

    {
        // Cannot use geometry shaders in #version 300 shaders
        constexpr char kGS[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "Geometry shader is not supported in this shader version.");
    }

    {
        // Cannot use geometry shaders in #version 300 shaders
        constexpr char kGS[] = R"(#version 300 es
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "Geometry shader is not supported in this shader version.");
    }

    {
        // Cannot use out gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kGS[] = R"(#version 310 es
out gl_PerVertex{vec4 gl_Position;};
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "'out' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    {
        // Cannot use in gl_PerVertex (without EXT_shader_io_blocks)
        constexpr char kGS[] = R"(#version 310 es
in gl_PerVertex{vec4 gl_Position;} gl_in[];
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "'in' : invalid qualifier: shader IO blocks need shader io block extension");
    }

    {
        // Cannot use out gl_PerVertex with a name
        constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (points, max_vertices = 1) out;
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "'name' : out gl_PerVertex instance name must be empty in this shader");
    }

    {
        // Cannot use in gl_PerVertex with a name
        constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (points, max_vertices = 1) out;
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "'name' : in gl_PerVertex instance name must be gl_in");
    }

    {
        // Cannot use out gl_PerVertex if arrayed
        constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (points, max_vertices = 1) out;
out gl_PerVertex{vec4 gl_Position;} gl_out[];
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS,
                      "'gl_out' : out gl_PerVertex instance name must be empty in this shader");
    }

    {
        // Cannot use in gl_PerVertex if not arrayed
        constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (points, max_vertices = 1) out;
in gl_PerVertex{vec4 gl_Position;} gl_in;
void main() {})";
        validateError(GL_GEOMETRY_SHADER, kGS, "'gl_PerVertex' : type must be an array");
    }

    {
        // out gl_PerVertex without a name, and in gl_PerVertex with gl_in are ok.
        constexpr char kGS[] = R"(#version 310 es
#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (points, max_vertices = 1) out;
out gl_PerVertex{vec4 gl_Position;};
in gl_PerVertex{vec4 gl_Position;} gl_in[];
void main() {})";
        validateSuccess(GL_GEOMETRY_SHADER, kGS);
    }
}

// Negative test using builtins that can only be used when redefining gl_PerVertex
TEST_P(GLSLValidationTest_ES31, RedefinePerVertexMembersSeparately)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));

    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;

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();
})";

    validateError(GL_GEOMETRY_SHADER, kGS,
                  "'gl_Position' : redeclaration of built-in is not allowed");
}

// Negative test using builtins that can only be used when redefining gl_PerVertex but have the
// builtins in a differently named struct
TEST_P(GLSLValidationTest_ES31, RedefinePerVertexMembersInOtherBlock)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));

    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 Block {
    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();
})";

    validateError(GL_GEOMETRY_SHADER, kGS,
                  "'gl_Position' : redefinition in an invalid interface block");
}

// Regression test case of unary + constant folding of a void struct member.
TEST_P(GLSLValidationTest, UnaryPlusOnVoidStructMemory)
{
    constexpr char kFS[] = R"(uniform mediump vec4 u;
struct U
{
    void t;
};
void main() {
  +U().t;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'t' : illegal use of type 'void'");
}

// Test compiling shaders using the GL_EXT_shader_texture_lod extension
TEST_P(GLSLValidationTest, TextureLOD)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_texture_lod"));

    constexpr char kFS[] = R"(#extension GL_EXT_shader_texture_lod : require
uniform sampler2D u_texture;
void main() {
    gl_FragColor = texture2DGradEXT(u_texture, vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0));
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Verify that using a struct as both invariant and non-invariant output works.
TEST_P(GLSLValidationTest_ES31, StructBothInvariantAndNot)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

struct S
{
    vec4 s;
};

out Output
{
    vec4 x;
    invariant S s;
};

out S s2;

void main(){
    x = vec4(0);
    s.s = vec4(1);
    s2.s = vec4(2);
    S s3 = s;
    s.s = s3.s;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that using a struct as both invariant and non-invariant output works.
// The shader interface block has a variable name in this variant.
TEST_P(GLSLValidationTest_ES31, StructBothInvariantAndNot2)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_io_blocks"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

struct S
{
    vec4 s;
};

out Output
{
    vec4 x;
    invariant S s;
} o;

out S s2;

void main(){
    o.x = vec4(0);
    o.s.s = vec4(1);
    s2.s = vec4(2);
    S s3 = o.s;
    o.s.s = s3.s;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnFloat)
{
    constexpr char kVS[] = R"(varying float v_varying;
float f();
void main() { gl_Position = vec4(f(), 0, 0, 1); }
float f() { if (v_varying > 0.0) return 1.0; })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnVec2)
{
    constexpr char kVS[] = R"(varying float v_varying;
vec2 f() { if (v_varying > 0.0) return vec2(1.0, 1.0); }
void main() { gl_Position = vec4(f().x, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnVec3)
{
    constexpr char kVS[] = R"(varying float v_varying;
vec3 f() { if (v_varying > 0.0) return vec3(1.0, 1.0, 1.0); }
void main() { gl_Position = vec4(f().x, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnVec4)
{
    constexpr char kVS[] = R"(varying float v_varying;
vec4 f() { if (v_varying > 0.0) return vec4(1.0, 1.0, 1.0, 1.0); }
void main() { gl_Position = vec4(f().x, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnIVec4)
{
    constexpr char kVS[] = R"(varying float v_varying;
ivec4 f() { if (v_varying > 0.0) return ivec4(1, 1, 1, 1); }
void main() { gl_Position = vec4(f().x, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnMat4)
{
    constexpr char kVS[] = R"(varying float v_varying;
mat4 f() { if (v_varying > 0.0) return mat4(1.0); }
void main() { gl_Position = vec4(f()[0][0], 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest, MissingReturnStruct)
{
    constexpr char kVS[] = R"(varying float v_varying;
struct s { float a; int b; vec2 c; };
s f() { if (v_varying > 0.0) return s(1.0, 1, vec2(1.0, 1.0)); }
void main() { gl_Position = vec4(f().a, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest_ES3, MissingReturnArray)
{
    constexpr char kVS[] = R"(#version 300 es
in float v_varying;
vec2[2] f() { if (v_varying > 0.0) { return vec2[2](vec2(1.0, 1.0), vec2(1.0, 1.0)); } }
void main() { gl_Position = vec4(f()[0].x, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest_ES3, MissingReturnArrayOfStructs)
{
    constexpr char kVS[] = R"(#version 300 es
in float v_varying;
struct s { float a; int b; vec2 c; };
s[2] f() { if (v_varying > 0.0) { return s[2](s(1.0, 1, vec2(1.0, 1.0)), s(1.0, 1, vec2(1.0, 1.0))); } }
void main() { gl_Position = vec4(f()[0].a, 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that functions without return statements still compile
TEST_P(GLSLValidationTest_ES3, MissingReturnStructOfArrays)
{
    // TODO(crbug.com/998505): Test failing on Android FYI Release (NVIDIA Shield TV)
    ANGLE_SKIP_TEST_IF(IsNVIDIAShield());

    constexpr char kVS[] = R"(#version 300 es
in float v_varying;
struct s { float a[2]; int b[2]; vec2 c[2]; };
s f() { if (v_varying > 0.0) { return s(float[2](1.0, 1.0), int[2](1, 1), vec2[2](vec2(1.0, 1.0), vec2(1.0, 1.0))); } }
void main() { gl_Position = vec4(f().a[0], 0, 0, 1); })";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify that non-const index used on an array returned by a function compiles
TEST_P(GLSLValidationTest_ES3, ReturnArrayOfStructsThenNonConstIndex)
{
    constexpr char kVS[] = R"(#version 300 es
in float v_varying;
struct s { float a; int b; vec2 c; };
s[2] f()
{
    return s[2](s(v_varying, 1, vec2(1.0, 1.0)), s(v_varying / 2.0, 1, vec2(1.0, 1.0)));
}
void main()
{
    gl_Position = vec4(f()[uint(v_varying)].a, 0, 0, 1);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Verify shader source with a fixed length that is less than the null-terminated length will
// compile.
TEST_P(GLSLValidationTest, FixedShaderLength)
{
    GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);

    const std::string appendGarbage = "abcdefghijklmnopqrstuvwxyz";
    const std::string source   = "void main() { gl_FragColor = vec4(0, 0, 0, 0); }" + appendGarbage;
    const char *sourceArray[1] = {source.c_str()};
    GLint lengths[1]           = {static_cast<GLint>(source.length() - appendGarbage.length())};
    glShaderSource(shader, static_cast<GLsizei>(ArraySize(sourceArray)), sourceArray, lengths);
    glCompileShader(shader);

    GLint compileResult;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
    EXPECT_NE(compileResult, 0);
}

// Verify that a negative shader source length is treated as a null-terminated length.
TEST_P(GLSLValidationTest, NegativeShaderLength)
{
    GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);

    const char *sourceArray[1] = {essl1_shaders::fs::Red()};
    GLint lengths[1]           = {-10};
    glShaderSource(shader, static_cast<GLsizei>(ArraySize(sourceArray)), sourceArray, lengths);
    glCompileShader(shader);

    GLint compileResult;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
    EXPECT_NE(compileResult, 0);
}

// Verify that a length array with mixed positive and negative values compiles.
TEST_P(GLSLValidationTest, MixedShaderLengths)
{
    GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);

    const char *sourceArray[] = {
        "void main()",
        "{",
        "    gl_FragColor = vec4(0, 0, 0, 0);",
        "}",
    };
    GLint lengths[] = {
        -10,
        1,
        static_cast<GLint>(strlen(sourceArray[2])),
        -1,
    };
    ASSERT_EQ(ArraySize(sourceArray), ArraySize(lengths));

    glShaderSource(shader, static_cast<GLsizei>(ArraySize(sourceArray)), sourceArray, lengths);
    glCompileShader(shader);

    GLint compileResult;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
    EXPECT_NE(compileResult, 0);
}

// Verify that zero-length shader source does not affect shader compilation.
TEST_P(GLSLValidationTest, ZeroShaderLength)
{
    GLuint shader = glCreateShader(GL_FRAGMENT_SHADER);

    const char *sourceArray[] = {
        "abcdefg", "34534", "void main() { gl_FragColor = vec4(0, 0, 0, 0); }", "", "abcdefghijklm",
    };
    GLint lengths[] = {
        0, 0, -1, 0, 0,
    };
    ASSERT_EQ(ArraySize(sourceArray), ArraySize(lengths));

    glShaderSource(shader, static_cast<GLsizei>(ArraySize(sourceArray)), sourceArray, lengths);
    glCompileShader(shader);

    GLint compileResult;
    glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
    EXPECT_NE(compileResult, 0);
}

// Test that structs defined in uniforms are translated correctly.
TEST_P(GLSLValidationTest, StructSpecifiersUniforms)
{
    constexpr char kFS[] = R"(precision mediump float;

uniform struct S { float field; } s;

void main()
{
    gl_FragColor = vec4(1, 0, 0, 1);
    gl_FragColor.a += s.field;
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that if a non-preprocessor token is seen in a disabled if-block then it does not disallow
// extension pragmas later
TEST_P(GLSLValidationTest, NonPreprocessorTokensInIfBlocks)
{
    constexpr const char *kFS = R"(
#if __VERSION__ >= 300
    inout mediump vec4 fragData;
#else
    #extension GL_EXT_shader_texture_lod :enable
#endif

void main()
{
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that two constructors which have vec4 and mat2 parameters get disambiguated (issue in
// HLSL).
TEST_P(GLSLValidationTest_ES3, AmbiguousConstructorCall2x2)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
in vec4 a_vec;
in mat2 a_mat;
void main()
{
    gl_Position = vec4(a_vec) + vec4(a_mat);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that two constructors which have mat2x3 and mat3x2 parameters get disambiguated.
// This was suspected to be an issue in HLSL, but HLSL seems to be able to natively choose between
// the function signatures in this case.
TEST_P(GLSLValidationTest_ES3, AmbiguousConstructorCall2x3)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
in mat3x2 a_matA;
in mat2x3 a_matB;
void main()
{
    gl_Position = vec4(a_matA) + vec4(a_matB);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that two functions which have vec4 and mat2 parameters get disambiguated (issue in HLSL).
TEST_P(GLSLValidationTest_ES3, AmbiguousFunctionCall2x2)
{
    constexpr char kVS[] = R"(#version 300 es
precision highp float;
in vec4 a_vec;
in mat2 a_mat;
vec4 foo(vec4 a)
{
    return a;
}
vec4 foo(mat2 a)
{
    return vec4(a[0][0]);
}
void main()
{
    gl_Position = foo(a_vec) + foo(a_mat);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that an user-defined function with a large number of float4 parameters doesn't fail due to
// the function name being too long.
TEST_P(GLSLValidationTest_ES3, LargeNumberOfFloat4Parameters)
{
    std::stringstream vs;

    vs << R"(#version 300 es
precision highp float;
in vec4 a_vec;
vec4 lotsOfVec4Parameters()";
    for (unsigned int i = 0; i < kMaxFunctionParameters - 1; ++i)
    {
        vs << "vec4 a" << i << ", ";
    }
    vs << R"(vec4 aLast)
{
    vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);)";
    for (unsigned int i = 0; i < kMaxFunctionParameters - 1; ++i)
    {
        vs << "    sum += a" << i << ";\n";
    }
    vs << R"(    sum += aLast;
    return sum;
}
void main()
{
    gl_Position = lotsOfVec4Parameters()";
    for (unsigned int i = 0; i < kMaxFunctionParameters - 1; ++i)
    {
        vs << "a_vec, ";
    }
    vs << R"(a_vec);
})";
    validateSuccess(GL_VERTEX_SHADER, vs.str().c_str());
}

// This test was written specifically to stress DeferGlobalInitializers AST transformation.
// Test a shader where a global constant array is initialized with an expression containing array
// indexing. This initializer is tricky to constant fold, so if it's not constant folded it needs to
// be handled in a way that doesn't generate statements in the global scope in HLSL output.
// Also includes multiple array initializers in one declaration, where only the second one has
// array indexing. This makes sure that the qualifier for the declaration is set correctly if
// transformations are applied to the declaration also in the case of ESSL output.
TEST_P(GLSLValidationTest_ES3, InitGlobalArrayWithArrayIndexing)
{
    // TODO(ynovikov): re-enable once root cause of http://anglebug.com/42260423 is fixed
    ANGLE_SKIP_TEST_IF(IsAndroid() && IsAdreno() && IsOpenGLES());

    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 my_FragColor;
const highp float f[2] = float[2](0.1, 0.2);
const highp float[2] g = float[2](0.3, 0.4), h = float[2](0.5, f[1]);
void main()
{
    my_FragColor = vec4(h[1]);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that index-constant sampler array indexing is supported.
TEST_P(GLSLValidationTest, IndexConstantSamplerArrayIndexing)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform sampler2D uni[2];

float zero(int x)
{
    return float(x) - float(x);
}

void main()
{
    vec4 c = vec4(0,0,0,0);
    for (int ii = 1; ii < 3; ++ii) {
        if (c.x > 255.0) {
            c.x = 255.0 + zero(ii);
            break;
        }
        // Index the sampler array with a predictable loop index (index-constant) as opposed to
        // a true constant. This is valid in OpenGL ES but isn't in many Desktop OpenGL versions,
        // without an extension.
        c += texture2D(uni[ii - 1], vec2(0.5, 0.5));
    }
    gl_FragColor = c;
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that the #pragma directive is supported and doesn't trigger a compilation failure on the
// native driver. The only pragma that gets passed to the OpenGL driver is "invariant" but we don't
// want to test its behavior, so don't use any varyings.
TEST_P(GLSLValidationTest, PragmaDirective)
{
    constexpr char kVS[] = R"(#pragma STDGL invariant(all)
void main()
{
    gl_Position = vec4(1.0, 0.0, 0.0, 1.0);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Tests that using a constant declaration as the only statement in a for loop without curly braces
// doesn't crash.
TEST_P(GLSLValidationTest, ConstantStatementInForLoop)
{
    constexpr char kVS[] = R"(void main()
{
    for (int i = 0; i < 10; ++i)
        const int b = 0;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Tests that using a constant declaration as a loop init expression doesn't crash.
TEST_P(GLSLValidationTest_ES3, ConstantStatementAsLoopInit)
{
    constexpr char kVS[] = R"(void main()
{
    for (const int i = 0; i < 0;) {}
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Tests that using a constant condition guarding a discard works
// Covers a failing case in the Vulkan backend: http://anglebug.com/42265506
TEST_P(GLSLValidationTest_ES3, ConstantConditionGuardingDiscard)
{
    constexpr char kFS[] = R"(#version 300 es
void main()
{
    if (true)
    {
        discard;
    }
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Tests that nesting a discard in unconditional blocks works
// Covers a failing case in the Vulkan backend: http://anglebug.com/42265506
TEST_P(GLSLValidationTest_ES3, NestedUnconditionalDiscards)
{
    constexpr char kFS[] = R"(#version 300 es
out mediump vec4 c;
void main()
{
    {
        c = vec4(0);
        {
            discard;
        }
    }
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Tests that rewriting samplers in structs works when passed as function argument.  In this test,
// the function references another struct, which is not being modified.  Regression test for AST
// validation applied to a multipass transformation, where references to declarations were attempted
// to be validated without having the entire shader.  In this case, the reference to S2 was flagged
// as invalid because S2's declaration was not visible.
TEST_P(GLSLValidationTest, SamplerInStructAsFunctionArg)
{
    constexpr char kFS[] = R"(precision mediump float;
struct S { sampler2D samp; bool b; };
struct S2 { float f; };

uniform S us;

float f(S s)
{
    S2 s2;
    s2.f = float(s.b);
    return s2.f;
}

void main()
{
    gl_FragColor = vec4(f(us), 0, 0, 1);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that (a, struct_with_sampler).field fails to compile without IR.
TEST_P(GLSLValidationTest_ES3, SamplerInStructRHSOfCommaWithSideEffectWithSelectField)
{
    // The GLSLTest_ES3.SamplerInStructRHSOfCommaWithSideEffectWithSelectField test functionally
    // tests this same shader and ensures it translates correctly with the IR.
    // The AST path cannot handle this, and so fails compilation.
    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;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'Internal Error' : accessing fields of the result of a comma expression that is "
                  "a structure with samplers is not currently supported");
}

// Test that struct with samplers can be passed to functions where the function modifies a
// non-sampler field.
TEST_P(GLSLValidationTest_ES3, StructWithSamplersNonSamplerFieldModifiedInFunction)
{
    const char kFS[] = R"(#version 300 es
precision mediump float;

struct OnlySampler
{
    sampler2D s;
};

struct Inner
{
    sampler2D s;
    float a;
    OnlySampler o;
};

uniform struct Outer
{
    Inner i;
    float b[3];
    OnlySampler o;
} u[2];

out vec4 color;

vec4 sampleFromOnlySampler(OnlySampler o)
{
    return texture(o.s, vec2(0));
}

float getAndModifyInner(Inner i)
{
    i.a += 0.2;
    i.a += sampleFromOnlySampler(i.o).x;
    return i.a;
}

vec3 getAndModifyOuter(Outer o)
{
    o.b[0] += 0.3;
    o.b[0] += getAndModifyInner(o.i);
    o.b[0] += sampleFromOnlySampler(o.o).y;
    o.b[1] += 0.4;
    o.b[2] += 0.5;
    return vec3(o.b[0], o.b[1], o.b[2]);
}

void main()
{
    int a = 0;
    color = vec4(getAndModifyInner(u[a++].i), getAndModifyOuter(u[1]));
    if (a != 1)
        color = vec4(1, 0, 0, 1);
})";

    // Note: The above is actually valid GLSL, but is unsupported.  Once implemented correctly, the
    // test should move to GLSLTest.cpp with the following verification:
    //
    //     ANGLE_GL_PROGRAM(program, essl3_shaders::vs::Simple(), kFS);
    //     glUseProgram(program);
    //
    //     glUniform1f(glGetUniformLocation(program, "u[0].i.a"), 0.05f);
    //     glUniform1f(glGetUniformLocation(program, "u[1].i.a"), 0.15f);
    //     glUniform1f(glGetUniformLocation(program, "u[1].b[0]"), 0.05f);
    //     glUniform1f(glGetUniformLocation(program, "u[1].b[1]"), 0.25f);
    //     glUniform1f(glGetUniformLocation(program, "u[1].b[2]"), 0.35f);
    //
    //     drawQuad(program, essl3_shaders::PositionAttrib(), 0.0f);
    //     EXPECT_PIXEL_COLOR_NEAR(0, 0, GLColor(64, 128, 166, 217), 1);
    //     ASSERT_GL_NO_ERROR();
    //
    validateError(
        GL_FRAGMENT_SHADER, kFS,
        "l-value required (modifying structures containing samplers is not currently supported");
}

// Test a fuzzer-discovered bug with the VectorizeVectorScalarArithmetic transformation.
TEST_P(GLSLValidationTest, VectorScalarArithmeticWithSideEffectInLoop)
{
    // The VectorizeVectorScalarArithmetic transformation was generating invalid code in the past
    // (notice how sbcd references i outside the for loop.  The loop condition doesn't look right
    // either):
    //
    //     #version 450
    //     void main(){
    //     (gl_Position = vec4(0.0, 0.0, 0.0, 0.0));
    //     mat3 _utmp = mat3(0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0);
    //     vec3 _ures = vec3(0.0, 0.0, 0.0);
    //     vec3 sbcd = vec3(_ures[_ui]);
    //     for (int _ui = 0; (_ures[((_utmp[_ui] += (((sbcd *= _ures[_ui]), (_ures[_ui] = sbcd.x)),
    //     sbcd)), _ui)], (_ui < 7)); )
    //     {
    //     }
    //     }

    constexpr char kVS[] = R"(
void main()
{
    mat3 tmp;
    vec3 res;
    for(int i; res[tmp[i]+=res[i]*=res[i],i],i<7;);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that inactive output variables compile ok in combination with initOutputVariables
// (which is enabled on WebGL).
TEST_P(WebGL2GLSLValidationTest, InactiveOutput)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
out vec4 _cassgl_2_;
void main()
{
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that output variables declared after main work in combination with initOutputVariables
// (which is enabled on WebGL).
TEST_P(WebGLGLSLValidationTest, OutputAfterMain)
{
    constexpr char kVS[] = R"(void main(){}
varying float r;)";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test angle can handle big initial stack size with dynamic stack allocation.
TEST_P(GLSLValidationTest, MemoryExhaustedTest)
{
    constexpr int kLength = 36;

    std::stringstream fs;
    fs << "void main() {\n";
    for (int i = 0; i < kLength; i++)
    {
        fs << "  if (true) {\n";
    }
    fs << "  int temp;\n";
    for (int i = 0; i <= kLength; i++)
    {
        fs << "}";
    }
    validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
}

// Test that separating declarators works with structs that have been separately defined.
TEST_P(GLSLValidationTest_ES31, SeparateDeclaratorsOfStructType)
{
    constexpr char kVS[] = R"(#version 310 es
precision highp float;

struct S
{
    mat4 a;
    mat4 b;
};

S s1 = S(mat4(1), mat4(2)), s2[2][3], s3[2] = S[2](S(mat4(0), mat4(3)), S(mat4(4), mat4(5)));

void main() {
    S s4[2][3] = s2, s5 = s3[0], s6[2] = S[2](s1, s5), s7 = s5;

    gl_Position = vec4(s3[1].a[0].x, s2[0][2].b[1].y, s4[1][0].a[2].z, s6[0].b[3].w);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that separating declarators works with structs that are simultaneously defined.
TEST_P(GLSLValidationTest_ES31, SeparateDeclaratorsOfStructTypeBeingSpecified)
{
    constexpr char kVS[] = R"(#version 310 es
precision highp float;

struct S
{
    mat4 a;
    mat4 b;
} s1 = S(mat4(1), mat4(2)), s2[2][3], s3[2] = S[2](S(mat4(0), mat4(3)), S(mat4(4), mat4(5)));

void main() {
    struct T
    {
        mat4 a;
        mat4 b;
    } s4[2][3], s5 = T(s3[0].a, s3[0].b), s6[2] = T[2](T(s1.a, s1.b), s5), s7 = s5;

    float f1 = s3[1].a[0].x, f2 = s2[0][2].b[1].y;

    gl_Position = vec4(f1, f2, s4[1][0].a[2].z, s6[0].b[3].w);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that separating declarators works with structs that are simultaneously defined and that are
// nameless.
TEST_P(GLSLValidationTest_ES31, SeparateDeclaratorsOfNamelessStructType)
{
    constexpr char kVS[] = R"(#version 310 es
precision highp float;

struct
{
    mat4 a;
    mat4 b;
} s1, s2[2][3], s3[2];

void main() {
    struct
    {
        mat4 a;
        mat4 b;
    } s4[2][3], s5, s6[2], s7 = s5;

    float f1 = s1.a[0].x + s3[1].a[0].x, f2 = s2[0][2].b[1].y + s7.b[1].z;

    gl_Position = vec4(f1, f2, s4[1][0].a[2].z, s6[0].b[3].w);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test for transformation bug which separates struct declarations from uniform
// declarations.  The bug was that the uniform variable usage in the initializer of a new
// declaration (y below) was not being processed.
TEST_P(GLSLValidationTest, UniformStructBug)
{
    constexpr char kVS[] = R"(precision highp float;

uniform struct Global
{
    float x;
} u_global;

void main() {
  float y = u_global.x;

  gl_Position = vec4(y);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test for transformation bug which separates struct declarations from uniform
// declarations.  The bug was that the arrayness of the declaration was not being applied to the
// replaced uniform variable.
TEST_P(GLSLValidationTest_ES31, UniformStructBug2)
{
    constexpr char kVS[] = R"(#version 310 es
precision highp float;

uniform struct Global
{
    float x;
} u_global[2][3];

void main() {
  float y = u_global[0][0].x;

  gl_Position = vec4(y);
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test based on fuzzer issue resulting in an AST validation failure.  Struct definition
// was not found in the tree.  Tests that struct declaration in function return value is visible to
// instantiations later on.
TEST_P(GLSLValidationTest, MissingStructDeclarationBug)
{
    constexpr char kVS[] = R"(
struct S
{
    vec4 i;
} p();
void main()
{
    S s;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test based on fuzzer issue resulting in an AST validation failure.  Struct definition
// was not found in the tree.  Tests that struct declaration in function return value is visible to
// other struct declarations.
TEST_P(GLSLValidationTest, MissingStructDeclarationBug2)
{
    constexpr char kVS[] = R"(
struct T
{
    vec4 I;
} p();
struct
{
    T c;
};
void main()
{
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test for bug in HLSL code generation where the for loop init expression was expected
// to always have an initializer.
TEST_P(GLSLValidationTest, HandleExcessiveLoopBug)
{
    constexpr char kVS[] = R"(void main(){for(int i;i>6;);})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that providing more components to a matrix constructor than necessary works.  Based on a
// clusterfuzz test that caught an OOB array write in glslang.
TEST_P(GLSLValidationTest, MatrixConstructor)
{
    constexpr char kVS[] = R"(attribute vec4 aPosition;
varying vec4 vColor;
void main()
{
    gl_Position = aPosition;
    vec4 color = vec4(aPosition.xy, 0, 1);
    mat4 m4 = mat4(color, color.yzwx, color.zwx, color.zwxy, color.wxyz);
    vColor = m4[0];
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test constructors without precision
TEST_P(GLSLValidationTest, ConstructFromBoolVector)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform float u;
void main()
{
    mat4 m = mat4(u);
    mat2(0, bvec3(m));
    gl_FragColor = vec4(m);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test constructing vector from matrix
TEST_P(GLSLValidationTest, VectorConstructorFromMatrix)
{
    constexpr char kFS[] = R"(precision mediump float;
uniform mat2 umat2;
void main()
{
    gl_FragColor = vec4(umat2);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that initializing global variables with non-constant values work
TEST_P(GLSLValidationTest_ES3, InitGlobalNonConstant)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_non_constant_global_initializers"));

    constexpr char kVS[] = R"(#version 300 es
#extension GL_EXT_shader_non_constant_global_initializers : require
uniform vec4 u;
out vec4 color;

vec4 global1 = u;
vec4 global2 = u + vec4(1);
vec4 global3 = global1 * global2;
void main()
{
    color = global3;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test for a crash in SPIR-V output when faced with an array of struct constant.
TEST_P(GLSLValidationTest_ES3, ArrayOfStructConstantBug)
{
    constexpr char kFS[] = R"(#version 300 es
struct S {
    int foo;
};
void main() {
    S a[3];
    a = S[3](S(0), S(1), S(2));
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Regression test for a bug in SPIR-V output where float+matrix was mishandled.
TEST_P(GLSLValidationTest_ES3, FloatPlusMatrix)
{
    constexpr char kFS[] = R"(#version 300 es

precision mediump float;

layout(location=0) out vec4 color;

uniform float f;

void main()
{
    mat3x2 m = f + mat3x2(0);
    color = vec4(m[0][0]);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Regression test for a bug in SPIR-V output where a transformation creates float(constant) without
// folding it into a TIntermConstantUnion.  This transformation is clamping non-constant indices in
// WebGL.  The |false ? i : 5| as index caused the transformation to consider this a non-constant
// index.
TEST_P(WebGL2GLSLValidationTest, IndexClampConstantIndexBug)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;

layout(location=0) out float f;

uniform int i;

void main()
{
    float data[10];
    f = data[false ? i : 5];
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that framebuffer fetch transforms gl_LastFragData in the presence of gl_FragCoord without
// failing validation (adapted from a Chromium test, see anglebug.com/42265427)
TEST_P(GLSLValidationTest, FramebufferFetchWithLastFragData)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch"));

    constexpr char kFS[] = R"(#version 100

#extension GL_EXT_shader_framebuffer_fetch : require
varying mediump vec4 color;
void main() {
    gl_FragColor = length(gl_FragCoord.xy) * gl_LastFragData[0];
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that loop body ending in a branch doesn't fail compilation
TEST_P(GLSLValidationTest, LoopBodyEndingInBranch1)
{
    constexpr char kFS[] = R"(void main(){for(int a,i;;gl_FragCoord)continue;})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that loop body ending in a branch doesn't fail compilation
TEST_P(GLSLValidationTest, LoopBodyEndingInBranch2)
{
    constexpr char kFS[] =
        R"(void main(){for(int a,i;bool(gl_FragCoord.x);gl_FragCoord){continue;}})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that loop body ending in a branch doesn't fail compilation
TEST_P(GLSLValidationTest, LoopBodyEndingInBranch3)
{
    constexpr char kFS[] = R"(void main(){for(int a,i;;gl_FragCoord){{continue;}}})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that loop body ending in a branch doesn't fail compilation
TEST_P(GLSLValidationTest, LoopBodyEndingInBranch4)
{
    constexpr char kFS[] = R"(void main(){for(int a,i;;gl_FragCoord){{continue;}{}{}{{}{}}}})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that loop body ending in a branch doesn't fail compilation
TEST_P(GLSLValidationTest, LoopBodyEndingInBranch5)
{
    constexpr char kFS[] = R"(void main(){while(bool(gl_FragCoord.x)){{continue;{}}{}}})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that loop body ending in a branch doesn't fail compilation
TEST_P(GLSLValidationTest, LoopBodyEndingInBranch6)
{
    constexpr char kFS[] = R"(void main(){do{{continue;{}}{}}while(bool(gl_FragCoord.x));})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that using only a type and no variable in the loop init expression compiles
TEST_P(GLSLValidationTest_ES3, LoopInitOnlyInt)
{
    constexpr char kFS[] = R"(#version 300 es
void main(){for(int;;){}})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that using only a struct type (i.e. a symbol, and not a keyword) and no variable in the loop
// init expression compiles
TEST_P(GLSLValidationTest_ES3, LoopInitOnlyStructName)
{
    constexpr char kFS[] = R"(#version 300 es
struct S { int a; }; void main(){for(S;;){}})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Fuzzer test involving struct samplers and comma operator
TEST_P(GLSLValidationTest, StructSamplerVsComma)
{
    constexpr char kVS[] = R"(uniform struct S1
{
    samplerCube ar;
    vec2 c;
} a;

struct S2
{
    vec3 c;
} b[2];

void main (void)
{
    ++b[0].c,a;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test for a bug where the sampler-in-struct rewrite transformation did not take a
// specific pattern of side_effect,index_the_struct_to_write into account.
TEST_P(GLSLValidationTest_ES3, StructWithSamplerRHSOfCommaWithSideEffect)
{
    constexpr char kVS[] = R"(uniform struct S {
    sampler2D s;
    mat2 m;
} u[2];
void main()
{
    ++gl_Position, u[0];
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Regression test for a bug where the sampler-in-struct rewrite transformation did not take a
// specific pattern of side_effect,struct_with_only_samplers into account.
TEST_P(GLSLValidationTest_ES3, StructWithOnlySamplersRHSOfCommaWithSideEffect)
{
    constexpr char kVS[] = R"(uniform struct S {
    sampler2D s;
} u;
void main()
{
    ++gl_Position, u;
})";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test that structs-with-samplers cannot be the result of the ternary operator.  The spec says for
// the ternary operator that:
//
// > The second and third expressions cannot be opaque types, or there will be an error.
//
// While it doesn't specifically rule out structs with samplers in them, it's reasonable to also
// disallow that.
TEST_P(GLSLValidationTest_ES31, StructWithSamplerAsTernaryResult)
{
    constexpr char kVS[] = R"(#version 310 es
struct A {
    vec4 v;
    sampler2D s;
};
struct B {
    A a[2];
};
uniform B b1[3];
uniform B b2[4];
uniform bool choose;

vec4 f(A a[2])
{
    return texture(a[0].s, vec2(0));
}

void main()
{
    gl_Position = f(choose ? b1[0].a : b2[0].a);
})";
    validateError(GL_VERTEX_SHADER, kVS, "'?:' : ternary operator is not allowed for opaque types");
}

// Test that gl_FragDepth can be marked invariant.
TEST_P(GLSLValidationTest_ES3, FragDepthInvariant)
{
    constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_conservative_depth: enable
precision mediump float;
invariant gl_FragDepth;
void main() {
    gl_FragDepth = 0.5;
}
)";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that gl_Position and gl_PointSize can be marked invariant and redeclared in separate
// statements. Note that EXT_seperate_shader_objects expects the redeclaration to come first.
TEST_P(GLSLValidationTest_ES31, PositionRedeclaredAndInvariant)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_separate_shader_objects"));

    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_separate_shader_objects: require
precision mediump float;
out vec4 gl_Position;
out float gl_PointSize;
invariant gl_Position;
invariant gl_PointSize;
void main() {
}
)";
    validateSuccess(GL_VERTEX_SHADER, kVS);
}

// Test an invalid shader where a for loop index is used as an out parameter.
// See limitations in ESSL 1.00 Appendix A.
TEST_P(WebGLGLSLValidationTest, IndexAsFunctionOutParameter)
{
    constexpr char kFS[] = R"(precision mediump float;
void fun(out int a)
{
   a = 2;
}
void main()
{
    for (int i = 0; i < 2; ++i)
    {
        fun(i);
    }
    gl_FragColor = vec4(0.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'i' : Loop index cannot be statically assigned to within the body of the loop");
}

// Test an invalid shader where a for loop index is used as an inout parameter.
// See limitations in ESSL 1.00 Appendix A.
TEST_P(WebGLGLSLValidationTest, IndexAsFunctionInOutParameter)
{
    constexpr char kFS[] = R"(precision mediump float;
void fun(int b, inout int a)
{
   a += b;
}
void main()
{
    for (int i = 0; i < 2; ++i)
    {
        fun(2, i);
    }
    gl_FragColor = vec4(0.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'i' : Loop index cannot be statically assigned to within the body of the loop");
}

// Test a valid shader where a for loop index is used as an in parameter in a function that also has
// an out parameter.
// See limitations in ESSL 1.00 Appendix A.
TEST_P(WebGLGLSLValidationTest, IndexAsFunctionInParameter)
{
    constexpr char kFS[] = R"(precision mediump float;
void fun(int b, inout int a)
{
   a += b;
}
void main()
{
    for (int i = 0; i < 2; ++i)
    {
        int a = 1;
        fun(i, a);
    }
    gl_FragColor = vec4(0.0);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test an invalid shader where a for loop index is used as a target of assignment.
// See limitations in ESSL 1.00 Appendix A.
TEST_P(WebGLGLSLValidationTest, IndexAsTargetOfAssignment)
{
    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    for (int i = 0; i < 2; ++i)
    {
        i = 2;
    }
    gl_FragColor = vec4(0.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'i' : Loop index cannot be statically assigned to within the body of the loop");
}

// Test an invalid shader where a for loop index is incremented inside the loop.
// See limitations in ESSL 1.00 Appendix A.
TEST_P(WebGLGLSLValidationTest, IndexIncrementedInLoopBody)
{
    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    for (int i = 0; i < 2; ++i)
    {
        ++i;
    }
    gl_FragColor = vec4(0.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'i' : Loop index cannot be statically assigned to within the body of the loop");
}

// Shader that writes to FragData at index >= gl_MaxDrawBuffers.
TEST_P(GLSLValidationTest, FragDataIndexTooLarge)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
precision mediump float;
void main() {
    gl_FragData[gl_MaxDrawBuffers] = vec4(0.1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "array index out of range");
}

// Shader that writes to SecondaryFragColor and SecondaryFragData does not compile.
TEST_P(GLSLValidationTest, BlendFuncExtendedSecondaryColorAndData)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_SecondaryFragColorEXT = vec4(1.0);
    gl_SecondaryFragDataEXT[gl_MaxDualSourceDrawBuffersEXT - 1] = vec4(0.1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT) and "
                  "(gl_FragColor, gl_SecondaryFragColorEXT)");
}

// Shader that writes to FragColor and SecondaryFragData does not compile.
TEST_P(GLSLValidationTest, BlendFuncExtendedColorAndSecondaryData)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_FragColor = vec4(1.0);
    gl_SecondaryFragDataEXT[gl_MaxDualSourceDrawBuffersEXT - 1] = vec4(0.1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT) and "
                  "(gl_FragColor, gl_SecondaryFragColorEXT)");
}

// Shader that writes to FragData and SecondaryFragColor.
TEST_P(GLSLValidationTest, BlendFuncExtendedDataAndSecondaryColor)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_SecondaryFragColorEXT = vec4(1.0);
    gl_FragData[gl_MaxDualSourceDrawBuffersEXT - 1] = vec4(0.1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT) and "
                  "(gl_FragColor, gl_SecondaryFragColorEXT)");
}

// Shader that writes to SecondaryFragData and FragData at an index >= than
// gl_MaxDualSourceDrawBuffersEXT.
TEST_P(GLSLValidationTest, BlendFuncExtendedDataArrayAndSecondaryData)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_SecondaryFragDataEXT[0] = vec4(1.0);
    gl_FragData[gl_MaxDualSourceDrawBuffersEXT] = vec4(0.1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "array index for gl_FragData must be less than "
                  "GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT when gl_SecondaryFragDataEXT is used");
}

// Shader that writes to SecondaryFragData and passes FragData to a function.
TEST_P(GLSLValidationTest, BlendFuncExtendedPassFragDataToFunction)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    GLint maxDrawBuffers = 0, maxDualSourceDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ANGLE_SKIP_TEST_IF(maxDualSourceDrawBuffers == maxDrawBuffers);

    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(0.1);
}
void main() {
    f(gl_FragData);
    gl_SecondaryFragDataEXT[0] = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "array index for gl_FragData must be less than "
                  "GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT when gl_SecondaryFragDataEXT is used");
}

// Shader that writes to SecondaryFragData and passes FragData to a function.
TEST_P(GLSLValidationTest, BlendFuncExtendedPassFragDataToFunctionInCommaExpr)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    GLint maxDrawBuffers = 0, maxDualSourceDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
    glGetIntegerv(GL_MAX_DRAW_BUFFERS, &maxDrawBuffers);
    ANGLE_SKIP_TEST_IF(maxDualSourceDrawBuffers == maxDrawBuffers);

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void f(vec4 fragData[gl_MaxDrawBuffers])
{
    fragData[0] = vec4(0.1);
}
void main() {
    vec4 data[gl_MaxDrawBuffers];
    f((data, gl_FragData));
    gl_SecondaryFragDataEXT[0] = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "array index for gl_FragData must be less than "
                  "GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT when gl_SecondaryFragDataEXT is used");
}

// Shader that writes to FragData at an index >= than gl_MaxDualSourceDrawBuffersEXT is fine if
// SecondaryFragData is not used.  Note that gl_MaxDualSourceDrawBuffersEXT is typically 1, while
// the size of gl_FragData (gl_MaxDrawBuffers) is larger.
TEST_P(GLSLValidationTest, BlendFuncExtendedDataArrayOnly)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_draw_buffers"));

    constexpr char kFS[] = R"(#extension GL_EXT_draw_buffers : require
#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    gl_FragData[gl_MaxDrawBuffers - 1] = vec4(0.1);
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Dynamic indexing of SecondaryFragData is not allowed in WebGL 2.0.
TEST_P(WebGL2GLSLValidationTest, BlendFuncExtendedSecondaryDataIndexing)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] = R"(#extension GL_EXT_blend_func_extended : require
precision mediump float;
void main() {
    for (int i = 0; i < 2; ++i) {
        gl_SecondaryFragDataEXT[true ? 0 : i] = vec4(0.0);
    }
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "array index for gl_SecondaryFragDataEXT must be constant zero");
}

// Shader that specifies index layout qualifier but not location fails to compile.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedNoLocationQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(index = 0) out vec4 fragColor;
void main() {
    fragColor = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'index' : If index layout qualifier is specified for a fragment output, "
                  "location must also be specified");
}

// Shader that specifies index layout qualifier multiple times fails to compile.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedMultipleIndexQualifiers)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(index = 0, location = 0, index = 1) out vec4 fragColor;
void main() {
    fragColor = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'index' : Cannot have multiple index specifiers");
}

// Shader that specifies an output with out-of-bounds location
// for index 0 when another output uses index 1 is invalid.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedOutOfBoundsLocationQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    GLint maxDualSourceDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
    ANGLE_SKIP_TEST_IF(maxDualSourceDrawBuffers > 1);

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(location = 1, index = 0) out mediump vec4 fragColor;
layout(location = 0, index = 1) out mediump vec4 secondaryFragColor;
void main() {
    fragColor = vec4(1);
    secondaryFragColor = vec4(1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'fragColor' : output location must be < MAX_DUAL_SOURCE_DRAW_BUFFERS");
}

// Shader that specifies an output with out-of-bounds location for index 1 is invalid.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedOutOfBoundsLocationQualifierIndex1)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    GLint maxDualSourceDrawBuffers = 0;
    glGetIntegerv(GL_MAX_DUAL_SOURCE_DRAW_BUFFERS_EXT, &maxDualSourceDrawBuffers);
    ANGLE_SKIP_TEST_IF(maxDualSourceDrawBuffers > 1);

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(location = 1, index = 1) out mediump vec4 secondaryFragColor;
void main() {
    secondaryFragColor = vec4(1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'secondaryFragColor' : output location must be < MAX_DUAL_SOURCE_DRAW_BUFFERS");
}

// Shader that specifies two outputs with the same location
// but different indices and different base types is invalid.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedLocationOverlap)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(location = 0, index = 0) out mediump vec4 fragColor;
layout(location = 0, index = 1) out mediump ivec4 secondaryFragColor;
void main() {
    fragColor = vec4(1);
    secondaryFragColor = ivec4(1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'secondaryFragColor' : conflicting output types with previously defined output "
                  "'fragColor' for location 0");
}

// Global index layout qualifier fails.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedGlobalIndexQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(index = 0);
out vec4 fragColor;
void main() {
    fragColor = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'index' : invalid layout qualifier: only valid when used with a fragment shader "
                  "output in ESSL version >= 3.00 and EXT_blend_func_extended is enabled");
}

// Index layout qualifier on a non-output variable fails.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedIndexQualifierOnUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(index = 0) uniform vec4 u;
out vec4 fragColor;
void main() {
    fragColor = u;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'index' : invalid layout qualifier: only valid when used with a fragment shader "
                  "output in ESSL version >= 3.00 and EXT_blend_func_extended is enabled");
}

// Index layout qualifier on a struct fails.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedIndexQualifierOnStruct)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
layout(index = 0) struct S {
    vec4 field;
};
out vec4 fragColor;
void main() {
    fragColor = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'index' : invalid layout qualifier: only valid when used with a fragment shader "
                  "output in ESSL version >= 3.00 and EXT_blend_func_extended is enabled");
}

// Index layout qualifier on a struct member fails.
TEST_P(GLSLValidationTest_ES3, BlendFuncExtendedIndexQualifierOnField)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_blend_func_extended : require
precision mediump float;
struct S {
    layout(index = 0) vec4 field;
};
out mediump vec4 fragColor;
void main() {
    fragColor = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'index' : invalid layout qualifier: only valid when used with a fragment shader "
                  "output in ESSL version >= 3.00 and EXT_blend_func_extended is enabled");
}

// Shader that specifies yuv layout qualifier for not output fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetYuvQualifierOnInput)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(yuv) in vec4 fragColor;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'yuv' : invalid layout qualifier: only valid on program outputs");
}

// Shader that specifies yuv layout qualifier for not output fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetYuvQualifierOnUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(yuv) uniform;
layout(yuv) uniform Transform {
     mat4 M1;
};
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'yuv' : invalid layout qualifier: only valid on program outputs");
}

// Shader that specifies yuv layout qualifier with location fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetYuvQualifierAndLocation)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(location = 0, yuv) out vec4 fragColor;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'yuv' : invalid layout qualifier combination");
}

// Shader that specifies yuv layout qualifier with multiple color outputs fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetYuvAndColorOutput)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(yuv) out vec4 fragColor;
out vec4 fragColor1;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'fragColor' : not allowed to specify yuv qualifier when using depth or multiple "
                  "color fragment outputs");
}

// Shader that specifies yuv layout qualifier with multiple color outputs fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetYuvAndColorOutputWithLocation)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(yuv) out vec4 fragColor;
layout(location = 1) out vec4 fragColor1;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'fragColor' : not allowed to specify yuv qualifier when using depth or multiple "
                  "color fragment outputs");
}

// Shader that specifies yuv layout qualifier with depth output fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetWithFragDepth)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(yuv) out vec4 fragColor;
void main() {
    gl_FragDepth = 1.0f;
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'fragColor' : not allowed to specify yuv qualifier when using depth or multiple "
                  "color fragment outputs");
}

// Shader that specifies yuv layout qualifier with multiple outputs fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetMultipleYuvOutputs)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
layout(yuv) out vec4 fragColor;
layout(yuv) out vec4 fragColor1;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'fragColor' : not allowed to specify yuv qualifier when using depth or multiple "
                  "color fragment outputs");
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'fragColor1' : not allowed to specify yuv qualifier when using depth or "
                  "multiple color fragment outputs");
}

// Shader that specifies yuvCscStandardEXT type constructor fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetEmptyCscStandardConstructor)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = yuvCscStandardEXT();
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'yuvCscStandardEXT' : cannot construct this type");
}

// Shader that specifies yuvCscStandardEXT type constructor fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardConstructor)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = yuvCscStandardEXT(itu_601);
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'yuvCscStandardEXT' : cannot construct this type");
}

// Shader that specifies yuvCscStandardEXT type conversion fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetImplicitTypeConversionToCscStandardFromBool)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = false;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "cannot convert from 'const bool' to 'yuvCscStandardEXT'");
}

// Shader that specifies yuvCscStandardEXT type conversion fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetImplicitTypeConversionToCscStandardFromInt)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = 0;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "cannot convert from 'const int' to 'yuvCscStandardEXT'");
}

// Shader that specifies yuvCscStandardEXT type conversion fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetImplicitTypeConversionToCscStandardFromFloat)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = 2.0f;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "cannot convert from 'const float' to 'yuvCscStandardEXT'");
}

// Shader that does arithmetics on yuvCscStandardEXT fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardOr)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = itu_601 | itu_709;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "wrong operand types - no operation '|' exists that takes a left-hand operand of "
                  "type 'const yuvCscStandardEXT' and a right operand of type 'const "
                  "yuvCscStandardEXT' (or there is no acceptable conversion)");
}

// Shader that does arithmetics on yuvCscStandardEXT fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardAnd)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
yuvCscStandardEXT conv = itu_601 & 3.0f;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "wrong operand types - no operation '&' exists that takes a left-hand operand of "
                  "type 'const yuvCscStandardEXT' and a right operand of type 'const float' (or "
                  "there is no acceptable conversion)");
}

// Shader that uses yuvCscStandardEXT in constructor fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardUintConstructor)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
const yuvCscStandardEXT conv = itu_601;
const uint u = uint(conv);
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'constructor' : cannot convert a yuvCscStandardEXT");
}

// Shader that specifies yuvCscStandardEXT type qualifiers fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardInput)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
in yuvCscStandardEXT conv = itu_601;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'in' : cannot be used with a yuvCscStandardEXT");
}

// Shader that specifies yuvCscStandardEXT type qualifiers fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardOutput)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
out yuvCscStandardEXT conv = itu_601;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'out' : cannot be used with a yuvCscStandardEXT");
}

// Shader that specifies yuvCscStandardEXT type qualifiers fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardUniform)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
uniform yuvCscStandardEXT conv = itu_601;
void main() {
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "'uniform' : cannot be used with a yuvCscStandardEXT");
}

// Overloading rgb_2_yuv is ok if the extension is not supported.
TEST_P(GLSLValidationTest_ES3, OverloadRgb2Yuv)
{
    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
float rgb_2_yuv(float x) { return x + 1.0; }

in float i;
out float o;

void main()
{
    o = rgb_2_yuv(i);
})";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Overloading yuv_2_rgb is ok if the extension is not supported.
TEST_P(GLSLValidationTest_ES3, OverloadYuv2Rgb)
{
    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_EXT_YUV_target"));

    constexpr char kFS[] = R"(#version 300 es
precision mediump float;
float yuv_2_rgb(float x) { return x + 1.0; }

in float i;
out float o;

void main()
{
    o = yuv_2_rgb(i);
})";

    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Use gl_LastFragData without redeclaration of gl_LastFragData with noncoherent qualifier
TEST_P(GLSLValidationTest, FramebufferFetchNoLastFragDataRedeclaration)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));

    constexpr char kFS[] =
        R"(#extension GL_EXT_shader_framebuffer_fetch_non_coherent : require
uniform highp vec4 u_color;

void main (void)
{
    gl_FragColor = u_color + gl_LastFragData[0];
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'noncoherent' qualifier must be used when "
                  "GL_EXT_shader_framebuffer_fetch_non_coherent extension is used");
}

// Redeclare gl_LastFragData without noncoherent qualifier
TEST_P(GLSLValidationTest, FramebufferFetchLastFragDataWithoutNoncoherentQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));

    constexpr char kFS[] =
        R"(#extension GL_EXT_shader_framebuffer_fetch_non_coherent : require
uniform highp vec4 u_color;
highp vec4 gl_LastFragData[gl_MaxDrawBuffers];

void main (void)
{
    gl_FragColor = u_color + gl_LastFragData[0];
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'noncoherent' qualifier must be used when "
                  "GL_EXT_shader_framebuffer_fetch_non_coherent extension is used");
}

// Declare inout without noncoherent qualifier
TEST_P(GLSLValidationTest_ES3, FramebufferFetchInoutWithoutNoncoherentQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent"));

    constexpr char kFS[] =
        R"(#version 300 es
#extension GL_EXT_shader_framebuffer_fetch_non_coherent : require
layout(location = 0) inout highp vec4 o_color;
uniform highp vec4 u_color;

void main (void)
{
    o_color = clamp(o_color + u_color, vec4(0.0f), vec4(1.0f));
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'noncoherent' qualifier must be used when "
                  "GL_EXT_shader_framebuffer_fetch_non_coherent extension is used");
}

// Redeclare gl_LastFragColorARM with unexpected basic type
TEST_P(GLSLValidationTest, FramebufferFetchLastFragColorWrongType)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch"));

    constexpr char kFS[] =
        R"(#extension GL_ARM_shader_framebuffer_fetch : require
highp int gl_LastFragColorARM;

void main (void)
{
    gl_FragColor = vec4(gl_LastFragColorARM);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_LastFragColorARM' : redeclaration of built-in with a different type");
}

// Redeclare gl_LastFragColorARM with unexpected arrayness
TEST_P(GLSLValidationTest, FramebufferFetchLastFragColorArrayed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch"));

    constexpr char kFS[] =
        R"(#extension GL_ARM_shader_framebuffer_fetch : require
highp vec4 gl_LastFragColorARM[4];

void main (void)
{
    gl_FragColor = vec4(gl_LastFragColorARM[0]);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_LastFragColorARM' : redeclaration of built-in with a different type");
}

// Redeclare gl_LastFragDepthARM with unexpected basic type
TEST_P(GLSLValidationTest, FramebufferFetchLastFragDepthWrongType)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));

    constexpr char kFS[] =
        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
highp int gl_LastFragDepthARM;

void main (void)
{
    gl_FragColor = vec4(gl_LastFragDepthARM);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_LastFragDepthARM' : redeclaration of built-in with a different type");
}

// Redeclare gl_LastFragDepthARM with unexpected arrayness
TEST_P(GLSLValidationTest, FramebufferFetchLastFragDepthArrayed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));

    constexpr char kFS[] =
        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
highp float gl_LastFragDepthARM[4];

void main (void)
{
    gl_FragColor = vec4(gl_LastFragDepthARM[0]);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_LastFragDepthARM' : redeclaration of built-in with a different type");
}

// Redeclare gl_LastFragStencilARM with unexpected basic type
TEST_P(GLSLValidationTest, FramebufferFetchLastFragStencilWrongType)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));

    constexpr char kFS[] =
        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
highp float gl_LastFragStencilARM;

void main (void)
{
    gl_FragColor = vec4(gl_LastFragStencilARM);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_LastFragStencilARM' : redeclaration of built-in with a different type");
}

// Redeclare gl_LastFragStencilARM with unexpected arrayness
TEST_P(GLSLValidationTest, FramebufferFetchLastFragStencilArrayed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ARM_shader_framebuffer_fetch_depth_stencil"));

    constexpr char kFS[] =
        R"(#extension GL_ARM_shader_framebuffer_fetch_depth_stencil : require
highp int gl_LastFragStencilARM[4];

void main (void)
{
    gl_FragColor = vec4(gl_LastFragStencilARM[0]);
})";

    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'gl_LastFragStencilARM' : redeclaration of built-in with a different type");
}

// Ensure that a negative index after a comma generates an error.
TEST_P(GLSLValidationTest_ES3, NegativeIndexAfterComma)
{
    constexpr char kFS[] = R"(#version 300 es
layout(location = 0) out mediump vec4 o_color;
uniform mediump float u;
uniform mediump vec4 u_color[4];

void main (void)
{
    o_color = u_color[u,-2];
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "index expression is negative");
}

// Ensure that a negative const-variable index after a comma generates an error.
TEST_P(GLSLValidationTest_ES3, NegativeConstVarIndexAfterComma)
{
    constexpr char kFS[] = R"(#version 300 es
layout(location = 0) out mediump vec4 o_color;
uniform mediump float u;
uniform mediump vec4 u_color[4];

void main (void)
{
    const int index = -2;
    o_color = u_color[u,index];
})";

    validateError(GL_FRAGMENT_SHADER, kFS, "index expression is negative");
}

// Validate that clip/cull distance extensions are not available in ESSL 100
TEST_P(GLSLValidationTest, ClipCullDistance)
{
    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        constexpr char kVS[] = R"(#extension GL_ANGLE_clip_cull_distance : require
attribute vec4 aPosition;
void main()
{
    gl_Position = aPosition;
})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'GL_ANGLE_clip_cull_distance' : extension is not supported");
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        constexpr char kVS[] = R"(#extension GL_EXT_clip_cull_distance : require
attribute vec4 aPosition;
void main()
{
    gl_Position = aPosition;
})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'GL_EXT_clip_cull_distance' : extension is not supported");
    }
}

// Validate that conflicts are generated for locations consumed by an I/O block array.
TEST_P(GLSLValidationTest_ES31, IOBlockArrayLocations)
{
    constexpr char kVS[] = R"(#version 310 es
#extension GL_EXT_shader_io_blocks : require

in highp vec4 position;

// This should consume locations [3, 8]
layout(location = 3) out Block
{
    vec4 a;
    vec4 b;
} block[3];

// c will conflict with block[0].a
layout(location = 2) out vec4 c[2];

// d will conflict with block[0].b, block[1].a and block[1].b
layout(location = 4) out vec4 d[3];

// e will conflict with block[2].a
layout(location = 7) out vec4 e;

// f will conflict with block[2].b
layout(location = 7) out vec4 f[2];

void main()
{
    block[0].a = vec4(0.1);
    block[0].b = vec4(0.2);
    block[1].a = vec4(0.3);
    block[1].b = vec4(0.4);
    block[2].a = vec4(0.5);
    block[2].b = vec4(0.6);
    c[0] = vec4(0.7);
    c[1] = vec4(0.8);
    d[0] = vec4(0.9);
    d[1] = vec4(1.0);
    d[2] = vec4(1.1);
    e = vec4(1.2);
    f[0] = vec4(1.3);
    f[1] = vec4(1.4);
    gl_Position = position;
})";

    const CompiledShader &shader = compile(GL_VERTEX_SHADER, kVS);
    EXPECT_FALSE(shader.success());

    const char *kExpect[] = {
        "'c' conflicting location with 'block.a'",
        "'d' conflicting location with 'block.b'",
        "'e' conflicting location with 'block'",
        "'f' conflicting location with 'block'",
    };

    for (const char *expect : kExpect)
    {
        EXPECT_TRUE(shader.hasInfoLog(expect)) << expect;
    }

    reset();
}

// Validate that deeply nested |while| loops fail in WebGL.
TEST_P(WebGL2GLSLValidationTest, DeeplyNestedWhileStatements)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
void main() {
)";
    for (int i = 0; i < 1700; ++i)
    {
        fs << " while(true)";
    }
    fs << "; }";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "statement is too deeply nested");
}

// Validate that deeply nested |for| loops fail in WebGL.
TEST_P(WebGL2GLSLValidationTest, DeeplyNestedForStatements)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
void main() {
)";
    for (int i = 0; i < 1700; ++i)
    {
        fs << " for(int i = 0; i < 10; i++)";
    }
    fs << "; }";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "statement is too deeply nested");
}

// Validate that deeply nested |do-while| loops fail in WebGL.
TEST_P(WebGL2GLSLValidationTest, DeeplyNestedDoWhileStatements)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
void main() {
)";
    for (int i = 0; i < 1700; ++i)
    {
        fs << " do {";
    }
    for (int i = 0; i < 1700; ++i)
    {
        fs << "} while(true);";
    }
    fs << "}";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "statement is too deeply nested");
}

// Validate that deeply nested |switch| blocks fail in WebGL.
TEST_P(WebGL2GLSLValidationTest, DeeplyNestedSwitchStatements)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
void main() {
)";
    for (int i = 0; i < 1700; ++i)
    {
        fs << " switch(1) { default: int i=0;";
    }
    for (int i = 0; i < 1700; ++i)
    {
        fs << "}";
    }
    fs << "}";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "statement is too deeply nested");
}

// Validate that too many array dimensions fail in WebGL.
TEST_P(WebGL2GLSLValidationTest, HugeUnsizedMultidimensionalArrayConstructor)
{
    std::ostringstream fs;
    fs << R"(#version 310 es
int E=int)";
    for (int i = 0; i < 10000; ++i)
    {
        fs << "[]";
    }
    fs << "()";
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "unsupported shader version");
}

// Validate that too many array dimensions fail in WebGL.
TEST_P(WebGL2GLSLValidationTest, HugeMultidimensionalArrayConstructor)
{
    std::ostringstream fs;
    fs << R"(#version 310 es
int E=int)";
    for (int i = 0; i < 10000; ++i)
    {
        fs << "[1]";
    }

    for (int i = 0; i < 10000; ++i)
    {
        fs << "(2)";
    }
    validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "unsupported shader version");
}

// Validate that too-complex unary expressions fail to compile in WebGL.
TEST_P(WebGL2GLSLValidationTest, ManyChainedUnaryExpressions)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
precision mediump float;
void main() {
  int iterations=0;)";
    for (int i = 0; i < 6000; ++i)
    {
        fs << "~";
    }
    fs << R"(++iterations;
})";

    // The IR has no limitations with expression complexity.
    if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
    {
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }
    else
    {
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
    }
}

// Validate that too-complex assignment chains fail to compile in WebGL.
TEST_P(WebGL2GLSLValidationTest, ManyChainedAssignments)
{
    std::ostringstream fs;
    fs << R"(#version 300 es
void main() {
    int c = 0;
)";
    for (int i = 0; i < 3750; ++i)
    {
        fs << "c=\n";
    }
    fs << "c+1; }";

    // The IR has no limitations with expression complexity.
    if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
    {
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }
    else
    {
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
    }
}

// Validate that too-complex binary expressions fail to compile in WebGL.
TEST_P(WebGL2GLSLValidationTest, ManyChainedBinaryExpressions)
{
    constexpr char kPreamble[] = R"(precision mediump float;
            uniform vec4 u_color;
            void main()
            {
               gl_FragColor = u_color)";

    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxExpressionComplexity - 10; ++i)
        {
            fs << "+ vec4(" << i << ")";
        }
        fs << "; }";
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }

    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxExpressionComplexity + 10; ++i)
        {
            fs << "+ vec4(" << i << ")";
        }
        fs << "; }";
        // The IR has no limitations with expression complexity.
        if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
        {
            validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
        }
        else
        {
            validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
        }
    }
}

// Validate that too-complex binary expressions fail to compile in WebGL, even inside an unused
// function.
TEST_P(WebGL2GLSLValidationTest, ManyChainedBinaryExpressionsInUnusedFunction)
{
    constexpr char kPreamble[] = R"(precision mediump float;
            uniform vec4 u_color;
            void main()
            {
               gl_FragColor = u_color;
            }
            vec4 someFunction() {
              return u_color)";

    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxExpressionComplexity - 10; ++i)
        {
            fs << "+ vec4(" << i << ")";
        }
        fs << "; }";
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }

    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxExpressionComplexity + 10; ++i)
        {
            fs << "+ vec4(" << i << ")";
        }
        fs << "; }";
        // The IR has no limitations with expression complexity.
        if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
        {
            validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
        }
        else
        {
            validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
        }
    }
}

// Validate that too-complex binary expressions fail to compile in WebGL, specifically nested inside
// a switch statement.
TEST_P(WebGL2GLSLValidationTest, ManyChainedBinaryExpressionsInSwitch)
{
    constexpr char kPreamble[]  = R"(#version 300 es
            uniform int u;

            void main()
            {
                int x;
                switch (u)
                {
                    case 0:
                        x = x)";
    constexpr char kPostamble[] = R"(;
                }  // switch (u)
            })";
    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxExpressionComplexity + 1; ++i)
        {
            fs << " + x";
        }
        fs << kPostamble;
        // The IR has no limitations with expression complexity.
        if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
        {
            validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
        }
        else
        {
            validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
        }
    }

    // Same test but way over the limit.
    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < 20 * kMaxExpressionComplexity; ++i)
        {
            fs << " + x";
        }
        fs << kPostamble;
        // The IR has no limitations with expression complexity.
        if (getEGLWindow()->isFeatureEnabled(Feature::UseIr))
        {
            validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
        }
        else
        {
            validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
        }
    }
}

// Validate that too-complex binary expressions fail to compile in WebGL, specifically inside a
// global initializer.
TEST_P(WebGL2GLSLValidationTest, NestingInsideGlobalInitializer)
{
    constexpr char kPreamble[]  = R"(uniform int u;
            int x = u)";
    constexpr char kPostamble[] = R"(;
            void main()
            {
                gl_FragColor = vec4(0.0);
            })";
    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxExpressionComplexity + 1; ++i)
        {
            fs << " + u";
        }
        fs << kPostamble;
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
    }

    // Same test but way over the limit.
    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < 20 * kMaxExpressionComplexity; ++i)
        {
            fs << " + u";
        }
        fs << kPostamble;
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Expression too complex");
    }
}

// Validate that too-deep call chains fail to compile in WebGL.
TEST_P(WebGL2GLSLValidationTest, TooDeepCallStack)
{
    std::ostringstream preamble;
    preamble << R"(precision mediump float;
        uniform vec4 u_color;
        vec4 function0()  {
          return u_color;
        }
    )";
    for (uint32_t i = 0; i < kMaxCallStackDepth - 10; ++i)
    {
        preamble << "vec4 function" << (i + 1) << "() {\n"
                 << "  return function" << i << "();\n"
                 << "}\n";
    }

    {
        std::ostringstream fs;
        fs << preamble.str();
        fs << R"(void main() {
    gl_FragColor = function)"
           << (kMaxCallStackDepth - 10) << R"(();
})";
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }

    // Same test, but the call chain is unused.
    {
        std::ostringstream fs;
        fs << preamble.str();
        fs << R"(void main() {
    gl_FragColor = vec4(0,0,0,0);
})";
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }

    // Add more to the call chain, so the depth goes over the limit
    for (uint32_t i = kMaxCallStackDepth - 10; i < kMaxCallStackDepth + 10; ++i)
    {
        preamble << "vec4 function" << (i + 1) << "() {\n"
                 << "  return function" << i << "();\n"
                 << "}\n";
    }

    {
        std::ostringstream fs;
        fs << preamble.str();
        fs << R"(void main() {
    gl_FragColor = function)"
           << (kMaxCallStackDepth + 10) << R"(();
})";
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Call stack too deep");
    }

    // Same test, but the call chain is unused.
    {
        std::ostringstream fs;
        fs << preamble.str();
        fs << R"(void main() {
    gl_FragColor = vec4(0,0,0,0);
})";
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Call stack too deep");
    }
}

// Validate that too many function arguments fail to compile in WebGL.
TEST_P(WebGL2GLSLValidationTest, TooManyFunctionParameters)
{
    constexpr char kPreamble[]  = R"(precision mediump float;
        float foo()";
    constexpr char kPostamble[] = R"() { return f0; }
void main() { gl_FragColor = vec4(0, 0, 0, 0); })";

    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxFunctionParameters - 10; ++i)
        {
            if (i != 0)
            {
                fs << ", ";
            }
            fs << "float f" << i;
        }
        fs << kPostamble;
        validateSuccess(GL_FRAGMENT_SHADER, fs.str().c_str());
    }

    {
        std::ostringstream fs;
        fs << kPreamble;
        for (uint32_t i = 0; i < kMaxFunctionParameters + 10; ++i)
        {
            if (i != 0)
            {
                fs << ", ";
            }
            fs << "float f" << i;
        }
        fs << kPostamble;
        validateError(GL_FRAGMENT_SHADER, fs.str().c_str(), "Function has too many parameters");
    }
}

// Test that recursion fails compilation
TEST_P(GLSLValidationTest, Recursion)
{
    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;
        vec4 someFunc()  {
            return someFunc();
        }

        void main() {
            gl_FragColor = u_color * someFunc();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;

        vec4 someFunc();

        vec4 someFunc1()  {
            return someFunc();
        }

        vec4 someFunc()  {
            return someFunc1();
        }

        void main() {
            gl_FragColor = u_color * someFunc();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc1 <- someFunc "
                      "<- someFunc1");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;
        vec4 someFunc()  {
            if (u_color.x > 0.5) {
                return someFunc();
            } else {
                return vec4(1);
            }
        }

        void main() {
            gl_FragColor = someFunc();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;
        vec4 someFunc()  {
            if (u_color.x > 0.5) {
                return vec4(1);
            } else {
                return someFunc();
            }
        }

        void main() {
            gl_FragColor = someFunc();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;
        vec4 someFunc()  {
            return (u_color.x > 0.5) ? vec4(1) : someFunc();
        }

        void main() {
            gl_FragColor = someFunc();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;
        vec4 someFunc()  {
            return (u_color.x > 0.5) ? someFunc() : vec4(1);
        }

        void main() {
            gl_FragColor = someFunc();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;
        vec4 someFunc()  {
            return someFunc();
        }

        void main() {
            gl_FragColor = u_color;
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: someFunc");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;

        vec4 function2() {
            return u_color;
        }

        vec4 function1() {
            vec4 a = function2();
            vec4 b = function1();
            return a + b;
        }

        void main() {
            gl_FragColor = function1();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: function1");
    }

    {
        constexpr char kFS[] =
            R"(precision mediump float;
        uniform vec4 u_color;

        vec4 function1();

        vec4 function3() {
            return function1();
        }

        vec4 function2() {
            return function3();
        }

        vec4 function1() {
            return function2();
        }

        void main() {
            gl_FragColor = function1();
        }
    )";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "Recursive function call in the following call chain: function3 <- function2 "
                      "<- function1 <- function3");
    }
}

// Test that in WebGL1, extensions can be enabled late in the shader.
TEST_P(WebGLGLSLValidationTest, LateEnableExtension)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_frag_depth"));
    constexpr char kFS[] = R"(precision mediump float;
void main()
{
#extension GL_EXT_frag_depth : enable
    gl_FragDepthEXT = 1.0;
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

class WebGLGLSLValidationExtensionDisableTest : public WebGLGLSLValidationTest
{};

// Test that in WebGL1, even though an extension can be enabled late in the shader, it cannot be
// disabled late.
TEST_P(WebGLGLSLValidationExtensionDisableTest, LateDisableExtension)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_frag_depth"));
    {
        constexpr char kFS[] = R"(#extension GL_EXT_frag_depth : enable
precision mediump float;
void main()
{
    gl_FragDepthEXT = 1.0;
#extension GL_EXT_frag_depth : disable
})";
        if (getEGLWindow()->isFeatureEnabled(Feature::AllowExtensionDisableAfterNonPpTokens))
        {
            validateSuccess(GL_FRAGMENT_SHADER, kFS);
        }
        else
        {
            validateError(GL_FRAGMENT_SHADER, kFS,
                          "extension directive with disable behavior must occur before any "
                          "non-preprocessor tokens");
        }
    }

    {
        constexpr char kFS[] = R"(#extension GL_EXT_frag_depth : enable
precision mediump float;
void main()
{
    gl_FragDepthEXT = 1.0;
}
#extension all : disable
)";
        if (getEGLWindow()->isFeatureEnabled(Feature::AllowExtensionDisableAfterNonPpTokens))
        {
            validateSuccess(GL_FRAGMENT_SHADER, kFS);
        }
        else
        {
            validateError(GL_FRAGMENT_SHADER, kFS,
                          "extension directive with disable behavior must occur before any "
                          "non-preprocessor tokens");
        }
    }
}

class GLSLValidationClipDistanceTest_ES3 : public GLSLValidationTest_ES3
{
  protected:
    void validateErrorWithExt(GLenum shaderType,
                              const char *extension,
                              const char *shaderSource,
                              const char *expectedError)
    {
        std::stringstream src;
        src << R"(#version 300 es
#extension )"
            << extension << ": require\n"
            << shaderSource;
        validateError(shaderType, src.str().c_str(), expectedError);
    }
};

class GLSLValidationClipDistanceTest_ES31 : public GLSLValidationTest_ES31
{
  protected:
    void validateErrorWithExt(GLenum shaderType,
                              const char *extension,
                              const char *shaderSource,
                              const char *expectedError)
    {
        std::stringstream src;
        src << R"(#version 310 es
#extension )"
            << extension << ": require\n"
            << shaderSource;
        validateError(shaderType, src.str().c_str(), expectedError);
    }
};

// Extension support is required to compile properly.  Expect failure when it is not present.
TEST_P(GLSLValidationClipDistanceTest_ES3, CompileFailsWithoutExtension)
{
    ANGLE_SKIP_TEST_IF(IsGLExtensionEnabled("GL_APPLE_clip_distance"));

    {
        constexpr char kVS[] = R"(#extension GL_APPLE_clip_distance : require
uniform vec4 uPlane;

attribute vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[1] = dot(aPosition, uPlane);
})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'GL_APPLE_clip_distance' : extension is not supported");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
#extension GL_APPLE_clip_distance : require
uniform vec4 uPlane;

in vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[1] = dot(aPosition, uPlane);
})";
        validateError(GL_VERTEX_SHADER, kVS,
                      "'GL_APPLE_clip_distance' : extension is not supported");
    }
}

// Extension directive is required to compile properly.  Expect failure when it is not present.
TEST_P(GLSLValidationClipDistanceTest_ES3, CompileFailsWithExtensionWithoutPragma)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_APPLE_clip_distance"));

    {
        constexpr char kVS[] = R"(uniform vec4 uPlane;

attribute vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[1] = dot(aPosition, uPlane);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'GL_APPLE_clip_distance' : extension is disabled");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
uniform vec4 uPlane;

in vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[1] = dot(aPosition, uPlane);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'GL_APPLE_clip_distance' : extension is disabled");
    }
}

// Shader using gl_ClipDistance and gl_CullDistance
// But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances
TEST_P(GLSLValidationClipDistanceTest_ES3, TooManyCombined)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    GLint maxCombinedClipAndCullDistances = 0;
    glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
    ANGLE_SKIP_TEST_IF(maxCombinedClipAndCullDistances > 11);

    constexpr char kVS[] =
        R"(uniform vec4 uPlane;

in vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[5] = dot(aPosition, uPlane);
    gl_CullDistance[4] = dot(aPosition, uPlane);
})";
    constexpr char kExpect[] =
        "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
        "gl_MaxCombinedClipAndCullDistance";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance, but without a side
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareClipDistanceNoSize)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
out highp float gl_ClipDistance[];
void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = 1.0;
})";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : implicitly sized arrays only allowed for tessellation shaders or "
        "geometry shader inputs";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_CullDistance, but without a side
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareCullDistanceNoSize)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
out highp float gl_CullDistance[];
void main()
{
    gl_Position = aPosition;
})";
    constexpr char kExpect[] =
        "'gl_CullDistance' : implicitly sized arrays only allowed for tessellation shaders or "
        "geometry shader inputs";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance and gl_CullDistance
// But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances
TEST_P(GLSLValidationClipDistanceTest_ES3, TooManyCombined2)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    GLint maxCombinedClipAndCullDistances = 0;
    glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
    ANGLE_SKIP_TEST_IF(maxCombinedClipAndCullDistances > 9);

    constexpr char kVS[] =
        R"(uniform vec4 uPlane;

in vec4 aPosition;

out highp float gl_ClipDistance[5];
out highp float gl_CullDistance[4];

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[gl_MaxClipDistances - 6 + 1] = dot(aPosition, uPlane);
    gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)] = dot(aPosition, uPlane);
    gl_CullDistance[gl_MaxCullDistances - 6 + 1] = dot(aPosition, uPlane);
    gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)] = dot(aPosition, uPlane);
})";
    constexpr char kExpect[] =
        "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
        "gl_MaxCombinedClipAndCullDistance";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance
// But, the array size is greater than gl_MaxClipDistances
TEST_P(GLSLValidationClipDistanceTest_ES3, TooManyClip)
{
    constexpr char kVS[] =
        R"(uniform vec4 uPlane;

in vec4 aPosition;

out highp float gl_ClipDistance[gl_MaxClipDistances + 1];

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[gl_MaxClipDistances - 6 + 1] = dot(aPosition, uPlane);
    gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)] = dot(aPosition, uPlane);
})";
    constexpr char kExpect[] = "redeclaration of gl_ClipDistance with size > gl_MaxClipDistances";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Access gl_CullDistance with integral constant index
// But, the index is gl_MaxCullDistances, greater than gl_CullDistance array size.
TEST_P(GLSLValidationClipDistanceTest_ES3, OutOfBoundsCullIndex)
{
    constexpr char kVS[] =
        R"(uniform vec4 uPlane;

in vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_CullDistance[gl_MaxCullDistances] = dot(aPosition, uPlane);
})";
    constexpr char kExpect[] = "array index out of range";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader using gl_ClipDistance and gl_CullDistance
// But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances
TEST_P(GLSLValidationClipDistanceTest_ES3, TooManyCombinedFS)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    GLint maxCombinedClipAndCullDistances = 0;
    glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
    ANGLE_SKIP_TEST_IF(maxCombinedClipAndCullDistances > 11);

    constexpr char kFS[] = R"(out highp vec4 fragColor;

void main()
{
    fragColor = vec4(gl_ClipDistance[4], gl_CullDistance[5], 0, 1);
})";
    constexpr char kExpect[] =
        "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
        "gl_MaxCombinedClipAndCullDistances";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_ANGLE_clip_cull_distance", kFS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_EXT_clip_cull_distance", kFS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance and gl_CullDistance
// But, the sum of the sizes is greater than gl_MaxCombinedClipAndCullDistances
TEST_P(GLSLValidationClipDistanceTest_ES3, TooManyCombinedFS2)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    GLint maxCombinedClipAndCullDistances = 0;
    glGetIntegerv(GL_MAX_COMBINED_CLIP_AND_CULL_DISTANCES_EXT, &maxCombinedClipAndCullDistances);
    ANGLE_SKIP_TEST_IF(maxCombinedClipAndCullDistances > 9);

    constexpr char kFS[] = R"(in highp float gl_ClipDistance[5];
in highp float gl_CullDistance[4];

in highp vec4 aPosition;

out highp vec4 fragColor;

void main()
{
    fragColor.x = gl_ClipDistance[gl_MaxClipDistances - 6 + 1];
    fragColor.y = gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)];
    fragColor.z = gl_CullDistance[gl_MaxCullDistances - 6 + 1];
    fragColor.w = gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)];
})";
    constexpr char kExpect[] =
        "The sum of 'gl_ClipDistance' and 'gl_CullDistance' size is greater than "
        "gl_MaxCombinedClipAndCullDistances";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_ANGLE_clip_cull_distance", kFS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_EXT_clip_cull_distance", kFS, kExpect);
    }
}

// In fragment shader, writing to gl_ClipDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES3, FragmentWriteToClipDistance)
{
    constexpr char kFS[] = R"(out highp vec4 fragColor;

void main()
{
    gl_ClipDistance[0] = 0.0f;
    fragColor = vec4(1, gl_ClipDistance[0], 0, 1);
})";
    constexpr char kExpect[] =
        "l-value required (can't modify gl_ClipDistance in a fragment shader \"gl_ClipDistance\")";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_ANGLE_clip_cull_distance", kFS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_EXT_clip_cull_distance", kFS, kExpect);
    }
}

// In fragment shader, writing to gl_CullDistance should be denied even if redeclaring it with the
// array size
TEST_P(GLSLValidationClipDistanceTest_ES3, FragmentWriteToCullDistance)
{
    constexpr char kFS[] = R"(out highp vec4 fragColor;

in highp float gl_CullDistance[1];

void main()
{
    gl_CullDistance[0] = 0.0f;
    fragColor = vec4(1, gl_CullDistance[0], 0, 1);
})";
    constexpr char kExpect[] =
        "l-value required (can't modify gl_CullDistance in a fragment shader \"gl_CullDistance\")";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_ANGLE_clip_cull_distance", kFS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_EXT_clip_cull_distance", kFS, kExpect);
    }
}

// Accessing to gl_Clip/CullDistance with non-const index should be denied if the size of
// gl_Clip/CullDistance is not decided.
TEST_P(GLSLValidationClipDistanceTest_ES3, FragmentDynamicIndexWhenNotRedeclared)
{
    constexpr char kFS[] = R"(out highp vec4 fragColor;

void main()
{
    mediump float color[3];
    for(int i = 0 ; i < 3 ; i++)
    {
        color[i] = gl_CullDistance[i];
    }
    fragColor = vec4(color[0], color[1], color[2], 1.0f);
})";
    constexpr char kExpect[] =
        "The gl_CullDistance array must be sized by the shader either redeclaring it with a size "
        "or indexing it only with constant integral expressions";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_ANGLE_clip_cull_distance", kFS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_FRAGMENT_SHADER, "GL_EXT_clip_cull_distance", kFS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance, but after it's been referenced with a constant index.
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareClipDistanceAfterReferenceConstantIndex)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = 1.0;
}
out highp float gl_ClipDistance[3];
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : redeclaration of gl_ClipDistance after it is referenced is not "
        "allowed";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_CullDistance, but after it's been referenced with a constant index.
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareCullDistanceAfterReferenceConstantIndex)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_CullDistance[0] = 1.0;
}
out highp float gl_CullDistance[3];
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : redeclaration of gl_CullDistance after it is referenced is not "
        "allowed";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance, but after it's been referenced with a non-constant index.
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareClipDistanceAfterReferenceNonConstantIndex)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    for (int i = 0; i < 2; ++i)
    {
        gl_ClipDistance[i] = 1.0;
    }
}
out highp float gl_ClipDistance[3];
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : redeclaration of gl_ClipDistance after it is referenced is not "
        "allowed";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_CullDistance, but after it's been referenced with a non-constant index.
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareCullDistanceAfterReferenceNonConstantIndex)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    for (int i = 0; i < 2; ++i)
    {
        gl_CullDistance[i] = 1.0;
    }
}
out highp float gl_CullDistance[3];
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : redeclaration of gl_CullDistance after it is referenced is not "
        "allowed";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance, but after it's been referenced with .length().
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareClipDistanceAfterReferenceLength)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_Position.z = gl_ClipDistance.length();
}
out highp float gl_ClipDistance[3];
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : redeclaration of gl_ClipDistance after it is referenced is not "
        "allowed";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_CullDistance, but after it's been referenced with .length().
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareCullDistanceAfterReferenceLength)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_Position.z = gl_CullDistance.length();
}
out highp float gl_CullDistance[3];
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : redeclaration of gl_CullDistance after it is referenced is not "
        "allowed";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_ClipDistance twice.
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareClipDistanceTwice)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
out highp float gl_ClipDistance[3];
out highp float gl_ClipDistance[3];
void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = 1.0;
}
)";
    constexpr char kExpect[] = "'gl_ClipDistance' : redefinition";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader redeclares gl_CullDistance twice.
TEST_P(GLSLValidationClipDistanceTest_ES3, RedeclareCullDistanceTwice)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
out highp float gl_CullDistance[3];
out highp float gl_CullDistance[3];
void main()
{
    gl_Position = aPosition;
    gl_CullDistance[0] = 1.0;
}
)";
    constexpr char kExpect[] = "'gl_CullDistance' : redefinition";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader passes gl_ClipDistance to function without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedClipDistancePassedToFunction)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void f(float d[8]) {}
void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = 1.0;
    f((gl_Position.x, gl_ClipDistance));
}
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : Cannot pass to function unless it is explicitly sized";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader passes gl_CullDistance to function without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedCullDistancePassedToFunction)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void f(float d[8]) {}
uniform int zero;
void main()
{
    gl_Position = aPosition;
    gl_CullDistance[0] = 1.0;
    float unused[8];
    f(zero == 0 ? gl_CullDistance : unused);
}
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : Cannot pass to function unless it is explicitly sized";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader assigns to gl_ClipDistance without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedClipDistanceAssignedTo)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    float d[8];
    gl_ClipDistance = d;
}
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : Cannot use as left-hand side of assignment unless it is explicitly "
        "sized";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader assigns to gl_CullDistance without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedCullDistanceAssignedTo)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    float d[8];
    gl_CullDistance = d;
}
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : Cannot use as left-hand side of assignment unless it is explicitly "
        "sized";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader assigns gl_ClipDistance to array without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedClipDistanceAssignedFrom)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
uniform int zero;
void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = 1.0;
    float d[8];
    d = zero == 1 ? d : gl_ClipDistance;
}
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : Cannot use as right-hand side of assignment unless it is explicitly "
        "sized";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader assigns gl_CullDistance to array without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedCullDistanceAssignedFrom)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_CullDistance[0] = 1.0;
    float d[8];
    d = gl_CullDistance;
}
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : Cannot use as right-hand side of assignment unless it is explicitly "
        "sized";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader assigns gl_ClipDistance to array as initializer without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedClipDistanceInitializer)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = 1.0;
    float d[8] = gl_ClipDistance;
}
)";
    constexpr char kExpect[] =
        "'gl_ClipDistance' : Cannot use as right-hand side of assignment unless it is explicitly "
        "sized";

    if (hasAngle)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// Shader assigns gl_CullDistance to array as initializer without explicitly sizing it
TEST_P(GLSLValidationClipDistanceTest_ES3, UnsizedCullDistanceInitializer)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");
    ANGLE_SKIP_TEST_IF(!hasExt && !hasAngle);

    constexpr char kVS[] =
        R"(in vec4 aPosition;
void main()
{
    gl_Position = aPosition;
    gl_CullDistance[0] = 1.0;
    float d[8] = (gl_Position.z, gl_Position.w, gl_CullDistance);
}
)";
    constexpr char kExpect[] =
        "'gl_CullDistance' : Cannot use as right-hand side of assignment unless it is explicitly "
        "sized";

    if (hasAngle)
    {
        GLint maxCullDistances = 0;
        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        if (maxCullDistances > 0)
        {
            validateErrorWithExt(GL_VERTEX_SHADER, "GL_ANGLE_clip_cull_distance", kVS, kExpect);
        }
    }

    if (hasExt)
    {
        validateErrorWithExt(GL_VERTEX_SHADER, "GL_EXT_clip_cull_distance", kVS, kExpect);
    }
}

// In compute shader, redeclaring gl_ClipDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeDeclareClipDistance)
{
    constexpr char kCS[]     = R"(layout(local_size_x = 1) in;
highp float gl_ClipDistance[1];
void main() {})";
    constexpr char kExpect[] = "reserved built-in name";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_ANGLE_clip_cull_distance", kCS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_EXT_clip_cull_distance", kCS, kExpect);
    }
}

// In compute shader, writing to gl_ClipDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeWriteClipDistance)
{
    constexpr char kCS[]     = R"(layout(local_size_x = 1) in;
void main() { gl_ClipDistance[0] = 1.0; })";
    constexpr char kExpect[] = "'gl_ClipDistance' : undeclared identifier";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_ANGLE_clip_cull_distance", kCS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_EXT_clip_cull_distance", kCS, kExpect);
    }
}

// In compute shader, reading gl_ClipDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeReadClipDistance)
{
    constexpr char kCS[]     = R"(layout(local_size_x = 1) in;
void main() { highp float c = gl_ClipDistance[0]; })";
    constexpr char kExpect[] = "'gl_ClipDistance' : undeclared identifier";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_ANGLE_clip_cull_distance", kCS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_EXT_clip_cull_distance", kCS, kExpect);
    }
}

// In compute shader, redeclaring gl_CullDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeDeclareCullDistance)
{
    constexpr char kCS[]     = R"(layout(local_size_x = 1) in;
highp float gl_CullDistance[1];
void main() {})";
    constexpr char kExpect[] = "reserved built-in name";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_ANGLE_clip_cull_distance", kCS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_EXT_clip_cull_distance", kCS, kExpect);
    }
}

// In compute shader, writing to gl_CullDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeWriteCullDistance)
{
    constexpr char kCS[]     = R"(layout(local_size_x = 1) in;
void main() { gl_CullDistance[0] = 1.0; })";
    constexpr char kExpect[] = "'gl_CullDistance' : undeclared identifier";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_ANGLE_clip_cull_distance", kCS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_EXT_clip_cull_distance", kCS, kExpect);
    }
}

// In compute shader, reading gl_CullDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeReadCullDistance)
{
    constexpr char kCS[]     = R"(layout(local_size_x = 1) in;
void main() { highp float c = gl_CullDistance[0]; })";
    constexpr char kExpect[] = "'gl_CullDistance' : undeclared identifier";

    if (IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_ANGLE_clip_cull_distance", kCS, kExpect);
    }

    if (IsGLExtensionEnabled("GL_EXT_clip_cull_distance"))
    {
        validateErrorWithExt(GL_COMPUTE_SHADER, "GL_EXT_clip_cull_distance", kCS, kExpect);
    }
}

class GLSLValidationTextureRectangleTest : public GLSLValidationTest
{};

// Check that if the extension is not supported, trying to use the features without having an
// extension directive fails.
//
// If the extension is supported, check that new types and builtins are usable even with the
// #extension directive
// Issue #15 of ARB_texture_rectangle explains that the extension was specified before the
// #extension mechanism was in place so it doesn't require explicit enabling.
TEST_P(GLSLValidationTextureRectangleTest, NewTypeAndBuiltinsWithoutExtensionDirective)
{
    constexpr char kFS[] = R"(
precision mediump float;
uniform sampler2DRect tex;
void main()
{
    vec4 color = texture2DRect(tex, vec2(1.0));
    color = texture2DRectProj(tex, vec3(1.0));
    color = texture2DRectProj(tex, vec4(1.0));
})";
    if (IsGLExtensionEnabled("GL_ANGLE_texture_rectangle"))
    {
        validateSuccess(GL_FRAGMENT_SHADER, kFS);
    }
    else
    {
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'GL_ARB_texture_rectangle' : extension is not supported");
    }
}

// Check that if the extension is not supported, trying to use the features fails.
//
// If the extension is supported, test that using the feature with the extension directive passes.
TEST_P(GLSLValidationTextureRectangleTest, NewTypeAndBuiltinsWithExtensionDirective)
{
    constexpr char kFS[] = R"(#extension GL_ARB_texture_rectangle : enable
precision mediump float;
uniform sampler2DRect tex;
void main()
{
    vec4 color = texture2DRect(tex, vec2(1.0));
    color = texture2DRectProj(tex, vec3(1.0));
    color = texture2DRectProj(tex, vec4(1.0));
})";
    if (IsGLExtensionEnabled("GL_ANGLE_texture_rectangle"))
    {
        validateSuccess(GL_FRAGMENT_SHADER, kFS);
    }
    else
    {
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'GL_ARB_texture_rectangle' : extension is not supported");
    }
}

// Check that it is not possible to pass a sampler2DRect where sampler2D is expected, and vice versa
TEST_P(GLSLValidationTextureRectangleTest, Rect2DVs2DMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_texture_rectangle"));

    {
        constexpr char kFS[] = R"(
#extension GL_ARB_texture_rectangle : require
precision mediump float;
uniform sampler2DRect tex;
void main() {
    vec4 color = texture2D(tex, vec2(1.0));"
})";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'texture2D' : no matching overloaded function found");
    }

    {
        constexpr char kFS[] = R"(
#extension GL_ARB_texture_rectangle : require
precision mediump float;
uniform sampler2D tex;
void main() {
    vec4 color = texture2DRect(tex, vec2(1.0));"
})";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'texture2DRect' : no matching overloaded function found");
    }
}

// Disabling ARB_texture_rectangle in GLSL should work, even if it is enabled by default.
// See ARB_texture_rectangle spec: "a shader can still include all variations of #extension
// GL_ARB_texture_rectangle in its source code"
TEST_P(GLSLValidationTextureRectangleTest, DisableARBTextureRectangle)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_texture_rectangle"));

    constexpr char kFS[] = R"(#extension GL_ARB_texture_rectangle : disable
precision mediump float;

uniform sampler2DRect s;
void main()
{})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'GL_ARB_texture_rectangle' : extension is disabled");
}

class GLSLValidationAtomicCounterTest_ES31 : public GLSLValidationTest_ES31
{};

// Test that ESSL 3.00 doesn't support atomic_uint.
TEST_P(GLSLValidationAtomicCounterTest_ES31, InvalidShaderVersion)
{
    constexpr char kFS[] = R"(#version 300 es
layout(binding = 0, offset = 4) uniform atomic_uint a;
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'atomic_uint' : Illegal use of reserved word");
}

// Test that any qualifier other than uniform leads to compile-time error.
TEST_P(GLSLValidationAtomicCounterTest_ES31, InvalidQualifier)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 0, offset = 4) in atomic_uint a;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS, "'atomic_uint' : atomic_uints must be uniform");
}

// Test that uniform must be specified for declaration.
TEST_P(GLSLValidationAtomicCounterTest_ES31, UniformMustSpecifiedForDeclaration)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
atomic_uint a;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS, "'atomic_uint' : atomic_uints must be uniform");
}

// Test that offset overlapping leads to compile-time error (ESSL 3.10 section 4.4.6).
TEST_P(GLSLValidationAtomicCounterTest_ES31, BindingOffsetOverlapping)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 0, offset = 4) uniform atomic_uint a;
layout(binding = 0, offset = 6) uniform atomic_uint b;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS, "'atomic counter' : Offset overlapping");
}

// Test offset inheritance for multiple variables in one same declaration.
TEST_P(GLSLValidationAtomicCounterTest_ES31, MultipleVariablesDeclaration)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 0, offset = 4) uniform atomic_uint a, b;
layout(binding = 0, offset = 8) uniform atomic_uint c;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS, "'atomic counter' : Offset overlapping");
}

// Test that subsequent declarations inherit the globally specified offset.
TEST_P(GLSLValidationAtomicCounterTest_ES31, GlobalBindingOffsetOverlapping)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 2, offset = 4) uniform atomic_uint;
layout(binding = 2) uniform atomic_uint b;
layout(binding = 2, offset = 4) uniform atomic_uint c;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS, "'atomic counter' : Offset overlapping");
}

// The spec only demands offset unique and non-overlapping. So this should be allowed.
TEST_P(GLSLValidationAtomicCounterTest_ES31, DeclarationSequenceWithDecrementalOffsetsSpecified)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 2, offset = 4) uniform atomic_uint a;
layout(binding = 2, offset = 0) uniform atomic_uint b;
void main()
{
})";
    validateSuccess(GL_COMPUTE_SHADER, kCS);
}

// Test that image format qualifiers are not allowed for atomic counters.
TEST_P(GLSLValidationAtomicCounterTest_ES31, ImageFormatMustNotSpecified)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 0, offset = 4, rgba32f) uniform atomic_uint a;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS,
                  "'rgba32f' : invalid layout qualifier: only valid when used with images");
}

// Test that global layout qualifiers must not use 'offset'.
TEST_P(GLSLValidationAtomicCounterTest_ES31, OffsetMustNotSpecifiedForGlobalLayoutQualifier)
{
    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(offset = 4) in;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS,
                  "'offset' : invalid layout qualifier: only valid when used with atomic counters");
}

// Test that offset overlapping leads to compile-time error (ESSL 3.10 section 4.4.6).
// Note that there is some vagueness in the spec when it comes to this test.
TEST_P(GLSLValidationAtomicCounterTest_ES31, BindingOffsetOverlappingForArrays)
{
    GLint maxAtomicCounterBuffers = 0;
    glGetIntegerv(GL_MAX_COMPUTE_ATOMIC_COUNTER_BUFFERS, &maxAtomicCounterBuffers);
    ANGLE_SKIP_TEST_IF(maxAtomicCounterBuffers < 3);

    constexpr char kCS[] = R"(#version 310 es
layout(local_size_x=1) in;
layout(binding = 2, offset = 4) uniform atomic_uint[2] a;
layout(binding = 2, offset = 8) uniform atomic_uint b;
void main()
{
})";
    validateError(GL_COMPUTE_SHADER, kCS, "'atomic counter' : Offset overlapping");
}

class GLSLValidationShaderStorageBlockTest_ES31 : public GLSLValidationTest_ES31
{};

// Test that shader storage block layout qualifiers can be declared for global scope.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, LayoutQualifiersDeclaredInGlobal)
{
    constexpr char kFS[] = R"(#version 310 es
layout(shared, column_major) buffer;
void main()
{
})";
    validateSuccess(GL_FRAGMENT_SHADER, kFS);
}

// Test that it is a compile-time error to declare buffer variables at global scope (outside a
// block).
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, DeclareBufferVariableAtGlobal)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer int a;
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'buffer' : cannot declare buffer variables at global scope(outside a block)");
}

// Test that the buffer variable can't be opaque type.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, BufferVariableWithOpaqueType)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    int b1;
    atomic_uint b2;
};
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'buf' : Opaque types are not allowed in interface blocks");
}

// Test that the uniform variable can't be in shader storage block.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, UniformVariableInShaderStorageBlock)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    uniform int a;
};
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'uniform' : invalid qualifier on shader storage block member");
}

// Test that buffer qualifier is not supported in version lower than GLSL ES 3.10.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, BufferQualifierInESSL3)
{
    constexpr char kFS[] = R"(#version 300 es
layout(binding = 3) buffer buf {
    int b1;
    buffer int b2;
};
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'binding' : invalid layout qualifier: not supported");
}

// Test that can't assign to a readonly buffer variable.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AssignToReadonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    readonly int b1;
};
void main()
{
    b1 = 5;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  R"('assign' : l-value required (can't modify a readonly variable "b1"))");
}

// Test that can't assign to a buffer variable declared within shader storage block with readonly.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AssignToBufferVariableWithinReadonlyBlock)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) readonly buffer buf {
    int b1;
};
void main()
{
    b1 = 5;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  R"('assign' : l-value required (can't modify a readonly variable "b1"))");
}

// Test that can't assign to a readonly buffer variable through an instance name.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AssignToReadonlyBufferVariableByInstanceName)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(binding = 3) buffer buf {
    readonly float f;
} instanceBuffer;
void main()
{
    instanceBuffer.f += 0.2;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'assign' : can't modify a readonly variable");
}

// Test that can't assign to a readonly struct buffer variable.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AssignToReadonlyStructBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
struct S {
    float f;
};
layout(binding = 3) buffer buf {
    readonly S s;
};
void main()
{
    s.f += 0.2;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  R"('assign' : l-value required (can't modify a readonly variable "s"))");
}

// Test that can't assign to a readonly struct buffer variable through an instance name.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31,
       AssignToReadonlyStructBufferVariableByInstanceName)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
struct S {
    float f;
};
layout(binding = 3) buffer buf {
    readonly S s;
} instanceBuffer;
void main()
{
    instanceBuffer.s.f += 0.2;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'assign' : can't modify a readonly variable");
}

// Test that a readonly and writeonly buffer variable should neither read or write.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AccessReadonlyWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    readonly writeonly int b1;
};
void main()
{
    b1 = 5;
    int test = b1;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  R"('assign' : l-value required (can't modify a readonly variable "b1"))");
}

// Test that accessing a writeonly buffer variable should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AccessWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    writeonly int b1;
};
void main()
{
    int test = b1;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'=' : Invalid operation for variables with writeonly");
}

// Test that accessing a buffer variable through an instance name inherits the writeonly qualifier
// and generates errors.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, AccessWriteonlyBufferVariableByInstanceName)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(binding = 3) writeonly buffer buf {
    float f;
} instanceBuffer;
void main()
{
    float test = instanceBuffer.f;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'=' : Invalid operation for variables with writeonly");
}

// Test that writeonly buffer variable as the argument of a unary operator should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, UnaryOperatorWithWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    writeonly int b1;
};
void main()
{
    ++b1;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'++' : wrong operand type - no operation '++' exists that takes an operand of "
                  "type buffer mediump writeonly int (or there is no acceptable conversion)");
}

// Test that writeonly buffer variable on the left-hand side of compound assignment should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, CompoundAssignmentToWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    writeonly int b1;
};
void main()
{
    b1 += 5;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'+=' : Invalid operation for variables with writeonly");
}

// Test that writeonly buffer variable as ternary op argument should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, TernarySelectionWithWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    writeonly bool b1;
};
void main()
{
    int test = b1 ? 1 : 0;
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'?:' : ternary operator is not allowed for variables with writeonly");
}

// Test that writeonly buffer variable as array constructor argument should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, ArrayConstructorWithWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(binding = 3) buffer buf {
    writeonly float f;
};
void main()
{
    float a[3] = float[3](f, f, f);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : cannot convert a variable with writeonly");
}

// Test that writeonly buffer variable as structure constructor argument should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, StructureConstructorWithWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
struct S {
    int a;
};
struct T {
    S b;
};
layout(binding = 3) buffer buf {
    writeonly S c;
};
void main()
{
    T t = T(c);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'constructor' : cannot convert a variable with writeonly");
}

// Test that writeonly buffer variable as built-in function argument should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, BuiltInFunctionWithWriteonlyBufferVariable)
{
    constexpr char kFS[] = R"(#version 310 es
layout(binding = 3) buffer buf {
    writeonly int a;
};
void main()
{
    int test = min(a, 1);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'min' : Writeonly value cannot be passed for 'in' or 'inout' parameters");
}

// Test that writeonly buffer variable as user-defined function in argument should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31,
       UserDefinedFunctionWithWriteonlyBufferVariableInArgument)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(binding = 3) buffer buf {
    writeonly float f;
};
void foo(float a) {}
void main()
{
    foo(f);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "foo' : Writeonly value cannot be passed for 'in' or 'inout' parameters");
}

// Test that readonly buffer variable as user-defined function out argument should be error.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31,
       UserDefinedFunctionWithReadonlyBufferVariableOutArgument)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
layout(binding = 3) buffer buf {
    readonly float f;
};
void foo(out float a) {}
void main()
{
    foo(f);
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  R"('assign' : l-value required (can't modify a readonly variable "f"))");
}

// Test that buffer qualifier can't modify a function parameter.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, BufferQualifierOnFunctionParameter)
{
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
void foo(buffer float a) {}
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'buffer' : only allowed at global scope");
}

// Test that using std430 qualifier on a uniform block will fail to compile.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, UniformBlockWithStd430)
{
    constexpr char kFS[] = R"(#version 310 es
layout(std430) uniform buf {
    int b1;
    int b2;
};
void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'std430' : The std430 layout is supported only for shader storage blocks");
}

// Test that indexing a runtime-sized array with a negative constant index does not compile.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, IndexRuntimeSizedArrayWithNegativeIndex)
{
    constexpr char kFS[] = R"(#version 310 es
layout(std430) buffer buf
{
    int arr[];
};

void main()
{
    arr[-1];
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'[]' : index expression is negative");
}

// Test that only the last member of a buffer can be runtime-sized.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, RuntimeSizedVariableInNotLastInBuffer)
{
    constexpr char kFS[] = R"(#version 310 es
layout(std430) buffer buf
{
    int arr[];
    int i;
};

void main()
{
})";
    validateError(GL_FRAGMENT_SHADER, kFS,
                  "'arr' : array members of interface blocks must specify a size");
}

// Test that memory qualifiers are output.
TEST_P(GLSLValidationShaderStorageBlockTest_ES31, MemoryQualifiers)
{
    constexpr char kFS[]         = R"(#version 310 es
precision highp float;
precision highp int;
layout(std430) coherent buffer buf
{
    int defaultCoherent;
    coherent ivec2 specifiedCoherent;
    volatile ivec3 specifiedVolatile;
    restrict ivec4 specifiedRestrict;
    readonly float specifiedReadOnly;
    writeonly vec2 specifiedWriteOnly;
    volatile readonly vec3 specifiedMultiple;
};

void main()
{
})";
    const CompiledShader &shader = compile(GL_FRAGMENT_SHADER, kFS);
    EXPECT_TRUE(shader.success());
    if (IsOpenGLES())
    {
        // The following are GLSL qualifiers, so only valid with GLSL translation.
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent highp int"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent highp ivec2"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent volatile highp ivec3"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent restrict highp ivec4"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("readonly coherent highp float"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("writeonly coherent highp vec2"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("readonly coherent volatile highp vec3"));
    }
    else if (IsOpenGL())
    {
        // The following are GLSL qualifiers, so only valid with GLSL translation.
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent int"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent ivec2"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent volatile ivec3"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("coherent restrict ivec4"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("readonly coherent float"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("writeonly coherent vec2"));
        EXPECT_TRUE(shader.verifyInTranslatedSource("readonly coherent volatile vec3"));
    }
    reset();
}

class GLSLValidationBaseVertexTest_ES3 : public GLSLValidationTest_ES3
{};

class WebGL2GLSLValidationBaseVertexTest : public WebGL2GLSLValidationTest
{};

// Check that base vertex/instance is not exposed to WebGL.
TEST_P(WebGL2GLSLValidationBaseVertexTest, NoSupport)
{
    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
void main() {
   gl_Position = vec4(float(gl_BaseVertex), float(gl_BaseInstance), 0.0, 1.0);
})";
    validateError(
        GL_VERTEX_SHADER, kVS,
        "'GL_ANGLE_base_vertex_base_instance_shader_builtin' : extension is not supported");
}

// Check that compiling with the old extension doesn't work
TEST_P(GLSLValidationBaseVertexTest_ES3, CheckCompileOldExtension)
{
    constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance : require
void main() {
   gl_Position = vec4(float(gl_BaseVertex), float(gl_BaseInstance), 0.0, 1.0);
})";
    validateError(GL_VERTEX_SHADER, kVS,
                  "'GL_ANGLE_base_vertex_base_instance' : extension is not supported");
}

// Check that a user-defined "gl_BaseVertex" or "gl_BaseInstance" is not permitted
TEST_P(GLSLValidationBaseVertexTest_ES3, DisallowsUserDefinedGLDrawID)
{
    {
        // Check that it is not permitted without the
        // GL_ANGLE_base_vertex_base_instance_shader_builtin extension
        constexpr char kVS[] = R"(#version 300 es
uniform int gl_BaseVertex;
void main() {
   gl_Position = vec4(float(gl_BaseVertex), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseVertex' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
uniform int gl_BaseInstance;
void main() {
   gl_Position = vec4(float(gl_BaseInstance), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseInstance' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
void main() {
   int gl_BaseVertex = 0;
   gl_Position = vec4(float(gl_BaseVertex), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseVertex' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
void main() {
   int gl_BaseInstance = 0;
   gl_Position = vec4(float(gl_BaseInstance), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseInstance' : reserved built-in name");
    }

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_base_vertex_base_instance_shader_builtin"));

    {
        // Check that it is not permitted with the extension
        constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
uniform int gl_BaseVertex;
void main() {
   gl_Position = vec4(float(gl_BaseVertex), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseVertex' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
uniform int gl_BaseInstance;
void main() {
   gl_Position = vec4(float(gl_BaseInstance), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseInstance' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
void main() {
   int gl_BaseVertex = 0;
   gl_Position = vec4(float(gl_BaseVertex), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseVertex' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#version 300 es
#extension GL_ANGLE_base_vertex_base_instance_shader_builtin : require
void main() {
   int gl_BaseInstance = 0;
   gl_Position = vec4(float(gl_BaseInstance), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_BaseInstance' : reserved built-in name");
    }
}

class GLSLValidationDrawIDTest : public GLSLValidationTest
{};

// Check that a user-defined "gl_DrawID" is not permitted
TEST_P(GLSLValidationDrawIDTest, DisallowsUserDefinedGLDrawID)
{
    {
        // Check that it is not permitted without the GL_ANGLE_multi_draw extension
        constexpr char kVS[] = R"(uniform int gl_DrawID;
void main() {
   gl_Position = vec4(float(gl_DrawID), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_DrawID' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(void main() {
   int gl_DrawID = 0;
   gl_Position = vec4(float(gl_DrawID), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_DrawID' : reserved built-in name");
    }

    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_ANGLE_multi_draw"));

    {
        // Check that it is not permitted with the extension
        constexpr char kVS[] = R"(#extension GL_ANGLE_multi_draw : require
uniform int gl_DrawID;
void main() {
   gl_Position = vec4(float(gl_DrawID), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_DrawID' : reserved built-in name");
    }

    {
        constexpr char kVS[] = R"(#extension GL_ANGLE_multi_draw : require
void main() {
   int gl_DrawID = 0;
   gl_Position = vec4(float(gl_DrawID), 0.0, 0.0, 1.0);
})";
        validateError(GL_VERTEX_SHADER, kVS, "'gl_DrawID' : reserved built-in name");
    }
}

class GLSLValidationExtensionDirectiveTest_ES3 : public GLSLValidationTest_ES3
{
  public:
    void testCompileNeedsExtensionDirective(GLenum shaderType,
                                            const char *shaderSource,
                                            const char *version,
                                            const char *extension,
                                            bool isExtensionSupported,
                                            const char *expectWithoutPragma,
                                            const char *expectWithExtDisabled)
    {
        testCompileNeedsExtensionDirectiveImpl(shaderType, shaderSource, version, extension,
                                               isExtensionSupported, true, expectWithoutPragma,
                                               expectWithExtDisabled);
    }

    void testCompileNeedsExtensionDirectiveGenericKeyword(GLenum shaderType,
                                                          const char *shaderSource,
                                                          const char *version,
                                                          const char *extension,
                                                          bool isExtensionSupported,
                                                          const char *expect)
    {
        testCompileNeedsExtensionDirectiveImpl(shaderType, shaderSource, version, extension,
                                               isExtensionSupported, false, expect, expect);
    }

    void testCompileNeedsExtensionDirectiveImpl(GLenum shaderType,
                                                const char *shaderSource,
                                                const char *version,
                                                const char *extension,
                                                bool isExtensionSupported,
                                                bool willWarnOnUse,
                                                const char *expectWithoutPragma,
                                                const char *expectWithExtDisabled)
    {
        {
            std::stringstream src;
            if (version)
            {
                src << version << "\n";
            }
            src << shaderSource;
            const CompiledShader &shader = compile(shaderType, src.str().c_str());
            EXPECT_FALSE(shader.success());
            EXPECT_TRUE(shader.hasInfoLog(expectWithoutPragma));
            reset();
        }

        {
            std::stringstream src;
            if (version)
            {
                src << version << "\n";
            }
            src << "#extension " << extension << ": disable\n" << shaderSource;
            const CompiledShader &shader = compile(shaderType, src.str().c_str());
            EXPECT_FALSE(shader.success());
            EXPECT_TRUE(shader.hasInfoLog(expectWithExtDisabled));
            reset();
        }

        {
            std::stringstream src;
            if (version)
            {
                src << version << "\n";
            }
            src << "#extension " << extension << ": enable\n" << shaderSource;
            if (isExtensionSupported)
            {
                EXPECT_TRUE(compile(shaderType, src.str().c_str()).success());
            }
            else
            {
                const CompiledShader &shader = compile(shaderType, src.str().c_str());
                EXPECT_FALSE(shader.success());
                EXPECT_TRUE(shader.hasInfoLog("extension is not supported"));
            }
            reset();
        }

        // The Nvidia/GLES driver doesn't treat warn like enable and gives an error, declaring that
        // using a token from the extension needs `#extension EXT: enable`.  Don't run these tests
        // on that config.
        const bool driverMishandlesWarn = IsOpenGLES() && IsNVIDIA();

        if (!driverMishandlesWarn)
        {
            std::stringstream src;
            if (version)
            {
                src << version << "\n";
            }
            src << "#extension " << extension << ": warn\n" << shaderSource;
            const CompiledShader &shader = compile(shaderType, src.str().c_str());
            if (!isExtensionSupported)
            {
                EXPECT_FALSE(shader.success());
                EXPECT_TRUE(shader.hasInfoLog("extension is not supported"));
            }
            else
            {
                EXPECT_TRUE(shader.success());
                if (willWarnOnUse)
                {
                    EXPECT_TRUE(shader.hasInfoLog("WARNING"));
                    EXPECT_TRUE(shader.hasInfoLog("extension is being used"));
                }
            }
            reset();
        }
    }
};

class GLSLValidationExtensionDirectiveTest_ES31 : public GLSLValidationExtensionDirectiveTest_ES3
{};

// OES_EGL_image_external needs to be enabled in GLSL to be able to use samplerExternalOES.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SamplerExternalOESWithImage)
{
    const bool hasExt    = IsGLExtensionEnabled("GL_OES_EGL_image_external");
    const bool hasAnyExt = hasExt || IsGLExtensionEnabled("GL_NV_EGL_stream_consumer_external");

    constexpr char kFS[] = R"(precision mediump float;
uniform samplerExternalOES s;
void main()
{})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, nullptr, "GL_OES_EGL_image_external", hasExt,
        hasAnyExt ? "extension is disabled" : "extension is not supported",
        hasAnyExt ? "extension is disabled" : "extension is not supported");
}

// NV_EGL_stream_consumer_external needs to be enabled in GLSL to be able to use samplerExternalOES.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SamplerExternalOESWithStreamConstumer)
{
    const bool hasExt    = IsGLExtensionEnabled("GL_NV_EGL_stream_consumer_external");
    const bool hasAnyExt = hasExt || IsGLExtensionEnabled("GL_OES_EGL_image_external");

    constexpr char kFS[] = R"(precision mediump float;
uniform samplerExternalOES s;
void main()
{})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, nullptr, "GL_NV_EGL_stream_consumer_external", hasExt,
        hasAnyExt ? "extension is disabled" : "extension is not supported",
        hasAnyExt ? "extension is disabled" : "extension is not supported");
}

// GL_EXT_YUV_target needs to be enabled in GLSL to be able to use samplerExternal2DY2YEXT.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SamplerExternal2DY2YEXT)
{
    const bool hasExt = IsGLExtensionEnabled("GL_EXT_YUV_target");

    constexpr char kFS[] = R"(precision mediump float;
uniform __samplerExternal2DY2YEXT s;
void main()
{})";
    // __samplerExternal2DY2YEXT is not a reserved keyword, and the translator fails with syntax
    // error if extension is not specified.
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, "#version 300 es", "GL_EXT_YUV_target", hasExt,
        "'s' : syntax error", hasExt ? "'s' : syntax error" : "extension is not supported");
}

// GL_EXT_YUV_target needs to be enabled in GLSL to be able to use layout(yuv).
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, YUVLayoutNeedsExtensionDirective)
{
    const bool hasExt = IsGLExtensionEnabled("GL_EXT_YUV_target");

    constexpr char kFS[] = R"(precision mediump float;
layout(yuv) out vec4 color;
void main()
{})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, "#version 300 es", "GL_EXT_YUV_target", hasExt,
        hasExt ? "extension is disabled" : "extension is not supported",
        hasExt ? "extension is disabled" : "extension is not supported");
}

// GL_EXT_blend_func_extended needs to be enabled in GLSL to be able to use
// gl_MaxDualSourceDrawBuffersEXT.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, MaxDualSourceDrawBuffersNeedsExtensionDirective)
{
    const bool hasExt = IsGLExtensionEnabled("GL_EXT_blend_func_extended");

    {
        constexpr char kFS[] = R"(precision mediump float;
void main() {
    gl_FragColor = vec4(gl_MaxDualSourceDrawBuffersEXT / 10);
})";
        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, nullptr, "GL_EXT_blend_func_extended", hasExt,
            hasExt ? "extension is disabled"
                   : "'gl_MaxDualSourceDrawBuffersEXT' : undeclared identifier",
            hasExt ? "extension is disabled"
                   : "'gl_MaxDualSourceDrawBuffersEXT' : undeclared identifier");
    }

    {
        constexpr char kFS[] = R"(precision mediump float;
layout(location = 0) out mediump vec4 fragColor;
void main() {
    fragColor = vec4(gl_MaxDualSourceDrawBuffersEXT / 10);
})";
        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, "#version 300 es", "GL_EXT_blend_func_extended", hasExt,
            hasExt ? "extension is disabled"
                   : "'gl_MaxDualSourceDrawBuffersEXT' : undeclared identifier",
            hasExt ? "extension is disabled"
                   : "'gl_MaxDualSourceDrawBuffersEXT' : undeclared identifier");
    }
}

// GL_EXT_clip_cull_distance or GL_ANGLE_clip_cull_distance needs to be enabled in GLSL to be able
// to use gl_ClipDistance and gl_CullDistance.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, ClipCullDistanceNeedsExtensionDirective)
{
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");

    GLint maxClipDistances = 0;
    GLint maxCullDistances = 0;
    if (hasExt || hasAngle)
    {
        glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
        EXPECT_GE(maxClipDistances, 8);

        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        EXPECT_TRUE(maxCullDistances == 0 || maxCullDistances >= 8);
        if (hasExt)
        {
            EXPECT_GE(maxCullDistances, 8);
        }
    }

    constexpr char kVS1[] = R"(uniform vec4 uPlane;
in vec4 aPosition;

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[0] = dot(aPosition, uPlane);
    gl_CullDistance[0] = dot(aPosition, uPlane);
})";

    constexpr char kVS2[] = R"(uniform vec4 uPlane;
in vec4 aPosition;

out highp float gl_ClipDistance[4];
out highp float gl_CullDistance[4];

void main()
{
    gl_Position = aPosition;
    gl_ClipDistance[gl_MaxClipDistances - 6 + 1] = dot(aPosition, uPlane);
    gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)] = dot(aPosition, uPlane);
    gl_ClipDistance[gl_MaxCombinedClipAndCullDistances / 4 - 1] = dot(aPosition, uPlane);
    gl_CullDistance[gl_MaxCullDistances - 6 + 1] = dot(aPosition, uPlane);
    gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)] = dot(aPosition, uPlane);
})";

    // Shader using gl_ClipDistance and gl_CullDistance
    constexpr char kFS1[] = R"(out highp vec4 fragColor;
void main()
{
    fragColor = vec4(gl_ClipDistance[0], gl_CullDistance[0], 0, 1);
})";

    // Shader redeclares gl_ClipDistance and gl_CullDistance
    constexpr char kFS2[] = R"(in highp float gl_ClipDistance[4];
in highp float gl_CullDistance[4];
in highp vec4 aPosition;

out highp vec4 fragColor;

void main()
{
    fragColor.x = gl_ClipDistance[gl_MaxClipDistances - 6 + 1];
    fragColor.y = gl_ClipDistance[gl_MaxClipDistances - int(aPosition.x)];
    fragColor.z = gl_CullDistance[gl_MaxCullDistances - 6 + 1];
    fragColor.w = gl_CullDistance[gl_MaxCullDistances - int(aPosition.x)];
    fragColor *= gl_CullDistance[gl_MaxCombinedClipAndCullDistances / 4 - 1];
})";

    if (hasExt)
    {
        const char *expectWithoutPragma =
            hasExt ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasExt ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_VERTEX_SHADER, kVS1, "#version 300 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_VERTEX_SHADER, kVS2, "#version 300 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_FRAGMENT_SHADER, kFS1, "#version 300 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_FRAGMENT_SHADER, kFS2, "#version 300 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
    }

    if (hasAngle && maxCullDistances > 0)
    {
        const char *expectWithoutPragma =
            hasAngle ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasAngle ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_VERTEX_SHADER, kVS1, "#version 300 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_VERTEX_SHADER, kVS2, "#version 300 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_FRAGMENT_SHADER, kFS1, "#version 300 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_FRAGMENT_SHADER, kFS2, "#version 300 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
    }
}

// GL_EXT_frag_depth needs to be enabled in GLSL 100 to be able to use gl_FragDepthEXT.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, FragDepth)
{
    const bool hasExt = IsGLExtensionEnabled("GL_EXT_frag_depth");

    constexpr char kFS[] = R"(precision mediump float;
void main()
{
    gl_FragDepthEXT = 1.0;
})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, nullptr, "GL_EXT_frag_depth", hasExt,
        hasExt ? "extension is disabled" : "'gl_FragDepthEXT' : undeclared identifier",
        hasExt ? "extension is disabled" : "extension is not supported");
}

// The GLES SL 3.0 built-in variable gl_FragDepth fails to compile with GLES SL 1.0.
TEST_P(GLSLValidationTest, FragDepthFailsESSL100)
{
    constexpr char kFS[] = R"(precision mediump float;
void main() {
    gl_FragDepth = 1.0;
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "'gl_FragDepth' : undeclared identifier");

    // Even with GL_EXT_frag_depth extension enabled, gl_FragDepth (ES3 built-in) should fail in
    // ESSL 100. Note: The extension provides gl_FragDepthEXT, not gl_FragDepth.
    if (IsGLExtensionEnabled("GL_EXT_frag_depth"))
    {
        constexpr char kFSWithExt[] = R"(#extension GL_EXT_frag_depth : enable
precision mediump float;
void main() {
    gl_FragDepth = 1.0;
})";
        validateError(GL_FRAGMENT_SHADER, kFSWithExt, "'gl_FragDepth' : undeclared identifier");
    }
}

// Using #extension GL_EXT_frag_depth in GLSL ES 3.0 shader fails to compile.
TEST_P(GLSLValidationTest_ES3, FragDepthExtensionFailsESSL300)
{
    constexpr char kFS[] = R"(#version 300 es
#extension GL_EXT_frag_depth : require
precision mediump float;
out vec4 fragColor;
void main() {
    fragColor = vec4(1.0);
})";
    validateError(GL_FRAGMENT_SHADER, kFS, "extension is not supported");
}

// GL_EXT_shader_framebuffer_fetch or GL_EXT_shader_framebuffer_fetch_non_coherent needs to be
// enabled in GLSL 100 to be able to use gl_LastFragData and in GLSL 300+ to use inout.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, LastFragData)
{
    const bool hasCoherent = IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch");
    const bool hasNonCoherent =
        IsGLExtensionEnabled("GL_EXT_shader_framebuffer_fetch_non_coherent");

    constexpr char kFS100Coherent[] = R"(
uniform highp vec4 u_color;
highp vec4 gl_LastFragData[gl_MaxDrawBuffers];

void main (void)
{
    gl_FragColor = u_color + gl_LastFragData[0] + gl_LastFragData[2];
})";

    constexpr char kFS300Coherent[] = R"(
inout highp vec4 o_color;
uniform highp vec4 u_color;

void main (void)
{
    o_color = clamp(o_color + u_color, vec4(0.0f), vec4(1.0f));
})";

    constexpr char kFS100NonCoherent[] = R"(
uniform highp vec4 u_color;
layout(noncoherent) highp vec4 gl_LastFragData[gl_MaxDrawBuffers];

void main (void)
{
    gl_FragColor = u_color + gl_LastFragData[0] + gl_LastFragData[2];
})";

    constexpr char kFS300NonCoherent[] = R"(
layout(noncoherent, location = 0) inout highp vec4 o_color;
uniform highp vec4 u_color;

void main (void)
{
    o_color = clamp(o_color + u_color, vec4(0.0f), vec4(1.0f));
})";

    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS100Coherent, nullptr, "GL_EXT_shader_framebuffer_fetch", hasCoherent,
        hasCoherent ? hasNonCoherent ? "extension is disabled" : "extension is not supported"
                    : "'gl_LastFragData' : reserved built-in name",
        hasCoherent ? hasNonCoherent ? "extension is disabled" : "extension is not supported"
                    : "extension is not supported");
    testCompileNeedsExtensionDirectiveGenericKeyword(
        GL_FRAGMENT_SHADER, kFS300Coherent, "#version 300 es", "GL_EXT_shader_framebuffer_fetch",
        hasCoherent, "'inout' : invalid qualifier");

    testCompileNeedsExtensionDirectiveGenericKeyword(GL_FRAGMENT_SHADER, kFS100NonCoherent, nullptr,
                                                     "GL_EXT_shader_framebuffer_fetch_non_coherent",
                                                     hasNonCoherent, "'layout' : syntax error");
    testCompileNeedsExtensionDirectiveGenericKeyword(GL_FRAGMENT_SHADER, kFS300NonCoherent,
                                                     "#version 300 es",
                                                     "GL_EXT_shader_framebuffer_fetch_non_coherent",
                                                     hasNonCoherent, "'inout' : invalid qualifier");
}

// GL_EXT_shader_texture_lod needs to be enabled to be able to use texture2DLodEXT.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, Texture2DLod)
{
    const bool hasExt = IsGLExtensionEnabled("GL_EXT_shader_texture_lod");

    constexpr char kFS[] = R"(precision mediump float;
varying vec2 texCoord0v;
uniform float lod;
uniform sampler2D tex;
void main()
{
    vec4 color = texture2DLodEXT(tex, texCoord0v, lod);
})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, nullptr, "GL_EXT_shader_texture_lod", hasExt,
        hasExt ? "extension is disabled"
               : "'texture2DLodEXT' : no matching overloaded function found",
        hasExt ? "extension is disabled" : "extension is not supported");
}

// GL_EXT_shadow_samplers needs to be enabled to be able to use shadow2DEXT.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, Sampler2DShadow)
{
    const bool hasExt = IsGLExtensionEnabled("GL_EXT_shadow_samplers");

    constexpr char kFS[] = R"(precision mediump float;
varying vec3 texCoord0v;
uniform mediump sampler2DShadow tex;
void main()
{
    float color = shadow2DEXT(tex, texCoord0v);
})";
    testCompileNeedsExtensionDirective(GL_FRAGMENT_SHADER, kFS, nullptr, "GL_EXT_shadow_samplers",
                                       hasExt, "'sampler2DShadow' : Illegal use of reserved word",
                                       "'sampler2DShadow' : Illegal use of reserved word");
}

// GL_KHR_blend_equation_advanced needs to be enabled to be able to use layout(blend_support_*).
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, AdvancedBlendSupport)
{
    const bool hasExt = IsGLExtensionEnabled("GL_KHR_blend_equation_advanced");
    const bool hasAnyExt =
        hasExt || IsGLExtensionEnabled("GL_KHR_blend_equation_advanced_coherent");

    constexpr char kFS[] = R"(precision highp float;
layout (blend_support_multiply) out;
layout (location = 0) out vec4 oCol;

uniform vec4 uSrcCol;

void main (void)
{
    oCol = uSrcCol;
})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, "#version 300 es", "GL_KHR_blend_equation_advanced", hasExt,
        hasAnyExt ? "extension is disabled" : "extension is not supported",
        hasAnyExt ? "extension is disabled" : "extension is not supported");
}

// GL_OES_sample_variables needs to be enabled to be able to use gl_SampleMask.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SampleMask)
{
    const bool hasExt = IsGLExtensionEnabled("GL_OES_sample_variables");

    // This shader is in the deqp test
    // functional_shaders_sample_variables_sample_mask_discard_half_per_sample_default_framebuffer
    constexpr char kFS[] = R"(layout(location = 0) out mediump vec4 fragColor;
void main (void)
{
    for (int i = 0; i < gl_SampleMask.length(); ++i)
            gl_SampleMask[i] = int(0xAAAAAAAA);

    // force per-sample shading
    highp float blue = float(gl_SampleID);

    fragColor = vec4(0.0, 1.0, blue, 1.0);
})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, "#version 300 es", "GL_OES_sample_variables", hasExt,
        hasExt ? "extension is disabled" : "'gl_SampleMask' : undeclared identifier",
        hasExt ? "extension is disabled" : "extension is not supported");
}

// GL_OES_sample_variables needs to be enabled to be able to use gl_SampleMaskIn.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SampleMaskIn)
{
    const bool hasExt = IsGLExtensionEnabled("GL_OES_sample_variables");

    // This shader is in the deqp test
    // functional_shaders_sample_variables_sample_mask_in_bit_count_per_sample_multisample_texture_2
    constexpr char kFS[] = R"(layout(location = 0) out mediump vec4 fragColor;
void main (void)
{
    mediump int maskBitCount = 0;
    for (int j = 0; j < gl_SampleMaskIn.length(); ++j)
    {
        for (int i = 0; i < 32; ++i)
        {
            if (((gl_SampleMaskIn[j] >> i) & 0x01) == 0x01)
            {
                ++maskBitCount;
            }
        }
    }

    // force per-sample shading
    highp float blue = float(gl_SampleID);

    if (maskBitCount != 1)
        fragColor = vec4(1.0, 0.0, blue, 1.0);
    else
        fragColor = vec4(0.0, 1.0, blue, 1.0);
})";
    testCompileNeedsExtensionDirective(
        GL_FRAGMENT_SHADER, kFS, "#version 300 es", "GL_OES_sample_variables", hasExt,
        hasExt ? "extension is disabled" : "'gl_SampleMaskIn' : undeclared identifier",
        hasExt ? "extension is disabled" : "extension is not supported");
}

// GL_OES_standard_derivatives needs to be enabled to be able to use dFdx, dFdy and fwidth.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, StandardDerivatives)
{
    const bool hasExt = IsGLExtensionEnabled("GL_OES_standard_derivatives");

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

void main()
{
    gl_FragColor = vec4(dFdx(x));
})";
        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, nullptr, "GL_OES_standard_derivatives", hasExt,
            hasExt ? "extension is disabled" : "extension is not supported",
            hasExt ? "extension is disabled" : "extension is not supported");
    }

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

void main()
{
    gl_FragColor = vec4(dFdy(x));
})";
        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, nullptr, "GL_OES_standard_derivatives", hasExt,
            hasExt ? "extension is disabled" : "extension is not supported",
            hasExt ? "extension is disabled" : "extension is not supported");
    }

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

void main()
{
    gl_FragColor = vec4(fwidth(x));
})";
        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, nullptr, "GL_OES_standard_derivatives", hasExt,
            hasExt ? "extension is disabled" : "extension is not supported",
            hasExt ? "extension is disabled" : "extension is not supported");
    }
}

// GL_OES_texture_cube_map_array or GL_EXT_texture_cube_map_array needs to be enabled to be able to
// use *samplerCubeArray.
TEST_P(GLSLValidationExtensionDirectiveTest_ES31, TextureCubeMapArray)
{
    const bool hasExt    = IsGLExtensionEnabled("GL_EXT_texture_cube_map_array");
    const bool hasOes    = IsGLExtensionEnabled("GL_OES_texture_cube_map_array");
    const bool hasAnyExt = hasExt || hasOes;

    {
        constexpr char kFS[] = R"(precision mediump float;
uniform highp isamplerCubeArray u_sampler;
void main()
{
    vec4 color = vec4(texture(u_sampler, vec4(0, 0, 0, 0)));
})";

        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, "#version 310 es", "GL_EXT_texture_cube_map_array", hasExt,
            "'isamplerCubeArray' : Illegal use of reserved word",
            hasAnyExt ? "'isamplerCubeArray' : Illegal use of reserved word"
                      : "extension is not supported");
        testCompileNeedsExtensionDirective(
            GL_FRAGMENT_SHADER, kFS, "#version 310 es", "GL_OES_texture_cube_map_array", hasOes,
            "'isamplerCubeArray' : Illegal use of reserved word",
            hasAnyExt ? "'isamplerCubeArray' : Illegal use of reserved word"
                      : "extension is not supported");
    }

    // Make sure support for EXT or OES doesn't imply support for the other.
    if (hasExt && !hasOes)
    {
        constexpr char kFS[] = R"(#version 310 es
#extension GL_OES_texture_cube_map_array: enable
precision mediump float;
uniform highp isamplerCubeArray u_sampler;
void main()
{
    vec4 color = vec4(texture(u_sampler, vec4(0, 0, 0, 0)));
})";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'isamplerCubeArray' : Illegal use of reserved word");
    }
    if (!hasExt && hasOes)
    {
        constexpr char kFS[] = R"(#version 310 es
#extension GL_EXT_texture_cube_map_array: enable
precision mediump float;
uniform highp isamplerCubeArray u_sampler;
void main()
{
    vec4 color = vec4(texture(u_sampler, vec4(0, 0, 0, 0)));
})";
        validateError(GL_FRAGMENT_SHADER, kFS,
                      "'isamplerCubeArray' : Illegal use of reserved word");
    }
}

class GLSLValidationExtensionDirectiveTestClipCull_ES31
    : public GLSLValidationExtensionDirectiveTest_ES31
{};

// GL_EXT_clip_cull_distance or GL_ANGLE_clip_cull_distance needs to be enabled in GLSL to be able
// to use gl_ClipDistance and gl_CullDistance in Geometry Shaders.
TEST_P(GLSLValidationExtensionDirectiveTestClipCull_ES31, GeometryShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");

    GLint maxClipDistances = 0;
    GLint maxCullDistances = 0;
    if (hasExt || hasAngle)
    {
        glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
        EXPECT_GE(maxClipDistances, 8);

        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        EXPECT_TRUE(maxCullDistances == 0 || maxCullDistances >= 8);
        if (hasExt)
        {
            EXPECT_GE(maxCullDistances, 8);
        }
    }

    constexpr char kGS[] = R"(#extension GL_EXT_geometry_shader : require
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in gl_PerVertex {
    highp vec4 gl_Position;
    highp float gl_ClipDistance[4];
    highp float gl_CullDistance[4];
} gl_in[];
out gl_PerVertex {
    highp vec4 gl_Position;
    highp float gl_ClipDistance[4];
    highp float gl_CullDistance[4];
};
void main()
{
    for (int i = 0; i < 3; ++i)
    {
        gl_Position = gl_in[i].gl_Position;
        for (int j = 0; j < 4; ++j)
        {
            gl_ClipDistance[j] = gl_in[i].gl_ClipDistance[j];
            gl_CullDistance[j] = gl_in[i].gl_CullDistance[j];
        }
        EmitVertex();
    }
    EndPrimitive();
})";

    {
        const char *expectWithoutPragma =
            hasExt ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasExt ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_GEOMETRY_SHADER, kGS, "#version 310 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
    }

    if (maxCullDistances > 0)
    {
        const char *expectWithoutPragma =
            hasAngle ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasAngle ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_GEOMETRY_SHADER, kGS, "#version 310 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
    }
}

// GL_EXT/OES_geometry_point_size needs to be enabled in GLSL to be able
// to use gl_PointSize in Geometry Shaders.
TEST_P(GLSLValidationExtensionDirectiveTestClipCull_ES31, GeometryShaderPointSize)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_geometry_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));
    const bool hasPointSizeEXT = IsGLExtensionEnabled("GL_EXT_geometry_point_size");
    const bool hasPointSizeOES = IsGLExtensionEnabled("GL_OES_geometry_point_size");

    GLint maxClipDistances = 0;
    GLint maxCullDistances = 0;

    glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
    EXPECT_GE(maxClipDistances, 8);

    glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
    EXPECT_GE(maxCullDistances, 8);

    constexpr char kGS[] = R"(#extension GL_EXT_geometry_shader : require
#extension GL_EXT_clip_cull_distance : require
layout (triangles) in;
layout (triangle_strip, max_vertices = 3) out;
in gl_PerVertex {
    highp vec4 gl_Position;
    highp float gl_PointSize;
    highp float gl_ClipDistance[4];
} gl_in[];
out gl_PerVertex {
    highp vec4 gl_Position;
    highp float gl_PointSize;
    highp float gl_ClipDistance[4];
};
void main()
{
    for (int i = 0; i < 3; ++i)
    {
        gl_Position = gl_in[i].gl_Position;
        gl_PointSize = gl_in[i].gl_PointSize;
        for (int j = 0; j < 4; ++j)
        {
            gl_ClipDistance[j] = gl_in[i].gl_ClipDistance[j];
        }
        EmitVertex();
    }
    EndPrimitive();
})";

    {
        const char *expectWithoutPragma =
            hasPointSizeEXT ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasPointSizeEXT ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_GEOMETRY_SHADER, kGS, "#version 310 es",
                                           "GL_EXT_geometry_point_size", hasPointSizeEXT,
                                           expectWithoutPragma, expectWithExtDisabled);
    }

    {
        const char *expectWithoutPragma =
            hasPointSizeOES ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasPointSizeOES ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_GEOMETRY_SHADER, kGS, "#version 310 es",
                                           "GL_OES_geometry_point_size", hasPointSizeOES,
                                           expectWithoutPragma, expectWithExtDisabled);
    }
}

// GL_EXT_clip_cull_distance or GL_ANGLE_clip_cull_distance needs to be enabled in GLSL to be able
// to use gl_ClipDistance and gl_CullDistance in Tessellation Shaders.
TEST_P(GLSLValidationExtensionDirectiveTestClipCull_ES31, TessellationShader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    const bool hasExt   = IsGLExtensionEnabled("GL_EXT_clip_cull_distance");
    const bool hasAngle = IsGLExtensionEnabled("GL_ANGLE_clip_cull_distance");

    GLint maxClipDistances = 0;
    GLint maxCullDistances = 0;
    if (hasExt || hasAngle)
    {
        glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
        EXPECT_GE(maxClipDistances, 8);

        glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
        EXPECT_TRUE(maxCullDistances == 0 || maxCullDistances >= 8);
        if (hasExt)
        {
            EXPECT_GE(maxCullDistances, 8);
        }
    }

    constexpr char kTCS[] = R"(#extension GL_EXT_tessellation_shader : require
layout (vertices = 3) out;
in gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
} gl_in[];
out gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
} gl_out[];
void main()
{
    gl_out[gl_InvocationID].gl_ClipDistance[0] = gl_in[gl_InvocationID].gl_ClipDistance[0];
    gl_out[gl_InvocationID].gl_CullDistance[0] = gl_in[gl_InvocationID].gl_CullDistance[0];
    gl_out[gl_InvocationID].gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    if (gl_InvocationID == 0)
    {
        gl_TessLevelOuter[0] = 2.0;
        gl_TessLevelOuter[1] = 2.0;
        gl_TessLevelOuter[2] = 2.0;
        gl_TessLevelInner[0] = 2.0;
    }
})";

    constexpr char kTES[] = R"(#extension GL_EXT_tessellation_shader : require
layout(triangles, equal_spacing, ccw) in;
in gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
} gl_in[];
out gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
};
void main()
{
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    gl_ClipDistance[0] = gl_in[0].gl_ClipDistance[0];
    gl_CullDistance[0] = gl_in[0].gl_CullDistance[0];
})";

    {
        const char *expectWithoutPragma =
            hasExt ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasExt ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_TESS_CONTROL_SHADER, kTCS, "#version 310 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_TESS_EVALUATION_SHADER, kTES, "#version 310 es",
                                           "GL_EXT_clip_cull_distance", hasExt, expectWithoutPragma,
                                           expectWithExtDisabled);
    }

    if (maxCullDistances > 0)
    {
        const char *expectWithoutPragma =
            hasAngle ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasAngle ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_TESS_CONTROL_SHADER, kTCS, "#version 310 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_TESS_EVALUATION_SHADER, kTES, "#version 310 es",
                                           "GL_ANGLE_clip_cull_distance", hasAngle,
                                           expectWithoutPragma, expectWithExtDisabled);
    }
}

// GL_EXT/OES_tessellation_point_size needs to be enabled in GLSL to be able
// to use gl_PointSize in Tessellation Shaders.
TEST_P(GLSLValidationExtensionDirectiveTestClipCull_ES31, TessellationShaderPointSize)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_tessellation_shader"));
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_clip_cull_distance"));
    const bool hasPointSizeEXT = IsGLExtensionEnabled("GL_EXT_tessellation_point_size");
    const bool hasPointSizeOES = IsGLExtensionEnabled("GL_OES_tessellation_point_size");

    GLint maxClipDistances = 0;
    GLint maxCullDistances = 0;

    glGetIntegerv(GL_MAX_CLIP_DISTANCES_EXT, &maxClipDistances);
    EXPECT_GE(maxClipDistances, 8);

    glGetIntegerv(GL_MAX_CULL_DISTANCES_EXT, &maxCullDistances);
    EXPECT_GE(maxCullDistances, 8);

    constexpr char kTCS[] = R"(#extension GL_EXT_tessellation_shader : require
#extension GL_EXT_clip_cull_distance : require
layout (vertices = 3) out;
in gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_PointSize;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
} gl_in[];
out gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_PointSize;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
} gl_out[];
void main()
{
    gl_out[gl_InvocationID].gl_ClipDistance[0] = gl_in[gl_InvocationID].gl_ClipDistance[0];
    gl_out[gl_InvocationID].gl_CullDistance[0] = gl_in[gl_InvocationID].gl_CullDistance[0];
    gl_out[gl_InvocationID].gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    gl_out[gl_InvocationID].gl_PointSize = gl_in[gl_InvocationID].gl_PointSize;
    if (gl_InvocationID == 0)
    {
        gl_TessLevelOuter[0] = 2.0;
        gl_TessLevelOuter[1] = 2.0;
        gl_TessLevelOuter[2] = 2.0;
        gl_TessLevelInner[0] = 2.0;
    }
})";

    constexpr char kTES[] = R"(#extension GL_EXT_tessellation_shader : require
#extension GL_EXT_clip_cull_distance : require
layout(triangles, equal_spacing, ccw) in;
in gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_PointSize;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
} gl_in[];
out gl_PerVertex
{
    highp vec4 gl_Position;
    highp float gl_PointSize;
    highp float gl_ClipDistance[1];
    highp float gl_CullDistance[1];
};
void main()
{
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
    gl_PointSize = gl_in[0].gl_PointSize;
    gl_ClipDistance[0] = gl_in[0].gl_ClipDistance[0];
    gl_CullDistance[0] = gl_in[0].gl_CullDistance[0];
})";

    {
        const char *expectWithoutPragma =
            hasPointSizeEXT ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasPointSizeEXT ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_TESS_CONTROL_SHADER, kTCS, "#version 310 es",
                                           "GL_EXT_tessellation_point_size", hasPointSizeEXT,
                                           expectWithoutPragma, expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_TESS_EVALUATION_SHADER, kTES, "#version 310 es",
                                           "GL_EXT_tessellation_point_size", hasPointSizeEXT,
                                           expectWithoutPragma, expectWithExtDisabled);
    }

    {
        const char *expectWithoutPragma =
            hasPointSizeOES ? "extension is disabled" : "extension is not supported";
        const char *expectWithExtDisabled =
            hasPointSizeOES ? "extension is disabled" : "extension is not supported";

        testCompileNeedsExtensionDirective(GL_TESS_CONTROL_SHADER, kTCS, "#version 310 es",
                                           "GL_OES_tessellation_point_size", hasPointSizeOES,
                                           expectWithoutPragma, expectWithExtDisabled);
        testCompileNeedsExtensionDirective(GL_TESS_EVALUATION_SHADER, kTES, "#version 310 es",
                                           "GL_OES_tessellation_point_size", hasPointSizeOES,
                                           expectWithoutPragma, expectWithExtDisabled);
    }
}

class GLSLValidationMultiviewTest_ES3 : public GLSLValidationTest_ES3
{
  protected:
    std::string makeShader(const char *extension, const char *shader)
    {
        std::stringstream src;
        src << R"(#version 300 es
#extension )"
            << extension << " : require\n"
            << shader;
        return src.str();
    }
};

// Make sure either both OVR_multiview and OVR_multiview2 are exposed or neither are.
TEST_P(GLSLValidationMultiviewTest_ES3, MultiviewBothOrNone)
{
    EXPECT_EQ(IsGLExtensionEnabled("GL_OVR_multiview2"), IsGLExtensionEnabled("GL_OVR_multiview"));
}

// Invalid combination of non-matching num_views declarations.
TEST_P(GLSLValidationMultiviewTest_ES3, InvalidNumViewsMismatch)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS[]     = R"(
layout(num_views = 2) in;
layout(num_views = 1) in;
void main()
{
    gl_Position.x = (gl_ViewID_OVR == 0u) ? 1.0 : 0.0;
    gl_Position.yzw = vec3(0, 0, 1);
})";
    constexpr char kExpect[] = "'layout' : Number of views does not match the previous declaration";

    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview", kVS).c_str(), kExpect);
    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview2", kVS).c_str(), kExpect);
}

// Invalid value zero for num_views.
TEST_P(GLSLValidationMultiviewTest_ES3, InvalidNumViewsZero)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS[]     = R"(
layout(num_views = 0) in;
void main()
{
    gl_Position.x = (gl_ViewID_OVR == 0u) ? 1.0 : 0.0;
    gl_Position.yzw = vec3(0, 0, 1);
})";
    constexpr char kExpect[] = "'0' : out of range: num_views must be positive";

    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview", kVS).c_str(), kExpect);
    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview2", kVS).c_str(), kExpect);
}

// Too large value for num_views.
TEST_P(GLSLValidationMultiviewTest_ES3, InvalidNumViewsGreaterThanMax)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    GLint maxViews = 0;
    glGetIntegerv(GL_MAX_VIEWS_OVR, &maxViews);
    // Max views is typically 2 or 4.
    ANGLE_SKIP_TEST_IF(maxViews >= 50);

    constexpr char kVS[]     = R"(
layout(num_views = 50) in;
void main()
{
    gl_Position.x = (gl_ViewID_OVR == 0u) ? 1.0 : 0.0;
    gl_Position.yzw = vec3(0, 0, 1);
})";
    constexpr char kExpect[] = "'layout' : num_views greater than the value of GL_MAX_VIEWS_OVR";

    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview", kVS).c_str(), kExpect);
    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview2", kVS).c_str(), kExpect);
}

// Test that GL_OVR_multiview cannot be used in an ESSL 1.00 vertex shader.
TEST_P(GLSLValidationMultiviewTest_ES3, InvalidShaderVersion)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS1[] = R"(#extension GL_OVR_multiview : require
void main()
{
})";
    constexpr char kVS2[] = R"(#extension GL_OVR_multiview2 : require
void main()
{
})";

    validateError(GL_VERTEX_SHADER, kVS1, "'GL_OVR_multiview' : extension is not supported");
    validateError(GL_VERTEX_SHADER, kVS2, "'GL_OVR_multiview2' : extension is not supported");
}

// Valid use of gl_ViewID_OVR.
TEST_P(GLSLValidationMultiviewTest_ES3, ViewIDUsed)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS[] = R"(
layout(num_views = 2) in;
layout(num_views = 2) in;  // Duplicated on purpose
in vec4 pos;
out float myOutput;
void main()
{
    if (gl_ViewID_OVR == 0u)
    {
        gl_Position = pos;
        myOutput = 1.0;
    }
    else
    {
        gl_Position = pos + vec4(1.0, 0.0, 0.0, 0.0);
        myOutput = 2.0;
    }
    gl_Position += (gl_ViewID_OVR == 0u) ? 1.0 : 0.0;
})";

    validateSuccess(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview", kVS).c_str());
    validateSuccess(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview2", kVS).c_str());
}

// Read gl_FragCoord in a OVR_multiview2 fragment shader.
TEST_P(GLSLValidationMultiviewTest_ES3, ReadOfFragCoord)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kFS[] = R"(
precision highp float;
out vec4 outColor;
void main()
{
    outColor = vec4(gl_FragCoord.xy, 0, 1);
})";

    validateSuccess(GL_FRAGMENT_SHADER, makeShader("GL_OVR_multiview", kFS).c_str());
    validateSuccess(GL_FRAGMENT_SHADER, makeShader("GL_OVR_multiview2", kFS).c_str());
}

// Read gl_ViewID_OVR in an OVR_multiview2 fragment shader.
TEST_P(GLSLValidationMultiviewTest_ES3, ReadOfViewID)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kFS[] = R"(
precision highp float;
out vec4 outColor;
void main()
{
    outColor = vec4(gl_ViewID_OVR, 0, 0, 1);
})";

    validateSuccess(GL_FRAGMENT_SHADER, makeShader("GL_OVR_multiview", kFS).c_str());
    validateSuccess(GL_FRAGMENT_SHADER, makeShader("GL_OVR_multiview2", kFS).c_str());
}

// Correct use of GL_OVR_multiview macro.
TEST_P(GLSLValidationMultiviewTest_ES3, UseOfExtensionMacro)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS1[] = R"(#version 300 es
#ifdef GL_OVR_multiview
#if (GL_OVR_multiview == 1)
void main()
{
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
#endif
#endif)";

    constexpr char kVS2[] = R"(#version 300 es
#ifdef GL_OVR_multiview2
#if (GL_OVR_multiview2 == 1)
void main()
{
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
#endif
#endif)";

    validateSuccess(GL_VERTEX_SHADER, kVS1);
    validateSuccess(GL_VERTEX_SHADER, kVS2);
}

// Test that gl_ViewID_OVR can't be used as an l-value.
TEST_P(GLSLValidationMultiviewTest_ES3, ViewIdAsLValue)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS[]     = R"(
layout(num_views = 2) in;
void foo(out uint u)
{
    u = 3u;
}
void main()
{
    foo(gl_ViewID_OVR);
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
})";
    constexpr char kExpect[] = "l-value required (can't modify gl_ViewID_OVR \"gl_ViewID_OVR\")";

    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview", kVS).c_str(), kExpect);
    validateError(GL_VERTEX_SHADER, makeShader("GL_OVR_multiview2", kVS).c_str(), kExpect);
}

// Test that compiling an ESSL 1.00 shader with multiview support fails.
TEST_P(GLSLValidationMultiviewTest_ES3, ESSL1Shader)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS1[] = R"(#extension GL_OVR_multiview : require
layout(num_views = 2) in;
void main()
{
    if (gl_ViewID_OVR == 0)
    {
        gl_Position = vec4(-1.0, 0.0, 0.0, 1.0);
    }
    else
    {
        gl_Position = vec4(1.0, 0.0, 0.0, 1.0);
    }
})";
    constexpr char kVS2[] = R"(#extension GL_OVR_multiview2 : require
layout(num_views = 2) in;
void main()
{
    if (gl_ViewID_OVR == 0)
    {
        gl_Position = vec4(-1.0, 0.0, 0.0, 1.0);
    }
    else
    {
        gl_Position = vec4(1.0, 0.0, 0.0, 1.0);
    }
})";

    validateError(GL_VERTEX_SHADER, kVS1, "'GL_OVR_multiview' : extension is not supported");
    validateError(GL_VERTEX_SHADER, kVS2, "'GL_OVR_multiview2' : extension is not supported");
}

// Test that compiling an ESSL 1.00 shader with an unsupported global layout qualifier fails.
TEST_P(GLSLValidationMultiviewTest_ES3, ESSL1ShaderUnsupportedGlobalLayoutQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS1[] = R"(#extension GL_OVR_multiview : require
layout(num_views = 2) in;
layout(std140) uniform;
void main()
{
    gl_Position = vec4(gl_ViewID_OVR == 0 ? -1.0 : 1.0, 0.0, 0.0, 1.0);
})";
    constexpr char kVS2[] = R"(#extension GL_OVR_multiview2 : require
layout(num_views = 2) in;
layout(std140) uniform;
void main()
{
    gl_Position = vec4(gl_ViewID_OVR == 0 ? -1.0 : 1.0, 0.0, 0.0, 1.0);
})";

    validateError(GL_VERTEX_SHADER, kVS1, "'GL_OVR_multiview' : extension is not supported");
    validateError(GL_VERTEX_SHADER, kVS2, "'GL_OVR_multiview2' : extension is not supported");
}

// Test that compiling an ESSL 1.00 vertex shader with an unsupported input storage qualifier fails.
TEST_P(GLSLValidationMultiviewTest_ES3, ESSL1ShaderUnsupportedInputStorageQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kVS1[] = R"(#extension GL_OVR_multiview : require
layout(num_views = 2) in;
in vec4 pos;
void main()
{
    gl_Position = vec4(gl_ViewID_OVR == 0 ? -1.0 : 1.0, 0.0, 0.0, 1.0);
})";
    constexpr char kVS2[] = R"(#extension GL_OVR_multiview2 : require
layout(num_views = 2) in;
in vec4 pos;
void main()
{
    gl_Position = vec4(gl_ViewID_OVR == 0 ? -1.0 : 1.0, 0.0, 0.0, 1.0);
})";

    validateError(GL_VERTEX_SHADER, kVS1, "'GL_OVR_multiview' : extension is not supported");
    validateError(GL_VERTEX_SHADER, kVS2, "'GL_OVR_multiview2' : extension is not supported");
}

// Test that compiling an ESSL 1.00 fragment shader with an unsupported input storage qualifier
// fails.
TEST_P(GLSLValidationMultiviewTest_ES3, ESSL1ShaderUnsupportedInStorageQualifier)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview"));

    constexpr char kFS1[] = R"(#extension GL_OVR_multiview : require
precision highp float;
in vec4 color;
void main()
{
    gl_FragColor = color + (gl_ViewID_OVR == 0 ? vec4(0) : vec4(1));
})";
    constexpr char kFS2[] = R"(#extension GL_OVR_multiview2 : require
precision highp float;
in vec4 color;
void main()
{
    gl_FragColor = color + (gl_ViewID_OVR == 0 ? vec4(0) : vec4(1));
})";

    validateError(GL_FRAGMENT_SHADER, kFS1, "'GL_OVR_multiview' : extension is not supported");
    validateError(GL_FRAGMENT_SHADER, kFS2, "'GL_OVR_multiview2' : extension is not supported");
}

// Test that GL_OVR_multiview is not defined by the preprocessor for WebGL spec shader.
// Test that GL_OVR_multiview2 is defined by the preprocessor for WebGL spec shader.
TEST_P(WebGL2GLSLValidationTest, OnlyMultiview2)
{
    ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_OVR_multiview2"));

    constexpr char kShader[] = R"(#version 300 es
#extension GL_OVR_multiview2 : require
#ifdef GL_OVR_multiview
    #error legacy GL_OVR_multiview support must be forbidden
#endif
#ifndef GL_OVR_multiview2
    #error GL_OVR_multiview2 support must be enabled
#endif
void main()
{
})";
    validateSuccess(GL_VERTEX_SHADER, kShader);
    validateSuccess(GL_FRAGMENT_SHADER, kShader);
}

class GLSLValidationTest_ES3_ValidateUniformBlocks : public GLSLValidationTest_ES3
{};

// Test the validate_max_per_stage_uniform_blocks_at_compile_time feature which validates the
// uniform block count at compile time instead of link time.
TEST_P(GLSLValidationTest_ES3_ValidateUniformBlocks,
       MaxPerStageUniformBlockLimitsValidatedByCompile)
{
    GLint maxVertexUniformBlocks, maxFragmentUniformBlocks;
    glGetIntegerv(GL_MAX_VERTEX_UNIFORM_BLOCKS, &maxVertexUniformBlocks);
    glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_BLOCKS, &maxFragmentUniformBlocks);

    // Test with one large array that is larger than the limit
    std::ostringstream vsStream;
    vsStream << R"(#version 300 es
precision mediump float;
#define BLOCK_COUNT )"
             << maxVertexUniformBlocks + 1 << R"(
layout(std140) uniform MyBlock {
    float x;
} blocks[BLOCK_COUNT];

void main() {
    gl_Position = vec4(blocks[BLOCK_COUNT - 1].x);
})";

    const std::string vertexShader = vsStream.str();
    validateError(GL_VERTEX_SHADER, vertexShader.c_str(),
                  "uniform block count greater than per stage maximum uniform blocks");

    // Test with one array that reaches the limit and a single block which overflows the limit
    std::ostringstream fsStream;
    fsStream << R"(#version 300 es
precision mediump float;
#define BLOCK_COUNT )"
             << maxFragmentUniformBlocks + 1 << R"(
layout(std140) uniform MyBlock0 {
    float x;
} blocks0[BLOCK_COUNT - 1];

layout(std140) uniform MyBlock1 {
    float x;
} block1;

out vec4 fragColor;

void main() {
    fragColor = vec4(blocks0[BLOCK_COUNT-2].x, block1.x, 0.0, 1.0);
})";

    const std::string fragmentShader = fsStream.str();
    validateError(GL_FRAGMENT_SHADER, fragmentShader.c_str(),
                  "uniform block count greater than per stage maximum uniform blocks");
}

// Test that attempting to declare a multidimensional array (not supported by WebGL)
// using an unsupported shader version is rejected immediately with an unsupported
// version error, and compilation aborts.
TEST_P(WebGL2GLSLValidationTest, AttemptedArraySizeOverflow)
{
    // The shader declares a multidimensional array which is not supported in WebGL,
    // and uses a version directive (#version 310 es) that is also unsupported.
    constexpr char kFS[] = R"(#version 310 es
precision highp float;
float big[65536][65536];
out vec4 col;
void main() { big[0][0] = 1.0; col = vec4(big[0][0]); })";

    validateError(GL_FRAGMENT_SHADER, kFS, "unsupported shader version");
}

// Test that declaring a multidimensional array is not supported in ESSL 3.00.
TEST_P(WebGL2GLSLValidationTest, MultidimensionalArrayUnsupported)
{
    constexpr char kFS[] = R"(#version 300 es
precision highp float;
float big[65536][65536];
out vec4 col;
void main() { big[0][0] = 1.0; col = vec4(big[0][0]); })";

    validateError(GL_FRAGMENT_SHADER, kFS, "arrays of arrays");
}

// Test that having a second version directive is rejected.
TEST_P(WebGL2GLSLValidationTest, DoubleVersionDirective)
{
    constexpr char kFS[] = R"(#version 300 es
#version 310 es
precision highp float;
float big[65536][65536];
out vec4 col;
void main() { big[0][0] = 1.0; col = vec4(big[0][0]); })";

    validateError(GL_FRAGMENT_SHADER, kFS, "version");
}

class GLSLValidationTest_ES3_LimitOutputVaryings : public GLSLValidationTest_ES3
{};

// Regression test for crbug.com/529991907.
// Verify that compiling a shader with up to 1024 output varying components
// succeeds, and exceeding 1024 components is rejected at compile time.
TEST_P(GLSLValidationTest_ES3_LimitOutputVaryings, TooManyDeclaredVertexOutputComponents)
{
    ANGLE_SKIP_TEST_IF(
        !getEGLWindow()->isFeatureEnabled(Feature::LimitOutputVaryingsTo256AtCompileTime));

    constexpr int kMaxVectors = 1024 / 4;

    std::stringstream vsValid;
    vsValid << "#version 300 es\n";
    for (int i = 0; i < kMaxVectors; ++i)
    {
        vsValid << "out highp vec4 v" << i << ";\n";
    }
    vsValid << "void main() { gl_Position = vec4(0.0); }\n";
    validateSuccess(GL_VERTEX_SHADER, vsValid.str().c_str());

    std::stringstream vsInvalid;
    vsInvalid << "#version 300 es\n";
    for (int i = 0; i < kMaxVectors + 1; ++i)
    {
        vsInvalid << "out highp vec4 v" << i << ";\n";
    }
    vsInvalid << "void main() { gl_Position = vec4(0.0); }\n";
    validateError(GL_VERTEX_SHADER, vsInvalid.str().c_str(),
                  "Too many declared shader output varying components for this device");
}

}  // namespace

ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLValidationTest);
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLValidationTestNoValidation);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationTest_ES3);
ANGLE_INSTANTIATE_TEST_ES3(GLSLValidationTest_ES3);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationTest_ES3_ValidateUniformBlocks);
ANGLE_INSTANTIATE_TEST(
    GLSLValidationTest_ES3_ValidateUniformBlocks,
    ES3_OPENGL().enable(Feature::ValidateMaxPerStageUniformBlocksAtCompileTime),
    ES3_OPENGLES().enable(Feature::ValidateMaxPerStageUniformBlocksAtCompileTime));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationTest_ES31);
ANGLE_INSTANTIATE_TEST_ES31(GLSLValidationTest_ES31);

ANGLE_INSTANTIATE_TEST_ES2(WebGLGLSLValidationTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WebGL2GLSLValidationTest);
ANGLE_INSTANTIATE_TEST_ES3(WebGL2GLSLValidationTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationTest_ES3_LimitOutputVaryings);
ANGLE_INSTANTIATE_TEST(GLSLValidationTest_ES3_LimitOutputVaryings,
                       ES3_OPENGL().enable(Feature::LimitOutputVaryingsTo256AtCompileTime),
                       ES3_OPENGLES().enable(Feature::LimitOutputVaryingsTo256AtCompileTime));

ANGLE_INSTANTIATE_TEST_ES2_AND(WebGLGLSLValidationExtensionDisableTest,
                               ES2_OPENGL().enable(Feature::AllowExtensionDisableAfterNonPpTokens));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationClipDistanceTest_ES3);
ANGLE_INSTANTIATE_TEST_ES3_AND(GLSLValidationClipDistanceTest_ES3,
                               ES3_VULKAN().disable(Feature::SupportsAppleClipDistance));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationClipDistanceTest_ES31);
ANGLE_INSTANTIATE_TEST_ES31(GLSLValidationClipDistanceTest_ES31);

ANGLE_INSTANTIATE_TEST_ES2(GLSLValidationTextureRectangleTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationAtomicCounterTest_ES31);
ANGLE_INSTANTIATE_TEST_ES31(GLSLValidationAtomicCounterTest_ES31);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationShaderStorageBlockTest_ES31);
ANGLE_INSTANTIATE_TEST_ES31(GLSLValidationShaderStorageBlockTest_ES31);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationBaseVertexTest_ES3);
ANGLE_INSTANTIATE_TEST(
    GLSLValidationBaseVertexTest_ES3,
    ES3_D3D11().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_OPENGL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_OPENGLES().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_VULKAN().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_VULKAN_SWIFTSHADER().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_METAL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(WebGL2GLSLValidationBaseVertexTest);
ANGLE_INSTANTIATE_TEST_ES3(WebGL2GLSLValidationBaseVertexTest);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationDrawIDTest);
ANGLE_INSTANTIATE_TEST(
    GLSLValidationDrawIDTest,
    ES3_D3D11().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_OPENGL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_OPENGLES().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_VULKAN().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_VULKAN_SWIFTSHADER().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions),
    ES3_METAL().enable(Feature::AlwaysEnableEmulatedMultidrawExtensions));

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationExtensionDirectiveTest_ES3);
ANGLE_INSTANTIATE_TEST_ES3(GLSLValidationExtensionDirectiveTest_ES3);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationExtensionDirectiveTest_ES31);
ANGLE_INSTANTIATE_TEST_ES31(GLSLValidationExtensionDirectiveTest_ES31);

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationExtensionDirectiveTestClipCull_ES31);
ANGLE_INSTANTIATE_TEST(GLSLValidationExtensionDirectiveTestClipCull_ES31,
                       ES31_VULKAN(),
                       ES31_VULKAN_SWIFTSHADER());

GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(GLSLValidationMultiviewTest_ES3);
ANGLE_INSTANTIATE_TEST_ES3(GLSLValidationMultiviewTest_ES3);
