Edit

kc3-lang/angle/src/tests/compiler_tests/InitOutputVariables_test.cpp

Branch :

  • Show log

    Commit

  • Author : Olli Etuaho
    Date : 2017-07-05 14:50:54
    Hash : cccf2b00
    Message : Reorganize AST traversal utility code Define TIntermTraverser and TIntermLValueTrackingTraverser in a separate header file. hash() function is moved out from TIntermTraverser as it is not related to the core functionality of traversing and transforming ASTs. Also reorganize some traversers to follow common conventions: - Intermediate output is now in OutputTree.h/.cpp - Max tree depth check is now in IsASTDepthBelowLimit.h/.cpp BUG=angleproject:1490 TEST=angle_unittests Change-Id: Id4968aa9d4e24d0c5bac90dc147fc9f310de0184 Reviewed-on: https://chromium-review.googlesource.com/559531 Reviewed-by: Jamie Madill <jmadill@chromium.org> Reviewed-by: Corentin Wallez <cwallez@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>

  • src/tests/compiler_tests/InitOutputVariables_test.cpp
  • //
    // Copyright (c) 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/FindMain.h"
    #include "compiler/translator/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->getSymbol() == candidate->getSymbol();
        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 TString &lValueName, const TType &type)
    {
        return new TIntermSymbol(0, lValueName, type);
    }
    
    ExpectedLValues CreateIndexedLValueNodeList(const TString &lValueName,
                                                TType elementType,
                                                unsigned arraySize)
    {
        ASSERT(elementType.isArray() == false);
        elementType.setArraySize(arraySize);
    
        ExpectedLValues expected(arraySize);
        for (unsigned index = 0u; index < arraySize; ++index)
        {
            expected[index] =
                new TIntermBinary(EOpIndexDirect, new TIntermSymbol(0, lValueName, elementType),
                                  TIntermTyped::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 TString &structName)
            : TIntermTraverser(true, false, false), mStructName(structName), mStructure(nullptr)
        {
        }
    
        void visitSymbol(TIntermSymbol *symbol) override
        {
            if (isStructureFound())
            {
                return;
            }
    
            TStructure *structure = symbol->getType().getStruct();
    
            if (structure != nullptr && structure->name() == mStructName)
            {
                mStructure = structure;
            }
        }
    
        bool isStructureFound() const { return mStructure != nullptr; };
        TStructure *getStructure() const { return mStructure; }
    
      private:
        TString mStructName;
        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)
        {
            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)
        {
            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("out1", TType(EbtFloat, EbpMedium, EvqVertexOut, 4)),
            CreateLValueNode("out2", TType(EbtInt, EbpLow, EvqFlatOut)),
            CreateLValueNode("out3", TType(EbtFloat, EbpMedium, EvqCentroidOut)),
            CreateLValueNode("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("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("MyS");
        mASTRoot->traverse(&findStruct);
        ASSERT(findStruct.isStructureFound());
    
        TType type(EbtStruct, EbpUndefined, EvqVertexOut);
        type.setStruct(findStruct.getStructure());
    
        TIntermTyped *expectedLValue = CreateLValueNode("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("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("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("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);
    
        ExpectedLValues expectedLValues =
            CreateIndexedLValueNodeList("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 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("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("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("gl_Position", TType(EbtFloat, EbpHigh, EvqPosition, 4));
        EXPECT_TRUE(verifier.isExpectedLValueFound(glPosition));
        EXPECT_EQ(1u, verifier.getCandidates().size());
    }
    
    }  // namespace sh