| // | |
| // Copyright 2002 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. | |
| // | |
| // Analysis of the AST needed for HLSL generation | |
| #include "compiler/translator/ASTMetadataHLSL.h" | |
| #include "compiler/translator/CallDAG.h" | |
| #include "compiler/translator/SymbolTable.h" | |
| #include "compiler/translator/tree_util/IntermTraverse.h" | |
| namespace sh | |
| { | |
| namespace | |
| { | |
| // Class used to traverse the AST of a function definition, checking if the | |
| // function uses a gradient, and writing the set of control flow using gradients. | |
| // It assumes that the analysis has already been made for the function's | |
| // callees. | |
| class PullGradient : public TIntermTraverser | |
| { | |
| public: | |
| PullGradient(MetadataList *metadataList, size_t index, const CallDAG &dag) | |
| : TIntermTraverser(true, false, true), | |
| mMetadataList(metadataList), | |
| mMetadata(&(*metadataList)[index]), | |
| mIndex(index), | |
| mDag(dag) | |
| { | |
| ASSERT(index < metadataList->size()); | |
| // ESSL 100 builtin gradient functions | |
| mGradientBuiltinFunctions.insert(ImmutableString("texture2D")); | |
| mGradientBuiltinFunctions.insert(ImmutableString("texture2DProj")); | |
| mGradientBuiltinFunctions.insert(ImmutableString("textureCube")); | |
| // ESSL 300 builtin gradient functions | |
| mGradientBuiltinFunctions.insert(ImmutableString("texture")); | |
| mGradientBuiltinFunctions.insert(ImmutableString("textureProj")); | |
| mGradientBuiltinFunctions.insert(ImmutableString("textureOffset")); | |
| mGradientBuiltinFunctions.insert(ImmutableString("textureProjOffset")); | |
| // ESSL 310 doesn't add builtin gradient functions | |
| } | |
| void traverse(TIntermFunctionDefinition *node) | |
| { | |
| node->traverse(this); | |
| ASSERT(mParents.empty()); | |
| } | |
| // Called when a gradient operation or a call to a function using a gradient is found. | |
| void onGradient() | |
| { | |
| mMetadata->mUsesGradient = true; | |
| // Mark the latest control flow as using a gradient. | |
| if (!mParents.empty()) | |
| { | |
| mMetadata->mControlFlowsContainingGradient.insert(mParents.back()); | |
| } | |
| } | |
| void visitControlFlow(Visit visit, TIntermNode *node) | |
| { | |
| if (visit == PreVisit) | |
| { | |
| mParents.push_back(node); | |
| } | |
| else if (visit == PostVisit) | |
| { | |
| ASSERT(mParents.back() == node); | |
| mParents.pop_back(); | |
| // A control flow's using a gradient means its parents are too. | |
| if (mMetadata->mControlFlowsContainingGradient.count(node) > 0 && !mParents.empty()) | |
| { | |
| mMetadata->mControlFlowsContainingGradient.insert(mParents.back()); | |
| } | |
| } | |
| } | |
| bool visitLoop(Visit visit, TIntermLoop *loop) override | |
| { | |
| visitControlFlow(visit, loop); | |
| return true; | |
| } | |
| bool visitIfElse(Visit visit, TIntermIfElse *ifElse) override | |
| { | |
| visitControlFlow(visit, ifElse); | |
| return true; | |
| } | |
| bool visitUnary(Visit visit, TIntermUnary *node) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| switch (node->getOp()) | |
| { | |
| case EOpDFdx: | |
| case EOpDFdy: | |
| case EOpFwidth: | |
| onGradient(); | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| return true; | |
| } | |
| bool visitAggregate(Visit visit, TIntermAggregate *node) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| if (node->getOp() == EOpCallFunctionInAST) | |
| { | |
| size_t calleeIndex = mDag.findIndex(node->getFunction()->uniqueId()); | |
| ASSERT(calleeIndex != CallDAG::InvalidIndex && calleeIndex < mIndex); | |
| if ((*mMetadataList)[calleeIndex].mUsesGradient) | |
| { | |
| onGradient(); | |
| } | |
| } | |
| else if (node->getOp() == EOpCallBuiltInFunction) | |
| { | |
| if (mGradientBuiltinFunctions.find(node->getFunction()->name()) != | |
| mGradientBuiltinFunctions.end()) | |
| { | |
| onGradient(); | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| private: | |
| MetadataList *mMetadataList; | |
| ASTMetadataHLSL *mMetadata; | |
| size_t mIndex; | |
| const CallDAG &mDag; | |
| // Contains a stack of the control flow nodes that are parents of the node being | |
| // currently visited. It is used to mark control flows using a gradient. | |
| std::vector<TIntermNode *> mParents; | |
| // A list of builtin functions that use gradients | |
| std::set<ImmutableString> mGradientBuiltinFunctions; | |
| }; | |
| // Traverses the AST of a function definition to compute the the discontinuous loops | |
| // and the if statements containing gradient loops. It assumes that the gradient loops | |
| // (loops that contain a gradient) have already been computed and that it has already | |
| // traversed the current function's callees. | |
| class PullComputeDiscontinuousAndGradientLoops : public TIntermTraverser | |
| { | |
| public: | |
| PullComputeDiscontinuousAndGradientLoops(MetadataList *metadataList, | |
| size_t index, | |
| const CallDAG &dag) | |
| : TIntermTraverser(true, false, true), | |
| mMetadataList(metadataList), | |
| mMetadata(&(*metadataList)[index]), | |
| mIndex(index), | |
| mDag(dag) | |
| {} | |
| void traverse(TIntermFunctionDefinition *node) | |
| { | |
| node->traverse(this); | |
| ASSERT(mLoopsAndSwitches.empty()); | |
| ASSERT(mIfs.empty()); | |
| } | |
| // Called when traversing a gradient loop or a call to a function with a | |
| // gradient loop in its call graph. | |
| void onGradientLoop() | |
| { | |
| mMetadata->mHasGradientLoopInCallGraph = true; | |
| // Mark the latest if as using a discontinuous loop. | |
| if (!mIfs.empty()) | |
| { | |
| mMetadata->mIfsContainingGradientLoop.insert(mIfs.back()); | |
| } | |
| } | |
| bool visitLoop(Visit visit, TIntermLoop *loop) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| mLoopsAndSwitches.push_back(loop); | |
| if (mMetadata->hasGradientInCallGraph(loop)) | |
| { | |
| onGradientLoop(); | |
| } | |
| } | |
| else if (visit == PostVisit) | |
| { | |
| ASSERT(mLoopsAndSwitches.back() == loop); | |
| mLoopsAndSwitches.pop_back(); | |
| } | |
| return true; | |
| } | |
| bool visitIfElse(Visit visit, TIntermIfElse *node) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| mIfs.push_back(node); | |
| } | |
| else if (visit == PostVisit) | |
| { | |
| ASSERT(mIfs.back() == node); | |
| mIfs.pop_back(); | |
| // An if using a discontinuous loop means its parents ifs are also discontinuous. | |
| if (mMetadata->mIfsContainingGradientLoop.count(node) > 0 && !mIfs.empty()) | |
| { | |
| mMetadata->mIfsContainingGradientLoop.insert(mIfs.back()); | |
| } | |
| } | |
| return true; | |
| } | |
| bool visitBranch(Visit visit, TIntermBranch *node) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| switch (node->getFlowOp()) | |
| { | |
| case EOpBreak: | |
| { | |
| ASSERT(!mLoopsAndSwitches.empty()); | |
| TIntermLoop *loop = mLoopsAndSwitches.back()->getAsLoopNode(); | |
| if (loop != nullptr) | |
| { | |
| mMetadata->mDiscontinuousLoops.insert(loop); | |
| } | |
| } | |
| break; | |
| case EOpContinue: | |
| { | |
| ASSERT(!mLoopsAndSwitches.empty()); | |
| TIntermLoop *loop = nullptr; | |
| size_t i = mLoopsAndSwitches.size(); | |
| while (loop == nullptr && i > 0) | |
| { | |
| --i; | |
| loop = mLoopsAndSwitches.at(i)->getAsLoopNode(); | |
| } | |
| ASSERT(loop != nullptr); | |
| mMetadata->mDiscontinuousLoops.insert(loop); | |
| } | |
| break; | |
| case EOpKill: | |
| case EOpReturn: | |
| // A return or discard jumps out of all the enclosing loops | |
| if (!mLoopsAndSwitches.empty()) | |
| { | |
| for (TIntermNode *intermNode : mLoopsAndSwitches) | |
| { | |
| TIntermLoop *loop = intermNode->getAsLoopNode(); | |
| if (loop) | |
| { | |
| mMetadata->mDiscontinuousLoops.insert(loop); | |
| } | |
| } | |
| } | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| } | |
| } | |
| return true; | |
| } | |
| bool visitAggregate(Visit visit, TIntermAggregate *node) override | |
| { | |
| if (visit == PreVisit && node->getOp() == EOpCallFunctionInAST) | |
| { | |
| size_t calleeIndex = mDag.findIndex(node->getFunction()->uniqueId()); | |
| ASSERT(calleeIndex != CallDAG::InvalidIndex && calleeIndex < mIndex); | |
| if ((*mMetadataList)[calleeIndex].mHasGradientLoopInCallGraph) | |
| { | |
| onGradientLoop(); | |
| } | |
| } | |
| return true; | |
| } | |
| bool visitSwitch(Visit visit, TIntermSwitch *node) override | |
| { | |
| if (visit == PreVisit) | |
| { | |
| mLoopsAndSwitches.push_back(node); | |
| } | |
| else if (visit == PostVisit) | |
| { | |
| ASSERT(mLoopsAndSwitches.back() == node); | |
| mLoopsAndSwitches.pop_back(); | |
| } | |
| return true; | |
| } | |
| private: | |
| MetadataList *mMetadataList; | |
| ASTMetadataHLSL *mMetadata; | |
| size_t mIndex; | |
| const CallDAG &mDag; | |
| std::vector<TIntermNode *> mLoopsAndSwitches; | |
| std::vector<TIntermIfElse *> mIfs; | |
| }; | |
| // Tags all the functions called in a discontinuous loop | |
| class PushDiscontinuousLoops : public TIntermTraverser | |
| { | |
| public: | |
| PushDiscontinuousLoops(MetadataList *metadataList, size_t index, const CallDAG &dag) | |
| : TIntermTraverser(true, true, true), | |
| mMetadataList(metadataList), | |
| mMetadata(&(*metadataList)[index]), | |
| mIndex(index), | |
| mDag(dag), | |
| mNestedDiscont(mMetadata->mCalledInDiscontinuousLoop ? 1 : 0) | |
| {} | |
| void traverse(TIntermFunctionDefinition *node) | |
| { | |
| node->traverse(this); | |
| ASSERT(mNestedDiscont == (mMetadata->mCalledInDiscontinuousLoop ? 1 : 0)); | |
| } | |
| bool visitLoop(Visit visit, TIntermLoop *loop) override | |
| { | |
| bool isDiscontinuous = mMetadata->mDiscontinuousLoops.count(loop) > 0; | |
| if (visit == PreVisit && isDiscontinuous) | |
| { | |
| mNestedDiscont++; | |
| } | |
| else if (visit == PostVisit && isDiscontinuous) | |
| { | |
| mNestedDiscont--; | |
| } | |
| return true; | |
| } | |
| bool visitAggregate(Visit visit, TIntermAggregate *node) override | |
| { | |
| switch (node->getOp()) | |
| { | |
| case EOpCallFunctionInAST: | |
| if (visit == PreVisit && mNestedDiscont > 0) | |
| { | |
| size_t calleeIndex = mDag.findIndex(node->getFunction()->uniqueId()); | |
| ASSERT(calleeIndex != CallDAG::InvalidIndex && calleeIndex < mIndex); | |
| (*mMetadataList)[calleeIndex].mCalledInDiscontinuousLoop = true; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return true; | |
| } | |
| private: | |
| MetadataList *mMetadataList; | |
| ASTMetadataHLSL *mMetadata; | |
| size_t mIndex; | |
| const CallDAG &mDag; | |
| int mNestedDiscont; | |
| }; | |
| } // namespace | |
| bool ASTMetadataHLSL::hasGradientInCallGraph(TIntermLoop *node) | |
| { | |
| return mControlFlowsContainingGradient.count(node) > 0; | |
| } | |
| bool ASTMetadataHLSL::hasGradientLoop(TIntermIfElse *node) | |
| { | |
| return mIfsContainingGradientLoop.count(node) > 0; | |
| } | |
| MetadataList CreateASTMetadataHLSL(TIntermNode *root, const CallDAG &callDag) | |
| { | |
| MetadataList metadataList(callDag.size()); | |
| // Compute all the information related to when gradient operations are used. | |
| // We want to know for each function and control flow operation if they have | |
| // a gradient operation in their call graph (shortened to "using a gradient" | |
| // in the rest of the file). | |
| // | |
| // This computation is logically split in three steps: | |
| // 1 - For each function compute if it uses a gradient in its body, ignoring | |
| // calls to other user-defined functions. | |
| // 2 - For each function determine if it uses a gradient in its call graph, | |
| // using the result of step 1 and the CallDAG to know its callees. | |
| // 3 - For each control flow statement of each function, check if it uses a | |
| // gradient in the function's body, or if it calls a user-defined function that | |
| // uses a gradient. | |
| // | |
| // We take advantage of the call graph being a DAG and instead compute 1, 2 and 3 | |
| // for leaves first, then going down the tree. This is correct because 1 doesn't | |
| // depend on other functions, and 2 and 3 depend only on callees. | |
| for (size_t i = 0; i < callDag.size(); i++) | |
| { | |
| PullGradient pull(&metadataList, i, callDag); | |
| pull.traverse(callDag.getRecordFromIndex(i).node); | |
| } | |
| // Compute which loops are discontinuous and which function are called in | |
| // these loops. The same way computing gradient usage is a "pull" process, | |
| // computing "bing used in a discont. loop" is a push process. However we also | |
| // need to know what ifs have a discontinuous loop inside so we do the same type | |
| // of callgraph analysis as for the gradient. | |
| // First compute which loops are discontinuous (no specific order) and pull | |
| // the ifs and functions using a gradient loop. | |
| for (size_t i = 0; i < callDag.size(); i++) | |
| { | |
| PullComputeDiscontinuousAndGradientLoops pull(&metadataList, i, callDag); | |
| pull.traverse(callDag.getRecordFromIndex(i).node); | |
| } | |
| // Then push the information to callees, either from the a local discontinuous | |
| // loop or from the caller being called in a discontinuous loop already | |
| for (size_t i = callDag.size(); i-- > 0;) | |
| { | |
| PushDiscontinuousLoops push(&metadataList, i, callDag); | |
| push.traverse(callDag.getRecordFromIndex(i).node); | |
| } | |
| // We create "Lod0" version of functions with the gradient operations replaced | |
| // by non-gradient operations so that the D3D compiler is happier with discont | |
| // loops. | |
| for (auto &metadata : metadataList) | |
| { | |
| metadata.mNeedsLod0 = metadata.mCalledInDiscontinuousLoop && metadata.mUsesGradient; | |
| } | |
| return metadataList; | |
| } | |
| } // namespace sh |