| // |
| // 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. |
| // |
| // InitOutputVariables_test.cpp: Tests correctness of the AST pass enabled through |
| // SH_INIT_OUTPUT_VARIABLES. |
| // |
| |
| #include "common/angleutils.h" |
| |
| #include "compiler/translator/SymbolTable.h" |
| #include "compiler/translator/tree_util/FindMain.h" |
| #include "compiler/translator/tree_util/IntermNode_util.h" |
| #include "compiler/translator/tree_util/IntermTraverse.h" |
| #include "tests/test_utils/ShaderCompileTreeTest.h" |
| |
| #include <algorithm> |
| |
| namespace sh |
| { |
| |
| namespace |
| { |
| |
| typedef std::vector<TIntermTyped *> ExpectedLValues; |
| |
| bool AreSymbolsTheSame(const TIntermSymbol *expected, const TIntermSymbol *candidate) |
| { |
| if (expected == nullptr || candidate == nullptr) |
| { |
| return false; |
| } |
| const TType &expectedType = expected->getType(); |
| const TType &candidateType = candidate->getType(); |
| const bool sameTypes = expectedType == candidateType && |
| expectedType.getPrecision() == candidateType.getPrecision() && |
| expectedType.getQualifier() == candidateType.getQualifier(); |
| const bool sameSymbols = (expected->variable().symbolType() == SymbolType::Empty && |
| candidate->variable().symbolType() == SymbolType::Empty) || |
| expected->getName() == candidate->getName(); |
| return sameSymbols && sameTypes; |
| } |
| |
| bool AreLValuesTheSame(TIntermTyped *expected, TIntermTyped *candidate) |
| { |
| const TIntermBinary *expectedBinary = expected->getAsBinaryNode(); |
| if (expectedBinary) |
| { |
| ASSERT(expectedBinary->getOp() == EOpIndexDirect); |
| const TIntermBinary *candidateBinary = candidate->getAsBinaryNode(); |
| if (candidateBinary == nullptr || candidateBinary->getOp() != EOpIndexDirect) |
| { |
| return false; |
| } |
| if (expectedBinary->getRight()->getAsConstantUnion()->getIConst(0) != |
| candidateBinary->getRight()->getAsConstantUnion()->getIConst(0)) |
| { |
| return false; |
| } |
| return AreSymbolsTheSame(expectedBinary->getLeft()->getAsSymbolNode(), |
| candidateBinary->getLeft()->getAsSymbolNode()); |
| } |
| return AreSymbolsTheSame(expected->getAsSymbolNode(), candidate->getAsSymbolNode()); |
| } |
| |
| TIntermTyped *CreateLValueNode(const ImmutableString &lValueName, const TType &type) |
| { |
| // We're using a dummy symbol table here, don't need to assign proper symbol ids to these nodes. |
| TSymbolTable symbolTable; |
| TVariable *variable = |
| new TVariable(&symbolTable, lValueName, new TType(type), SymbolType::UserDefined); |
| return new TIntermSymbol(variable); |
| } |
| |
| ExpectedLValues CreateIndexedLValueNodeList(const ImmutableString &lValueName, |
| const TType &elementType, |
| unsigned arraySize) |
| { |
| ASSERT(elementType.isArray() == false); |
| TType *arrayType = new TType(elementType); |
| arrayType->makeArray(arraySize); |
| |
| // We're using a dummy symbol table here, don't need to assign proper symbol ids to these nodes. |
| TSymbolTable symbolTable; |
| TVariable *variable = |
| new TVariable(&symbolTable, lValueName, arrayType, SymbolType::UserDefined); |
| TIntermSymbol *arraySymbol = new TIntermSymbol(variable); |
| |
| ExpectedLValues expected(arraySize); |
| for (unsigned index = 0u; index < arraySize; ++index) |
| { |
| expected[index] = new TIntermBinary(EOpIndexDirect, arraySymbol->deepCopy(), |
| CreateIndexNode(static_cast<int>(index))); |
| } |
| return expected; |
| } |
| |
| // VerifyOutputVariableInitializers traverses the subtree covering main and collects the lvalues in |
| // assignments for which the rvalue is an expression containing only zero constants. |
| class VerifyOutputVariableInitializers final : public TIntermTraverser |
| { |
| public: |
| VerifyOutputVariableInitializers(TIntermBlock *root) : TIntermTraverser(true, false, false) |
| { |
| ASSERT(root != nullptr); |
| |
| // The traversal starts in the body of main because this is where the varyings and output |
| // variables are initialized. |
| sh::TIntermFunctionDefinition *main = FindMain(root); |
| ASSERT(main != nullptr); |
| main->traverse(this); |
| } |
| |
| bool visitBinary(Visit visit, TIntermBinary *node) override |
| { |
| if (node->getOp() == EOpAssign && IsZero(node->getRight())) |
| { |
| mCandidateLValues.push_back(node->getLeft()); |
| return false; |
| } |
| return true; |
| } |
| |
| // The collected lvalues are considered valid if every expected lvalue in expectedLValues is |
| // matched by name and type with any lvalue in mCandidateLValues. |
| bool areAllExpectedLValuesFound(const ExpectedLValues &expectedLValues) const |
| { |
| for (size_t i = 0u; i < expectedLValues.size(); ++i) |
| { |
| if (!isExpectedLValueFound(expectedLValues[i])) |
| { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool isExpectedLValueFound(TIntermTyped *expectedLValue) const |
| { |
| bool isFound = false; |
| for (size_t j = 0; j < mCandidateLValues.size() && !isFound; ++j) |
| { |
| isFound = AreLValuesTheSame(expectedLValue, mCandidateLValues[j]); |
| } |
| return isFound; |
| } |
| |
| const ExpectedLValues &getCandidates() const { return mCandidateLValues; } |
| |
| private: |
| ExpectedLValues mCandidateLValues; |
| }; |
| |
| // Traverses the AST and records a pointer to a structure with a given name. |
| class FindStructByName final : public TIntermTraverser |
| { |
| public: |
| FindStructByName(const ImmutableString &structName) |
| : TIntermTraverser(true, false, false), mStructName(structName), mStructure(nullptr) |
| {} |
| |
| void visitSymbol(TIntermSymbol *symbol) override |
| { |
| if (isStructureFound()) |
| { |
| return; |
| } |
| |
| const TStructure *structure = symbol->getType().getStruct(); |
| |
| if (structure != nullptr && structure->symbolType() != SymbolType::Empty && |
| structure->name() == mStructName) |
| { |
| mStructure = structure; |
| } |
| } |
| |
| bool isStructureFound() const { return mStructure != nullptr; } |
| const TStructure *getStructure() const { return mStructure; } |
| |
| private: |
| ImmutableString mStructName; |
| const TStructure *mStructure; |
| }; |
| |
| } // namespace |
| |
| class InitOutputVariablesWebGL2Test : public ShaderCompileTreeTest |
| { |
| public: |
| void SetUp() override |
| { |
| mExtraCompileOptions |= SH_VARIABLES; |
| mExtraCompileOptions |= SH_INIT_OUTPUT_VARIABLES; |
| if (getShaderType() == GL_VERTEX_SHADER) |
| { |
| mExtraCompileOptions |= SH_INIT_GL_POSITION; |
| } |
| ShaderCompileTreeTest::SetUp(); |
| } |
| |
| protected: |
| ShShaderSpec getShaderSpec() const override { return SH_WEBGL2_SPEC; } |
| }; |
| |
| class InitOutputVariablesWebGL2VertexShaderTest : public InitOutputVariablesWebGL2Test |
| { |
| protected: |
| ::GLenum getShaderType() const override { return GL_VERTEX_SHADER; } |
| }; |
| |
| class InitOutputVariablesWebGL2FragmentShaderTest : public InitOutputVariablesWebGL2Test |
| { |
| protected: |
| ::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; } |
| void initResources(ShBuiltInResources *resources) override |
| { |
| resources->EXT_draw_buffers = 1; |
| resources->MaxDrawBuffers = 2; |
| } |
| }; |
| |
| class InitOutputVariablesWebGL1FragmentShaderTest : public ShaderCompileTreeTest |
| { |
| public: |
| InitOutputVariablesWebGL1FragmentShaderTest() |
| { |
| mExtraCompileOptions |= SH_VARIABLES; |
| mExtraCompileOptions |= SH_INIT_OUTPUT_VARIABLES; |
| } |
| |
| protected: |
| ::GLenum getShaderType() const override { return GL_FRAGMENT_SHADER; } |
| ShShaderSpec getShaderSpec() const override { return SH_WEBGL_SPEC; } |
| void initResources(ShBuiltInResources *resources) override |
| { |
| resources->EXT_draw_buffers = 1; |
| resources->MaxDrawBuffers = 2; |
| } |
| }; |
| |
| // Test the initialization of output variables with various qualifiers in a vertex shader. |
| TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputAllQualifiers) |
| { |
| const std::string &shaderString = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "precision lowp int;\n" |
| "out vec4 out1;\n" |
| "flat out int out2;\n" |
| "centroid out float out3;\n" |
| "smooth out float out4;\n" |
| "void main() {\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| ExpectedLValues expectedLValues = { |
| CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVertexOut, 4)), |
| CreateLValueNode(ImmutableString("out2"), TType(EbtInt, EbpLow, EvqFlatOut)), |
| CreateLValueNode(ImmutableString("out3"), TType(EbtFloat, EbpMedium, EvqCentroidOut)), |
| CreateLValueNode(ImmutableString("out4"), TType(EbtFloat, EbpMedium, EvqSmoothOut))}; |
| EXPECT_TRUE(verifier.areAllExpectedLValuesFound(expectedLValues)); |
| } |
| |
| // Test the initialization of an output array in a vertex shader. |
| TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputArray) |
| { |
| const std::string &shaderString = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "out float out1[2];\n" |
| "void main() {\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| ExpectedLValues expectedLValues = CreateIndexedLValueNodeList( |
| ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVertexOut), 2); |
| EXPECT_TRUE(verifier.areAllExpectedLValuesFound(expectedLValues)); |
| } |
| |
| // Test the initialization of a struct output variable in a vertex shader. |
| TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputStruct) |
| { |
| const std::string &shaderString = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "struct MyS{\n" |
| " float a;\n" |
| " float b;\n" |
| "};\n" |
| "out MyS out1;\n" |
| "void main() {\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| FindStructByName findStruct(ImmutableString("MyS")); |
| mASTRoot->traverse(&findStruct); |
| ASSERT(findStruct.isStructureFound()); |
| |
| TType type(findStruct.getStructure(), false); |
| type.setQualifier(EvqVertexOut); |
| |
| TIntermTyped *expectedLValue = CreateLValueNode(ImmutableString("out1"), type); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue)); |
| delete expectedLValue; |
| } |
| |
| // Test the initialization of a varying variable in an ESSL1 vertex shader. |
| TEST_F(InitOutputVariablesWebGL2VertexShaderTest, OutputFromESSL1Shader) |
| { |
| const std::string &shaderString = |
| "precision mediump float;\n" |
| "varying vec4 out1;\n" |
| "void main() {\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| TIntermTyped *expectedLValue = |
| CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqVaryingOut, 4)); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue)); |
| delete expectedLValue; |
| } |
| |
| // Test the initialization of output variables in a fragment shader. |
| TEST_F(InitOutputVariablesWebGL2FragmentShaderTest, Output) |
| { |
| const std::string &shaderString = |
| "#version 300 es\n" |
| "precision mediump float;\n" |
| "out vec4 out1;\n" |
| "void main() {\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| TIntermTyped *expectedLValue = |
| CreateLValueNode(ImmutableString("out1"), TType(EbtFloat, EbpMedium, EvqFragmentOut, 4)); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValue)); |
| delete expectedLValue; |
| } |
| |
| // Test the initialization of gl_FragData in a WebGL2 ESSL1 fragment shader. Only writes to |
| // gl_FragData[0] should be found. |
| TEST_F(InitOutputVariablesWebGL2FragmentShaderTest, FragData) |
| { |
| const std::string &shaderString = |
| "precision mediump float;\n" |
| "void main() {\n" |
| " gl_FragData[0] = vec4(1.);\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| ExpectedLValues expectedLValues = CreateIndexedLValueNodeList( |
| ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 1); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0])); |
| EXPECT_EQ(1u, verifier.getCandidates().size()); |
| } |
| |
| // Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader. Only writes to |
| // gl_FragData[0] should be found. |
| TEST_F(InitOutputVariablesWebGL1FragmentShaderTest, FragData) |
| { |
| const std::string &shaderString = |
| "precision mediump float;\n" |
| "void main() {\n" |
| " gl_FragData[0] = vec4(1.);\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| // In the symbol table, gl_FragData array has 2 elements. However, only the 1st one should be |
| // initialized. |
| ExpectedLValues expectedLValues = CreateIndexedLValueNodeList( |
| ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 2); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0])); |
| EXPECT_EQ(1u, verifier.getCandidates().size()); |
| } |
| |
| // Test the initialization of gl_FragData in a WebGL1 ESSL1 fragment shader with GL_EXT_draw_buffers |
| // enabled. All attachment slots should be initialized. |
| TEST_F(InitOutputVariablesWebGL1FragmentShaderTest, FragDataWithDrawBuffersExtEnabled) |
| { |
| const std::string &shaderString = |
| "#extension GL_EXT_draw_buffers : enable\n" |
| "precision mediump float;\n" |
| "void main() {\n" |
| " gl_FragData[0] = vec4(1.);\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| ExpectedLValues expectedLValues = CreateIndexedLValueNodeList( |
| ImmutableString("gl_FragData"), TType(EbtFloat, EbpMedium, EvqFragData, 4), 2); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[0])); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(expectedLValues[1])); |
| EXPECT_EQ(2u, verifier.getCandidates().size()); |
| } |
| |
| // Test that gl_Position is initialized once in case it is not statically used and both |
| // SH_INIT_OUTPUT_VARIABLES and SH_INIT_GL_POSITION flags are set. |
| TEST_F(InitOutputVariablesWebGL2VertexShaderTest, InitGLPositionWhenNotStaticallyUsed) |
| { |
| const std::string &shaderString = |
| "#version 300 es\n" |
| "precision highp float;\n" |
| "void main() {\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| TIntermTyped *glPosition = |
| CreateLValueNode(ImmutableString("gl_Position"), TType(EbtFloat, EbpHigh, EvqPosition, 4)); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition)); |
| EXPECT_EQ(1u, verifier.getCandidates().size()); |
| } |
| |
| // Test that gl_Position is initialized once in case it is statically used and both |
| // SH_INIT_OUTPUT_VARIABLES and SH_INIT_GL_POSITION flags are set. |
| TEST_F(InitOutputVariablesWebGL2VertexShaderTest, InitGLPositionOnceWhenStaticallyUsed) |
| { |
| const std::string &shaderString = |
| "#version 300 es\n" |
| "precision highp float;\n" |
| "void main() {\n" |
| " gl_Position = vec4(1.0);\n" |
| "}\n"; |
| compileAssumeSuccess(shaderString); |
| VerifyOutputVariableInitializers verifier(mASTRoot); |
| |
| TIntermTyped *glPosition = |
| CreateLValueNode(ImmutableString("gl_Position"), TType(EbtFloat, EbpHigh, EvqPosition, 4)); |
| EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition)); |
| EXPECT_EQ(1u, verifier.getCandidates().size()); |
| } |
| |
| } // namespace sh |