// // Copyright 2024 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. // #include "compiler/translator/tree_ops/RemoveUnusedFramebufferFetch.h" #include "common/bitset_utils.h" #include "compiler/translator/SymbolTable.h" #include "compiler/translator/tree_util/IntermTraverse.h" #include "compiler/translator/tree_util/ReplaceVariable.h" namespace sh { namespace { class FindUnusedInoutVariablesTraverser : public TIntermTraverser { public: FindUnusedInoutVariablesTraverser(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable) {} VariableReplacementMap getReplacementMap() const; private: bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override; bool visitBranch(Visit visit, TIntermBranch *node) override; void visitSymbol(TIntermSymbol *node) override; void markWrite(const TVariable *var, uint8_t channels); void markRead(const TVariable *var); bool isDirectlyInMain(uint32_t expectedBlockLevel); angle::HashMap mAssignedChannels; angle::HashSet mVariablesWithLoadAccess; bool mMainHasReturn = false; bool mShaderHasDiscard = false; bool mIsInMain = false; }; void FindUnusedInoutVariablesTraverser::markWrite(const TVariable *var, uint8_t channels) { auto iter = mAssignedChannels.insert(std::make_pair(var, static_cast(0))); iter.first->second |= channels; } void FindUnusedInoutVariablesTraverser::markRead(const TVariable *var) { mVariablesWithLoadAccess.insert(var); } bool FindUnusedInoutVariablesTraverser::isDirectlyInMain(uint32_t expectedBlockLevel) { TIntermNode *block = getAncestorNode(expectedBlockLevel); TIntermNode *functionNode = getAncestorNode(expectedBlockLevel + 1); TIntermFunctionDefinition *function = functionNode ? functionNode->getAsFunctionDefinition() : nullptr; return block && block->getAsBlock() && function && function->getFunction()->isMain(); } bool FindUnusedInoutVariablesTraverser::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) { const TFunction *function = node->getFunction(); mIsInMain = function->isMain(); return true; } bool FindUnusedInoutVariablesTraverser::visitBranch(Visit visit, TIntermBranch *node) { switch (node->getFlowOp()) { case EOpReturn: if (mIsInMain) { mMainHasReturn = true; } break; case EOpKill: mShaderHasDiscard = true; break; default: break; } return true; } void FindUnusedInoutVariablesTraverser::visitSymbol(TIntermSymbol *node) { const TVariable *var = &node->variable(); const TType &type = var->getType(); if (type.getQualifier() != EvqFragmentInOut) { return; } if (getParentNode()->getAsDeclarationNode()) { return; } // Look for the following patterns: // // var = ... // var.xy = ... // // And only directly in main()'s body. Otherwise it may be an assignment in a conditional that // is not executed. Additionally, main() shouldn't have |return| prior to this access, and the // shader shouldn't have any |discard|s anywhere. // // Every other access is considered a load access for simplicity. TIntermNode *parent = getAncestorNode(0); TIntermNode *grandParent = getAncestorNode(1); TIntermBinary *parentBinary = parent ? parent->getAsBinaryNode() : nullptr; TIntermSwizzle *parentSwizzle = parent ? parent->getAsSwizzleNode() : nullptr; TIntermBinary *grandParentBinary = grandParent ? grandParent->getAsBinaryNode() : nullptr; if (parentBinary != nullptr && parentBinary->getOp() == EOpAssign && parentBinary->getLeft() == node && isDirectlyInMain(1) && !mMainHasReturn) { // All channels are written to ASSERT(mIsInMain); markWrite(var, 0xF); } else if (parentSwizzle != nullptr && grandParentBinary != nullptr && grandParentBinary->getOp() == EOpAssign && grandParentBinary->getLeft() == parent && isDirectlyInMain(2) && !mMainHasReturn) { // Some channels are written to ASSERT(mIsInMain); uint8_t channels = 0; for (uint32_t channel : parentSwizzle->getSwizzleOffsets()) { channels |= static_cast(1 << channel); } markWrite(var, channels); } else { // For simplicity, assume read. If other write patterns need to be detected, they can be // done here. markRead(var); } } VariableReplacementMap FindUnusedInoutVariablesTraverser::getReplacementMap() const { VariableReplacementMap replacementMap; if (mShaderHasDiscard) { return replacementMap; } for (auto iter : mAssignedChannels) { const TVariable *var = iter.first; const uint8_t writtenChannels = iter.second; // Only replace variables that have only been written to. if (mVariablesWithLoadAccess.find(var) != mVariablesWithLoadAccess.end()) { continue; } // ... and have overwritten all their channels const TType &type = var->getType(); const uint8_t allChannels = angle::BitMask(type.getNominalSize()); if ((writtenChannels & allChannels) != allChannels) { continue; } // Create an identical variable, only with EvqFragmentOut instead of EvqFragmentInOut. TType *newType = new TType(type); newType->setQualifier(EvqFragmentOut); const TVariable *replacement = new TVariable(mSymbolTable, var->name(), newType, var->symbolType()); replacementMap[var->uniqueId()] = new TIntermSymbol(replacement); } return replacementMap; } } // namespace bool RemoveUnusedFramebufferFetch(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) { FindUnusedInoutVariablesTraverser traverser(symbolTable); root->traverse(&traverser); VariableReplacementMap replacementMap = traverser.getReplacementMap(); if (replacementMap.empty()) { return true; } return ReplaceVariables(compiler, root, replacementMap); } } // namespace sh