| // | |
| // Copyright 2017 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. | |
| // | |
| // MSLOutput_test.cpp: | |
| // Tests for MSL output. | |
| // | |
| #include <regex> | |
| #include "GLSLANG/ShaderLang.h" | |
| #include "angle_gl.h" | |
| #include "gtest/gtest.h" | |
| #include "tests/test_utils/compiler_test.h" | |
| using namespace sh; | |
| class MSLVertexOutputTest : public MatchOutputCodeTest | |
| { | |
| public: | |
| MSLVertexOutputTest() : MatchOutputCodeTest(GL_VERTEX_SHADER, SH_MSL_METAL_OUTPUT) | |
| { | |
| ShCompileOptions defaultCompileOptions = {}; | |
| defaultCompileOptions.validateAST = true; | |
| setDefaultCompileOptions(defaultCompileOptions); | |
| } | |
| }; | |
| class MSLOutputTest : public MatchOutputCodeTest | |
| { | |
| public: | |
| MSLOutputTest() : MatchOutputCodeTest(GL_FRAGMENT_SHADER, SH_MSL_METAL_OUTPUT) | |
| { | |
| ShCompileOptions defaultCompileOptions = {}; | |
| defaultCompileOptions.rescopeGlobalVariables = true; | |
| defaultCompileOptions.validateAST = true; | |
| setDefaultCompileOptions(defaultCompileOptions); | |
| } | |
| }; | |
| // Test that having dynamic indexing of a vector inside the right hand side of logical or doesn't | |
| // trigger asserts in MSL output. | |
| TEST_F(MSLOutputTest, DynamicIndexingOfVectorOnRightSideOfLogicalOr) | |
| { | |
| const std::string &shaderString = | |
| "#version 300 es\n" | |
| "precision highp float;\n" | |
| "out vec4 my_FragColor;\n" | |
| "uniform int u1;\n" | |
| "void main() {\n" | |
| " bvec4 v = bvec4(true, true, true, false);\n" | |
| " my_FragColor = vec4(v[u1 + 1] || v[u1]);\n" | |
| "}\n"; | |
| compile(shaderString); | |
| } | |
| // Test that having an array constructor as a statement doesn't trigger an assert in MSL output. | |
| TEST_F(MSLOutputTest, ArrayConstructorStatement) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| out vec4 outColor; | |
| void main() | |
| { | |
| outColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| float[1](outColor[1]++); | |
| })"; | |
| compile(shaderString); | |
| } | |
| // Test an array of arrays constructor as a statement. | |
| TEST_F(MSLOutputTest, ArrayOfArraysStatement) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 310 es | |
| precision mediump float; | |
| out vec4 outColor; | |
| void main() | |
| { | |
| outColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| float[2][2](float[2](outColor[1]++, 0.0), float[2](1.0, 2.0)); | |
| })"; | |
| compile(shaderString); | |
| } | |
| // Test dynamic indexing of a vector. This makes sure that helper functions added for dynamic | |
| // indexing have correct data that subsequent traversal steps rely on. | |
| TEST_F(MSLOutputTest, VectorDynamicIndexing) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| out vec4 outColor; | |
| uniform int i; | |
| void main() | |
| { | |
| vec4 foo = vec4(0.0, 0.0, 0.0, 1.0); | |
| foo[i] = foo[i + 1]; | |
| outColor = foo; | |
| })"; | |
| compile(shaderString); | |
| } | |
| // Test returning an array from a user-defined function. This makes sure that function symbols are | |
| // changed consistently when the user-defined function is changed to have an array out parameter. | |
| TEST_F(MSLOutputTest, ArrayReturnValue) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float u; | |
| out vec4 outColor; | |
| float[2] getArray(float f) | |
| { | |
| return float[2](f, f + 1.0); | |
| } | |
| void main() | |
| { | |
| float[2] arr = getArray(u); | |
| outColor = vec4(arr[0], arr[1], 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| // Test that writing parameters without a name doesn't assert. | |
| TEST_F(MSLOutputTest, ParameterWithNoName) | |
| { | |
| const std::string &shaderString = | |
| R"(precision mediump float; | |
| uniform vec4 v; | |
| vec4 s(vec4) | |
| { | |
| return v; | |
| } | |
| void main() | |
| { | |
| gl_FragColor = s(v); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, Macro) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| #define FOO vec4 | |
| out vec4 outColor; | |
| void main() | |
| { | |
| outColor = FOO(1.0, 2.0, 3.0, 4.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, UniformSimple) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out vec4 outColor; | |
| uniform float x; | |
| void main() | |
| { | |
| outColor = vec4(x, x, x, x); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, FragmentOutSimple) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out vec4 outColor; | |
| void main() | |
| { | |
| outColor = vec4(1.0, 2.0, 3.0, 4.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, FragmentOutIndirect1) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out vec4 outColor; | |
| void foo() | |
| { | |
| outColor = vec4(1.0, 2.0, 3.0, 4.0); | |
| } | |
| void bar() | |
| { | |
| foo(); | |
| } | |
| void main() | |
| { | |
| bar(); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, FragmentOutIndirect2) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out vec4 outColor; | |
| void foo(); | |
| void bar() | |
| { | |
| foo(); | |
| } | |
| void foo() | |
| { | |
| outColor = vec4(1.0, 2.0, 3.0, 4.0); | |
| } | |
| void main() | |
| { | |
| bar(); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, FragmentOutIndirect3) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out vec4 outColor; | |
| float foo(float x, float y) | |
| { | |
| outColor = vec4(x, y, 3.0, 4.0); | |
| return 7.0; | |
| } | |
| float bar(float x) | |
| { | |
| return foo(x, 2.0); | |
| } | |
| float baz() | |
| { | |
| return 13.0; | |
| } | |
| float identity(float x) | |
| { | |
| return x; | |
| } | |
| void main() | |
| { | |
| identity(bar(baz())); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, VertexInOut) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| in float in0; | |
| out float out0; | |
| void main() | |
| { | |
| out0 = in0; | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, SymbolSharing) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out vec4 outColor; | |
| struct Foo { | |
| float x; | |
| float y; | |
| }; | |
| void doFoo(Foo foo, float zw); | |
| void doFoo(Foo foo, float zw) | |
| { | |
| foo.x = foo.y; | |
| outColor = vec4(foo.x, foo.y, zw, zw); | |
| } | |
| void main() | |
| { | |
| Foo foo; | |
| foo.x = 2.0; | |
| foo.y = 2.0; | |
| doFoo(foo, 3.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, StructDecl) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| out float out0; | |
| struct Foo { | |
| float value; | |
| }; | |
| void main() | |
| { | |
| Foo foo; | |
| out0 = foo.value; | |
| } | |
| )"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, Structs) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| struct Foo { | |
| float value; | |
| }; | |
| out vec4 out0; | |
| struct Bar { | |
| Foo foo; | |
| }; | |
| void go(); | |
| uniform UniInstance { | |
| Bar bar; | |
| float instance; | |
| } uniInstance; | |
| uniform UniGlobal { | |
| Foo foo; | |
| float global; | |
| }; | |
| void main() | |
| { | |
| go(); | |
| } | |
| struct Baz { | |
| Bar bar; | |
| } baz; | |
| void go() | |
| { | |
| out0.x = baz.bar.foo.value; | |
| out0.y = global; | |
| out0.z = uniInstance.instance; | |
| out0.w = 0.0; | |
| } | |
| )"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, KeywordConflict) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| struct fragment { | |
| float kernel; | |
| } device; | |
| struct Foo { | |
| fragment frag; | |
| } foo; | |
| out float vertex; | |
| float kernel; | |
| float stage_in(float x) | |
| { | |
| return x; | |
| } | |
| void metal(float metal, float fragment); | |
| void metal(float metal, float fragment) | |
| { | |
| vertex = metal * fragment * foo.frag.kernel; | |
| } | |
| void main() | |
| { | |
| metal(stage_in(stage_in(kernel * device.kernel)), foo.frag.kernel); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLVertexOutputTest, Vertex) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| void main() | |
| { | |
| gl_Position = vec4(1.0,1.0,1.0,1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLVertexOutputTest, LastReturn) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| in highp vec4 a_position; | |
| in highp vec4 a_coords; | |
| out highp vec4 v_color; | |
| void main (void) | |
| { | |
| gl_Position = a_position; | |
| v_color = vec4(a_coords.xyz, 1.0); | |
| return; | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, LastReturn) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| in mediump vec4 v_coords; | |
| layout(location = 0) out mediump vec4 o_color; | |
| void main (void) | |
| { | |
| o_color = vec4(v_coords.xyz, 1.0); | |
| return; | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, FragColor) | |
| { | |
| const std::string &shaderString = R"( | |
| void main () | |
| { | |
| gl_FragColor = vec4(1.0, 0.0, 1.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, MatrixIn) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision highp float; | |
| in mat4 mat; | |
| out float out0; | |
| void main() | |
| { | |
| out0 = mat[0][0]; | |
| } | |
| )"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, WhileTrue) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| my_FragColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| while (true) | |
| { | |
| break; | |
| } | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, ForTrue) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| my_FragColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| for (;true;) | |
| { | |
| break; | |
| } | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, ForEmpty) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| my_FragColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| for (;;) | |
| { | |
| break; | |
| } | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, ForComplex) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| my_FragColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| for (int i = 0, j = 2; i < j; ++i) { | |
| if (i == 0) continue; | |
| if (i == 42) break; | |
| my_FragColor.x += float(i); | |
| } | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, ForSymbol) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| bool cond = true; | |
| for (;cond;) | |
| { | |
| my_FragColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| cond = false; | |
| } | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, DoWhileSymbol) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| uniform float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| bool cond = false; | |
| do | |
| { | |
| my_FragColor = vec4(0.0, 0.0, 0.0, 1.0); | |
| } while (cond); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, AnonymousStruct) | |
| { | |
| const std::string &shaderString = | |
| R"( | |
| precision mediump float; | |
| struct { vec4 v; } anonStruct; | |
| void main() { | |
| anonStruct.v = vec4(0.0,1.0,0.0,1.0); | |
| gl_FragColor = anonStruct.v; | |
| })"; | |
| compile(shaderString); | |
| // TODO(anglebug.com/42264909): This success condition is expected to fail now. | |
| // When WebKit build is able to run the tests, this should be changed to something else. | |
| // ASSERT_TRUE(foundInCode(SH_MSL_METAL_OUTPUT, "__unnamed")); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingSimple) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should rescope uf into main | |
| float uf; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| uf += 1.0f; | |
| my_FragColor = vec4(uf, 0.0, 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingNoRescope) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should not rescope any variable | |
| float uf; | |
| out vec4 my_FragColor; | |
| void modifyGlobal() | |
| { | |
| uf = 1.0f; | |
| } | |
| void main() | |
| { | |
| modifyGlobal(); | |
| my_FragColor = vec4(uf, 0.0, 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingInitializer) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should rescope uf into main | |
| float uf = 1.0f; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| uf += 1.0; | |
| my_FragColor = vec4(uf, 0.0, 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingInitializerNoRescope) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should not rescope any variable | |
| float uf = 1.0f; | |
| out vec4 my_FragColor; | |
| void modifyGlobal() | |
| { | |
| uf =+ 1.0f; | |
| } | |
| void main() | |
| { | |
| modifyGlobal(); | |
| my_FragColor = vec4(uf, 0.0, 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingNestedFunction) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should rescope a info modifyGlobal | |
| float a = 1.0f; | |
| float uf = 1.0f; | |
| out vec4 my_FragColor; | |
| void modifyGlobal() | |
| { | |
| uf =+ a; | |
| } | |
| void main() | |
| { | |
| modifyGlobal(); | |
| my_FragColor = vec4(uf, 0.0, 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingMultipleUses) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should rescope uf into main | |
| float uf = 1.0f; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| uf =+ 1.0; | |
| if (uf > 0.0) | |
| { | |
| uf =- 0.5; | |
| } | |
| my_FragColor = vec4(uf, 0.0, 0.0, 1.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingGloballyReferencedVar) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should rescope uf into main | |
| const float a = 1.0f; | |
| float uf = a; | |
| out vec4 my_FragColor; | |
| void main() | |
| { | |
| my_FragColor = vec4(uf, 0.0, a, 0.0); | |
| })"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, GlobalRescopingDeclarationAfterFunction) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| // Should rescope c and b into main | |
| float a = 1.0f; | |
| float c = 1.0f; | |
| out vec4 my_FragColor; | |
| void modifyGlobal() | |
| { | |
| a =+ 1.0f; | |
| } | |
| float b = 1.0f; | |
| void main() | |
| { | |
| modifyGlobal(); | |
| my_FragColor = vec4(a, b, c, 0.0); | |
| } | |
| )"; | |
| compile(shaderString); | |
| } | |
| TEST_F(MSLOutputTest, ReusedOutVarName) | |
| { | |
| const std::string &shaderString = | |
| R"(#version 300 es | |
| precision mediump float; | |
| out vec4 my_FragColor; | |
| void funcWith1Out( | |
| out float outC) { | |
| outC = 1.0; | |
| } | |
| void funcWith4Outs( | |
| out float outA, | |
| out float outB, | |
| out float outC, | |
| out float outD) { | |
| outA = 1.0; | |
| outB = 1.0; | |
| outD = 1.0; | |
| } | |
| void main() | |
| { | |
| funcWith1Out(my_FragColor.g); | |
| funcWith4Outs(my_FragColor.r, my_FragColor.g, my_FragColor.b, my_FragColor.a); | |
| } | |
| )"; | |
| compile(shaderString); | |
| } | |
| // Test that for loops without body do not crash. At the time of writing, constant hoisting would | |
| // traverse such ASTs and crash when loop bodies were not present. | |
| TEST_F(MSLOutputTest, RemovedForBodyNoCrash) | |
| { | |
| const char kShader[] = R"(#version 310 es | |
| void main() { | |
| for(;;)if(2==0); | |
| })"; | |
| compile(kShader); | |
| } | |
| // Test that accessing array element of array of anonymous struct instances does not fail | |
| // validation. | |
| TEST_F(MSLOutputTest, AnonymousStructArrayValidationNoCrash) | |
| { | |
| const char kShader[] = R"( | |
| precision mediump float; | |
| void main() { | |
| struct { vec4 field; } s1[1]; | |
| gl_FragColor = s1[0].field; | |
| })"; | |
| compile(kShader); | |
| } | |
| // Tests that rewriting varyings for per-element element access does not cause crash. | |
| // At the time of writing a_ would be confused with a due to matrixes being flattened | |
| // for fragment inputs, and the new variables would be given semantic names separated | |
| // with _. This would cause confusion because semantic naming would filter underscores. | |
| TEST_F(MSLOutputTest, VaryingRewriteUnderscoreNoCrash) | |
| { | |
| const char kShader[] = R"(precision mediump float; | |
| varying mat2 a_; | |
| varying mat3 a; | |
| void main(){ | |
| gl_FragColor = vec4(a_) + vec4(a); | |
| })"; | |
| compile(kShader); | |
| } | |
| // Tests that rewriting attributes for per-element element access does not cause crash. | |
| // At the time of writing a_ would be confused with a due to matrixes being flattened | |
| // for fragment inputs, and the new variables would be given semantic names separated | |
| // with _. This would cause confusion because semantic naming would filter underscores. | |
| TEST_F(MSLVertexOutputTest, AttributeRewriteUnderscoreNoCrash) | |
| { | |
| const char kShader[] = R"(precision mediump float; | |
| attribute mat2 a_; | |
| attribute mat3 a; | |
| void main(){ | |
| gl_Position = vec4(a_) + vec4(a); | |
| })"; | |
| compile(kShader); | |
| } | |
| // Test that emulated clip distance varying passes AST validation | |
| TEST_F(MSLVertexOutputTest, ClipDistanceVarying) | |
| { | |
| getResources()->ANGLE_clip_cull_distance = 1; | |
| const char kShader[] = R"(#version 300 es | |
| #extension GL_ANGLE_clip_cull_distance:require | |
| void main(){gl_ClipDistance[0];})"; | |
| compile(kShader); | |
| } | |
| TEST_F(MSLVertexOutputTest, VertexIDIvecNoCrash) | |
| { | |
| const char kShader[] = R"(#version 300 es | |
| void main(){ivec2 xy=ivec2((+gl_VertexID));gl_Position=vec4((xy), 0,1);})"; | |
| compile(kShader); | |
| } |