// // 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 namespace sh { namespace { typedef std::vector 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 mock 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 mock 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(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 { mCompileOptions.initOutputVariables = true; if (getShaderType() == GL_VERTEX_SHADER) { mCompileOptions.initGLPosition = true; } 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() { mCompileOptions.initOutputVariables = true; } 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; } }; class InitOutputVariablesVertexShaderClipDistanceTest : public ShaderCompileTreeTest { public: InitOutputVariablesVertexShaderClipDistanceTest() { mCompileOptions.initOutputVariables = true; mCompileOptions.validateAST = true; } protected: ::GLenum getShaderType() const override { return GL_VERTEX_SHADER; } ShShaderSpec getShaderSpec() const override { return SH_GLES2_SPEC; } void initResources(ShBuiltInResources *resources) override { resources->APPLE_clip_distance = 1; resources->MaxClipDistances = 8; } }; // 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()); } // Mirrors ClipDistanceTest.ThreeClipDistancesRedeclared TEST_F(InitOutputVariablesVertexShaderClipDistanceTest, RedeclareClipDistance) { constexpr char shaderString[] = R"( #extension GL_APPLE_clip_distance : require varying highp float gl_ClipDistance[3]; void computeClipDistances(in vec4 position, in vec4 plane[3]) { gl_ClipDistance[0] = dot(position, plane[0]); gl_ClipDistance[1] = dot(position, plane[1]); gl_ClipDistance[2] = dot(position, plane[2]); } uniform vec4 u_plane[3]; attribute vec2 a_position; void main() { gl_Position = vec4(a_position, 0.0, 1.0); computeClipDistances(gl_Position, u_plane); })"; compileAssumeSuccess(shaderString); } } // namespace sh