blob: 3f8c24bdeda9f89adeea5aec4c662730d78891e4 [file] [log] [blame] [edit]
//
// 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.
//
#ifdef UNSAFE_BUFFERS_BUILD
# pragma allow_unsafe_buffers
#endif
#include "test_utils/CompilerTest.h"
#include "test_utils/angle_test_configs.h"
using namespace angle;
namespace
{
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");
}
// 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");
}
// 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' : 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' : 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' : 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 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");
}
// 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 be only 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)
{
const 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)
{
const 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 packing of excessive 3-column variables does not overflow the count of 3-column
// variables in VariablePacker
TEST_P(WebGL2GLSLValidationTest, ExcessiveMat3UniformPacking)
{
std::ostringstream vs;
vs << R"(#version 300 es
precision mediump float;
out vec4 finalColor;
in vec4 color;
uniform mat4 r[254];
uniform mat3 )";
constexpr size_t kNumUniforms = 10000;
for (size_t i = 0; i < kNumUniforms; ++i)
{
if (i > 0)
{
vs << ", ";
}
vs << "m3a_" << i << "[256]";
}
vs << R"(;
void main(void) { finalColor = color; })";
validateError(GL_VERTEX_SHADER, vs.str().c_str(), "too many uniforms");
}
// 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());
const 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());
const 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());
const 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 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 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 out gl_PerVertex with a name (without EXT_shader_io_blocks)
constexpr char kTCS[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
validateError(GL_TESS_CONTROL_SHADER, kTCS,
"'out' : invalid qualifier: interface blocks must be uniform in version "
"lower than GLSL ES 3.10");
}
{
// Cannot use in gl_PerVertex with a name (without EXT_shader_io_blocks)
constexpr char kTCS[] = R"(#version 300 es
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
validateError(GL_TESS_CONTROL_SHADER, kTCS,
"'in' : invalid qualifier: interface blocks must be uniform in version lower "
"than GLSL ES 3.10");
}
{
// 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 out gl_PerVertex with a name (without EXT_shader_io_blocks)
constexpr char kTES[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
validateError(GL_TESS_EVALUATION_SHADER, kTES,
"'out' : invalid qualifier: interface blocks must be uniform in version "
"lower than GLSL ES 3.10");
}
{
// Cannot use in gl_PerVertex with a name (without EXT_shader_io_blocks)
constexpr char kTES[] = R"(#version 300 es
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
validateError(GL_TESS_EVALUATION_SHADER, kTES,
"'in' : invalid qualifier: interface blocks must be uniform in version lower "
"than GLSL ES 3.10");
}
{
// 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 out gl_PerVertex with a name (without EXT_shader_io_blocks)
constexpr char kGS[] = R"(#version 300 es
out gl_PerVertex{vec4 gl_Position;} name;
void main() {})";
validateError(GL_GEOMETRY_SHADER, kGS,
"'out' : invalid qualifier: interface blocks must be uniform in version "
"lower than GLSL ES 3.10");
}
{
// Cannot use in gl_PerVertex with a name (without EXT_shader_io_blocks)
constexpr char kGS[] = R"(#version 300 es
in gl_PerVertex{vec4 gl_Position;} name[];
void main() {})";
validateError(GL_GEOMETRY_SHADER, kGS,
"'in' : invalid qualifier: interface blocks must be uniform in version lower "
"than GLSL ES 3.10");
}
{
// 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);
}
}
// 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;
// Note: SPIR-V doesn't allow more than 255 parameters to a function.
const unsigned int paramCount = (IsVulkan() || IsMetal()) ? 255u : 1024u;
vs << R"(#version 300 es
precision highp float;
in vec4 a_vec;
vec4 lotsOfVec4Parameters()";
for (unsigned int i = 0; i < paramCount - 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 < paramCount - 1; ++i)
{
vs << " sum += a" << i << ";\n";
}
vs << R"( sum += aLast;
return sum;
}
void main()
{
gl_Position = lotsOfVec4Parameters()";
for (unsigned int i = 0; i < paramCount - 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. Note that this
// test doesn't work on D3D9 due to looping limitations, so it is only run on ES3.
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)
{
const 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 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);
}
// 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 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)
{
const 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)
{
const 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)
{
const 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)
{
const 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)
{
const 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 SecondaryFragColor and SecondaryFragData does not compile.
TEST_P(GLSLValidationTest, BlendFuncExtendedSecondaryColorAndData)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_blend_func_extended"));
const 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"));
const 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"));
const 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_MaxDrawBuffers - 1] = vec4(0.1);
})";
validateError(GL_FRAGMENT_SHADER, kFS,
"cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT) and "
"(gl_FragColor, gl_SecondaryFragColorEXT)");
}
// 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"));
const 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"));
const 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"));
const 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);
const 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);
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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 specifies yuvCscStandardEXT type qualifiers fails to compile.
TEST_P(GLSLValidationTest_ES3, YUVTargetCscStandardInput)
{
ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_YUV_target"));
const char kFS[] =
R"(#version 300 es
#extension GL_EXT_YUV_target : require
precision mediump float;
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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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"));
const 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");
}
// Ensure that a negative index after a comma generates an error.
TEST_P(GLSLValidationTest_ES3, NegativeIndexAfterComma)
{
const 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)
{
const 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"))
{
const 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"))
{
const 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");
}
}
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);
const 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 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);
const 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)
{
const 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)
{
const 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);
const 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);
const 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)
{
const 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)
{
const 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)
{
const 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);
}
}
// In compute shader, redeclaring gl_ClipDistance should be denied.
TEST_P(GLSLValidationClipDistanceTest_ES31, ComputeDeclareClipDistance)
{
const 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)
{
const 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)
{
const 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)
{
const 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)
{
const 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)
{
const 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)
{
const 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)
{
const 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"));
{
const 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");
}
{
const 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"));
const 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_' : 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_' : 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_' : 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_' : reserved built-in name");
}
{
// 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_' : 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_' : 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_' : 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_' : 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_' : 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_' : reserved built-in name");
}
{
// 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_' : 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_' : 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_WEBGL_video_texture needs to be enabled in GLSL to be able to use samplerVideoWEBGL.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SamplerVideoWEBGL_ESSL100)
{
const bool hasExt = IsGLExtensionEnabled("GL_WEBGL_video_texture");
constexpr char kFS[] = R"(precision mediump float;
uniform mediump samplerVideoWEBGL s;
void main() {
gl_FragColor = textureVideoWEBGL(s, vec2(0.0, 0.0));
})";
// samplerVideoWEBGL is not a reserved keyword, and the translator fails with syntax
// error if extension is not specified.
testCompileNeedsExtensionDirective(
GL_FRAGMENT_SHADER, kFS, nullptr, "GL_WEBGL_video_texture", hasExt, "'s' : syntax error",
hasExt ? "'s' : syntax error" : "extension is not supported");
}
// GL_WEBGL_video_texture needs to be enabled in GLSL to be able to use samplerVideoWEBGL.
TEST_P(GLSLValidationExtensionDirectiveTest_ES3, SamplerVideoWEBGL_ESSL300)
{
const bool hasExt = IsGLExtensionEnabled("GL_WEBGL_video_texture");
constexpr char kFS[] = R"(precision mediump float;
uniform mediump samplerVideoWEBGL s;
out vec4 my_FragColor;
void main() {
my_FragColor = texture(s, vec2(0.0, 0.0));
})";
// samplerVideoWEBGL 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_WEBGL_video_texture", 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");
const 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];
})";
const 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));
})";
const 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];
})";
const 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_' : 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");
}
}
} // 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_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(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);