• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2017 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // InitOutputVariables_test.cpp: Tests correctness of the AST pass enabled through
7 // SH_INIT_OUTPUT_VARIABLES.
8 //
9 
10 #include "common/angleutils.h"
11 
12 #include "compiler/translator/SymbolTable.h"
13 #include "compiler/translator/tree_util/FindMain.h"
14 #include "compiler/translator/tree_util/IntermNode_util.h"
15 #include "compiler/translator/tree_util/IntermTraverse.h"
16 #include "tests/test_utils/ShaderCompileTreeTest.h"
17 
18 #include <algorithm>
19 
20 namespace sh
21 {
22 
23 namespace
24 {
25 
26 typedef std::vector<TIntermTyped *> ExpectedLValues;
27 
AreSymbolsTheSame(const TIntermSymbol * expected,const TIntermSymbol * candidate)28 bool AreSymbolsTheSame(const TIntermSymbol *expected, const TIntermSymbol *candidate)
29 {
30     if (expected == nullptr || candidate == nullptr)
31     {
32         return false;
33     }
34     const TType &expectedType  = expected->getType();
35     const TType &candidateType = candidate->getType();
36     const bool sameTypes       = expectedType == candidateType &&
37                            expectedType.getPrecision() == candidateType.getPrecision() &&
38                            expectedType.getQualifier() == candidateType.getQualifier();
39     const bool sameSymbols = (expected->variable().symbolType() == SymbolType::Empty &&
40                               candidate->variable().symbolType() == SymbolType::Empty) ||
41                              expected->getName() == candidate->getName();
42     return sameSymbols && sameTypes;
43 }
44 
AreLValuesTheSame(TIntermTyped * expected,TIntermTyped * candidate)45 bool AreLValuesTheSame(TIntermTyped *expected, TIntermTyped *candidate)
46 {
47     const TIntermBinary *expectedBinary = expected->getAsBinaryNode();
48     if (expectedBinary)
49     {
50         ASSERT(expectedBinary->getOp() == EOpIndexDirect);
51         const TIntermBinary *candidateBinary = candidate->getAsBinaryNode();
52         if (candidateBinary == nullptr || candidateBinary->getOp() != EOpIndexDirect)
53         {
54             return false;
55         }
56         if (expectedBinary->getRight()->getAsConstantUnion()->getIConst(0) !=
57             candidateBinary->getRight()->getAsConstantUnion()->getIConst(0))
58         {
59             return false;
60         }
61         return AreSymbolsTheSame(expectedBinary->getLeft()->getAsSymbolNode(),
62                                  candidateBinary->getLeft()->getAsSymbolNode());
63     }
64     return AreSymbolsTheSame(expected->getAsSymbolNode(), candidate->getAsSymbolNode());
65 }
66 
CreateLValueNode(const ImmutableString & lValueName,const TType & type)67 TIntermTyped *CreateLValueNode(const ImmutableString &lValueName, const TType &type)
68 {
69     // We're using a mock symbol table here, don't need to assign proper symbol ids to these nodes.
70     TSymbolTable symbolTable;
71     TVariable *variable =
72         new TVariable(&symbolTable, lValueName, new TType(type), SymbolType::UserDefined);
73     return new TIntermSymbol(variable);
74 }
75 
CreateIndexedLValueNodeList(const ImmutableString & lValueName,const TType & elementType,unsigned arraySize)76 ExpectedLValues CreateIndexedLValueNodeList(const ImmutableString &lValueName,
77                                             const TType &elementType,
78                                             unsigned arraySize)
79 {
80     ASSERT(elementType.isArray() == false);
81     TType *arrayType = new TType(elementType);
82     arrayType->makeArray(arraySize);
83 
84     // We're using a mock symbol table here, don't need to assign proper symbol ids to these nodes.
85     TSymbolTable symbolTable;
86     TVariable *variable =
87         new TVariable(&symbolTable, lValueName, arrayType, SymbolType::UserDefined);
88     TIntermSymbol *arraySymbol = new TIntermSymbol(variable);
89 
90     ExpectedLValues expected(arraySize);
91     for (unsigned index = 0u; index < arraySize; ++index)
92     {
93         expected[index] = new TIntermBinary(EOpIndexDirect, arraySymbol->deepCopy(),
94                                             CreateIndexNode(static_cast<int>(index)));
95     }
96     return expected;
97 }
98 
99 // VerifyOutputVariableInitializers traverses the subtree covering main and collects the lvalues in
100 // assignments for which the rvalue is an expression containing only zero constants.
101 class VerifyOutputVariableInitializers final : public TIntermTraverser
102 {
103   public:
VerifyOutputVariableInitializers(TIntermBlock * root)104     VerifyOutputVariableInitializers(TIntermBlock *root) : TIntermTraverser(true, false, false)
105     {
106         ASSERT(root != nullptr);
107 
108         // The traversal starts in the body of main because this is where the varyings and output
109         // variables are initialized.
110         sh::TIntermFunctionDefinition *main = FindMain(root);
111         ASSERT(main != nullptr);
112         main->traverse(this);
113     }
114 
visitBinary(Visit visit,TIntermBinary * node)115     bool visitBinary(Visit visit, TIntermBinary *node) override
116     {
117         if (node->getOp() == EOpAssign && IsZero(node->getRight()))
118         {
119             mCandidateLValues.push_back(node->getLeft());
120             return false;
121         }
122         return true;
123     }
124 
125     // The collected lvalues are considered valid if every expected lvalue in expectedLValues is
126     // matched by name and type with any lvalue in mCandidateLValues.
areAllExpectedLValuesFound(const ExpectedLValues & expectedLValues) const127     bool areAllExpectedLValuesFound(const ExpectedLValues &expectedLValues) const
128     {
129         for (size_t i = 0u; i < expectedLValues.size(); ++i)
130         {
131             if (!isExpectedLValueFound(expectedLValues[i]))
132             {
133                 return false;
134             }
135         }
136         return true;
137     }
138 
isExpectedLValueFound(TIntermTyped * expectedLValue) const139     bool isExpectedLValueFound(TIntermTyped *expectedLValue) const
140     {
141         bool isFound = false;
142         for (size_t j = 0; j < mCandidateLValues.size() && !isFound; ++j)
143         {
144             isFound = AreLValuesTheSame(expectedLValue, mCandidateLValues[j]);
145         }
146         return isFound;
147     }
148 
getCandidates() const149     const ExpectedLValues &getCandidates() const { return mCandidateLValues; }
150 
151   private:
152     ExpectedLValues mCandidateLValues;
153 };
154 
155 // Traverses the AST and records a pointer to a structure with a given name.
156 class FindStructByName final : public TIntermTraverser
157 {
158   public:
FindStructByName(const ImmutableString & structName)159     FindStructByName(const ImmutableString &structName)
160         : TIntermTraverser(true, false, false), mStructName(structName), mStructure(nullptr)
161     {}
162 
visitSymbol(TIntermSymbol * symbol)163     void visitSymbol(TIntermSymbol *symbol) override
164     {
165         if (isStructureFound())
166         {
167             return;
168         }
169 
170         const TStructure *structure = symbol->getType().getStruct();
171 
172         if (structure != nullptr && structure->symbolType() != SymbolType::Empty &&
173             structure->name() == mStructName)
174         {
175             mStructure = structure;
176         }
177     }
178 
isStructureFound() const179     bool isStructureFound() const { return mStructure != nullptr; }
getStructure() const180     const TStructure *getStructure() const { return mStructure; }
181 
182   private:
183     ImmutableString mStructName;
184     const TStructure *mStructure;
185 };
186 
187 }  // namespace
188 
189 class InitOutputVariablesWebGL2Test : public ShaderCompileTreeTest
190 {
191   public:
SetUp()192     void SetUp() override
193     {
194         mExtraCompileOptions |= SH_VARIABLES;
195         mExtraCompileOptions |= SH_INIT_OUTPUT_VARIABLES;
196         if (getShaderType() == GL_VERTEX_SHADER)
197         {
198             mExtraCompileOptions |= SH_INIT_GL_POSITION;
199         }
200         ShaderCompileTreeTest::SetUp();
201     }
202 
203   protected:
getShaderSpec() const204     ShShaderSpec getShaderSpec() const override { return SH_WEBGL2_SPEC; }
205 };
206 
207 class InitOutputVariablesWebGL2VertexShaderTest : public InitOutputVariablesWebGL2Test
208 {
209   protected:
getShaderType() const210     ::GLenum getShaderType() const override { return GL_VERTEX_SHADER; }
211 };
212 
213 class InitOutputVariablesWebGL2FragmentShaderTest : public InitOutputVariablesWebGL2Test
214 {
215   protected:
getShaderType() const216     ::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; }
initResources(ShBuiltInResources * resources)217     void initResources(ShBuiltInResources *resources) override
218     {
219         resources->EXT_draw_buffers = 1;
220         resources->MaxDrawBuffers   = 2;
221     }
222 };
223 
224 class InitOutputVariablesWebGL1FragmentShaderTest : public ShaderCompileTreeTest
225 {
226   public:
InitOutputVariablesWebGL1FragmentShaderTest()227     InitOutputVariablesWebGL1FragmentShaderTest()
228     {
229         mExtraCompileOptions |= SH_VARIABLES;
230         mExtraCompileOptions |= SH_INIT_OUTPUT_VARIABLES;
231     }
232 
233   protected:
getShaderType() const234     ::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; }
getShaderSpec() const235     ShShaderSpec getShaderSpec() const override { return SH_WEBGL_SPEC; }
initResources(ShBuiltInResources * resources)236     void initResources(ShBuiltInResources *resources) override
237     {
238         resources->EXT_draw_buffers = 1;
239         resources->MaxDrawBuffers   = 2;
240     }
241 };
242 
243 // Test the initialization of output variables with various qualifiers in a vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest,OutputAllQualifiers)244 TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputAllQualifiers)
245 {
246     const std::string &shaderString =
247         "#version 300 es\n"
248         "precision mediump float;\n"
249         "precision lowp int;\n"
250         "out vec4 out1;\n"
251         "flat out int out2;\n"
252         "centroid out float out3;\n"
253         "smooth out float out4;\n"
254         "void main() {\n"
255         "}\n";
256     compileAssumeSuccess(shaderString);
257     VerifyOutputVariableInitializers verifier(mASTRoot);
258 
259     ExpectedLValues expectedLValues = {
260         CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVertexOut, 4)),
261         CreateLValueNode(ImmutableString("out2"), TType(EbtInt, EbpLow, EvqFlatOut)),
262         CreateLValueNode(ImmutableString("out3"), TType(EbtFloat, EbpMedium, EvqCentroidOut)),
263         CreateLValueNode(ImmutableString("out4"), TType(EbtFloat, EbpMedium, EvqSmoothOut))};
264     EXPECT_TRUE(verifier.areAllExpectedLValuesFound(expectedLValues));
265 }
266 
267 // Test the initialization of an output array in a vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest,OutputArray)268 TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputArray)
269 {
270     const std::string &shaderString =
271         "#version 300 es\n"
272         "precision mediump float;\n"
273         "out float out1[2];\n"
274         "void main() {\n"
275         "}\n";
276     compileAssumeSuccess(shaderString);
277     VerifyOutputVariableInitializers verifier(mASTRoot);
278 
279     ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
280         ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVertexOut), 2);
281     EXPECT_TRUE(verifier.areAllExpectedLValuesFound(expectedLValues));
282 }
283 
284 // Test the initialization of a struct output variable in a vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest,OutputStruct)285 TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputStruct)
286 {
287     const std::string &shaderString =
288         "#version 300 es\n"
289         "precision mediump float;\n"
290         "struct MyS{\n"
291         "   float a;\n"
292         "   float b;\n"
293         "};\n"
294         "out MyS out1;\n"
295         "void main() {\n"
296         "}\n";
297     compileAssumeSuccess(shaderString);
298     VerifyOutputVariableInitializers verifier(mASTRoot);
299 
300     FindStructByName findStruct(ImmutableString("MyS"));
301     mASTRoot->traverse(&findStruct);
302     ASSERT(findStruct.isStructureFound());
303 
304     TType type(findStruct.getStructure(), false);
305     type.setQualifier(EvqVertexOut);
306 
307     TIntermTyped *expectedLValue = CreateLValueNode(ImmutableString("out1"), type);
308     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue));
309     delete expectedLValue;
310 }
311 
312 // Test the initialization of a varying variable in an ESSL1 vertex shader.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest,OutputFromESSL1Shader)313 TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputFromESSL1Shader)
314 {
315     const std::string &shaderString =
316         "precision mediump float;\n"
317         "varying vec4 out1;\n"
318         "void main() {\n"
319         "}\n";
320     compileAssumeSuccess(shaderString);
321     VerifyOutputVariableInitializers verifier(mASTRoot);
322 
323     TIntermTyped *expectedLValue =
324         CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVaryingOut, 4));
325     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue));
326     delete expectedLValue;
327 }
328 
329 // Test the initialization of output variables in a fragment shader.
TEST_F(InitOutputVariablesWebGL2FragmentShaderTest,Output)330 TEST_F(InitOutputVariablesWebGL2FragmentShaderTest, Output)
331 {
332     const std::string &shaderString =
333         "#version 300 es\n"
334         "precision mediump float;\n"
335         "out vec4 out1;\n"
336         "void main() {\n"
337         "}\n";
338     compileAssumeSuccess(shaderString);
339     VerifyOutputVariableInitializers verifier(mASTRoot);
340 
341     TIntermTyped *expectedLValue =
342         CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqFragmentOut, 4));
343     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue));
344     delete expectedLValue;
345 }
346 
347 // Test the initialization of gl_FragData in a WebGL2 ESSL1 fragment shader. Only writes to
348 // gl_FragData[0] should be found.
TEST_F(InitOutputVariablesWebGL2FragmentShaderTest,FragData)349 TEST_F(InitOutputVariablesWebGL2FragmentShaderTest, FragData)
350 {
351     const std::string &shaderString =
352         "precision mediump float;\n"
353         "void main() {\n"
354         "   gl_FragData[0] = vec4(1.);\n"
355         "}\n";
356     compileAssumeSuccess(shaderString);
357     VerifyOutputVariableInitializers verifier(mASTRoot);
358 
359     ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
360         ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 1);
361     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0]));
362     EXPECT_EQ(1u, verifier.getCandidates().size());
363 }
364 
365 // Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader. Only writes to
366 // gl_FragData[0] should be found.
TEST_F(InitOutputVariablesWebGL1FragmentShaderTest,FragData)367 TEST_F(InitOutputVariablesWebGL1FragmentShaderTest, FragData)
368 {
369     const std::string &shaderString =
370         "precision mediump float;\n"
371         "void main() {\n"
372         "   gl_FragData[0] = vec4(1.);\n"
373         "}\n";
374     compileAssumeSuccess(shaderString);
375     VerifyOutputVariableInitializers verifier(mASTRoot);
376 
377     // In the symbol table, gl_FragData array has 2 elements. However, only the 1st one should be
378     // initialized.
379     ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
380         ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 2);
381     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0]));
382     EXPECT_EQ(1u, verifier.getCandidates().size());
383 }
384 
385 // Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader with GL_EXT_draw_buffers
386 // enabled. All attachment slots should be initialized.
TEST_F(InitOutputVariablesWebGL1FragmentShaderTest,FragDataWithDrawBuffersExtEnabled)387 TEST_F(InitOutputVariablesWebGL1FragmentShaderTest, FragDataWithDrawBuffersExtEnabled)
388 {
389     const std::string &shaderString =
390         "#extension GL_EXT_draw_buffers : enable\n"
391         "precision mediump float;\n"
392         "void main() {\n"
393         "   gl_FragData[0] = vec4(1.);\n"
394         "}\n";
395     compileAssumeSuccess(shaderString);
396     VerifyOutputVariableInitializers verifier(mASTRoot);
397 
398     ExpectedLValues expectedLValues = CreateIndexedLValueNodeList(
399         ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 2);
400     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0]));
401     EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[1]));
402     EXPECT_EQ(2u, verifier.getCandidates().size());
403 }
404 
405 // Test that gl_Position is initialized once in case it is not statically used and both
406 // SH_INIT_OUTPUT_VARIABLES and SH_INIT_GL_POSITION flags are set.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest,InitGLPositionWhenNotStaticallyUsed)407 TEST_F(InitOutputVariablesWebGL2VertexShaderTest, InitGLPositionWhenNotStaticallyUsed)
408 {
409     const std::string &shaderString =
410         "#version 300 es\n"
411         "precision highp float;\n"
412         "void main() {\n"
413         "}\n";
414     compileAssumeSuccess(shaderString);
415     VerifyOutputVariableInitializers verifier(mASTRoot);
416 
417     TIntermTyped *glPosition =
418         CreateLValueNode(ImmutableString("gl_Position"), TType(EbtFloat, EbpHigh, EvqPosition, 4));
419     EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition));
420     EXPECT_EQ(1u, verifier.getCandidates().size());
421 }
422 
423 // Test that gl_Position is initialized once in case it is statically used and both
424 // SH_INIT_OUTPUT_VARIABLES and SH_INIT_GL_POSITION flags are set.
TEST_F(InitOutputVariablesWebGL2VertexShaderTest,InitGLPositionOnceWhenStaticallyUsed)425 TEST_F(InitOutputVariablesWebGL2VertexShaderTest, InitGLPositionOnceWhenStaticallyUsed)
426 {
427     const std::string &shaderString =
428         "#version 300 es\n"
429         "precision highp float;\n"
430         "void main() {\n"
431         "    gl_Position = vec4(1.0);\n"
432         "}\n";
433     compileAssumeSuccess(shaderString);
434     VerifyOutputVariableInitializers verifier(mASTRoot);
435 
436     TIntermTyped *glPosition =
437         CreateLValueNode(ImmutableString("gl_Position"), TType(EbtFloat, EbpHigh, EvqPosition, 4));
438     EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition));
439     EXPECT_EQ(1u, verifier.getCandidates().size());
440 }
441 
442 }  // namespace sh
443