// // Copyright 2014 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. // // CollectVariables_test.cpp: // Some tests for shader inspection // #include "GLSLANG/ShaderLang.h" #include "angle_gl.h" #include "compiler/translator/Compiler.h" #include "gtest/gtest.h" namespace sh { TEST(ShaderVariableTest, FindInfoByMappedName) { // struct A { // float x[2]; // vec3 y; // }; // struct B { // A a[3]; // }; // B uni[2]; ShaderVariable uni(0, 2); uni.name = "uni"; uni.mappedName = "m_uni"; uni.structName = "B"; { ShaderVariable a(0, 3); a.name = "a"; a.mappedName = "m_a"; a.structName = "A"; { ShaderVariable x(GL_FLOAT, 2); x.name = "x"; x.mappedName = "m_x"; a.fields.push_back(x); ShaderVariable y(GL_FLOAT_VEC3); y.name = "y"; y.mappedName = "m_y"; a.fields.push_back(y); } uni.fields.push_back(a); } const ShaderVariable *leafVar = nullptr; std::string originalFullName; std::string mappedFullName = "wrongName"; EXPECT_FALSE(uni.findInfoByMappedName(mappedFullName, &leafVar, &originalFullName)); mappedFullName = "m_uni"; EXPECT_TRUE(uni.findInfoByMappedName(mappedFullName, &leafVar, &originalFullName)); EXPECT_EQ(&uni, leafVar); EXPECT_STREQ("uni", originalFullName.c_str()); mappedFullName = "m_uni[0].m_a[1].wrongName"; EXPECT_FALSE(uni.findInfoByMappedName(mappedFullName, &leafVar, &originalFullName)); mappedFullName = "m_uni[0].m_a[1].m_x"; EXPECT_TRUE(uni.findInfoByMappedName(mappedFullName, &leafVar, &originalFullName)); EXPECT_EQ(&(uni.fields[0].fields[0]), leafVar); EXPECT_STREQ("uni[0].a[1].x", originalFullName.c_str()); mappedFullName = "m_uni[0].m_a[1].m_x[0]"; EXPECT_TRUE(uni.findInfoByMappedName(mappedFullName, &leafVar, &originalFullName)); EXPECT_EQ(&(uni.fields[0].fields[0]), leafVar); EXPECT_STREQ("uni[0].a[1].x[0]", originalFullName.c_str()); mappedFullName = "m_uni[0].m_a[1].m_y"; EXPECT_TRUE(uni.findInfoByMappedName(mappedFullName, &leafVar, &originalFullName)); EXPECT_EQ(&(uni.fields[0].fields[1]), leafVar); EXPECT_STREQ("uni[0].a[1].y", originalFullName.c_str()); } TEST(ShaderVariableTest, IsSameUniformWithDifferentFieldOrder) { // struct A { // float x; // float y; // }; // uniform A uni; ShaderVariable vx_a; vx_a.name = "uni"; vx_a.mappedName = "m_uni"; vx_a.structName = "A"; { ShaderVariable x(GL_FLOAT); x.name = "x"; x.mappedName = "m_x"; vx_a.fields.push_back(x); ShaderVariable y(GL_FLOAT); y.name = "y"; y.mappedName = "m_y"; vx_a.fields.push_back(y); } // struct A { // float y; // float x; // }; // uniform A uni; ShaderVariable fx_a; fx_a.name = "uni"; fx_a.mappedName = "m_uni"; fx_a.structName = "A"; { ShaderVariable y(GL_FLOAT); y.name = "y"; y.mappedName = "m_y"; fx_a.fields.push_back(y); ShaderVariable x(GL_FLOAT); x.name = "x"; x.mappedName = "m_x"; fx_a.fields.push_back(x); } EXPECT_FALSE(vx_a.isSameUniformAtLinkTime(fx_a)); } TEST(ShaderVariableTest, IsSameUniformWithDifferentStructNames) { // struct A { // float x; // float y; // }; // uniform A uni; ShaderVariable vx_a; vx_a.name = "uni"; vx_a.mappedName = "m_uni"; vx_a.structName = "A"; { ShaderVariable x(GL_FLOAT); x.name = "x"; x.mappedName = "m_x"; vx_a.fields.push_back(x); ShaderVariable y(GL_FLOAT); y.name = "y"; y.mappedName = "m_y"; vx_a.fields.push_back(y); } // struct B { // float x; // float y; // }; // uniform B uni; ShaderVariable fx_a; fx_a.name = "uni"; fx_a.mappedName = "m_uni"; { ShaderVariable x(GL_FLOAT); x.name = "x"; x.mappedName = "m_x"; fx_a.fields.push_back(x); ShaderVariable y(GL_FLOAT); y.name = "y"; y.mappedName = "m_y"; fx_a.fields.push_back(y); } fx_a.structName = "B"; EXPECT_FALSE(vx_a.isSameUniformAtLinkTime(fx_a)); fx_a.structName = "A"; EXPECT_TRUE(vx_a.isSameUniformAtLinkTime(fx_a)); fx_a.structName = ""; EXPECT_FALSE(vx_a.isSameUniformAtLinkTime(fx_a)); } TEST(ShaderVariableTest, IsSameVaryingWithDifferentInvariance) { // invariant varying float vary; ShaderVariable vx; vx.type = GL_FLOAT; vx.precision = GL_MEDIUM_FLOAT; vx.name = "vary"; vx.mappedName = "m_vary"; vx.staticUse = true; vx.isInvariant = true; // varying float vary; ShaderVariable fx; fx.type = GL_FLOAT; fx.precision = GL_MEDIUM_FLOAT; fx.name = "vary"; fx.mappedName = "m_vary"; fx.staticUse = true; fx.isInvariant = false; // Default to ESSL1 behavior: invariance must match EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx)); EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 100)); // ESSL3 behavior: invariance doesn't need to match EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 300)); // invariant varying float vary; fx.isInvariant = true; EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx)); EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 100)); EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 300)); } // Test that using invariant varyings doesn't trigger a double delete. TEST(ShaderVariableTest, InvariantDoubleDeleteBug) { ShBuiltInResources resources; sh::InitBuiltInResources(&resources); ShHandle compiler = sh::ConstructCompiler(GL_VERTEX_SHADER, SH_GLES2_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT, &resources); EXPECT_NE(static_cast(0), compiler); const char *program[] = { "attribute vec4 position;\n" "varying float v;\n" "invariant v;\n" "void main() {\n" " v = 1.0;\n" " gl_Position = position;\n" "}"}; EXPECT_TRUE(sh::Compile(compiler, program, 1, SH_OBJECT_CODE)); EXPECT_TRUE(sh::Compile(compiler, program, 1, SH_OBJECT_CODE)); sh::Destruct(compiler); } TEST(ShaderVariableTest, IllegalInvariantVarying) { ShBuiltInResources resources; sh::InitBuiltInResources(&resources); ShHandle compiler = sh::ConstructCompiler(GL_VERTEX_SHADER, SH_GLES2_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT, &resources); EXPECT_NE(static_cast(0), compiler); const char *program1[] = { R"(void foo() { } varying vec4 v_varying; invariant v_varying; void main() { foo(); gl_Position = v_varying; })"}; const char *program2[] = { "varying vec4 v_varying;\n" "void foo() {\n" " invariant v_varying;\n" "}\n" "void main() {\n" " foo();\n" " gl_Position = v_varying;\n" "}"}; EXPECT_TRUE(sh::Compile(compiler, program1, 1, SH_VARIABLES)); EXPECT_FALSE(sh::Compile(compiler, program2, 1, SH_VARIABLES)); sh::Destruct(compiler); } TEST(ShaderVariableTest, InvariantLeakAcrossShaders) { ShBuiltInResources resources; sh::InitBuiltInResources(&resources); ShHandle compiler = sh::ConstructCompiler(GL_VERTEX_SHADER, SH_GLES2_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT, &resources); EXPECT_NE(static_cast(0), compiler); const char *program1[] = { "varying vec4 v_varying;\n" "invariant v_varying;\n" "void main() {\n" " gl_Position = v_varying;\n" "}"}; const char *program2[] = { "varying vec4 v_varying;\n" "void main() {\n" " gl_Position = v_varying;\n" "}"}; EXPECT_TRUE(sh::Compile(compiler, program1, 1, SH_VARIABLES)); const std::vector *varyings = sh::GetOutputVaryings(compiler); for (const sh::ShaderVariable &varying : *varyings) { if (varying.name == "v_varying") { EXPECT_TRUE(varying.isInvariant); } } EXPECT_TRUE(sh::Compile(compiler, program2, 1, SH_VARIABLES)); varyings = sh::GetOutputVaryings(compiler); for (const sh::ShaderVariable &varying : *varyings) { if (varying.name == "v_varying") { EXPECT_FALSE(varying.isInvariant); } } sh::Destruct(compiler); } TEST(ShaderVariableTest, GlobalInvariantLeakAcrossShaders) { ShBuiltInResources resources; sh::InitBuiltInResources(&resources); ShHandle compiler = sh::ConstructCompiler(GL_VERTEX_SHADER, SH_GLES2_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT, &resources); EXPECT_NE(static_cast(0), compiler); const char *program1[] = { "#pragma STDGL invariant(all)\n" "varying vec4 v_varying;\n" "void main() {\n" " gl_Position = v_varying;\n" "}"}; const char *program2[] = { "varying vec4 v_varying;\n" "void main() {\n" " gl_Position = v_varying;\n" "}"}; EXPECT_TRUE(sh::Compile(compiler, program1, 1, SH_VARIABLES)); const std::vector *varyings = sh::GetOutputVaryings(compiler); for (const sh::ShaderVariable &varying : *varyings) { if (varying.name == "v_varying") { EXPECT_TRUE(varying.isInvariant); } } EXPECT_TRUE(sh::Compile(compiler, program2, 1, SH_VARIABLES)); varyings = sh::GetOutputVaryings(compiler); for (const sh::ShaderVariable &varying : *varyings) { if (varying.name == "v_varying") { EXPECT_FALSE(varying.isInvariant); } } sh::Destruct(compiler); } TEST(ShaderVariableTest, BuiltinInvariantVarying) { ShBuiltInResources resources; sh::InitBuiltInResources(&resources); ShHandle compiler = sh::ConstructCompiler(GL_VERTEX_SHADER, SH_GLES2_SPEC, SH_GLSL_COMPATIBILITY_OUTPUT, &resources); EXPECT_NE(static_cast(0), compiler); const char *program1[] = { "invariant gl_Position;\n" "void main() {\n" " gl_Position = vec4(0, 0, 0, 0);\n" "}"}; const char *program2[] = { "void main() {\n" " gl_Position = vec4(0, 0, 0, 0);\n" "}"}; const char *program3[] = { "void main() {\n" " invariant gl_Position;\n" " gl_Position = vec4(0, 0, 0, 0);\n" "}"}; EXPECT_TRUE(sh::Compile(compiler, program1, 1, SH_VARIABLES)); const std::vector *varyings = sh::GetOutputVaryings(compiler); for (const sh::ShaderVariable &varying : *varyings) { if (varying.name == "gl_Position") { EXPECT_TRUE(varying.isInvariant); } } EXPECT_TRUE(sh::Compile(compiler, program2, 1, SH_VARIABLES)); varyings = sh::GetOutputVaryings(compiler); for (const sh::ShaderVariable &varying : *varyings) { if (varying.name == "gl_Position") { EXPECT_FALSE(varying.isInvariant); } } EXPECT_FALSE(sh::Compile(compiler, program3, 1, SH_VARIABLES)); sh::Destruct(compiler); } // Verify in ES3.1 two varyings with either same name or same declared location can match. TEST(ShaderVariableTest, IsSameVaryingWithDifferentName) { // Varying float vary1; ShaderVariable vx; vx.type = GL_FLOAT; vx.precision = GL_MEDIUM_FLOAT; vx.name = "vary1"; vx.mappedName = "m_vary1"; vx.staticUse = true; vx.isInvariant = false; // Varying float vary2; ShaderVariable fx; fx.type = GL_FLOAT; fx.precision = GL_MEDIUM_FLOAT; fx.name = "vary2"; fx.mappedName = "m_vary2"; fx.staticUse = true; fx.isInvariant = false; // ESSL3 behavior: name must match EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 300)); // ESSL3.1 behavior: // [OpenGL ES 3.1 SPEC Chapter 7.4.1] // An output variable is considered to match an input variable in the subsequent shader if: // - the two variables match in name, type, and qualification; or // - the two variables are declared with the same location qualifier and match in type and // qualification. vx.location = 0; fx.location = 0; EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 310)); fx.name = vx.name; fx.mappedName = vx.mappedName; fx.location = -1; EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 310)); fx.location = 1; EXPECT_FALSE(vx.isSameVaryingAtLinkTime(fx, 310)); fx.location = 0; EXPECT_TRUE(vx.isSameVaryingAtLinkTime(fx, 310)); } } // namespace sh