diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /gfx/angle/checkout/src/compiler/translator/tree_ops | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
118 files changed, 16319 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp new file mode 100644 index 0000000000..bd1997a21d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp @@ -0,0 +1,136 @@ +// +// Copyright 2021 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. +// +// ClampIndirectIndices.h: Add clamp to the indirect indices used on arrays. +// + +#include "compiler/translator/tree_ops/ClampIndirectIndices.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ +namespace +{ +// Traverser that finds EOpIndexIndirect nodes and applies a clamp to their right-hand side +// expression. +class ClampIndirectIndicesTraverser : public TIntermTraverser +{ + public: + ClampIndirectIndicesTraverser(TCompiler *compiler, TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), mCompiler(compiler) + {} + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + ASSERT(visit == PreVisit); + + // Only interested in EOpIndexIndirect nodes. + if (node->getOp() != EOpIndexIndirect) + { + return true; + } + + // Apply the transformation to the left and right nodes + bool valid = ClampIndirectIndices(mCompiler, node->getLeft(), mSymbolTable); + ASSERT(valid); + valid = ClampIndirectIndices(mCompiler, node->getRight(), mSymbolTable); + ASSERT(valid); + + // Generate clamp(right, 0, N), where N is the size of the array being indexed minus 1. If + // the array is runtime-sized, the length() method is called on it. + const TType &leftType = node->getLeft()->getType(); + const TType &rightType = node->getRight()->getType(); + + // Don't clamp indirect indices on unsized arrays in buffer blocks. They are covered by the + // relevant robust access behavior of the backend. + if (leftType.isUnsizedArray()) + { + return true; + } + + // On GLSL es 100, clamp is only defined for float, so float arguments are used. + // + // However, float clamp is unconditionally emitted to workaround driver bugs with integer + // clamp on Qualcomm. http://crbug.com/1217167 + // + // const bool useFloatClamp = mCompiler->getShaderVersion() == 100; + const bool useFloatClamp = true; + + TIntermConstantUnion *zero = createClampValue(0, useFloatClamp); + TIntermTyped *max; + + if (leftType.isArray()) + { + max = createClampValue(static_cast<int>(leftType.getOutermostArraySize()) - 1, + useFloatClamp); + } + else + { + ASSERT(leftType.isVector() || leftType.isMatrix()); + max = createClampValue(leftType.getNominalSize() - 1, useFloatClamp); + } + + TIntermTyped *index = node->getRight(); + // If the index node is not an int (i.e. it's a uint), or a float (if using float clamp), + // cast it. + const TBasicType requiredBasicType = useFloatClamp ? EbtFloat : EbtInt; + if (rightType.getBasicType() != requiredBasicType) + { + const TType *clampType = useFloatClamp ? StaticType::GetBasic<EbtFloat, EbpHigh>() + : StaticType::GetBasic<EbtInt, EbpHigh>(); + TIntermSequence constructorArgs = {index}; + index = TIntermAggregate::CreateConstructor(*clampType, &constructorArgs); + } + + // min(gl_PointSize, maxPointSize) + TIntermSequence args; + args.push_back(index); + args.push_back(zero); + args.push_back(max); + TIntermTyped *clamped = + CreateBuiltInFunctionCallNode("clamp", &args, *mSymbolTable, useFloatClamp ? 100 : 300); + + // Cast back to int if float clamp was used. + if (useFloatClamp) + { + TIntermSequence constructorArgs = {clamped}; + clamped = TIntermAggregate::CreateConstructor(*StaticType::GetBasic<EbtInt, EbpHigh>(), + &constructorArgs); + } + + // Replace the right node (the index) with the clamped result. + queueReplacementWithParent(node, node->getRight(), clamped, OriginalNode::IS_DROPPED); + + // Don't recurse as left and right nodes are already processed. + return false; + } + + private: + TIntermConstantUnion *createClampValue(int value, bool useFloat) + { + if (useFloat) + { + return CreateFloatNode(static_cast<float>(value), EbpHigh); + } + return CreateIndexNode(value); + } + + TCompiler *mCompiler; +}; +} // anonymous namespace + +bool ClampIndirectIndices(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + ClampIndirectIndicesTraverser traverser(compiler, symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h new file mode 100644 index 0000000000..eebbab02e6 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h @@ -0,0 +1,27 @@ +// +// Copyright 2021 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. +// +// ClampIndirectIndices.h: Add clamp to the indirect indices used on arrays. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_CLAMPINDIRECTINDICES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_CLAMPINDIRECTINDICES_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool ClampIndirectIndices(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_CLAMPINDIRECTINDICES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp new file mode 100644 index 0000000000..db006989e9 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp @@ -0,0 +1,52 @@ +// +// 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. +// +// ClampPointSize.cpp: Limit the value that is written to gl_PointSize. +// + +#include "compiler/translator/tree_ops/ClampPointSize.h" + +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/BuiltIn.h" +#include "compiler/translator/tree_util/FindSymbolNode.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/RunAtTheEndOfShader.h" + +namespace sh +{ + +bool ClampPointSize(TCompiler *compiler, + TIntermBlock *root, + float maxPointSize, + TSymbolTable *symbolTable) +{ + // Only clamp gl_PointSize if it's used in the shader. + const TIntermSymbol *glPointSize = FindSymbolNode(root, ImmutableString("gl_PointSize")); + if (glPointSize == nullptr) + { + return true; + } + + TIntermTyped *pointSizeNode = glPointSize->deepCopy(); + + TConstantUnion *maxPointSizeConstant = new TConstantUnion(); + maxPointSizeConstant->setFConst(maxPointSize); + TIntermConstantUnion *maxPointSizeNode = + new TIntermConstantUnion(maxPointSizeConstant, TType(EbtFloat, EbpHigh, EvqConst)); + + // min(gl_PointSize, maxPointSize) + TIntermSequence minArguments; + minArguments.push_back(pointSizeNode->deepCopy()); + minArguments.push_back(maxPointSizeNode); + TIntermTyped *clampedPointSize = + CreateBuiltInFunctionCallNode("min", &minArguments, *symbolTable, 100); + + // gl_PointSize = min(gl_PointSize, maxPointSize) + TIntermBinary *assignPointSize = new TIntermBinary(EOpAssign, pointSizeNode, clampedPointSize); + + return RunAtTheEndOfShader(compiler, root, assignPointSize, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h new file mode 100644 index 0000000000..c42f32bde2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h @@ -0,0 +1,28 @@ +// +// 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. +// +// ClampPointSize.h: Limit the value that is written to gl_PointSize. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_CLAMPPOINTSIZE_H_ +#define COMPILER_TRANSLATOR_TREEOPS_CLAMPPOINTSIZE_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool ClampPointSize(TCompiler *compiler, + TIntermBlock *root, + float maxPointSize, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_CLAMPPOINTSIZE_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp new file mode 100644 index 0000000000..5c45e1fb59 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp @@ -0,0 +1,335 @@ +// +// Copyright 2020 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/ConvertUnsupportedConstructorsToFunctionCalls.h" +#include "compiler/translator/ImmutableString.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/FindFunction.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermRebuild.h" + +using namespace sh; + +namespace +{ + +void AppendMatrixElementArgument(TIntermSymbol *parameter, + int colIndex, + int rowIndex, + TIntermSequence *returnCtorArgs) +{ + TIntermBinary *matColN = + new TIntermBinary(EOpIndexDirect, parameter->deepCopy(), CreateIndexNode(colIndex)); + TIntermSwizzle *matElem = new TIntermSwizzle(matColN, {rowIndex}); + returnCtorArgs->push_back(matElem); +} + +// Adds the argument to sequence for a scalar constructor. +// Given scalar(scalarA) appends scalarA +// Given scalar(vecA) appends vecA.x +// Given scalar(matA) appends matA[0].x +void AppendScalarFromNonScalarArguments(TFunction &function, TIntermSequence *returnCtorArgs) +{ + const TVariable *var = function.getParam(0); + TIntermSymbol *arg0 = new TIntermSymbol(var); + + const TType &type = arg0->getType(); + + if (type.isScalar()) + { + returnCtorArgs->push_back(arg0); + } + else if (type.isVector()) + { + TIntermSwizzle *vecX = new TIntermSwizzle(arg0, {0}); + returnCtorArgs->push_back(vecX); + } + else if (type.isMatrix()) + { + AppendMatrixElementArgument(arg0, 0, 0, returnCtorArgs); + } +} + +// Adds the arguments to sequence for a vector constructor from a scalar. +// Given vecN(scalarA) appends scalarA, scalarA, ... n times +void AppendVectorFromScalarArgument(const TType &type, + TFunction &function, + TIntermSequence *returnCtorArgs) +{ + const uint8_t vectorSize = type.getNominalSize(); + const TVariable *var = function.getParam(0); + TIntermSymbol *v = new TIntermSymbol(var); + for (uint8_t i = 0; i < vectorSize; ++i) + { + returnCtorArgs->push_back(v->deepCopy()); + } +} + +// Adds the arguments to sequence for a vector or matrix constructor from the available arguments +// applying arguments in order until the requested number of values have been extracted from the +// given arguments or until there are no more arguments. +void AppendValuesFromMultipleArguments(int numValuesNeeded, + TFunction &function, + TIntermSequence *returnCtorArgs) +{ + size_t numParameters = function.getParamCount(); + size_t paramIndex = 0; + uint8_t colIndex = 0; + uint8_t rowIndex = 0; + + for (int i = 0; i < numValuesNeeded && paramIndex < numParameters; ++i) + { + const TVariable *p = function.getParam(paramIndex); + TIntermSymbol *parameter = new TIntermSymbol(p); + if (parameter->isScalar()) + { + returnCtorArgs->push_back(parameter); + ++paramIndex; + } + else if (parameter->isVector()) + { + TIntermSwizzle *vecS = new TIntermSwizzle(parameter->deepCopy(), {rowIndex++}); + returnCtorArgs->push_back(vecS); + if (rowIndex == parameter->getNominalSize()) + { + ++paramIndex; + rowIndex = 0; + } + } + else if (parameter->isMatrix()) + { + AppendMatrixElementArgument(parameter, colIndex, rowIndex++, returnCtorArgs); + if (rowIndex == parameter->getSecondarySize()) + { + rowIndex = 0; + ++colIndex; + if (colIndex == parameter->getNominalSize()) + { + colIndex = 0; + ++paramIndex; + } + } + } + } +} + +// Adds the arguments for a matrix constructor from a scalar +// putting the scalar along the diagonal and 0 everywhere else. +void AppendMatrixFromScalarArgument(const TType &type, + TFunction &function, + TIntermSequence *returnCtorArgs) +{ + const TVariable *var = function.getParam(0); + TIntermSymbol *v = new TIntermSymbol(var); + const uint8_t numCols = type.getNominalSize(); + const uint8_t numRows = type.getSecondarySize(); + for (uint8_t col = 0; col < numCols; ++col) + { + for (uint8_t row = 0; row < numRows; ++row) + { + if (col == row) + { + returnCtorArgs->push_back(v->deepCopy()); + } + else + { + returnCtorArgs->push_back(CreateFloatNode(0.0f, sh::EbpUndefined)); + } + } + } +} + +// Add the argument for a matrix constructor from a matrix +// copying elements from the same column/row and otherwise +// initialize to the identity matrix. +void AppendMatrixFromMatrixArgument(const TType &type, + TFunction &function, + TIntermSequence *returnCtorArgs) +{ + const TVariable *var = function.getParam(0); + TIntermSymbol *v = new TIntermSymbol(var); + const uint8_t dstCols = type.getNominalSize(); + const uint8_t dstRows = type.getSecondarySize(); + const uint8_t srcCols = v->getNominalSize(); + const uint8_t srcRows = v->getSecondarySize(); + for (uint8_t dstCol = 0; dstCol < dstCols; ++dstCol) + { + for (uint8_t dstRow = 0; dstRow < dstRows; ++dstRow) + { + if (dstRow < srcRows && dstCol < srcCols) + { + AppendMatrixElementArgument(v, dstCol, dstRow, returnCtorArgs); + } + else + { + returnCtorArgs->push_back( + CreateFloatNode(dstRow == dstCol ? 1.0f : 0.0f, sh::EbpUndefined)); + } + } + } +} + +class Rebuild : public TIntermRebuild +{ + public: + explicit Rebuild(TCompiler &compiler) : TIntermRebuild(compiler, false, true) {} + PostResult visitAggregatePost(TIntermAggregate &node) override + { + if (!node.isConstructor()) + { + return node; + } + + TIntermSequence &arguments = *node.getSequence(); + if (arguments.empty()) + { + return node; + } + + const TType &type = node.getType(); + const TType &arg0Type = arguments[0]->getAsTyped()->getType(); + + if (!type.isScalar() && !type.isVector() && !type.isMatrix()) + { + return node; + } + + if (type.isArray()) + { + return node; + } + + // check for type_ctor(sameType) + // scalar(scalar) -> passthrough + // vecN(vecN) -> passthrough + // matN(matN) -> passthrough + if (arguments.size() == 1 && arg0Type == type) + { + return node; + } + + // The following are simple casts: + // + // - basic(s) (where basic is int, uint, float or bool, and s is scalar). + // - gvecN(vN) (where the argument is a single vector with the same number of components). + // - matNxM(mNxM) (where the argument is a single matrix with the same dimensions). Note + // that + // matrices are always float, so there's no actual cast and this would be a no-op. + // + const bool isSingleScalarCast = + arguments.size() == 1 && type.isScalar() && arg0Type.isScalar(); + const bool isSingleVectorCast = arguments.size() == 1 && type.isVector() && + arg0Type.isVector() && + type.getNominalSize() == arg0Type.getNominalSize(); + const bool isSingleMatrixCast = + arguments.size() == 1 && type.isMatrix() && arg0Type.isMatrix() && + type.getCols() == arg0Type.getCols() && type.getRows() == arg0Type.getRows(); + if (isSingleScalarCast || isSingleVectorCast || isSingleMatrixCast) + { + return node; + } + + // Cases we need to handle: + // scalar(vec) + // scalar(mat) + // vecN(scalar) + // vecN(vecM) + // vecN(a,...) + // matN(scalar) -> diag + // matN(vec) -> fail! + // manN(matM) -> corner + ident + // matN(a, ...) + + // Build a function and pass all the constructor's arguments to it. + TIntermBlock *body = new TIntermBlock; + TFunction *function = new TFunction(&mSymbolTable, ImmutableString(""), + SymbolType::AngleInternal, &type, true); + + for (size_t i = 0; i < arguments.size(); ++i) + { + TIntermTyped &arg = *arguments[i]->getAsTyped(); + TType *argType = new TType(arg.getBasicType(), arg.getPrecision(), EvqParamIn, + arg.getNominalSize(), arg.getSecondarySize()); + TVariable *var = CreateTempVariable(&mSymbolTable, argType); + function->addParameter(var); + } + + // Build a return statement for the function that + // converts the arguments into the required type. + TIntermSequence *returnCtorArgs = new TIntermSequence(); + + if (type.isScalar()) + { + AppendScalarFromNonScalarArguments(*function, returnCtorArgs); + } + else if (type.isVector()) + { + if (arguments.size() == 1 && arg0Type.isScalar()) + { + AppendVectorFromScalarArgument(type, *function, returnCtorArgs); + } + else + { + AppendValuesFromMultipleArguments(type.getNominalSize(), *function, returnCtorArgs); + } + } + else if (type.isMatrix()) + { + if (arguments.size() == 1 && arg0Type.isScalar()) + { + // MSL already handles this case + AppendMatrixFromScalarArgument(type, *function, returnCtorArgs); + } + else if (arg0Type.isMatrix()) + { + AppendMatrixFromMatrixArgument(type, *function, returnCtorArgs); + } + else + { + AppendValuesFromMultipleArguments(type.getNominalSize() * type.getSecondarySize(), + *function, returnCtorArgs); + } + } + + TIntermBranch *returnStatement = + new TIntermBranch(EOpReturn, TIntermAggregate::CreateConstructor(type, returnCtorArgs)); + body->appendStatement(returnStatement); + + TIntermFunctionDefinition *functionDefinition = + CreateInternalFunctionDefinitionNode(*function, body); + mFunctionDefs.push_back(functionDefinition); + + TIntermTyped *functionCall = TIntermAggregate::CreateFunctionCall(*function, &arguments); + + return *functionCall; + } + + bool rewrite(TIntermBlock &root) + { + if (!rebuildInPlace(root)) + { + return true; + } + + size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(&root); + for (TIntermFunctionDefinition *functionDefinition : mFunctionDefs) + { + root.insertChildNodes(firstFunctionIndex, TIntermSequence({functionDefinition})); + } + + return mCompiler.validateAST(&root); + } + + private: + TVector<TIntermFunctionDefinition *> mFunctionDefs; +}; + +} // anonymous namespace + +bool sh::ConvertUnsupportedConstructorsToFunctionCalls(TCompiler &compiler, TIntermBlock &root) +{ + return Rebuild(compiler).rewrite(root); +} diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h new file mode 100644 index 0000000000..720a02c9a6 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h @@ -0,0 +1,25 @@ +// +// Copyright 2020 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. +// + +#ifndef COMPILER_TRANSLATOR_TREE_OPS_CONVERTUNSUPPORTEDCONSTRUCTORSTOFUNCTIONCALLS_H_ +#define COMPILER_TRANSLATOR_TREE_OPS_CONVERTUNSUPPORTEDCONSTRUCTORSTOFUNCTIONCALLS_H_ + +#include "compiler/translator/Compiler.h" + +namespace sh +{ + +class TCompiler; +class TInterBlock; +class SymbolEnv; + +// Adds explicit type casts into the AST where casting is done implicitly. +[[nodiscard]] bool ConvertUnsupportedConstructorsToFunctionCalls(TCompiler &compiler, + TIntermBlock &root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREE_OPS_CONVERTUNSUPPORTEDCONSTRUCTORSTOFUNCTIONCALLS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp new file mode 100644 index 0000000000..8c2925949d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp @@ -0,0 +1,196 @@ +// +// 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. +// +// Applies the necessary AST transformations to support multiview rendering through instancing. +// Check the header file For more information. +// + +#include "compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_ops/InitializeVariables.h" +#include "compiler/translator/tree_util/BuiltIn.h" +#include "compiler/translator/tree_util/FindMain.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +constexpr const ImmutableString kViewIDVariableName("ViewID_OVR"); +constexpr const ImmutableString kInstanceIDVariableName("InstanceID"); +constexpr const ImmutableString kMultiviewBaseViewLayerIndexVariableName( + "multiviewBaseViewLayerIndex"); + +// Adds the InstanceID and ViewID_OVR initializers to the end of the initializers' sequence. +void InitializeViewIDAndInstanceID(const TVariable *viewID, + const TVariable *instanceID, + unsigned numberOfViews, + const TSymbolTable &symbolTable, + TIntermSequence *initializers) +{ + // Create an unsigned numberOfViews node. + TConstantUnion *numberOfViewsUnsignedConstant = new TConstantUnion(); + numberOfViewsUnsignedConstant->setUConst(numberOfViews); + TIntermConstantUnion *numberOfViewsUint = + new TIntermConstantUnion(numberOfViewsUnsignedConstant, TType(EbtUInt, EbpLow, EvqConst)); + + // Create a uint(gl_InstanceID) node. + TIntermSequence glInstanceIDSymbolCastArguments; + glInstanceIDSymbolCastArguments.push_back(new TIntermSymbol(BuiltInVariable::gl_InstanceID())); + TIntermAggregate *glInstanceIDAsUint = TIntermAggregate::CreateConstructor( + TType(EbtUInt, EbpHigh, EvqTemporary), &glInstanceIDSymbolCastArguments); + + // Create a uint(gl_InstanceID) / numberOfViews node. + TIntermBinary *normalizedInstanceID = + new TIntermBinary(EOpDiv, glInstanceIDAsUint, numberOfViewsUint); + + // Create an int(uint(gl_InstanceID) / numberOfViews) node. + TIntermSequence normalizedInstanceIDCastArguments; + normalizedInstanceIDCastArguments.push_back(normalizedInstanceID); + TIntermAggregate *normalizedInstanceIDAsInt = TIntermAggregate::CreateConstructor( + TType(EbtInt, EbpHigh, EvqTemporary), &normalizedInstanceIDCastArguments); + + // Create an InstanceID = int(uint(gl_InstanceID) / numberOfViews) node. + TIntermBinary *instanceIDInitializer = + new TIntermBinary(EOpAssign, new TIntermSymbol(instanceID), normalizedInstanceIDAsInt); + initializers->push_back(instanceIDInitializer); + + // Create a uint(gl_InstanceID) % numberOfViews node. + TIntermBinary *normalizedViewID = + new TIntermBinary(EOpIMod, glInstanceIDAsUint->deepCopy(), numberOfViewsUint->deepCopy()); + + // Create a ViewID_OVR = uint(gl_InstanceID) % numberOfViews node. + TIntermBinary *viewIDInitializer = + new TIntermBinary(EOpAssign, new TIntermSymbol(viewID), normalizedViewID); + initializers->push_back(viewIDInitializer); +} + +// Adds a branch to write int(ViewID_OVR) to either gl_ViewportIndex or gl_Layer. The branch is +// added to the end of the initializers' sequence. +void SelectViewIndexInVertexShader(const TVariable *viewID, + const TVariable *multiviewBaseViewLayerIndex, + TIntermSequence *initializers, + const TSymbolTable &symbolTable) +{ + // Create an int(ViewID_OVR) node. + TIntermSequence viewIDSymbolCastArguments; + viewIDSymbolCastArguments.push_back(new TIntermSymbol(viewID)); + TIntermAggregate *viewIDAsInt = TIntermAggregate::CreateConstructor( + TType(EbtInt, EbpHigh, EvqTemporary), &viewIDSymbolCastArguments); + + // Create a gl_ViewportIndex node. + TIntermSymbol *viewportIndexSymbol = new TIntermSymbol(BuiltInVariable::gl_ViewportIndex()); + + // Create a { gl_ViewportIndex = int(ViewID_OVR) } node. + TIntermBlock *viewportIndexInitializerInBlock = new TIntermBlock(); + viewportIndexInitializerInBlock->appendStatement( + new TIntermBinary(EOpAssign, viewportIndexSymbol, viewIDAsInt)); + + // Create a gl_Layer node. + TIntermSymbol *layerSymbol = new TIntermSymbol(BuiltInVariable::gl_LayerVS()); + + // Create an int(ViewID_OVR) + multiviewBaseViewLayerIndex node + TIntermBinary *sumOfViewIDAndBaseViewIndex = new TIntermBinary( + EOpAdd, viewIDAsInt->deepCopy(), new TIntermSymbol(multiviewBaseViewLayerIndex)); + + // Create a { gl_Layer = int(ViewID_OVR) + multiviewBaseViewLayerIndex } node. + TIntermBlock *layerInitializerInBlock = new TIntermBlock(); + layerInitializerInBlock->appendStatement( + new TIntermBinary(EOpAssign, layerSymbol, sumOfViewIDAndBaseViewIndex)); + + // Create a node to compare whether the base view index uniform is less than zero. + TIntermBinary *multiviewBaseViewLayerIndexZeroComparison = + new TIntermBinary(EOpLessThan, new TIntermSymbol(multiviewBaseViewLayerIndex), + CreateZeroNode(TType(EbtInt, EbpHigh, EvqConst))); + + // Create an if-else statement to select the code path. + TIntermIfElse *multiviewBranch = + new TIntermIfElse(multiviewBaseViewLayerIndexZeroComparison, + viewportIndexInitializerInBlock, layerInitializerInBlock); + + initializers->push_back(multiviewBranch); +} + +} // namespace + +bool DeclareAndInitBuiltinsForInstancedMultiview(TCompiler *compiler, + TIntermBlock *root, + unsigned numberOfViews, + GLenum shaderType, + const ShCompileOptions &compileOptions, + ShShaderOutput shaderOutput, + TSymbolTable *symbolTable) +{ + ASSERT(shaderType == GL_VERTEX_SHADER || shaderType == GL_FRAGMENT_SHADER); + + TQualifier viewIDQualifier = (shaderType == GL_VERTEX_SHADER) ? EvqFlatOut : EvqFlatIn; + const TVariable *viewID = + new TVariable(symbolTable, kViewIDVariableName, + new TType(EbtUInt, EbpHigh, viewIDQualifier), SymbolType::AngleInternal); + + DeclareGlobalVariable(root, viewID); + if (!ReplaceVariable(compiler, root, BuiltInVariable::gl_ViewID_OVR(), viewID)) + { + return false; + } + if (shaderType == GL_VERTEX_SHADER) + { + // Replacing gl_InstanceID with InstanceID should happen before adding the initializers of + // InstanceID and ViewID. + const TType *instanceIDVariableType = StaticType::Get<EbtInt, EbpHigh, EvqGlobal, 1, 1>(); + const TVariable *instanceID = + new TVariable(symbolTable, kInstanceIDVariableName, instanceIDVariableType, + SymbolType::AngleInternal); + DeclareGlobalVariable(root, instanceID); + if (!ReplaceVariable(compiler, root, BuiltInVariable::gl_InstanceID(), instanceID)) + { + return false; + } + + TIntermSequence initializers; + InitializeViewIDAndInstanceID(viewID, instanceID, numberOfViews, *symbolTable, + &initializers); + + // The AST transformation which adds the expression to select the viewport index should + // be done only for the GLSL and ESSL output. + const bool selectView = compileOptions.selectViewInNvGLSLVertexShader; + // Assert that if the view is selected in the vertex shader, then the output is + // either GLSL or ESSL. + ASSERT(!selectView || IsOutputGLSL(shaderOutput) || IsOutputESSL(shaderOutput)); + if (selectView) + { + // Add a uniform to switch between side-by-side and layered rendering. + const TType *baseLayerIndexVariableType = + StaticType::Get<EbtInt, EbpHigh, EvqUniform, 1, 1>(); + const TVariable *multiviewBaseViewLayerIndex = + new TVariable(symbolTable, kMultiviewBaseViewLayerIndexVariableName, + baseLayerIndexVariableType, SymbolType::AngleInternal); + DeclareGlobalVariable(root, multiviewBaseViewLayerIndex); + + // Setting a value to gl_ViewportIndex or gl_Layer should happen after ViewID_OVR's + // initialization. + SelectViewIndexInVertexShader(viewID, multiviewBaseViewLayerIndex, &initializers, + *symbolTable); + } + + // Insert initializers at the beginning of main(). + TIntermBlock *initializersBlock = new TIntermBlock(); + initializersBlock->getSequence()->swap(initializers); + TIntermBlock *mainBody = FindMainBody(root); + mainBody->getSequence()->insert(mainBody->getSequence()->begin(), initializersBlock); + } + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h new file mode 100644 index 0000000000..49a964d234 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h @@ -0,0 +1,52 @@ +// +// 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. +// +// Regardless of the shader type, the following AST transformations are applied: +// - Add declaration of View_ID_OVR. +// - Replace every occurrence of gl_ViewID_OVR with ViewID_OVR, mark ViewID_OVR as internal and +// declare it as a flat varying. +// +// If the shader type is a vertex shader, the following AST transformations are applied: +// - Replace every occurrence of gl_InstanceID with InstanceID, mark InstanceID as internal and set +// its qualifier to EvqTemporary. +// - Add initializers of ViewID_OVR and InstanceID to the beginning of the body of main. The pass +// should be executed before any variables get collected so that usage of gl_InstanceID is recorded. +// - If the output is ESSL or GLSL and the selectViewInNvGLSLVertexShader option is +// enabled, the expression +// "if (multiviewBaseViewLayerIndex < 0) { +// gl_ViewportIndex = int(ViewID_OVR); +// } else { +// gl_Layer = int(ViewID_OVR) + multiviewBaseViewLayerIndex; +// }" +// is added after ViewID and InstanceID are initialized. Also, MultiviewRenderPath is added as a +// uniform. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_ +#define COMPILER_TRANSLATOR_TREEOPS_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_ + +#include "GLSLANG/ShaderLang.h" +#include "angle_gl.h" +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool DeclareAndInitBuiltinsForInstancedMultiview( + TCompiler *compiler, + TIntermBlock *root, + unsigned numberOfViews, + GLenum shaderType, + const ShCompileOptions &compileOptions, + ShShaderOutput shaderOutput, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp new file mode 100644 index 0000000000..035de73431 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp @@ -0,0 +1,180 @@ +// +// Copyright 2016 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. +// +// DeferGlobalInitializers is an AST traverser that moves global initializers into a separate +// function that is called in the beginning of main(). This enables initialization of globals with +// uniforms or non-constant globals, as allowed by the WebGL spec. Some initializers referencing +// non-constants may need to be unfolded into if statements in HLSL - this kind of steps should be +// done after DeferGlobalInitializers is run. Note that it's important that the function definition +// is at the end of the shader, as some globals may be declared after main(). +// +// It can also initialize all uninitialized globals. +// + +#include "compiler/translator/tree_ops/DeferGlobalInitializers.h" + +#include <vector> + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_ops/InitializeVariables.h" +#include "compiler/translator/tree_util/FindMain.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ + +namespace +{ + +constexpr const ImmutableString kInitGlobalsString("initGlobals"); + +void GetDeferredInitializers(TIntermDeclaration *declaration, + bool initializeUninitializedGlobals, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + bool forceDeferGlobalInitializers, + TIntermSequence *deferredInitializersOut, + std::vector<const TVariable *> *variablesToReplaceOut, + TSymbolTable *symbolTable) +{ + // SeparateDeclarations should have already been run. + ASSERT(declaration->getSequence()->size() == 1); + + TIntermNode *declarator = declaration->getSequence()->back(); + TIntermBinary *init = declarator->getAsBinaryNode(); + if (init) + { + TIntermSymbol *symbolNode = init->getLeft()->getAsSymbolNode(); + ASSERT(symbolNode); + TIntermTyped *expression = init->getRight(); + + if (expression->getQualifier() != EvqConst || !expression->hasConstantValue() || + forceDeferGlobalInitializers) + { + // For variables which are not constant, defer their real initialization until + // after we initialize uniforms. + // Deferral is done also in any cases where the variable can not be converted to a + // constant union, since otherwise there's a chance that HLSL output will generate extra + // statements from the initializer expression. + + // Change const global to a regular global if its initialization is deferred. + // This can happen if ANGLE has not been able to fold the constant expression used + // as an initializer. + ASSERT(symbolNode->getQualifier() == EvqConst || + symbolNode->getQualifier() == EvqGlobal); + if (symbolNode->getQualifier() == EvqConst) + { + variablesToReplaceOut->push_back(&symbolNode->variable()); + } + + TIntermBinary *deferredInit = + new TIntermBinary(EOpAssign, symbolNode->deepCopy(), init->getRight()); + deferredInitializersOut->push_back(deferredInit); + + // Remove the initializer from the global scope and just declare the global instead. + declaration->replaceChildNode(init, symbolNode); + } + } + else if (initializeUninitializedGlobals) + { + TIntermSymbol *symbolNode = declarator->getAsSymbolNode(); + ASSERT(symbolNode); + + // Ignore ANGLE internal variables and nameless declarations. + if (symbolNode->variable().symbolType() == SymbolType::AngleInternal || + symbolNode->variable().symbolType() == SymbolType::Empty) + return; + + if (symbolNode->getQualifier() == EvqGlobal) + { + TIntermSequence initCode; + CreateInitCode(symbolNode, canUseLoopsToInitialize, highPrecisionSupported, &initCode, + symbolTable); + deferredInitializersOut->insert(deferredInitializersOut->end(), initCode.begin(), + initCode.end()); + } + } +} + +void InsertInitCallToMain(TIntermBlock *root, + TIntermSequence *deferredInitializers, + TSymbolTable *symbolTable) +{ + TIntermBlock *initGlobalsBlock = new TIntermBlock(); + initGlobalsBlock->getSequence()->swap(*deferredInitializers); + + TFunction *initGlobalsFunction = + new TFunction(symbolTable, kInitGlobalsString, SymbolType::AngleInternal, + StaticType::GetBasic<EbtVoid, EbpUndefined>(), false); + + TIntermFunctionPrototype *initGlobalsFunctionPrototype = + CreateInternalFunctionPrototypeNode(*initGlobalsFunction); + root->getSequence()->insert(root->getSequence()->begin(), initGlobalsFunctionPrototype); + TIntermFunctionDefinition *initGlobalsFunctionDefinition = + CreateInternalFunctionDefinitionNode(*initGlobalsFunction, initGlobalsBlock); + root->appendStatement(initGlobalsFunctionDefinition); + + TIntermSequence emptySequence; + TIntermAggregate *initGlobalsCall = + TIntermAggregate::CreateFunctionCall(*initGlobalsFunction, &emptySequence); + + TIntermBlock *mainBody = FindMainBody(root); + mainBody->getSequence()->insert(mainBody->getSequence()->begin(), initGlobalsCall); +} + +} // namespace + +bool DeferGlobalInitializers(TCompiler *compiler, + TIntermBlock *root, + bool initializeUninitializedGlobals, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + bool forceDeferGlobalInitializers, + TSymbolTable *symbolTable) +{ + TIntermSequence deferredInitializers; + std::vector<const TVariable *> variablesToReplace; + + // Loop over all global statements and process the declarations. This is simpler than using a + // traverser. + for (TIntermNode *statement : *root->getSequence()) + { + TIntermDeclaration *declaration = statement->getAsDeclarationNode(); + if (declaration) + { + GetDeferredInitializers(declaration, initializeUninitializedGlobals, + canUseLoopsToInitialize, highPrecisionSupported, + forceDeferGlobalInitializers, &deferredInitializers, + &variablesToReplace, symbolTable); + } + } + + // Add the function with initialization and the call to that. + if (!deferredInitializers.empty()) + { + InsertInitCallToMain(root, &deferredInitializers, symbolTable); + } + + // Replace constant variables with non-constant global variables. + for (const TVariable *var : variablesToReplace) + { + TType *replacementType = new TType(var->getType()); + replacementType->setQualifier(EvqGlobal); + TVariable *replacement = + new TVariable(symbolTable, var->name(), replacementType, var->symbolType()); + if (!ReplaceVariable(compiler, root, var, replacement)) + { + return false; + } + } + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h new file mode 100644 index 0000000000..58a38a982b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h @@ -0,0 +1,38 @@ +// +// Copyright 2016 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. +// +// DeferGlobalInitializers is an AST traverser that moves global initializers into a separate +// function that is called in the beginning of main(). This enables initialization of globals with +// uniforms or non-constant globals, as allowed by the WebGL spec. Some initializers referencing +// non-constants may need to be unfolded into if statements in HLSL - this kind of steps should be +// done after DeferGlobalInitializers is run. Note that it's important that the function definition +// is at the end of the shader, as some globals may be declared after main(). +// +// It can also initialize all uninitialized globals. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_DEFERGLOBALINITIALIZERS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_DEFERGLOBALINITIALIZERS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool DeferGlobalInitializers(TCompiler *compiler, + TIntermBlock *root, + bool initializeUninitializedGlobals, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + bool forceDeferGlobalInitializers, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_DEFERGLOBALINITIALIZERS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp new file mode 100644 index 0000000000..25e1fde29a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp @@ -0,0 +1,142 @@ +// +// 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. +// +// gl_FragColor needs to broadcast to all color buffers in ES2 if +// GL_EXT_draw_buffers is explicitly enabled in a fragment shader. +// +// We emulate this by replacing all gl_FragColor with gl_FragData[0], and in the end +// of main() function, assigning gl_FragData[1], ..., gl_FragData[maxDrawBuffers-1] +// with gl_FragData[0]. +// + +#include "compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/RunAtTheEndOfShader.h" + +namespace sh +{ + +namespace +{ + +constexpr const ImmutableString kGlFragDataString("gl_FragData"); + +class GLFragColorBroadcastTraverser : public TIntermTraverser +{ + public: + GLFragColorBroadcastTraverser(int maxDrawBuffers, TSymbolTable *symbolTable, int shaderVersion) + : TIntermTraverser(true, false, false, symbolTable), + mGLFragColorUsed(false), + mMaxDrawBuffers(maxDrawBuffers), + mShaderVersion(shaderVersion) + {} + + [[nodiscard]] bool broadcastGLFragColor(TCompiler *compiler, TIntermBlock *root); + + bool isGLFragColorUsed() const { return mGLFragColorUsed; } + + protected: + void visitSymbol(TIntermSymbol *node) override; + + TIntermBinary *constructGLFragDataNode(int index) const; + TIntermBinary *constructGLFragDataAssignNode(int index) const; + + private: + bool mGLFragColorUsed; + int mMaxDrawBuffers; + const int mShaderVersion; +}; + +TIntermBinary *GLFragColorBroadcastTraverser::constructGLFragDataNode(int index) const +{ + TIntermSymbol *symbol = + ReferenceBuiltInVariable(kGlFragDataString, *mSymbolTable, mShaderVersion); + TIntermTyped *indexNode = CreateIndexNode(index); + + TIntermBinary *binary = new TIntermBinary(EOpIndexDirect, symbol, indexNode); + return binary; +} + +TIntermBinary *GLFragColorBroadcastTraverser::constructGLFragDataAssignNode(int index) const +{ + TIntermTyped *fragDataIndex = constructGLFragDataNode(index); + TIntermTyped *fragDataZero = constructGLFragDataNode(0); + + return new TIntermBinary(EOpAssign, fragDataIndex, fragDataZero); +} + +void GLFragColorBroadcastTraverser::visitSymbol(TIntermSymbol *node) +{ + if (node->variable().symbolType() == SymbolType::BuiltIn && node->getName() == "gl_FragColor") + { + queueReplacement(constructGLFragDataNode(0), OriginalNode::IS_DROPPED); + mGLFragColorUsed = true; + } +} + +bool GLFragColorBroadcastTraverser::broadcastGLFragColor(TCompiler *compiler, TIntermBlock *root) +{ + ASSERT(mMaxDrawBuffers > 1); + if (!mGLFragColorUsed) + { + return true; + } + + TIntermBlock *broadcastBlock = new TIntermBlock(); + // Now insert statements + // gl_FragData[1] = gl_FragData[0]; + // ... + // gl_FragData[maxDrawBuffers - 1] = gl_FragData[0]; + for (int colorIndex = 1; colorIndex < mMaxDrawBuffers; ++colorIndex) + { + broadcastBlock->appendStatement(constructGLFragDataAssignNode(colorIndex)); + } + return RunAtTheEndOfShader(compiler, root, broadcastBlock, mSymbolTable); +} + +} // namespace + +bool EmulateGLFragColorBroadcast(TCompiler *compiler, + TIntermBlock *root, + int maxDrawBuffers, + std::vector<sh::ShaderVariable> *outputVariables, + TSymbolTable *symbolTable, + int shaderVersion) +{ + ASSERT(maxDrawBuffers > 1); + GLFragColorBroadcastTraverser traverser(maxDrawBuffers, symbolTable, shaderVersion); + root->traverse(&traverser); + if (traverser.isGLFragColorUsed()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + if (!traverser.broadcastGLFragColor(compiler, root)) + { + return false; + } + + for (auto &var : *outputVariables) + { + if (var.name == "gl_FragColor") + { + // TODO(zmo): Find a way to keep the original variable information. + var.name = "gl_FragData"; + var.mappedName = "gl_FragData"; + var.arraySizes.push_back(maxDrawBuffers); + ASSERT(var.arraySizes.size() == 1u); + } + } + } + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h new file mode 100644 index 0000000000..c71ba7c2be --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h @@ -0,0 +1,35 @@ +// +// 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. +// +// Emulate gl_FragColor broadcast behaviors in ES2 where +// GL_EXT_draw_buffers is explicitly enabled in a fragment shader. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_EMULATEGLFRAGCOLORBROADCAST_H_ +#define COMPILER_TRANSLATOR_TREEOPS_EMULATEGLFRAGCOLORBROADCAST_H_ + +#include <vector> + +#include "common/angleutils.h" + +namespace sh +{ +struct ShaderVariable; +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +// Replace all gl_FragColor with gl_FragData[0], and in the end of main() function, +// assign gl_FragData[1] ... gl_FragData[maxDrawBuffers - 1] with gl_FragData[0]. +// If gl_FragColor is in outputVariables, it is replaced by gl_FragData. +[[nodiscard]] bool EmulateGLFragColorBroadcast(TCompiler *compiler, + TIntermBlock *root, + int maxDrawBuffers, + std::vector<ShaderVariable> *outputVariables, + TSymbolTable *symbolTable, + int shaderVersion); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_EMULATEGLFRAGCOLORBROADCAST_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp new file mode 100644 index 0000000000..dfa36ad145 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp @@ -0,0 +1,274 @@ +// +// Copyright 2019 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. +// +// EmulateGLDrawID is an AST traverser to convert the gl_DrawID builtin +// to a uniform int +// +// EmulateGLBaseVertex is an AST traverser to convert the gl_BaseVertex builtin +// to a uniform int +// +// EmulateGLBaseInstance is an AST traverser to convert the gl_BaseInstance builtin +// to a uniform int +// + +#include "compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h" + +#include "angle_gl.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/BuiltIn.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +constexpr const ImmutableString kEmulatedGLDrawIDName("angle_DrawID"); + +class FindGLDrawIDTraverser : public TIntermTraverser +{ + public: + FindGLDrawIDTraverser() : TIntermTraverser(true, false, false), mVariable(nullptr) {} + + const TVariable *getGLDrawIDBuiltinVariable() { return mVariable; } + + protected: + void visitSymbol(TIntermSymbol *node) override + { + if (&node->variable() == BuiltInVariable::gl_DrawID()) + { + mVariable = &node->variable(); + } + } + + private: + const TVariable *mVariable; +}; + +class AddBaseVertexToGLVertexIDTraverser : public TIntermTraverser +{ + public: + AddBaseVertexToGLVertexIDTraverser() : TIntermTraverser(true, false, false) {} + + protected: + void visitSymbol(TIntermSymbol *node) override + { + if (&node->variable() == BuiltInVariable::gl_VertexID()) + { + + TIntermSymbol *baseVertexRef = new TIntermSymbol(BuiltInVariable::gl_BaseVertex()); + + TIntermBinary *addBaseVertex = new TIntermBinary(EOpAdd, node, baseVertexRef); + queueReplacement(addBaseVertex, OriginalNode::BECOMES_CHILD); + } + } +}; + +constexpr const ImmutableString kEmulatedGLBaseVertexName("angle_BaseVertex"); + +class FindGLBaseVertexTraverser : public TIntermTraverser +{ + public: + FindGLBaseVertexTraverser() : TIntermTraverser(true, false, false), mVariable(nullptr) {} + + const TVariable *getGLBaseVertexBuiltinVariable() { return mVariable; } + + protected: + void visitSymbol(TIntermSymbol *node) override + { + if (&node->variable() == BuiltInVariable::gl_BaseVertex()) + { + mVariable = &node->variable(); + } + } + + private: + const TVariable *mVariable; +}; + +constexpr const ImmutableString kEmulatedGLBaseInstanceName("angle_BaseInstance"); + +class FindGLBaseInstanceTraverser : public TIntermTraverser +{ + public: + FindGLBaseInstanceTraverser() : TIntermTraverser(true, false, false), mVariable(nullptr) {} + + const TVariable *getGLBaseInstanceBuiltinVariable() { return mVariable; } + + protected: + void visitSymbol(TIntermSymbol *node) override + { + if (&node->variable() == BuiltInVariable::gl_BaseInstance()) + { + mVariable = &node->variable(); + } + } + + private: + const TVariable *mVariable; +}; + +} // namespace + +bool EmulateGLDrawID(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + std::vector<sh::ShaderVariable> *uniforms, + bool shouldCollect) +{ + FindGLDrawIDTraverser traverser; + root->traverse(&traverser); + const TVariable *builtInVariable = traverser.getGLDrawIDBuiltinVariable(); + if (builtInVariable) + { + const TType *type = StaticType::Get<EbtInt, EbpHigh, EvqUniform, 1, 1>(); + const TVariable *drawID = + new TVariable(symbolTable, kEmulatedGLDrawIDName, type, SymbolType::AngleInternal); + const TIntermSymbol *drawIDSymbol = new TIntermSymbol(drawID); + + // AngleInternal variables don't get collected + if (shouldCollect) + { + ShaderVariable uniform; + uniform.name = kEmulatedGLDrawIDName.data(); + uniform.mappedName = kEmulatedGLDrawIDName.data(); + uniform.type = GLVariableType(*type); + uniform.precision = GLVariablePrecision(*type); + uniform.staticUse = symbolTable->isStaticallyUsed(*builtInVariable); + uniform.active = true; + uniform.binding = type->getLayoutQualifier().binding; + uniform.location = type->getLayoutQualifier().location; + uniform.offset = type->getLayoutQualifier().offset; + uniform.rasterOrdered = type->getLayoutQualifier().rasterOrdered; + uniform.readonly = type->getMemoryQualifier().readonly; + uniform.writeonly = type->getMemoryQualifier().writeonly; + uniforms->push_back(uniform); + } + + DeclareGlobalVariable(root, drawID); + if (!ReplaceVariableWithTyped(compiler, root, builtInVariable, drawIDSymbol)) + { + return false; + } + } + + return true; +} + +bool EmulateGLBaseVertexBaseInstance(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + std::vector<sh::ShaderVariable> *uniforms, + bool shouldCollect, + bool addBaseVertexToVertexID) +{ + bool addBaseVertex = false, addBaseInstance = false; + ShaderVariable uniformBaseVertex, uniformBaseInstance; + + if (addBaseVertexToVertexID) + { + // This is a workaround for Mac AMD GPU + // Replace gl_VertexID with (gl_VertexID + gl_BaseVertex) + AddBaseVertexToGLVertexIDTraverser traverserVertexID; + root->traverse(&traverserVertexID); + if (!traverserVertexID.updateTree(compiler, root)) + { + return false; + } + } + + FindGLBaseVertexTraverser traverserBaseVertex; + root->traverse(&traverserBaseVertex); + const TVariable *builtInVariableBaseVertex = + traverserBaseVertex.getGLBaseVertexBuiltinVariable(); + + if (builtInVariableBaseVertex) + { + const TVariable *baseVertex = BuiltInVariable::angle_BaseVertex(); + const TType &type = baseVertex->getType(); + const TIntermSymbol *baseVertexSymbol = new TIntermSymbol(baseVertex); + + // AngleInternal variables don't get collected + if (shouldCollect) + { + uniformBaseVertex.name = kEmulatedGLBaseVertexName.data(); + uniformBaseVertex.mappedName = kEmulatedGLBaseVertexName.data(); + uniformBaseVertex.type = GLVariableType(type); + uniformBaseVertex.precision = GLVariablePrecision(type); + uniformBaseVertex.staticUse = symbolTable->isStaticallyUsed(*builtInVariableBaseVertex); + uniformBaseVertex.active = true; + uniformBaseVertex.binding = type.getLayoutQualifier().binding; + uniformBaseVertex.location = type.getLayoutQualifier().location; + uniformBaseVertex.offset = type.getLayoutQualifier().offset; + uniformBaseVertex.rasterOrdered = type.getLayoutQualifier().rasterOrdered; + uniformBaseVertex.readonly = type.getMemoryQualifier().readonly; + uniformBaseVertex.writeonly = type.getMemoryQualifier().writeonly; + addBaseVertex = true; + } + + DeclareGlobalVariable(root, baseVertex); + if (!ReplaceVariableWithTyped(compiler, root, builtInVariableBaseVertex, baseVertexSymbol)) + { + return false; + } + } + + FindGLBaseInstanceTraverser traverserInstance; + root->traverse(&traverserInstance); + const TVariable *builtInVariableBaseInstance = + traverserInstance.getGLBaseInstanceBuiltinVariable(); + + if (builtInVariableBaseInstance) + { + const TVariable *baseInstance = BuiltInVariable::angle_BaseInstance(); + const TType &type = baseInstance->getType(); + const TIntermSymbol *baseInstanceSymbol = new TIntermSymbol(baseInstance); + + // AngleInternal variables don't get collected + if (shouldCollect) + { + uniformBaseInstance.name = kEmulatedGLBaseInstanceName.data(); + uniformBaseInstance.mappedName = kEmulatedGLBaseInstanceName.data(); + uniformBaseInstance.type = GLVariableType(type); + uniformBaseInstance.precision = GLVariablePrecision(type); + uniformBaseInstance.staticUse = + symbolTable->isStaticallyUsed(*builtInVariableBaseInstance); + uniformBaseInstance.active = true; + uniformBaseInstance.binding = type.getLayoutQualifier().binding; + uniformBaseInstance.location = type.getLayoutQualifier().location; + uniformBaseInstance.offset = type.getLayoutQualifier().offset; + uniformBaseInstance.rasterOrdered = type.getLayoutQualifier().rasterOrdered; + uniformBaseInstance.readonly = type.getMemoryQualifier().readonly; + uniformBaseInstance.writeonly = type.getMemoryQualifier().writeonly; + addBaseInstance = true; + } + + DeclareGlobalVariable(root, baseInstance); + if (!ReplaceVariableWithTyped(compiler, root, builtInVariableBaseInstance, + baseInstanceSymbol)) + { + return false; + } + } + + // Make sure the order in uniforms is the same as the traverse order + if (addBaseInstance) + { + uniforms->push_back(uniformBaseInstance); + } + if (addBaseVertex) + { + uniforms->push_back(uniformBaseVertex); + } + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h new file mode 100644 index 0000000000..792a7c96b2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h @@ -0,0 +1,47 @@ +// +// Copyright 2019 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. +// +// EmulateGLDrawID is an AST traverser to convert the gl_DrawID builtin +// to a uniform int +// +// EmulateGLBaseVertexBaseInstance is an AST traverser to convert the gl_BaseVertex and +// gl_BaseInstance builtin to uniform ints +// +// EmulateGLBaseInstance is an AST traverser to convert the gl_BaseInstance builtin +// to a uniform int +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_EMULATEMULTIDRAWSHADERBUILTINS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_EMULATEMULTIDRAWSHADERBUILTINS_H_ + +#include <GLSLANG/ShaderLang.h> +#include <vector> + +#include "common/angleutils.h" +#include "compiler/translator/HashNames.h" + +namespace sh +{ +struct ShaderVariable; +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool EmulateGLDrawID(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + std::vector<sh::ShaderVariable> *uniforms, + bool shouldCollect); + +[[nodiscard]] bool EmulateGLBaseVertexBaseInstance(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + std::vector<sh::ShaderVariable> *uniforms, + bool shouldCollect, + bool addBaseVertexToVertexID); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_EMULATEMULTIDRAWSHADERBUILTINS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp new file mode 100644 index 0000000000..1ede8c1a46 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp @@ -0,0 +1,120 @@ +// +// Copyright 2018 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. +// +// FoldExpressions.cpp: Fold expressions. This may fold expressions so that the qualifier of the +// folded node differs from the qualifier of the original expression, so it needs to be done after +// parsing and validation of qualifiers is complete. Expressions that are folded: +// 1. Ternary ops with a constant condition. +// 2. Sequence aka comma ops where the left side has no side effects. +// 3. Any expressions containing any of the above. + +#include "compiler/translator/tree_ops/FoldExpressions.h" + +#include "compiler/translator/Diagnostics.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class FoldExpressionsTraverser : public TIntermTraverser +{ + public: + FoldExpressionsTraverser(TDiagnostics *diagnostics) + : TIntermTraverser(true, false, false), mDiagnostics(diagnostics), mDidReplace(false) + {} + + bool didReplace() { return mDidReplace; } + + void nextIteration() { mDidReplace = false; } + + protected: + bool visitTernary(Visit visit, TIntermTernary *node) override + { + TIntermTyped *folded = node->fold(mDiagnostics); + if (folded != node) + { + queueReplacement(folded, OriginalNode::IS_DROPPED); + mDidReplace = true; + return false; + } + return true; + } + + bool visitAggregate(Visit visit, TIntermAggregate *node) override + { + TIntermTyped *folded = node->fold(mDiagnostics); + if (folded != node) + { + queueReplacement(folded, OriginalNode::IS_DROPPED); + mDidReplace = true; + return false; + } + return true; + } + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + TIntermTyped *folded = node->fold(mDiagnostics); + if (folded != node) + { + queueReplacement(folded, OriginalNode::IS_DROPPED); + mDidReplace = true; + return false; + } + return true; + } + + bool visitUnary(Visit visit, TIntermUnary *node) override + { + TIntermTyped *folded = node->fold(mDiagnostics); + if (folded != node) + { + queueReplacement(folded, OriginalNode::IS_DROPPED); + mDidReplace = true; + return false; + } + return true; + } + + bool visitSwizzle(Visit visit, TIntermSwizzle *node) override + { + TIntermTyped *folded = node->fold(mDiagnostics); + if (folded != node) + { + queueReplacement(folded, OriginalNode::IS_DROPPED); + mDidReplace = true; + return false; + } + return true; + } + + private: + TDiagnostics *mDiagnostics; + bool mDidReplace; +}; + +} // anonymous namespace + +bool FoldExpressions(TCompiler *compiler, TIntermBlock *root, TDiagnostics *diagnostics) +{ + FoldExpressionsTraverser traverser(diagnostics); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } while (traverser.didReplace()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h new file mode 100644 index 0000000000..1592444c50 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h @@ -0,0 +1,29 @@ +// +// Copyright 2018 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. +// +// FoldExpressions.h: Fold expressions. This may fold expressions so that the qualifier of the +// folded node differs from the qualifier of the original expression, so it needs to be done after +// parsing and validation of qualifiers is complete. Expressions that are folded: 1. Ternary ops +// with a constant condition. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_FOLDEXPRESSIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_FOLDEXPRESSIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TDiagnostics; + +[[nodiscard]] bool FoldExpressions(TCompiler *compiler, + TIntermBlock *root, + TDiagnostics *diagnostics); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_FOLDEXPRESSIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp new file mode 100644 index 0000000000..00dd0131c6 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp @@ -0,0 +1,101 @@ +// +// Copyright 2020 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/ForcePrecisionQualifier.h" +#include "angle_gl.h" +#include "common/debug.h" +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ +class TPrecisionTraverser : public TIntermTraverser +{ + public: + TPrecisionTraverser(TSymbolTable *symbolTable); + + protected: + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; + + void overwriteVariablePrecision(TType *type) const; +}; + +TPrecisionTraverser::TPrecisionTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, true, true, symbolTable) +{} + +void TPrecisionTraverser::overwriteVariablePrecision(TType *type) const +{ + if (type->getPrecision() == EbpHigh) + { + type->setPrecision(EbpMedium); + } +} + +bool TPrecisionTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) +{ + // Variable declaration. + if (visit == PreVisit) + { + const TIntermSequence &sequence = *(node->getSequence()); + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + TQualifier qualifier = variable->getQualifier(); + + // Don't modify uniform since it might be shared between vertex and fragment shader + if (qualifier == EvqUniform) + { + return true; + } + + // Visit the struct. + if (type.isStructSpecifier()) + { + const TStructure *structure = type.getStruct(); + const TFieldList &fields = structure->fields(); + for (size_t i = 0; i < fields.size(); ++i) + { + const TField *field = fields[i]; + const TType *fieldType = field->type(); + overwriteVariablePrecision((TType *)fieldType); + } + } + else if (type.getBasicType() == EbtInterfaceBlock) + { + const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock(); + const TFieldList &fields = interfaceBlock->fields(); + for (const TField *field : fields) + { + const TType *fieldType = field->type(); + overwriteVariablePrecision((TType *)fieldType); + } + } + else + { + overwriteVariablePrecision((TType *)&type); + } + } + return true; +} +} // namespace + +bool ForceShaderPrecisionToMediump(TIntermNode *root, TSymbolTable *symbolTable, GLenum shaderType) +{ + if (shaderType != GL_FRAGMENT_SHADER) + { + return true; + } + + TPrecisionTraverser traverser(symbolTable); + root->traverse(&traverser); + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h new file mode 100644 index 0000000000..c4cde25415 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h @@ -0,0 +1,17 @@ +// +// Copyright 2020 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_FORCEPRECISIONQUALIFIER_H_ +#define COMPILER_TRANSLATOR_TREEOPS_FORCEPRECISIONQUALIFIER_H_ + +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ +bool ForceShaderPrecisionToMediump(TIntermNode *root, TSymbolTable *symbolTable, GLenum shaderType); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_FORCEPRECISIONQUALIFIER_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp new file mode 100644 index 0000000000..789610ecc0 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp @@ -0,0 +1,359 @@ +// +// 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. +// + +#include "compiler/translator/tree_ops/InitializeVariables.h" + +#include "angle_gl.h" +#include "common/debug.h" +#include "compiler/translator/Compiler.h" +#include "compiler/translator/StaticType.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 "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +void AddArrayZeroInitSequence(const TIntermTyped *initializedNode, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable); + +void AddStructZeroInitSequence(const TIntermTyped *initializedNode, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable); + +TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode) +{ + TIntermTyped *zero = CreateZeroNode(initializedNode->getType()); + return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero); +} + +void AddZeroInitSequence(const TIntermTyped *initializedNode, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable) +{ + if (initializedNode->isArray()) + { + AddArrayZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported, + initSequenceOut, symbolTable); + } + else if (initializedNode->getType().isStructureContainingArrays() || + initializedNode->getType().isNamelessStruct()) + { + AddStructZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported, + initSequenceOut, symbolTable); + } + else if (initializedNode->getType().isInterfaceBlock()) + { + const TType &type = initializedNode->getType(); + const TInterfaceBlock &interfaceBlock = *type.getInterfaceBlock(); + const TFieldList &fieldList = interfaceBlock.fields(); + for (size_t fieldIndex = 0; fieldIndex < fieldList.size(); ++fieldIndex) + { + const TField &field = *fieldList[fieldIndex]; + TIntermTyped *fieldIndexRef = CreateIndexNode(static_cast<int>(fieldIndex)); + TIntermTyped *fieldReference = + new TIntermBinary(TOperator::EOpIndexDirectInterfaceBlock, + initializedNode->deepCopy(), fieldIndexRef); + TIntermTyped *fieldZero = CreateZeroNode(*field.type()); + TIntermTyped *assignment = + new TIntermBinary(TOperator::EOpAssign, fieldReference, fieldZero); + initSequenceOut->push_back(assignment); + } + } + else + { + initSequenceOut->push_back(CreateZeroInitAssignment(initializedNode)); + } +} + +void AddStructZeroInitSequence(const TIntermTyped *initializedNode, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable) +{ + ASSERT(initializedNode->getBasicType() == EbtStruct); + const TStructure *structType = initializedNode->getType().getStruct(); + for (int i = 0; i < static_cast<int>(structType->fields().size()); ++i) + { + TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct, + initializedNode->deepCopy(), CreateIndexNode(i)); + // Structs can't be defined inside structs, so the type of a struct field can't be a + // nameless struct. + ASSERT(!element->getType().isNamelessStruct()); + AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported, + initSequenceOut, symbolTable); + } +} + +void AddArrayZeroInitStatementList(const TIntermTyped *initializedNode, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable) +{ + for (unsigned int i = 0; i < initializedNode->getOutermostArraySize(); ++i) + { + TIntermBinary *element = + new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i)); + AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported, + initSequenceOut, symbolTable); + } +} + +void AddArrayZeroInitForLoop(const TIntermTyped *initializedNode, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable) +{ + ASSERT(initializedNode->isArray()); + const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>(); + const TType *highpIndexType = StaticType::Get<EbtInt, EbpHigh, EvqTemporary, 1, 1>(); + TVariable *indexVariable = + CreateTempVariable(symbolTable, highPrecisionSupported ? highpIndexType : mediumpIndexType); + + TIntermSymbol *indexSymbolNode = CreateTempSymbolNode(indexVariable); + TIntermDeclaration *indexInit = + CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType())); + TIntermConstantUnion *arraySizeNode = CreateIndexNode(initializedNode->getOutermostArraySize()); + TIntermBinary *indexSmallerThanSize = + new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode); + TIntermUnary *indexIncrement = + new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr); + + TIntermBlock *forLoopBody = new TIntermBlock(); + TIntermSequence *forLoopBodySeq = forLoopBody->getSequence(); + + TIntermBinary *element = new TIntermBinary(EOpIndexIndirect, initializedNode->deepCopy(), + indexSymbolNode->deepCopy()); + AddZeroInitSequence(element, true, highPrecisionSupported, forLoopBodySeq, symbolTable); + + TIntermLoop *forLoop = + new TIntermLoop(ELoopFor, indexInit, indexSmallerThanSize, indexIncrement, forLoopBody); + initSequenceOut->push_back(forLoop); +} + +void AddArrayZeroInitSequence(const TIntermTyped *initializedNode, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initSequenceOut, + TSymbolTable *symbolTable) +{ + // The array elements are assigned one by one to keep the AST compatible with ESSL 1.00 which + // doesn't have array assignment. We'll do this either with a for loop or just a list of + // statements assigning to each array index. Note that it is important to have the array init in + // the right order to workaround http://crbug.com/709317 + bool isSmallArray = initializedNode->getOutermostArraySize() <= 1u || + (initializedNode->getBasicType() != EbtStruct && + !initializedNode->getType().isArrayOfArrays() && + initializedNode->getOutermostArraySize() <= 3u); + if (initializedNode->getQualifier() == EvqFragData || + initializedNode->getQualifier() == EvqFragmentOut || isSmallArray || + !canUseLoopsToInitialize) + { + // Fragment outputs should not be indexed by non-constant indices. + // Also it doesn't make sense to use loops to initialize very small arrays. + AddArrayZeroInitStatementList(initializedNode, canUseLoopsToInitialize, + highPrecisionSupported, initSequenceOut, symbolTable); + } + else + { + AddArrayZeroInitForLoop(initializedNode, highPrecisionSupported, initSequenceOut, + symbolTable); + } +} + +void InsertInitCode(TCompiler *compiler, + TIntermSequence *mainBody, + const InitVariableList &variables, + TSymbolTable *symbolTable, + int shaderVersion, + const TExtensionBehavior &extensionBehavior, + bool canUseLoopsToInitialize, + bool highPrecisionSupported) +{ + for (const ShaderVariable &var : variables) + { + // Note that tempVariableName will reference a short-lived char array here - that's fine + // since we're only using it to find symbols. + ImmutableString tempVariableName(var.name.c_str(), var.name.length()); + + TIntermTyped *initializedSymbol = nullptr; + if (var.isBuiltIn() && !symbolTable->findUserDefined(tempVariableName)) + { + initializedSymbol = + ReferenceBuiltInVariable(tempVariableName, *symbolTable, shaderVersion); + if (initializedSymbol->getQualifier() == EvqFragData && + !IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers)) + { + // If GL_EXT_draw_buffers is disabled, only the 0th index of gl_FragData can be + // written to. + // TODO(oetuaho): This is a bit hacky and would be better to remove, if we came up + // with a good way to do it. Right now "gl_FragData" in symbol table is initialized + // to have the array size of MaxDrawBuffers, and the initialization happens before + // the shader sets the extensions it is using. + initializedSymbol = + new TIntermBinary(EOpIndexDirect, initializedSymbol, CreateIndexNode(0)); + } + } + else + { + if (tempVariableName != "") + { + initializedSymbol = ReferenceGlobalVariable(tempVariableName, *symbolTable); + } + else + { + // Must be a nameless interface block. + ASSERT(var.structOrBlockName != ""); + const TSymbol *symbol = symbolTable->findGlobal(var.structOrBlockName); + ASSERT(symbol && symbol->isInterfaceBlock()); + const TInterfaceBlock *block = static_cast<const TInterfaceBlock *>(symbol); + + for (const TField *field : block->fields()) + { + initializedSymbol = ReferenceGlobalVariable(field->name(), *symbolTable); + + TIntermSequence initCode; + CreateInitCode(initializedSymbol, canUseLoopsToInitialize, + highPrecisionSupported, &initCode, symbolTable); + mainBody->insert(mainBody->begin(), initCode.begin(), initCode.end()); + } + // Already inserted init code in this case + continue; + } + } + ASSERT(initializedSymbol != nullptr); + + TIntermSequence initCode; + CreateInitCode(initializedSymbol, canUseLoopsToInitialize, highPrecisionSupported, + &initCode, symbolTable); + mainBody->insert(mainBody->begin(), initCode.begin(), initCode.end()); + } +} + +class InitializeLocalsTraverser : public TIntermTraverser +{ + public: + InitializeLocalsTraverser(int shaderVersion, + TSymbolTable *symbolTable, + bool canUseLoopsToInitialize, + bool highPrecisionSupported) + : TIntermTraverser(true, false, false, symbolTable), + mShaderVersion(shaderVersion), + mCanUseLoopsToInitialize(canUseLoopsToInitialize), + mHighPrecisionSupported(highPrecisionSupported) + {} + + protected: + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + for (TIntermNode *declarator : *node->getSequence()) + { + if (!mInGlobalScope && !declarator->getAsBinaryNode()) + { + TIntermSymbol *symbol = declarator->getAsSymbolNode(); + ASSERT(symbol); + if (symbol->variable().symbolType() == SymbolType::Empty) + { + continue; + } + + // Arrays may need to be initialized one element at a time, since ESSL 1.00 does not + // support array constructors or assigning arrays. + bool arrayConstructorUnavailable = + (symbol->isArray() || symbol->getType().isStructureContainingArrays()) && + mShaderVersion == 100; + // Nameless struct constructors can't be referred to, so they also need to be + // initialized one element at a time. + // TODO(oetuaho): Check if it makes sense to initialize using a loop, even if we + // could use an initializer. It could at least reduce code size for very large + // arrays, but could hurt runtime performance. + if (arrayConstructorUnavailable || symbol->getType().isNamelessStruct()) + { + // SimplifyLoopConditions should have been run so the parent node of this node + // should not be a loop. + ASSERT(getParentNode()->getAsLoopNode() == nullptr); + // SeparateDeclarations should have already been run, so we don't need to worry + // about further declarators in this declaration depending on the effects of + // this declarator. + ASSERT(node->getSequence()->size() == 1); + TIntermSequence initCode; + CreateInitCode(symbol, mCanUseLoopsToInitialize, mHighPrecisionSupported, + &initCode, mSymbolTable); + insertStatementsInParentBlock(TIntermSequence(), initCode); + } + else + { + TIntermBinary *init = + new TIntermBinary(EOpInitialize, symbol, CreateZeroNode(symbol->getType())); + queueReplacementWithParent(node, symbol, init, OriginalNode::BECOMES_CHILD); + } + } + } + return false; + } + + private: + int mShaderVersion; + bool mCanUseLoopsToInitialize; + bool mHighPrecisionSupported; +}; + +} // namespace + +void CreateInitCode(const TIntermTyped *initializedSymbol, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initCode, + TSymbolTable *symbolTable) +{ + AddZeroInitSequence(initializedSymbol, canUseLoopsToInitialize, highPrecisionSupported, + initCode, symbolTable); +} + +bool InitializeUninitializedLocals(TCompiler *compiler, + TIntermBlock *root, + int shaderVersion, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TSymbolTable *symbolTable) +{ + InitializeLocalsTraverser traverser(shaderVersion, symbolTable, canUseLoopsToInitialize, + highPrecisionSupported); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +bool InitializeVariables(TCompiler *compiler, + TIntermBlock *root, + const InitVariableList &vars, + TSymbolTable *symbolTable, + int shaderVersion, + const TExtensionBehavior &extensionBehavior, + bool canUseLoopsToInitialize, + bool highPrecisionSupported) +{ + TIntermBlock *body = FindMainBody(root); + InsertInitCode(compiler, body->getSequence(), vars, symbolTable, shaderVersion, + extensionBehavior, canUseLoopsToInitialize, highPrecisionSupported); + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h new file mode 100644 index 0000000000..755b8d72eb --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h @@ -0,0 +1,60 @@ +// +// 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_INITIALIZEVARIABLES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_INITIALIZEVARIABLES_H_ + +#include <GLSLANG/ShaderLang.h> + +#include "compiler/translator/ExtensionBehavior.h" +#include "compiler/translator/IntermNode.h" + +namespace sh +{ +class TCompiler; +class TSymbolTable; + +typedef std::vector<sh::ShaderVariable> InitVariableList; + +// For all of the functions below: If canUseLoopsToInitialize is set, for loops are used instead of +// a large number of initializers where it can make sense, such as for initializing large arrays. + +// Populate a sequence of assignment operations to initialize "initializedSymbol". initializedSymbol +// may be an array, struct or any combination of these, as long as it contains only basic types. +void CreateInitCode(const TIntermTyped *initializedSymbol, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TIntermSequence *initCode, + TSymbolTable *symbolTable); + +// Initialize all uninitialized local variables, so that undefined behavior is avoided. +[[nodiscard]] bool InitializeUninitializedLocals(TCompiler *compiler, + TIntermBlock *root, + int shaderVersion, + bool canUseLoopsToInitialize, + bool highPrecisionSupported, + TSymbolTable *symbolTable); + +// This function can initialize all the types that CreateInitCode is able to initialize. All +// variables must be globals which can be found in the symbol table. For now it is used for the +// following two scenarios: +// 1. Initializing gl_Position; +// 2. Initializing output variables referred to in the shader source. +// Note: The type of each lvalue in an initializer is retrieved from the symbol table. gl_FragData +// requires special handling because the number of indices which can be initialized is determined by +// enabled extensions. +[[nodiscard]] bool InitializeVariables(TCompiler *compiler, + TIntermBlock *root, + const InitVariableList &vars, + TSymbolTable *symbolTable, + int shaderVersion, + const TExtensionBehavior &extensionBehavior, + bool canUseLoopsToInitialize, + bool highPrecisionSupported); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_INITIALIZEVARIABLES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp new file mode 100644 index 0000000000..11c8b72002 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp @@ -0,0 +1,613 @@ +// +// Copyright 2021 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. +// +// MonomorphizeUnsupportedFunctions: Monomorphize functions that are called with +// parameters that are incompatible with both Vulkan GLSL and Metal. +// + +#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h" + +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +struct Argument +{ + size_t argumentIndex; + TIntermTyped *argument; +}; + +struct FunctionData +{ + // Whether the original function is used. If this is false, the function can be removed because + // all callers have been modified. + bool isOriginalUsed; + // The original definition of the function, used to create the monomorphized version. + TIntermFunctionDefinition *originalDefinition; + // List of monomorphized versions of this function. They will be added next to the original + // version (or replace it). + TVector<TIntermFunctionDefinition *> monomorphizedDefinitions; +}; + +using FunctionMap = angle::HashMap<const TFunction *, FunctionData>; + +// Traverse the function definitions and initialize the map. Allows visitAggregate to have access +// to TIntermFunctionDefinition even when the function is only forward declared at that point. +void InitializeFunctionMap(TIntermBlock *root, FunctionMap *functionMapOut) +{ + TIntermSequence &sequence = *root->getSequence(); + + for (TIntermNode *node : sequence) + { + TIntermFunctionDefinition *asFuncDef = node->getAsFunctionDefinition(); + if (asFuncDef != nullptr) + { + const TFunction *function = asFuncDef->getFunction(); + ASSERT(function && functionMapOut->find(function) == functionMapOut->end()); + (*functionMapOut)[function] = FunctionData{false, asFuncDef, {}}; + } + } +} + +const TVariable *GetBaseUniform(TIntermTyped *node, bool *isSamplerInStructOut) +{ + *isSamplerInStructOut = false; + + while (node->getAsBinaryNode()) + { + TIntermBinary *asBinary = node->getAsBinaryNode(); + + TOperator op = asBinary->getOp(); + + // No opaque uniform can be inside an interface block. + if (op == EOpIndexDirectInterfaceBlock) + { + return nullptr; + } + + if (op == EOpIndexDirectStruct) + { + *isSamplerInStructOut = true; + } + + node = asBinary->getLeft(); + } + + // Only interested in uniform opaque types. If a function call within another function uses + // opaque uniforms in an unsupported way, it will be replaced in a follow up pass after the + // calling function is monomorphized. + if (node->getType().getQualifier() != EvqUniform) + { + return nullptr; + } + + ASSERT(IsOpaqueType(node->getType().getBasicType()) || + node->getType().isStructureContainingSamplers()); + + TIntermSymbol *asSymbol = node->getAsSymbolNode(); + ASSERT(asSymbol); + + return &asSymbol->variable(); +} + +TIntermTyped *ExtractSideEffects(TSymbolTable *symbolTable, + TIntermTyped *node, + TIntermSequence *replacementIndices) +{ + TIntermTyped *withoutSideEffects = node->deepCopy(); + + for (TIntermBinary *asBinary = withoutSideEffects->getAsBinaryNode(); asBinary; + asBinary = asBinary->getLeft()->getAsBinaryNode()) + { + TOperator op = asBinary->getOp(); + TIntermTyped *index = asBinary->getRight(); + + if (op == EOpIndexDirectStruct) + { + break; + } + + // No side effects with constant expressions. + if (op == EOpIndexDirect) + { + ASSERT(index->getAsConstantUnion()); + continue; + } + + ASSERT(op == EOpIndexIndirect); + + // If the index is a symbol, there's no side effect, so leave it as-is. + if (index->getAsSymbolNode()) + { + continue; + } + + // Otherwise create a temp variable initialized with the index and use that temp variable as + // the index. + TIntermDeclaration *tempDecl = nullptr; + TVariable *tempVar = DeclareTempVariable(symbolTable, index, EvqTemporary, &tempDecl); + + replacementIndices->push_back(tempDecl); + asBinary->replaceChildNode(index, new TIntermSymbol(tempVar)); + } + + return withoutSideEffects; +} + +void CreateMonomorphizedFunctionCallArgs(const TIntermSequence &originalCallArguments, + const TVector<Argument> &replacedArguments, + TIntermSequence *substituteArgsOut) +{ + size_t nextReplacedArg = 0; + for (size_t argIndex = 0; argIndex < originalCallArguments.size(); ++argIndex) + { + if (nextReplacedArg >= replacedArguments.size() || + argIndex != replacedArguments[nextReplacedArg].argumentIndex) + { + // Not replaced, keep argument as is. + substituteArgsOut->push_back(originalCallArguments[argIndex]); + } + else + { + TIntermTyped *argument = replacedArguments[nextReplacedArg].argument; + + // Iterate over indices of the argument and create a new arg for every non-const + // index. Note that the index itself may be an expression, and it may require further + // substitution in the next pass. + while (argument->getAsBinaryNode()) + { + TIntermBinary *asBinary = argument->getAsBinaryNode(); + if (asBinary->getOp() == EOpIndexIndirect) + { + TIntermTyped *index = asBinary->getRight(); + substituteArgsOut->push_back(index->deepCopy()); + } + argument = asBinary->getLeft(); + } + + ++nextReplacedArg; + } + } +} + +const TFunction *MonomorphizeFunction(TSymbolTable *symbolTable, + const TFunction *original, + TVector<Argument> *replacedArguments, + VariableReplacementMap *argumentMapOut) +{ + TFunction *substituteFunction = + new TFunction(symbolTable, kEmptyImmutableString, SymbolType::AngleInternal, + &original->getReturnType(), original->isKnownToNotHaveSideEffects()); + + size_t nextReplacedArg = 0; + for (size_t paramIndex = 0; paramIndex < original->getParamCount(); ++paramIndex) + { + const TVariable *originalParam = original->getParam(paramIndex); + + if (nextReplacedArg >= replacedArguments->size() || + paramIndex != (*replacedArguments)[nextReplacedArg].argumentIndex) + { + TVariable *substituteArgument = + new TVariable(symbolTable, originalParam->name(), &originalParam->getType(), + originalParam->symbolType()); + // Not replaced, add an identical parameter. + substituteFunction->addParameter(substituteArgument); + (*argumentMapOut)[originalParam] = new TIntermSymbol(substituteArgument); + } + else + { + TIntermTyped *substituteArgument = (*replacedArguments)[nextReplacedArg].argument; + (*argumentMapOut)[originalParam] = substituteArgument; + + // Iterate over indices of the argument and create a new parameter for every non-const + // index (which may be an expression). Replace the symbol in the argument with a + // variable of the index type. This is later used to replace the parameter in the + // function body. + while (substituteArgument->getAsBinaryNode()) + { + TIntermBinary *asBinary = substituteArgument->getAsBinaryNode(); + if (asBinary->getOp() == EOpIndexIndirect) + { + TIntermTyped *index = asBinary->getRight(); + TType *indexType = new TType(index->getType()); + indexType->setQualifier(EvqParamIn); + + TVariable *param = new TVariable(symbolTable, kEmptyImmutableString, indexType, + SymbolType::AngleInternal); + substituteFunction->addParameter(param); + + // The argument now uses the function parameters as indices. + asBinary->replaceChildNode(asBinary->getRight(), new TIntermSymbol(param)); + } + substituteArgument = asBinary->getLeft(); + } + + ++nextReplacedArg; + } + } + + return substituteFunction; +} + +class MonomorphizeTraverser final : public TIntermTraverser +{ + public: + explicit MonomorphizeTraverser(TCompiler *compiler, + TSymbolTable *symbolTable, + const ShCompileOptions &compileOptions, + UnsupportedFunctionArgsBitSet unsupportedFunctionArgs, + FunctionMap *functionMap) + : TIntermTraverser(true, false, false, symbolTable), + mCompiler(compiler), + mCompileOptions(compileOptions), + mUnsupportedFunctionArgs(unsupportedFunctionArgs), + mFunctionMap(functionMap) + {} + + bool visitAggregate(Visit visit, TIntermAggregate *node) override + { + if (node->getOp() != EOpCallFunctionInAST) + { + return true; + } + + const TFunction *function = node->getFunction(); + ASSERT(function && mFunctionMap->find(function) != mFunctionMap->end()); + + FunctionData &data = (*mFunctionMap)[function]; + + TIntermFunctionDefinition *monomorphized = + processFunctionCall(node, data.originalDefinition, &data.isOriginalUsed); + if (monomorphized) + { + data.monomorphizedDefinitions.push_back(monomorphized); + } + + return true; + } + + bool getAnyMonomorphized() const { return mAnyMonomorphized; } + + private: + bool isUnsupportedArgument(TIntermTyped *callArgument, const TVariable *funcArgument) const + { + // Only interested in opaque uniforms and structs that contain samplers. + const bool isOpaqueType = IsOpaqueType(funcArgument->getType().getBasicType()); + const bool isStructContainingSamplers = + funcArgument->getType().isStructureContainingSamplers(); + if (!isOpaqueType && !isStructContainingSamplers) + { + return false; + } + + // If not uniform (the variable was itself a function parameter), don't process it in + // this pass, as we don't know which actual uniform it corresponds to. + bool isSamplerInStruct = false; + const TVariable *uniform = GetBaseUniform(callArgument, &isSamplerInStruct); + if (uniform == nullptr) + { + return false; + } + + const TType &type = uniform->getType(); + + if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::StructContainingSamplers]) + { + // Monomorphize if the parameter is a structure that contains samplers (so in + // RewriteStructSamplers we don't need to rewrite the functions to accept multiple + // parameters split from the struct). + if (isStructContainingSamplers) + { + return true; + } + } + + if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::ArrayOfArrayOfSamplerOrImage]) + { + // Monomorphize if: + // + // - The opaque uniform is a sampler in a struct (which can create an array-of-array + // situation), and the function expects an array of samplers, or + // + // - The opaque uniform is an array of array of sampler or image, and it's partially + // subscripted (i.e. the function itself expects an array) + // + const bool isParameterArrayOfOpaqueType = funcArgument->getType().isArray(); + const bool isArrayOfArrayOfSamplerOrImage = + (type.isSampler() || type.isImage()) && type.isArrayOfArrays(); + if (isSamplerInStruct && isParameterArrayOfOpaqueType) + { + return true; + } + if (isArrayOfArrayOfSamplerOrImage && isParameterArrayOfOpaqueType) + { + return true; + } + } + + if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::AtomicCounter]) + { + if (type.isAtomicCounter()) + { + return true; + } + } + + if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::SamplerCubeEmulation]) + { + // Monomorphize if the opaque uniform is a samplerCube and ES2's cube sampling emulation + // is requested. + if (type.isSamplerCube() && mCompileOptions.emulateSeamfulCubeMapSampling) + { + return true; + } + } + + if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::Image]) + { + if (type.isImage()) + { + return true; + } + } + + if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::PixelLocalStorage]) + { + if (type.isPixelLocal()) + { + return true; + } + } + + return false; + } + + TIntermFunctionDefinition *processFunctionCall(TIntermAggregate *functionCall, + TIntermFunctionDefinition *originalDefinition, + bool *isOriginalUsedOut) + { + const TFunction *function = functionCall->getFunction(); + const TIntermSequence &callArguments = *functionCall->getSequence(); + + TVector<Argument> replacedArguments; + TIntermSequence replacementIndices; + + // Go through function call arguments, and see if any is used in an unsupported way. + for (size_t argIndex = 0; argIndex < callArguments.size(); ++argIndex) + { + TIntermTyped *callArgument = callArguments[argIndex]->getAsTyped(); + const TVariable *funcArgument = function->getParam(argIndex); + if (isUnsupportedArgument(callArgument, funcArgument)) + { + // Copy the argument and extract the side effects. + TIntermTyped *argument = + ExtractSideEffects(mSymbolTable, callArgument, &replacementIndices); + + replacedArguments.push_back({argIndex, argument}); + } + } + + if (replacedArguments.empty()) + { + *isOriginalUsedOut = true; + return nullptr; + } + + mAnyMonomorphized = true; + + insertStatementsInParentBlock(replacementIndices); + + // Create the arguments for the substitute function call. Done before monomorphizing the + // function, which transforms the arguments to what needs to be replaced in the function + // body. + TIntermSequence newCallArgs; + CreateMonomorphizedFunctionCallArgs(callArguments, replacedArguments, &newCallArgs); + + // Duplicate the function and substitute the replaced arguments with only the non-const + // indices. Additionally, substitute the non-const indices of arguments with the new + // function parameters. + VariableReplacementMap argumentMap; + const TFunction *monomorphized = + MonomorphizeFunction(mSymbolTable, function, &replacedArguments, &argumentMap); + + // Replace this function call with a call to the new one. + queueReplacement(TIntermAggregate::CreateFunctionCall(*monomorphized, &newCallArgs), + OriginalNode::IS_DROPPED); + + // Create a new function definition, with the body of the old function but with the replaced + // parameters substituted with the calling expressions. + TIntermFunctionPrototype *substitutePrototype = new TIntermFunctionPrototype(monomorphized); + TIntermBlock *substituteBlock = originalDefinition->getBody()->deepCopy(); + GetDeclaratorReplacements(mSymbolTable, substituteBlock, &argumentMap); + bool valid = ReplaceVariables(mCompiler, substituteBlock, argumentMap); + ASSERT(valid); + + return new TIntermFunctionDefinition(substitutePrototype, substituteBlock); + } + + TCompiler *mCompiler; + const ShCompileOptions &mCompileOptions; + UnsupportedFunctionArgsBitSet mUnsupportedFunctionArgs; + bool mAnyMonomorphized = false; + + // Map of original to monomorphized functions. + FunctionMap *mFunctionMap; +}; + +class UpdateFunctionsDefinitionsTraverser final : public TIntermTraverser +{ + public: + explicit UpdateFunctionsDefinitionsTraverser(TSymbolTable *symbolTable, + const FunctionMap &functionMap) + : TIntermTraverser(true, false, false, symbolTable), mFunctionMap(functionMap) + {} + + void visitFunctionPrototype(TIntermFunctionPrototype *node) override + { + const bool isInFunctionDefinition = getParentNode()->getAsFunctionDefinition() != nullptr; + if (isInFunctionDefinition) + { + return; + } + + // Add to and possibly replace the function prototype with replacement prototypes. + const TFunction *function = node->getFunction(); + ASSERT(function && mFunctionMap.find(function) != mFunctionMap.end()); + + const FunctionData &data = mFunctionMap.at(function); + + // If nothing to do, leave it be. + if (data.monomorphizedDefinitions.empty()) + { + ASSERT(data.isOriginalUsed); + return; + } + + // Replace the prototype with itself (if function is still used) as well as any + // monomorphized versions. + TIntermSequence replacement; + if (data.isOriginalUsed) + { + replacement.push_back(node); + } + for (TIntermFunctionDefinition *monomorphizedDefinition : data.monomorphizedDefinitions) + { + replacement.push_back(new TIntermFunctionPrototype( + monomorphizedDefinition->getFunctionPrototype()->getFunction())); + } + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(replacement)); + } + + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override + { + // Add to and possibly replace the function definition with replacement definitions. + const TFunction *function = node->getFunction(); + ASSERT(function && mFunctionMap.find(function) != mFunctionMap.end()); + + const FunctionData &data = mFunctionMap.at(function); + + // If nothing to do, leave it be. + if (data.monomorphizedDefinitions.empty()) + { + ASSERT(data.isOriginalUsed || function->name() == "main"); + return false; + } + + // Replace the definition with itself (if function is still used) as well as any + // monomorphized versions. + TIntermSequence replacement; + if (data.isOriginalUsed) + { + replacement.push_back(node); + } + for (TIntermFunctionDefinition *monomorphizedDefinition : data.monomorphizedDefinitions) + { + replacement.push_back(monomorphizedDefinition); + } + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(replacement)); + + return false; + } + + private: + const FunctionMap &mFunctionMap; +}; + +void SortDeclarations(TIntermBlock *root) +{ + TIntermSequence *original = root->getSequence(); + + TIntermSequence replacement; + TIntermSequence functionDefs; + + // Accumulate non-function-definition declarations in |replacement| and function definitions in + // |functionDefs|. + for (TIntermNode *node : *original) + { + if (node->getAsFunctionDefinition() || node->getAsFunctionPrototypeNode()) + { + functionDefs.push_back(node); + } + else + { + replacement.push_back(node); + } + } + + // Append function definitions to |replacement|. + replacement.insert(replacement.end(), functionDefs.begin(), functionDefs.end()); + + // Replace root's sequence with |replacement|. + root->replaceAllChildren(replacement); +} + +bool MonomorphizeUnsupportedFunctionsImpl(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const ShCompileOptions &compileOptions, + UnsupportedFunctionArgsBitSet unsupportedFunctionArgs) +{ + // First, sort out the declarations such that all non-function declarations are placed before + // function definitions. This way when the function is replaced with one that references said + // declarations (i.e. uniforms), the uniform declaration is already present above it. + SortDeclarations(root); + + while (true) + { + FunctionMap functionMap; + InitializeFunctionMap(root, &functionMap); + + MonomorphizeTraverser monomorphizer(compiler, symbolTable, compileOptions, + unsupportedFunctionArgs, &functionMap); + root->traverse(&monomorphizer); + + if (!monomorphizer.getAnyMonomorphized()) + { + break; + } + + if (!monomorphizer.updateTree(compiler, root)) + { + return false; + } + + UpdateFunctionsDefinitionsTraverser functionUpdater(symbolTable, functionMap); + root->traverse(&functionUpdater); + + if (!functionUpdater.updateTree(compiler, root)) + { + return false; + } + } + + return true; +} +} // anonymous namespace + +bool MonomorphizeUnsupportedFunctions(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const ShCompileOptions &compileOptions, + UnsupportedFunctionArgsBitSet unsupportedFunctionArgs) +{ + // This function actually applies multiple transformation, and the AST may not be valid until + // the transformations are entirely done. Some validation is momentarily disabled. + bool enableValidateFunctionCall = compiler->disableValidateFunctionCall(); + + bool result = MonomorphizeUnsupportedFunctionsImpl(compiler, root, symbolTable, compileOptions, + unsupportedFunctionArgs); + + compiler->restoreValidateFunctionCall(enableValidateFunctionCall); + return result && compiler->validateAST(root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h new file mode 100644 index 0000000000..a9a212704b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h @@ -0,0 +1,54 @@ +// +// Copyright 2021 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. +// +// MonomorphizeUnsupportedFunctions: Monomorphize functions that are called with +// parameters that are incompatible with both Vulkan GLSL and Metal: +// +// - Samplers in structs +// - Structs that have samplers +// - Partially subscripted array of array of samplers +// - Partially subscripted array of array of images +// - Atomic counters +// - samplerCube variables when emulating ES2's cube map sampling +// - image* variables with r32f formats (to emulate imageAtomicExchange) +// +// This transformation basically duplicates such functions, removes the +// sampler/image/atomic_counter parameters and uses the opaque uniforms used by the caller. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_VULKAN_MONOMORPHIZEUNSUPPORTEDFUNCTIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_VULKAN_MONOMORPHIZEUNSUPPORTEDFUNCTIONS_H_ + +#include "common/angleutils.h" +#include "compiler/translator/Compiler.h" + +namespace sh +{ +class TIntermBlock; +class TSymbolTable; + +// Types of function prameters that should trigger monomorphization. +enum class UnsupportedFunctionArgs +{ + StructContainingSamplers = 0, + ArrayOfArrayOfSamplerOrImage = 1, + AtomicCounter = 2, + SamplerCubeEmulation = 3, + Image = 4, + PixelLocalStorage = 5, + + InvalidEnum = 6, + EnumCount = 6, +}; + +using UnsupportedFunctionArgsBitSet = angle::PackedEnumBitSet<UnsupportedFunctionArgs>; + +[[nodiscard]] bool MonomorphizeUnsupportedFunctions(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const ShCompileOptions &compileOptions, + UnsupportedFunctionArgsBitSet); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_VULKAN_MONOMORPHIZEUNSUPPORTEDFUNCTIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp new file mode 100644 index 0000000000..a23d0770ef --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp @@ -0,0 +1,127 @@ +// +// Copyright 2019 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. +// +// NameNamelessUniformBuffers: Gives nameless uniform buffer variables internal names. +// + +#include "compiler/translator/tree_ops/NameNamelessUniformBuffers.h" + +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ +namespace +{ +// Traverse uniform buffer declarations and give name to nameless declarations. Keeps track of +// the interface fields which will be used in the source without the interface block variable name +// and replaces them with name.field. +class NameUniformBufferVariablesTraverser : public TIntermTraverser +{ + public: + explicit NameUniformBufferVariablesTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override + { + ASSERT(visit == PreVisit); + + const TIntermSequence &sequence = *(decl->getSequence()); + + TIntermTyped *variableNode = sequence.front()->getAsTyped(); + const TType &type = variableNode->getType(); + + // If it's an interface block, it may have to be converted if it contains any row-major + // fields. + if (!type.isInterfaceBlock()) + { + return true; + } + + // Multi declaration statements are already separated, so there can only be one variable + // here. + ASSERT(sequence.size() == 1); + const TVariable *variable = &variableNode->getAsSymbolNode()->variable(); + if (variable->symbolType() != SymbolType::Empty) + { + return false; + } + + TIntermDeclaration *newDeclaration = new TIntermDeclaration; + TVariable *newVariable = new TVariable(mSymbolTable, kEmptyImmutableString, &type, + SymbolType::AngleInternal, variable->extensions()); + newDeclaration->appendDeclarator(new TIntermSymbol(newVariable)); + + queueReplacement(newDeclaration, OriginalNode::IS_DROPPED); + + // It's safe to key the map with the interface block, as there couldn't have been multiple + // declarations with this interface block (as the variable is nameless), so for nameless + // uniform buffers, the interface block is unique. + mNamelessUniformBuffersMap[type.getInterfaceBlock()] = newVariable; + + return false; + } + + void visitSymbol(TIntermSymbol *symbol) override + { + const TType &type = symbol->getType(); + + // The symbols we are looking for have the interface block pointer set, but are not + // interface blocks. These are references to fields of nameless uniform buffers. + if (type.isInterfaceBlock() || type.getInterfaceBlock() == nullptr) + { + return; + } + + const TInterfaceBlock *block = type.getInterfaceBlock(); + + // If block variable is not nameless, there's nothing to do. + if (mNamelessUniformBuffersMap.count(block) == 0) + { + return; + } + + const ImmutableString symbolName = symbol->getName(); + + // Find which field it is + const TVector<TField *> fields = block->fields(); + for (size_t fieldIndex = 0; fieldIndex < fields.size(); ++fieldIndex) + { + const TField *field = fields[fieldIndex]; + if (field->name() != symbolName) + { + continue; + } + + // Replace this node with a binary node that indexes the named uniform buffer. + TIntermSymbol *namedUniformBuffer = + new TIntermSymbol(mNamelessUniformBuffersMap[block]); + TIntermBinary *replacement = + new TIntermBinary(EOpIndexDirectInterfaceBlock, namedUniformBuffer, + CreateIndexNode(static_cast<uint32_t>(fieldIndex))); + + queueReplacement(replacement, OriginalNode::IS_DROPPED); + + return; + } + + UNREACHABLE(); + } + + private: + // A map from nameless uniform buffers to their named replacements. + std::unordered_map<const TInterfaceBlock *, const TVariable *> mNamelessUniformBuffersMap; +}; +} // anonymous namespace + +bool NameNamelessUniformBuffers(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + NameUniformBufferVariablesTraverser nameUniformBufferVariables(symbolTable); + root->traverse(&nameUniformBufferVariables); + return nameUniformBufferVariables.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h new file mode 100644 index 0000000000..23964a5846 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h @@ -0,0 +1,32 @@ +// +// Copyright 2019 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. +// +// NameNamelessUniformBuffers: Gives nameless uniform buffer variables internal names. +// +// For example: +// uniform UniformBuffer { int a; }; +// x = a; +// becomes: +// uniform UniformBuffer { int a; } s123; +// x = s123.a; +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_NAMENAMELESSUNIFORMBUFFERS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_NAMENAMELESSUNIFORMBUFFERS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool NameNamelessUniformBuffers(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_NAMENAMELESSUNIFORMBUFFERS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp new file mode 100644 index 0000000000..276c8c98ed --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp @@ -0,0 +1,127 @@ +// +// Copyright 2018 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. +// +// PruneEmptyCases.cpp: The PruneEmptyCases function prunes cases that are followed by nothing from +// the AST. + +#include "compiler/translator/tree_ops/PruneEmptyCases.h" + +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +bool AreEmptyBlocks(const TIntermSequence *statements); + +bool IsEmptyBlock(TIntermNode *node) +{ + TIntermBlock *asBlock = node->getAsBlock(); + if (asBlock) + { + return AreEmptyBlocks(asBlock->getSequence()); + } + // Empty declarations should have already been pruned, otherwise they would need to be handled + // here. Note that declarations for struct types do contain a nameless child node. + ASSERT(node->getAsDeclarationNode() == nullptr || + !node->getAsDeclarationNode()->getSequence()->empty()); + // Pure literal statements should also already be pruned. + ASSERT(node->getAsConstantUnion() == nullptr); + return false; +} + +// Return true if all statements in "statements" consist only of empty blocks and no-op statements. +// Returns true also if there are no statements. +bool AreEmptyBlocks(const TIntermSequence *statements) +{ + for (size_t i = 0u; i < statements->size(); ++i) + { + if (!IsEmptyBlock(statements->at(i))) + { + return false; + } + } + return true; +} + +class PruneEmptyCasesTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, TIntermBlock *root); + + private: + PruneEmptyCasesTraverser(); + bool visitSwitch(Visit visit, TIntermSwitch *node) override; +}; + +bool PruneEmptyCasesTraverser::apply(TCompiler *compiler, TIntermBlock *root) +{ + PruneEmptyCasesTraverser prune; + root->traverse(&prune); + return prune.updateTree(compiler, root); +} + +PruneEmptyCasesTraverser::PruneEmptyCasesTraverser() : TIntermTraverser(true, false, false) {} + +bool PruneEmptyCasesTraverser::visitSwitch(Visit visit, TIntermSwitch *node) +{ + // This may mutate the statementList, but that's okay, since traversal has not yet reached + // there. + TIntermBlock *statementList = node->getStatementList(); + TIntermSequence *statements = statementList->getSequence(); + + // Iterate block children in reverse order. Cases that are only followed by other cases or empty + // blocks are marked for pruning. + size_t i = statements->size(); + size_t lastNoOpInStatementList = i; + while (i > 0) + { + --i; + TIntermNode *statement = statements->at(i); + if (statement->getAsCaseNode() || IsEmptyBlock(statement)) + { + lastNoOpInStatementList = i; + } + else + { + break; + } + } + if (lastNoOpInStatementList == 0) + { + // Remove the entire switch statement, extracting the init expression if needed. + TIntermTyped *init = node->getInit(); + if (init->hasSideEffects()) + { + queueReplacement(init, OriginalNode::IS_DROPPED); + } + else + { + TIntermSequence emptyReplacement; + ASSERT(getParentNode()->getAsBlock()); + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(emptyReplacement)); + } + return false; + } + if (lastNoOpInStatementList < statements->size()) + { + statements->erase(statements->begin() + lastNoOpInStatementList, statements->end()); + } + + return true; +} + +} // namespace + +bool PruneEmptyCases(TCompiler *compiler, TIntermBlock *root) +{ + return PruneEmptyCasesTraverser::apply(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h new file mode 100644 index 0000000000..5098fec9b7 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h @@ -0,0 +1,22 @@ +// +// Copyright 2018 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. +// +// PruneEmptyCases.h: The PruneEmptyCases function prunes cases that are followed by nothing from +// the AST. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_PRUNEEMPTYCASES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_PRUNEEMPTYCASES_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool PruneEmptyCases(TCompiler *compiler, TIntermBlock *root); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_PRUNEEMPTYCASES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp new file mode 100644 index 0000000000..fdc6070660 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp @@ -0,0 +1,215 @@ +// +// 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. +// +// PruneNoOps.cpp: The PruneNoOps function prunes: +// 1. Empty declarations "int;". Empty declarators will be pruned as well, so for example: +// int , a; +// is turned into +// int a; +// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision for float, +// so float literal statements would end up with no precision which is invalid ESSL. +// 3. Statements after discard, return, break and continue. + +#include "compiler/translator/tree_ops/PruneNoOps.h" + +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +bool IsNoOp(TIntermNode *node) +{ + if (node->getAsConstantUnion() != nullptr) + { + return true; + } + bool isEmptyDeclaration = node->getAsDeclarationNode() != nullptr && + node->getAsDeclarationNode()->getSequence()->empty(); + if (isEmptyDeclaration) + { + return true; + } + return false; +} + +class PruneNoOpsTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); + + private: + PruneNoOpsTraverser(TSymbolTable *symbolTable); + bool visitDeclaration(Visit, TIntermDeclaration *node) override; + bool visitBlock(Visit visit, TIntermBlock *node) override; + bool visitLoop(Visit visit, TIntermLoop *loop) override; + bool visitBranch(Visit visit, TIntermBranch *node) override; + + bool mIsBranchVisited = false; +}; + +bool PruneNoOpsTraverser::apply(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + PruneNoOpsTraverser prune(symbolTable); + root->traverse(&prune); + return prune.updateTree(compiler, root); +} + +PruneNoOpsTraverser::PruneNoOpsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, true, true, symbolTable) +{} + +bool PruneNoOpsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) +{ + if (visit != PreVisit) + { + return true; + } + + TIntermSequence *sequence = node->getSequence(); + if (sequence->size() >= 1) + { + TIntermSymbol *declaratorSymbol = sequence->front()->getAsSymbolNode(); + // Prune declarations without a variable name, unless it's an interface block declaration. + if (declaratorSymbol != nullptr && + declaratorSymbol->variable().symbolType() == SymbolType::Empty && + !declaratorSymbol->isInterfaceBlock()) + { + if (sequence->size() > 1) + { + // Generate a replacement that will remove the empty declarator in the beginning of + // a declarator list. Example of a declaration that will be changed: + // float, a; + // will be changed to + // float a; + // This applies also to struct declarations. + TIntermSequence emptyReplacement; + mMultiReplacements.emplace_back(node, declaratorSymbol, + std::move(emptyReplacement)); + } + else if (declaratorSymbol->getBasicType() != EbtStruct) + { + // If there are entirely empty non-struct declarations, they result in + // TIntermDeclaration nodes without any children in the parsing stage. These are + // handled in visitBlock and visitLoop. + UNREACHABLE(); + } + else if (declaratorSymbol->getQualifier() != EvqGlobal && + declaratorSymbol->getQualifier() != EvqTemporary) + { + // Single struct declarations may just declare the struct type and no variables, so + // they should not be pruned. Here we handle an empty struct declaration with a + // qualifier, for example like this: + // const struct a { int i; }; + // NVIDIA GL driver version 367.27 doesn't accept this kind of declarations, so we + // convert the declaration to a regular struct declaration. This is okay, since ESSL + // 1.00 spec section 4.1.8 says about structs that "The optional qualifiers only + // apply to any declarators, and are not part of the type being defined for name." + + // Create a new variable to use in the declarator so that the variable and node + // types are kept consistent. + TType *type = new TType(declaratorSymbol->getType()); + if (mInGlobalScope) + { + type->setQualifier(EvqGlobal); + } + else + { + type->setQualifier(EvqTemporary); + } + TVariable *variable = + new TVariable(mSymbolTable, kEmptyImmutableString, type, SymbolType::Empty); + queueReplacementWithParent(node, declaratorSymbol, new TIntermSymbol(variable), + OriginalNode::IS_DROPPED); + } + } + } + return false; +} + +bool PruneNoOpsTraverser::visitBlock(Visit visit, TIntermBlock *node) +{ + ASSERT(visit == PreVisit); + + TIntermSequence &statements = *node->getSequence(); + + // Visit each statement in the block one by one. Once a branch is visited (break, continue, + // return or discard), drop the rest of the statements. + for (size_t statementIndex = 0; statementIndex < statements.size(); ++statementIndex) + { + TIntermNode *statement = statements[statementIndex]; + + // If the statement is a switch case label, stop pruning and continue visiting the children. + if (statement->getAsCaseNode() != nullptr) + { + mIsBranchVisited = false; + } + + // If a branch is visited, prune the statement. If the statement is a no-op, also prune it. + if (mIsBranchVisited || IsNoOp(statement)) + { + TIntermSequence emptyReplacement; + mMultiReplacements.emplace_back(node, statement, std::move(emptyReplacement)); + continue; + } + + // Visit the statement if not pruned. + statement->traverse(this); + } + + // If the parent is a block and mIsBranchVisited is set, this is a nested block without any + // condition (like if, loop or switch), so the rest of the parent block should also be pruned. + // Otherwise the parent block should be unaffected. + if (mIsBranchVisited && getParentNode()->getAsBlock() == nullptr) + { + mIsBranchVisited = false; + } + + return false; +} + +bool PruneNoOpsTraverser::visitLoop(Visit visit, TIntermLoop *loop) +{ + if (visit != PreVisit) + { + return true; + } + + TIntermTyped *expr = loop->getExpression(); + if (expr != nullptr && IsNoOp(expr)) + { + loop->setExpression(nullptr); + } + TIntermNode *init = loop->getInit(); + if (init != nullptr && IsNoOp(init)) + { + loop->setInit(nullptr); + } + + return true; +} + +bool PruneNoOpsTraverser::visitBranch(Visit visit, TIntermBranch *node) +{ + ASSERT(visit == PreVisit); + + mIsBranchVisited = true; + + // Only possible child is the value of a return statement, which has nothing to prune. + return false; +} +} // namespace + +bool PruneNoOps(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + return PruneNoOpsTraverser::apply(compiler, root, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h new file mode 100644 index 0000000000..a73cb71d08 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h @@ -0,0 +1,29 @@ +// +// 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. +// +// PruneNoOps.h: The PruneNoOps function prunes: +// 1. Empty declarations "int;". Empty declarators will be pruned as well, so for example: +// int , a; +// is turned into +// int a; +// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision for float, +// so float literal statements would end up with no precision which is invalid ESSL. +// 3. Statements after discard, return, break and continue. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_PRUNENOOPS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_PRUNENOOPS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool PruneNoOps(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_PRUNENOOPS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp new file mode 100644 index 0000000000..ea15ab7844 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp @@ -0,0 +1,119 @@ +// +// 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. +// +// During parsing, all constant expressions are folded to constant union nodes. The expressions that +// have been folded may have had precision qualifiers, which should affect the precision of the +// consuming operation. If the folded constant union nodes are written to output as such they won't +// have any precision qualifiers, and their effect on the precision of the consuming operation is +// lost. +// +// RecordConstantPrecision is an AST traverser that inspects the precision qualifiers of constants +// and hoists the constants outside the containing expression as precision qualified named variables +// in case that is required for correct precision propagation. +// + +#include "compiler/translator/tree_ops/RecordConstantPrecision.h" + +#include "compiler/translator/InfoSink.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class RecordConstantPrecisionTraverser : public TIntermTraverser +{ + public: + RecordConstantPrecisionTraverser(TSymbolTable *symbolTable); + + void visitConstantUnion(TIntermConstantUnion *node) override; + + protected: + bool operandAffectsParentOperationPrecision(TIntermTyped *operand); +}; + +RecordConstantPrecisionTraverser::RecordConstantPrecisionTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable) +{} + +bool RecordConstantPrecisionTraverser::operandAffectsParentOperationPrecision(TIntermTyped *operand) +{ + if (getParentNode()->getAsCaseNode() || getParentNode()->getAsBlock()) + { + return false; + } + + if (operand->getBasicType() == EbtBool || operand->getBasicType() == EbtStruct) + { + return false; + } + + const TIntermBinary *parentAsBinary = getParentNode()->getAsBinaryNode(); + if (parentAsBinary != nullptr) + { + // If the constant is assigned or is used to initialize a variable, or if it's an index, + // its precision has no effect. + switch (parentAsBinary->getOp()) + { + case EOpInitialize: + case EOpAssign: + case EOpIndexDirect: + case EOpIndexDirectStruct: + case EOpIndexDirectInterfaceBlock: + case EOpIndexIndirect: + return false; + default: + return true; + } + } + + TIntermAggregate *parentAsAggregate = getParentNode()->getAsAggregate(); + if (parentAsAggregate != nullptr) + { + // The precision of an aggregate is derived from children only in the following conditions: + // + // - Built-in math operations + // - Constructors + // + return parentAsAggregate->isConstructor() || + BuiltInGroup::IsMath(parentAsAggregate->getOp()); + } + + return true; +} + +void RecordConstantPrecisionTraverser::visitConstantUnion(TIntermConstantUnion *node) +{ + // If the constant has lowp or undefined precision, it can't increase the precision of consuming + // operations. + if (node->getPrecision() < EbpMedium) + return; + + // It's possible the node has no effect on the precision of the consuming expression, depending + // on the consuming expression, and the precision of the other parameters of the expression. + if (!operandAffectsParentOperationPrecision(node)) + return; + + // Make the constant a precision-qualified named variable to make sure it affects the precision + // of the consuming expression. + TIntermDeclaration *variableDeclaration = nullptr; + TVariable *variable = DeclareTempVariable(mSymbolTable, node, EvqConst, &variableDeclaration); + insertStatementInParentBlock(variableDeclaration); + queueReplacement(CreateTempSymbolNode(variable), OriginalNode::IS_DROPPED); +} + +} // namespace + +bool RecordConstantPrecision(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + RecordConstantPrecisionTraverser traverser(symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h new file mode 100644 index 0000000000..f615b11221 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h @@ -0,0 +1,33 @@ +// +// 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. +// +// During parsing, all constant expressions are folded to constant union nodes. The expressions that +// have been folded may have had precision qualifiers, which should affect the precision of the +// consuming operation. If the folded constant union nodes are written to output as such they won't +// have any precision qualifiers, and their effect on the precision of the consuming operation is +// lost. +// +// RecordConstantPrecision is an AST traverser that inspects the precision qualifiers of constants +// and hoists the constants outside the containing expression as precision qualified named variables +// in case that is required for correct precision propagation. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_RECORDCONSTANTPRECISION_H_ +#define COMPILER_TRANSLATOR_TREEOPS_RECORDCONSTANTPRECISION_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RecordConstantPrecision(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_RECORDCONSTANTPRECISION_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp new file mode 100644 index 0000000000..85ecb9346b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp @@ -0,0 +1,109 @@ +// +// 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. +// +// RemoveArrayLengthMethod.cpp: +// Fold array length expressions, including cases where the "this" node has side effects. +// Example: +// int i = (a = b).length(); +// int j = (func()).length(); +// becomes: +// (a = b); +// int i = <constant array length>; +// func(); +// int j = <constant array length>; +// +// Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps +// have been done to expressions containing calls of the array length method. +// +// Does nothing to length method calls done on runtime-sized arrays. + +#include "compiler/translator/tree_ops/RemoveArrayLengthMethod.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class RemoveArrayLengthTraverser : public TIntermTraverser +{ + public: + RemoveArrayLengthTraverser() : TIntermTraverser(true, false, false), mFoundArrayLength(false) {} + + bool visitUnary(Visit visit, TIntermUnary *node) override; + + void nextIteration() { mFoundArrayLength = false; } + + bool foundArrayLength() const { return mFoundArrayLength; } + + private: + void insertSideEffectsInParentBlock(TIntermTyped *node); + + bool mFoundArrayLength; +}; + +bool RemoveArrayLengthTraverser::visitUnary(Visit visit, TIntermUnary *node) +{ + // The only case where we leave array length() in place is for runtime-sized arrays. + if (node->getOp() == EOpArrayLength && !node->getOperand()->getType().isUnsizedArray()) + { + mFoundArrayLength = true; + insertSideEffectsInParentBlock(node->getOperand()); + TConstantUnion *constArray = new TConstantUnion[1]; + constArray->setIConst(node->getOperand()->getOutermostArraySize()); + queueReplacement(new TIntermConstantUnion(constArray, node->getType()), + OriginalNode::IS_DROPPED); + return false; + } + return true; +} + +void RemoveArrayLengthTraverser::insertSideEffectsInParentBlock(TIntermTyped *node) +{ + // If the node is an index type, traverse it and add the indices as side effects. If at the end + // an expression without side effect is encountered, such as an opaque uniform or a lone symbol, + // don't generate a statement for it. + if (!node->hasSideEffects()) + { + return; + } + + TIntermBinary *asBinary = node->getAsBinaryNode(); + if (asBinary && !asBinary->isAssignment()) + { + insertSideEffectsInParentBlock(asBinary->getLeft()); + insertSideEffectsInParentBlock(asBinary->getRight()); + } + else + { + insertStatementInParentBlock(node); + } +} + +} // anonymous namespace + +bool RemoveArrayLengthMethod(TCompiler *compiler, TIntermBlock *root) +{ + RemoveArrayLengthTraverser traverser; + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundArrayLength()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundArrayLength()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h new file mode 100644 index 0000000000..8468e1e635 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h @@ -0,0 +1,37 @@ +// +// 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. +// +// RemoveArrayLengthMethod.h: +// Fold array length expressions, including cases where the "this" node has side effects. +// Example: +// int i = (a = b).length(); +// int j = (func()).length(); +// becomes: +// (a = b); +// int i = <constant array length>; +// func(); +// int j = <constant array length>; +// +// Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps +// have been done to expressions containing calls of the array length method. +// +// Does nothing to length method calls done on runtime-sized arrays. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEARRAYLENGTHMETHOD_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REMOVEARRAYLENGTHMETHOD_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool RemoveArrayLengthMethod(TCompiler *compiler, TIntermBlock *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEARRAYLENGTHMETHOD_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp new file mode 100644 index 0000000000..ed0ba3fed8 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp @@ -0,0 +1,74 @@ +// +// Copyright 2020 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. +// +// RemoveAtomicCounterBuiltins: Remove atomic counter builtins. +// + +#include "compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ +namespace +{ + +bool IsAtomicCounterDecl(const TIntermDeclaration *node) +{ + const TIntermSequence &sequence = *(node->getSequence()); + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + return type.getQualifier() == EvqUniform && type.isAtomicCounter(); +} + +// Traverser that removes all GLSL built-ins that use AtomicCounters +// Only called when the builtins are in use, but no atomic counters have been declared +class RemoveAtomicCounterBuiltinsTraverser : public TIntermTraverser +{ + public: + RemoveAtomicCounterBuiltinsTraverser() : TIntermTraverser(true, false, false) {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + ASSERT(visit == PreVisit); + + // Active atomic counters should have been removed by RewriteAtomicCounters, and this + // traversal should not have been invoked + ASSERT(!IsAtomicCounterDecl(node)); + return false; + } + + bool visitAggregate(Visit visit, TIntermAggregate *node) override + { + if (node->getOp() == EOpMemoryBarrierAtomicCounter) + { + // Vulkan does not support atomic counters, so if this builtin finds its way here, + // we need to remove it. + TIntermSequence emptySequence; + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(emptySequence)); + return true; + } + + // We shouldn't see any other builtins because they cannot be present without an active + // atomic counter, and should have been removed by RewriteAtomicCounters. If this fires, + // this traversal should not have been called. + ASSERT(!(BuiltInGroup::IsBuiltIn(node->getOp()) && + node->getFunction()->isAtomicCounterFunction())); + + return false; + } +}; + +} // anonymous namespace + +bool RemoveAtomicCounterBuiltins(TCompiler *compiler, TIntermBlock *root) +{ + RemoveAtomicCounterBuiltinsTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h new file mode 100644 index 0000000000..efe8a53595 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h @@ -0,0 +1,24 @@ +// +// Copyright 2020 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. +// +// RemoveAtomicCounterBuiltins: Remove atomic counter builtins. +// Normally handled by RewriteAtomicCounters, but that is only invoked when +// atomic counters are actually in use. This pass removes the builtins and +// asserts no atomic counters are declared. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEATOMICCOUNTERBUILTINS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REMOVEATOMICCOUNTERBUILTINS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool RemoveAtomicCounterBuiltins(TCompiler *compiler, TIntermBlock *root); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEATOMICCOUNTERBUILTINS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp new file mode 100644 index 0000000000..fda6de6f48 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp @@ -0,0 +1,597 @@ +// +// 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. +// +// RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of non-SSBO vectors and +// matrices, replacing them with calls to functions that choose which component to return or write. +// We don't need to consider dynamic indexing in SSBO since it can be directly as part of the offset +// of RWByteAddressBuffer. +// + +#include "compiler/translator/tree_ops/RemoveDynamicIndexing.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/Diagnostics.h" +#include "compiler/translator/InfoSink.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +using DynamicIndexingNodeMatcher = std::function<bool(TIntermBinary *)>; + +const TType *kIndexType = StaticType::Get<EbtInt, EbpHigh, EvqParamIn, 1, 1>(); + +constexpr const ImmutableString kBaseName("base"); +constexpr const ImmutableString kIndexName("index"); +constexpr const ImmutableString kValueName("value"); + +std::string GetIndexFunctionName(const TType &type, bool write) +{ + TInfoSinkBase nameSink; + nameSink << "dyn_index_"; + if (write) + { + nameSink << "write_"; + } + if (type.isMatrix()) + { + nameSink << "mat" << static_cast<uint32_t>(type.getCols()) << "x" + << static_cast<uint32_t>(type.getRows()); + } + else + { + switch (type.getBasicType()) + { + case EbtInt: + nameSink << "ivec"; + break; + case EbtBool: + nameSink << "bvec"; + break; + case EbtUInt: + nameSink << "uvec"; + break; + case EbtFloat: + nameSink << "vec"; + break; + default: + UNREACHABLE(); + } + nameSink << static_cast<uint32_t>(type.getNominalSize()); + } + return nameSink.str(); +} + +TIntermConstantUnion *CreateIntConstantNode(int i) +{ + TConstantUnion *constant = new TConstantUnion(); + constant->setIConst(i); + return new TIntermConstantUnion(constant, TType(EbtInt, EbpHigh)); +} + +TIntermTyped *EnsureSignedInt(TIntermTyped *node) +{ + if (node->getBasicType() == EbtInt) + return node; + + TIntermSequence arguments; + arguments.push_back(node); + return TIntermAggregate::CreateConstructor(TType(EbtInt), &arguments); +} + +TType *GetFieldType(const TType &indexedType) +{ + TType *fieldType = new TType(indexedType); + if (indexedType.isMatrix()) + { + fieldType->toMatrixColumnType(); + } + else + { + ASSERT(indexedType.isVector()); + fieldType->toComponentType(); + } + // Default precision to highp if not specified. For example in |vec3(0)[i], i < 0|, there is no + // precision assigned to vec3(0). + if (fieldType->getPrecision() == EbpUndefined) + { + fieldType->setPrecision(EbpHigh); + } + return fieldType; +} + +const TType *GetBaseType(const TType &type, bool write) +{ + TType *baseType = new TType(type); + // Conservatively use highp here, even if the indexed type is not highp. That way the code can't + // end up using mediump version of an indexing function for a highp value, if both mediump and + // highp values are being indexed in the shader. For HLSL precision doesn't matter, but in + // principle this code could be used with multiple backends. + baseType->setPrecision(EbpHigh); + baseType->setQualifier(EvqParamInOut); + if (!write) + baseType->setQualifier(EvqParamIn); + return baseType; +} + +// Generate a read or write function for one field in a vector/matrix. +// Out-of-range indices are clamped. This is consistent with how ANGLE handles out-of-range +// indices in other places. +// Note that indices can be either int or uint. We create only int versions of the functions, +// and convert uint indices to int at the call site. +// read function example: +// float dyn_index_vec2(in vec2 base, in int index) +// { +// switch(index) +// { +// case (0): +// return base[0]; +// case (1): +// return base[1]; +// default: +// break; +// } +// if (index < 0) +// return base[0]; +// return base[1]; +// } +// write function example: +// void dyn_index_write_vec2(inout vec2 base, in int index, in float value) +// { +// switch(index) +// { +// case (0): +// base[0] = value; +// return; +// case (1): +// base[1] = value; +// return; +// default: +// break; +// } +// if (index < 0) +// { +// base[0] = value; +// return; +// } +// base[1] = value; +// } +// Note that else is not used in above functions to avoid the RewriteElseBlocks transformation. +TIntermFunctionDefinition *GetIndexFunctionDefinition(const TType &type, + bool write, + const TFunction &func, + TSymbolTable *symbolTable) +{ + ASSERT(!type.isArray()); + + uint8_t numCases = 0; + if (type.isMatrix()) + { + numCases = type.getCols(); + } + else + { + numCases = type.getNominalSize(); + } + + std::string functionName = GetIndexFunctionName(type, write); + TIntermFunctionPrototype *prototypeNode = CreateInternalFunctionPrototypeNode(func); + + TIntermSymbol *baseParam = new TIntermSymbol(func.getParam(0)); + TIntermSymbol *indexParam = new TIntermSymbol(func.getParam(1)); + TIntermSymbol *valueParam = nullptr; + if (write) + { + valueParam = new TIntermSymbol(func.getParam(2)); + } + + TIntermBlock *statementList = new TIntermBlock(); + for (uint8_t i = 0; i < numCases; ++i) + { + TIntermCase *caseNode = new TIntermCase(CreateIntConstantNode(i)); + statementList->getSequence()->push_back(caseNode); + + TIntermBinary *indexNode = + new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(i)); + if (write) + { + TIntermBinary *assignNode = + new TIntermBinary(EOpAssign, indexNode, valueParam->deepCopy()); + statementList->getSequence()->push_back(assignNode); + TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr); + statementList->getSequence()->push_back(returnNode); + } + else + { + TIntermBranch *returnNode = new TIntermBranch(EOpReturn, indexNode); + statementList->getSequence()->push_back(returnNode); + } + } + + // Default case + TIntermCase *defaultNode = new TIntermCase(nullptr); + statementList->getSequence()->push_back(defaultNode); + TIntermBranch *breakNode = new TIntermBranch(EOpBreak, nullptr); + statementList->getSequence()->push_back(breakNode); + + TIntermSwitch *switchNode = new TIntermSwitch(indexParam->deepCopy(), statementList); + + TIntermBlock *bodyNode = new TIntermBlock(); + bodyNode->getSequence()->push_back(switchNode); + + TIntermBinary *cond = + new TIntermBinary(EOpLessThan, indexParam->deepCopy(), CreateIntConstantNode(0)); + + // Two blocks: one accesses (either reads or writes) the first element and returns, + // the other accesses the last element. + TIntermBlock *useFirstBlock = new TIntermBlock(); + TIntermBlock *useLastBlock = new TIntermBlock(); + TIntermBinary *indexFirstNode = + new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(0)); + TIntermBinary *indexLastNode = + new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(numCases - 1)); + if (write) + { + TIntermBinary *assignFirstNode = + new TIntermBinary(EOpAssign, indexFirstNode, valueParam->deepCopy()); + useFirstBlock->getSequence()->push_back(assignFirstNode); + TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr); + useFirstBlock->getSequence()->push_back(returnNode); + + TIntermBinary *assignLastNode = + new TIntermBinary(EOpAssign, indexLastNode, valueParam->deepCopy()); + useLastBlock->getSequence()->push_back(assignLastNode); + } + else + { + TIntermBranch *returnFirstNode = new TIntermBranch(EOpReturn, indexFirstNode); + useFirstBlock->getSequence()->push_back(returnFirstNode); + + TIntermBranch *returnLastNode = new TIntermBranch(EOpReturn, indexLastNode); + useLastBlock->getSequence()->push_back(returnLastNode); + } + TIntermIfElse *ifNode = new TIntermIfElse(cond, useFirstBlock, nullptr); + bodyNode->getSequence()->push_back(ifNode); + bodyNode->getSequence()->push_back(useLastBlock); + + TIntermFunctionDefinition *indexingFunction = + new TIntermFunctionDefinition(prototypeNode, bodyNode); + return indexingFunction; +} + +class RemoveDynamicIndexingTraverser : public TLValueTrackingTraverser +{ + public: + RemoveDynamicIndexingTraverser(DynamicIndexingNodeMatcher &&matcher, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics); + + bool visitBinary(Visit visit, TIntermBinary *node) override; + + void insertHelperDefinitions(TIntermNode *root); + + void nextIteration(); + + bool usedTreeInsertion() const { return mUsedTreeInsertion; } + + protected: + // Maps of types that are indexed to the indexing function ids used for them. Note that these + // can not store multiple variants of the same type with different precisions - only one + // precision gets stored. + std::map<TType, TFunction *> mIndexedVecAndMatrixTypes; + std::map<TType, TFunction *> mWrittenVecAndMatrixTypes; + + bool mUsedTreeInsertion; + + // When true, the traverser will remove side effects from any indexing expression. + // This is done so that in code like + // V[j++][i]++. + // where V is an array of vectors, j++ will only be evaluated once. + bool mRemoveIndexSideEffectsInSubtree; + + DynamicIndexingNodeMatcher mMatcher; + PerformanceDiagnostics *mPerfDiagnostics; +}; + +RemoveDynamicIndexingTraverser::RemoveDynamicIndexingTraverser( + DynamicIndexingNodeMatcher &&matcher, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics) + : TLValueTrackingTraverser(true, false, false, symbolTable), + mUsedTreeInsertion(false), + mRemoveIndexSideEffectsInSubtree(false), + mMatcher(matcher), + mPerfDiagnostics(perfDiagnostics) +{} + +void RemoveDynamicIndexingTraverser::insertHelperDefinitions(TIntermNode *root) +{ + TIntermBlock *rootBlock = root->getAsBlock(); + ASSERT(rootBlock != nullptr); + TIntermSequence insertions; + for (auto &type : mIndexedVecAndMatrixTypes) + { + insertions.push_back( + GetIndexFunctionDefinition(type.first, false, *type.second, mSymbolTable)); + } + for (auto &type : mWrittenVecAndMatrixTypes) + { + insertions.push_back( + GetIndexFunctionDefinition(type.first, true, *type.second, mSymbolTable)); + } + rootBlock->insertChildNodes(0, insertions); +} + +// Create a call to dyn_index_*() based on an indirect indexing op node +TIntermAggregate *CreateIndexFunctionCall(TIntermBinary *node, + TIntermTyped *index, + TFunction *indexingFunction) +{ + ASSERT(node->getOp() == EOpIndexIndirect); + TIntermSequence arguments; + arguments.push_back(node->getLeft()); + arguments.push_back(index); + + TIntermAggregate *indexingCall = + TIntermAggregate::CreateFunctionCall(*indexingFunction, &arguments); + indexingCall->setLine(node->getLine()); + return indexingCall; +} + +TIntermAggregate *CreateIndexedWriteFunctionCall(TIntermBinary *node, + TVariable *index, + TVariable *writtenValue, + TFunction *indexedWriteFunction) +{ + ASSERT(node->getOp() == EOpIndexIndirect); + TIntermSequence arguments; + // Deep copy the child nodes so that two pointers to the same node don't end up in the tree. + arguments.push_back(node->getLeft()->deepCopy()); + arguments.push_back(CreateTempSymbolNode(index)); + arguments.push_back(CreateTempSymbolNode(writtenValue)); + + TIntermAggregate *indexedWriteCall = + TIntermAggregate::CreateFunctionCall(*indexedWriteFunction, &arguments); + indexedWriteCall->setLine(node->getLine()); + return indexedWriteCall; +} + +bool RemoveDynamicIndexingTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (mUsedTreeInsertion) + return false; + + if (node->getOp() == EOpIndexIndirect) + { + if (mRemoveIndexSideEffectsInSubtree) + { + ASSERT(node->getRight()->hasSideEffects()); + // In case we're just removing index side effects, convert + // v_expr[index_expr] + // to this: + // int s0 = index_expr; v_expr[s0]; + // Now v_expr[s0] can be safely executed several times without unintended side effects. + TIntermDeclaration *indexVariableDeclaration = nullptr; + TVariable *indexVariable = DeclareTempVariable(mSymbolTable, node->getRight(), + EvqTemporary, &indexVariableDeclaration); + insertStatementInParentBlock(indexVariableDeclaration); + mUsedTreeInsertion = true; + + // Replace the index with the temp variable + TIntermSymbol *tempIndex = CreateTempSymbolNode(indexVariable); + queueReplacementWithParent(node, node->getRight(), tempIndex, OriginalNode::IS_DROPPED); + } + else if (mMatcher(node)) + { + if (mPerfDiagnostics) + { + mPerfDiagnostics->warning(node->getLine(), + "Performance: dynamic indexing of vectors and " + "matrices is emulated and can be slow.", + "[]"); + } + bool write = isLValueRequiredHere(); + +#if defined(ANGLE_ENABLE_ASSERTS) + // Make sure that IntermNodePatternMatcher is consistent with the slightly differently + // implemented checks in this traverser. + IntermNodePatternMatcher matcher( + IntermNodePatternMatcher::kDynamicIndexingOfVectorOrMatrixInLValue); + ASSERT(matcher.match(node, getParentNode(), isLValueRequiredHere()) == write); +#endif + + const TType &type = node->getLeft()->getType(); + ImmutableString indexingFunctionName(GetIndexFunctionName(type, false)); + TFunction *indexingFunction = nullptr; + if (mIndexedVecAndMatrixTypes.find(type) == mIndexedVecAndMatrixTypes.end()) + { + indexingFunction = + new TFunction(mSymbolTable, indexingFunctionName, SymbolType::AngleInternal, + GetFieldType(type), true); + indexingFunction->addParameter(new TVariable( + mSymbolTable, kBaseName, GetBaseType(type, false), SymbolType::AngleInternal)); + indexingFunction->addParameter( + new TVariable(mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal)); + mIndexedVecAndMatrixTypes[type] = indexingFunction; + } + else + { + indexingFunction = mIndexedVecAndMatrixTypes[type]; + } + + if (write) + { + // Convert: + // v_expr[index_expr]++; + // to this: + // int s0 = index_expr; float s1 = dyn_index(v_expr, s0); s1++; + // dyn_index_write(v_expr, s0, s1); + // This works even if index_expr has some side effects. + if (node->getLeft()->hasSideEffects()) + { + // If v_expr has side effects, those need to be removed before proceeding. + // Otherwise the side effects of v_expr would be evaluated twice. + // The only case where an l-value can have side effects is when it is + // indexing. For example, it can be V[j++] where V is an array of vectors. + mRemoveIndexSideEffectsInSubtree = true; + return true; + } + + TIntermBinary *leftBinary = node->getLeft()->getAsBinaryNode(); + if (leftBinary != nullptr && mMatcher(leftBinary)) + { + // This is a case like: + // mat2 m; + // m[a][b]++; + // Process the child node m[a] first. + return true; + } + + // TODO(oetuaho@nvidia.com): This is not optimal if the expression using the value + // only writes it and doesn't need the previous value. http://anglebug.com/1116 + + TFunction *indexedWriteFunction = nullptr; + if (mWrittenVecAndMatrixTypes.find(type) == mWrittenVecAndMatrixTypes.end()) + { + ImmutableString functionName( + GetIndexFunctionName(node->getLeft()->getType(), true)); + indexedWriteFunction = + new TFunction(mSymbolTable, functionName, SymbolType::AngleInternal, + StaticType::GetBasic<EbtVoid, EbpUndefined>(), false); + indexedWriteFunction->addParameter(new TVariable(mSymbolTable, kBaseName, + GetBaseType(type, true), + SymbolType::AngleInternal)); + indexedWriteFunction->addParameter(new TVariable( + mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal)); + TType *valueType = GetFieldType(type); + valueType->setQualifier(EvqParamIn); + indexedWriteFunction->addParameter(new TVariable( + mSymbolTable, kValueName, static_cast<const TType *>(valueType), + SymbolType::AngleInternal)); + mWrittenVecAndMatrixTypes[type] = indexedWriteFunction; + } + else + { + indexedWriteFunction = mWrittenVecAndMatrixTypes[type]; + } + + TIntermSequence insertionsBefore; + TIntermSequence insertionsAfter; + + // Store the index in a temporary signed int variable. + // s0 = index_expr; + TIntermTyped *indexInitializer = EnsureSignedInt(node->getRight()); + TIntermDeclaration *indexVariableDeclaration = nullptr; + TVariable *indexVariable = DeclareTempVariable( + mSymbolTable, indexInitializer, EvqTemporary, &indexVariableDeclaration); + insertionsBefore.push_back(indexVariableDeclaration); + + // s1 = dyn_index(v_expr, s0); + TIntermAggregate *indexingCall = CreateIndexFunctionCall( + node, CreateTempSymbolNode(indexVariable), indexingFunction); + TIntermDeclaration *fieldVariableDeclaration = nullptr; + TVariable *fieldVariable = DeclareTempVariable( + mSymbolTable, indexingCall, EvqTemporary, &fieldVariableDeclaration); + insertionsBefore.push_back(fieldVariableDeclaration); + + // dyn_index_write(v_expr, s0, s1); + TIntermAggregate *indexedWriteCall = CreateIndexedWriteFunctionCall( + node, indexVariable, fieldVariable, indexedWriteFunction); + insertionsAfter.push_back(indexedWriteCall); + insertStatementsInParentBlock(insertionsBefore, insertionsAfter); + + // replace the node with s1 + queueReplacement(CreateTempSymbolNode(fieldVariable), OriginalNode::IS_DROPPED); + mUsedTreeInsertion = true; + } + else + { + // The indexed value is not being written, so we can simply convert + // v_expr[index_expr] + // into + // dyn_index(v_expr, index_expr) + // If the index_expr is unsigned, we'll convert it to signed. + ASSERT(!mRemoveIndexSideEffectsInSubtree); + TIntermAggregate *indexingCall = CreateIndexFunctionCall( + node, EnsureSignedInt(node->getRight()), indexingFunction); + queueReplacement(indexingCall, OriginalNode::IS_DROPPED); + } + } + } + return !mUsedTreeInsertion; +} + +void RemoveDynamicIndexingTraverser::nextIteration() +{ + mUsedTreeInsertion = false; + mRemoveIndexSideEffectsInSubtree = false; +} + +bool RemoveDynamicIndexingIf(DynamicIndexingNodeMatcher &&matcher, + TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics) +{ + // This transformation adds function declarations after the fact and so some validation is + // momentarily disabled. + bool enableValidateFunctionCall = compiler->disableValidateFunctionCall(); + + RemoveDynamicIndexingTraverser traverser(std::move(matcher), symbolTable, perfDiagnostics); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } while (traverser.usedTreeInsertion()); + // TODO(oetuaho@nvidia.com): It might be nicer to add the helper definitions also in the middle + // of traversal. Now the tree ends up in an inconsistent state in the middle, since there are + // function call nodes with no corresponding definition nodes. This needs special handling in + // TIntermLValueTrackingTraverser, and creates intricacies that are not easily apparent from a + // superficial reading of the code. + traverser.insertHelperDefinitions(root); + + compiler->restoreValidateFunctionCall(enableValidateFunctionCall); + return compiler->validateAST(root); +} + +} // namespace + +[[nodiscard]] bool RemoveDynamicIndexingOfNonSSBOVectorOrMatrix( + TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics) +{ + DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) { + return IntermNodePatternMatcher::IsDynamicIndexingOfNonSSBOVectorOrMatrix(node); + }; + return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable, + perfDiagnostics); +} + +[[nodiscard]] bool RemoveDynamicIndexingOfSwizzledVector(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics) +{ + DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) { + return IntermNodePatternMatcher::IsDynamicIndexingOfSwizzledVector(node); + }; + return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable, + perfDiagnostics); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h new file mode 100644 index 0000000000..9693f55c40 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h @@ -0,0 +1,41 @@ +// +// 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. +// +// RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of non-SSBO vectors and +// matrices, replacing them with calls to functions that choose which component to return or write. +// We don't need to consider dynamic indexing in SSBO since it can be directly as part of the offset +// of RWByteAddressBuffer. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEDYNAMICINDEXING_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REMOVEDYNAMICINDEXING_H_ + +#include "common/angleutils.h" + +#include <functional> + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TIntermBinary; +class TSymbolTable; +class PerformanceDiagnostics; + +[[nodiscard]] bool RemoveDynamicIndexingOfNonSSBOVectorOrMatrix( + TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics); + +[[nodiscard]] bool RemoveDynamicIndexingOfSwizzledVector(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + PerformanceDiagnostics *perfDiagnostics); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEDYNAMICINDEXING_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp new file mode 100644 index 0000000000..a5e20ebcb0 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp @@ -0,0 +1,209 @@ +// +// Copyright 2019 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. +// +// RemoveInactiveInterfaceVariables.h: +// Drop shader interface variable declarations for those that are inactive. +// + +#include "compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h" + +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +// Traverser that removes all declarations that correspond to inactive variables. +class RemoveInactiveInterfaceVariablesTraverser : public TIntermTraverser +{ + public: + RemoveInactiveInterfaceVariablesTraverser( + TSymbolTable *symbolTable, + const std::vector<sh::ShaderVariable> &attributes, + const std::vector<sh::ShaderVariable> &inputVaryings, + const std::vector<sh::ShaderVariable> &outputVariables, + const std::vector<sh::ShaderVariable> &uniforms, + const std::vector<sh::InterfaceBlock> &interfaceBlocks, + bool removeFragmentOutputs); + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + + private: + const std::vector<sh::ShaderVariable> &mAttributes; + const std::vector<sh::ShaderVariable> &mInputVaryings; + const std::vector<sh::ShaderVariable> &mOutputVariables; + const std::vector<sh::ShaderVariable> &mUniforms; + const std::vector<sh::InterfaceBlock> &mInterfaceBlocks; + bool mRemoveFragmentOutputs; +}; + +RemoveInactiveInterfaceVariablesTraverser::RemoveInactiveInterfaceVariablesTraverser( + TSymbolTable *symbolTable, + const std::vector<sh::ShaderVariable> &attributes, + const std::vector<sh::ShaderVariable> &inputVaryings, + const std::vector<sh::ShaderVariable> &outputVariables, + const std::vector<sh::ShaderVariable> &uniforms, + const std::vector<sh::InterfaceBlock> &interfaceBlocks, + bool removeFragmentOutputs) + : TIntermTraverser(true, false, false, symbolTable), + mAttributes(attributes), + mInputVaryings(inputVaryings), + mOutputVariables(outputVariables), + mUniforms(uniforms), + mInterfaceBlocks(interfaceBlocks), + mRemoveFragmentOutputs(removeFragmentOutputs) +{} + +template <typename Variable> +bool IsVariableActive(const std::vector<Variable> &mVars, const ImmutableString &name) +{ + for (const Variable &var : mVars) + { + if (name == var.name) + { + return var.active; + } + } + UNREACHABLE(); + return true; +} + +bool RemoveInactiveInterfaceVariablesTraverser::visitDeclaration(Visit visit, + TIntermDeclaration *node) +{ + // SeparateDeclarations should have already been run. + ASSERT(node->getSequence()->size() == 1u); + + TIntermTyped *declarator = node->getSequence()->front()->getAsTyped(); + ASSERT(declarator); + + TIntermSymbol *asSymbol = declarator->getAsSymbolNode(); + if (!asSymbol) + { + return false; + } + + const TType &type = declarator->getType(); + + // Remove all shader interface variables except outputs, i.e. uniforms, interface blocks and + // inputs. + // + // Imagine a situation where the VS doesn't write to a varying but the FS reads from it. This + // is allowed, though the value of the varying is undefined. If the varying is removed here, + // the situation is changed to VS not declaring the varying, but the FS reading from it, which + // is not allowed. That's why inactive shader outputs are not removed. + // + // Inactive fragment shader outputs can be removed though, as there is no next stage. + bool removeDeclaration = false; + const TQualifier qualifier = type.getQualifier(); + + if (type.isInterfaceBlock()) + { + // When a member has an explicit location, interface block should not be removed. + // If the member or interface would be removed, GetProgramResource could not return the + // location. + if (!IsShaderIoBlock(type.getQualifier()) && type.getQualifier() != EvqPatchIn && + type.getQualifier() != EvqPatchOut) + { + removeDeclaration = + !IsVariableActive(mInterfaceBlocks, type.getInterfaceBlock()->name()); + } + } + else if (qualifier == EvqUniform) + { + removeDeclaration = !IsVariableActive(mUniforms, asSymbol->getName()); + } + else if (qualifier == EvqAttribute || qualifier == EvqVertexIn) + { + removeDeclaration = !IsVariableActive(mAttributes, asSymbol->getName()); + } + else if (IsShaderIn(qualifier)) + { + removeDeclaration = !IsVariableActive(mInputVaryings, asSymbol->getName()); + } + else if (qualifier == EvqFragmentOut) + { + removeDeclaration = + !IsVariableActive(mOutputVariables, asSymbol->getName()) && mRemoveFragmentOutputs; + } + + if (removeDeclaration) + { + TIntermSequence replacement; + + // If the declaration was of a struct, keep the struct declaration itself. + if (type.isStructSpecifier()) + { + TType *structSpecifierType = new TType(type.getStruct(), true); + TVariable *emptyVariable = new TVariable(mSymbolTable, kEmptyImmutableString, + structSpecifierType, SymbolType::Empty); + TIntermDeclaration *declaration = new TIntermDeclaration(); + declaration->appendDeclarator(new TIntermSymbol(emptyVariable)); + replacement.push_back(declaration); + } + + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(replacement)); + } + + return false; +} + +bool RemoveInactiveInterfaceVariablesTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + // Remove any code that initOutputVariables might have added corresponding to inactive + // output variables. This code is always in the form of `variable = ...;`. + if (node->getOp() != EOpAssign) + { + // Don't recurse, won't find the initialization nested in another expression. + return false; + } + + // Get the symbol being initialized, and check if it's an inactive output. If it is, this must + // necessarily be initialization code that ANGLE has added (and wasn't there in the original + // shader; if it was, the symbol wouldn't have been inactive). + TIntermSymbol *symbol = node->getLeft()->getAsSymbolNode(); + if (symbol == nullptr) + { + return false; + } + + const TQualifier qualifier = symbol->getType().getQualifier(); + if (qualifier != EvqFragmentOut || IsVariableActive(mOutputVariables, symbol->getName())) + { + return false; + } + + // Drop the initialization code. + TIntermSequence replacement; + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, std::move(replacement)); + return false; +} + +} // namespace + +bool RemoveInactiveInterfaceVariables(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const std::vector<sh::ShaderVariable> &attributes, + const std::vector<sh::ShaderVariable> &inputVaryings, + const std::vector<sh::ShaderVariable> &outputVariables, + const std::vector<sh::ShaderVariable> &uniforms, + const std::vector<sh::InterfaceBlock> &interfaceBlocks, + bool removeFragmentOutputs) +{ + RemoveInactiveInterfaceVariablesTraverser traverser(symbolTable, attributes, inputVaryings, + outputVariables, uniforms, interfaceBlocks, + removeFragmentOutputs); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h new file mode 100644 index 0000000000..ddbdea2ced --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h @@ -0,0 +1,42 @@ +// +// Copyright 2019 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. +// +// RemoveInactiveInterfaceVariables.h: +// Drop shader interface variable declarations for those that are inactive. This step needs to be +// done after CollectVariables. This avoids having to emulate them (e.g. atomic counters for +// Vulkan) or remove them in glslang wrapper (again, for Vulkan). +// +// Shouldn't be run for the GL backend, as it queries shader interface variables from GL itself, +// instead of relying on what was gathered during CollectVariables. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEINACTIVEVARIABLES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REMOVEINACTIVEVARIABLES_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +struct InterfaceBlock; +struct ShaderVariable; +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool RemoveInactiveInterfaceVariables( + TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const std::vector<sh::ShaderVariable> &attributes, + const std::vector<sh::ShaderVariable> &inputVaryings, + const std::vector<sh::ShaderVariable> &outputVariables, + const std::vector<sh::ShaderVariable> &uniforms, + const std::vector<sh::InterfaceBlock> &interfaceBlocks, + bool removeFragmentOutputs); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEINACTIVEVARIABLES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp new file mode 100644 index 0000000000..5a8ca44150 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp @@ -0,0 +1,47 @@ +// +// Copyright 2016 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/RemoveInvariantDeclaration.h" + +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// An AST traverser that removes invariant declaration for input in fragment shader +// when GLSL >= 4.20 and for output in vertex shader when GLSL < 4.2. +class RemoveInvariantDeclarationTraverser : public TIntermTraverser +{ + public: + RemoveInvariantDeclarationTraverser() : TIntermTraverser(true, false, false) {} + + private: + bool visitGlobalQualifierDeclaration(Visit visit, + TIntermGlobalQualifierDeclaration *node) override + { + if (node->isInvariant()) + { + TIntermSequence emptyReplacement; + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(emptyReplacement)); + } + return false; + } +}; + +} // anonymous namespace + +bool RemoveInvariantDeclaration(TCompiler *compiler, TIntermNode *root) +{ + RemoveInvariantDeclarationTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h new file mode 100644 index 0000000000..0fee87b3b1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h @@ -0,0 +1,21 @@ +// +// Copyright 2016 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEINVARIANTDECLARATION_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REMOVEINVARIANTDECLARATION_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool RemoveInvariantDeclaration(TCompiler *compiler, TIntermNode *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEINVARIANTDECLARATION_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp new file mode 100644 index 0000000000..b87ad33633 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp @@ -0,0 +1,371 @@ +// +// 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. +// +// RemoveUnreferencedVariables.cpp: +// Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary +// initialization code for them. Also removes unreferenced struct types. +// + +#include "compiler/translator/tree_ops/RemoveUnreferencedVariables.h" + +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class CollectVariableRefCountsTraverser : public TIntermTraverser +{ + public: + CollectVariableRefCountsTraverser(); + + using RefCountMap = angle::HashMap<int, unsigned int>; + RefCountMap &getSymbolIdRefCounts() { return mSymbolIdRefCounts; } + RefCountMap &getStructIdRefCounts() { return mStructIdRefCounts; } + + void visitSymbol(TIntermSymbol *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + void visitFunctionPrototype(TIntermFunctionPrototype *node) override; + + private: + void incrementStructTypeRefCount(const TType &type); + + RefCountMap mSymbolIdRefCounts; + + // Structure reference counts are counted from symbols, constructors, function calls, function + // return values and from interface block and structure fields. We need to track both function + // calls and function return values since there's a compiler option not to prune unused + // functions. The type of a constant union may also be a struct, but statements that are just a + // constant union are always pruned, and if the constant union is used somehow it will get + // counted by something else. + RefCountMap mStructIdRefCounts; +}; + +CollectVariableRefCountsTraverser::CollectVariableRefCountsTraverser() + : TIntermTraverser(true, false, false) +{} + +void CollectVariableRefCountsTraverser::incrementStructTypeRefCount(const TType &type) +{ + if (type.isInterfaceBlock()) + { + const auto *block = type.getInterfaceBlock(); + ASSERT(block); + + // We can end up incrementing ref counts of struct types referenced from an interface block + // multiple times for the same block. This doesn't matter, because interface blocks can't be + // pruned so we'll never do the reverse operation. + for (const auto &field : block->fields()) + { + ASSERT(!field->type()->isInterfaceBlock()); + incrementStructTypeRefCount(*field->type()); + } + return; + } + + const auto *structure = type.getStruct(); + if (structure != nullptr) + { + auto structIter = mStructIdRefCounts.find(structure->uniqueId().get()); + if (structIter == mStructIdRefCounts.end()) + { + mStructIdRefCounts[structure->uniqueId().get()] = 1u; + + for (const auto &field : structure->fields()) + { + incrementStructTypeRefCount(*field->type()); + } + + return; + } + ++(structIter->second); + } +} + +void CollectVariableRefCountsTraverser::visitSymbol(TIntermSymbol *node) +{ + incrementStructTypeRefCount(node->getType()); + + auto iter = mSymbolIdRefCounts.find(node->uniqueId().get()); + if (iter == mSymbolIdRefCounts.end()) + { + mSymbolIdRefCounts[node->uniqueId().get()] = 1u; + return; + } + ++(iter->second); +} + +bool CollectVariableRefCountsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + // This tracks struct references in both function calls and constructors. + incrementStructTypeRefCount(node->getType()); + return true; +} + +void CollectVariableRefCountsTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node) +{ + incrementStructTypeRefCount(node->getType()); + size_t paramCount = node->getFunction()->getParamCount(); + for (size_t i = 0; i < paramCount; ++i) + { + incrementStructTypeRefCount(node->getFunction()->getParam(i)->getType()); + } +} + +// Traverser that removes all unreferenced variables on one traversal. +class RemoveUnreferencedVariablesTraverser : public TIntermTraverser +{ + public: + RemoveUnreferencedVariablesTraverser( + CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts, + CollectVariableRefCountsTraverser::RefCountMap *structIdRefCounts, + TSymbolTable *symbolTable); + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; + void visitSymbol(TIntermSymbol *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + + // Traverse loop and block nodes in reverse order. Note that this traverser does not track + // parent block positions, so insertStatementInParentBlock is unusable! + void traverseBlock(TIntermBlock *block) override; + void traverseLoop(TIntermLoop *loop) override; + + private: + void removeVariableDeclaration(TIntermDeclaration *node, TIntermTyped *declarator); + void decrementStructTypeRefCount(const TType &type); + + CollectVariableRefCountsTraverser::RefCountMap *mSymbolIdRefCounts; + CollectVariableRefCountsTraverser::RefCountMap *mStructIdRefCounts; + bool mRemoveReferences; +}; + +RemoveUnreferencedVariablesTraverser::RemoveUnreferencedVariablesTraverser( + CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts, + CollectVariableRefCountsTraverser::RefCountMap *structIdRefCounts, + TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), + mSymbolIdRefCounts(symbolIdRefCounts), + mStructIdRefCounts(structIdRefCounts), + mRemoveReferences(false) +{} + +void RemoveUnreferencedVariablesTraverser::decrementStructTypeRefCount(const TType &type) +{ + auto *structure = type.getStruct(); + if (structure != nullptr) + { + ASSERT(mStructIdRefCounts->find(structure->uniqueId().get()) != mStructIdRefCounts->end()); + unsigned int structRefCount = --(*mStructIdRefCounts)[structure->uniqueId().get()]; + + if (structRefCount == 0) + { + for (const auto &field : structure->fields()) + { + decrementStructTypeRefCount(*field->type()); + } + } + } +} + +void RemoveUnreferencedVariablesTraverser::removeVariableDeclaration(TIntermDeclaration *node, + TIntermTyped *declarator) +{ + if (declarator->getType().isStructSpecifier() && !declarator->getType().isNamelessStruct()) + { + unsigned int structId = declarator->getType().getStruct()->uniqueId().get(); + unsigned int structRefCountInThisDeclarator = 1u; + if (declarator->getAsBinaryNode() && + declarator->getAsBinaryNode()->getRight()->getAsAggregate()) + { + ASSERT(declarator->getAsBinaryNode()->getLeft()->getType().getStruct() == + declarator->getType().getStruct()); + ASSERT(declarator->getAsBinaryNode()->getRight()->getType().getStruct() == + declarator->getType().getStruct()); + structRefCountInThisDeclarator = 2u; + } + if ((*mStructIdRefCounts)[structId] > structRefCountInThisDeclarator) + { + // If this declaration declares a named struct type that is used elsewhere, we need to + // keep it. We can still change the declarator though so that it doesn't declare an + // unreferenced variable. + + // Note that since we're not removing the entire declaration, the struct's reference + // count will end up being one less than the correct refcount. But since the struct + // declaration is kept, the incorrect refcount can't cause any other problems. + + if (declarator->getAsSymbolNode() && + declarator->getAsSymbolNode()->variable().symbolType() == SymbolType::Empty) + { + // Already an empty declaration - nothing to do. + return; + } + TVariable *emptyVariable = + new TVariable(mSymbolTable, kEmptyImmutableString, new TType(declarator->getType()), + SymbolType::Empty); + queueReplacementWithParent(node, declarator, new TIntermSymbol(emptyVariable), + OriginalNode::IS_DROPPED); + return; + } + } + + if (getParentNode()->getAsBlock()) + { + TIntermSequence emptyReplacement; + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(emptyReplacement)); + } + else + { + ASSERT(getParentNode()->getAsLoopNode()); + queueReplacement(nullptr, OriginalNode::IS_DROPPED); + } +} + +bool RemoveUnreferencedVariablesTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) +{ + if (visit == PreVisit) + { + // SeparateDeclarations should have already been run. + ASSERT(node->getSequence()->size() == 1u); + + TIntermTyped *declarator = node->getSequence()->back()->getAsTyped(); + ASSERT(declarator); + + // We can only remove variables that are not a part of the shader interface. + TQualifier qualifier = declarator->getQualifier(); + if (qualifier != EvqTemporary && qualifier != EvqGlobal && qualifier != EvqConst) + { + return true; + } + + bool canRemoveVariable = false; + TIntermSymbol *symbolNode = declarator->getAsSymbolNode(); + if (symbolNode != nullptr) + { + canRemoveVariable = (*mSymbolIdRefCounts)[symbolNode->uniqueId().get()] == 1u || + symbolNode->variable().symbolType() == SymbolType::Empty; + } + TIntermBinary *initNode = declarator->getAsBinaryNode(); + if (initNode != nullptr) + { + ASSERT(initNode->getLeft()->getAsSymbolNode()); + int symbolId = initNode->getLeft()->getAsSymbolNode()->uniqueId().get(); + canRemoveVariable = + (*mSymbolIdRefCounts)[symbolId] == 1u && !initNode->getRight()->hasSideEffects(); + } + + if (canRemoveVariable) + { + removeVariableDeclaration(node, declarator); + mRemoveReferences = true; + } + return true; + } + ASSERT(visit == PostVisit); + mRemoveReferences = false; + return true; +} + +void RemoveUnreferencedVariablesTraverser::visitSymbol(TIntermSymbol *node) +{ + if (mRemoveReferences) + { + ASSERT(mSymbolIdRefCounts->find(node->uniqueId().get()) != mSymbolIdRefCounts->end()); + --(*mSymbolIdRefCounts)[node->uniqueId().get()]; + + decrementStructTypeRefCount(node->getType()); + } +} + +bool RemoveUnreferencedVariablesTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (visit == PreVisit && mRemoveReferences) + { + decrementStructTypeRefCount(node->getType()); + } + return true; +} + +void RemoveUnreferencedVariablesTraverser::traverseBlock(TIntermBlock *node) +{ + // We traverse blocks in reverse order. This way reference counts can be decremented when + // removing initializers, and variables that become unused when initializers are removed can be + // removed on the same traversal. + + ScopedNodeInTraversalPath addToPath(this, node); + + bool visit = true; + + TIntermSequence *sequence = node->getSequence(); + + if (preVisit) + visit = visitBlock(PreVisit, node); + + if (visit) + { + for (auto iter = sequence->rbegin(); iter != sequence->rend(); ++iter) + { + (*iter)->traverse(this); + if (visit && inVisit) + { + if ((iter + 1) != sequence->rend()) + visit = visitBlock(InVisit, node); + } + } + } + + if (visit && postVisit) + visitBlock(PostVisit, node); +} + +void RemoveUnreferencedVariablesTraverser::traverseLoop(TIntermLoop *node) +{ + // We traverse loops in reverse order as well. The loop body gets traversed before the init + // node. + + ScopedNodeInTraversalPath addToPath(this, node); + + bool visit = true; + + if (preVisit) + visit = visitLoop(PreVisit, node); + + if (visit) + { + // We don't need to traverse loop expressions or conditions since they can't be declarations + // in the AST (loops which have a declaration in their condition get transformed in the + // parsing stage). + ASSERT(node->getExpression() == nullptr || + node->getExpression()->getAsDeclarationNode() == nullptr); + ASSERT(node->getCondition() == nullptr || + node->getCondition()->getAsDeclarationNode() == nullptr); + + if (node->getBody()) + node->getBody()->traverse(this); + + if (node->getInit()) + node->getInit()->traverse(this); + } + + if (visit && postVisit) + visitLoop(PostVisit, node); +} + +} // namespace + +bool RemoveUnreferencedVariables(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + CollectVariableRefCountsTraverser collector; + root->traverse(&collector); + RemoveUnreferencedVariablesTraverser traverser(&collector.getSymbolIdRefCounts(), + &collector.getStructIdRefCounts(), symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h new file mode 100644 index 0000000000..071d19e51e --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h @@ -0,0 +1,29 @@ +// +// 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. +// +// RemoveUnreferencedVariables.h: +// Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary +// initialization code for them. Also removes unreferenced struct types. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEUNREFERENCEDVARIABLES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REMOVEUNREFERENCEDVARIABLES_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool RemoveUnreferencedVariables(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEUNREFERENCEDVARIABLES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp new file mode 100644 index 0000000000..bd12cd1006 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp @@ -0,0 +1,348 @@ +// +// Copyright 2019 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. +// +// RewriteAtomicCounters: Emulate atomic counter buffers with storage buffers. +// + +#include "compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +struct UniformData +{ + // Corresponding to an array of array of opaque uniform variable, this is the flattened variable + // that is replacing it. + const TVariable *flattened; + // Assume a general case of array declaration with N dimensions: + // + // uniform type u[Dn]..[D2][D1]; + // + // Let's define + // + // Pn = D(n-1)*...*D2*D1 + // + // In that case, we have: + // + // u[In] = ac + In*Pn + // u[In][I(n-1)] = ac + In*Pn + I(n-1)*P(n-1) + // u[In]...[Ii] = ac + In*Pn + ... + Ii*Pi + // + // This array contains Pi. Note that the like TType::mArraySizes, the last element is the + // outermost dimension. Element 0 is necessarily 1. + TVector<unsigned int> mSubArraySizes; +}; + +using UniformMap = angle::HashMap<const TVariable *, UniformData>; + +TIntermTyped *RewriteArrayOfArraySubscriptExpression(TCompiler *compiler, + TIntermBinary *node, + const UniformMap &uniformMap); + +// Given an expression, this traverser calculates a new expression where array of array of opaque +// uniforms are replaced with their flattened ones. In particular, this is run on the right node of +// EOpIndexIndirect binary nodes, so that the expression in the index gets a chance to go through +// this transformation. +class RewriteExpressionTraverser final : public TIntermTraverser +{ + public: + explicit RewriteExpressionTraverser(TCompiler *compiler, const UniformMap &uniformMap) + : TIntermTraverser(true, false, false), mCompiler(compiler), mUniformMap(uniformMap) + {} + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + TIntermTyped *rewritten = + RewriteArrayOfArraySubscriptExpression(mCompiler, node, mUniformMap); + if (rewritten == nullptr) + { + return true; + } + + queueReplacement(rewritten, OriginalNode::IS_DROPPED); + + // Don't iterate as the expression is rewritten. + return false; + } + + void visitSymbol(TIntermSymbol *node) override + { + // We cannot reach here for an opaque uniform that is being replaced. visitBinary should + // have taken care of it. + ASSERT(!IsOpaqueType(node->getType().getBasicType()) || + mUniformMap.find(&node->variable()) == mUniformMap.end()); + } + + private: + TCompiler *mCompiler; + + const UniformMap &mUniformMap; +}; + +// Rewrite the index of an EOpIndexIndirect expression. The root can never need replacing, because +// it cannot be an opaque uniform itself. +void RewriteIndexExpression(TCompiler *compiler, + TIntermTyped *expression, + const UniformMap &uniformMap) +{ + RewriteExpressionTraverser traverser(compiler, uniformMap); + expression->traverse(&traverser); + bool valid = traverser.updateTree(compiler, expression); + ASSERT(valid); +} + +// Given an expression such as the following: +// +// EOpIndex(In)Direct (opaque uniform) +// / \ +// EOpIndex(In)Direct I1 +// / \ +// ... I2 +// / +// EOpIndex(In)Direct +// / \ +// uniform In +// +// produces: +// +// EOpIndex(In)Direct +// / \ +// uniform In*Pn + ... + I2*P2 + I1*P1 +// +TIntermTyped *RewriteArrayOfArraySubscriptExpression(TCompiler *compiler, + TIntermBinary *node, + const UniformMap &uniformMap) +{ + // Only interested in opaque uniforms. + if (!IsOpaqueType(node->getType().getBasicType())) + { + return nullptr; + } + + TIntermSymbol *opaqueUniform = nullptr; + + // Iterate once and find the opaque uniform that's being indexed. + TIntermBinary *iter = node; + while (opaqueUniform == nullptr) + { + ASSERT(iter->getOp() == EOpIndexDirect || iter->getOp() == EOpIndexIndirect); + + opaqueUniform = iter->getLeft()->getAsSymbolNode(); + iter = iter->getLeft()->getAsBinaryNode(); + } + + // If not being replaced, there's nothing to do. + auto flattenedIter = uniformMap.find(&opaqueUniform->variable()); + if (flattenedIter == uniformMap.end()) + { + return nullptr; + } + + const UniformData &data = flattenedIter->second; + + // Iterate again and build the index expression. The index expression constitutes the sum of + // the variable indices plus a constant offset calculated from the constant indices. For + // example, smplr[1][x][2][y] will have an index of x*P3 + y*P1 + c, where c = (1*P4 + 2*P2). + unsigned int constantOffset = 0; + TIntermTyped *variableIndex = nullptr; + + // Since the opaque uniforms are fully subscripted, we know exactly how many EOpIndex* nodes + // there should be. + for (size_t dimIndex = 0; dimIndex < data.mSubArraySizes.size(); ++dimIndex) + { + ASSERT(node); + + unsigned int subArraySize = data.mSubArraySizes[dimIndex]; + + switch (node->getOp()) + { + case EOpIndexDirect: + // Accumulate the constant index. + constantOffset += + node->getRight()->getAsConstantUnion()->getIConst(0) * subArraySize; + break; + case EOpIndexIndirect: + { + // Run RewriteExpressionTraverser on the right node. It may itself be an expression + // with an array of array of opaque uniform inside that needs to be rewritten. + TIntermTyped *indexExpression = node->getRight(); + RewriteIndexExpression(compiler, indexExpression, uniformMap); + + // Scale and accumulate. + if (subArraySize != 1) + { + indexExpression = + new TIntermBinary(EOpMul, indexExpression, CreateIndexNode(subArraySize)); + } + + if (variableIndex == nullptr) + { + variableIndex = indexExpression; + } + else + { + variableIndex = new TIntermBinary(EOpAdd, variableIndex, indexExpression); + } + break; + } + default: + UNREACHABLE(); + break; + } + + node = node->getLeft()->getAsBinaryNode(); + } + + // Add the two accumulated indices together. + TIntermTyped *index = nullptr; + if (constantOffset == 0 && variableIndex != nullptr) + { + // No constant offset, but there's variable offset. Take that as offset. + index = variableIndex; + } + else + { + // Either the constant offset is non zero, or there's no variable offset (so constant 0 + // should be used). + index = CreateIndexNode(constantOffset); + + if (variableIndex) + { + index = new TIntermBinary(EOpAdd, index, variableIndex); + } + } + + // Create an index into the flattened uniform. + TOperator op = variableIndex ? EOpIndexIndirect : EOpIndexDirect; + return new TIntermBinary(op, new TIntermSymbol(data.flattened), index); +} + +// Traverser that takes: +// +// uniform sampler/image/atomic_uint u[N][M].. +// +// and transforms it to: +// +// uniform sampler/image/atomic_uint u[N * M * ..] +// +// MonomorphizeUnsupportedFunctions makes it impossible for this array to be partially +// subscripted, or passed as argument to a function unsubscripted. This means that every encounter +// of this uniform can be expected to be fully subscripted. +// +class RewriteArrayOfArrayOfOpaqueUniformsTraverser : public TIntermTraverser +{ + public: + RewriteArrayOfArrayOfOpaqueUniformsTraverser(TCompiler *compiler, TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), mCompiler(compiler) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + if (!mInGlobalScope) + { + return true; + } + + const TIntermSequence &sequence = *(node->getSequence()); + + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + bool isOpaqueUniform = + type.getQualifier() == EvqUniform && IsOpaqueType(type.getBasicType()); + + // Only interested in array of array of opaque uniforms. + if (!isOpaqueUniform || !type.isArrayOfArrays()) + { + return false; + } + + // Opaque uniforms cannot have initializers, so the declaration must necessarily be a + // symbol. + TIntermSymbol *symbol = variable->getAsSymbolNode(); + ASSERT(symbol != nullptr); + + const TVariable *uniformVariable = &symbol->variable(); + + // Create an entry in the map. + ASSERT(mUniformMap.find(uniformVariable) == mUniformMap.end()); + UniformData &data = mUniformMap[uniformVariable]; + + // Calculate the accumulated dimension products. See UniformData::mSubArraySizes. + const TSpan<const unsigned int> &arraySizes = type.getArraySizes(); + mUniformMap[uniformVariable].mSubArraySizes.resize(arraySizes.size()); + unsigned int runningProduct = 1; + for (size_t dimension = 0; dimension < arraySizes.size(); ++dimension) + { + data.mSubArraySizes[dimension] = runningProduct; + runningProduct *= arraySizes[dimension]; + } + + // Create a replacement variable with the array flattened. + TType *newType = new TType(type); + newType->toArrayBaseType(); + newType->makeArray(runningProduct); + + data.flattened = new TVariable(mSymbolTable, uniformVariable->name(), newType, + uniformVariable->symbolType()); + + TIntermDeclaration *decl = new TIntermDeclaration; + decl->appendDeclarator(new TIntermSymbol(data.flattened)); + + queueReplacement(decl, OriginalNode::IS_DROPPED); + return false; + } + + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override + { + // As an optimization, don't bother inspecting functions if there aren't any opaque uniforms + // to replace. + return !mUniformMap.empty(); + } + + // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root. + bool visitBinary(Visit visit, TIntermBinary *node) override + { + TIntermTyped *rewritten = + RewriteArrayOfArraySubscriptExpression(mCompiler, node, mUniformMap); + if (rewritten == nullptr) + { + return true; + } + + queueReplacement(rewritten, OriginalNode::IS_DROPPED); + + // Don't iterate as the expression is rewritten. + return false; + } + + void visitSymbol(TIntermSymbol *node) override + { + ASSERT(!IsOpaqueType(node->getType().getBasicType()) || + mUniformMap.find(&node->variable()) == mUniformMap.end()); + } + + private: + TCompiler *mCompiler; + UniformMap mUniformMap; +}; +} // anonymous namespace + +bool RewriteArrayOfArrayOfOpaqueUniforms(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + RewriteArrayOfArrayOfOpaqueUniformsTraverser traverser(compiler, symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h new file mode 100644 index 0000000000..859ce16dc8 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h @@ -0,0 +1,25 @@ +// +// Copyright 2021 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. +// +// RewriteArrayOfArrayOfOpaqueUniforms: Flatten array of array of opaque uniforms. Requires +// MonomorphizeUnsupportedFunctions and RewriteStructSamplers to have been run. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITEARRAYOFARRAYOFOPAQUEUNIFORMS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITEARRAYOFARRAYOFOPAQUEUNIFORMS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool RewriteArrayOfArrayOfOpaqueUniforms(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEARRAYOFARRAYOFOPAQUEUNIFORMS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp new file mode 100644 index 0000000000..defc59bcc0 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp @@ -0,0 +1,328 @@ +// +// Copyright 2019 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. +// +// RewriteAtomicCounters: Emulate atomic counter buffers with storage buffers. +// + +#include "compiler/translator/tree_ops/RewriteAtomicCounters.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +constexpr ImmutableString kAtomicCountersVarName = ImmutableString("atomicCounters"); +constexpr ImmutableString kAtomicCounterFieldName = ImmutableString("counters"); + +// DeclareAtomicCountersBuffer adds a storage buffer array that's used with atomic counters. +const TVariable *DeclareAtomicCountersBuffers(TIntermBlock *root, TSymbolTable *symbolTable) +{ + // Define `uint counters[];` as the only field in the interface block. + TFieldList *fieldList = new TFieldList; + TType *counterType = new TType(EbtUInt, EbpHigh, EvqGlobal); + counterType->makeArray(0); + + TField *countersField = + new TField(counterType, kAtomicCounterFieldName, TSourceLoc(), SymbolType::AngleInternal); + + fieldList->push_back(countersField); + + TMemoryQualifier coherentMemory = TMemoryQualifier::Create(); + coherentMemory.coherent = true; + + // There are a maximum of 8 atomic counter buffers per IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS + // in libANGLE/Constants.h. + constexpr uint32_t kMaxAtomicCounterBuffers = 8; + + // Define a storage block "ANGLEAtomicCounters" with instance name "atomicCounters". + TLayoutQualifier layoutQualifier = TLayoutQualifier::Create(); + layoutQualifier.blockStorage = EbsStd430; + + return DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, layoutQualifier, + coherentMemory, kMaxAtomicCounterBuffers, + ImmutableString(vk::kAtomicCountersBlockName), + kAtomicCountersVarName); +} + +TIntermTyped *CreateUniformBufferOffset(const TIntermTyped *uniformBufferOffsets, int binding) +{ + // Each uint in the |acbBufferOffsets| uniform contains offsets for 4 bindings. Therefore, the + // expression to get the uniform offset for the binding is: + // + // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF + + // acbBufferOffsets[binding / 4] + TIntermBinary *uniformBufferOffsetUint = new TIntermBinary( + EOpIndexDirect, uniformBufferOffsets->deepCopy(), CreateIndexNode(binding / 4)); + + // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) + TIntermBinary *uniformBufferOffsetShifted = uniformBufferOffsetUint; + if (binding % 4 != 0) + { + uniformBufferOffsetShifted = new TIntermBinary(EOpBitShiftRight, uniformBufferOffsetUint, + CreateUIntNode((binding % 4) * 8)); + } + + // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF + return new TIntermBinary(EOpBitwiseAnd, uniformBufferOffsetShifted, CreateUIntNode(0xFF)); +} + +TIntermBinary *CreateAtomicCounterRef(TIntermTyped *atomicCounterExpression, + const TVariable *atomicCounters, + const TIntermTyped *uniformBufferOffsets) +{ + // The atomic counters storage buffer declaration looks as such: + // + // layout(...) buffer ANGLEAtomicCounters + // { + // uint counters[]; + // } atomicCounters[N]; + // + // Where N is large enough to accommodate atomic counter buffer bindings used in the shader. + // + // This function takes an expression that uses an atomic counter, which can either be: + // + // - ac + // - acArray[index] + // + // Note that RewriteArrayOfArrayOfOpaqueUniforms has already flattened array of array of atomic + // counters. + // + // For the first case (ac), the following code is generated: + // + // atomicCounters[binding].counters[offset] + // + // For the second case (acArray[index]), the following code is generated: + // + // atomicCounters[binding].counters[offset + index] + // + // In either case, an offset given through uniforms is also added to |offset|. The binding is + // necessarily a constant thanks to MonomorphizeUnsupportedFunctions. + + // First determine if there's an index, and extract the atomic counter symbol out of the + // expression. + TIntermSymbol *atomicCounterSymbol = atomicCounterExpression->getAsSymbolNode(); + TIntermTyped *atomicCounterIndex = nullptr; + int atomicCounterConstIndex = 0; + TIntermBinary *asBinary = atomicCounterExpression->getAsBinaryNode(); + if (asBinary != nullptr) + { + atomicCounterSymbol = asBinary->getLeft()->getAsSymbolNode(); + + switch (asBinary->getOp()) + { + case EOpIndexDirect: + atomicCounterConstIndex = asBinary->getRight()->getAsConstantUnion()->getIConst(0); + break; + case EOpIndexIndirect: + atomicCounterIndex = asBinary->getRight(); + break; + default: + UNREACHABLE(); + } + } + + // Extract binding and offset information out of the atomic counter symbol. + ASSERT(atomicCounterSymbol); + const TVariable *atomicCounterVar = &atomicCounterSymbol->variable(); + const TType &atomicCounterType = atomicCounterVar->getType(); + + const int binding = atomicCounterType.getLayoutQualifier().binding; + int offset = atomicCounterType.getLayoutQualifier().offset / 4; + + // Create the expression: + // + // offset + arrayIndex + uniformOffset + // + // If arrayIndex is a constant, it's added with offset right here. + + offset += atomicCounterConstIndex; + + TIntermTyped *index = CreateUniformBufferOffset(uniformBufferOffsets, binding); + if (atomicCounterIndex != nullptr) + { + index = new TIntermBinary(EOpAdd, index, atomicCounterIndex); + } + if (offset != 0) + { + index = new TIntermBinary(EOpAdd, index, CreateIndexNode(offset)); + } + + // Finally, create the complete expression: + // + // atomicCounters[binding].counters[index] + + TIntermSymbol *atomicCountersRef = new TIntermSymbol(atomicCounters); + + // atomicCounters[binding] + TIntermBinary *countersBlock = + new TIntermBinary(EOpIndexDirect, atomicCountersRef, CreateIndexNode(binding)); + + // atomicCounters[binding].counters + TIntermBinary *counters = + new TIntermBinary(EOpIndexDirectInterfaceBlock, countersBlock, CreateIndexNode(0)); + + return new TIntermBinary(EOpIndexIndirect, counters, index); +} + +// Traverser that: +// +// 1. Removes the |uniform atomic_uint| declarations and remembers the binding and offset. +// 2. Substitutes |atomicVar[n]| with |buffer[binding].counters[offset + n]|. +class RewriteAtomicCountersTraverser : public TIntermTraverser +{ + public: + RewriteAtomicCountersTraverser(TSymbolTable *symbolTable, + const TVariable *atomicCounters, + const TIntermTyped *acbBufferOffsets) + : TIntermTraverser(true, false, false, symbolTable), + mAtomicCounters(atomicCounters), + mAcbBufferOffsets(acbBufferOffsets) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + if (!mInGlobalScope) + { + return true; + } + + const TIntermSequence &sequence = *(node->getSequence()); + + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + bool isAtomicCounter = type.isAtomicCounter(); + + if (isAtomicCounter) + { + ASSERT(type.getQualifier() == EvqUniform); + TIntermSequence emptySequence; + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(emptySequence)); + + return false; + } + + return true; + } + + bool visitAggregate(Visit visit, TIntermAggregate *node) override + { + if (BuiltInGroup::IsBuiltIn(node->getOp())) + { + bool converted = convertBuiltinFunction(node); + return !converted; + } + + // AST functions don't require modification as atomic counter function parameters are + // removed by MonomorphizeUnsupportedFunctions. + return true; + } + + void visitSymbol(TIntermSymbol *symbol) override + { + // Cannot encounter the atomic counter symbol directly. It can only be used with functions, + // and therefore it's handled by visitAggregate. + ASSERT(!symbol->getType().isAtomicCounter()); + } + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + // Cannot encounter an atomic counter expression directly. It can only be used with + // functions, and therefore it's handled by visitAggregate. + ASSERT(!node->getType().isAtomicCounter()); + return true; + } + + private: + bool convertBuiltinFunction(TIntermAggregate *node) + { + const TOperator op = node->getOp(); + + // If the function is |memoryBarrierAtomicCounter|, simply replace it with + // |memoryBarrierBuffer|. + if (op == EOpMemoryBarrierAtomicCounter) + { + TIntermSequence emptySequence; + TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode( + "memoryBarrierBuffer", &emptySequence, *mSymbolTable, 310); + queueReplacement(substituteCall, OriginalNode::IS_DROPPED); + return true; + } + + // If it's an |atomicCounter*| function, replace the function with an |atomic*| equivalent. + if (!node->getFunction()->isAtomicCounterFunction()) + { + return false; + } + + // Note: atomicAdd(0) is used for atomic reads. + uint32_t valueChange = 0; + constexpr char kAtomicAddFunction[] = "atomicAdd"; + bool isDecrement = false; + + if (op == EOpAtomicCounterIncrement) + { + valueChange = 1; + } + else if (op == EOpAtomicCounterDecrement) + { + // uint values are required to wrap around, so 0xFFFFFFFFu is used as -1. + valueChange = std::numeric_limits<uint32_t>::max(); + static_assert(static_cast<uint32_t>(-1) == std::numeric_limits<uint32_t>::max(), + "uint32_t max is not -1"); + + isDecrement = true; + } + else + { + ASSERT(op == EOpAtomicCounter); + } + + TIntermTyped *param = (*node->getSequence())[0]->getAsTyped(); + + TIntermSequence substituteArguments; + substituteArguments.push_back( + CreateAtomicCounterRef(param, mAtomicCounters, mAcbBufferOffsets)); + substituteArguments.push_back(CreateUIntNode(valueChange)); + + TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode( + kAtomicAddFunction, &substituteArguments, *mSymbolTable, 310); + + // Note that atomicCounterDecrement returns the *new* value instead of the prior value, + // unlike atomicAdd. So we need to do a -1 on the result as well. + if (isDecrement) + { + substituteCall = new TIntermBinary(EOpSub, substituteCall, CreateUIntNode(1)); + } + + queueReplacement(substituteCall, OriginalNode::IS_DROPPED); + return true; + } + + const TVariable *mAtomicCounters; + const TIntermTyped *mAcbBufferOffsets; +}; + +} // anonymous namespace + +bool RewriteAtomicCounters(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const TIntermTyped *acbBufferOffsets) +{ + const TVariable *atomicCounters = DeclareAtomicCountersBuffers(root, symbolTable); + + RewriteAtomicCountersTraverser traverser(symbolTable, atomicCounters, acbBufferOffsets); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h new file mode 100644 index 0000000000..8a94f84b3a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h @@ -0,0 +1,28 @@ +// +// Copyright 2019 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. +// +// RewriteAtomicCounters: Change atomic counter buffers to storage buffers, with atomic counter +// variables being offsets into the uint array of that storage buffer. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TIntermTyped; +class TSymbolTable; +class TVariable; + +[[nodiscard]] bool RewriteAtomicCounters(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + const TIntermTyped *acbBufferOffsets); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp new file mode 100644 index 0000000000..f7e5814361 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp @@ -0,0 +1,984 @@ +// +// Copyright 2019 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. +// +// RewriteCubeMapSamplersAs2DArray: Change samplerCube samplers to sampler2DArray for seamful cube +// map emulation. +// +// Relies on MonomorphizeUnsupportedFunctions to ensure samplerCube variables are not +// passed to functions (for simplicity). +// + +#include "compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +constexpr ImmutableString kCoordTransformFuncName("ANGLECubeMapCoordTransform"); +constexpr ImmutableString kCoordTransformFuncNameImplicit("ANGLECubeMapCoordTransformImplicit"); + +TIntermTyped *DerivativeQuotient(TIntermTyped *u, + TIntermTyped *du, + TIntermTyped *v, + TIntermTyped *dv, + TIntermTyped *vRecip) +{ + // (du v - dv u) / v^2 + return new TIntermBinary( + EOpMul, + new TIntermBinary(EOpSub, new TIntermBinary(EOpMul, du->deepCopy(), v->deepCopy()), + new TIntermBinary(EOpMul, dv->deepCopy(), u->deepCopy())), + new TIntermBinary(EOpMul, vRecip->deepCopy(), vRecip->deepCopy())); +} + +TIntermTyped *Swizzle1(TIntermTyped *array, int i) +{ + return new TIntermSwizzle(array, {i}); +} + +TIntermTyped *IndexDirect(TIntermTyped *array, int i) +{ + return new TIntermBinary(EOpIndexDirect, array, CreateIndexNode(i)); +} + +// Generated the common transformation in each coord transformation case. See comment in +// declareCoordTranslationFunction(). Called with P, dPdx and dPdy. +void TransformXMajor(const TSymbolTable &symbolTable, + TIntermBlock *block, + TIntermTyped *x, + TIntermTyped *y, + TIntermTyped *z, + TIntermTyped *uc, + TIntermTyped *vc) +{ + // uc = -sign(x)*z + // vc = -y + TIntermTyped *signX = + CreateBuiltInUnaryFunctionCallNode("sign", x->deepCopy(), symbolTable, 100); + + TIntermTyped *ucValue = + new TIntermUnary(EOpNegative, new TIntermBinary(EOpMul, signX, z->deepCopy()), nullptr); + TIntermTyped *vcValue = new TIntermUnary(EOpNegative, y->deepCopy(), nullptr); + + block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue)); + block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue)); +} + +void TransformDerivativeXMajor(TIntermBlock *block, + TSymbolTable *symbolTable, + TIntermTyped *x, + TIntermTyped *y, + TIntermTyped *z, + TIntermTyped *dx, + TIntermTyped *dy, + TIntermTyped *dz, + TIntermTyped *du, + TIntermTyped *dv, + TIntermTyped *xRecip) +{ + // Only the magnitude of the derivative matters, so we ignore the sign(x) + // and the negations. + TIntermTyped *duValue = DerivativeQuotient(z, dz, x, dx, xRecip); + TIntermTyped *dvValue = DerivativeQuotient(y, dy, x, dx, xRecip); + duValue = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f, EbpMedium)); + dvValue = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f, EbpMedium)); + block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue)); + block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue)); +} + +void TransformImplicitDerivativeXMajor(TIntermBlock *block, + TIntermTyped *dOuter, + TIntermTyped *du, + TIntermTyped *dv) +{ + block->appendStatement( + new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 2))); + block->appendStatement( + new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 1))); +} + +void TransformYMajor(const TSymbolTable &symbolTable, + TIntermBlock *block, + TIntermTyped *x, + TIntermTyped *y, + TIntermTyped *z, + TIntermTyped *uc, + TIntermTyped *vc) +{ + // uc = x + // vc = sign(y)*z + TIntermTyped *signY = + CreateBuiltInUnaryFunctionCallNode("sign", y->deepCopy(), symbolTable, 100); + + TIntermTyped *ucValue = x->deepCopy(); + TIntermTyped *vcValue = new TIntermBinary(EOpMul, signY, z->deepCopy()); + + block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue)); + block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue)); +} + +void TransformDerivativeYMajor(TIntermBlock *block, + TSymbolTable *symbolTable, + TIntermTyped *x, + TIntermTyped *y, + TIntermTyped *z, + TIntermTyped *dx, + TIntermTyped *dy, + TIntermTyped *dz, + TIntermTyped *du, + TIntermTyped *dv, + TIntermTyped *yRecip) +{ + // Only the magnitude of the derivative matters, so we ignore the sign(x) + // and the negations. + TIntermTyped *duValue = DerivativeQuotient(x, dx, y, dy, yRecip); + TIntermTyped *dvValue = DerivativeQuotient(z, dz, y, dy, yRecip); + duValue = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f, EbpMedium)); + dvValue = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f, EbpMedium)); + block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue)); + block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue)); +} + +void TransformImplicitDerivativeYMajor(TIntermBlock *block, + TIntermTyped *dOuter, + TIntermTyped *du, + TIntermTyped *dv) +{ + block->appendStatement( + new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 0))); + block->appendStatement( + new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 2))); +} + +void TransformZMajor(const TSymbolTable &symbolTable, + TIntermBlock *block, + TIntermTyped *x, + TIntermTyped *y, + TIntermTyped *z, + TIntermTyped *uc, + TIntermTyped *vc) +{ + // uc = size(z)*x + // vc = -y + TIntermTyped *signZ = + CreateBuiltInUnaryFunctionCallNode("sign", z->deepCopy(), symbolTable, 100); + + TIntermTyped *ucValue = new TIntermBinary(EOpMul, signZ, x->deepCopy()); + TIntermTyped *vcValue = new TIntermUnary(EOpNegative, y->deepCopy(), nullptr); + + block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue)); + block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue)); +} + +void TransformDerivativeZMajor(TIntermBlock *block, + TSymbolTable *symbolTable, + TIntermTyped *x, + TIntermTyped *y, + TIntermTyped *z, + TIntermTyped *dx, + TIntermTyped *dy, + TIntermTyped *dz, + TIntermTyped *du, + TIntermTyped *dv, + TIntermTyped *zRecip) +{ + // Only the magnitude of the derivative matters, so we ignore the sign(x) + // and the negations. + TIntermTyped *duValue = DerivativeQuotient(x, dx, z, dz, zRecip); + TIntermTyped *dvValue = DerivativeQuotient(y, dy, z, dz, zRecip); + duValue = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f, EbpMedium)); + dvValue = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f, EbpMedium)); + block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue)); + block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue)); +} + +void TransformImplicitDerivativeZMajor(TIntermBlock *block, + TIntermTyped *dOuter, + TIntermTyped *du, + TIntermTyped *dv) +{ + block->appendStatement( + new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 0))); + block->appendStatement( + new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 1))); +} + +class RewriteCubeMapSamplersAs2DArrayTraverser : public TIntermTraverser +{ + public: + RewriteCubeMapSamplersAs2DArrayTraverser(TSymbolTable *symbolTable, bool isFragmentShader) + : TIntermTraverser(true, false, false, symbolTable), + mCubeXYZToArrayUVL(nullptr), + mCubeXYZToArrayUVLImplicit(nullptr), + mIsFragmentShader(isFragmentShader), + mCoordTranslationFunctionDecl(nullptr), + mCoordTranslationFunctionImplicitDecl(nullptr) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + const TIntermSequence &sequence = *(node->getSequence()); + + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + bool isSamplerCube = type.getQualifier() == EvqUniform && type.isSamplerCube(); + + if (isSamplerCube) + { + // Samplers cannot have initializers, so the declaration must necessarily be a symbol. + TIntermSymbol *samplerVariable = variable->getAsSymbolNode(); + ASSERT(samplerVariable != nullptr); + + declareSampler2DArray(&samplerVariable->variable(), node); + return false; + } + + return true; + } + + bool visitAggregate(Visit visit, TIntermAggregate *node) override + { + if (BuiltInGroup::IsBuiltIn(node->getOp())) + { + bool converted = convertBuiltinFunction(node); + return !converted; + } + + // AST functions don't require modification as samplerCube function parameters are removed + // by MonomorphizeUnsupportedFunctions. + return true; + } + + TIntermFunctionDefinition *getCoordTranslationFunctionDecl() + { + return mCoordTranslationFunctionDecl; + } + + TIntermFunctionDefinition *getCoordTranslationFunctionDeclImplicit() + { + return mCoordTranslationFunctionImplicitDecl; + } + + private: + void declareSampler2DArray(const TVariable *samplerCubeVar, TIntermDeclaration *node) + { + if (mCubeXYZToArrayUVL == nullptr) + { + // If not done yet, declare the function that transforms cube map texture sampling + // coordinates to face index and uv coordinates. + declareCoordTranslationFunction(false, kCoordTransformFuncName, &mCubeXYZToArrayUVL, + &mCoordTranslationFunctionDecl); + } + if (mCubeXYZToArrayUVLImplicit == nullptr && mIsFragmentShader) + { + declareCoordTranslationFunction(true, kCoordTransformFuncNameImplicit, + &mCubeXYZToArrayUVLImplicit, + &mCoordTranslationFunctionImplicitDecl); + } + + TType *newType = new TType(samplerCubeVar->getType()); + newType->setBasicType(EbtSampler2DArray); + + TVariable *sampler2DArrayVar = new TVariable(mSymbolTable, samplerCubeVar->name(), newType, + samplerCubeVar->symbolType()); + + TIntermDeclaration *sampler2DArrayDecl = new TIntermDeclaration(); + sampler2DArrayDecl->appendDeclarator(new TIntermSymbol(sampler2DArrayVar)); + + queueReplacement(sampler2DArrayDecl, OriginalNode::IS_DROPPED); + + // Remember the sampler2DArray variable. + mSamplerMap[samplerCubeVar] = sampler2DArrayVar; + } + + void declareCoordTranslationFunction(bool implicit, + const ImmutableString &name, + TFunction **functionOut, + TIntermFunctionDefinition **declOut) + { + // GLES2.0 (as well as desktop OpenGL 2.0) define the coordination transformation as + // follows. Given xyz cube coordinates, where each channel is in [-1, 1], the following + // table calculates uc, vc and ma as well as the cube map face. + // + // Major Axis Direction Target uc vc ma + // +x TEXTURE_CUBE_MAP_POSITIVE_X -z -y |x| + // -x TEXTURE_CUBE_MAP_NEGATIVE_X z -y |x| + // +y TEXTURE_CUBE_MAP_POSITIVE_Y x z |y| + // -y TEXTURE_CUBE_MAP_NEGATIVE_Y x -z |y| + // +z TEXTURE_CUBE_MAP_POSITIVE_Z x -y |z| + // -z TEXTURE_CUBE_MAP_NEGATIVE_Z -x -y |z| + // + // "Major" is an indication of the axis with the largest value. The cube map face indicates + // the layer to sample from. The uv coordinates to sample from are calculated as, + // effectively transforming the uv values to [0, 1]: + // + // u = (1 + uc/ma) / 2 + // v = (1 + vc/ma) / 2 + // + // The function can be implemented as 6 ifs, though it would be far from efficient. The + // following calculations implement the table above in a smaller number of instructions. + // + // First, ma can be calculated as the max of the three axes. + // + // ma = max3(|x|, |y|, |z|) + // + // We have three cases: + // + // ma == |x|: uc = -sign(x)*z + // vc = -y + // layer = float(x < 0) + // + // ma == |y|: uc = x + // vc = sign(y)*z + // layer = 2 + float(y < 0) + // + // ma == |z|: uc = size(z)*x + // vc = -y + // layer = 4 + float(z < 0) + // + // This can be implemented with a number of ?: instructions or 3 ifs. ?: would require all + // expressions to be evaluated (vector ALU) while if would require exec mask and jumps + // (scalar operations). We implement this using ifs as there would otherwise be many vector + // operations and not much of anything else. + // + // If textureCubeGrad is used, we also need to transform the provided dPdx and dPdy (both + // vec3) to a dUVdx and dUVdy. Assume P=(r,s,t) and we are investigating dx (note the + // change from xyz to rst to not confuse with dx and dy): + // + // uv = (f(r,s,t)/ma + 1)/2 + // + // Where f is one of the transformations above for uc and vc. Between two neighbors along + // the x axis, we have P0=(r0,s0,t0) and P1=(r1,s1,t1) + // + // dP = (r1-r0, s1-s0, t1-t0) + // dUV = (f(r1,s1,t1)/ma1 - g(r0,s0,t0)/ma0) / 2 + // + // f and g may not necessarily be the same because the two points may have different major + // axes. Even with the same major access, the sign that's used in the formulas may not be + // the same. Furthermore, ma0 and ma1 may not be the same. This makes it impossible to + // derive dUV from dP exactly. + // + // However, gradient transformation is implementation dependant, so we will simplify and + // assume all the above complications are non-existent. We therefore have: + // + // dUV = (f(r1,s1,t1)/ma0 - f(r0,s0,t0)/ma0)/2 + // + // Given that we assumed the sign functions are returning identical results for the two + // points, f becomes a linear transformation. Thus: + // + // dUV = f(r1-r0,s1-0,t1-t0)/ma0/2 + // + // In other words, we use the same formulae that transform XYZ (RST here) to UV to + // transform the derivatives. + // + // ma == |x|: dUdx = -sign(x)*dPdx.z / ma / 2 + // dVdx = -dPdx.y / ma / 2 + // + // ma == |y|: dUdx = dPdx.x / ma / 2 + // dVdx = sign(y)*dPdx.z / ma / 2 + // + // ma == |z|: dUdx = size(z)*dPdx.x / ma / 2 + // dVdx = -dPdx.y / ma / 2 + // + // Similarly for dy. + + // Create the function parameters: vec3 P, vec3 dPdx, vec3 dPdy, + // out vec2 dUVdx, out vec2 dUVdy + const TType *vec3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3>(); + TType *inVec3Type = new TType(*vec3Type); + inVec3Type->setQualifier(EvqParamIn); + + TVariable *pVar = new TVariable(mSymbolTable, ImmutableString("P"), inVec3Type, + SymbolType::AngleInternal); + TVariable *dPdxVar = new TVariable(mSymbolTable, ImmutableString("dPdx"), inVec3Type, + SymbolType::AngleInternal); + TVariable *dPdyVar = new TVariable(mSymbolTable, ImmutableString("dPdy"), inVec3Type, + SymbolType::AngleInternal); + + const TType *vec2Type = StaticType::GetBasic<EbtFloat, EbpHigh, 2>(); + TType *outVec2Type = new TType(*vec2Type); + outVec2Type->setQualifier(EvqParamOut); + + TVariable *dUVdxVar = new TVariable(mSymbolTable, ImmutableString("dUVdx"), outVec2Type, + SymbolType::AngleInternal); + TVariable *dUVdyVar = new TVariable(mSymbolTable, ImmutableString("dUVdy"), outVec2Type, + SymbolType::AngleInternal); + + TIntermSymbol *p = new TIntermSymbol(pVar); + TIntermSymbol *dPdx = new TIntermSymbol(dPdxVar); + TIntermSymbol *dPdy = new TIntermSymbol(dPdyVar); + TIntermSymbol *dUVdx = new TIntermSymbol(dUVdxVar); + TIntermSymbol *dUVdy = new TIntermSymbol(dUVdyVar); + + // Create the function body as statements are generated. + TIntermBlock *body = new TIntermBlock; + + // Create the swizzle nodes that will be used in multiple expressions: + TIntermSwizzle *x = new TIntermSwizzle(p->deepCopy(), {0}); + TIntermSwizzle *y = new TIntermSwizzle(p->deepCopy(), {1}); + TIntermSwizzle *z = new TIntermSwizzle(p->deepCopy(), {2}); + + // Create abs and "< 0" expressions from the channels. + const TType *floatType = StaticType::GetBasic<EbtFloat, EbpHigh>(); + + TIntermTyped *isNegX = new TIntermBinary(EOpLessThan, x, CreateZeroNode(*floatType)); + TIntermTyped *isNegY = new TIntermBinary(EOpLessThan, y, CreateZeroNode(*floatType)); + TIntermTyped *isNegZ = new TIntermBinary(EOpLessThan, z, CreateZeroNode(*floatType)); + + TIntermSymbol *absX = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *absY = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *absZ = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + + TIntermDeclaration *absXDecl = CreateTempInitDeclarationNode( + &absX->variable(), + CreateBuiltInUnaryFunctionCallNode("abs", x->deepCopy(), *mSymbolTable, 100)); + TIntermDeclaration *absYDecl = CreateTempInitDeclarationNode( + &absY->variable(), + CreateBuiltInUnaryFunctionCallNode("abs", y->deepCopy(), *mSymbolTable, 100)); + TIntermDeclaration *absZDecl = CreateTempInitDeclarationNode( + &absZ->variable(), + CreateBuiltInUnaryFunctionCallNode("abs", z->deepCopy(), *mSymbolTable, 100)); + + body->appendStatement(absXDecl); + body->appendStatement(absYDecl); + body->appendStatement(absZDecl); + + // Create temporary variable for division outer product matrix and its + // derivatives. + // recipOuter[i][j] = 0.5 * P[j] / P[i] + const TType *mat3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3, 3>(); + TIntermSymbol *recipOuter = new TIntermSymbol(CreateTempVariable(mSymbolTable, mat3Type)); + + TIntermTyped *pRecip = + new TIntermBinary(EOpDiv, CreateFloatNode(1.0, EbpMedium), p->deepCopy()); + TIntermSymbol *pRecipVar = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + + body->appendStatement(CreateTempInitDeclarationNode(&pRecipVar->variable(), pRecip)); + + TIntermSequence args = { + p->deepCopy(), new TIntermBinary(EOpVectorTimesScalar, CreateFloatNode(0.5, EbpMedium), + pRecipVar->deepCopy())}; + TIntermDeclaration *recipOuterDecl = CreateTempInitDeclarationNode( + &recipOuter->variable(), + CreateBuiltInFunctionCallNode("outerProduct", &args, *mSymbolTable, 300)); + body->appendStatement(recipOuterDecl); + + TIntermSymbol *dPDXdx = nullptr; + TIntermSymbol *dPDYdx = nullptr; + TIntermSymbol *dPDZdx = nullptr; + TIntermSymbol *dPDXdy = nullptr; + TIntermSymbol *dPDYdy = nullptr; + TIntermSymbol *dPDZdy = nullptr; + if (implicit) + { + dPDXdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + dPDYdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + dPDZdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + dPDXdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + dPDYdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + dPDZdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + + TIntermDeclaration *dPDXdxDecl = CreateTempInitDeclarationNode( + &dPDXdx->variable(), + CreateBuiltInUnaryFunctionCallNode("dFdx", IndexDirect(recipOuter, 0)->deepCopy(), + *mSymbolTable, 300)); + TIntermDeclaration *dPDYdxDecl = CreateTempInitDeclarationNode( + &dPDYdx->variable(), + CreateBuiltInUnaryFunctionCallNode("dFdx", IndexDirect(recipOuter, 1)->deepCopy(), + *mSymbolTable, 300)); + TIntermDeclaration *dPDZdxDecl = CreateTempInitDeclarationNode( + &dPDZdx->variable(), + CreateBuiltInUnaryFunctionCallNode("dFdx", IndexDirect(recipOuter, 2)->deepCopy(), + *mSymbolTable, 300)); + TIntermDeclaration *dPDXdyDecl = CreateTempInitDeclarationNode( + &dPDXdy->variable(), + CreateBuiltInUnaryFunctionCallNode("dFdy", IndexDirect(recipOuter, 0)->deepCopy(), + *mSymbolTable, 300)); + TIntermDeclaration *dPDYdyDecl = CreateTempInitDeclarationNode( + &dPDYdy->variable(), + CreateBuiltInUnaryFunctionCallNode("dFdy", IndexDirect(recipOuter, 1)->deepCopy(), + *mSymbolTable, 300)); + TIntermDeclaration *dPDZdyDecl = CreateTempInitDeclarationNode( + &dPDZdy->variable(), + CreateBuiltInUnaryFunctionCallNode("dFdy", IndexDirect(recipOuter, 2)->deepCopy(), + *mSymbolTable, 300)); + + body->appendStatement(dPDXdxDecl); + body->appendStatement(dPDYdxDecl); + body->appendStatement(dPDZdxDecl); + body->appendStatement(dPDXdyDecl); + body->appendStatement(dPDYdyDecl); + body->appendStatement(dPDZdyDecl); + } + + // Create temporary variables for ma, uc, vc, and l (layer), as well as dUdx, dVdx, dUdy + // and dVdy. + TIntermSymbol *ma = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *l = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *uc = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *vc = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *dUdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *dVdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *dUdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + TIntermSymbol *dVdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + + body->appendStatement(CreateTempDeclarationNode(&ma->variable())); + body->appendStatement(CreateTempDeclarationNode(&l->variable())); + body->appendStatement(CreateTempDeclarationNode(&uc->variable())); + body->appendStatement(CreateTempDeclarationNode(&vc->variable())); + body->appendStatement(CreateTempDeclarationNode(&dUdx->variable())); + body->appendStatement(CreateTempDeclarationNode(&dVdx->variable())); + body->appendStatement(CreateTempDeclarationNode(&dUdy->variable())); + body->appendStatement(CreateTempDeclarationNode(&dVdy->variable())); + + // ma = max(|x|, max(|y|, |z|)) + TIntermSequence argsMaxYZ = {absY->deepCopy(), absZ->deepCopy()}; + TIntermTyped *maxYZ = CreateBuiltInFunctionCallNode("max", &argsMaxYZ, *mSymbolTable, 100); + TIntermSequence argsMaxValue = {absX->deepCopy(), maxYZ}; + TIntermTyped *maValue = + CreateBuiltInFunctionCallNode("max", &argsMaxValue, *mSymbolTable, 100); + body->appendStatement(new TIntermBinary(EOpAssign, ma, maValue)); + + // ma == |x| and ma == |y| expressions + TIntermTyped *isXMajor = new TIntermBinary(EOpEqual, ma->deepCopy(), absX->deepCopy()); + TIntermTyped *isYMajor = new TIntermBinary(EOpEqual, ma->deepCopy(), absY->deepCopy()); + + // Determine the cube face: + + // The case where x is major: + // layer = float(x < 0) + TIntermSequence argsNegX = {isNegX}; + TIntermTyped *xl = TIntermAggregate::CreateConstructor(*floatType, &argsNegX); + + TIntermBlock *calculateXL = new TIntermBlock; + calculateXL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), xl)); + + // The case where y is major: + // layer = 2 + float(y < 0) + TIntermSequence argsNegY = {isNegY}; + TIntermTyped *yl = + new TIntermBinary(EOpAdd, CreateFloatNode(2.0f, EbpMedium), + TIntermAggregate::CreateConstructor(*floatType, &argsNegY)); + + TIntermBlock *calculateYL = new TIntermBlock; + calculateYL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), yl)); + + // The case where z is major: + // layer = 4 + float(z < 0) + TIntermSequence argsNegZ = {isNegZ}; + TIntermTyped *zl = + new TIntermBinary(EOpAdd, CreateFloatNode(4.0f, EbpMedium), + TIntermAggregate::CreateConstructor(*floatType, &argsNegZ)); + + TIntermBlock *calculateZL = new TIntermBlock; + calculateZL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), zl)); + + // Create the if-else paths: + TIntermIfElse *calculateYZL = new TIntermIfElse(isYMajor, calculateYL, calculateZL); + TIntermBlock *calculateYZLBlock = new TIntermBlock; + calculateYZLBlock->appendStatement(calculateYZL); + TIntermIfElse *calculateXYZL = new TIntermIfElse(isXMajor, calculateXL, calculateYZLBlock); + body->appendStatement(calculateXYZL); + + // layer < 1.5 (covering faces 0 and 1, corresponding to major axis being X) and layer < 3.5 + // (covering faces 2 and 3, corresponding to major axis being Y). Used to determine which + // of the three transformations to apply. Previously, ma == |X| and ma == |Y| was used, + // which is no longer correct for helper invocations. The value of ma is updated in each + // case for these invocations. + isXMajor = new TIntermBinary(EOpLessThan, l->deepCopy(), CreateFloatNode(1.5f, EbpMedium)); + isYMajor = new TIntermBinary(EOpLessThan, l->deepCopy(), CreateFloatNode(3.5f, EbpMedium)); + + TIntermSwizzle *dPdxX = new TIntermSwizzle(dPdx->deepCopy(), {0}); + TIntermSwizzle *dPdxY = new TIntermSwizzle(dPdx->deepCopy(), {1}); + TIntermSwizzle *dPdxZ = new TIntermSwizzle(dPdx->deepCopy(), {2}); + + TIntermSwizzle *dPdyX = new TIntermSwizzle(dPdy->deepCopy(), {0}); + TIntermSwizzle *dPdyY = new TIntermSwizzle(dPdy->deepCopy(), {1}); + TIntermSwizzle *dPdyZ = new TIntermSwizzle(dPdy->deepCopy(), {2}); + + TIntermBlock *calculateXUcVc = new TIntermBlock; + calculateXUcVc->appendStatement( + new TIntermBinary(EOpAssign, ma->deepCopy(), absX->deepCopy())); + TransformXMajor(*mSymbolTable, calculateXUcVc, x, y, z, uc, vc); + + TIntermBlock *calculateYUcVc = new TIntermBlock; + calculateYUcVc->appendStatement( + new TIntermBinary(EOpAssign, ma->deepCopy(), absY->deepCopy())); + TransformYMajor(*mSymbolTable, calculateYUcVc, x, y, z, uc, vc); + + TIntermBlock *calculateZUcVc = new TIntermBlock; + calculateZUcVc->appendStatement( + new TIntermBinary(EOpAssign, ma->deepCopy(), absZ->deepCopy())); + TransformZMajor(*mSymbolTable, calculateZUcVc, x, y, z, uc, vc); + + // Compute derivatives. + if (implicit) + { + TransformImplicitDerivativeXMajor(calculateXUcVc, dPDXdx, dUdx, dVdx); + TransformImplicitDerivativeXMajor(calculateXUcVc, dPDXdy, dUdy, dVdy); + TransformImplicitDerivativeYMajor(calculateYUcVc, dPDYdx, dUdx, dVdx); + TransformImplicitDerivativeYMajor(calculateYUcVc, dPDYdy, dUdy, dVdy); + TransformImplicitDerivativeZMajor(calculateZUcVc, dPDZdx, dUdx, dVdx); + TransformImplicitDerivativeZMajor(calculateZUcVc, dPDZdy, dUdy, dVdy); + } + else + { + TransformDerivativeXMajor(calculateXUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ, + dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 0)); + TransformDerivativeXMajor(calculateXUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ, + dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 0)); + TransformDerivativeYMajor(calculateYUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ, + dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 1)); + TransformDerivativeYMajor(calculateYUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ, + dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 1)); + TransformDerivativeZMajor(calculateZUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ, + dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 2)); + TransformDerivativeZMajor(calculateZUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ, + dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 2)); + } + + // Create the if-else paths: + TIntermIfElse *calculateYZUcVc = + new TIntermIfElse(isYMajor, calculateYUcVc, calculateZUcVc); + TIntermBlock *calculateYZUcVcBlock = new TIntermBlock; + calculateYZUcVcBlock->appendStatement(calculateYZUcVc); + TIntermIfElse *calculateXYZUcVc = + new TIntermIfElse(isXMajor, calculateXUcVc, calculateYZUcVcBlock); + body->appendStatement(calculateXYZUcVc); + + // u = (1 + uc/|ma|) / 2 + // v = (1 + vc/|ma|) / 2 + TIntermTyped *maTimesTwoRecip = new TIntermBinary( + EOpAssign, ma->deepCopy(), + new TIntermBinary(EOpDiv, CreateFloatNode(0.5f, EbpMedium), ma->deepCopy())); + body->appendStatement(maTimesTwoRecip); + + TIntermTyped *ucDivMa = new TIntermBinary(EOpMul, uc, ma->deepCopy()); + TIntermTyped *vcDivMa = new TIntermBinary(EOpMul, vc, ma->deepCopy()); + TIntermTyped *uNormalized = + new TIntermBinary(EOpAdd, CreateFloatNode(0.5f, EbpMedium), ucDivMa); + TIntermTyped *vNormalized = + new TIntermBinary(EOpAdd, CreateFloatNode(0.5f, EbpMedium), vcDivMa); + + body->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), uNormalized)); + body->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vNormalized)); + + TIntermSequence argsDUVdx = {dUdx, dVdx}; + TIntermTyped *dUVdxValue = TIntermAggregate::CreateConstructor(*vec2Type, &argsDUVdx); + + TIntermSequence argsDUVdy = {dUdy, dVdy}; + TIntermTyped *dUVdyValue = TIntermAggregate::CreateConstructor(*vec2Type, &argsDUVdy); + + body->appendStatement(new TIntermBinary(EOpAssign, dUVdx, dUVdxValue)); + body->appendStatement(new TIntermBinary(EOpAssign, dUVdy, dUVdyValue)); + + // return vec3(u, v, l) + TIntermSequence argsUVL = {uc->deepCopy(), vc->deepCopy(), l}; + TIntermBranch *returnStatement = + new TIntermBranch(EOpReturn, TIntermAggregate::CreateConstructor(*vec3Type, &argsUVL)); + body->appendStatement(returnStatement); + + TFunction *function; + function = new TFunction(mSymbolTable, name, SymbolType::AngleInternal, vec3Type, true); + function->addParameter(pVar); + function->addParameter(dPdxVar); + function->addParameter(dPdyVar); + function->addParameter(dUVdxVar); + function->addParameter(dUVdyVar); + + *functionOut = function; + + *declOut = CreateInternalFunctionDefinitionNode(*function, body); + } + + TIntermTyped *createCoordTransformationCall(TIntermTyped *P, + TIntermTyped *dPdx, + TIntermTyped *dPdy, + TIntermTyped *dUVdx, + TIntermTyped *dUVdy) + { + TIntermSequence args = {P, dPdx, dPdy, dUVdx, dUVdy}; + return TIntermAggregate::CreateFunctionCall(*mCubeXYZToArrayUVL, &args); + } + + TIntermTyped *createImplicitCoordTransformationCall(TIntermTyped *P, + TIntermTyped *dUVdx, + TIntermTyped *dUVdy) + { + const TType *vec3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3>(); + TIntermTyped *dPdx = CreateZeroNode(*vec3Type); + TIntermTyped *dPdy = CreateZeroNode(*vec3Type); + TIntermSequence args = {P, dPdx, dPdy, dUVdx, dUVdy}; + return TIntermAggregate::CreateFunctionCall(*mCubeXYZToArrayUVLImplicit, &args); + } + + TIntermTyped *getMappedSamplerExpression(TIntermNode *samplerCubeExpression) + { + // The argument passed to a function can either be the sampler, if not array, or a subscript + // into the sampler array. + TIntermSymbol *asSymbol = samplerCubeExpression->getAsSymbolNode(); + TIntermBinary *asBinary = samplerCubeExpression->getAsBinaryNode(); + + if (asBinary) + { + // Only constant indexing is supported in ES2.0. + ASSERT(asBinary->getOp() == EOpIndexDirect); + asSymbol = asBinary->getLeft()->getAsSymbolNode(); + } + + // Arrays of arrays are not available in ES2.0. + ASSERT(asSymbol != nullptr); + const TVariable *samplerCubeVar = &asSymbol->variable(); + + ASSERT(mSamplerMap.find(samplerCubeVar) != mSamplerMap.end()); + const TVariable *mappedSamplerVar = mSamplerMap.at(samplerCubeVar); + + TIntermTyped *mappedExpression = new TIntermSymbol(mappedSamplerVar); + if (asBinary) + { + mappedExpression = + new TIntermBinary(asBinary->getOp(), mappedExpression, asBinary->getRight()); + } + + return mappedExpression; + } + + bool convertBuiltinFunction(TIntermAggregate *node) + { + const TFunction *function = node->getFunction(); + if (!function->name().beginsWith("textureCube")) + { + return false; + } + + // All textureCube* functions are in the form: + // + // textureCube??(samplerCube, vec3, ??) + // + // They should be converted to: + // + // texture??(sampler2DArray, convertCoords(vec3), ??) + // + // We assume the target platform supports texture() functions (currently only used in + // Vulkan). + // + // The intrinsics map as follows: + // + // textureCube -> textureGrad + // textureCubeLod -> textureLod + // textureCubeLodEXT -> textureLod + // textureCubeGrad -> textureGrad + // textureCubeGradEXT -> textureGrad + // + // Note that dPdx and dPdy in textureCubeGrad* are vec3, while the textureGrad equivalent + // for sampler2DArray is vec2. The EXT_shader_texture_lod that introduces thid function + // says: + // + // > For the "Grad" functions, dPdx is the explicit derivative of P with respect + // > to window x, and similarly dPdy with respect to window y. ... For a cube map texture, + // > dPdx and dPdy are vec3. + // > + // > Let + // > + // > dSdx = dPdx.s; + // > dSdy = dPdy.s; + // > dTdx = dPdx.t; + // > dTdy = dPdy.t; + // > + // > and + // > + // > / 0.0; for two-dimensional texture + // > dRdx = ( + // > \ dPdx.p; for cube map texture + // > + // > / 0.0; for two-dimensional texture + // > dRdy = ( + // > \ dPdy.p; for cube map texture + // > + // > (See equation 3.12a in The OpenGL ES 2.0 Specification.) + // + // It's unclear to me what dRdx and dRdy are. EXT_gpu_shader4 that promotes this function + // has the following additional information: + // + // > For the "Cube" versions, the partial + // > derivatives ddx and ddy are assumed to be in the coordinate system used + // > before texture coordinates are projected onto the appropriate cube + // > face. The partial derivatives of the post-projection texture coordinates, + // > which are used for level-of-detail and anisotropic filtering + // > calculations, are derived from coord, ddx and ddy in an + // > implementation-dependent manner. + // + // The calculation of dPdx and dPdy is declared as implementation-dependent, so we have + // freedom to calculate it as fit, even if not precisely the same as hardware might. + + const char *substituteFunctionName = "textureGrad"; + bool isGrad = false; + bool isTranslatedGrad = true; + bool hasBias = false; + if (function->name().beginsWith("textureCubeLod")) + { + substituteFunctionName = "textureLod"; + isTranslatedGrad = false; + } + else if (function->name().beginsWith("textureCubeGrad")) + { + isGrad = true; + } + else if (!mIsFragmentShader) + { + substituteFunctionName = "texture"; + isTranslatedGrad = false; + } + + TIntermSequence *arguments = node->getSequence(); + ASSERT(arguments->size() >= 2); + + const TType *vec2Type = StaticType::GetBasic<EbtFloat, EbpHigh, 2>(); + const TType *vec3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3>(); + TIntermSymbol *uvl = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type)); + TIntermSymbol *dUVdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec2Type)); + TIntermSymbol *dUVdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec2Type)); + + TIntermTyped *dPdx = nullptr; + TIntermTyped *dPdy = nullptr; + if (isGrad) + { + ASSERT(arguments->size() == 4); + dPdx = (*arguments)[2]->getAsTyped()->deepCopy(); + dPdy = (*arguments)[3]->getAsTyped()->deepCopy(); + } + else if (isTranslatedGrad && mIsFragmentShader && arguments->size() == 3) + { + hasBias = true; + } + else + { + dPdx = CreateZeroNode(*vec3Type); + dPdy = CreateZeroNode(*vec3Type); + } + + if (isTranslatedGrad && !mIsFragmentShader) + { + substituteFunctionName = "texture"; + isTranslatedGrad = false; + } + + // The function call to transform the coordinates, dPdx and dPdy. If not textureCubeGrad, + // the driver compiler will optimize out the unnecessary calculations. + TIntermSequence coordTransform; + coordTransform.push_back(CreateTempDeclarationNode(&dUVdx->variable())); + coordTransform.push_back(CreateTempDeclarationNode(&dUVdy->variable())); + TIntermTyped *coordTransformCall; + if (isGrad || !isTranslatedGrad) + { + coordTransformCall = createCoordTransformationCall( + (*arguments)[1]->getAsTyped()->deepCopy(), dPdx, dPdy, dUVdx, dUVdy); + } + else + { + coordTransformCall = createImplicitCoordTransformationCall( + (*arguments)[1]->getAsTyped()->deepCopy(), dUVdx, dUVdy); + } + coordTransform.push_back( + CreateTempInitDeclarationNode(&uvl->variable(), coordTransformCall)); + + TIntermTyped *dUVdxArg = dUVdx; + TIntermTyped *dUVdyArg = dUVdy; + if (hasBias) + { + const TType *floatType = StaticType::GetBasic<EbtFloat, EbpHigh>(); + TIntermTyped *bias = (*arguments)[2]->getAsTyped()->deepCopy(); + TIntermSequence exp2Args = {bias}; + TIntermTyped *exp2Call = + CreateBuiltInFunctionCallNode("exp2", &exp2Args, *mSymbolTable, 100); + TIntermSymbol *biasFac = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType)); + coordTransform.push_back(CreateTempInitDeclarationNode(&biasFac->variable(), exp2Call)); + dUVdxArg = + new TIntermBinary(EOpVectorTimesScalar, biasFac->deepCopy(), dUVdx->deepCopy()); + dUVdyArg = + new TIntermBinary(EOpVectorTimesScalar, biasFac->deepCopy(), dUVdy->deepCopy()); + } + + insertStatementsInParentBlock(coordTransform); + + TIntermSequence substituteArguments; + // Replace the first argument (samplerCube) with the sampler2DArray. + substituteArguments.push_back(getMappedSamplerExpression((*arguments)[0])); + // Replace the second argument with the coordination transformation. + substituteArguments.push_back(uvl->deepCopy()); + if (isTranslatedGrad) + { + substituteArguments.push_back(dUVdxArg->deepCopy()); + substituteArguments.push_back(dUVdyArg->deepCopy()); + } + else + { + // Pass the rest of the parameters as is. + for (size_t argIndex = 2; argIndex < arguments->size(); ++argIndex) + { + substituteArguments.push_back((*arguments)[argIndex]->getAsTyped()->deepCopy()); + } + } + + TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode( + substituteFunctionName, &substituteArguments, *mSymbolTable, 300); + + queueReplacement(substituteCall, OriginalNode::IS_DROPPED); + + return true; + } + + // A map from the samplerCube variable to the sampler2DArray one. + angle::HashMap<const TVariable *, const TVariable *> mSamplerMap; + + // A helper function to convert xyz coordinates passed to a cube map sampling function into the + // array layer (cube map face) and uv coordinates. + TFunction *mCubeXYZToArrayUVL; + // A specialized version of the same function which uses implicit derivatives. + TFunction *mCubeXYZToArrayUVLImplicit; + + bool mIsFragmentShader; + + // Stored to be put before the first function after the pass. + TIntermFunctionDefinition *mCoordTranslationFunctionDecl; + TIntermFunctionDefinition *mCoordTranslationFunctionImplicitDecl; +}; +} // anonymous namespace + +bool RewriteCubeMapSamplersAs2DArray(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + bool isFragmentShader) +{ + RewriteCubeMapSamplersAs2DArrayTraverser traverser(symbolTable, isFragmentShader); + root->traverse(&traverser); + + TIntermFunctionDefinition *coordTranslationFunctionDecl = + traverser.getCoordTranslationFunctionDecl(); + TIntermFunctionDefinition *coordTranslationFunctionDeclImplicit = + traverser.getCoordTranslationFunctionDeclImplicit(); + size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root); + if (coordTranslationFunctionDecl) + { + root->insertChildNodes(firstFunctionIndex, TIntermSequence({coordTranslationFunctionDecl})); + } + if (coordTranslationFunctionDeclImplicit) + { + root->insertChildNodes(firstFunctionIndex, + TIntermSequence({coordTranslationFunctionDeclImplicit})); + } + + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h new file mode 100644 index 0000000000..515c1de103 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h @@ -0,0 +1,29 @@ +// +// Copyright 2019 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. +// +// RewriteCubeMapSamplersAs2DArray: Change samplerCube samplers to sampler2DArray, and the +// textureCube* function calls to calls to helper functions that select the cube map face and sample +// from the face as a 2D texture. This emulates seamful cube map sampling in ES2 (or desktop GL 2) +// and therefore only looks at samplerCube (i.e. not integer variants or cube arrays) and sampling +// functions that are defined in ES GLSL 1.0 (i.e. not the cube overload of texture()). + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITECUBEMAPSAMPLERSAS2DARRAY_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITECUBEMAPSAMPLERSAS2DARRAY_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool RewriteCubeMapSamplersAs2DArray(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + bool isFragmentShader); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITECUBEMAPSAMPLERSAS2DARRAY_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp new file mode 100644 index 0000000000..1a2aabf7dc --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp @@ -0,0 +1,141 @@ +// +// Copyright 2019 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. +// +// Implementation of dFdy viewport transformation. +// See header for more info. + +#include "compiler/translator/tree_ops/RewriteDfdy.h" + +#include "common/angleutils.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/TranslatorVulkan.h" +#include "compiler/translator/tree_util/DriverUniform.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/SpecializationConstant.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + Traverser(TSymbolTable *symbolTable, SpecConst *specConst, const DriverUniform *driverUniforms); + + private: + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + + SpecConst *mSpecConst = nullptr; + const DriverUniform *mDriverUniforms = nullptr; +}; + +Traverser::Traverser(TSymbolTable *symbolTable, + SpecConst *specConst, + const DriverUniform *driverUniforms) + : TIntermTraverser(true, false, false, symbolTable), + mSpecConst(specConst), + mDriverUniforms(driverUniforms) +{} + +bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + // Decide if the node represents a call to dFdx() or dFdy() + if (node->getOp() != EOpDFdx && node->getOp() != EOpDFdy) + { + return true; + } + + const bool isDFdx = node->getOp() == EOpDFdx; + + // Two transformations are done on dFdx and dFdy: + // + // - If pre-rotation is applied, dFdx and dFdy may need to swap their axis based on the degree + // of rotation. dFdx becomes dFdy if rotation is 90 or 270 degrees. Similarly, dFdy becomes + // dFdx. + // - The result is potentially negated. This could be due to viewport y-flip or pre-rotation. + // + // Accordingly, there are two variables controlling the above transformations: + // + // - Rotation: A vec2 that is either (0, 1) or (1, 0). dFdx and dFdy are replaced with: + // + // dFdx * Rotation.x + dFdy * Rotation.y + // + // - Scale: A vec2 with -1 or 1 for either x or y components. The previous result is multiplied + // by this. + // + // Together, the above operations account for the combinations of 4 possible rotations and + // y-flip. + + // Get the results of dFdx(operand) and dFdy(operand), and multiply them by the swizzles + TIntermTyped *operand = node->getChildNode(0)->getAsTyped(); + + TIntermTyped *dFdx = CreateBuiltInUnaryFunctionCallNode("dFdx", operand, *mSymbolTable, 300); + TIntermTyped *dFdy = + CreateBuiltInUnaryFunctionCallNode("dFdy", operand->deepCopy(), *mSymbolTable, 300); + + // Get rotation multiplier + TIntermTyped *swapXY = mSpecConst->getSwapXY(); + if (swapXY == nullptr) + { + swapXY = mDriverUniforms->getSwapXY(); + } + + TIntermTyped *swapXMultiplier = MakeSwapXMultiplier(swapXY); + TIntermTyped *swapYMultiplier = MakeSwapYMultiplier(swapXY->deepCopy()); + + // Get flip multiplier + TIntermTyped *flipXY = mDriverUniforms->getFlipXY(mSymbolTable, DriverUniformFlip::Fragment); + + // Multiply the flip and rotation multipliers + TIntermTyped *xMultiplier = + new TIntermBinary(EOpMul, isDFdx ? swapXMultiplier : swapYMultiplier, + (new TIntermSwizzle(flipXY->deepCopy(), {0}))->fold(nullptr)); + TIntermTyped *yMultiplier = + new TIntermBinary(EOpMul, isDFdx ? swapYMultiplier : swapXMultiplier, + (new TIntermSwizzle(flipXY->deepCopy(), {1}))->fold(nullptr)); + + const TOperator mulOp = dFdx->getType().isVector() ? EOpVectorTimesScalar : EOpMul; + TIntermTyped *rotatedFlippedDfdx = new TIntermBinary(mulOp, dFdx, xMultiplier); + TIntermTyped *rotatedFlippedDfdy = new TIntermBinary(mulOp, dFdy, yMultiplier); + + // Sum them together into the result + TIntermBinary *rotatedFlippedResult = + new TIntermBinary(EOpAdd, rotatedFlippedDfdx, rotatedFlippedDfdy); + + // Replace the old dFdx() or dFdy() node with the new node that contains the corrected value + // + // Note the following bugs (anglebug.com/7346): + // + // - Side effects of operand are duplicated with the above + // - If the direct child of this node is itself dFdx/y, its queueReplacement will not be + // effective as the parent is also replaced. + queueReplacement(rotatedFlippedResult, OriginalNode::IS_DROPPED); + + return true; +} +} // anonymous namespace + +bool RewriteDfdy(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + int shaderVersion, + SpecConst *specConst, + const DriverUniform *driverUniforms) +{ + // dFdx/dFdy is only valid in GLSL 3.0 and later. + if (shaderVersion < 300) + { + return true; + } + + Traverser traverser(symbolTable, specConst, driverUniforms); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h new file mode 100644 index 0000000000..d1a6399696 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h @@ -0,0 +1,32 @@ +// +// Copyright 2019 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. +// +// RewriteDfdy: Transform dFdx and dFdy according to pre-rotation and viewport y-flip. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITEDFDY_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITEDFDY_H_ + +#include "common/angleutils.h" +#include "compiler/translator/Compiler.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; +class SpecConst; +class DriverUniform; + +[[nodiscard]] bool RewriteDfdy(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + int shaderVersion, + SpecConst *specConst, + const DriverUniform *driverUniforms); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEDFDY_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp new file mode 100644 index 0000000000..1c86cafe03 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp @@ -0,0 +1,861 @@ +// +// Copyright 2022 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/RewritePixelLocalStorage.h" + +#include "common/angleutils.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h" +#include "compiler/translator/tree_util/BuiltIn.h" +#include "compiler/translator/tree_util/FindMain.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ +namespace +{ +constexpr static TBasicType DataTypeOfPLSType(TBasicType plsType) +{ + switch (plsType) + { + case EbtPixelLocalANGLE: + return EbtFloat; + case EbtIPixelLocalANGLE: + return EbtInt; + case EbtUPixelLocalANGLE: + return EbtUInt; + default: + UNREACHABLE(); + return EbtVoid; + } +} + +constexpr static TBasicType DataTypeOfImageType(TBasicType imageType) +{ + switch (imageType) + { + case EbtImage2D: + return EbtFloat; + case EbtIImage2D: + return EbtInt; + case EbtUImage2D: + return EbtUInt; + default: + UNREACHABLE(); + return EbtVoid; + } +} + +// Maps PLS symbols to a backing store. +template <typename T> +class PLSBackingStoreMap +{ + public: + // Sets the given variable as the backing storage for the plsSymbol's binding point. An entry + // must not already exist in the map for this binding point. + void insertNew(TIntermSymbol *plsSymbol, const T &backingStore) + { + ASSERT(plsSymbol); + ASSERT(IsPixelLocal(plsSymbol->getBasicType())); + int binding = plsSymbol->getType().getLayoutQualifier().binding; + ASSERT(binding >= 0); + auto result = mMap.insert({binding, backingStore}); + ASSERT(result.second); // Ensure an image didn't already exist for this symbol. + } + + // Looks up the backing store for the given plsSymbol's binding point. An entry must already + // exist in the map for this binding point. + const T &find(TIntermSymbol *plsSymbol) + { + ASSERT(plsSymbol); + ASSERT(IsPixelLocal(plsSymbol->getBasicType())); + int binding = plsSymbol->getType().getLayoutQualifier().binding; + ASSERT(binding >= 0); + auto iter = mMap.find(binding); + ASSERT(iter != mMap.end()); // Ensure PLSImages already exist for this symbol. + return iter->second; + } + + const std::map<int, T> &bindingOrderedMap() const { return mMap; } + + private: + // Use std::map so the backing stores are ordered by binding when we iterate. + std::map<int, T> mMap; +}; + +// Base class for rewriting high level PLS operations to AST operations specified by +// ShPixelLocalStorageType. +class RewritePLSTraverser : public TIntermTraverser +{ + public: + RewritePLSTraverser(TCompiler *compiler, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + int shaderVersion) + : TIntermTraverser(true, false, false, &symbolTable), + mCompiler(compiler), + mCompileOptions(&compileOptions), + mShaderVersion(shaderVersion) + {} + + bool visitDeclaration(Visit, TIntermDeclaration *decl) override + { + TIntermTyped *declVariable = (decl->getSequence())->front()->getAsTyped(); + ASSERT(declVariable); + + if (!IsPixelLocal(declVariable->getBasicType())) + { + return true; + } + + // PLS is not allowed in arrays. + ASSERT(!declVariable->isArray()); + + // This visitDeclaration doesn't get called for function arguments, and opaque types can + // otherwise only be uniforms. + ASSERT(declVariable->getQualifier() == EvqUniform); + + TIntermSymbol *plsSymbol = declVariable->getAsSymbolNode(); + ASSERT(plsSymbol); + + visitPLSDeclaration(plsSymbol); + + return false; + } + + bool visitAggregate(Visit, TIntermAggregate *aggregate) override + { + if (!BuiltInGroup::IsPixelLocal(aggregate->getOp())) + { + return true; + } + + const TIntermSequence &args = *aggregate->getSequence(); + ASSERT(args.size() >= 1); + TIntermSymbol *plsSymbol = args[0]->getAsSymbolNode(); + + // Rewrite pixelLocalLoadANGLE -> imageLoad. + if (aggregate->getOp() == EOpPixelLocalLoadANGLE) + { + visitPLSLoad(plsSymbol); + return false; // No need to recurse since this node is being dropped. + } + + // Rewrite pixelLocalStoreANGLE -> imageStore. + if (aggregate->getOp() == EOpPixelLocalStoreANGLE) + { + // Also hoist the 'value' expression into a temp. In the event of + // "pixelLocalStoreANGLE(..., pixelLocalLoadANGLE(...))", this ensures the load occurs + // _before_ any potential barriers required by the subclass. + // + // NOTE: It is generally unsafe to hoist function arguments due to short circuiting, + // e.g., "if (false && function(...))", but pixelLocalStoreANGLE returns type void, so + // it is safe in this particular case. + TType *valueType = new TType(DataTypeOfPLSType(plsSymbol->getBasicType()), + plsSymbol->getPrecision(), EvqTemporary, 4); + TVariable *valueVar = CreateTempVariable(mSymbolTable, valueType); + TIntermDeclaration *valueDecl = + CreateTempInitDeclarationNode(valueVar, args[1]->getAsTyped()); + valueDecl->traverse(this); // Rewrite any potential pixelLocalLoadANGLEs in valueDecl. + insertStatementInParentBlock(valueDecl); + + visitPLSStore(plsSymbol, valueVar); + return false; // No need to recurse since this node is being dropped. + } + + return true; + } + + // Called after rewrite. Injects one-time setup code that needs to run before any PLS accesses. + virtual void injectSetupCode(TCompiler *, + TSymbolTable &, + const ShCompileOptions &, + TIntermBlock *mainBody, + size_t plsBeginPosition) + {} + + // Called after rewrite. Injects one-time finalization code that needs to run after all PLS. + virtual void injectFinalizeCode(TCompiler *, + TSymbolTable &, + const ShCompileOptions &, + TIntermBlock *mainBody, + size_t plsEndPosition) + {} + + TVariable *globalPixelCoord() const { return mGlobalPixelCoord; } + + protected: + virtual void visitPLSDeclaration(TIntermSymbol *plsSymbol) = 0; + virtual void visitPLSLoad(TIntermSymbol *plsSymbol) = 0; + virtual void visitPLSStore(TIntermSymbol *plsSymbol, TVariable *value) = 0; + + void ensureGlobalPixelCoordDeclared() + { + // Insert a global to hold the pixel coordinate as soon as we see PLS declared. This will be + // initialized at the beginning of main(). + if (!mGlobalPixelCoord) + { + TType *coordType = new TType(EbtInt, EbpHigh, EvqGlobal, 2); + mGlobalPixelCoord = CreateTempVariable(mSymbolTable, coordType); + insertStatementInParentBlock(CreateTempDeclarationNode(mGlobalPixelCoord)); + } + } + + const TCompiler *const mCompiler; + const ShCompileOptions *const mCompileOptions; + const int mShaderVersion; + + // Stores the shader invocation's pixel coordinate as "ivec2(floor(gl_FragCoord.xy))". + TVariable *mGlobalPixelCoord = nullptr; +}; + +// Rewrites high level PLS operations to shader image operations. +class RewritePLSToImagesTraverser : public RewritePLSTraverser +{ + public: + RewritePLSToImagesTraverser(TCompiler *compiler, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + int shaderVersion) + : RewritePLSTraverser(compiler, symbolTable, compileOptions, shaderVersion) + {} + + private: + void visitPLSDeclaration(TIntermSymbol *plsSymbol) override + { + // Replace the PLS declaration with an image2D. + ensureGlobalPixelCoordDeclared(); + TVariable *image2D = createPLSImageReplacement(plsSymbol); + mImages.insertNew(plsSymbol, image2D); + queueReplacement(new TIntermDeclaration({new TIntermSymbol(image2D)}), + OriginalNode::IS_DROPPED); + } + + // Do all PLS formats need to be packed into r32f, r32i, or r32ui image2Ds? + bool needsR32Packing() const + { + return mCompileOptions->pls.type == ShPixelLocalStorageType::ImageStoreR32PackedFormats; + } + + // Creates an image2D that replaces a pixel local storage handle. + TVariable *createPLSImageReplacement(const TIntermSymbol *plsSymbol) + { + ASSERT(plsSymbol); + ASSERT(IsPixelLocal(plsSymbol->getBasicType())); + + TType *imageType = new TType(plsSymbol->getType()); + + TLayoutQualifier layoutQualifier = imageType->getLayoutQualifier(); + switch (layoutQualifier.imageInternalFormat) + { + case TLayoutImageInternalFormat::EiifRGBA8: + if (needsR32Packing()) + { + layoutQualifier.imageInternalFormat = EiifR32UI; + imageType->setPrecision(EbpHigh); + imageType->setBasicType(EbtUImage2D); + } + else + { + imageType->setBasicType(EbtImage2D); + } + break; + case TLayoutImageInternalFormat::EiifRGBA8I: + if (needsR32Packing()) + { + layoutQualifier.imageInternalFormat = EiifR32I; + imageType->setPrecision(EbpHigh); + } + imageType->setBasicType(EbtIImage2D); + break; + case TLayoutImageInternalFormat::EiifRGBA8UI: + if (needsR32Packing()) + { + layoutQualifier.imageInternalFormat = EiifR32UI; + imageType->setPrecision(EbpHigh); + } + imageType->setBasicType(EbtUImage2D); + break; + case TLayoutImageInternalFormat::EiifR32F: + imageType->setBasicType(EbtImage2D); + break; + case TLayoutImageInternalFormat::EiifR32UI: + imageType->setBasicType(EbtUImage2D); + break; + default: + UNREACHABLE(); + } + layoutQualifier.rasterOrdered = mCompileOptions->pls.fragmentSynchronizationType == + ShFragmentSynchronizationType::RasterizerOrderViews_D3D; + imageType->setLayoutQualifier(layoutQualifier); + + TMemoryQualifier memoryQualifier{}; + memoryQualifier.coherent = true; + memoryQualifier.restrictQualifier = true; + memoryQualifier.volatileQualifier = false; + // TODO(anglebug.com/7279): Maybe we could walk the tree first and see which PLS is used + // how. If the PLS is never loaded, we could add a writeonly qualifier, for example. + memoryQualifier.readonly = false; + memoryQualifier.writeonly = false; + imageType->setMemoryQualifier(memoryQualifier); + + const TVariable &plsVar = plsSymbol->variable(); + return new TVariable(plsVar.uniqueId(), plsVar.name(), plsVar.symbolType(), + plsVar.extensions(), imageType); + } + + void visitPLSLoad(TIntermSymbol *plsSymbol) override + { + // Replace the pixelLocalLoadANGLE with imageLoad. + TVariable *image2D = mImages.find(plsSymbol); + ASSERT(mGlobalPixelCoord); + TIntermTyped *pls = CreateBuiltInFunctionCallNode( + "imageLoad", {new TIntermSymbol(image2D), new TIntermSymbol(mGlobalPixelCoord)}, + *mSymbolTable, 310); + pls = unpackImageDataIfNecessary(pls, plsSymbol, image2D); + queueReplacement(pls, OriginalNode::IS_DROPPED); + } + + // Unpacks the raw PLS data if the output shader language needs r32* packing. + TIntermTyped *unpackImageDataIfNecessary(TIntermTyped *data, + TIntermSymbol *plsSymbol, + TVariable *image2D) + { + TLayoutImageInternalFormat plsFormat = + plsSymbol->getType().getLayoutQualifier().imageInternalFormat; + TLayoutImageInternalFormat imageFormat = + image2D->getType().getLayoutQualifier().imageInternalFormat; + if (plsFormat == imageFormat) + { + return data; // This PLS storage isn't packed. + } + ASSERT(needsR32Packing()); + switch (plsFormat) + { + case EiifRGBA8: + // Unpack and normalize r,g,b,a from a single 32-bit unsigned int: + // + // unpackUnorm4x8(data.r) + // + data = CreateBuiltInFunctionCallNode("unpackUnorm4x8", {CreateSwizzle(data, 0)}, + *mSymbolTable, 310); + break; + case EiifRGBA8I: + case EiifRGBA8UI: + { + constexpr unsigned shifts[] = {24, 16, 8, 0}; + // Unpack r,g,b,a form a single (signed or unsigned) 32-bit int. Shift left, + // then right, to preserve the sign for ints. (highp integers are exactly + // 32-bit, two's compliment.) + // + // data.rrrr << uvec4(24, 16, 8, 0) >> 24u + // + data = CreateSwizzle(data, 0, 0, 0, 0); + data = new TIntermBinary(EOpBitShiftLeft, data, CreateUVecNode(shifts, 4, EbpHigh)); + data = new TIntermBinary(EOpBitShiftRight, data, CreateUIntNode(24)); + break; + } + default: + UNREACHABLE(); + } + return data; + } + + void visitPLSStore(TIntermSymbol *plsSymbol, TVariable *value) override + { + TVariable *image2D = mImages.find(plsSymbol); + TIntermTyped *packedData = clampAndPackPLSDataIfNecessary(value, plsSymbol, image2D); + + // Surround the store with memoryBarrierImage calls in order to ensure dependent stores and + // loads in a single shader invocation are coherent. From the ES 3.1 spec: + // + // Using variables declared as "coherent" guarantees only that the results of stores will + // be immediately visible to shader invocations using similarly-declared variables; + // calling MemoryBarrier is required to ensure that the stores are visible to other + // operations. + // + insertStatementsInParentBlock( + {CreateBuiltInFunctionCallNode("memoryBarrierImage", {}, *mSymbolTable, + 310)}, // Before. + {CreateBuiltInFunctionCallNode("memoryBarrierImage", {}, *mSymbolTable, + 310)}); // After. + + // Rewrite the pixelLocalStoreANGLE with imageStore. + ASSERT(mGlobalPixelCoord); + queueReplacement( + CreateBuiltInFunctionCallNode( + "imageStore", + {new TIntermSymbol(image2D), new TIntermSymbol(mGlobalPixelCoord), packedData}, + *mSymbolTable, 310), + OriginalNode::IS_DROPPED); + } + + // Packs the PLS to raw data if the output shader language needs r32* packing. + TIntermTyped *clampAndPackPLSDataIfNecessary(TVariable *plsVar, + TIntermSymbol *plsSymbol, + TVariable *image2D) + { + TLayoutImageInternalFormat plsFormat = + plsSymbol->getType().getLayoutQualifier().imageInternalFormat; + // anglebug.com/7524: Storing to integer formats with values larger than can be represented + // is specified differently on different APIs. Clamp integer formats here to make it uniform + // and more GL-like. + switch (plsFormat) + { + case EiifRGBA8I: + { + // Clamp r,g,b,a to their min/max 8-bit values: + // + // plsVar = clamp(plsVar, -128, 127) & 0xff + // + TIntermTyped *newPLSValue = CreateBuiltInFunctionCallNode( + "clamp", + {new TIntermSymbol(plsVar), CreateIndexNode(-128), CreateIndexNode(127)}, + *mSymbolTable, mShaderVersion); + insertStatementInParentBlock(CreateTempAssignmentNode(plsVar, newPLSValue)); + break; + } + case EiifRGBA8UI: + { + // Clamp r,g,b,a to their max 8-bit values: + // + // plsVar = min(plsVar, 255) + // + TIntermTyped *newPLSValue = CreateBuiltInFunctionCallNode( + "min", {new TIntermSymbol(plsVar), CreateUIntNode(255)}, *mSymbolTable, + mShaderVersion); + insertStatementInParentBlock(CreateTempAssignmentNode(plsVar, newPLSValue)); + break; + } + default: + break; + } + TIntermTyped *result = new TIntermSymbol(plsVar); + TLayoutImageInternalFormat imageFormat = + image2D->getType().getLayoutQualifier().imageInternalFormat; + if (plsFormat == imageFormat) + { + return result; // This PLS storage isn't packed. + } + ASSERT(needsR32Packing()); + switch (plsFormat) + { + case EiifRGBA8: + { + if (mCompileOptions->passHighpToPackUnormSnormBuiltins) + { + // anglebug.com/7527: unpackUnorm4x8 doesn't work on Pixel 4 when passed + // a mediump vec4. Use an intermediate highp vec4. + // + // It's safe to inject a variable here because it happens right before + // pixelLocalStoreANGLE, which returns type void. (See visitAggregate.) + TType *highpType = new TType(EbtFloat, EbpHigh, EvqTemporary, 4); + TVariable *workaroundHighpVar = CreateTempVariable(mSymbolTable, highpType); + insertStatementInParentBlock( + CreateTempInitDeclarationNode(workaroundHighpVar, result)); + result = new TIntermSymbol(workaroundHighpVar); + } + + // Denormalize and pack r,g,b,a into a single 32-bit unsigned int: + // + // packUnorm4x8(workaroundHighpVar) + // + result = + CreateBuiltInFunctionCallNode("packUnorm4x8", {result}, *mSymbolTable, 310); + break; + } + case EiifRGBA8I: + case EiifRGBA8UI: + { + if (plsFormat == EiifRGBA8I) + { + // Mask off extra sign bits beyond 8. + // + // plsVar &= 0xff + // + insertStatementInParentBlock(new TIntermBinary( + EOpBitwiseAndAssign, new TIntermSymbol(plsVar), CreateIndexNode(0xff))); + } + // Pack r,g,b,a into a single 32-bit (signed or unsigned) int: + // + // r | (g << 8) | (b << 16) | (a << 24) + // + auto shiftComponent = [=](int componentIdx) { + return new TIntermBinary(EOpBitShiftLeft, + CreateSwizzle(new TIntermSymbol(plsVar), componentIdx), + CreateUIntNode(componentIdx * 8)); + }; + result = CreateSwizzle(result, 0); + result = new TIntermBinary(EOpBitwiseOr, result, shiftComponent(1)); + result = new TIntermBinary(EOpBitwiseOr, result, shiftComponent(2)); + result = new TIntermBinary(EOpBitwiseOr, result, shiftComponent(3)); + break; + } + default: + UNREACHABLE(); + } + // Convert the packed data to a {u,i}vec4 for imageStore. + TType imageStoreType(DataTypeOfImageType(image2D->getType().getBasicType()), 4); + return TIntermAggregate::CreateConstructor(imageStoreType, {result}); + } + + void injectSetupCode(TCompiler *compiler, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + TIntermBlock *mainBody, + size_t plsBeginPosition) override + { + // When PLS is implemented with images, early_fragment_tests ensure that depth/stencil + // can also block stores to PLS. + compiler->specifyEarlyFragmentTests(); + + // Delimit the beginning of a per-pixel critical section, if supported. This makes pixel + // local storage coherent. + // + // Either: GL_NV_fragment_shader_interlock + // GL_INTEL_fragment_shader_ordering + // GL_ARB_fragment_shader_interlock (may compile to + // SPV_EXT_fragment_shader_interlock) + switch (compileOptions.pls.fragmentSynchronizationType) + { + // ROVs don't need explicit synchronization calls. + case ShFragmentSynchronizationType::RasterizerOrderViews_D3D: + case ShFragmentSynchronizationType::NotSupported: + break; + case ShFragmentSynchronizationType::FragmentShaderInterlock_NV_GL: + mainBody->insertStatement( + plsBeginPosition, + CreateBuiltInFunctionCallNode("beginInvocationInterlockNV", {}, symbolTable, + kESSLInternalBackendBuiltIns)); + break; + case ShFragmentSynchronizationType::FragmentShaderOrdering_INTEL_GL: + mainBody->insertStatement( + plsBeginPosition, + CreateBuiltInFunctionCallNode("beginFragmentShaderOrderingINTEL", {}, + symbolTable, kESSLInternalBackendBuiltIns)); + break; + case ShFragmentSynchronizationType::FragmentShaderInterlock_ARB_GL: + mainBody->insertStatement( + plsBeginPosition, + CreateBuiltInFunctionCallNode("beginInvocationInterlockARB", {}, symbolTable, + kESSLInternalBackendBuiltIns)); + break; + default: + UNREACHABLE(); + } + } + + void injectFinalizeCode(TCompiler *, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + TIntermBlock *mainBody, + size_t plsEndPosition) override + { + // Delimit the end of the PLS critical section, if required. + // + // Either: GL_NV_fragment_shader_interlock + // GL_ARB_fragment_shader_interlock (may compile to + // SPV_EXT_fragment_shader_interlock) + switch (compileOptions.pls.fragmentSynchronizationType) + { + // ROVs don't need explicit synchronization calls. + case ShFragmentSynchronizationType::RasterizerOrderViews_D3D: + // GL_INTEL_fragment_shader_ordering doesn't have an "end()" call. + case ShFragmentSynchronizationType::FragmentShaderOrdering_INTEL_GL: + case ShFragmentSynchronizationType::NotSupported: + break; + case ShFragmentSynchronizationType::FragmentShaderInterlock_NV_GL: + + mainBody->insertStatement( + plsEndPosition, + CreateBuiltInFunctionCallNode("endInvocationInterlockNV", {}, symbolTable, + kESSLInternalBackendBuiltIns)); + break; + case ShFragmentSynchronizationType::FragmentShaderInterlock_ARB_GL: + mainBody->insertStatement( + plsEndPosition, + CreateBuiltInFunctionCallNode("endInvocationInterlockARB", {}, symbolTable, + kESSLInternalBackendBuiltIns)); + break; + default: + UNREACHABLE(); + } + } + + PLSBackingStoreMap<TVariable *> mImages; +}; + +// Rewrites high level PLS operations to framebuffer fetch operations. +class RewritePLSToFramebufferFetchTraverser : public RewritePLSTraverser +{ + public: + RewritePLSToFramebufferFetchTraverser(TCompiler *compiler, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + int shaderVersion) + : RewritePLSTraverser(compiler, symbolTable, compileOptions, shaderVersion) + {} + + void visitPLSDeclaration(TIntermSymbol *plsSymbol) override + { + // Replace the PLS declaration with a framebuffer attachment. + PLSAttachment attachment(mCompiler, mSymbolTable, *mCompileOptions, plsSymbol->variable()); + mPLSAttachments.insertNew(plsSymbol, attachment); + insertStatementInParentBlock( + new TIntermDeclaration({new TIntermSymbol(attachment.fragmentVar)})); + queueReplacement(CreateTempDeclarationNode(attachment.accessVar), OriginalNode::IS_DROPPED); + } + + void visitPLSLoad(TIntermSymbol *plsSymbol) override + { + // Read our temporary accessVar. + const PLSAttachment &attachment = mPLSAttachments.find(plsSymbol); + queueReplacement(attachment.expandAccessVar(), OriginalNode::IS_DROPPED); + } + + void visitPLSStore(TIntermSymbol *plsSymbol, TVariable *value) override + { + // Set our temporary accessVar. + const PLSAttachment &attachment = mPLSAttachments.find(plsSymbol); + queueReplacement(CreateTempAssignmentNode(attachment.accessVar, attachment.swizzle(value)), + OriginalNode::IS_DROPPED); + } + + void injectSetupCode(TCompiler *compiler, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + TIntermBlock *mainBody, + size_t plsBeginPosition) override + { + // [OpenGL ES Version 3.0.6, 3.9.2.3 "Shader Output"]: Any colors, or color components, + // associated with a fragment that are not written by the fragment shader are undefined. + // + // [EXT_shader_framebuffer_fetch]: Prior to fragment shading, fragment outputs declared + // inout are populated with the value last written to the framebuffer at the same(x, y, + // sample) position. + // + // It's unclear from the EXT_shader_framebuffer_fetch spec whether inout fragment variables + // become undefined if not explicitly written, but either way, when this compiles to subpass + // loads in Vulkan, we definitely get undefined behavior if PLS variables are not written. + // + // To make sure every PLS variable gets written, we read them all before PLS operations, + // then write them all back out after all PLS is complete. + std::vector<TIntermNode *> plsPreloads; + plsPreloads.reserve(mPLSAttachments.bindingOrderedMap().size()); + for (const auto &entry : mPLSAttachments.bindingOrderedMap()) + { + const PLSAttachment &attachment = entry.second; + plsPreloads.push_back( + CreateTempAssignmentNode(attachment.accessVar, attachment.swizzleFragmentVar())); + } + mainBody->getSequence()->insert(mainBody->getSequence()->begin() + plsBeginPosition, + plsPreloads.begin(), plsPreloads.end()); + } + + void injectFinalizeCode(TCompiler *, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + TIntermBlock *mainBody, + size_t plsEndPosition) override + { + std::vector<TIntermNode *> plsWrites; + plsWrites.reserve(mPLSAttachments.bindingOrderedMap().size()); + for (const auto &entry : mPLSAttachments.bindingOrderedMap()) + { + const PLSAttachment &attachment = entry.second; + plsWrites.push_back(new TIntermBinary(EOpAssign, attachment.swizzleFragmentVar(), + new TIntermSymbol(attachment.accessVar))); + } + mainBody->getSequence()->insert(mainBody->getSequence()->begin() + plsEndPosition, + plsWrites.begin(), plsWrites.end()); + } + + private: + struct PLSAttachment + { + PLSAttachment(const TCompiler *compiler, + TSymbolTable *symbolTable, + const ShCompileOptions &compileOptions, + const TVariable &plsVar) + { + const TType &plsType = plsVar.getType(); + + TType *accessVarType; + switch (plsType.getLayoutQualifier().imageInternalFormat) + { + default: + UNREACHABLE(); + [[fallthrough]]; + case EiifRGBA8: + accessVarType = new TType(EbtFloat, 4); + break; + case EiifRGBA8I: + accessVarType = new TType(EbtInt, 4); + break; + case EiifRGBA8UI: + accessVarType = new TType(EbtUInt, 4); + break; + case EiifR32F: + accessVarType = new TType(EbtFloat, 1); + break; + case EiifR32UI: + accessVarType = new TType(EbtUInt, 1); + break; + } + accessVarType->setPrecision(plsType.getPrecision()); + accessVar = CreateTempVariable(symbolTable, accessVarType); + + // Qualcomm seems to want fragment outputs to be 4-component vectors, and produces a + // compile error from "inout uint". Our Metal translator also saturates color outputs to + // 4 components. And since the spec also seems silent on how many components an output + // must have, we always use 4. + TType *fragmentVarType = new TType(accessVarType->getBasicType(), 4); + fragmentVarType->setPrecision(plsType.getPrecision()); + fragmentVarType->setQualifier(EvqFragmentInOut); + + // PLS attachments are bound in reverse order from the rear. + TLayoutQualifier layoutQualifier = TLayoutQualifier::Create(); + layoutQualifier.location = + compiler->getResources().MaxCombinedDrawBuffersAndPixelLocalStoragePlanes - + plsType.getLayoutQualifier().binding - 1; + layoutQualifier.locationsSpecified = 1; + if (compileOptions.pls.fragmentSynchronizationType == + ShFragmentSynchronizationType::NotSupported) + { + // We're using EXT_shader_framebuffer_fetch_non_coherent, which requires the + // "noncoherent" qualifier. + layoutQualifier.noncoherent = true; + } + fragmentVarType->setLayoutQualifier(layoutQualifier); + + fragmentVar = new TVariable(plsVar.uniqueId(), plsVar.name(), plsVar.symbolType(), + plsVar.extensions(), fragmentVarType); + } + + // Expands our accessVar to 4 components, regardless of the size of the pixel local storage + // internalformat. + TIntermTyped *expandAccessVar() const + { + TIntermTyped *expanded = new TIntermSymbol(accessVar); + if (accessVar->getType().getNominalSize() == 1) + { + switch (accessVar->getType().getBasicType()) + { + case EbtFloat: + expanded = TIntermAggregate::CreateConstructor( // "vec4(r, 0, 0, 1)" + TType(EbtFloat, 4), + {expanded, CreateFloatNode(0, EbpHigh), CreateFloatNode(0, EbpHigh), + CreateFloatNode(1, EbpHigh)}); + break; + case EbtUInt: + expanded = TIntermAggregate::CreateConstructor( // "uvec4(r, 0, 0, 1)" + TType(EbtUInt, 4), + {expanded, CreateUIntNode(0), CreateUIntNode(0), CreateUIntNode(1)}); + break; + default: + UNREACHABLE(); + break; + } + } + return expanded; + } + + // Swizzles a variable down to the same number of components as the PLS internalformat. + TIntermTyped *swizzle(TVariable *var) const + { + TIntermTyped *swizzled = new TIntermSymbol(var); + if (var->getType().getNominalSize() != accessVar->getType().getNominalSize()) + { + ASSERT(var->getType().getNominalSize() > accessVar->getType().getNominalSize()); + TVector swizzleOffsets{0, 1, 2, 3}; + swizzleOffsets.resize(accessVar->getType().getNominalSize()); + swizzled = new TIntermSwizzle(swizzled, swizzleOffsets); + } + return swizzled; + } + + TIntermTyped *swizzleFragmentVar() const { return swizzle(fragmentVar); } + + TVariable *fragmentVar; + TVariable *accessVar; + }; + + PLSBackingStoreMap<PLSAttachment> mPLSAttachments; +}; +} // anonymous namespace + +bool RewritePixelLocalStorage(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + int shaderVersion) +{ + // If any functions take PLS arguments, monomorphize the functions by removing said parameters + // and making the PLS calls from main() instead, using the global uniform from the call site + // instead of the function argument. This is necessary because function arguments don't carry + // the necessary "binding" or "format" layout qualifiers. + if (!MonomorphizeUnsupportedFunctions( + compiler, root, &symbolTable, compileOptions, + UnsupportedFunctionArgsBitSet{UnsupportedFunctionArgs::PixelLocalStorage})) + { + return false; + } + + TIntermBlock *mainBody = FindMainBody(root); + + std::unique_ptr<RewritePLSTraverser> traverser; + switch (compileOptions.pls.type) + { + case ShPixelLocalStorageType::ImageStoreR32PackedFormats: + case ShPixelLocalStorageType::ImageStoreNativeFormats: + traverser = std::make_unique<RewritePLSToImagesTraverser>( + compiler, symbolTable, compileOptions, shaderVersion); + break; + case ShPixelLocalStorageType::FramebufferFetch: + traverser = std::make_unique<RewritePLSToFramebufferFetchTraverser>( + compiler, symbolTable, compileOptions, shaderVersion); + break; + default: + UNREACHABLE(); + return false; + } + + // Rewrite PLS operations to image operations. + root->traverse(traverser.get()); + if (!traverser->updateTree(compiler, root)) + { + return false; + } + + // Inject the code that needs to run before and after all PLS operations. + // TODO(anglebug.com/7279): Inject these functions in a tight critical section, instead of + // just locking the entire main() function: + // - Monomorphize all PLS calls into main(). + // - Insert begin/end calls around the first/last PLS calls (and outside of flow control). + traverser->injectSetupCode(compiler, symbolTable, compileOptions, mainBody, 0); + traverser->injectFinalizeCode(compiler, symbolTable, compileOptions, mainBody, + mainBody->getChildCount()); + + if (traverser->globalPixelCoord()) + { + // Initialize the global pixel coord at the beginning of main(): + // + // pixelCoord = ivec2(floor(gl_FragCoord.xy)); + // + TIntermTyped *exp; + exp = ReferenceBuiltInVariable(ImmutableString("gl_FragCoord"), symbolTable, shaderVersion); + exp = CreateSwizzle(exp, 0, 1); + exp = CreateBuiltInFunctionCallNode("floor", {exp}, symbolTable, shaderVersion); + exp = TIntermAggregate::CreateConstructor(TType(EbtInt, 2), {exp}); + exp = CreateTempAssignmentNode(traverser->globalPixelCoord(), exp); + mainBody->insertStatement(0, exp); + } + + return compiler->validateAST(root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h new file mode 100644 index 0000000000..9bcfcbb62e --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h @@ -0,0 +1,29 @@ +// +// Copyright 2022 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITE_PIXELLOCALSTORAGE_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITE_PIXELLOCALSTORAGE_H_ + +#include <GLSLANG/ShaderLang.h> + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +// This mutating tree traversal rewrites high level ANGLE_shader_pixel_local_storage operations to +// the type of shader operations specified by ShPixelLocalStorageType, found in ShCompileOptions. +[[nodiscard]] bool RewritePixelLocalStorage(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable &symbolTable, + const ShCompileOptions &compileOptions, + int shaderVersion); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITE_PIXELLOCALSTORAGE_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp new file mode 100644 index 0000000000..219a6b31fc --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp @@ -0,0 +1,673 @@ +// +// Copyright 2018 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. +// +// RewriteStructSamplers: Extract samplers from structs. +// + +#include "compiler/translator/tree_ops/RewriteStructSamplers.h" + +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ +namespace +{ + +// Used to map one structure type to another (one where the samplers are removed). +struct StructureData +{ + // The structure this was replaced with. If nullptr, it means the structure is removed (because + // it had all samplers). + const TStructure *modified; + // Indexed by the field index of original structure, to get the field index of the modified + // structure. For example: + // + // struct Original + // { + // sampler2D s1; + // vec4 f1; + // sampler2D s2; + // sampler2D s3; + // vec4 f2; + // }; + // + // struct Modified + // { + // vec4 f1; + // vec4 f2; + // }; + // + // fieldMap: + // 0 -> Invalid + // 1 -> 0 + // 2 -> Invalid + // 3 -> Invalid + // 4 -> 1 + // + TVector<int> fieldMap; +}; + +using StructureMap = angle::HashMap<const TStructure *, StructureData>; +using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>; +using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>; + +TIntermTyped *RewriteModifiedStructFieldSelectionExpression( + TCompiler *compiler, + TIntermBinary *node, + const StructureMap &structureMap, + const StructureUniformMap &structureUniformMap, + const ExtractedSamplerMap &extractedSamplers); + +TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler, + TIntermBinary *node, + const StructureMap &structureMap, + const StructureUniformMap &structureUniformMap, + const ExtractedSamplerMap &extractedSamplers) +{ + // Only interested in EOpIndexDirectStruct binary nodes. + if (node->getOp() != EOpIndexDirectStruct) + { + return nullptr; + } + + const TStructure *structure = node->getLeft()->getType().getStruct(); + ASSERT(structure); + + // If the result of the index is not a sampler and the struct is not replaced, there's nothing + // to do. + if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end()) + { + return nullptr; + } + + // Otherwise, replace the whole expression such that: + // + // - if sampler, it's indexed with whatever indices the parent structs were indexed with, + // - otherwise, the chain of field selections is rewritten by modifying the base uniform so all + // the intermediate nodes would have the correct type (and therefore fields). + ASSERT(structureMap.find(structure) != structureMap.end()); + + return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap, + structureUniformMap, extractedSamplers); +} + +// Given an expression, this traverser calculates a new expression where sampler-in-structs are +// replaced with their extracted ones, and field indices are adjusted for the rest of the fields. +// In particular, this is run on the right node of EOpIndexIndirect binary nodes, so that the +// expression in the index gets a chance to go through this transformation. +class RewriteExpressionTraverser final : public TIntermTraverser +{ + public: + explicit RewriteExpressionTraverser(TCompiler *compiler, + const StructureMap &structureMap, + const StructureUniformMap &structureUniformMap, + const ExtractedSamplerMap &extractedSamplers) + : TIntermTraverser(true, false, false), + mCompiler(compiler), + mStructureMap(structureMap), + mStructureUniformMap(structureUniformMap), + mExtractedSamplers(extractedSamplers) + {} + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper( + mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers); + + if (rewritten == nullptr) + { + return true; + } + + queueReplacement(rewritten, OriginalNode::IS_DROPPED); + + // Don't iterate as the expression is rewritten. + return false; + } + + void visitSymbol(TIntermSymbol *node) override + { + // It's impossible to reach here with a symbol that needs replacement. + // MonomorphizeUnsupportedFunctions makes sure that whole structs containing + // samplers are not passed to functions, so any instance of the struct uniform is + // necessarily indexed right away. visitBinary should have already taken care of it. + ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end()); + } + + private: + TCompiler *mCompiler; + + // See RewriteStructSamplersTraverser. + const StructureMap &mStructureMap; + const StructureUniformMap &mStructureUniformMap; + const ExtractedSamplerMap &mExtractedSamplers; +}; + +// Rewrite the index of an EOpIndexIndirect expression. The root can never need replacing, because +// it cannot be a sampler itself or of a struct type. +void RewriteIndexExpression(TCompiler *compiler, + TIntermTyped *expression, + const StructureMap &structureMap, + const StructureUniformMap &structureUniformMap, + const ExtractedSamplerMap &extractedSamplers) +{ + RewriteExpressionTraverser traverser(compiler, structureMap, structureUniformMap, + extractedSamplers); + expression->traverse(&traverser); + bool valid = traverser.updateTree(compiler, expression); + ASSERT(valid); +} + +// Given an expression such as the following: +// +// EOpIndexDirectStruct (sampler) +// / \ +// EOpIndex* field index +// / \ +// EOpIndexDirectStruct index 2 +// / \ +// EOpIndex* field index +// / \ +// EOpIndexDirectStruct index 1 +// / \ +// Uniform Struct field index +// +// produces: +// +// EOpIndex* +// / \ +// EOpIndex* index 2 +// / \ +// sampler index 1 +// +// Alternatively, if the expression is as such: +// +// EOpIndexDirectStruct +// / \ +// (modified struct type) EOpIndex* field index +// / \ +// EOpIndexDirectStruct index 2 +// / \ +// EOpIndex* field index +// / \ +// EOpIndexDirectStruct index 1 +// / \ +// Uniform Struct field index +// +// produces: +// +// EOpIndexDirectStruct +// / \ +// EOpIndex* mapped field index +// / \ +// EOpIndexDirectStruct index 2 +// / \ +// EOpIndex* mapped field index +// / \ +// EOpIndexDirectStruct index 1 +// / \ +// Uniform Struct mapped field index +// +TIntermTyped *RewriteModifiedStructFieldSelectionExpression( + TCompiler *compiler, + TIntermBinary *node, + const StructureMap &structureMap, + const StructureUniformMap &structureUniformMap, + const ExtractedSamplerMap &extractedSamplers) +{ + ASSERT(node->getOp() == EOpIndexDirectStruct); + + const bool isSampler = node->getType().isSampler(); + + TIntermSymbol *baseUniform = nullptr; + std::string samplerName; + + TVector<TIntermBinary *> indexNodeStack; + + // Iterate once and build the name of the sampler. + TIntermBinary *iter = node; + while (baseUniform == nullptr) + { + indexNodeStack.push_back(iter); + baseUniform = iter->getLeft()->getAsSymbolNode(); + + if (isSampler) + { + if (iter->getOp() == EOpIndexDirectStruct) + { + // When indexed into a struct, get the field name instead and construct the sampler + // name. + samplerName.insert(0, iter->getIndexStructFieldName().data()); + samplerName.insert(0, "_"); + } + + if (baseUniform) + { + // If left is a symbol, we have reached the end of the chain. Use the struct name + // to finish building the name of the sampler. + samplerName.insert(0, baseUniform->variable().name().data()); + } + } + + iter = iter->getLeft()->getAsBinaryNode(); + } + + TIntermTyped *rewritten = nullptr; + + if (isSampler) + { + ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end()); + rewritten = new TIntermSymbol(extractedSamplers.at(samplerName)); + } + else + { + const TVariable *baseUniformVar = &baseUniform->variable(); + ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end()); + rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar)); + } + + // Iterate again and build the expression from bottom up. + for (auto it = indexNodeStack.rbegin(); it != indexNodeStack.rend(); ++it) + { + TIntermBinary *indexNode = *it; + + switch (indexNode->getOp()) + { + case EOpIndexDirectStruct: + if (!isSampler) + { + // Remap the field. + const TStructure *structure = indexNode->getLeft()->getType().getStruct(); + ASSERT(structureMap.find(structure) != structureMap.end()); + + TIntermConstantUnion *asConstantUnion = + indexNode->getRight()->getAsConstantUnion(); + ASSERT(asConstantUnion); + + const int fieldIndex = asConstantUnion->getIConst(0); + ASSERT(fieldIndex < + static_cast<int>(structureMap.at(structure).fieldMap.size())); + + const int mappedFieldIndex = structureMap.at(structure).fieldMap[fieldIndex]; + + rewritten = new TIntermBinary(EOpIndexDirectStruct, rewritten, + CreateIndexNode(mappedFieldIndex)); + } + break; + + case EOpIndexDirect: + rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight()); + break; + + case EOpIndexIndirect: + { + // Run RewriteExpressionTraverser on the right node. It may itself be an expression + // with a sampler inside that needs to be rewritten, or simply use a field of a + // struct that's remapped. + TIntermTyped *indexExpression = indexNode->getRight(); + RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap, + extractedSamplers); + rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression); + break; + } + + default: + UNREACHABLE(); + break; + } + } + + return rewritten; +} + +class RewriteStructSamplersTraverser final : public TIntermTraverser +{ + public: + explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), + mCompiler(compiler), + mRemovedUniformsCount(0) + {} + + int removedUniformsCount() const { return mRemovedUniformsCount; } + + // Each struct sampler declaration is stripped of its samplers. New uniforms are added for each + // stripped struct sampler. + bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override + { + if (!mInGlobalScope) + { + return true; + } + + const TIntermSequence &sequence = *(decl->getSequence()); + TIntermTyped *declarator = sequence.front()->getAsTyped(); + const TType &type = declarator->getType(); + + if (!type.isStructureContainingSamplers()) + { + return false; + } + + TIntermSequence newSequence; + + if (type.isStructSpecifier()) + { + // If this is just a struct definition (not a uniform variable declaration of a + // struct type), just remove the samplers. They are not instantiated yet. + const TStructure *structure = type.getStruct(); + ASSERT(structure && mStructureMap.find(structure) == mStructureMap.end()); + + stripStructSpecifierSamplers(structure, &newSequence); + } + else + { + const TStructure *structure = type.getStruct(); + + // If the structure is defined at the same time, create the mapping to the stripped + // version first. + if (mStructureMap.find(structure) == mStructureMap.end()) + { + stripStructSpecifierSamplers(structure, &newSequence); + } + + // Then, extract the samplers from the struct and create global-scope variables instead. + TIntermSymbol *asSymbol = declarator->getAsSymbolNode(); + ASSERT(asSymbol); + const TVariable &variable = asSymbol->variable(); + ASSERT(variable.symbolType() != SymbolType::Empty); + + extractStructSamplerUniforms(variable, structure, &newSequence); + } + + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl, + std::move(newSequence)); + + return false; + } + + // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root. + bool visitBinary(Visit visit, TIntermBinary *node) override + { + TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper( + mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers); + + if (rewritten == nullptr) + { + return true; + } + + queueReplacement(rewritten, OriginalNode::IS_DROPPED); + + // Don't iterate as the expression is rewritten. + return false; + } + + // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root. + void visitSymbol(TIntermSymbol *node) override + { + ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end()); + } + + private: + // Removes all samplers from a struct specifier. + void stripStructSpecifierSamplers(const TStructure *structure, TIntermSequence *newSequence) + { + TFieldList *newFieldList = new TFieldList; + ASSERT(structure->containsSamplers()); + + // Add this struct to the struct map + ASSERT(mStructureMap.find(structure) == mStructureMap.end()); + StructureData *modifiedData = &mStructureMap[structure]; + + modifiedData->modified = nullptr; + modifiedData->fieldMap.resize(structure->fields().size(), std::numeric_limits<int>::max()); + + for (size_t fieldIndex = 0; fieldIndex < structure->fields().size(); ++fieldIndex) + { + const TField *field = structure->fields()[fieldIndex]; + const TType &fieldType = *field->type(); + + // If the field is a sampler, or a struct that's entirely removed, skip it. + if (!fieldType.isSampler() && !isRemovedStructType(fieldType)) + { + TType *newType = nullptr; + + // Otherwise, if it's a struct that's replaced, create a new field of the replaced + // type. + if (fieldType.isStructureContainingSamplers()) + { + const TStructure *fieldStruct = fieldType.getStruct(); + ASSERT(mStructureMap.find(fieldStruct) != mStructureMap.end()); + + const TStructure *modifiedStruct = mStructureMap[fieldStruct].modified; + ASSERT(modifiedStruct); + + newType = new TType(modifiedStruct, true); + if (fieldType.isArray()) + { + newType->makeArrays(fieldType.getArraySizes()); + } + } + else + { + // If not, duplicate the field as is. + newType = new TType(fieldType); + } + + // Record the mapping of the field indices, so future EOpIndexDirectStruct's into + // this struct can be fixed up. + modifiedData->fieldMap[fieldIndex] = static_cast<int>(newFieldList->size()); + + TField *newField = + new TField(newType, field->name(), field->line(), field->symbolType()); + newFieldList->push_back(newField); + } + } + + // Prune empty structs. + if (newFieldList->empty()) + { + return; + } + + // Declare a new struct with the same name and the new fields. + modifiedData->modified = + new TStructure(mSymbolTable, structure->name(), newFieldList, structure->symbolType()); + TType *newStructType = new TType(modifiedData->modified, true); + TVariable *newStructVar = + new TVariable(mSymbolTable, kEmptyImmutableString, newStructType, SymbolType::Empty); + TIntermSymbol *newStructRef = new TIntermSymbol(newStructVar); + + TIntermDeclaration *structDecl = new TIntermDeclaration; + structDecl->appendDeclarator(newStructRef); + + newSequence->push_back(structDecl); + } + + // Returns true if the type is a struct that was removed because we extracted all the members. + bool isRemovedStructType(const TType &type) const + { + const TStructure *structure = type.getStruct(); + if (structure == nullptr) + { + // Not a struct + return false; + } + + // A struct is removed if it is in the map, but doesn't have a replacement struct. + auto iter = mStructureMap.find(structure); + return iter != mStructureMap.end() && iter->second.modified == nullptr; + } + + // Removes samplers from struct uniforms. For each sampler removed also adds a new globally + // defined sampler uniform. + void extractStructSamplerUniforms(const TVariable &variable, + const TStructure *structure, + TIntermSequence *newSequence) + { + ASSERT(structure->containsSamplers()); + ASSERT(mStructureMap.find(structure) != mStructureMap.end()); + + const TType &type = variable.getType(); + enterArray(type); + + for (const TField *field : structure->fields()) + { + extractFieldSamplers(variable.name().data(), field, newSequence); + } + + // If there's a replacement structure (because there are non-sampler fields in the struct), + // add a declaration with that type. + const TStructure *modified = mStructureMap[structure].modified; + if (modified != nullptr) + { + TType *newType = new TType(modified, false); + if (type.isArray()) + { + newType->makeArrays(type.getArraySizes()); + } + newType->setQualifier(EvqUniform); + const TVariable *newVariable = + new TVariable(mSymbolTable, variable.name(), newType, variable.symbolType()); + + TIntermDeclaration *newDecl = new TIntermDeclaration(); + newDecl->appendDeclarator(new TIntermSymbol(newVariable)); + + newSequence->push_back(newDecl); + + ASSERT(mStructureUniformMap.find(&variable) == mStructureUniformMap.end()); + mStructureUniformMap[&variable] = newVariable; + } + else + { + mRemovedUniformsCount++; + } + + exitArray(type); + } + + // Extracts samplers from a field of a struct. Works with nested structs and arrays. + void extractFieldSamplers(const std::string &prefix, + const TField *field, + TIntermSequence *newSequence) + { + const TType &fieldType = *field->type(); + if (fieldType.isSampler() || fieldType.isStructureContainingSamplers()) + { + std::string newPrefix = prefix + "_" + field->name().data(); + + if (fieldType.isSampler()) + { + extractSampler(newPrefix, fieldType, newSequence); + } + else + { + enterArray(fieldType); + const TStructure *structure = fieldType.getStruct(); + for (const TField *nestedField : structure->fields()) + { + extractFieldSamplers(newPrefix, nestedField, newSequence); + } + exitArray(fieldType); + } + } + } + + void GenerateArraySizesFromStack(TVector<unsigned int> *sizesOut) + { + sizesOut->reserve(mArraySizeStack.size()); + + for (auto it = mArraySizeStack.rbegin(); it != mArraySizeStack.rend(); ++it) + { + sizesOut->push_back(*it); + } + } + + // Extracts a sampler from a struct. Declares the new extracted sampler. + void extractSampler(const std::string &newName, + const TType &fieldType, + TIntermSequence *newSequence) + { + ASSERT(fieldType.isSampler()); + + TType *newType = new TType(fieldType); + + // Add array dimensions accumulated so far due to struct arrays. Note that to support + // nested arrays, mArraySizeStack has the outermost size in the front. |makeArrays| thus + // expects this in reverse order. + TVector<unsigned int> parentArraySizes; + GenerateArraySizesFromStack(&parentArraySizes); + newType->makeArrays(parentArraySizes); + + ImmutableStringBuilder nameBuilder(newName.size() + 1); + nameBuilder << newName; + + newType->setQualifier(EvqUniform); + TVariable *newVariable = + new TVariable(mSymbolTable, nameBuilder, newType, SymbolType::AngleInternal); + TIntermSymbol *newSymbol = new TIntermSymbol(newVariable); + + TIntermDeclaration *samplerDecl = new TIntermDeclaration; + samplerDecl->appendDeclarator(newSymbol); + + newSequence->push_back(samplerDecl); + + // TODO: Use a temp name instead of generating a name as currently done. There is no + // guarantee that these generated names cannot clash. Create a mapping from the previous + // name to the name assigned to the temp variable so ShaderVariable::mappedName can be + // updated post-transformation. http://anglebug.com/4301 + ASSERT(mExtractedSamplers.find(newName) == mExtractedSamplers.end()); + mExtractedSamplers[newName] = newVariable; + } + + void enterArray(const TType &arrayType) + { + const TSpan<const unsigned int> &arraySizes = arrayType.getArraySizes(); + for (auto it = arraySizes.rbegin(); it != arraySizes.rend(); ++it) + { + unsigned int arraySize = *it; + mArraySizeStack.push_back(arraySize); + } + } + + void exitArray(const TType &arrayType) + { + mArraySizeStack.resize(mArraySizeStack.size() - arrayType.getNumArraySizes()); + } + + TCompiler *mCompiler; + int mRemovedUniformsCount; + + // Map structures with samplers to ones that have their samplers removed. + StructureMap mStructureMap; + + // Map uniform variables of structure type that are replaced with another variable. + StructureUniformMap mStructureUniformMap; + + // Map a constructed sampler name to its variable. Used to replace an expression that uses this + // sampler with the extracted one. + ExtractedSamplerMap mExtractedSamplers; + + // A stack of array sizes. Used to figure out the array dimensions of the extracted sampler, + // for example when it's nested in an array of structs in an array of structs. + TVector<unsigned int> mArraySizeStack; +}; +} // anonymous namespace + +bool RewriteStructSamplers(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + int *removedUniformsCountOut) +{ + RewriteStructSamplersTraverser traverser(compiler, symbolTable); + root->traverse(&traverser); + *removedUniformsCountOut = traverser.removedUniformsCount(); + return traverser.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h new file mode 100644 index 0000000000..f4c73a27fc --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h @@ -0,0 +1,38 @@ +// +// Copyright 2018 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. +// +// RewriteStructSamplers: Extract structs from samplers. +// +// This traverser is designed to strip out samplers from structs. It moves them into separate +// uniform sampler declarations. This allows the struct to be stored in the default uniform block. +// This transformation requires MonomorphizeUnsupportedFunctions to have been run so it +// wouldn't need to deal with functions that are passed such structs. +// +// For example: +// struct S { sampler2D samp; int i; }; +// uniform S uni; +// Is rewritten as: +// struct S { int i; }; +// uniform S uni; +// uniform sampler2D uni_i; + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITESTRUCTSAMPLERS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITESTRUCTSAMPLERS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool RewriteStructSamplers(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable, + int *removedUniformsCountOut); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_VULKAN_REWRITESTRUCTSAMPLERS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp new file mode 100644 index 0000000000..71284ecc1b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp @@ -0,0 +1,168 @@ +// +// Copyright 2016 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. +// +// Implementation of texelFetchOffset translation issue workaround. +// See header for more info. + +#include "compiler/translator/tree_ops/RewriteTexelFetchOffset.h" + +#include "common/angleutils.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool Apply(TCompiler *compiler, + TIntermNode *root, + const TSymbolTable &symbolTable, + int shaderVersion); + + private: + Traverser(const TSymbolTable &symbolTable, int shaderVersion); + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + void nextIteration(); + + const TSymbolTable *symbolTable; + const int shaderVersion; + bool mFound = false; +}; + +Traverser::Traverser(const TSymbolTable &symbolTable, int shaderVersion) + : TIntermTraverser(true, false, false), symbolTable(&symbolTable), shaderVersion(shaderVersion) +{} + +// static +bool Traverser::Apply(TCompiler *compiler, + TIntermNode *root, + const TSymbolTable &symbolTable, + int shaderVersion) +{ + Traverser traverser(symbolTable, shaderVersion); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.mFound) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.mFound); + + return true; +} + +void Traverser::nextIteration() +{ + mFound = false; +} + +bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (mFound) + { + return false; + } + + // Decide if the node represents the call of texelFetchOffset. + if (!BuiltInGroup::IsBuiltIn(node->getOp())) + { + return true; + } + + ASSERT(node->getFunction()->symbolType() == SymbolType::BuiltIn); + if (node->getFunction()->name() != "texelFetchOffset") + { + return true; + } + + // Potential problem case detected, apply workaround. + const TIntermSequence *sequence = node->getSequence(); + ASSERT(sequence->size() == 4u); + + // Decide if the sampler is a 2DArray sampler. In that case position is ivec3 and offset is + // ivec2. + bool is2DArray = sequence->at(1)->getAsTyped()->getNominalSize() == 3 && + sequence->at(3)->getAsTyped()->getNominalSize() == 2; + + // Create new node that represents the call of function texelFetch. + // Its argument list will be: texelFetch(sampler, Position+offset, lod). + + TIntermSequence texelFetchArguments; + + // sampler + texelFetchArguments.push_back(sequence->at(0)); + + // Position + TIntermTyped *texCoordNode = sequence->at(1)->getAsTyped(); + ASSERT(texCoordNode); + + // offset + TIntermTyped *offsetNode = nullptr; + ASSERT(sequence->at(3)->getAsTyped()); + if (is2DArray) + { + // For 2DArray samplers, Position is ivec3 and offset is ivec2; + // So offset must be converted into an ivec3 before being added to Position. + TIntermSequence constructOffsetIvecArguments; + constructOffsetIvecArguments.push_back(sequence->at(3)->getAsTyped()); + + TIntermTyped *zeroNode = CreateZeroNode(TType(EbtInt)); + constructOffsetIvecArguments.push_back(zeroNode); + + offsetNode = TIntermAggregate::CreateConstructor(texCoordNode->getType(), + &constructOffsetIvecArguments); + offsetNode->setLine(texCoordNode->getLine()); + } + else + { + offsetNode = sequence->at(3)->getAsTyped(); + } + + // Position+offset + TIntermBinary *add = new TIntermBinary(EOpAdd, texCoordNode, offsetNode); + add->setLine(texCoordNode->getLine()); + texelFetchArguments.push_back(add); + + // lod + texelFetchArguments.push_back(sequence->at(2)); + + ASSERT(texelFetchArguments.size() == 3u); + + TIntermTyped *texelFetchNode = CreateBuiltInFunctionCallNode("texelFetch", &texelFetchArguments, + *symbolTable, shaderVersion); + texelFetchNode->setLine(node->getLine()); + + // Replace the old node by this new node. + queueReplacement(texelFetchNode, OriginalNode::IS_DROPPED); + mFound = true; + return false; +} + +} // anonymous namespace + +bool RewriteTexelFetchOffset(TCompiler *compiler, + TIntermNode *root, + const TSymbolTable &symbolTable, + int shaderVersion) +{ + // texelFetchOffset is only valid in GLSL 3.0 and later. + if (shaderVersion < 300) + return true; + + return Traverser::Apply(compiler, root, symbolTable, shaderVersion); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h new file mode 100644 index 0000000000..c2c3c07aad --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h @@ -0,0 +1,34 @@ +// +// Copyright 2016 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. +// +// This mutating tree traversal works around an issue on the translation +// from texelFetchOffset into HLSL function Load on INTEL drivers. It +// works by translating texelFetchOffset into texelFetch: +// +// - From: texelFetchOffset(sampler, Position, lod, offset) +// - To: texelFetch(sampler, Position+offset, lod) +// +// See http://anglebug.com/1469 + +#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITE_TEXELFETCHOFFSET_H_ +#define COMPILER_TRANSLATOR_TREEOPS_REWRITE_TEXELFETCHOFFSET_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteTexelFetchOffset(TCompiler *compiler, + TIntermNode *root, + const TSymbolTable &symbolTable, + int shaderVersion); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITE_TEXELFETCHOFFSET_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp new file mode 100644 index 0000000000..4eab90e4fa --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp @@ -0,0 +1,223 @@ +// +// 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. +// +// Scalarize vector and matrix constructor args, so that vectors built from components don't have +// matrix arguments, and matrices built from components don't have vector arguments. This avoids +// driver bugs around vector and matrix constructors. +// + +#include "compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h" +#include "common/debug.h" + +#include <algorithm> + +#include "angle_gl.h" +#include "common/angleutils.h" +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +TIntermBinary *ConstructVectorIndexBinaryNode(TIntermTyped *symbolNode, int index) +{ + return new TIntermBinary(EOpIndexDirect, symbolNode, CreateIndexNode(index)); +} + +TIntermBinary *ConstructMatrixIndexBinaryNode(TIntermTyped *symbolNode, int colIndex, int rowIndex) +{ + TIntermBinary *colVectorNode = ConstructVectorIndexBinaryNode(symbolNode, colIndex); + + return new TIntermBinary(EOpIndexDirect, colVectorNode, CreateIndexNode(rowIndex)); +} + +class ScalarizeArgsTraverser : public TIntermTraverser +{ + public: + ScalarizeArgsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), + mNodesToScalarize(IntermNodePatternMatcher::kScalarizedVecOrMatConstructor) + {} + + protected: + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitBlock(Visit visit, TIntermBlock *node) override; + + private: + void scalarizeArgs(TIntermAggregate *aggregate, bool scalarizeVector, bool scalarizeMatrix); + + // If we have the following code: + // mat4 m(0); + // vec4 v(1, m); + // We will rewrite to: + // mat4 m(0); + // mat4 s0 = m; + // vec4 v(1, s0[0][0], s0[0][1], s0[0][2]); + // This function is to create nodes for "mat4 s0 = m;" and insert it to the code sequence. This + // way the possible side effects of the constructor argument will only be evaluated once. + TIntermTyped *createTempVariable(TIntermTyped *original); + + std::vector<TIntermSequence> mBlockStack; + + IntermNodePatternMatcher mNodesToScalarize; +}; + +bool ScalarizeArgsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + ASSERT(visit == PreVisit); + if (mNodesToScalarize.match(node, getParentNode())) + { + if (node->getType().isVector()) + { + scalarizeArgs(node, false, true); + } + else + { + ASSERT(node->getType().isMatrix()); + scalarizeArgs(node, true, false); + } + } + return true; +} + +bool ScalarizeArgsTraverser::visitBlock(Visit visit, TIntermBlock *node) +{ + mBlockStack.push_back(TIntermSequence()); + { + for (TIntermNode *child : *node->getSequence()) + { + ASSERT(child != nullptr); + child->traverse(this); + mBlockStack.back().push_back(child); + } + } + if (mBlockStack.back().size() > node->getSequence()->size()) + { + node->getSequence()->clear(); + *(node->getSequence()) = mBlockStack.back(); + } + mBlockStack.pop_back(); + return false; +} + +void ScalarizeArgsTraverser::scalarizeArgs(TIntermAggregate *aggregate, + bool scalarizeVector, + bool scalarizeMatrix) +{ + ASSERT(aggregate); + ASSERT(!aggregate->isArray()); + int size = static_cast<int>(aggregate->getType().getObjectSize()); + TIntermSequence *sequence = aggregate->getSequence(); + TIntermSequence originalArgs(*sequence); + sequence->clear(); + for (TIntermNode *originalArgNode : originalArgs) + { + ASSERT(size > 0); + TIntermTyped *originalArg = originalArgNode->getAsTyped(); + ASSERT(originalArg); + TIntermTyped *argVariable = createTempVariable(originalArg); + if (originalArg->isScalar()) + { + sequence->push_back(argVariable); + size--; + } + else if (originalArg->isVector()) + { + if (scalarizeVector) + { + int repeat = std::min<int>(size, originalArg->getNominalSize()); + size -= repeat; + for (int index = 0; index < repeat; ++index) + { + TIntermBinary *newNode = + ConstructVectorIndexBinaryNode(argVariable->deepCopy(), index); + sequence->push_back(newNode); + } + } + else + { + sequence->push_back(argVariable); + size -= originalArg->getNominalSize(); + } + } + else + { + ASSERT(originalArg->isMatrix()); + if (scalarizeMatrix) + { + int colIndex = 0, rowIndex = 0; + int repeat = std::min<int>(size, originalArg->getCols() * originalArg->getRows()); + size -= repeat; + while (repeat > 0) + { + TIntermBinary *newNode = + ConstructMatrixIndexBinaryNode(argVariable->deepCopy(), colIndex, rowIndex); + sequence->push_back(newNode); + rowIndex++; + if (rowIndex >= originalArg->getRows()) + { + rowIndex = 0; + colIndex++; + } + repeat--; + } + } + else + { + sequence->push_back(argVariable); + size -= originalArg->getCols() * originalArg->getRows(); + } + } + } +} + +TIntermTyped *ScalarizeArgsTraverser::createTempVariable(TIntermTyped *original) +{ + ASSERT(original); + + TType *type = new TType(original->getType()); + type->setQualifier(EvqTemporary); + + // The precision of the constant must have been retained (or derived), which will now apply to + // the temp variable. In some cases, the precision cannot be derived, so use the constant as + // is. For example, in the following standalone statement, the precision of the constant 0 + // cannot be determined: + // + // mat2(0, bvec3(m)); + // + if (IsPrecisionApplicableToType(type->getBasicType()) && type->getPrecision() == EbpUndefined) + { + return original; + } + + TVariable *variable = CreateTempVariable(mSymbolTable, type); + + ASSERT(mBlockStack.size() > 0); + TIntermSequence &sequence = mBlockStack.back(); + TIntermDeclaration *declaration = CreateTempInitDeclarationNode(variable, original); + sequence.push_back(declaration); + + return CreateTempSymbolNode(variable); +} + +} // namespace + +bool ScalarizeVecAndMatConstructorArgs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + ScalarizeArgsTraverser scalarizer(symbolTable); + root->traverse(&scalarizer); + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h new file mode 100644 index 0000000000..617cbd7682 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h @@ -0,0 +1,28 @@ +// +// 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. +// +// Scalarize vector and matrix constructor args, so that vectors built from components don't have +// matrix arguments, and matrices built from components don't have vector arguments. This avoids +// driver bugs around vector and matrix constructors. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_SCALARIZEVECANDMATCONSTRUCTORARGS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_SCALARIZEVECANDMATCONSTRUCTORARGS_H_ + +#include "GLSLANG/ShaderLang.h" +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool ScalarizeVecAndMatConstructorArgs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_SCALARIZEVECANDMATCONSTRUCTORARGS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp new file mode 100644 index 0000000000..6d48449154 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp @@ -0,0 +1,199 @@ +// +// 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. +// +// The SeparateDeclarations function processes declarations, so that in the end each declaration +// contains only one declarator. +// This is useful as an intermediate step when initialization needs to be separated from +// declaration, or when things need to be unfolded out of the initializer. +// Example: +// int a[1] = int[1](1), b[1] = int[1](2); +// gets transformed when run through this class into the AST equivalent of: +// int a[1] = int[1](1); +// int b[1] = int[1](2); + +#include "compiler/translator/tree_ops/SeparateDeclarations.h" + +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ + +namespace +{ + +class SeparateDeclarationsTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + + private: + SeparateDeclarationsTraverser(TSymbolTable *symbolTable); + bool visitDeclaration(Visit, TIntermDeclaration *node) override; + void visitSymbol(TIntermSymbol *symbol) override; + + void separateDeclarator(TIntermSequence *sequence, + size_t index, + TIntermSequence *replacementDeclarations, + const TStructure **replacementStructure); + + VariableReplacementMap mVariableMap; +}; + +bool SeparateDeclarationsTraverser::apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + SeparateDeclarationsTraverser separateDecl(symbolTable); + root->traverse(&separateDecl); + return separateDecl.updateTree(compiler, root); +} + +SeparateDeclarationsTraverser::SeparateDeclarationsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) +{} + +bool SeparateDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration *node) +{ + TIntermSequence *sequence = node->getSequence(); + if (sequence->size() <= 1) + { + return true; + } + + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + ASSERT(parentBlock != nullptr); + + TIntermSequence replacementDeclarations; + const TStructure *replacementStructure = nullptr; + for (size_t ii = 0; ii < sequence->size(); ++ii) + { + separateDeclarator(sequence, ii, &replacementDeclarations, &replacementStructure); + } + + mMultiReplacements.emplace_back(parentBlock, node, std::move(replacementDeclarations)); + return false; +} + +void SeparateDeclarationsTraverser::visitSymbol(TIntermSymbol *symbol) +{ + const TVariable *variable = &symbol->variable(); + if (mVariableMap.count(variable) > 0) + { + queueAccessChainReplacement(mVariableMap[variable]->deepCopy()); + } +} + +void SeparateDeclarationsTraverser::separateDeclarator(TIntermSequence *sequence, + size_t index, + TIntermSequence *replacementDeclarations, + const TStructure **replacementStructure) +{ + TIntermTyped *declarator = sequence->at(index)->getAsTyped(); + const TType &declaratorType = declarator->getType(); + + // If the declaration is not simultaneously declaring a struct, can use the same declarator. + // Otherwise, the first declarator is taken as-is if the struct has a name. + const TStructure *structure = declaratorType.getStruct(); + const bool isStructSpecifier = declaratorType.isStructSpecifier(); + if (!isStructSpecifier || (index == 0 && structure->symbolType() != SymbolType::Empty)) + { + TIntermDeclaration *replacementDeclaration = new TIntermDeclaration; + + // Make sure to update the declarator's initializers if any. + declarator->traverse(this); + + replacementDeclaration->appendDeclarator(declarator); + replacementDeclaration->setLine(declarator->getLine()); + replacementDeclarations->push_back(replacementDeclaration); + return; + } + + // If the struct is nameless, split it out first. + if (structure->symbolType() == SymbolType::Empty) + { + if (*replacementStructure == nullptr) + { + TStructure *newStructure = + new TStructure(mSymbolTable, kEmptyImmutableString, &structure->fields(), + SymbolType::AngleInternal); + newStructure->setAtGlobalScope(structure->atGlobalScope()); + *replacementStructure = structure = newStructure; + + TType *namedType = new TType(structure, true); + namedType->setQualifier(EvqGlobal); + + TVariable *structVariable = + new TVariable(mSymbolTable, kEmptyImmutableString, namedType, SymbolType::Empty); + + TIntermDeclaration *structDeclaration = new TIntermDeclaration; + structDeclaration->appendDeclarator(new TIntermSymbol(structVariable)); + structDeclaration->setLine(declarator->getLine()); + replacementDeclarations->push_back(structDeclaration); + } + else + { + structure = *replacementStructure; + } + } + + // Redeclare the declarator but not as a struct specifier. + TIntermSymbol *asSymbol = declarator->getAsSymbolNode(); + TIntermTyped *initializer = nullptr; + if (asSymbol == nullptr) + { + TIntermBinary *asBinary = declarator->getAsBinaryNode(); + ASSERT(asBinary->getOp() == EOpInitialize); + asSymbol = asBinary->getLeft()->getAsSymbolNode(); + initializer = asBinary->getRight(); + + // Make sure the initializer itself has its variables replaced if necessary. + if (initializer->getAsSymbolNode()) + { + const TVariable *initializerVariable = &initializer->getAsSymbolNode()->variable(); + if (mVariableMap.count(initializerVariable) > 0) + { + initializer = mVariableMap[initializerVariable]->deepCopy(); + } + } + else + { + initializer->traverse(this); + } + } + + ASSERT(asSymbol && asSymbol->variable().symbolType() != SymbolType::Empty); + + TType *newType = new TType(structure, false); + newType->setQualifier(asSymbol->getType().getQualifier()); + newType->makeArrays(asSymbol->getType().getArraySizes()); + + TVariable *replacementVar = new TVariable(mSymbolTable, asSymbol->getName(), newType, + asSymbol->variable().symbolType()); + TIntermSymbol *replacementSymbol = new TIntermSymbol(replacementVar); + TIntermTyped *replacement = replacementSymbol; + if (initializer) + { + replacement = new TIntermBinary(EOpInitialize, replacement, initializer); + } + + TIntermDeclaration *replacementDeclaration = new TIntermDeclaration; + replacementDeclaration->appendDeclarator(replacement); + replacementDeclaration->setLine(declarator->getLine()); + replacementDeclarations->push_back(replacementDeclaration); + + mVariableMap[&asSymbol->variable()] = replacementSymbol; +} +} // namespace + +bool SeparateDeclarations(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + return SeparateDeclarationsTraverser::apply(compiler, root, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h new file mode 100644 index 0000000000..5f55162ef4 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h @@ -0,0 +1,32 @@ +// +// 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. +// +// The SeparateDeclarations function processes declarations, so that in the end each declaration +// contains only one declarator. +// This is useful as an intermediate step when initialization needs to be separated from +// declaration, or when things need to be unfolded out of the initializer. +// Example: +// int a[1] = int[1](1), b[1] = int[1](2); +// gets transformed when run through this class into the AST equivalent of: +// int a[1] = int[1](1); +// int b[1] = int[1](2); + +#ifndef COMPILER_TRANSLATOR_TREEOPS_SEPARATEDECLARATIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_SEPARATEDECLARATIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool SeparateDeclarations(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_SEPARATEDECLARATIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp new file mode 100644 index 0000000000..fb3f4ba1c1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp @@ -0,0 +1,115 @@ +// +// Copyright 2018 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. +// +// SeparateStructFromUniformDeclarations: Separate struct declarations from uniform declarations. +// + +#include "compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +// This traverser translates embedded uniform structs into a specifier and declaration. +// This makes the declarations easier to move into uniform blocks. +class Traverser : public TIntermTraverser +{ + public: + explicit Traverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override + { + ASSERT(visit == PreVisit); + + if (!mInGlobalScope) + { + return true; + } + + const TIntermSequence &sequence = *(decl->getSequence()); + ASSERT(sequence.size() == 1); + TIntermTyped *declarator = sequence.front()->getAsTyped(); + const TType &type = declarator->getType(); + + if (type.isStructSpecifier() && type.getQualifier() == EvqUniform) + { + doReplacement(decl, declarator, type); + return false; + } + + return true; + } + + void visitSymbol(TIntermSymbol *symbol) override + { + const TVariable *variable = &symbol->variable(); + if (mVariableMap.count(variable) > 0) + { + queueAccessChainReplacement(mVariableMap[variable]->deepCopy()); + } + } + + private: + void doReplacement(TIntermDeclaration *decl, TIntermTyped *declarator, const TType &oldType) + { + const TStructure *structure = oldType.getStruct(); + if (structure->symbolType() == SymbolType::Empty) + { + // Handle nameless structs: uniform struct { ... } variable; + structure = new TStructure(mSymbolTable, kEmptyImmutableString, &structure->fields(), + SymbolType::AngleInternal); + } + TType *namedType = new TType(structure, true); + namedType->setQualifier(EvqGlobal); + + TVariable *structVariable = + new TVariable(mSymbolTable, kEmptyImmutableString, namedType, SymbolType::Empty); + TIntermSymbol *structDeclarator = new TIntermSymbol(structVariable); + TIntermDeclaration *structDeclaration = new TIntermDeclaration; + structDeclaration->appendDeclarator(structDeclarator); + + TIntermSequence newSequence; + newSequence.push_back(structDeclaration); + + // Redeclare the uniform with the (potentially) new struct type + TIntermSymbol *asSymbol = declarator->getAsSymbolNode(); + ASSERT(asSymbol && asSymbol->variable().symbolType() != SymbolType::Empty); + + TIntermDeclaration *namedDecl = new TIntermDeclaration; + TType *uniformType = new TType(structure, false); + uniformType->setQualifier(EvqUniform); + uniformType->makeArrays(oldType.getArraySizes()); + + TVariable *newVar = new TVariable(mSymbolTable, asSymbol->getName(), uniformType, + asSymbol->variable().symbolType()); + TIntermSymbol *newSymbol = new TIntermSymbol(newVar); + namedDecl->appendDeclarator(newSymbol); + + newSequence.push_back(namedDecl); + + mVariableMap[&asSymbol->variable()] = newSymbol; + + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl, + std::move(newSequence)); + } + + VariableReplacementMap mVariableMap; +}; +} // anonymous namespace + +bool SeparateStructFromUniformDeclarations(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + Traverser separateStructDecls(symbolTable); + root->traverse(&separateStructDecls); + return separateStructDecls.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h new file mode 100644 index 0000000000..424a742e54 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h @@ -0,0 +1,39 @@ +// +// Copyright 2018 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. +// +// SeparateStructFromUniformDeclarations: Separate struct declarations from uniform declarations. +// It necessarily gives nameless uniform structs internal names. +// +// For example: +// uniform struct { int a; } uni; +// becomes: +// struct s1 { int a; }; +// uniform s1 uni; +// +// And: +// uniform struct S { int a; } uni; +// becomes: +// struct S { int a; }; +// uniform S uni; +// +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_SEPARATESTRUCTFROMUNIFORMDECLARATIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_SEPARATESTRUCTFROMUNIFORMDECLARATIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool SeparateStructFromUniformDeclarations(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_SEPARATESTRUCTFROMUNIFORMDECLARATIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp new file mode 100644 index 0000000000..c6a3e0b9ee --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp @@ -0,0 +1,499 @@ +// +// Copyright 2016 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. +// +// SimplifyLoopConditions is an AST traverser that converts loop conditions and loop expressions +// to regular statements inside the loop. This way further transformations that generate statements +// from loop conditions and loop expressions work correctly. +// + +#include "compiler/translator/tree_ops/SimplifyLoopConditions.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +struct LoopInfo +{ + const TVariable *conditionVariable = nullptr; + TIntermTyped *condition = nullptr; + TIntermTyped *expression = nullptr; +}; + +class SimplifyLoopConditionsTraverser : public TLValueTrackingTraverser +{ + public: + SimplifyLoopConditionsTraverser(const IntermNodePatternMatcher *conditionsToSimplify, + TSymbolTable *symbolTable); + + void traverseLoop(TIntermLoop *node) override; + + bool visitUnary(Visit visit, TIntermUnary *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override; + bool visitBranch(Visit visit, TIntermBranch *node) override; + + bool foundLoopToChange() const { return mFoundLoopToChange; } + + protected: + // Marked to true once an operation that needs to be hoisted out of a loop expression has been + // found. + bool mFoundLoopToChange; + bool mInsideLoopInitConditionOrExpression; + const IntermNodePatternMatcher *mConditionsToSimplify; + + private: + LoopInfo mLoop; +}; + +SimplifyLoopConditionsTraverser::SimplifyLoopConditionsTraverser( + const IntermNodePatternMatcher *conditionsToSimplify, + TSymbolTable *symbolTable) + : TLValueTrackingTraverser(true, false, false, symbolTable), + mFoundLoopToChange(false), + mInsideLoopInitConditionOrExpression(false), + mConditionsToSimplify(conditionsToSimplify) +{} + +// If we're inside a loop initialization, condition, or expression, we check for expressions that +// should be moved out of the loop condition or expression. If one is found, the loop is +// transformed. +// If we're not inside loop initialization, condition, or expression, we only need to traverse nodes +// that may contain loops. + +bool SimplifyLoopConditionsTraverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (!mInsideLoopInitConditionOrExpression) + return false; + + if (mFoundLoopToChange) + return false; // Already decided to change this loop. + + ASSERT(mConditionsToSimplify); + mFoundLoopToChange = mConditionsToSimplify->match(node); + return !mFoundLoopToChange; +} + +bool SimplifyLoopConditionsTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (!mInsideLoopInitConditionOrExpression) + return false; + + if (mFoundLoopToChange) + return false; // Already decided to change this loop. + + ASSERT(mConditionsToSimplify); + mFoundLoopToChange = + mConditionsToSimplify->match(node, getParentNode(), isLValueRequiredHere()); + return !mFoundLoopToChange; +} + +bool SimplifyLoopConditionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (!mInsideLoopInitConditionOrExpression) + return false; + + if (mFoundLoopToChange) + return false; // Already decided to change this loop. + + ASSERT(mConditionsToSimplify); + mFoundLoopToChange = mConditionsToSimplify->match(node, getParentNode()); + return !mFoundLoopToChange; +} + +bool SimplifyLoopConditionsTraverser::visitTernary(Visit visit, TIntermTernary *node) +{ + if (!mInsideLoopInitConditionOrExpression) + return false; + + if (mFoundLoopToChange) + return false; // Already decided to change this loop. + + ASSERT(mConditionsToSimplify); + mFoundLoopToChange = mConditionsToSimplify->match(node); + return !mFoundLoopToChange; +} + +bool SimplifyLoopConditionsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node) +{ + if (!mInsideLoopInitConditionOrExpression) + return false; + + if (mFoundLoopToChange) + return false; // Already decided to change this loop. + + ASSERT(mConditionsToSimplify); + mFoundLoopToChange = mConditionsToSimplify->match(node); + return !mFoundLoopToChange; +} + +bool SimplifyLoopConditionsTraverser::visitBranch(Visit visit, TIntermBranch *node) +{ + if (node->getFlowOp() == EOpContinue && (mLoop.condition || mLoop.expression)) + { + TIntermBlock *parent = getParentNode()->getAsBlock(); + ASSERT(parent); + TIntermSequence seq; + if (mLoop.expression) + { + seq.push_back(mLoop.expression->deepCopy()); + } + if (mLoop.condition) + { + ASSERT(mLoop.conditionVariable); + seq.push_back( + CreateTempAssignmentNode(mLoop.conditionVariable, mLoop.condition->deepCopy())); + } + seq.push_back(node); + mMultiReplacements.push_back(NodeReplaceWithMultipleEntry(parent, node, std::move(seq))); + } + + return true; +} + +TIntermBlock *CreateFromBody(TIntermLoop *node, bool *bodyEndsInBranchOut) +{ + TIntermBlock *newBody = new TIntermBlock(); + *bodyEndsInBranchOut = false; + + TIntermBlock *nodeBody = node->getBody(); + if (nodeBody != nullptr) + { + newBody->getSequence()->push_back(nodeBody); + *bodyEndsInBranchOut = EndsInBranch(nodeBody); + } + return newBody; +} + +void SimplifyLoopConditionsTraverser::traverseLoop(TIntermLoop *node) +{ + // Mark that we're inside a loop condition or expression, and determine if the loop needs to be + // transformed. + + ScopedNodeInTraversalPath addToPath(this, node); + + mInsideLoopInitConditionOrExpression = true; + mFoundLoopToChange = !mConditionsToSimplify; + + if (!mFoundLoopToChange && node->getInit()) + { + node->getInit()->traverse(this); + } + + if (!mFoundLoopToChange && node->getCondition()) + { + node->getCondition()->traverse(this); + } + + if (!mFoundLoopToChange && node->getExpression()) + { + node->getExpression()->traverse(this); + } + + mInsideLoopInitConditionOrExpression = false; + + const LoopInfo prevLoop = mLoop; + + if (mFoundLoopToChange) + { + const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>(); + mLoop.conditionVariable = CreateTempVariable(mSymbolTable, boolType); + mLoop.condition = node->getCondition(); + mLoop.expression = node->getExpression(); + + // Replace the loop condition with a boolean variable that's updated on each iteration. + TLoopType loopType = node->getType(); + if (loopType == ELoopWhile) + { + ASSERT(!mLoop.expression); + + if (mLoop.condition->getAsSymbolNode()) + { + // Mask continue statement condition variable update. + mLoop.condition = nullptr; + } + else if (mLoop.condition->getAsConstantUnion()) + { + // Transform: + // while (expr) { body; } + // into + // bool s0 = expr; + // while (s0) { body; } + TIntermDeclaration *tempInitDeclaration = + CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition); + insertStatementInParentBlock(tempInitDeclaration); + + node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable)); + + // Mask continue statement condition variable update. + mLoop.condition = nullptr; + } + else + { + // Transform: + // while (expr) { body; } + // into + // bool s0 = expr; + // while (s0) { { body; } s0 = expr; } + // + // Local case statements are transformed into: + // s0 = expr; continue; + TIntermDeclaration *tempInitDeclaration = + CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition); + insertStatementInParentBlock(tempInitDeclaration); + + bool bodyEndsInBranch; + TIntermBlock *newBody = CreateFromBody(node, &bodyEndsInBranch); + if (!bodyEndsInBranch) + { + newBody->getSequence()->push_back(CreateTempAssignmentNode( + mLoop.conditionVariable, mLoop.condition->deepCopy())); + } + + // Can't use queueReplacement to replace old body, since it may have been nullptr. + // It's safe to do the replacements in place here - the new body will still be + // traversed, but that won't create any problems. + node->setBody(newBody); + node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable)); + } + } + else if (loopType == ELoopDoWhile) + { + ASSERT(!mLoop.expression); + + if (mLoop.condition->getAsSymbolNode()) + { + // Mask continue statement condition variable update. + mLoop.condition = nullptr; + } + else if (mLoop.condition->getAsConstantUnion()) + { + // Transform: + // do { + // body; + // } while (expr); + // into + // bool s0 = expr; + // do { + // body; + // } while (s0); + TIntermDeclaration *tempInitDeclaration = + CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition); + insertStatementInParentBlock(tempInitDeclaration); + + node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable)); + + // Mask continue statement condition variable update. + mLoop.condition = nullptr; + } + else + { + // Transform: + // do { + // body; + // } while (expr); + // into + // bool s0; + // do { + // { body; } + // s0 = expr; + // } while (s0); + // Local case statements are transformed into: + // s0 = expr; continue; + TIntermDeclaration *tempInitDeclaration = + CreateTempDeclarationNode(mLoop.conditionVariable); + insertStatementInParentBlock(tempInitDeclaration); + + bool bodyEndsInBranch; + TIntermBlock *newBody = CreateFromBody(node, &bodyEndsInBranch); + if (!bodyEndsInBranch) + { + newBody->getSequence()->push_back( + CreateTempAssignmentNode(mLoop.conditionVariable, mLoop.condition)); + } + + // Can't use queueReplacement to replace old body, since it may have been nullptr. + // It's safe to do the replacements in place here - the new body will still be + // traversed, but that won't create any problems. + node->setBody(newBody); + node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable)); + } + } + else if (loopType == ELoopFor) + { + if (!mLoop.condition) + { + mLoop.condition = CreateBoolNode(true); + } + + TIntermLoop *whileLoop; + TIntermBlock *loopScope = new TIntermBlock(); + TIntermSequence *loopScopeSequence = loopScope->getSequence(); + + // Insert "init;" + if (node->getInit()) + { + loopScopeSequence->push_back(node->getInit()); + } + + if (mLoop.condition->getAsSymbolNode()) + { + // Move the loop condition inside the loop. + // Transform: + // for (init; expr; exprB) { body; } + // into + // { + // init; + // while (expr) { + // { body; } + // exprB; + // } + // } + // + // Local case statements are transformed into: + // exprB; continue; + + // Insert "{ body; }" in the while loop + bool bodyEndsInBranch; + TIntermBlock *whileLoopBody = CreateFromBody(node, &bodyEndsInBranch); + // Insert "exprB;" in the while loop + if (!bodyEndsInBranch && node->getExpression()) + { + whileLoopBody->getSequence()->push_back(node->getExpression()); + } + // Create "while(expr) { whileLoopBody }" + whileLoop = + new TIntermLoop(ELoopWhile, nullptr, mLoop.condition, nullptr, whileLoopBody); + + // Mask continue statement condition variable update. + mLoop.condition = nullptr; + } + else if (mLoop.condition->getAsConstantUnion()) + { + // Move the loop condition inside the loop. + // Transform: + // for (init; expr; exprB) { body; } + // into + // { + // init; + // bool s0 = expr; + // while (s0) { + // { body; } + // exprB; + // } + // } + // + // Local case statements are transformed into: + // exprB; continue; + + // Insert "bool s0 = expr;" + loopScopeSequence->push_back( + CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition)); + // Insert "{ body; }" in the while loop + bool bodyEndsInBranch; + TIntermBlock *whileLoopBody = CreateFromBody(node, &bodyEndsInBranch); + // Insert "exprB;" in the while loop + if (!bodyEndsInBranch && node->getExpression()) + { + whileLoopBody->getSequence()->push_back(node->getExpression()); + } + // Create "while(s0) { whileLoopBody }" + whileLoop = new TIntermLoop(ELoopWhile, nullptr, + CreateTempSymbolNode(mLoop.conditionVariable), nullptr, + whileLoopBody); + + // Mask continue statement condition variable update. + mLoop.condition = nullptr; + } + else + { + // Move the loop condition inside the loop. + // Transform: + // for (init; expr; exprB) { body; } + // into + // { + // init; + // bool s0 = expr; + // while (s0) { + // { body; } + // exprB; + // s0 = expr; + // } + // } + // + // Local case statements are transformed into: + // exprB; s0 = expr; continue; + + // Insert "bool s0 = expr;" + loopScopeSequence->push_back( + CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition)); + // Insert "{ body; }" in the while loop + bool bodyEndsInBranch; + TIntermBlock *whileLoopBody = CreateFromBody(node, &bodyEndsInBranch); + // Insert "exprB;" in the while loop + if (!bodyEndsInBranch && node->getExpression()) + { + whileLoopBody->getSequence()->push_back(node->getExpression()); + } + // Insert "s0 = expr;" in the while loop + if (!bodyEndsInBranch) + { + whileLoopBody->getSequence()->push_back(CreateTempAssignmentNode( + mLoop.conditionVariable, mLoop.condition->deepCopy())); + } + // Create "while(s0) { whileLoopBody }" + whileLoop = new TIntermLoop(ELoopWhile, nullptr, + CreateTempSymbolNode(mLoop.conditionVariable), nullptr, + whileLoopBody); + } + + loopScope->getSequence()->push_back(whileLoop); + queueReplacement(loopScope, OriginalNode::IS_DROPPED); + + // After this the old body node will be traversed and loops inside it may be + // transformed. This is fine, since the old body node will still be in the AST after + // the transformation that's queued here, and transforming loops inside it doesn't + // need to know the exact post-transform path to it. + } + } + + mFoundLoopToChange = false; + + // We traverse the body of the loop even if the loop is transformed. + if (node->getBody()) + node->getBody()->traverse(this); + + mLoop = prevLoop; +} + +} // namespace + +bool SimplifyLoopConditions(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + SimplifyLoopConditionsTraverser traverser(nullptr, symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +bool SimplifyLoopConditions(TCompiler *compiler, + TIntermNode *root, + unsigned int conditionsToSimplifyMask, + TSymbolTable *symbolTable) +{ + IntermNodePatternMatcher conditionsToSimplify(conditionsToSimplifyMask); + SimplifyLoopConditionsTraverser traverser(&conditionsToSimplify, symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h new file mode 100644 index 0000000000..2a554c019d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h @@ -0,0 +1,32 @@ +// +// Copyright 2016 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. +// +// SimplifyLoopConditions is an AST traverser that converts loop conditions and loop expressions +// to regular statements inside the loop. This way further transformations that generate statements +// from loop conditions and loop expressions work correctly. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_SIMPLIFYLOOPCONDITIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_SIMPLIFYLOOPCONDITIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool SimplifyLoopConditions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +[[nodiscard]] bool SimplifyLoopConditions(TCompiler *compiler, + TIntermNode *root, + unsigned int conditionsToSimplify, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_SIMPLIFYLOOPCONDITIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp new file mode 100644 index 0000000000..45985954b0 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp @@ -0,0 +1,173 @@ +// +// Copyright 2016 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. +// +// SplitSequenceOperator is an AST traverser that detects sequence operator expressions that +// go through further AST transformations that generate statements, and splits them so that +// possible side effects of earlier parts of the sequence operator expression are guaranteed to be +// evaluated before the latter parts of the sequence operator expression are evaluated. +// + +#include "compiler/translator/tree_ops/SplitSequenceOperator.h" + +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class SplitSequenceOperatorTraverser : public TLValueTrackingTraverser +{ + public: + SplitSequenceOperatorTraverser(unsigned int patternsToSplitMask, TSymbolTable *symbolTable); + + bool visitUnary(Visit visit, TIntermUnary *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + + void nextIteration(); + bool foundExpressionToSplit() const { return mFoundExpressionToSplit; } + + protected: + // Marked to true once an operation that needs to be hoisted out of the expression has been + // found. After that, no more AST updates are performed on that traversal. + bool mFoundExpressionToSplit; + int mInsideSequenceOperator; + + IntermNodePatternMatcher mPatternToSplitMatcher; +}; + +SplitSequenceOperatorTraverser::SplitSequenceOperatorTraverser(unsigned int patternsToSplitMask, + TSymbolTable *symbolTable) + : TLValueTrackingTraverser(true, false, true, symbolTable), + mFoundExpressionToSplit(false), + mInsideSequenceOperator(0), + mPatternToSplitMatcher(patternsToSplitMask) +{} + +void SplitSequenceOperatorTraverser::nextIteration() +{ + mFoundExpressionToSplit = false; + mInsideSequenceOperator = 0; +} + +bool SplitSequenceOperatorTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (mFoundExpressionToSplit) + return false; + + if (mInsideSequenceOperator > 0 && visit == PreVisit) + { + // Detect expressions that need to be simplified + mFoundExpressionToSplit = mPatternToSplitMatcher.match(node, getParentNode()); + return !mFoundExpressionToSplit; + } + + return true; +} + +bool SplitSequenceOperatorTraverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (mFoundExpressionToSplit) + return false; + + if (mInsideSequenceOperator > 0 && visit == PreVisit) + { + // Detect expressions that need to be simplified + mFoundExpressionToSplit = mPatternToSplitMatcher.match(node); + return !mFoundExpressionToSplit; + } + + return true; +} + +bool SplitSequenceOperatorTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (node->getOp() == EOpComma) + { + if (visit == PreVisit) + { + if (mFoundExpressionToSplit) + { + return false; + } + mInsideSequenceOperator++; + } + else if (visit == PostVisit) + { + // Split sequence operators starting from the outermost one to preserve correct + // execution order. + if (mFoundExpressionToSplit && mInsideSequenceOperator == 1) + { + // Move the left side operand into a separate statement in the parent block. + TIntermSequence insertions; + insertions.push_back(node->getLeft()); + insertStatementsInParentBlock(insertions); + // Replace the comma node with its right side operand. + queueReplacement(node->getRight(), OriginalNode::IS_DROPPED); + } + mInsideSequenceOperator--; + } + return true; + } + + if (mFoundExpressionToSplit) + return false; + + if (mInsideSequenceOperator > 0 && visit == PreVisit) + { + // Detect expressions that need to be simplified + mFoundExpressionToSplit = + mPatternToSplitMatcher.match(node, getParentNode(), isLValueRequiredHere()); + return !mFoundExpressionToSplit; + } + + return true; +} + +bool SplitSequenceOperatorTraverser::visitTernary(Visit visit, TIntermTernary *node) +{ + if (mFoundExpressionToSplit) + return false; + + if (mInsideSequenceOperator > 0 && visit == PreVisit) + { + // Detect expressions that need to be simplified + mFoundExpressionToSplit = mPatternToSplitMatcher.match(node); + return !mFoundExpressionToSplit; + } + + return true; +} + +} // namespace + +bool SplitSequenceOperator(TCompiler *compiler, + TIntermNode *root, + int patternsToSplitMask, + TSymbolTable *symbolTable) +{ + SplitSequenceOperatorTraverser traverser(patternsToSplitMask, symbolTable); + // Separate one expression at a time, and reset the traverser between iterations. + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundExpressionToSplit()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundExpressionToSplit()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h new file mode 100644 index 0000000000..2a9f09a1f2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h @@ -0,0 +1,30 @@ +// +// Copyright 2016 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. +// +// SplitSequenceOperator is an AST traverser that detects sequence operator expressions that +// go through further AST transformations that generate statements, and splits them so that +// possible side effects of earlier parts of the sequence operator expression are guaranteed to be +// evaluated before the latter parts of the sequence operator expression are evaluated. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_SPLITSEQUENCEOPERATOR_H_ +#define COMPILER_TRANSLATOR_TREEOPS_SPLITSEQUENCEOPERATOR_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool SplitSequenceOperator(TCompiler *compiler, + TIntermNode *root, + int patternsToSplitMask, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_SPLITSEQUENCEOPERATOR_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp new file mode 100644 index 0000000000..36e426f8e1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp @@ -0,0 +1,60 @@ +// +// Copyright 2016 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/apple/AddAndTrueToLoopCondition.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// An AST traverser that rewrites for and while loops by replacing "condition" with +// "condition && true" to work around condition bug on Intel Mac. +class AddAndTrueToLoopConditionTraverser : public TIntermTraverser +{ + public: + AddAndTrueToLoopConditionTraverser() : TIntermTraverser(true, false, false) {} + + bool visitLoop(Visit, TIntermLoop *loop) override + { + // do-while loop doesn't have this bug. + if (loop->getType() != ELoopFor && loop->getType() != ELoopWhile) + { + return true; + } + + // For loop may not have a condition. + if (loop->getCondition() == nullptr) + { + return true; + } + + // Constant true. + TIntermTyped *trueValue = CreateBoolNode(true); + + // CONDITION && true. + TIntermBinary *andOp = new TIntermBinary(EOpLogicalAnd, loop->getCondition(), trueValue); + loop->setCondition(andOp); + + return true; + } +}; + +} // anonymous namespace + +bool AddAndTrueToLoopCondition(TCompiler *compiler, TIntermNode *root) +{ + AddAndTrueToLoopConditionTraverser traverser; + root->traverse(&traverser); + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h new file mode 100644 index 0000000000..d23158e81a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h @@ -0,0 +1,31 @@ +// +// Copyright 2016 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. +// + +// Rewrite condition in for and while loops to work around driver bug on Intel Mac. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_ADDANDTRUETOLOOPCONDITION_H_ +#define COMPILER_TRANSLATOR_TREEOPS_APPLE_ADDANDTRUETOLOOPCONDITION_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS) +[[nodiscard]] bool AddAndTrueToLoopCondition(TCompiler *compiler, TIntermNode *root); +#else +[[nodiscard]] ANGLE_INLINE bool AddAndTrueToLoopCondition(TCompiler *compiler, TIntermNode *root) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_ADDANDTRUETOLOOPCONDITION_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp new file mode 100644 index 0000000000..de322aadab --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp @@ -0,0 +1,147 @@ +// +// Copyright 2015 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. +// + +// RewriteDoWhile.cpp: rewrites do-while loops using another equivalent +// construct. + +#include "compiler/translator/tree_ops/apple/RewriteDoWhile.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// An AST traverser that rewrites loops of the form +// do { +// CODE; +// } while (CONDITION) +// +// to loops of the form +// bool temp = false; +// while (true) { +// if (temp) { +// if (!CONDITION) { +// break; +// } +// } +// temp = true; +// CODE; +// } +// +// The reason we don't use a simpler form, with for example just (temp && !CONDITION) in the +// while condition, is that short-circuit is often badly supported by driver shader compiler. +// The double if has the same effect, but forces shader compilers to behave. +// +// TODO(cwallez) when UnfoldShortCircuitIntoIf handles loops correctly, revisit this as we might +// be able to use while (temp || CONDITION) with temp initially set to true then run +// UnfoldShortCircuitIntoIf +class DoWhileRewriter : public TIntermTraverser +{ + public: + DoWhileRewriter(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable) + {} + + bool visitBlock(Visit, TIntermBlock *node) override + { + // A well-formed AST can only have do-while inside TIntermBlock. By doing a prefix traversal + // we are able to replace the do-while in the sequence directly as the content of the + // do-while will be traversed later. + + TIntermSequence *statements = node->getSequence(); + + // The statements vector will have new statements inserted when we encounter a do-while, + // which prevents us from using a range-based for loop. Using the usual i++ works, as + // the (two) new statements inserted replace the statement at the current position. + for (size_t i = 0; i < statements->size(); i++) + { + TIntermNode *statement = (*statements)[i]; + TIntermLoop *loop = statement->getAsLoopNode(); + + if (loop == nullptr || loop->getType() != ELoopDoWhile) + { + continue; + } + + // Found a loop to change. + const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>(); + TVariable *conditionVariable = CreateTempVariable(mSymbolTable, boolType); + + // bool temp = false; + TIntermDeclaration *tempDeclaration = + CreateTempInitDeclarationNode(conditionVariable, CreateBoolNode(false)); + + // temp = true; + TIntermBinary *assignTrue = + CreateTempAssignmentNode(conditionVariable, CreateBoolNode(true)); + + // if (temp) { + // if (!CONDITION) { + // break; + // } + // } + TIntermIfElse *breakIf = nullptr; + { + TIntermBranch *breakStatement = new TIntermBranch(EOpBreak, nullptr); + + TIntermBlock *breakBlock = new TIntermBlock(); + breakBlock->getSequence()->push_back(breakStatement); + + TIntermUnary *negatedCondition = + new TIntermUnary(EOpLogicalNot, loop->getCondition(), nullptr); + + TIntermIfElse *innerIf = new TIntermIfElse(negatedCondition, breakBlock, nullptr); + + TIntermBlock *innerIfBlock = new TIntermBlock(); + innerIfBlock->getSequence()->push_back(innerIf); + + breakIf = new TIntermIfElse(CreateTempSymbolNode(conditionVariable), innerIfBlock, + nullptr); + } + + // Assemble the replacement loops, reusing the do-while loop's body and inserting our + // statements at the front. + TIntermLoop *newLoop = nullptr; + { + TIntermBlock *body = loop->getBody(); + if (body == nullptr) + { + body = new TIntermBlock(); + } + auto sequence = body->getSequence(); + sequence->insert(sequence->begin(), assignTrue); + sequence->insert(sequence->begin(), breakIf); + + newLoop = new TIntermLoop(ELoopWhile, nullptr, CreateBoolNode(true), nullptr, body); + } + + TIntermSequence replacement; + replacement.push_back(tempDeclaration); + replacement.push_back(newLoop); + + node->replaceChildNodeWithMultiple(loop, replacement); + } + return true; + } +}; + +} // anonymous namespace + +bool RewriteDoWhile(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + DoWhileRewriter rewriter(symbolTable); + + root->traverse(&rewriter); + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h new file mode 100644 index 0000000000..71bdc8857b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h @@ -0,0 +1,38 @@ +// +// Copyright 2015 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. +// + +// RewriteDoWhile.h: rewrite do-while loops as while loops to work around +// driver bugs + +#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEDOWHILE_H_ +#define COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEDOWHILE_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS) +[[nodiscard]] bool RewriteDoWhile(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +#else +[[nodiscard]] ANGLE_INLINE bool RewriteDoWhile(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEDOWHILE_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp new file mode 100644 index 0000000000..49dc11efe2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp @@ -0,0 +1,1595 @@ +// +// Copyright 2019 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. +// +// RewriteRowMajorMatrices: Rewrite row-major matrices as column-major. +// + +#include "compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/tree_util/ReplaceVariable.h" + +namespace sh +{ +namespace +{ +// Only structs with matrices are tracked. If layout(row_major) is applied to a struct that doesn't +// have matrices, it's silently dropped. This is also used to avoid creating duplicates for inner +// structs that don't have matrices. +struct StructConversionData +{ + // The converted struct with every matrix transposed. + TStructure *convertedStruct = nullptr; + + // The copy-from and copy-to functions copying from a struct to its converted version and back. + TFunction *copyFromOriginal = nullptr; + TFunction *copyToOriginal = nullptr; +}; + +bool DoesFieldContainRowMajorMatrix(const TField *field, bool isBlockRowMajor) +{ + TLayoutMatrixPacking matrixPacking = field->type()->getLayoutQualifier().matrixPacking; + + // The field is row major if either explicitly specified as such, or if it inherits it from the + // block layout qualifier. + if (matrixPacking == EmpColumnMajor || (matrixPacking == EmpUnspecified && !isBlockRowMajor)) + { + return false; + } + + // The field is qualified with row_major, but if it's not a matrix or a struct containing + // matrices, that's a useless qualifier. + const TType *type = field->type(); + return type->isMatrix() || type->isStructureContainingMatrices(); +} + +TField *DuplicateField(const TField *field) +{ + return new TField(new TType(*field->type()), field->name(), field->line(), field->symbolType()); +} + +void SetColumnMajor(TType *type) +{ + TLayoutQualifier layoutQualifier = type->getLayoutQualifier(); + layoutQualifier.matrixPacking = EmpColumnMajor; + type->setLayoutQualifier(layoutQualifier); +} + +TType *TransposeMatrixType(const TType *type) +{ + TType *newType = new TType(*type); + + SetColumnMajor(newType); + + newType->setPrimarySize(type->getRows()); + newType->setSecondarySize(type->getCols()); + + return newType; +} + +void CopyArraySizes(const TType *from, TType *to) +{ + if (from->isArray()) + { + to->makeArrays(from->getArraySizes()); + } +} + +// Determine if the node is an index node (array index or struct field selection). For the purposes +// of this transformation, swizzle nodes are considered index nodes too. +bool IsIndexNode(TIntermNode *node, TIntermNode *child) +{ + if (node->getAsSwizzleNode()) + { + return true; + } + + TIntermBinary *binaryNode = node->getAsBinaryNode(); + if (binaryNode == nullptr || child != binaryNode->getLeft()) + { + return false; + } + + TOperator op = binaryNode->getOp(); + + return op == EOpIndexDirect || op == EOpIndexDirectInterfaceBlock || + op == EOpIndexDirectStruct || op == EOpIndexIndirect; +} + +TIntermSymbol *CopyToTempVariable(TSymbolTable *symbolTable, + TIntermTyped *node, + TIntermSequence *prependStatements) +{ + TVariable *temp = CreateTempVariable(symbolTable, &node->getType()); + TIntermDeclaration *tempDecl = CreateTempInitDeclarationNode(temp, node); + prependStatements->push_back(tempDecl); + + return new TIntermSymbol(temp); +} + +TIntermAggregate *CreateStructCopyCall(const TFunction *copyFunc, TIntermTyped *expression) +{ + TIntermSequence args = {expression}; + return TIntermAggregate::CreateFunctionCall(*copyFunc, &args); +} + +TIntermTyped *CreateTransposeCall(TSymbolTable *symbolTable, TIntermTyped *expression) +{ + TIntermSequence args = {expression}; + return CreateBuiltInFunctionCallNode("transpose", &args, *symbolTable, 300); +} + +TOperator GetIndex(TSymbolTable *symbolTable, + TIntermNode *node, + TIntermSequence *indices, + TIntermSequence *prependStatements) +{ + // Swizzle nodes are converted EOpIndexDirect for simplicity, with one index per swizzle + // channel. + TIntermSwizzle *asSwizzle = node->getAsSwizzleNode(); + if (asSwizzle) + { + for (int channel : asSwizzle->getSwizzleOffsets()) + { + indices->push_back(CreateIndexNode(channel)); + } + return EOpIndexDirect; + } + + TIntermBinary *binaryNode = node->getAsBinaryNode(); + ASSERT(binaryNode); + + TOperator op = binaryNode->getOp(); + ASSERT(op == EOpIndexDirect || op == EOpIndexDirectInterfaceBlock || + op == EOpIndexDirectStruct || op == EOpIndexIndirect); + + TIntermTyped *rhs = binaryNode->getRight()->deepCopy(); + if (rhs->getAsConstantUnion() == nullptr) + { + rhs = CopyToTempVariable(symbolTable, rhs, prependStatements); + } + + indices->push_back(rhs); + return op; +} + +TIntermTyped *ReplicateIndexNode(TSymbolTable *symbolTable, + TIntermNode *node, + TIntermTyped *lhs, + TIntermSequence *indices) +{ + TIntermSwizzle *asSwizzle = node->getAsSwizzleNode(); + if (asSwizzle) + { + return new TIntermSwizzle(lhs, asSwizzle->getSwizzleOffsets()); + } + + TIntermBinary *binaryNode = node->getAsBinaryNode(); + ASSERT(binaryNode); + + ASSERT(indices->size() == 1); + TIntermTyped *rhs = indices->front()->getAsTyped(); + + return new TIntermBinary(binaryNode->getOp(), lhs, rhs); +} + +TOperator GetIndexOp(TIntermNode *node) +{ + return node->getAsConstantUnion() ? EOpIndexDirect : EOpIndexIndirect; +} + +bool IsConvertedField(TIntermTyped *indexNode, + const angle::HashMap<const TField *, bool> &convertedFields) +{ + TIntermBinary *asBinary = indexNode->getAsBinaryNode(); + if (asBinary == nullptr) + { + return false; + } + + if (asBinary->getOp() != EOpIndexDirectInterfaceBlock) + { + return false; + } + + const TInterfaceBlock *interfaceBlock = asBinary->getLeft()->getType().getInterfaceBlock(); + ASSERT(interfaceBlock); + + TIntermConstantUnion *fieldIndexNode = asBinary->getRight()->getAsConstantUnion(); + ASSERT(fieldIndexNode); + ASSERT(fieldIndexNode->getConstantValue() != nullptr); + + int fieldIndex = fieldIndexNode->getConstantValue()->getIConst(); + const TField *field = interfaceBlock->fields()[fieldIndex]; + + return convertedFields.count(field) > 0 && convertedFields.at(field); +} + +// A helper class to transform expressions of array type. Iterates over every element of the +// array. +class TransformArrayHelper +{ + public: + TransformArrayHelper(TIntermTyped *baseExpression) + : mBaseExpression(baseExpression), + mBaseExpressionType(baseExpression->getType()), + mArrayIndices(mBaseExpressionType.getArraySizes().size(), 0) + {} + + TIntermTyped *getNextElement(TIntermTyped *valueExpression, TIntermTyped **valueElementOut) + { + const TSpan<const unsigned int> &arraySizes = mBaseExpressionType.getArraySizes(); + + // If the last index overflows, element enumeration is done. + if (mArrayIndices.back() >= arraySizes.back()) + { + return nullptr; + } + + TIntermTyped *element = getCurrentElement(mBaseExpression); + if (valueExpression) + { + *valueElementOut = getCurrentElement(valueExpression); + } + + incrementIndices(arraySizes); + return element; + } + + void accumulateForRead(TSymbolTable *symbolTable, + TIntermTyped *transformedElement, + TIntermSequence *prependStatements) + { + TIntermTyped *temp = CopyToTempVariable(symbolTable, transformedElement, prependStatements); + mReadTransformConstructorArgs.push_back(temp); + } + + TIntermTyped *constructReadTransformExpression() + { + const TSpan<const unsigned int> &baseTypeArraySizes = mBaseExpressionType.getArraySizes(); + TVector<unsigned int> arraySizes(baseTypeArraySizes.begin(), baseTypeArraySizes.end()); + TIntermTyped *firstElement = mReadTransformConstructorArgs.front()->getAsTyped(); + const TType &baseType = firstElement->getType(); + + // If N dimensions, acc[0] == size[0] and acc[i] == size[i] * acc[i-1]. + // The last value is unused, and is not present. + TVector<unsigned int> accumulatedArraySizes(arraySizes.size() - 1); + + if (accumulatedArraySizes.size() > 0) + { + accumulatedArraySizes[0] = arraySizes[0]; + } + for (size_t index = 1; index + 1 < arraySizes.size(); ++index) + { + accumulatedArraySizes[index] = accumulatedArraySizes[index - 1] * arraySizes[index]; + } + + return constructReadTransformExpressionHelper(arraySizes, accumulatedArraySizes, baseType, + 0); + } + + private: + TIntermTyped *getCurrentElement(TIntermTyped *expression) + { + TIntermTyped *element = expression->deepCopy(); + for (auto it = mArrayIndices.rbegin(); it != mArrayIndices.rend(); ++it) + { + unsigned int index = *it; + element = new TIntermBinary(EOpIndexDirect, element, CreateIndexNode(index)); + } + return element; + } + + void incrementIndices(const TSpan<const unsigned int> &arraySizes) + { + // Assume mArrayIndices is an N digit number, where digit i is in the range + // [0, arraySizes[i]). This function increments this number. Last digit is the most + // significant digit. + for (size_t digitIndex = 0; digitIndex < arraySizes.size(); ++digitIndex) + { + ++mArrayIndices[digitIndex]; + if (mArrayIndices[digitIndex] < arraySizes[digitIndex]) + { + break; + } + if (digitIndex + 1 != arraySizes.size()) + { + // This digit has now overflown and is reset to 0, carry will be added to the next + // digit. The most significant digit will keep the overflow though, to make it + // clear we have exhausted the range. + mArrayIndices[digitIndex] = 0; + } + } + } + + TIntermTyped *constructReadTransformExpressionHelper( + const TVector<unsigned int> &arraySizes, + const TVector<unsigned int> &accumulatedArraySizes, + const TType &baseType, + size_t elementsOffset) + { + ASSERT(!arraySizes.empty()); + + TType *transformedType = new TType(baseType); + transformedType->makeArrays(arraySizes); + + // If one dimensional, create the constructor with the given elements. + if (arraySizes.size() == 1) + { + ASSERT(accumulatedArraySizes.size() == 0); + + auto sliceStart = mReadTransformConstructorArgs.begin() + elementsOffset; + TIntermSequence slice(sliceStart, sliceStart + arraySizes[0]); + + return TIntermAggregate::CreateConstructor(*transformedType, &slice); + } + + // If not, create constructors for every column recursively. + TVector<unsigned int> subArraySizes(arraySizes.begin(), arraySizes.end() - 1); + TVector<unsigned int> subArrayAccumulatedSizes(accumulatedArraySizes.begin(), + accumulatedArraySizes.end() - 1); + + TIntermSequence constructorArgs; + unsigned int colStride = accumulatedArraySizes.back(); + for (size_t col = 0; col < arraySizes.back(); ++col) + { + size_t colElementsOffset = elementsOffset + col * colStride; + + constructorArgs.push_back(constructReadTransformExpressionHelper( + subArraySizes, subArrayAccumulatedSizes, baseType, colElementsOffset)); + } + + return TIntermAggregate::CreateConstructor(*transformedType, &constructorArgs); + } + + TIntermTyped *mBaseExpression; + const TType &mBaseExpressionType; + TVector<unsigned int> mArrayIndices; + + TIntermSequence mReadTransformConstructorArgs; +}; + +// Traverser that: +// +// 1. Converts |layout(row_major) matCxR M| to |layout(column_major) matRxC Mt|. +// 2. Converts |layout(row_major) S s| to |layout(column_major) St st|, where S is a struct that +// contains matrices, and St is a new struct with the transformation in 1 applied to matrix +// members (recursively). +// 3. When read from, the following transformations are applied: +// +// M -> transpose(Mt) +// M[c] -> gvecN(Mt[0][c], Mt[1][c], ..., Mt[N-1][c]) +// M[c][r] -> Mt[r][c] +// M[c].yz -> gvec2(Mt[1][c], Mt[2][c]) +// MArr -> MType[D1]..[DN](transpose(MtArr[0]...[0]), ...) +// s -> copy_St_to_S(st) +// sArr -> SType[D1]...[DN](copy_St_to_S(stArr[0]..[0]), ...) +// (matrix reads through struct are transformed similarly to M) +// +// 4. When written to, the following transformations are applied: +// +// M = exp -> Mt = transpose(exp) +// M[c] = exp -> temp = exp +// Mt[0][c] = temp[0] +// Mt[1][c] = temp[1] +// ... +// Mt[N-1][c] = temp[N-1] +// M[c][r] = exp -> Mt[r][c] = exp +// M[c].yz = exp -> temp = exp +// Mt[1][c] = temp[0] +// Mt[2][c] = temp[1] +// MArr = exp -> temp = exp +// Mt = MtType[D1]..[DN](temp([0]...[0]), ...) +// s = exp -> st = copy_S_to_St(exp) +// sArr = exp -> temp = exp +// St = StType[D1]...[DN](copy_S_to_St(temp[0]..[0]), ...) +// (matrix writes through struct are transformed similarly to M) +// +// 5. If any of the above is passed to an `inout` parameter, both transformations are applied: +// +// f(M[c]) -> temp = gvecN(Mt[0][c], Mt[1][c], ..., Mt[N-1][c]) +// f(temp) +// Mt[0][c] = temp[0] +// Mt[1][c] = temp[1] +// ... +// Mt[N-1][c] = temp[N-1] +// +// f(s) -> temp = copy_St_to_S(st) +// f(temp) +// st = copy_S_to_St(temp) +// +// If passed to an `out` parameter, the `temp` parameter is simply not initialized. +// +// 6. If the expression leading to the matrix or struct has array subscripts, temp values are +// created for them to avoid duplicating side effects. +// +class RewriteRowMajorMatricesTraverser : public TIntermTraverser +{ + public: + RewriteRowMajorMatricesTraverser(TCompiler *compiler, TSymbolTable *symbolTable) + : TIntermTraverser(true, true, true, symbolTable), + mCompiler(compiler), + mStructMapOut(&mOuterPass.structMap), + mInterfaceBlockMap(&mOuterPass.interfaceBlockMap), + mInterfaceBlockFieldConvertedIn(mOuterPass.interfaceBlockFieldConverted), + mCopyFunctionDefinitionsOut(&mOuterPass.copyFunctionDefinitions), + mOuterTraverser(nullptr), + mInnerPassRoot(nullptr), + mIsProcessingInnerPassSubtree(false) + {} + + bool visitDeclaration(Visit visit, TIntermDeclaration *node) override + { + // No need to process declarations in inner passes. + if (mInnerPassRoot != nullptr) + { + return true; + } + + if (visit != PreVisit) + { + return true; + } + + const TIntermSequence &sequence = *(node->getSequence()); + + TIntermTyped *variable = sequence.front()->getAsTyped(); + const TType &type = variable->getType(); + + // If it's a struct declaration that has matrices, remember it. If a row-major instance + // of it is created, it will have to be converted. + if (type.isStructSpecifier() && type.isStructureContainingMatrices()) + { + const TStructure *structure = type.getStruct(); + ASSERT(structure); + + ASSERT(mOuterPass.structMap.count(structure) == 0); + + StructConversionData structData; + mOuterPass.structMap[structure] = structData; + + return false; + } + + // If it's an interface block, it may have to be converted if it contains any row-major + // fields. + if (type.isInterfaceBlock() && type.getInterfaceBlock()->containsMatrices()) + { + const TInterfaceBlock *block = type.getInterfaceBlock(); + ASSERT(block); + bool isBlockRowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor; + + const TFieldList &fields = block->fields(); + bool anyRowMajor = isBlockRowMajor; + + for (const TField *field : fields) + { + if (DoesFieldContainRowMajorMatrix(field, isBlockRowMajor)) + { + anyRowMajor = true; + break; + } + } + + if (anyRowMajor) + { + convertInterfaceBlock(node); + } + + return false; + } + + return true; + } + + void visitSymbol(TIntermSymbol *symbol) override + { + // If in inner pass, only process if the symbol is under that root. + if (mInnerPassRoot != nullptr && !mIsProcessingInnerPassSubtree) + { + return; + } + + const TVariable *variable = &symbol->variable(); + bool needsRewrite = mInterfaceBlockMap->count(variable) != 0; + + // If it's a field of a nameless interface block, it may still need conversion. + if (!needsRewrite) + { + // Nameless interface block field symbols have the interface block pointer set, but are + // not interface blocks. + if (symbol->getType().getInterfaceBlock() && !variable->getType().isInterfaceBlock()) + { + needsRewrite = convertNamelessInterfaceBlockField(symbol); + } + } + + if (needsRewrite) + { + transformExpression(symbol); + } + } + + bool visitBinary(Visit visit, TIntermBinary *node) override + { + if (node == mInnerPassRoot) + { + // We only want to process the right-hand side of an assignment in inner passes. When + // visit is InVisit, the left-hand side is already processed, and the right-hand side is + // next. Set a flag to mark this duration. + mIsProcessingInnerPassSubtree = visit == InVisit; + } + + return true; + } + + TIntermSequence *getStructCopyFunctions() { return &mOuterPass.copyFunctionDefinitions; } + + private: + typedef angle::HashMap<const TStructure *, StructConversionData> StructMap; + typedef angle::HashMap<const TVariable *, TVariable *> InterfaceBlockMap; + typedef angle::HashMap<const TField *, bool> InterfaceBlockFieldConverted; + + RewriteRowMajorMatricesTraverser( + TSymbolTable *symbolTable, + RewriteRowMajorMatricesTraverser *outerTraverser, + InterfaceBlockMap *interfaceBlockMap, + const InterfaceBlockFieldConverted &interfaceBlockFieldConverted, + StructMap *structMap, + TIntermSequence *copyFunctionDefinitions, + TIntermBinary *innerPassRoot) + : TIntermTraverser(true, true, true, symbolTable), + mStructMapOut(structMap), + mInterfaceBlockMap(interfaceBlockMap), + mInterfaceBlockFieldConvertedIn(interfaceBlockFieldConverted), + mCopyFunctionDefinitionsOut(copyFunctionDefinitions), + mOuterTraverser(outerTraverser), + mInnerPassRoot(innerPassRoot), + mIsProcessingInnerPassSubtree(false) + {} + + void convertInterfaceBlock(TIntermDeclaration *node) + { + ASSERT(mInnerPassRoot == nullptr); + + const TIntermSequence &sequence = *(node->getSequence()); + + TIntermTyped *variableNode = sequence.front()->getAsTyped(); + const TType &type = variableNode->getType(); + const TInterfaceBlock *block = type.getInterfaceBlock(); + ASSERT(block); + + bool isBlockRowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor; + + // Recreate the struct with its row-major fields converted to column-major equivalents. + TIntermSequence newDeclarations; + + TFieldList *newFields = new TFieldList; + for (const TField *field : block->fields()) + { + TField *newField = nullptr; + + if (DoesFieldContainRowMajorMatrix(field, isBlockRowMajor)) + { + newField = convertField(field, &newDeclarations); + + // Remember that this field was converted. + mOuterPass.interfaceBlockFieldConverted[field] = true; + } + else + { + newField = DuplicateField(field); + } + + newFields->push_back(newField); + } + + // Create a new interface block with these fields. + TLayoutQualifier blockLayoutQualifier = type.getLayoutQualifier(); + blockLayoutQualifier.matrixPacking = EmpColumnMajor; + + TInterfaceBlock *newInterfaceBlock = + new TInterfaceBlock(mSymbolTable, block->name(), newFields, blockLayoutQualifier, + block->symbolType(), block->extensions()); + + // Create a new declaration with the new type. Declarations are separated at this point, + // so there should be only one variable here. + ASSERT(sequence.size() == 1); + + TType *newInterfaceBlockType = + new TType(newInterfaceBlock, type.getQualifier(), blockLayoutQualifier); + + TIntermDeclaration *newDeclaration = new TIntermDeclaration; + const TVariable *variable = &variableNode->getAsSymbolNode()->variable(); + + const TType *newType = newInterfaceBlockType; + if (type.isArray()) + { + TType *newArrayType = new TType(*newType); + CopyArraySizes(&type, newArrayType); + newType = newArrayType; + } + + // If the interface block variable itself is temp, use an empty name. + bool variableIsTemp = variable->symbolType() == SymbolType::Empty; + const ImmutableString &variableName = + variableIsTemp ? kEmptyImmutableString : variable->name(); + + TVariable *newVariable = new TVariable(mSymbolTable, variableName, newType, + variable->symbolType(), variable->extensions()); + + newDeclaration->appendDeclarator(new TIntermSymbol(newVariable)); + + mOuterPass.interfaceBlockMap[variable] = newVariable; + + newDeclarations.push_back(newDeclaration); + + // Replace the interface block definition with the new one, prepending any new struct + // definitions. + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(newDeclarations)); + } + + bool convertNamelessInterfaceBlockField(TIntermSymbol *symbol) + { + const TVariable *variable = &symbol->variable(); + const TInterfaceBlock *interfaceBlock = symbol->getType().getInterfaceBlock(); + + // Find the variable corresponding to this interface block. If the interface block + // is not rewritten, or this refers to a field that is not rewritten, there's + // nothing to do. + for (auto iter : *mInterfaceBlockMap) + { + // Skip other rewritten nameless interface block fields. + if (!iter.first->getType().isInterfaceBlock()) + { + continue; + } + + // Skip if this is not a field of this rewritten interface block. + if (iter.first->getType().getInterfaceBlock() != interfaceBlock) + { + continue; + } + + const ImmutableString symbolName = symbol->getName(); + + // Find which field it is + const TVector<TField *> fields = interfaceBlock->fields(); + const size_t fieldIndex = variable->getType().getInterfaceBlockFieldIndex(); + ASSERT(fieldIndex < fields.size()); + + const TField *field = fields[fieldIndex]; + ASSERT(field->name() == symbolName); + + // If this field doesn't need a rewrite, there's nothing to do. + if (mInterfaceBlockFieldConvertedIn.count(field) == 0 || + !mInterfaceBlockFieldConvertedIn.at(field)) + { + break; + } + + // Create a new variable that references the replaced interface block. + TType *newType = new TType(variable->getType()); + newType->setInterfaceBlockField(iter.second->getType().getInterfaceBlock(), fieldIndex); + + TVariable *newVariable = new TVariable(mSymbolTable, variable->name(), newType, + variable->symbolType(), variable->extensions()); + + (*mInterfaceBlockMap)[variable] = newVariable; + + return true; + } + + return false; + } + + void convertStruct(const TStructure *structure, TIntermSequence *newDeclarations) + { + ASSERT(mInnerPassRoot == nullptr); + + ASSERT(mOuterPass.structMap.count(structure) != 0); + StructConversionData *structData = &mOuterPass.structMap[structure]; + + if (structData->convertedStruct) + { + return; + } + + TFieldList *newFields = new TFieldList; + for (const TField *field : structure->fields()) + { + newFields->push_back(convertField(field, newDeclarations)); + } + + // Create unique names for the converted structs. We can't leave them nameless and have + // a name autogenerated similar to temp variables, as nameless structs exist. A fake + // variable is created for the sole purpose of generating a temp name. + TVariable *newStructTypeName = + new TVariable(mSymbolTable, kEmptyImmutableString, + StaticType::GetBasic<EbtUInt, EbpUndefined>(), SymbolType::Empty); + + TStructure *newStruct = new TStructure(mSymbolTable, newStructTypeName->name(), newFields, + SymbolType::AngleInternal); + TType *newType = new TType(newStruct, true); + TVariable *newStructVar = + new TVariable(mSymbolTable, kEmptyImmutableString, newType, SymbolType::Empty); + + TIntermDeclaration *structDecl = new TIntermDeclaration; + structDecl->appendDeclarator(new TIntermSymbol(newStructVar)); + + newDeclarations->push_back(structDecl); + + structData->convertedStruct = newStruct; + } + + TField *convertField(const TField *field, TIntermSequence *newDeclarations) + { + ASSERT(mInnerPassRoot == nullptr); + + TField *newField = nullptr; + + const TType *fieldType = field->type(); + TType *newType = nullptr; + + if (fieldType->isStructureContainingMatrices()) + { + // If the field is a struct instance, convert the struct and replace the field + // with an instance of the new struct. + const TStructure *fieldTypeStruct = fieldType->getStruct(); + convertStruct(fieldTypeStruct, newDeclarations); + + StructConversionData &structData = mOuterPass.structMap[fieldTypeStruct]; + newType = new TType(structData.convertedStruct, false); + SetColumnMajor(newType); + CopyArraySizes(fieldType, newType); + } + else if (fieldType->isMatrix()) + { + // If the field is a matrix, transpose the matrix and replace the field with + // that, removing the matrix packing qualifier. + newType = TransposeMatrixType(fieldType); + } + + if (newType) + { + newField = new TField(newType, field->name(), field->line(), field->symbolType()); + } + else + { + newField = DuplicateField(field); + } + + return newField; + } + + void determineAccess(TIntermNode *expression, + TIntermNode *accessor, + bool *isReadOut, + bool *isWriteOut) + { + // If passing to a function, look at whether the parameter is in, out or inout. + TIntermAggregate *functionCall = accessor->getAsAggregate(); + + if (functionCall) + { + TIntermSequence *arguments = functionCall->getSequence(); + for (size_t argIndex = 0; argIndex < arguments->size(); ++argIndex) + { + if ((*arguments)[argIndex] == expression) + { + TQualifier qualifier = EvqParamIn; + + // If the aggregate is not a function call, it's a constructor, and so every + // argument is an input. + const TFunction *function = functionCall->getFunction(); + if (function) + { + const TVariable *param = function->getParam(argIndex); + qualifier = param->getType().getQualifier(); + } + + *isReadOut = qualifier != EvqParamOut; + *isWriteOut = qualifier == EvqParamOut || qualifier == EvqParamInOut; + break; + } + } + return; + } + + TIntermBinary *assignment = accessor->getAsBinaryNode(); + if (assignment && IsAssignment(assignment->getOp())) + { + // If expression is on the right of assignment, it's being read from. + *isReadOut = assignment->getRight() == expression; + // If it's on the left of assignment, it's being written to. + *isWriteOut = assignment->getLeft() == expression; + return; + } + + // Any other usage is a read. + *isReadOut = true; + *isWriteOut = false; + } + + void transformExpression(TIntermSymbol *symbol) + { + // Walk up the parent chain while the nodes are EOpIndex* (whether array indexing or struct + // field selection) or swizzle and construct the replacement expression. This traversal can + // lead to one of the following possibilities: + // + // - a.b[N].etc.s (struct, or struct array): copy function should be declared and used, + // - a.b[N].etc.M (matrix or matrix array): transpose() should be used, + // - a.b[N].etc.M[c] (a column): each element in column needs to be handled separately, + // - a.b[N].etc.M[c].yz (multiple elements): similar to whole column, but a subset of + // elements, + // - a.b[N].etc.M[c][r] (an element): single element to handle. + // - a.b[N].etc.x (not struct or matrix): not modified + // + // primaryIndex will contain c, if any. secondaryIndices will contain {0, ..., R-1} + // (if no [r] or swizzle), {r} (if [r]), or {1, 2} (corresponding to .yz) if any. + // + // In all cases, the base symbol is replaced. |baseExpression| will contain everything up + // to (and not including) the last index/swizzle operations, i.e. a.b[N].etc.s/M/x. Any + // non constant array subscript is assigned to a temp variable to avoid duplicating side + // effects. + // + // --- + // + // NOTE that due to the use of insertStatementsInParentBlock, cases like this will be + // mistranslated, and this bug is likely present in most transformations that use this + // feature: + // + // if (x == 1 && a.b[x = 2].etc.M = value) + // + // which will translate to: + // + // temp = (x = 2) + // if (x == 1 && a.b[temp].etc.M = transpose(value)) + // + // See http://anglebug.com/3829. + // + TIntermTyped *baseExpression = + new TIntermSymbol(mInterfaceBlockMap->at(&symbol->variable())); + const TStructure *structure = nullptr; + + TIntermNode *primaryIndex = nullptr; + TIntermSequence secondaryIndices; + + // In some cases, it is necessary to prepend or append statements. Those are captured in + // |prependStatements| and |appendStatements|. + TIntermSequence prependStatements; + TIntermSequence appendStatements; + + // If the expression is neither a struct or matrix, no modification is necessary. + // If it's a struct that doesn't have matrices, again there's no transformation necessary. + // If it's an interface block matrix field that didn't need to be transposed, no + // transpformation is necessary. + // + // In all these cases, |baseExpression| contains all of the original expression. + // + // If the starting symbol itself is a field of a nameless interface block, it needs + // conversion if we reach here. + bool requiresTransformation = !symbol->getType().isInterfaceBlock(); + + uint32_t accessorIndex = 0; + TIntermTyped *previousAncestor = symbol; + while (IsIndexNode(getAncestorNode(accessorIndex), previousAncestor)) + { + TIntermTyped *ancestor = getAncestorNode(accessorIndex)->getAsTyped(); + ASSERT(ancestor); + + const TType &previousAncestorType = previousAncestor->getType(); + + TIntermSequence indices; + TOperator op = GetIndex(mSymbolTable, ancestor, &indices, &prependStatements); + + bool opIsIndex = op == EOpIndexDirect || op == EOpIndexIndirect; + bool isArrayIndex = opIsIndex && previousAncestorType.isArray(); + bool isMatrixIndex = opIsIndex && previousAncestorType.isMatrix(); + + // If it's a direct index in a matrix, it's the primary index. + bool isMatrixPrimarySubscript = isMatrixIndex && !isArrayIndex; + ASSERT(!isMatrixPrimarySubscript || + (primaryIndex == nullptr && secondaryIndices.empty())); + // If primary index is seen and the ancestor is still an index, it must be a direct + // index as the secondary one. Note that if primaryIndex is set, there can only ever be + // one more parent of interest, and that's subscripting the second dimension. + bool isMatrixSecondarySubscript = primaryIndex != nullptr; + ASSERT(!isMatrixSecondarySubscript || (opIsIndex && !isArrayIndex)); + + if (requiresTransformation && isMatrixPrimarySubscript) + { + ASSERT(indices.size() == 1); + primaryIndex = indices.front(); + + // Default the secondary indices to include every row. If there's a secondary + // subscript provided, it will override this. + const uint8_t rows = previousAncestorType.getRows(); + for (uint8_t r = 0; r < rows; ++r) + { + secondaryIndices.push_back(CreateIndexNode(r)); + } + } + else if (isMatrixSecondarySubscript) + { + ASSERT(requiresTransformation); + + secondaryIndices = indices; + + // Indices after this point are not interesting. There can't actually be any other + // index nodes other than desktop GLSL's swizzles on scalars, like M[1][2].yyy. + ++accessorIndex; + break; + } + else + { + // Replicate the expression otherwise. + baseExpression = + ReplicateIndexNode(mSymbolTable, ancestor, baseExpression, &indices); + + const TType &ancestorType = ancestor->getType(); + structure = ancestorType.getStruct(); + + requiresTransformation = + requiresTransformation || + IsConvertedField(ancestor, mInterfaceBlockFieldConvertedIn); + + // If we reach a point where the expression is neither a matrix-containing struct + // nor a matrix, there's no transformation required. This can happen if we decend + // through a struct marked with row-major but arrive at a member that doesn't + // include a matrix. + if (!ancestorType.isMatrix() && !ancestorType.isStructureContainingMatrices()) + { + requiresTransformation = false; + } + } + + previousAncestor = ancestor; + ++accessorIndex; + } + + TIntermNode *originalExpression = + accessorIndex == 0 ? symbol : getAncestorNode(accessorIndex - 1); + TIntermNode *accessor = getAncestorNode(accessorIndex); + + // if accessor is EOpArrayLength, we don't need to perform any transformations either. + // Note that this only applies to unsized arrays, as the RemoveArrayLengthMethod() + // transformation would have removed this operation otherwise. + TIntermUnary *accessorAsUnary = accessor->getAsUnaryNode(); + if (requiresTransformation && accessorAsUnary && accessorAsUnary->getOp() == EOpArrayLength) + { + ASSERT(accessorAsUnary->getOperand() == originalExpression); + ASSERT(accessorAsUnary->getOperand()->getType().isUnsizedArray()); + + requiresTransformation = false; + + // We need to replace the whole expression including the EOpArrayLength, to avoid + // confusing the replacement code as the original and new expressions don't have the + // same type (one is the transpose of the other). This doesn't affect the .length() + // operation, so this replacement is ok, though it's not worth special-casing this in + // the node replacement algorithm. + // + // Note: the |if (!requiresTransformation)| immediately below will be entered after + // this. + originalExpression = accessor; + accessor = getAncestorNode(accessorIndex + 1); + baseExpression = new TIntermUnary(EOpArrayLength, baseExpression, nullptr); + } + + if (!requiresTransformation) + { + ASSERT(primaryIndex == nullptr); + queueReplacementWithParent(accessor, originalExpression, baseExpression, + OriginalNode::IS_DROPPED); + + RewriteRowMajorMatricesTraverser *traverser = mOuterTraverser ? mOuterTraverser : this; + traverser->insertStatementsInParentBlock(prependStatements, appendStatements); + return; + } + + ASSERT(structure == nullptr || primaryIndex == nullptr); + ASSERT(structure != nullptr || baseExpression->getType().isMatrix()); + + // At the end, we can determine if the expression is being read from or written to (or both, + // if sent as an inout parameter to a function). For the sake of the transformation, the + // left-hand side of operations like += can be treated as "written to", without necessarily + // "read from". + bool isRead = false; + bool isWrite = false; + + determineAccess(originalExpression, accessor, &isRead, &isWrite); + + ASSERT(isRead || isWrite); + + TIntermTyped *readExpression = nullptr; + if (isRead) + { + readExpression = transformReadExpression( + baseExpression, primaryIndex, &secondaryIndices, structure, &prependStatements); + + // If both read from and written to (i.e. passed to inout parameter), store the + // expression in a temp variable and pass that to the function. + if (isWrite) + { + readExpression = + CopyToTempVariable(mSymbolTable, readExpression, &prependStatements); + } + + // Replace the original expression with the transformed one. Read transformations + // always generate a single expression that can be used in place of the original (as + // oppposed to write transformations that can generate multiple statements). + queueReplacementWithParent(accessor, originalExpression, readExpression, + OriginalNode::IS_DROPPED); + } + + TIntermSequence postTransformPrependStatements; + TIntermSequence *writeStatements = &appendStatements; + TOperator assignmentOperator = EOpAssign; + + if (isWrite) + { + TIntermTyped *valueExpression = readExpression; + + if (!valueExpression) + { + // If there's already a read expression, this was an inout parameter and + // |valueExpression| will contain the temp variable that was passed to the function + // instead. + // + // If not, then the modification is either through being passed as an out parameter + // to a function, or an assignment. In the former case, create a temp variable to + // be passed to the function. In the latter case, create a temp variable that holds + // the right hand side expression. + // + // In either case, use that temp value as the value to assign to |baseExpression|. + + TVariable *temp = + CreateTempVariable(mSymbolTable, &originalExpression->getAsTyped()->getType()); + TIntermDeclaration *tempDecl = nullptr; + + valueExpression = new TIntermSymbol(temp); + + TIntermBinary *assignment = accessor->getAsBinaryNode(); + if (assignment) + { + assignmentOperator = assignment->getOp(); + ASSERT(IsAssignment(assignmentOperator)); + + // We are converting the assignment to the left-hand side of an expression in + // the form M=exp. A subexpression of exp itself could require a + // transformation. This complicates things as there would be two replacements: + // + // - Replace M=exp with temp (because the return value of the assignment could + // be used) + // - Replace exp with exp2, where parent is M=exp + // + // The second replacement however is ineffective as the whole of M=exp is + // already transformed. What's worse, M=exp is transformed without taking exp's + // transformations into account. To address this issue, this same traverser is + // called on the right-hand side expression, with a special flag such that it + // only processes that expression. + // + RewriteRowMajorMatricesTraverser *outerTraverser = + mOuterTraverser ? mOuterTraverser : this; + RewriteRowMajorMatricesTraverser rhsTraverser( + mSymbolTable, outerTraverser, mInterfaceBlockMap, + mInterfaceBlockFieldConvertedIn, mStructMapOut, mCopyFunctionDefinitionsOut, + assignment); + getRootNode()->traverse(&rhsTraverser); + bool valid = rhsTraverser.updateTree(mCompiler, getRootNode()); + ASSERT(valid); + + tempDecl = CreateTempInitDeclarationNode(temp, assignment->getRight()); + + // Replace the whole assignment expression with the right-hand side as a read + // expression, in case the result of the assignment is used. For example, this + // transforms: + // + // if ((M += exp) == X) + // { + // // use M + // } + // + // to: + // + // temp = exp; + // M += transform(temp); + // if (transform(M) == X) + // { + // // use M + // } + // + // Note that in this case the assignment to M must be prepended in the parent + // block. In contrast, when sent to a function, the assignment to M should be + // done after the current function call is done. + // + // If the read from M itself (to replace assigmnet) needs to generate extra + // statements, they should be appended after the statements that write to M. + // These statements are stored in postTransformPrependStatements and appended to + // prependStatements in the end. + // + writeStatements = &prependStatements; + + TIntermTyped *assignmentResultExpression = transformReadExpression( + baseExpression->deepCopy(), primaryIndex, &secondaryIndices, structure, + &postTransformPrependStatements); + + // Replace the whole assignment, instead of just the right hand side. + TIntermNode *accessorParent = getAncestorNode(accessorIndex + 1); + queueReplacementWithParent(accessorParent, accessor, assignmentResultExpression, + OriginalNode::IS_DROPPED); + } + else + { + tempDecl = CreateTempDeclarationNode(temp); + + // Replace the write expression (a function call argument) with the temp + // variable. + queueReplacementWithParent(accessor, originalExpression, valueExpression, + OriginalNode::IS_DROPPED); + } + prependStatements.push_back(tempDecl); + } + + if (isRead) + { + baseExpression = baseExpression->deepCopy(); + } + transformWriteExpression(baseExpression, primaryIndex, &secondaryIndices, structure, + valueExpression, assignmentOperator, writeStatements); + } + + prependStatements.insert(prependStatements.end(), postTransformPrependStatements.begin(), + postTransformPrependStatements.end()); + + RewriteRowMajorMatricesTraverser *traverser = mOuterTraverser ? mOuterTraverser : this; + traverser->insertStatementsInParentBlock(prependStatements, appendStatements); + } + + TIntermTyped *transformReadExpression(TIntermTyped *baseExpression, + TIntermNode *primaryIndex, + TIntermSequence *secondaryIndices, + const TStructure *structure, + TIntermSequence *prependStatements) + { + const TType &baseExpressionType = baseExpression->getType(); + + if (structure) + { + ASSERT(primaryIndex == nullptr && secondaryIndices->empty()); + ASSERT(mStructMapOut->count(structure) != 0); + ASSERT((*mStructMapOut)[structure].convertedStruct != nullptr); + + // Declare copy-from-converted-to-original-struct function (if not already). + declareStructCopyToOriginal(structure); + + const TFunction *copyToOriginal = (*mStructMapOut)[structure].copyToOriginal; + + if (baseExpressionType.isArray()) + { + // If base expression is an array, transform every element. + TransformArrayHelper transformHelper(baseExpression); + + TIntermTyped *element = nullptr; + while ((element = transformHelper.getNextElement(nullptr, nullptr)) != nullptr) + { + TIntermTyped *transformedElement = + CreateStructCopyCall(copyToOriginal, element); + transformHelper.accumulateForRead(mSymbolTable, transformedElement, + prependStatements); + } + return transformHelper.constructReadTransformExpression(); + } + else + { + // If not reading an array, the result is simply a call to this function with the + // base expression. + return CreateStructCopyCall(copyToOriginal, baseExpression); + } + } + + // If not indexed, the result is transpose(exp) + if (primaryIndex == nullptr) + { + ASSERT(secondaryIndices->empty()); + + if (baseExpressionType.isArray()) + { + // If array, transpose every element. + TransformArrayHelper transformHelper(baseExpression); + + TIntermTyped *element = nullptr; + while ((element = transformHelper.getNextElement(nullptr, nullptr)) != nullptr) + { + TIntermTyped *transformedElement = CreateTransposeCall(mSymbolTable, element); + transformHelper.accumulateForRead(mSymbolTable, transformedElement, + prependStatements); + } + return transformHelper.constructReadTransformExpression(); + } + else + { + return CreateTransposeCall(mSymbolTable, baseExpression); + } + } + + // If indexed the result is a vector (or just one element) where the primary and secondary + // indices are swapped. + ASSERT(!secondaryIndices->empty()); + + TOperator primaryIndexOp = GetIndexOp(primaryIndex); + TIntermTyped *primaryIndexAsTyped = primaryIndex->getAsTyped(); + + TIntermSequence transposedColumn; + for (TIntermNode *secondaryIndex : *secondaryIndices) + { + TOperator secondaryIndexOp = GetIndexOp(secondaryIndex); + TIntermTyped *secondaryIndexAsTyped = secondaryIndex->getAsTyped(); + + TIntermBinary *colIndexed = new TIntermBinary( + secondaryIndexOp, baseExpression->deepCopy(), secondaryIndexAsTyped->deepCopy()); + TIntermBinary *colRowIndexed = + new TIntermBinary(primaryIndexOp, colIndexed, primaryIndexAsTyped->deepCopy()); + + transposedColumn.push_back(colRowIndexed); + } + + if (secondaryIndices->size() == 1) + { + // If only one element, return that directly. + return transposedColumn.front()->getAsTyped(); + } + + // Otherwise create a constructor with the appropriate dimension. + TType *vecType = new TType(baseExpressionType.getBasicType(), secondaryIndices->size()); + return TIntermAggregate::CreateConstructor(*vecType, &transposedColumn); + } + + void transformWriteExpression(TIntermTyped *baseExpression, + TIntermNode *primaryIndex, + TIntermSequence *secondaryIndices, + const TStructure *structure, + TIntermTyped *valueExpression, + TOperator assignmentOperator, + TIntermSequence *writeStatements) + { + const TType &baseExpressionType = baseExpression->getType(); + + if (structure) + { + ASSERT(primaryIndex == nullptr && secondaryIndices->empty()); + ASSERT(mStructMapOut->count(structure) != 0); + ASSERT((*mStructMapOut)[structure].convertedStruct != nullptr); + + // Declare copy-to-converted-from-original-struct function (if not already). + declareStructCopyFromOriginal(structure); + + // The result is call to this function with the value expression assigned to base + // expression. + const TFunction *copyFromOriginal = (*mStructMapOut)[structure].copyFromOriginal; + + if (baseExpressionType.isArray()) + { + // If array, assign every element. + TransformArrayHelper transformHelper(baseExpression); + + TIntermTyped *element = nullptr; + TIntermTyped *valueElement = nullptr; + while ((element = transformHelper.getNextElement(valueExpression, &valueElement)) != + nullptr) + { + TIntermTyped *functionCall = + CreateStructCopyCall(copyFromOriginal, valueElement); + writeStatements->push_back(new TIntermBinary(EOpAssign, element, functionCall)); + } + } + else + { + TIntermTyped *functionCall = + CreateStructCopyCall(copyFromOriginal, valueExpression->deepCopy()); + writeStatements->push_back( + new TIntermBinary(EOpAssign, baseExpression, functionCall)); + } + + return; + } + + // If not indexed, the result is transpose(exp) + if (primaryIndex == nullptr) + { + ASSERT(secondaryIndices->empty()); + + if (baseExpressionType.isArray()) + { + // If array, assign every element. + TransformArrayHelper transformHelper(baseExpression); + + TIntermTyped *element = nullptr; + TIntermTyped *valueElement = nullptr; + while ((element = transformHelper.getNextElement(valueExpression, &valueElement)) != + nullptr) + { + TIntermTyped *valueTransposed = CreateTransposeCall(mSymbolTable, valueElement); + writeStatements->push_back( + new TIntermBinary(EOpAssign, element, valueTransposed)); + } + } + else + { + TIntermTyped *valueTransposed = + CreateTransposeCall(mSymbolTable, valueExpression->deepCopy()); + writeStatements->push_back( + new TIntermBinary(assignmentOperator, baseExpression, valueTransposed)); + } + + return; + } + + // If indexed, create one assignment per secondary index. If the right-hand side is a + // scalar, it's used with every assignment. If it's a vector, the assignment is + // per-component. The right-hand side cannot be a matrix as that would imply left-hand + // side being a matrix too, which is covered above where |primaryIndex == nullptr|. + ASSERT(!secondaryIndices->empty()); + + bool isValueExpressionScalar = valueExpression->getType().getNominalSize() == 1; + ASSERT(isValueExpressionScalar || valueExpression->getType().getNominalSize() == + static_cast<int>(secondaryIndices->size())); + + TOperator primaryIndexOp = GetIndexOp(primaryIndex); + TIntermTyped *primaryIndexAsTyped = primaryIndex->getAsTyped(); + + for (TIntermNode *secondaryIndex : *secondaryIndices) + { + TOperator secondaryIndexOp = GetIndexOp(secondaryIndex); + TIntermTyped *secondaryIndexAsTyped = secondaryIndex->getAsTyped(); + + TIntermBinary *colIndexed = new TIntermBinary( + secondaryIndexOp, baseExpression->deepCopy(), secondaryIndexAsTyped->deepCopy()); + TIntermBinary *colRowIndexed = + new TIntermBinary(primaryIndexOp, colIndexed, primaryIndexAsTyped->deepCopy()); + + TIntermTyped *valueExpressionIndexed = valueExpression->deepCopy(); + if (!isValueExpressionScalar) + { + valueExpressionIndexed = new TIntermBinary(secondaryIndexOp, valueExpressionIndexed, + secondaryIndexAsTyped->deepCopy()); + } + + writeStatements->push_back( + new TIntermBinary(assignmentOperator, colRowIndexed, valueExpressionIndexed)); + } + } + + const TFunction *getCopyStructFieldFunction(const TType *fromFieldType, + const TType *toFieldType, + bool isCopyToOriginal) + { + ASSERT(fromFieldType->getStruct()); + ASSERT(toFieldType->getStruct()); + + // If copying from or to the original struct, the "to" field struct could require + // conversion to or from the "from" field struct. |isCopyToOriginal| tells us if we + // should expect to find toField or fromField in mStructMapOut, if true or false + // respectively. + const TFunction *fieldCopyFunction = nullptr; + if (isCopyToOriginal) + { + const TStructure *toFieldStruct = toFieldType->getStruct(); + + auto iter = mStructMapOut->find(toFieldStruct); + if (iter != mStructMapOut->end()) + { + declareStructCopyToOriginal(toFieldStruct); + fieldCopyFunction = iter->second.copyToOriginal; + } + } + else + { + const TStructure *fromFieldStruct = fromFieldType->getStruct(); + + auto iter = mStructMapOut->find(fromFieldStruct); + if (iter != mStructMapOut->end()) + { + declareStructCopyFromOriginal(fromFieldStruct); + fieldCopyFunction = iter->second.copyFromOriginal; + } + } + + return fieldCopyFunction; + } + + void addFieldCopy(TIntermBlock *body, + TIntermTyped *to, + TIntermTyped *from, + bool isCopyToOriginal) + { + const TType &fromType = from->getType(); + const TType &toType = to->getType(); + + TIntermTyped *rhs = from; + + if (fromType.getStruct()) + { + const TFunction *fieldCopyFunction = + getCopyStructFieldFunction(&fromType, &toType, isCopyToOriginal); + + if (fieldCopyFunction) + { + rhs = CreateStructCopyCall(fieldCopyFunction, from); + } + } + else if (fromType.isMatrix()) + { + rhs = CreateTransposeCall(mSymbolTable, from); + } + + body->appendStatement(new TIntermBinary(EOpAssign, to, rhs)); + } + + TFunction *declareStructCopy(const TStructure *from, + const TStructure *to, + bool isCopyToOriginal) + { + TType *fromType = new TType(from, true); + TType *toType = new TType(to, true); + + // Create the parameter and return value variables. + TVariable *fromVar = new TVariable(mSymbolTable, ImmutableString("from"), fromType, + SymbolType::AngleInternal); + TVariable *toVar = + new TVariable(mSymbolTable, ImmutableString("to"), toType, SymbolType::AngleInternal); + + TIntermSymbol *fromSymbol = new TIntermSymbol(fromVar); + TIntermSymbol *toSymbol = new TIntermSymbol(toVar); + + // Create the function body as statements are generated. + TIntermBlock *body = new TIntermBlock; + + // Declare the result variable. + TIntermDeclaration *toDecl = new TIntermDeclaration(); + toDecl->appendDeclarator(toSymbol); + body->appendStatement(toDecl); + + // Iterate over fields of the struct and copy one by one, transposing the matrices. If a + // struct is encountered that requires a transformation, this function is recursively + // called. As a result, it is important that the copy functions are placed in the code in + // order. + const TFieldList &fromFields = from->fields(); + const TFieldList &toFields = to->fields(); + ASSERT(fromFields.size() == toFields.size()); + + for (size_t fieldIndex = 0; fieldIndex < fromFields.size(); ++fieldIndex) + { + TIntermTyped *fieldIndexNode = CreateIndexNode(static_cast<int>(fieldIndex)); + + TIntermTyped *fromField = + new TIntermBinary(EOpIndexDirectStruct, fromSymbol->deepCopy(), fieldIndexNode); + TIntermTyped *toField = new TIntermBinary(EOpIndexDirectStruct, toSymbol->deepCopy(), + fieldIndexNode->deepCopy()); + + const TType *fromFieldType = fromFields[fieldIndex]->type(); + bool isStructOrMatrix = fromFieldType->getStruct() || fromFieldType->isMatrix(); + + if (fromFieldType->isArray() && isStructOrMatrix) + { + // If struct or matrix array, we need to copy element by element. + TransformArrayHelper transformHelper(toField); + + TIntermTyped *toElement = nullptr; + TIntermTyped *fromElement = nullptr; + while ((toElement = transformHelper.getNextElement(fromField, &fromElement)) != + nullptr) + { + addFieldCopy(body, toElement, fromElement, isCopyToOriginal); + } + } + else + { + addFieldCopy(body, toField, fromField, isCopyToOriginal); + } + } + + // Add return statement. + body->appendStatement(new TIntermBranch(EOpReturn, toSymbol->deepCopy())); + + // Declare the function + TFunction *copyFunction = new TFunction(mSymbolTable, kEmptyImmutableString, + SymbolType::AngleInternal, toType, true); + copyFunction->addParameter(fromVar); + + TIntermFunctionDefinition *functionDef = + CreateInternalFunctionDefinitionNode(*copyFunction, body); + mCopyFunctionDefinitionsOut->push_back(functionDef); + + return copyFunction; + } + + void declareStructCopyFromOriginal(const TStructure *structure) + { + StructConversionData *structData = &(*mStructMapOut)[structure]; + if (structData->copyFromOriginal) + { + return; + } + + structData->copyFromOriginal = + declareStructCopy(structure, structData->convertedStruct, false); + } + + void declareStructCopyToOriginal(const TStructure *structure) + { + StructConversionData *structData = &(*mStructMapOut)[structure]; + if (structData->copyToOriginal) + { + return; + } + + structData->copyToOriginal = + declareStructCopy(structData->convertedStruct, structure, true); + } + + TCompiler *mCompiler; + + // This traverser can call itself to transform a subexpression before moving on. However, it + // needs to accumulate conversion functions in inner passes. The fields below marked with Out + // or In are inherited from the outer pass (for inner passes), or point to storage fields in + // mOuterPass (for the outer pass). The latter should not be used by the inner passes as they + // would be empty, so they are placed inside a struct to make them explicit. + struct + { + StructMap structMap; + InterfaceBlockMap interfaceBlockMap; + InterfaceBlockFieldConverted interfaceBlockFieldConverted; + TIntermSequence copyFunctionDefinitions; + } mOuterPass; + + // A map from structures with matrices to their converted version. + StructMap *mStructMapOut; + // A map from interface block instances with row-major matrices to their converted variable. If + // an interface block is nameless, its fields are placed in this map instead. When a variable + // in this map is encountered, it signals the start of an expression that my need conversion, + // which is either "interfaceBlock.field..." or "field..." if nameless. + InterfaceBlockMap *mInterfaceBlockMap; + // A map from interface block fields to whether they need to be converted. If a field was + // already column-major, it shouldn't be transposed. + const InterfaceBlockFieldConverted &mInterfaceBlockFieldConvertedIn; + + TIntermSequence *mCopyFunctionDefinitionsOut; + + // If set, it's an inner pass and this will point to the outer pass traverser. All statement + // insertions are stored in the outer traverser and applied at once in the end. This prevents + // the inner passes from adding statements which invalidates the outer traverser's statement + // position tracking. + RewriteRowMajorMatricesTraverser *mOuterTraverser; + + // If set, it's an inner pass that should only process the right-hand side of this particular + // node. + TIntermBinary *mInnerPassRoot; + bool mIsProcessingInnerPassSubtree; +}; + +} // anonymous namespace + +bool RewriteRowMajorMatrices(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + RewriteRowMajorMatricesTraverser traverser(compiler, symbolTable); + root->traverse(&traverser); + if (!traverser.updateTree(compiler, root)) + { + return false; + } + + size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root); + root->insertChildNodes(firstFunctionIndex, *traverser.getStructCopyFunctions()); + + return compiler->validateAST(root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h new file mode 100644 index 0000000000..2f3577248e --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h @@ -0,0 +1,37 @@ +// +// Copyright 2019 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. +// +// RewriteRowMajorMatrices: Change row-major matrices to column-major in uniform and storage +// buffers. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEROWMAJORMATRICES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEROWMAJORMATRICES_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +#if (defined(ANGLE_ENABLE_GLSL) || defined(ANGLE_ENABLE_METAL)) && \ + defined(ANGLE_ENABLE_APPLE_WORKAROUNDS) +[[nodiscard]] bool RewriteRowMajorMatrices(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +#else +[[nodiscard]] ANGLE_INLINE bool RewriteRowMajorMatrices(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEROWMAJORMATRICES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp new file mode 100644 index 0000000000..4aa8a28203 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp @@ -0,0 +1,97 @@ +// +// Copyright 2016 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/apple/RewriteUnaryMinusOperatorFloat.h" + +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool Apply(TCompiler *compiler, TIntermNode *root); + + private: + Traverser(); + bool visitUnary(Visit visit, TIntermUnary *node) override; + void nextIteration(); + + bool mFound = false; +}; + +// static +bool Traverser::Apply(TCompiler *compiler, TIntermNode *root) +{ + Traverser traverser; + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.mFound) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.mFound); + + return true; +} + +Traverser::Traverser() : TIntermTraverser(true, false, false) {} + +void Traverser::nextIteration() +{ + mFound = false; +} + +bool Traverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (mFound) + { + return false; + } + + // Detect if the current operator is unary minus operator. + if (node->getOp() != EOpNegative) + { + return true; + } + + // Detect if the current operand is a float variable. + TIntermTyped *fValue = node->getOperand(); + if (!fValue->getType().isScalarFloat()) + { + return true; + } + + // 0.0 - float + TIntermTyped *zero = CreateZeroNode(fValue->getType()); + zero->setLine(fValue->getLine()); + TIntermBinary *sub = new TIntermBinary(EOpSub, zero, fValue); + sub->setLine(fValue->getLine()); + + queueReplacement(sub, OriginalNode::IS_DROPPED); + + mFound = true; + return false; +} + +} // anonymous namespace + +bool RewriteUnaryMinusOperatorFloat(TCompiler *compiler, TIntermNode *root) +{ + return Traverser::Apply(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h new file mode 100644 index 0000000000..30c4d25d01 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h @@ -0,0 +1,31 @@ +// Copyright 2016 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. +// +// Rewrite "-float" to "0.0 - float" to work around unary minus operator on float issue on Intel Mac +// OSX 10.11. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEUNARYMINUSOPERATORFLOAT_H_ +#define COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEUNARYMINUSOPERATORFLOAT_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS) +[[nodiscard]] bool RewriteUnaryMinusOperatorFloat(TCompiler *compiler, TIntermNode *root); +#else +[[nodiscard]] ANGLE_INLINE bool RewriteUnaryMinusOperatorFloat(TCompiler *compiler, + TIntermNode *root) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEUNARYMINUSOPERATORFLOAT_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp new file mode 100644 index 0000000000..55e2f8b971 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp @@ -0,0 +1,74 @@ +// +// 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. +// + +#include "compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// "x || y" is equivalent to "x ? true : y". +TIntermTernary *UnfoldOR(TIntermTyped *x, TIntermTyped *y) +{ + return new TIntermTernary(x, CreateBoolNode(true), y); +} + +// "x && y" is equivalent to "x ? y : false". +TIntermTernary *UnfoldAND(TIntermTyped *x, TIntermTyped *y) +{ + return new TIntermTernary(x, y, CreateBoolNode(false)); +} + +// This traverser identifies all the short circuit binary nodes that need to +// be replaced, and creates the corresponding replacement nodes. However, +// the actual replacements happen after the traverse through updateTree(). + +class UnfoldShortCircuitASTTraverser : public TIntermTraverser +{ + public: + UnfoldShortCircuitASTTraverser() : TIntermTraverser(true, false, false) {} + + bool visitBinary(Visit visit, TIntermBinary *) override; +}; + +bool UnfoldShortCircuitASTTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + TIntermTernary *replacement = nullptr; + + switch (node->getOp()) + { + case EOpLogicalOr: + replacement = UnfoldOR(node->getLeft(), node->getRight()); + break; + case EOpLogicalAnd: + replacement = UnfoldAND(node->getLeft(), node->getRight()); + break; + default: + break; + } + if (replacement) + { + queueReplacement(replacement, OriginalNode::IS_DROPPED); + } + return true; +} + +} // anonymous namespace + +bool UnfoldShortCircuitAST(TCompiler *compiler, TIntermBlock *root) +{ + UnfoldShortCircuitASTTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h new file mode 100644 index 0000000000..648c7190a9 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h @@ -0,0 +1,33 @@ +// +// 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. +// +// UnfoldShortCircuitAST is an AST traverser to replace short-circuiting +// operations with ternary operations. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_UNFOLDSHORTCIRCUITAST_H_ +#define COMPILER_TRANSLATOR_TREEOPS_APPLE_UNFOLDSHORTCIRCUITAST_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; + +#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS) +[[nodiscard]] bool UnfoldShortCircuitAST(TCompiler *compiler, TIntermBlock *root); +#else +[[nodiscard]] ANGLE_INLINE bool UnfoldShortCircuitAST(TCompiler *compiler, TIntermBlock *root) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_UNFOLDSHORTCIRCUITAST_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp new file mode 100644 index 0000000000..8790c4f5f2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp @@ -0,0 +1,61 @@ +// +// Copyright 2016 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. +// +// AddDefaultReturnStatements.cpp: Add default return statements to functions that do not end in a +// return. +// + +#include "compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +bool NeedsReturnStatement(TIntermFunctionDefinition *node, TType *returnType) +{ + *returnType = node->getFunctionPrototype()->getType(); + if (returnType->getBasicType() == EbtVoid) + { + return false; + } + + TIntermBlock *bodyNode = node->getBody(); + TIntermBranch *returnNode = bodyNode->getSequence()->back()->getAsBranchNode(); + if (returnNode != nullptr && returnNode->getFlowOp() == EOpReturn) + { + return false; + } + + return true; +} + +} // anonymous namespace + +bool AddDefaultReturnStatements(TCompiler *compiler, TIntermBlock *root) +{ + TType returnType; + for (TIntermNode *node : *root->getSequence()) + { + TIntermFunctionDefinition *definition = node->getAsFunctionDefinition(); + if (definition != nullptr && NeedsReturnStatement(definition, &returnType)) + { + TIntermBranch *branch = new TIntermBranch(EOpReturn, CreateZeroNode(returnType)); + + TIntermBlock *bodyNode = definition->getBody(); + bodyNode->getSequence()->push_back(branch); + } + } + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h new file mode 100644 index 0000000000..52b601514c --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h @@ -0,0 +1,24 @@ +// +// Copyright 2016 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. +// +// AddDefaultReturnStatements.h: Add default return statements to functions that do not end in a +// return. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool AddDefaultReturnStatements(TCompiler *compiler, TIntermBlock *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp new file mode 100644 index 0000000000..d9a0c9bebc --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp @@ -0,0 +1,82 @@ +// +// Copyright 2022 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/d3d/AggregateAssignArraysInSSBOs.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +class AggregateAssignArraysInSSBOsTraverser : public TIntermTraverser +{ + public: + AggregateAssignArraysInSSBOsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) + {} + + protected: + bool visitBinary(Visit visit, TIntermBinary *node) override + { + // Replace all aggregate assignments to arrays in SSBOs with element-by-element assignments. + // TODO(anglebug.com/7363): this implementation only works for the simple case (assignment + // statement), not more complex cases such as assignment-as-expression or functions with + // side effects in the RHS. + + if (node->getOp() != EOpAssign) + { + return true; + } + else if (!node->getLeft()->getType().isArray()) + { + return true; + } + else if (!IsInShaderStorageBlock(node->getLeft())) + { + return true; + } + const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>(); + auto *indexVariable = CreateTempVariable(mSymbolTable, mediumpIndexType); + auto *indexInit = + CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType())); + auto *arraySizeNode = CreateIndexNode(node->getOutermostArraySize()); + auto *indexSymbolNode = CreateTempSymbolNode(indexVariable); + auto *cond = new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode); + auto *indexIncrement = + new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr); + auto *forLoopBody = new TIntermBlock(); + auto *indexedLeft = + new TIntermBinary(EOpIndexDirect, node->getLeft(), indexSymbolNode->deepCopy()); + auto *indexedRight = + new TIntermBinary(EOpIndexDirect, node->getRight(), indexSymbolNode->deepCopy()); + auto *assign = new TIntermBinary(TOperator::EOpAssign, indexedLeft, indexedRight); + forLoopBody->appendStatement(assign); + auto *forLoop = + new TIntermLoop(ELoopFor, indexInit, cond, indexIncrement, EnsureBlock(forLoopBody)); + queueReplacement(forLoop, OriginalNode::IS_DROPPED); + return false; + } +}; + +} // namespace + +bool AggregateAssignArraysInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + AggregateAssignArraysInSSBOsTraverser traverser(symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h new file mode 100644 index 0000000000..ce965bb002 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h @@ -0,0 +1,23 @@ +// +// Copyright 2022 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool AggregateAssignArraysInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp new file mode 100644 index 0000000000..4293f4c4da --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp @@ -0,0 +1,76 @@ +// +// Copyright 2022 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/d3d/AggregateAssignStructsInSSBOs.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +class AggregateAssignStructsInSSBOsTraverser : public TIntermTraverser +{ + public: + AggregateAssignStructsInSSBOsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable) + {} + + protected: + bool visitBinary(Visit visit, TIntermBinary *node) override + { + // Replace all assignments to structs in SSBOs with field-by-field asignments. + // TODO(anglebug.com/7362): this implementation only works for the simple case (assignment + // statement), not more complex cases such as assignment-as-expression or functions with + // side effects in the RHS. + const TStructure *s; + if (node->getOp() != EOpAssign) + { + return true; + } + else if (!IsInShaderStorageBlock(node->getLeft())) + { + return true; + } + else if (!(s = node->getLeft()->getType().getStruct())) + { + return true; + } + ASSERT(node->getRight()->getType().getStruct() == s); + auto *block = new TIntermBlock(); + for (int i = 0; i < static_cast<int>(s->fields().size()); ++i) + { + auto *left = new TIntermBinary(EOpIndexDirectStruct, node->getLeft()->deepCopy(), + CreateIndexNode(i)); + auto *right = new TIntermBinary(EOpIndexDirectStruct, node->getRight()->deepCopy(), + CreateIndexNode(i)); + auto *assign = new TIntermBinary(TOperator::EOpAssign, left, right); + block->appendStatement(assign); + } + + queueReplacement(block, OriginalNode::IS_DROPPED); + return false; + } +}; + +} // namespace + +bool AggregateAssignStructsInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + AggregateAssignStructsInSSBOsTraverser traverser(symbolTable); + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h new file mode 100644 index 0000000000..910345902b --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h @@ -0,0 +1,23 @@ +// +// Copyright 2022 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +[[nodiscard]] bool AggregateAssignStructsInSSBOs(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp new file mode 100644 index 0000000000..54d8fc0808 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp @@ -0,0 +1,233 @@ +// +// 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. +// +// The ArrayReturnValueToOutParameter function changes return values of an array type to out +// parameters in function definitions, prototypes, and call sites. + +#include "compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h" + +#include <map> + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +constexpr const ImmutableString kReturnValueVariableName("angle_return"); + +class ArrayReturnValueToOutParameterTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + + private: + ArrayReturnValueToOutParameterTraverser(TSymbolTable *symbolTable); + + void visitFunctionPrototype(TIntermFunctionPrototype *node) override; + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitBranch(Visit visit, TIntermBranch *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + + TIntermAggregate *createReplacementCall(TIntermAggregate *originalCall, + TIntermTyped *returnValueTarget); + + // Set when traversal is inside a function with array return value. + TIntermFunctionDefinition *mFunctionWithArrayReturnValue; + + struct ChangedFunction + { + const TVariable *returnValueVariable; + const TFunction *func; + }; + + // Map from function symbol ids to the changed function. + std::map<int, ChangedFunction> mChangedFunctions; +}; + +TIntermAggregate *ArrayReturnValueToOutParameterTraverser::createReplacementCall( + TIntermAggregate *originalCall, + TIntermTyped *returnValueTarget) +{ + TIntermSequence replacementArguments; + TIntermSequence *originalArguments = originalCall->getSequence(); + for (auto &arg : *originalArguments) + { + replacementArguments.push_back(arg); + } + replacementArguments.push_back(returnValueTarget); + ASSERT(originalCall->getFunction()); + const TSymbolUniqueId &originalId = originalCall->getFunction()->uniqueId(); + TIntermAggregate *replacementCall = TIntermAggregate::CreateFunctionCall( + *mChangedFunctions[originalId.get()].func, &replacementArguments); + replacementCall->setLine(originalCall->getLine()); + return replacementCall; +} + +bool ArrayReturnValueToOutParameterTraverser::apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + ArrayReturnValueToOutParameterTraverser arrayReturnValueToOutParam(symbolTable); + root->traverse(&arrayReturnValueToOutParam); + return arrayReturnValueToOutParam.updateTree(compiler, root); +} + +ArrayReturnValueToOutParameterTraverser::ArrayReturnValueToOutParameterTraverser( + TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), mFunctionWithArrayReturnValue(nullptr) +{} + +bool ArrayReturnValueToOutParameterTraverser::visitFunctionDefinition( + Visit visit, + TIntermFunctionDefinition *node) +{ + if (node->getFunctionPrototype()->isArray() && visit == PreVisit) + { + // Replacing the function header is done on visitFunctionPrototype(). + mFunctionWithArrayReturnValue = node; + } + if (visit == PostVisit) + { + mFunctionWithArrayReturnValue = nullptr; + } + return true; +} + +void ArrayReturnValueToOutParameterTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node) +{ + if (node->isArray()) + { + // Replace the whole prototype node with another node that has the out parameter + // added. Also set the function to return void. + const TSymbolUniqueId &functionId = node->getFunction()->uniqueId(); + if (mChangedFunctions.find(functionId.get()) == mChangedFunctions.end()) + { + TType *returnValueVariableType = new TType(node->getType()); + returnValueVariableType->setQualifier(EvqParamOut); + ChangedFunction changedFunction; + changedFunction.returnValueVariable = + new TVariable(mSymbolTable, kReturnValueVariableName, returnValueVariableType, + SymbolType::AngleInternal); + TFunction *func = new TFunction(mSymbolTable, node->getFunction()->name(), + node->getFunction()->symbolType(), + StaticType::GetBasic<EbtVoid, EbpUndefined>(), false); + for (size_t i = 0; i < node->getFunction()->getParamCount(); ++i) + { + func->addParameter(node->getFunction()->getParam(i)); + } + func->addParameter(changedFunction.returnValueVariable); + changedFunction.func = func; + mChangedFunctions[functionId.get()] = changedFunction; + } + TIntermFunctionPrototype *replacement = + new TIntermFunctionPrototype(mChangedFunctions[functionId.get()].func); + replacement->setLine(node->getLine()); + + queueReplacement(replacement, OriginalNode::IS_DROPPED); + } +} + +bool ArrayReturnValueToOutParameterTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + ASSERT(!node->isArray() || node->getOp() != EOpCallInternalRawFunction); + if (visit == PreVisit && node->isArray() && node->getOp() == EOpCallFunctionInAST) + { + // Handle call sites where the returned array is not assigned. + // Examples where f() is a function returning an array: + // 1. f(); + // 2. another_array == f(); + // 3. another_function(f()); + // 4. return f(); + // Cases 2 to 4 are already converted to simpler cases by + // SeparateExpressionsReturningArrays, so we only need to worry about the case where a + // function call returning an array forms an expression by itself. + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + if (parentBlock) + { + // replace + // f(); + // with + // type s0[size]; f(s0); + TIntermSequence replacements; + + // type s0[size]; + TIntermDeclaration *returnValueDeclaration = nullptr; + TVariable *returnValue = DeclareTempVariable(mSymbolTable, new TType(node->getType()), + EvqTemporary, &returnValueDeclaration); + replacements.push_back(returnValueDeclaration); + + // f(s0); + TIntermSymbol *returnValueSymbol = CreateTempSymbolNode(returnValue); + replacements.push_back(createReplacementCall(node, returnValueSymbol)); + mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements)); + } + return false; + } + return true; +} + +bool ArrayReturnValueToOutParameterTraverser::visitBranch(Visit visit, TIntermBranch *node) +{ + if (mFunctionWithArrayReturnValue && node->getFlowOp() == EOpReturn) + { + // Instead of returning a value, assign to the out parameter and then return. + TIntermSequence replacements; + + TIntermTyped *expression = node->getExpression(); + ASSERT(expression != nullptr); + const TSymbolUniqueId &functionId = + mFunctionWithArrayReturnValue->getFunction()->uniqueId(); + ASSERT(mChangedFunctions.find(functionId.get()) != mChangedFunctions.end()); + TIntermSymbol *returnValueSymbol = + new TIntermSymbol(mChangedFunctions[functionId.get()].returnValueVariable); + TIntermBinary *replacementAssignment = + new TIntermBinary(EOpAssign, returnValueSymbol, expression); + replacementAssignment->setLine(expression->getLine()); + replacements.push_back(replacementAssignment); + + TIntermBranch *replacementBranch = new TIntermBranch(EOpReturn, nullptr); + replacementBranch->setLine(node->getLine()); + replacements.push_back(replacementBranch); + + mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, + std::move(replacements)); + } + return false; +} + +bool ArrayReturnValueToOutParameterTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (node->getOp() == EOpAssign && node->getLeft()->isArray()) + { + TIntermAggregate *rightAgg = node->getRight()->getAsAggregate(); + ASSERT(rightAgg == nullptr || rightAgg->getOp() != EOpCallInternalRawFunction); + if (rightAgg != nullptr && rightAgg->getOp() == EOpCallFunctionInAST) + { + TIntermAggregate *replacementCall = createReplacementCall(rightAgg, node->getLeft()); + queueReplacement(replacementCall, OriginalNode::IS_DROPPED); + } + } + return false; +} + +} // namespace + +bool ArrayReturnValueToOutParameter(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + return ArrayReturnValueToOutParameterTraverser::apply(compiler, root, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h new file mode 100644 index 0000000000..ae8d04ae9a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h @@ -0,0 +1,27 @@ +// +// 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. +// +// The ArrayReturnValueToOutParameter function changes return values of an array type to out +// parameters in function definitions, prototypes and call sites. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool ArrayReturnValueToOutParameter(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp new file mode 100644 index 0000000000..908da7a8c2 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp @@ -0,0 +1,110 @@ +// +// Copyright 2016 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. +// + +// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend +// may record a variable as aliasing another. Sometimes the alias information gets garbled +// so we work around this issue by breaking the aliasing chain in inner loops. + +#include "compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +// A HLSL compiler developer gave us more details on the root cause and the workaround needed: +// The root problem is that if the HLSL compiler is applying aliasing information even on +// incomplete simulations (in this case, a single pass). The bug is triggered by an assignment +// that comes from a series of assignments, possibly with swizzled or ternary operators with +// known conditionals, where the source is before the loop. +// So, a workaround is to add a +0 term to variables the first time they are assigned to in +// an inner loop (if they are declared in an outside scope, otherwise there is no need). +// This will break the aliasing chain. + +// For simplicity here we add a +0 to any assignment that is in at least two nested loops. Because +// the bug only shows up with swizzles, and ternary assignment, whole array or whole structure +// assignment don't need a workaround. + +namespace sh +{ + +namespace +{ + +class AliasingBreaker : public TIntermTraverser +{ + public: + AliasingBreaker() : TIntermTraverser(true, false, true) {} + + protected: + bool visitBinary(Visit visit, TIntermBinary *binary) override + { + if (visit != PreVisit) + { + return false; + } + + if (mLoopLevel < 2 || !binary->isAssignment()) + { + return true; + } + + TIntermTyped *B = binary->getRight(); + TType type = B->getType(); + + if (!type.isScalar() && !type.isVector() && !type.isMatrix()) + { + return true; + } + + if (type.isArray() || IsSampler(type.getBasicType())) + { + return true; + } + + // We have a scalar / vector / matrix assignment with loop depth 2. + // Transform it from + // A = B + // to + // A = (B + typeof<B>(0)); + + TIntermBinary *bPlusZero = new TIntermBinary(EOpAdd, B, CreateZeroNode(type)); + bPlusZero->setLine(B->getLine()); + + binary->replaceChildNode(B, bPlusZero); + + return true; + } + + bool visitLoop(Visit visit, TIntermLoop *loop) override + { + if (visit == PreVisit) + { + mLoopLevel++; + } + else + { + ASSERT(mLoopLevel > 0); + mLoopLevel--; + } + + return true; + } + + private: + int mLoopLevel = 0; +}; + +} // anonymous namespace + +bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root) +{ + AliasingBreaker breaker; + root->traverse(&breaker); + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h new file mode 100644 index 0000000000..455c7c322d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h @@ -0,0 +1,25 @@ +// +// Copyright 2016 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. +// + +// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend +// may record a variable as aliasing another. Sometimes the alias information gets garbled +// so we work around this issue by breaking the aliasing chain in inner loops. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp new file mode 100644 index 0000000000..e873db56cd --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp @@ -0,0 +1,152 @@ +// +// Copyright 2016 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. +// +// Implementation of the integer pow expressions HLSL bug workaround. +// See header for more info. + +#include "compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h" + +#include <cmath> +#include <cstdlib> + +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool Apply(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + + private: + Traverser(TSymbolTable *symbolTable); + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + void nextIteration(); + + bool mFound = false; +}; + +// static +bool Traverser::Apply(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + Traverser traverser(symbolTable); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.mFound) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.mFound); + + return true; +} + +Traverser::Traverser(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable) +{} + +void Traverser::nextIteration() +{ + mFound = false; +} + +bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (mFound) + { + return false; + } + + // Test 0: skip non-pow operators. + if (node->getOp() != EOpPow) + { + return true; + } + + const TIntermSequence *sequence = node->getSequence(); + ASSERT(sequence->size() == 2u); + const TIntermConstantUnion *constantExponent = sequence->at(1)->getAsConstantUnion(); + + // Test 1: check for a single constant. + if (!constantExponent || constantExponent->getNominalSize() != 1) + { + return true; + } + + float exponentValue = constantExponent->getConstantValue()->getFConst(); + + // Test 2: exponentValue is in the problematic range. + if (exponentValue < -5.0f || exponentValue > 9.0f) + { + return true; + } + + // Test 3: exponentValue is integer or pretty close to an integer. + if (std::abs(exponentValue - std::round(exponentValue)) > 0.0001f) + { + return true; + } + + // Test 4: skip -1, 0, and 1 + int exponent = static_cast<int>(std::round(exponentValue)); + int n = std::abs(exponent); + if (n < 2) + { + return true; + } + + // Potential problem case detected, apply workaround. + + TIntermTyped *lhs = sequence->at(0)->getAsTyped(); + ASSERT(lhs); + + TIntermDeclaration *lhsVariableDeclaration = nullptr; + TVariable *lhsVariable = + DeclareTempVariable(mSymbolTable, lhs, EvqTemporary, &lhsVariableDeclaration); + insertStatementInParentBlock(lhsVariableDeclaration); + + // Create a chain of n-1 multiples. + TIntermTyped *current = CreateTempSymbolNode(lhsVariable); + for (int i = 1; i < n; ++i) + { + TIntermBinary *mul = new TIntermBinary(EOpMul, current, CreateTempSymbolNode(lhsVariable)); + mul->setLine(node->getLine()); + current = mul; + } + + // For negative pow, compute the reciprocal of the positive pow. + if (exponent < 0) + { + TConstantUnion *oneVal = new TConstantUnion(); + oneVal->setFConst(1.0f); + TIntermConstantUnion *oneNode = new TIntermConstantUnion(oneVal, node->getType()); + TIntermBinary *div = new TIntermBinary(EOpDiv, oneNode, current); + current = div; + } + + queueReplacement(current, OriginalNode::IS_DROPPED); + mFound = true; + return false; +} + +} // anonymous namespace + +bool ExpandIntegerPowExpressions(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + return Traverser::Apply(compiler, root, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h new file mode 100644 index 0000000000..2404fb08ac --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h @@ -0,0 +1,34 @@ +// +// Copyright 2016 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. +// +// This mutating tree traversal works around a bug in the HLSL compiler optimizer with "pow" that +// manifests under the following conditions: +// +// - If pow() has a literal exponent value +// - ... and this value is integer or within 10e-6 of an integer +// - ... and it is in {-4, -3, -2, 2, 3, 4, 5, 6, 7, 8} +// +// The workaround is to replace the pow with a series of multiplies. +// See http://anglebug.com/851 + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool ExpandIntegerPowExpressions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp new file mode 100644 index 0000000000..18d49814ac --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp @@ -0,0 +1,385 @@ +// +// Copyright 2020 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. +// +// RecordUniformBlocksWithLargeArrayMember.h: +// Collect all uniform blocks which have one or more large array members, +// and the array sizes are greater than or equal to 50. If some of them +// satify some conditions, we will translate them to StructuredBuffers +// on Direct3D backend. +// + +#include "compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ +// Only when a uniform block member's array size is greater than or equal to +// kMinArraySizeUseStructuredBuffer, then we may translate the uniform block +// to a StructuredBuffer on Direct3D backend. +const unsigned int kMinArraySizeUseStructuredBuffer = 50u; + +// There is a maximum of D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT(128) slots that are +// available for shader resources on Direct3D 11. When shader version is 300, we only use +// D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT(16) slots for texture units. We allow StructuredBuffer +// to use the maximum of 60 slots, that is enough here. +const unsigned int kMaxAllowToUseRegisterCount = 60u; + +// Traverser that all uniform blocks which have one or more large array members, and the array +// sizes are greater than or equal to 50. +class UniformBlocksWithLargeArrayMemberTraverser : public TIntermTraverser +{ + public: + UniformBlocksWithLargeArrayMemberTraverser(); + + void visitSymbol(TIntermSymbol *node) override; + bool visitBinary(Visit visit, TIntermBinary *node) override; + std::map<int, const TInterfaceBlock *> &getUniformBlockMayTranslation() + { + return mUniformBlockMayTranslation; + } + std::map<int, const TInterfaceBlock *> &getUniformBlockNotAllowTranslation() + { + return mUniformBlockNotAllowTranslation; + } + std::map<int, unsigned int> &getUniformBlockUsedRegisterCount() + { + return mUniformBlockUsedRegisterCount; + } + std::map<int, const TInterfaceBlock *> &getUniformBlockWithLargeArrayMember() + { + return mUniformBlockWithLargeArrayMember; + } + + private: + std::map<int, const TInterfaceBlock *> mUniformBlockMayTranslation; + std::map<int, const TInterfaceBlock *> mUniformBlockNotAllowTranslation; + std::map<int, unsigned int> mUniformBlockUsedRegisterCount; + std::map<int, const TInterfaceBlock *> mUniformBlockWithLargeArrayMember; +}; + +UniformBlocksWithLargeArrayMemberTraverser::UniformBlocksWithLargeArrayMemberTraverser() + : TIntermTraverser(true, true, false) +{} + +static bool IsSupportedTypeForStructuredBuffer(const TType &type) +{ + const TStructure *structure = type.getStruct(); + const TLayoutMatrixPacking matrixPacking = type.getLayoutQualifier().matrixPacking; + if (structure) + { + const TFieldList &fields = structure->fields(); + for (size_t i = 0; i < fields.size(); i++) + { + const TType &fieldType = *fields[i]->type(); + // Do not allow the structure's member is array or structure. + if (!fieldType.isArray() && !fieldType.getStruct() && + (fieldType.isScalar() || fieldType.isVector() || + (fieldType.isMatrix() && + ((matrixPacking != EmpRowMajor && fieldType.getRows() == 4) || + (matrixPacking == EmpRowMajor && fieldType.getCols() == 4))))) + { + return true; + } + } + return false; + } + else if (type.isMatrix()) + { + // Only supports the matrix types that we do not need to pad in a structure or an array + // explicitly. + return (matrixPacking != EmpRowMajor && type.getRows() == 4) || + (matrixPacking == EmpRowMajor && type.getCols() == 4); + } + else + { + // Supports vector and scalar types in a structure or an array. + return true; + } +} + +static bool CanTranslateUniformBlockToStructuredBuffer(const TInterfaceBlock &interfaceBlock) +{ + const TLayoutBlockStorage blockStorage = interfaceBlock.blockStorage(); + + if (blockStorage == EbsStd140 && interfaceBlock.fields().size() == 1u) + { + const TType &fieldType = *interfaceBlock.fields()[0]->type(); + if (fieldType.getNumArraySizes() == 1u && + fieldType.getOutermostArraySize() >= kMinArraySizeUseStructuredBuffer) + { + return IsSupportedTypeForStructuredBuffer(fieldType); + } + } + + return false; +} + +static bool FieldIsOrHasLargeArrayField(const TField &field) +{ + const TType *type = field.type(); + if (type->getArraySizeProduct() >= kMinArraySizeUseStructuredBuffer) + { + return true; + } + + const TStructure *structure = type->getStruct(); + if (structure) + { + const TFieldList &fields = structure->fields(); + bool hasLargeArrayField = false; + for (size_t i = 0; i < fields.size(); i++) + { + hasLargeArrayField = FieldIsOrHasLargeArrayField(*fields[i]); + if (hasLargeArrayField) + { + break; + } + } + return hasLargeArrayField; + } + + return false; +} + +static bool IsInterfaceBlockWithLargeArrayField(const TInterfaceBlock &interfaceBlock) +{ + const TFieldList &fields = interfaceBlock.fields(); + bool isLargeArrayField = false; + for (size_t i = 0; i < fields.size(); i++) + { + isLargeArrayField = FieldIsOrHasLargeArrayField(*fields[i]); + if (isLargeArrayField) + { + break; + } + } + + return isLargeArrayField; +} + +void UniformBlocksWithLargeArrayMemberTraverser::visitSymbol(TIntermSymbol *node) +{ + const TVariable &variable = node->variable(); + const TType &variableType = variable.getType(); + TQualifier qualifier = variable.getType().getQualifier(); + + if (qualifier == EvqUniform) + { + const TInterfaceBlock *interfaceBlock = variableType.getInterfaceBlock(); + if (interfaceBlock) + { + if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock)) + { + if (mUniformBlockMayTranslation.count(interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockMayTranslation[interfaceBlock->uniqueId().get()] = interfaceBlock; + } + + if (!variableType.isInterfaceBlock()) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + // The uniform block variable is array type, only indexing operator is allowed + // to operate on the variable, otherwise do not translate the uniform block to + // HLSL StructuredBuffer. + if (!accessorAsBinary || + !(accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect))) + { + if (mUniformBlockNotAllowTranslation.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockNotAllowTranslation[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + else + { + if (mUniformBlockUsedRegisterCount.count( + interfaceBlock->uniqueId().get()) == 0) + { + // The uniform block is not an instanced one, so it only uses one + // register. + mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = 1; + } + } + } + else + { + if (mUniformBlockUsedRegisterCount.count(interfaceBlock->uniqueId().get()) == 0) + { + // The uniform block is an instanced one, the count of used registers + // depends on the array size of variable. + mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = + variableType.isArray() ? variableType.getOutermostArraySize() : 1; + } + } + } + + if (interfaceBlock->blockStorage() == EbsStd140 && + IsInterfaceBlockWithLargeArrayField(*interfaceBlock)) + { + if (!variableType.isInterfaceBlock()) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + if (accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect)) + { + if (mUniformBlockWithLargeArrayMember.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + } + } + } + } +} + +bool UniformBlocksWithLargeArrayMemberTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + switch (node->getOp()) + { + case EOpIndexDirect: + { + if (visit == PreVisit) + { + const TType &leftType = node->getLeft()->getType(); + if (leftType.isInterfaceBlock()) + { + const TInterfaceBlock *interfaceBlock = leftType.getInterfaceBlock(); + if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock) && + mUniformBlockMayTranslation.count(interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockMayTranslation[interfaceBlock->uniqueId().get()] = + interfaceBlock; + if (mUniformBlockUsedRegisterCount.count( + interfaceBlock->uniqueId().get()) == 0) + { + // The uniform block is an instanced one, the count of used registers + // depends on the array size of variable. + mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = + leftType.isArray() ? leftType.getOutermostArraySize() : 1; + } + return false; + } + + if (interfaceBlock->blockStorage() == EbsStd140 && + IsInterfaceBlockWithLargeArrayField(*interfaceBlock)) + { + if (mUniformBlockWithLargeArrayMember.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + } + } + break; + } + case EOpIndexDirectInterfaceBlock: + { + if (visit == InVisit) + { + const TInterfaceBlock *interfaceBlock = + node->getLeft()->getType().getInterfaceBlock(); + if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock)) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + // The uniform block variable is array type, only indexing operator is allowed + // to operate on the variable, otherwise do not translate the uniform block to + // HLSL StructuredBuffer. + if ((!accessorAsBinary || + !(accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect))) && + mUniformBlockNotAllowTranslation.count(interfaceBlock->uniqueId().get()) == + 0) + { + mUniformBlockNotAllowTranslation[interfaceBlock->uniqueId().get()] = + interfaceBlock; + return false; + } + } + + if (interfaceBlock->blockStorage() == EbsStd140 && + IsInterfaceBlockWithLargeArrayField(*interfaceBlock)) + { + TIntermNode *accessor = getAncestorNode(0); + TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode(); + if (accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect || + accessorAsBinary->getOp() == EOpIndexIndirect)) + { + if (mUniformBlockWithLargeArrayMember.count( + interfaceBlock->uniqueId().get()) == 0) + { + mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] = + interfaceBlock; + } + } + } + } + break; + } + default: + break; + } + + return true; +} +} // namespace + +bool RecordUniformBlocksWithLargeArrayMember( + TIntermNode *root, + std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap, + std::set<std::string> &slowCompilingUniformBlockSet) +{ + UniformBlocksWithLargeArrayMemberTraverser traverser; + root->traverse(&traverser); + std::map<int, const TInterfaceBlock *> &uniformBlockMayTranslation = + traverser.getUniformBlockMayTranslation(); + std::map<int, const TInterfaceBlock *> &uniformBlockNotAllowTranslation = + traverser.getUniformBlockNotAllowTranslation(); + std::map<int, unsigned int> &uniformBlockUsedRegisterCount = + traverser.getUniformBlockUsedRegisterCount(); + std::map<int, const TInterfaceBlock *> &uniformBlockWithLargeArrayMember = + traverser.getUniformBlockWithLargeArrayMember(); + + unsigned int usedRegisterCount = 0; + for (auto &uniformBlock : uniformBlockMayTranslation) + { + if (uniformBlockNotAllowTranslation.count(uniformBlock.first) == 0) + { + usedRegisterCount += uniformBlockUsedRegisterCount[uniformBlock.first]; + if (usedRegisterCount > kMaxAllowToUseRegisterCount) + { + break; + } + uniformBlockOptimizedMap[uniformBlock.first] = uniformBlock.second; + } + } + + for (auto &uniformBlock : uniformBlockWithLargeArrayMember) + { + if (uniformBlockOptimizedMap.count(uniformBlock.first) == 0) + { + slowCompilingUniformBlockSet.insert(uniformBlock.second->name().data()); + } + } + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h new file mode 100644 index 0000000000..d7825c28cb --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h @@ -0,0 +1,28 @@ +// +// Copyright 2020 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. +// +// RecordUniformBlocksWithLargeArrayMember.h: +// Collect all uniform blocks which have one or more large array members, +// and the array sizes are greater than or equal to 50. If some of them +// satify some conditions, we will translate them to StructuredBuffers +// on Direct3D backend. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_ + +#include "compiler/translator/IntermNode.h" + +namespace sh +{ +class TIntermNode; + +[[nodiscard]] bool RecordUniformBlocksWithLargeArrayMember( + TIntermNode *root, + std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap, + std::set<std::string> &slowCompilingUniformBlockSet); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp new file mode 100644 index 0000000000..6360f1083c --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp @@ -0,0 +1,270 @@ +// +// 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. +// +// RemoveSwitchFallThrough.cpp: Remove fall-through from switch statements. +// Note that it is unsafe to do further AST transformations on the AST generated +// by this function. It leaves duplicate nodes in the AST making replacements +// unreliable. + +#include "compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h" + +#include "compiler/translator/Diagnostics.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class RemoveSwitchFallThroughTraverser : public TIntermTraverser +{ + public: + static TIntermBlock *removeFallThrough(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics); + + private: + RemoveSwitchFallThroughTraverser(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics); + + void visitSymbol(TIntermSymbol *node) override; + void visitConstantUnion(TIntermConstantUnion *node) override; + bool visitDeclaration(Visit, TIntermDeclaration *node) override; + bool visitBinary(Visit, TIntermBinary *node) override; + bool visitUnary(Visit, TIntermUnary *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + bool visitSwizzle(Visit, TIntermSwizzle *node) override; + bool visitIfElse(Visit visit, TIntermIfElse *node) override; + bool visitSwitch(Visit, TIntermSwitch *node) override; + bool visitCase(Visit, TIntermCase *node) override; + bool visitAggregate(Visit, TIntermAggregate *node) override; + bool visitBlock(Visit, TIntermBlock *node) override; + bool visitLoop(Visit, TIntermLoop *node) override; + bool visitBranch(Visit, TIntermBranch *node) override; + + void outputSequence(TIntermSequence *sequence, size_t startIndex); + void handlePreviousCase(); + + TIntermBlock *mStatementList; + TIntermBlock *mStatementListOut; + bool mLastStatementWasBreak; + TIntermBlock *mPreviousCase; + std::vector<TIntermBlock *> mCasesSharingBreak; + PerformanceDiagnostics *mPerfDiagnostics; +}; + +TIntermBlock *RemoveSwitchFallThroughTraverser::removeFallThrough( + TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics) +{ + RemoveSwitchFallThroughTraverser rm(statementList, perfDiagnostics); + ASSERT(statementList); + statementList->traverse(&rm); + ASSERT(rm.mPreviousCase || statementList->getSequence()->empty()); + if (!rm.mLastStatementWasBreak && rm.mPreviousCase) + { + // Make sure that there's a branch at the end of the final case inside the switch statement. + // This also ensures that any cases that fall through to the final case will get the break. + TIntermBranch *finalBreak = new TIntermBranch(EOpBreak, nullptr); + rm.mPreviousCase->getSequence()->push_back(finalBreak); + rm.mLastStatementWasBreak = true; + } + rm.handlePreviousCase(); + return rm.mStatementListOut; +} + +RemoveSwitchFallThroughTraverser::RemoveSwitchFallThroughTraverser( + TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics) + : TIntermTraverser(true, false, false), + mStatementList(statementList), + mLastStatementWasBreak(false), + mPreviousCase(nullptr), + mPerfDiagnostics(perfDiagnostics) +{ + mStatementListOut = new TIntermBlock(); +} + +void RemoveSwitchFallThroughTraverser::visitSymbol(TIntermSymbol *node) +{ + // Note that this assumes that switch statements which don't begin by a case statement + // have already been weeded out in validation. + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; +} + +void RemoveSwitchFallThroughTraverser::visitConstantUnion(TIntermConstantUnion *node) +{ + // Conditions of case labels are not traversed, so this is a constant statement like "0;". + // These are no-ops so there's no need to add them back to the statement list. Should have + // already been pruned out of the AST, in fact. + UNREACHABLE(); +} + +bool RemoveSwitchFallThroughTraverser::visitDeclaration(Visit, TIntermDeclaration *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitBinary(Visit, TIntermBinary *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitUnary(Visit, TIntermUnary *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitTernary(Visit, TIntermTernary *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitSwizzle(Visit, TIntermSwizzle *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitIfElse(Visit, TIntermIfElse *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitSwitch(Visit, TIntermSwitch *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + // Don't go into nested switch statements + return false; +} + +void RemoveSwitchFallThroughTraverser::outputSequence(TIntermSequence *sequence, size_t startIndex) +{ + for (size_t i = startIndex; i < sequence->size(); ++i) + { + mStatementListOut->getSequence()->push_back(sequence->at(i)); + } +} + +void RemoveSwitchFallThroughTraverser::handlePreviousCase() +{ + if (mPreviousCase) + mCasesSharingBreak.push_back(mPreviousCase); + if (mLastStatementWasBreak) + { + for (size_t i = 0; i < mCasesSharingBreak.size(); ++i) + { + ASSERT(!mCasesSharingBreak.at(i)->getSequence()->empty()); + if (mCasesSharingBreak.at(i)->getSequence()->size() == 1) + { + // Fall-through is allowed in case the label has no statements. + outputSequence(mCasesSharingBreak.at(i)->getSequence(), 0); + } + else + { + // Include all the statements that this case can fall through under the same label. + if (mCasesSharingBreak.size() > i + 1u) + { + mPerfDiagnostics->warning(mCasesSharingBreak.at(i)->getLine(), + "Performance: non-empty fall-through cases in " + "switch statements generate extra code.", + "switch"); + } + for (size_t j = i; j < mCasesSharingBreak.size(); ++j) + { + size_t startIndex = + j > i ? 1 : 0; // Add the label only from the first sequence. + outputSequence(mCasesSharingBreak.at(j)->getSequence(), startIndex); + } + } + } + mCasesSharingBreak.clear(); + } + mLastStatementWasBreak = false; + mPreviousCase = nullptr; +} + +bool RemoveSwitchFallThroughTraverser::visitCase(Visit, TIntermCase *node) +{ + handlePreviousCase(); + mPreviousCase = new TIntermBlock(); + mPreviousCase->getSequence()->push_back(node); + mPreviousCase->setLine(node->getLine()); + // Don't traverse the condition of the case statement + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitAggregate(Visit, TIntermAggregate *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool DoesBlockAlwaysBreak(TIntermBlock *node) +{ + if (node->getSequence()->empty()) + { + return false; + } + + TIntermBlock *lastStatementAsBlock = node->getSequence()->back()->getAsBlock(); + if (lastStatementAsBlock) + { + return DoesBlockAlwaysBreak(lastStatementAsBlock); + } + + TIntermBranch *lastStatementAsBranch = node->getSequence()->back()->getAsBranchNode(); + return lastStatementAsBranch != nullptr; +} + +bool RemoveSwitchFallThroughTraverser::visitBlock(Visit, TIntermBlock *node) +{ + if (node != mStatementList) + { + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = DoesBlockAlwaysBreak(node); + return false; + } + return true; +} + +bool RemoveSwitchFallThroughTraverser::visitLoop(Visit, TIntermLoop *node) +{ + mPreviousCase->getSequence()->push_back(node); + mLastStatementWasBreak = false; + return false; +} + +bool RemoveSwitchFallThroughTraverser::visitBranch(Visit, TIntermBranch *node) +{ + mPreviousCase->getSequence()->push_back(node); + // TODO: Verify that accepting return or continue statements here doesn't cause problems. + mLastStatementWasBreak = true; + return false; +} + +} // anonymous namespace + +TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics) +{ + return RemoveSwitchFallThroughTraverser::removeFallThrough(statementList, perfDiagnostics); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h new file mode 100644 index 0000000000..b92e7e5f6d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h @@ -0,0 +1,27 @@ +// +// 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. +// +// RemoveSwitchFallThrough.h: Remove fall-through from switch statements. +// Note that it is unsafe to do further AST transformations on the AST generated +// by this function. It leaves duplicate nodes in the AST making replacements +// unreliable. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_ + +namespace sh +{ + +class TIntermBlock; +class PerformanceDiagnostics; + +// When given a statementList from a switch AST node, return an updated +// statementList that has fall-through removed. +TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList, + PerformanceDiagnostics *perfDiagnostics); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp new file mode 100644 index 0000000000..b2de6079b7 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp @@ -0,0 +1,183 @@ +// +// Copyright 2018 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. +// +// Implementation of the function RewriteAtomicFunctionExpressions. +// See the header for more details. + +#include "compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h" + +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ +namespace +{ +// Traverser that simplifies all the atomic function expressions into the ones that can be directly +// translated into HLSL. +// +// case 1 (only for atomicExchange and atomicCompSwap): +// original: +// atomicExchange(counter, newValue); +// new: +// tempValue = atomicExchange(counter, newValue); +// +// case 2 (atomic function, temporary variable required): +// original: +// value = atomicAdd(counter, 1) * otherValue; +// someArray[atomicAdd(counter, 1)] = someOtherValue; +// new: +// value = ((tempValue = atomicAdd(counter, 1)), tempValue) * otherValue; +// someArray[((tempValue = atomicAdd(counter, 1)), tempValue)] = someOtherValue; +// +// case 3 (atomic function used directly initialize a variable): +// original: +// int value = atomicAdd(counter, 1); +// new: +// tempValue = atomicAdd(counter, 1); +// int value = tempValue; +// +class RewriteAtomicFunctionExpressionsTraverser : public TIntermTraverser +{ + public: + RewriteAtomicFunctionExpressionsTraverser(TSymbolTable *symbolTable, int shaderVersion); + + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitBlock(Visit visit, TIntermBlock *node) override; + + private: + static bool IsAtomicExchangeOrCompSwapNoReturnValue(TIntermAggregate *node, + TIntermNode *parentNode); + static bool IsAtomicFunctionInsideExpression(TIntermAggregate *node, TIntermNode *parentNode); + + void rewriteAtomicFunctionCallNode(TIntermAggregate *oldAtomicFunctionNode); + + const TVariable *getTempVariable(const TType *type); + + int mShaderVersion; + TIntermSequence mTempVariables; +}; + +RewriteAtomicFunctionExpressionsTraverser::RewriteAtomicFunctionExpressionsTraverser( + TSymbolTable *symbolTable, + int shaderVersion) + : TIntermTraverser(false, false, true, symbolTable), mShaderVersion(shaderVersion) +{} + +void RewriteAtomicFunctionExpressionsTraverser::rewriteAtomicFunctionCallNode( + TIntermAggregate *oldAtomicFunctionNode) +{ + ASSERT(oldAtomicFunctionNode); + + const TVariable *returnVariable = getTempVariable(&oldAtomicFunctionNode->getType()); + + TIntermBinary *rewrittenNode = new TIntermBinary( + TOperator::EOpAssign, CreateTempSymbolNode(returnVariable), oldAtomicFunctionNode); + + auto *parentNode = getParentNode(); + + auto *parentBinary = parentNode->getAsBinaryNode(); + if (parentBinary && parentBinary->getOp() == EOpInitialize) + { + insertStatementInParentBlock(rewrittenNode); + queueReplacement(CreateTempSymbolNode(returnVariable), OriginalNode::IS_DROPPED); + } + else + { + // As all atomic function assignment will be converted to the last argument of an + // interlocked function, if we need the return value, assignment needs to be wrapped with + // the comma operator and the temporary variables. + if (!parentNode->getAsBlock()) + { + rewrittenNode = TIntermBinary::CreateComma( + rewrittenNode, new TIntermSymbol(returnVariable), mShaderVersion); + } + + queueReplacement(rewrittenNode, OriginalNode::IS_DROPPED); + } +} + +const TVariable *RewriteAtomicFunctionExpressionsTraverser::getTempVariable(const TType *type) +{ + TIntermDeclaration *variableDeclaration; + TVariable *returnVariable = + DeclareTempVariable(mSymbolTable, type, EvqTemporary, &variableDeclaration); + mTempVariables.push_back(variableDeclaration); + return returnVariable; +} + +bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicExchangeOrCompSwapNoReturnValue( + TIntermAggregate *node, + TIntermNode *parentNode) +{ + ASSERT(node); + return (node->getOp() == EOpAtomicExchange || node->getOp() == EOpAtomicCompSwap) && + parentNode && parentNode->getAsBlock(); +} + +bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicFunctionInsideExpression( + TIntermAggregate *node, + TIntermNode *parentNode) +{ + ASSERT(node); + // We only need to handle atomic functions with a parent that it is not block nodes. If the + // parent node is block, it means that the atomic function is not inside an expression. + if (!BuiltInGroup::IsAtomicMemory(node->getOp()) || parentNode->getAsBlock()) + { + return false; + } + + auto *parentAsBinary = parentNode->getAsBinaryNode(); + // Assignments are handled in OutputHLSL + return !parentAsBinary || parentAsBinary->getOp() != EOpAssign; +} + +bool RewriteAtomicFunctionExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + ASSERT(visit == PostVisit); + // Skip atomic memory functions for SSBO. They will be processed in the OutputHLSL traverser. + if (BuiltInGroup::IsAtomicMemory(node->getOp()) && + IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped())) + { + return false; + } + + TIntermNode *parentNode = getParentNode(); + if (IsAtomicExchangeOrCompSwapNoReturnValue(node, parentNode) || + IsAtomicFunctionInsideExpression(node, parentNode)) + { + rewriteAtomicFunctionCallNode(node); + } + + return true; +} + +bool RewriteAtomicFunctionExpressionsTraverser::visitBlock(Visit visit, TIntermBlock *node) +{ + ASSERT(visit == PostVisit); + + if (!mTempVariables.empty() && getParentNode()->getAsFunctionDefinition()) + { + insertStatementsInBlockAtPosition(node, 0, mTempVariables, TIntermSequence()); + mTempVariables.clear(); + } + + return true; +} + +} // anonymous namespace + +bool RewriteAtomicFunctionExpressions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + int shaderVersion) +{ + RewriteAtomicFunctionExpressionsTraverser traverser(symbolTable, shaderVersion); + traverser.traverse(root); + return traverser.updateTree(compiler, root); +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h new file mode 100644 index 0000000000..60e6ffa2d1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h @@ -0,0 +1,42 @@ +// +// Copyright 2018 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. +// +// RewriteAtomicFunctionExpressions rewrites the expressions that contain +// atomic function calls and cannot be directly translated into HLSL into +// several simple ones that can be easily handled in the HLSL translator. +// +// We need to rewite these expressions because: +// 1. All GLSL atomic functions have return values, which all represent the +// original value of the shared or ssbo variable; while all HLSL atomic +// functions don't, and the original value can be stored in the last +// parameter of the function call. +// 2. For HLSL atomic functions, the last parameter that stores the original +// value is optional except for InterlockedExchange and +// InterlockedCompareExchange. Missing original_value in the call of +// InterlockedExchange or InterlockedCompareExchange results in a compile +// error from HLSL compiler. +// +// RewriteAtomicFunctionExpressions is a function that can modify the AST +// to ensure all the expressions that contain atomic function calls can be +// directly translated into HLSL expressions. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteAtomicFunctionExpressions(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable, + int shaderVersion); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp new file mode 100644 index 0000000000..10647d1bf1 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp @@ -0,0 +1,123 @@ +// +// Copyright 2014 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. +// +// RewriteElseBlocks.cpp: Implementation for tree transform to change +// all if-else blocks to if-if blocks. +// + +#include "compiler/translator/tree_ops/d3d/RewriteElseBlocks.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/NodeSearch.h" + +namespace sh +{ + +namespace +{ + +class ElseBlockRewriter : public TIntermTraverser +{ + public: + ElseBlockRewriter(TSymbolTable *symbolTable); + + protected: + bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *aggregate) override; + bool visitBlock(Visit visit, TIntermBlock *block) override; + + private: + TIntermNode *rewriteIfElse(TIntermIfElse *ifElse); + + const TType *mFunctionType; +}; + +ElseBlockRewriter::ElseBlockRewriter(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), mFunctionType(nullptr) +{} + +bool ElseBlockRewriter::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) +{ + // Store the current function context (see comment below) + mFunctionType = ((visit == PreVisit) ? &node->getFunctionPrototype()->getType() : nullptr); + return true; +} + +bool ElseBlockRewriter::visitBlock(Visit visit, TIntermBlock *node) +{ + if (visit == PostVisit) + { + for (size_t statementIndex = 0; statementIndex != node->getSequence()->size(); + statementIndex++) + { + TIntermNode *statement = (*node->getSequence())[statementIndex]; + TIntermIfElse *ifElse = statement->getAsIfElseNode(); + if (ifElse && ifElse->getFalseBlock() != nullptr) + { + (*node->getSequence())[statementIndex] = rewriteIfElse(ifElse); + } + } + } + return true; +} + +TIntermNode *ElseBlockRewriter::rewriteIfElse(TIntermIfElse *ifElse) +{ + ASSERT(ifElse != nullptr); + + TIntermDeclaration *storeCondition = nullptr; + TVariable *conditionVariable = + DeclareTempVariable(mSymbolTable, ifElse->getCondition(), EvqTemporary, &storeCondition); + + TIntermBlock *falseBlock = nullptr; + + TType boolType(EbtBool, EbpUndefined, EvqTemporary); + + if (ifElse->getFalseBlock()) + { + TIntermBlock *negatedElse = nullptr; + // crbug.com/346463 + // D3D generates error messages claiming a function has no return value, when rewriting + // an if-else clause that returns something non-void in a function. By appending mock + // returns (that are unreachable) we can silence this compile error. + if (mFunctionType && mFunctionType->getBasicType() != EbtVoid) + { + TIntermNode *returnNode = new TIntermBranch(EOpReturn, CreateZeroNode(*mFunctionType)); + negatedElse = new TIntermBlock(); + negatedElse->appendStatement(returnNode); + } + + TIntermSymbol *conditionSymbolElse = CreateTempSymbolNode(conditionVariable); + TIntermUnary *negatedCondition = + new TIntermUnary(EOpLogicalNot, conditionSymbolElse, nullptr); + TIntermIfElse *falseIfElse = + new TIntermIfElse(negatedCondition, ifElse->getFalseBlock(), negatedElse); + falseBlock = EnsureBlock(falseIfElse); + } + + TIntermSymbol *conditionSymbolSel = CreateTempSymbolNode(conditionVariable); + TIntermIfElse *newIfElse = + new TIntermIfElse(conditionSymbolSel, ifElse->getTrueBlock(), falseBlock); + + TIntermBlock *block = new TIntermBlock(); + block->getSequence()->push_back(storeCondition); + block->getSequence()->push_back(newIfElse); + + return block; +} + +} // anonymous namespace + +bool RewriteElseBlocks(TCompiler *compiler, TIntermNode *node, TSymbolTable *symbolTable) +{ + ElseBlockRewriter rewriter(symbolTable); + node->traverse(&rewriter); + + return compiler->validateAST(node); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h new file mode 100644 index 0000000000..cab0f7090c --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h @@ -0,0 +1,27 @@ +// +// Copyright 2014 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. +// +// RewriteElseBlocks.h: Prototype for tree transform to change +// all if-else blocks to if-if blocks. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteElseBlocks(TCompiler *compiler, + TIntermNode *node, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp new file mode 100644 index 0000000000..709f394878 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp @@ -0,0 +1,420 @@ +// +// Copyright 2018 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. +// +// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage +// block calls into several simple ones that can be easily handled in the HLSL translator. After the +// AST pass, all ssbo related blocks will be like below: +// ssbo_access_chain = ssbo_access_chain; +// ssbo_access_chain = expr_no_ssbo; +// lvalue_no_ssbo = ssbo_access_chain; +// + +#include "compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h" + +#include "compiler/translator/Symbol.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" +#include "compiler/translator/util.h" + +namespace sh +{ +namespace +{ + +bool IsIncrementOrDecrementOperator(TOperator op) +{ + switch (op) + { + case EOpPostIncrement: + case EOpPostDecrement: + case EOpPreIncrement: + case EOpPreDecrement: + return true; + default: + return false; + } +} + +bool IsCompoundAssignment(TOperator op) +{ + switch (op) + { + case EOpAddAssign: + case EOpSubAssign: + case EOpMulAssign: + case EOpVectorTimesMatrixAssign: + case EOpVectorTimesScalarAssign: + case EOpMatrixTimesScalarAssign: + case EOpMatrixTimesMatrixAssign: + case EOpDivAssign: + case EOpIModAssign: + case EOpBitShiftLeftAssign: + case EOpBitShiftRightAssign: + case EOpBitwiseAndAssign: + case EOpBitwiseXorAssign: + case EOpBitwiseOrAssign: + return true; + default: + return false; + } +} + +// EOpIndexDirect, EOpIndexIndirect, EOpIndexDirectStruct, EOpIndexDirectInterfaceBlock belong to +// operators in SSBO access chain. +bool IsReadonlyBinaryOperatorNotInSSBOAccessChain(TOperator op) +{ + switch (op) + { + case EOpComma: + case EOpAdd: + case EOpSub: + case EOpMul: + case EOpDiv: + case EOpIMod: + case EOpBitShiftLeft: + case EOpBitShiftRight: + case EOpBitwiseAnd: + case EOpBitwiseXor: + case EOpBitwiseOr: + case EOpEqual: + case EOpNotEqual: + case EOpLessThan: + case EOpGreaterThan: + case EOpLessThanEqual: + case EOpGreaterThanEqual: + case EOpVectorTimesScalar: + case EOpMatrixTimesScalar: + case EOpVectorTimesMatrix: + case EOpMatrixTimesVector: + case EOpMatrixTimesMatrix: + case EOpLogicalOr: + case EOpLogicalXor: + case EOpLogicalAnd: + return true; + default: + return false; + } +} + +bool HasSSBOAsFunctionArgument(TIntermSequence *arguments) +{ + for (TIntermNode *arg : *arguments) + { + TIntermTyped *typedArg = arg->getAsTyped(); + if (IsInShaderStorageBlock(typedArg)) + { + return true; + } + } + return false; +} + +class RewriteExpressionsWithShaderStorageBlockTraverser : public TIntermTraverser +{ + public: + RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable); + void nextIteration(); + bool foundSSBO() const { return mFoundSSBO; } + + private: + bool visitBinary(Visit, TIntermBinary *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + bool visitUnary(Visit visit, TIntermUnary *node) override; + + TIntermSymbol *insertInitStatementAndReturnTempSymbol(TIntermTyped *node, + TIntermSequence *insertions); + + bool mFoundSSBO; +}; + +RewriteExpressionsWithShaderStorageBlockTraverser:: + RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, true, false, symbolTable), mFoundSSBO(false) +{} + +TIntermSymbol * +RewriteExpressionsWithShaderStorageBlockTraverser::insertInitStatementAndReturnTempSymbol( + TIntermTyped *node, + TIntermSequence *insertions) +{ + TIntermDeclaration *variableDeclaration; + TVariable *tempVariable = + DeclareTempVariable(mSymbolTable, node, EvqTemporary, &variableDeclaration); + + insertions->push_back(variableDeclaration); + return CreateTempSymbolNode(tempVariable); +} + +bool RewriteExpressionsWithShaderStorageBlockTraverser::visitBinary(Visit visit, + TIntermBinary *node) +{ + // Make sure that the expression is caculated from left to right. + if (visit != InVisit) + { + return true; + } + + if (mFoundSSBO) + { + return false; + } + + bool rightSSBO = IsInShaderStorageBlock(node->getRight()); + bool leftSSBO = IsInShaderStorageBlock(node->getLeft()); + if (!leftSSBO && !rightSSBO) + { + return true; + } + + // case 1: Compound assigment operator + // original: + // lssbo += expr; + // new: + // var rvalue = expr; + // var temp = lssbo; + // temp += rvalue; + // lssbo = temp; + // + // original: + // lvalue_no_ssbo += rssbo; + // new: + // var rvalue = rssbo; + // lvalue_no_ssbo += rvalue; + if (IsCompoundAssignment(node->getOp())) + { + mFoundSSBO = true; + TIntermSequence insertions; + TIntermTyped *rightNode = + insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions); + if (leftSSBO) + { + TIntermSymbol *tempSymbol = + insertInitStatementAndReturnTempSymbol(node->getLeft()->deepCopy(), &insertions); + TIntermBinary *tempCompoundOperate = + new TIntermBinary(node->getOp(), tempSymbol->deepCopy(), rightNode->deepCopy()); + insertions.push_back(tempCompoundOperate); + insertStatementsInParentBlock(insertions); + + TIntermBinary *assignTempValueToSSBO = + new TIntermBinary(EOpAssign, node->getLeft(), tempSymbol->deepCopy()); + queueReplacement(assignTempValueToSSBO, OriginalNode::IS_DROPPED); + } + else + { + insertStatementsInParentBlock(insertions); + TIntermBinary *compoundAssignRValueToLValue = + new TIntermBinary(node->getOp(), node->getLeft(), rightNode->deepCopy()); + queueReplacement(compoundAssignRValueToLValue, OriginalNode::IS_DROPPED); + } + } + // case 2: Readonly binary operator + // original: + // ssbo0 + ssbo1 + ssbo2; + // new: + // var temp0 = ssbo0; + // var temp1 = ssbo1; + // var temp2 = ssbo2; + // temp0 + temp1 + temp2; + else if (IsReadonlyBinaryOperatorNotInSSBOAccessChain(node->getOp()) && (leftSSBO || rightSSBO)) + { + mFoundSSBO = true; + TIntermTyped *rightNode = node->getRight(); + TIntermTyped *leftNode = node->getLeft(); + TIntermSequence insertions; + if (rightSSBO) + { + rightNode = insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions); + } + if (leftSSBO) + { + leftNode = insertInitStatementAndReturnTempSymbol(node->getLeft(), &insertions); + } + + insertStatementsInParentBlock(insertions); + TIntermBinary *newExpr = + new TIntermBinary(node->getOp(), leftNode->deepCopy(), rightNode->deepCopy()); + queueReplacement(newExpr, OriginalNode::IS_DROPPED); + } + return !mFoundSSBO; +} + +// case 3: ssbo as the argument of aggregate type +// original: +// foo(ssbo); +// new: +// var tempArg = ssbo; +// foo(tempArg); +// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument) +// +// original: +// foo(ssbo) * expr; +// new: +// var tempArg = ssbo; +// var tempReturn = foo(tempArg); +// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument) +// tempReturn * expr; +bool RewriteExpressionsWithShaderStorageBlockTraverser::visitAggregate(Visit visit, + TIntermAggregate *node) +{ + // Make sure that visitAggregate is only executed once for same node. + if (visit != PreVisit) + { + return true; + } + + if (mFoundSSBO) + { + return false; + } + + // We still need to process the ssbo as the non-first argument of atomic memory functions. + if (BuiltInGroup::IsAtomicMemory(node->getOp()) && + IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped())) + { + return true; + } + + if (!HasSSBOAsFunctionArgument(node->getSequence())) + { + return true; + } + + mFoundSSBO = true; + TIntermSequence insertions; + TIntermSequence readBackToSSBOs; + TIntermSequence *originalArguments = node->getSequence(); + for (size_t i = 0; i < node->getChildCount(); ++i) + { + TIntermTyped *ssboArgument = (*originalArguments)[i]->getAsTyped(); + if (IsInShaderStorageBlock(ssboArgument)) + { + TIntermSymbol *argumentCopy = + insertInitStatementAndReturnTempSymbol(ssboArgument, &insertions); + if (node->getFunction() != nullptr) + { + TQualifier qual = node->getFunction()->getParam(i)->getType().getQualifier(); + if (qual == EvqParamInOut || qual == EvqParamOut) + { + TIntermBinary *readBackToSSBO = new TIntermBinary( + EOpAssign, ssboArgument->deepCopy(), argumentCopy->deepCopy()); + readBackToSSBOs.push_back(readBackToSSBO); + } + } + node->replaceChildNode(ssboArgument, argumentCopy); + } + } + + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + if (parentBlock) + { + // Aggregate node is as a single sentence. + insertions.push_back(node); + if (!readBackToSSBOs.empty()) + { + insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end()); + } + mMultiReplacements.emplace_back(parentBlock, node, std::move(insertions)); + } + else + { + // Aggregate node is inside an expression. + TIntermSymbol *tempSymbol = insertInitStatementAndReturnTempSymbol(node, &insertions); + if (!readBackToSSBOs.empty()) + { + insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end()); + } + insertStatementsInParentBlock(insertions); + queueReplacement(tempSymbol->deepCopy(), OriginalNode::IS_DROPPED); + } + + return false; +} + +bool RewriteExpressionsWithShaderStorageBlockTraverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (mFoundSSBO) + { + return false; + } + + if (!IsInShaderStorageBlock(node->getOperand())) + { + return true; + } + + // .length() is processed in OutputHLSL. + if (node->getOp() == EOpArrayLength) + { + return true; + } + + mFoundSSBO = true; + + // case 4: ssbo as the operand of ++/-- + // original: + // ++ssbo * expr; + // new: + // var temp1 = ssbo; + // var temp2 = ++temp1; + // ssbo = temp1; + // temp2 * expr; + if (IsIncrementOrDecrementOperator(node->getOp())) + { + TIntermSequence insertions; + TIntermSymbol *temp1 = + insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions); + TIntermUnary *newUnary = new TIntermUnary(node->getOp(), temp1->deepCopy(), nullptr); + TIntermSymbol *temp2 = insertInitStatementAndReturnTempSymbol(newUnary, &insertions); + TIntermBinary *readBackToSSBO = + new TIntermBinary(EOpAssign, node->getOperand()->deepCopy(), temp1->deepCopy()); + insertions.push_back(readBackToSSBO); + insertStatementsInParentBlock(insertions); + queueReplacement(temp2->deepCopy(), OriginalNode::IS_DROPPED); + } + // case 5: ssbo as the operand of readonly unary operator + // original: + // ~ssbo * expr; + // new: + // var temp = ssbo; + // ~temp * expr; + else + { + TIntermSequence insertions; + TIntermSymbol *temp = + insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions); + insertStatementsInParentBlock(insertions); + node->replaceChildNode(node->getOperand(), temp->deepCopy()); + } + return false; +} + +void RewriteExpressionsWithShaderStorageBlockTraverser::nextIteration() +{ + mFoundSSBO = false; +} + +} // anonymous namespace + +bool RewriteExpressionsWithShaderStorageBlock(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + RewriteExpressionsWithShaderStorageBlockTraverser traverser(symbolTable); + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundSSBO()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundSSBO()); + + return true; +} +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h new file mode 100644 index 0000000000..bf824c4a23 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h @@ -0,0 +1,37 @@ +// +// Copyright 2018 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. +// +// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage +// block calls into several simple ones that can be easily handled in the HLSL translator. After the +// AST pass, all ssbo related blocks will be like below: +// ssbo_access_chain = ssbo_access_chain; +// ssbo_access_chain = expr_no_ssbo; +// lvalue_no_ssbo = ssbo_access_chain; +// +// Below situations are needed to be rewritten (Details can be found in .cpp file). +// SSBO as the operand of compound assignment operators. +// SSBO as the operand of ++/--. +// SSBO as the operand of repeated assignment. +// SSBO as the operand of readonly unary/binary/ternary operators. +// SSBO as the argument of aggregate type. +// SSBO as the condition of if/switch/while/do-while/for + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool RewriteExpressionsWithShaderStorageBlock(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp new file mode 100644 index 0000000000..5d8cdb657d --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp @@ -0,0 +1,117 @@ +// +// Copyright 2016 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. +// +// Implementation of evaluating unary integer variable bug workaround. +// See header for more info. + +#include "compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h" + +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class Traverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool Apply(TCompiler *compiler, TIntermNode *root); + + private: + Traverser(); + bool visitUnary(Visit visit, TIntermUnary *node) override; + void nextIteration(); + + bool mFound = false; +}; + +// static +bool Traverser::Apply(TCompiler *compiler, TIntermNode *root) +{ + Traverser traverser; + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.mFound) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.mFound); + + return true; +} + +Traverser::Traverser() : TIntermTraverser(true, false, false) {} + +void Traverser::nextIteration() +{ + mFound = false; +} + +bool Traverser::visitUnary(Visit visit, TIntermUnary *node) +{ + if (mFound) + { + return false; + } + + // Decide if the current unary operator is unary minus. + if (node->getOp() != EOpNegative) + { + return true; + } + + // Decide if the current operand is an integer variable. + TIntermTyped *opr = node->getOperand(); + if (!opr->getType().isScalarInt()) + { + return true; + } + + // Potential problem case detected, apply workaround: -(int) -> ~(int) + 1. + // ~(int) + TIntermUnary *bitwiseNot = new TIntermUnary(EOpBitwiseNot, opr, nullptr); + bitwiseNot->setLine(opr->getLine()); + + // Constant 1 (or 1u) + TConstantUnion *one = new TConstantUnion(); + if (opr->getType().getBasicType() == EbtInt) + { + one->setIConst(1); + } + else + { + one->setUConst(1u); + } + TType *oneType = new TType(opr->getType()); + oneType->setQualifier(EvqConst); + + TIntermConstantUnion *oneNode = new TIntermConstantUnion(one, *oneType); + oneNode->setLine(opr->getLine()); + + // ~(int) + 1 + TIntermBinary *add = new TIntermBinary(EOpAdd, bitwiseNot, oneNode); + add->setLine(opr->getLine()); + + queueReplacement(add, OriginalNode::IS_DROPPED); + + mFound = true; + return false; +} + +} // anonymous namespace + +bool RewriteUnaryMinusOperatorInt(TCompiler *compiler, TIntermNode *root) +{ + return Traverser::Apply(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h new file mode 100644 index 0000000000..cc5ac86456 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h @@ -0,0 +1,23 @@ +// Copyright 2016 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. +// +// This mutating tree traversal works around a bug on evaluating unary +// integer variable on Intel D3D driver. It works by rewriting -(int) to +// ~(int) + 1 when evaluating unary integer variables. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool RewriteUnaryMinusOperatorInt(TCompiler *compiler, TIntermNode *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp new file mode 100644 index 0000000000..170b29acd7 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp @@ -0,0 +1,83 @@ +// +// Copyright 2018 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. +// +// SeparateArrayConstructorStatements splits statements that are array constructors and drops all of +// their constant arguments. For example, a statement like: +// int[2](0, i++); +// Will be changed to: +// i++; + +#include "compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h" + +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +void SplitConstructorArgs(const TIntermSequence &originalArgs, TIntermSequence *argsOut) +{ + for (TIntermNode *arg : originalArgs) + { + TIntermTyped *argTyped = arg->getAsTyped(); + if (argTyped->hasSideEffects()) + { + TIntermAggregate *argAggregate = argTyped->getAsAggregate(); + if (argTyped->isArray() && argAggregate && argAggregate->isConstructor()) + { + SplitConstructorArgs(*argAggregate->getSequence(), argsOut); + } + else + { + argsOut->push_back(argTyped); + } + } + } +} + +class SeparateArrayConstructorStatementsTraverser : public TIntermTraverser +{ + public: + SeparateArrayConstructorStatementsTraverser(); + + bool visitAggregate(Visit visit, TIntermAggregate *node) override; +}; + +SeparateArrayConstructorStatementsTraverser::SeparateArrayConstructorStatementsTraverser() + : TIntermTraverser(true, false, false) +{} + +bool SeparateArrayConstructorStatementsTraverser::visitAggregate(Visit visit, + TIntermAggregate *node) +{ + TIntermBlock *parentAsBlock = getParentNode()->getAsBlock(); + if (!parentAsBlock) + { + return false; + } + if (!node->isArray() || !node->isConstructor()) + { + return false; + } + + TIntermSequence constructorArgs; + SplitConstructorArgs(*node->getSequence(), &constructorArgs); + mMultiReplacements.emplace_back(parentAsBlock, node, std::move(constructorArgs)); + + return false; +} + +} // namespace + +bool SeparateArrayConstructorStatements(TCompiler *compiler, TIntermBlock *root) +{ + SeparateArrayConstructorStatementsTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h new file mode 100644 index 0000000000..225a4651da --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h @@ -0,0 +1,25 @@ +// +// Copyright 2018 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. +// +// SeparateArrayConstructorStatements splits statements that are array constructors and drops all of +// their constant arguments. For example, a statement like: +// int[2](0, i++); +// Will be changed to: +// i++; + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; + +[[nodiscard]] bool SeparateArrayConstructorStatements(TCompiler *compiler, TIntermBlock *root); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp new file mode 100644 index 0000000000..de5c393cf6 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp @@ -0,0 +1,89 @@ +// +// 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. +// +// The SeparateArrayInitialization function splits each array initialization into a declaration and +// an assignment. +// Example: +// type[n] a = initializer; +// will effectively become +// type[n] a; +// a = initializer; +// +// Note that if the array is declared as const, the initialization may still be split, making the +// AST technically invalid. Because of that this transformation should only be used when subsequent +// stages don't care about const qualifiers. However, the initialization will not be split if the +// initializer can be written as a HLSL literal. + +#include "compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/OutputHLSL.h" + +namespace sh +{ + +namespace +{ + +class SeparateArrayInitTraverser : private TIntermTraverser +{ + public: + [[nodiscard]] static bool apply(TCompiler *compiler, TIntermNode *root); + + private: + SeparateArrayInitTraverser(); + bool visitDeclaration(Visit, TIntermDeclaration *node) override; +}; + +bool SeparateArrayInitTraverser::apply(TCompiler *compiler, TIntermNode *root) +{ + SeparateArrayInitTraverser separateInit; + root->traverse(&separateInit); + return separateInit.updateTree(compiler, root); +} + +SeparateArrayInitTraverser::SeparateArrayInitTraverser() : TIntermTraverser(true, false, false) {} + +bool SeparateArrayInitTraverser::visitDeclaration(Visit, TIntermDeclaration *node) +{ + TIntermSequence *sequence = node->getSequence(); + TIntermBinary *initNode = sequence->back()->getAsBinaryNode(); + if (initNode != nullptr && initNode->getOp() == EOpInitialize) + { + TIntermTyped *initializer = initNode->getRight(); + if (initializer->isArray() && !initializer->hasConstantValue()) + { + // We rely on that array declarations have been isolated to single declarations. + ASSERT(sequence->size() == 1); + TIntermTyped *symbol = initNode->getLeft(); + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + ASSERT(parentBlock != nullptr); + + TIntermSequence replacements; + + TIntermDeclaration *replacementDeclaration = new TIntermDeclaration(); + replacementDeclaration->appendDeclarator(symbol); + replacementDeclaration->setLine(symbol->getLine()); + replacements.push_back(replacementDeclaration); + + TIntermBinary *replacementAssignment = + new TIntermBinary(EOpAssign, symbol, initializer); + replacementAssignment->setLine(symbol->getLine()); + replacements.push_back(replacementAssignment); + + mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements)); + } + } + return false; +} + +} // namespace + +bool SeparateArrayInitialization(TCompiler *compiler, TIntermNode *root) +{ + return SeparateArrayInitTraverser::apply(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h new file mode 100644 index 0000000000..e83554186a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h @@ -0,0 +1,32 @@ +// +// 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. +// +// The SeparateArrayInitialization function splits each array initialization into a declaration and +// an assignment. +// Example: +// type[n] a = initializer; +// will effectively become +// type[n] a; +// a = initializer; +// +// Note that if the array is declared as const, the initialization may still be split, making the +// AST technically invalid. Because of that this transformation should only be used when subsequent +// stages don't care about const qualifiers. However, the initialization will not be split if the +// initializer can be written as a HLSL literal. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; + +[[nodiscard]] bool SeparateArrayInitialization(TCompiler *compiler, TIntermNode *root); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp new file mode 100644 index 0000000000..58d8a0a9be --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp @@ -0,0 +1,138 @@ +// +// 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. +// +// SeparateExpressionsReturningArrays splits array-returning expressions that are not array names +// from more complex expressions, assigning them to a temporary variable a#. +// Examples where a, b and c are all arrays: +// (a = b) == (a = c) is split into a = b; type[n] a1 = a; a = c; type[n] a2 = a; a1 == a2; +// type d = type[n](...)[i]; is split into type[n] a1 = type[n](...); type d = a1[i]; + +#include "compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h" + +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// Traverser that separates one array expression into a statement at a time. +class SeparateExpressionsTraverser : public TIntermTraverser +{ + public: + SeparateExpressionsTraverser(TSymbolTable *symbolTable); + + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitAggregate(Visit visit, TIntermAggregate *node) override; + + void nextIteration(); + bool foundArrayExpression() const { return mFoundArrayExpression; } + + protected: + // Marked to true once an operation that needs to be hoisted out of the expression has been + // found. After that, no more AST updates are performed on that traversal. + bool mFoundArrayExpression; + + IntermNodePatternMatcher mPatternToSeparateMatcher; +}; + +SeparateExpressionsTraverser::SeparateExpressionsTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), + mFoundArrayExpression(false), + mPatternToSeparateMatcher(IntermNodePatternMatcher::kExpressionReturningArray) +{} + +// Performs a shallow copy of an assignment node. +// These shallow copies are useful when a node gets inserted into an aggregate node +// and also needs to be replaced in its original location by a different node. +TIntermBinary *CopyAssignmentNode(TIntermBinary *node) +{ + return new TIntermBinary(node->getOp(), node->getLeft(), node->getRight()); +} + +bool SeparateExpressionsTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (mFoundArrayExpression) + return false; + + // Return if the expression is not an array or if we're not inside a complex expression. + if (!mPatternToSeparateMatcher.match(node, getParentNode())) + return true; + + ASSERT(node->getOp() == EOpAssign); + + mFoundArrayExpression = true; + + TIntermSequence insertions; + insertions.push_back(CopyAssignmentNode(node)); + // TODO(oetuaho): In some cases it would be more optimal to not add the temporary node, but just + // use the original target of the assignment. Care must be taken so that this doesn't happen + // when the same array symbol is a target of assignment more than once in one expression. + TIntermDeclaration *arrayVariableDeclaration; + TVariable *arrayVariable = + DeclareTempVariable(mSymbolTable, node->getLeft(), EvqTemporary, &arrayVariableDeclaration); + insertions.push_back(arrayVariableDeclaration); + insertStatementsInParentBlock(insertions); + + queueReplacement(CreateTempSymbolNode(arrayVariable), OriginalNode::IS_DROPPED); + + return false; +} + +bool SeparateExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node) +{ + if (mFoundArrayExpression) + return false; // No need to traverse further + + if (!mPatternToSeparateMatcher.match(node, getParentNode())) + return true; + + ASSERT(node->isConstructor() || node->getOp() == EOpCallFunctionInAST); + + mFoundArrayExpression = true; + + TIntermDeclaration *arrayVariableDeclaration; + TVariable *arrayVariable = DeclareTempVariable(mSymbolTable, node->shallowCopy(), EvqTemporary, + &arrayVariableDeclaration); + insertStatementInParentBlock(arrayVariableDeclaration); + + queueReplacement(CreateTempSymbolNode(arrayVariable), OriginalNode::IS_DROPPED); + + return false; +} + +void SeparateExpressionsTraverser::nextIteration() +{ + mFoundArrayExpression = false; +} + +} // namespace + +bool SeparateExpressionsReturningArrays(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable) +{ + SeparateExpressionsTraverser traverser(symbolTable); + // Separate one expression at a time, and reset the traverser between iterations. + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundArrayExpression()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundArrayExpression()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h new file mode 100644 index 0000000000..6a9350d89a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h @@ -0,0 +1,28 @@ +// +// 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. +// +// SeparateExpressionsReturningArrays splits array-returning expressions that are not array names +// from more complex expressions, assigning them to a temporary variable a#. +// Examples where a, b and c are all arrays: +// (a = b) == (a = c) is split into a = b; type[n] a1 = a; a = c; type[n] a2 = a; a1 == a2; +// type d = type[n](...)[i]; is split into type[n] a1 = type[n](...); type d = a1[i]; + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool SeparateExpressionsReturningArrays(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp new file mode 100644 index 0000000000..5b104c4889 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp @@ -0,0 +1,200 @@ +// +// 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. +// +// UnfoldShortCircuitToIf is an AST traverser to convert short-circuiting operators to if-else +// statements. +// The results are assigned to s# temporaries, which are used by the main translator instead of +// the original expression. +// + +#include "compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h" + +#include "compiler/translator/StaticType.h" +#include "compiler/translator/tree_util/IntermNodePatternMatcher.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +// Traverser that unfolds one short-circuiting operation at a time. +class UnfoldShortCircuitTraverser : public TIntermTraverser +{ + public: + UnfoldShortCircuitTraverser(TSymbolTable *symbolTable); + + bool visitBinary(Visit visit, TIntermBinary *node) override; + bool visitTernary(Visit visit, TIntermTernary *node) override; + + void nextIteration(); + bool foundShortCircuit() const { return mFoundShortCircuit; } + + protected: + // Marked to true once an operation that needs to be unfolded has been found. + // After that, no more unfolding is performed on that traversal. + bool mFoundShortCircuit; + + IntermNodePatternMatcher mPatternToUnfoldMatcher; +}; + +UnfoldShortCircuitTraverser::UnfoldShortCircuitTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, true, symbolTable), + mFoundShortCircuit(false), + mPatternToUnfoldMatcher(IntermNodePatternMatcher::kUnfoldedShortCircuitExpression) +{} + +bool UnfoldShortCircuitTraverser::visitBinary(Visit visit, TIntermBinary *node) +{ + if (mFoundShortCircuit) + return false; + + if (visit != PreVisit) + return true; + + if (!mPatternToUnfoldMatcher.match(node, getParentNode())) + return true; + + // If our right node doesn't have side effects, we know we don't need to unfold this + // expression: there will be no short-circuiting side effects to avoid + // (note: unfolding doesn't depend on the left node -- it will always be evaluated) + ASSERT(node->getRight()->hasSideEffects()); + + mFoundShortCircuit = true; + + switch (node->getOp()) + { + case EOpLogicalOr: + { + // "x || y" is equivalent to "x ? true : y", which unfolds to "bool s; if(x) s = true; + // else s = y;", + // and then further simplifies down to "bool s = x; if(!s) s = y;". + + TIntermSequence insertions; + const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>(); + TVariable *resultVariable = CreateTempVariable(mSymbolTable, boolType); + + ASSERT(node->getLeft()->getType() == *boolType); + insertions.push_back(CreateTempInitDeclarationNode(resultVariable, node->getLeft())); + + TIntermBlock *assignRightBlock = new TIntermBlock(); + ASSERT(node->getRight()->getType() == *boolType); + assignRightBlock->getSequence()->push_back( + CreateTempAssignmentNode(resultVariable, node->getRight())); + + TIntermUnary *notTempSymbol = + new TIntermUnary(EOpLogicalNot, CreateTempSymbolNode(resultVariable), nullptr); + TIntermIfElse *ifNode = new TIntermIfElse(notTempSymbol, assignRightBlock, nullptr); + insertions.push_back(ifNode); + + insertStatementsInParentBlock(insertions); + + queueReplacement(CreateTempSymbolNode(resultVariable), OriginalNode::IS_DROPPED); + return false; + } + case EOpLogicalAnd: + { + // "x && y" is equivalent to "x ? y : false", which unfolds to "bool s; if(x) s = y; + // else s = false;", + // and then further simplifies down to "bool s = x; if(s) s = y;". + TIntermSequence insertions; + const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>(); + TVariable *resultVariable = CreateTempVariable(mSymbolTable, boolType); + + ASSERT(node->getLeft()->getType() == *boolType); + insertions.push_back(CreateTempInitDeclarationNode(resultVariable, node->getLeft())); + + TIntermBlock *assignRightBlock = new TIntermBlock(); + ASSERT(node->getRight()->getType() == *boolType); + assignRightBlock->getSequence()->push_back( + CreateTempAssignmentNode(resultVariable, node->getRight())); + + TIntermIfElse *ifNode = + new TIntermIfElse(CreateTempSymbolNode(resultVariable), assignRightBlock, nullptr); + insertions.push_back(ifNode); + + insertStatementsInParentBlock(insertions); + + queueReplacement(CreateTempSymbolNode(resultVariable), OriginalNode::IS_DROPPED); + return false; + } + default: + UNREACHABLE(); + return true; + } +} + +bool UnfoldShortCircuitTraverser::visitTernary(Visit visit, TIntermTernary *node) +{ + if (mFoundShortCircuit) + return false; + + if (visit != PreVisit) + return true; + + if (!mPatternToUnfoldMatcher.match(node)) + return true; + + mFoundShortCircuit = true; + + // Unfold "b ? x : y" into "type s; if(b) s = x; else s = y;" + TIntermSequence insertions; + TIntermDeclaration *tempDeclaration = nullptr; + TVariable *resultVariable = DeclareTempVariable(mSymbolTable, new TType(node->getType()), + EvqTemporary, &tempDeclaration); + insertions.push_back(tempDeclaration); + + TIntermBlock *trueBlock = new TIntermBlock(); + TIntermBinary *trueAssignment = + CreateTempAssignmentNode(resultVariable, node->getTrueExpression()); + trueBlock->getSequence()->push_back(trueAssignment); + + TIntermBlock *falseBlock = new TIntermBlock(); + TIntermBinary *falseAssignment = + CreateTempAssignmentNode(resultVariable, node->getFalseExpression()); + falseBlock->getSequence()->push_back(falseAssignment); + + TIntermIfElse *ifNode = + new TIntermIfElse(node->getCondition()->getAsTyped(), trueBlock, falseBlock); + insertions.push_back(ifNode); + + insertStatementsInParentBlock(insertions); + + TIntermSymbol *ternaryResult = CreateTempSymbolNode(resultVariable); + queueReplacement(ternaryResult, OriginalNode::IS_DROPPED); + + return false; +} + +void UnfoldShortCircuitTraverser::nextIteration() +{ + mFoundShortCircuit = false; +} + +} // namespace + +bool UnfoldShortCircuitToIf(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable) +{ + UnfoldShortCircuitTraverser traverser(symbolTable); + // Unfold one operator at a time, and reset the traverser between iterations. + do + { + traverser.nextIteration(); + root->traverse(&traverser); + if (traverser.foundShortCircuit()) + { + if (!traverser.updateTree(compiler, root)) + { + return false; + } + } + } while (traverser.foundShortCircuit()); + + return true; +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h new file mode 100644 index 0000000000..97587e4d38 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h @@ -0,0 +1,30 @@ +// +// 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. +// +// UnfoldShortCircuitToIf is an AST traverser to convert short-circuiting operators to if-else +// statements. +// The results are assigned to s# temporaries, which are used by the main translator instead of +// the original expression. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermNode; +class TSymbolTable; + +[[nodiscard]] bool UnfoldShortCircuitToIf(TCompiler *compiler, + TIntermNode *root, + TSymbolTable *symbolTable); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp new file mode 100644 index 0000000000..58a941da0e --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp @@ -0,0 +1,126 @@ +// +// 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. +// +// WrapSwitchStatementsInBlocks.cpp: Wrap switch statements in blocks and declare all switch-scoped +// variables there to make the AST compatible with HLSL output. +// +// switch (init) +// { +// case 0: +// float f; +// default: +// f = 1.0; +// } +// +// becomes +// +// { +// float f; +// switch (init) +// { +// case 0: +// default: +// f = 1.0; +// } +// } + +#include "compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h" + +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class WrapSwitchStatementsInBlocksTraverser : public TIntermTraverser +{ + public: + WrapSwitchStatementsInBlocksTraverser() : TIntermTraverser(true, false, false) {} + + bool visitSwitch(Visit visit, TIntermSwitch *node) override; +}; + +bool WrapSwitchStatementsInBlocksTraverser::visitSwitch(Visit, TIntermSwitch *node) +{ + std::vector<TIntermDeclaration *> declarations; + TIntermSequence *statementList = node->getStatementList()->getSequence(); + for (TIntermNode *statement : *statementList) + { + TIntermDeclaration *asDeclaration = statement->getAsDeclarationNode(); + if (asDeclaration) + { + declarations.push_back(asDeclaration); + } + } + if (declarations.empty()) + { + // We don't need to wrap the switch if it doesn't contain declarations as its direct + // descendants. + return true; + } + + TIntermBlock *wrapperBlock = new TIntermBlock(); + for (TIntermDeclaration *declaration : declarations) + { + // SeparateDeclarations should have already been run. + ASSERT(declaration->getSequence()->size() == 1); + + TIntermDeclaration *declarationInBlock = new TIntermDeclaration(); + TIntermSymbol *declaratorAsSymbol = declaration->getSequence()->at(0)->getAsSymbolNode(); + if (declaratorAsSymbol) + { + // This is a simple declaration like: "float f;" + // Remove the declaration from inside the switch and put it in the wrapping block. + TIntermSequence emptyReplacement; + mMultiReplacements.emplace_back(node->getStatementList(), declaration, + std::move(emptyReplacement)); + + declarationInBlock->appendDeclarator(declaratorAsSymbol->deepCopy()); + // The declaration can't be the last statement inside the switch since unused variables + // should already have been pruned. + ASSERT(declaration != statementList->back()); + } + else + { + // This is an init declaration like: "float f = 0.0;" + // Change the init declaration inside the switch into an assignment and put a plain + // declaration in the wrapping block. + TIntermBinary *declaratorAsBinary = + declaration->getSequence()->at(0)->getAsBinaryNode(); + ASSERT(declaratorAsBinary); + + TIntermBinary *initAssignment = new TIntermBinary( + EOpAssign, declaratorAsBinary->getLeft(), declaratorAsBinary->getRight()); + + queueReplacementWithParent(node->getStatementList(), declaration, initAssignment, + OriginalNode::IS_DROPPED); + + declarationInBlock->appendDeclarator(declaratorAsBinary->getLeft()->deepCopy()); + } + wrapperBlock->appendStatement(declarationInBlock); + } + + wrapperBlock->appendStatement(node); + queueReplacement(wrapperBlock, OriginalNode::BECOMES_CHILD); + + // Should be fine to process multiple switch statements, even nesting ones in the same + // traversal. + return true; +} + +} // anonymous namespace + +// Wrap switch statements in the AST into blocks when needed. +bool WrapSwitchStatementsInBlocks(TCompiler *compiler, TIntermBlock *root) +{ + WrapSwitchStatementsInBlocksTraverser traverser; + root->traverse(&traverser); + return traverser.updateTree(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h new file mode 100644 index 0000000000..0c765c306a --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h @@ -0,0 +1,25 @@ +// +// 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. +// +// WrapSwitchStatementsInBlocks.h: Wrap switch statements in blocks and declare all switch-scoped +// variables there to make the AST compatible with HLSL output. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; + +// Wrap switch statements in the AST into blocks when needed. Returns true if the AST was changed. +[[nodiscard]] bool WrapSwitchStatementsInBlocks(TCompiler *compiler, TIntermBlock *root); + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp new file mode 100644 index 0000000000..309cb14752 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp @@ -0,0 +1,54 @@ +// +// 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. +// +// ClampFragDepth.cpp: Limit the value that is written to gl_FragDepth to the range [0.0, 1.0]. +// The clamping is run at the very end of shader execution, and is only performed if the shader +// statically accesses gl_FragDepth. +// + +#include "compiler/translator/tree_ops/gl/ClampFragDepth.h" + +#include "compiler/translator/ImmutableString.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/BuiltIn.h" +#include "compiler/translator/tree_util/FindSymbolNode.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/RunAtTheEndOfShader.h" + +namespace sh +{ + +bool ClampFragDepth(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + // Only clamp gl_FragDepth if it's used in the shader. + if (!FindSymbolNode(root, ImmutableString("gl_FragDepth"))) + { + return true; + } + + TIntermSymbol *fragDepthNode = new TIntermSymbol(BuiltInVariable::gl_FragDepth()); + + TIntermTyped *minFragDepthNode = CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst)); + + TConstantUnion *maxFragDepthConstant = new TConstantUnion(); + maxFragDepthConstant->setFConst(1.0); + TIntermConstantUnion *maxFragDepthNode = + new TIntermConstantUnion(maxFragDepthConstant, TType(EbtFloat, EbpHigh, EvqConst)); + + // clamp(gl_FragDepth, 0.0, 1.0) + TIntermSequence clampArguments; + clampArguments.push_back(fragDepthNode->deepCopy()); + clampArguments.push_back(minFragDepthNode); + clampArguments.push_back(maxFragDepthNode); + TIntermTyped *clampedFragDepth = + CreateBuiltInFunctionCallNode("clamp", &clampArguments, *symbolTable, 100); + + // gl_FragDepth = clamp(gl_FragDepth, 0.0, 1.0) + TIntermBinary *assignFragDepth = new TIntermBinary(EOpAssign, fragDepthNode, clampedFragDepth); + + return RunAtTheEndOfShader(compiler, root, assignFragDepth, symbolTable); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h new file mode 100644 index 0000000000..70326c9a34 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h @@ -0,0 +1,39 @@ +// +// 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. +// +// ClampFragDepth.h: Limit the value that is written to gl_FragDepth to the range [0.0, 1.0]. +// The clamping is run at the very end of shader execution, and is only performed if the shader +// statically accesses gl_FragDepth. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_CLAMPFRAGDEPTH_H_ +#define COMPILER_TRANSLATOR_TREEOPS_GL_CLAMPFRAGDEPTH_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +#ifdef ANGLE_ENABLE_GLSL +[[nodiscard]] bool ClampFragDepth(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +#else +[[nodiscard]] ANGLE_INLINE bool ClampFragDepth(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_GL_CLAMPFRAGDEPTH_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp new file mode 100644 index 0000000000..e15ef32166 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp @@ -0,0 +1,119 @@ +// +// 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. +// + +#include "compiler/translator/tree_ops/gl/RegenerateStructNames.h" + +#include "common/debug.h" +#include "compiler/translator/Compiler.h" +#include "compiler/translator/ImmutableStringBuilder.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +#include <set> + +namespace sh +{ + +namespace +{ +constexpr const ImmutableString kPrefix("_webgl_struct_"); +} // anonymous namespace + +class RegenerateStructNamesTraverser : public TIntermTraverser +{ + public: + RegenerateStructNamesTraverser(TSymbolTable *symbolTable) + : TIntermTraverser(true, false, false, symbolTable), mScopeDepth(0) + {} + + protected: + void visitSymbol(TIntermSymbol *) override; + bool visitBlock(Visit, TIntermBlock *block) override; + + private: + // Indicating the depth of the current scope. + // The global scope is 1. + int mScopeDepth; + + // If a struct is declared globally, push its ID in this set. + std::set<int> mDeclaredGlobalStructs; +}; + +void RegenerateStructNamesTraverser::visitSymbol(TIntermSymbol *symbol) +{ + ASSERT(symbol); + const TType &type = symbol->getType(); + const TStructure *userType = type.getStruct(); + if (!userType) + return; + + if (userType->symbolType() == SymbolType::BuiltIn || + userType->symbolType() == SymbolType::Empty) + { + // Built-in struct or nameless struct, do not touch it. + return; + } + + int uniqueId = userType->uniqueId().get(); + + ASSERT(mScopeDepth > 0); + if (mScopeDepth == 1) + { + // If a struct is defined at global scope, we don't map its name. + // This is because at global level, the struct might be used to + // declare a uniform, so the same name needs to stay the same for + // vertex/fragment shaders. However, our mapping uses internal ID, + // which will be different for the same struct in vertex/fragment + // shaders. + // This is OK because names for any structs defined in other scopes + // will begin with "_webgl", which is reserved. So there will be + // no conflicts among unmapped struct names from global scope and + // mapped struct names from other scopes. + // However, we need to keep track of these global structs, so if a + // variable is used in a local scope, we don't try to modify the + // struct name through that variable. + mDeclaredGlobalStructs.insert(uniqueId); + return; + } + if (mDeclaredGlobalStructs.count(uniqueId) > 0) + return; + // Map {name} to _webgl_struct_{uniqueId}_{name}. + if (userType->name().beginsWith(kPrefix)) + { + // The name has already been regenerated. + return; + } + ImmutableStringBuilder tmp(kPrefix.length() + sizeof(uniqueId) * 2u + 1u + + userType->name().length()); + tmp << kPrefix; + tmp.appendHex(uniqueId); + tmp << '_' << userType->name(); + + // TODO(oetuaho): Add another mechanism to change symbol names so that the const_cast is not + // needed. + const_cast<TStructure *>(userType)->setName(tmp); +} + +bool RegenerateStructNamesTraverser::visitBlock(Visit, TIntermBlock *block) +{ + ++mScopeDepth; + TIntermSequence &sequence = *(block->getSequence()); + for (TIntermNode *node : sequence) + { + node->traverse(this); + } + --mScopeDepth; + return false; +} + +bool RegenerateStructNames(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable) +{ + RegenerateStructNamesTraverser traverser(symbolTable); + root->traverse(&traverser); + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h new file mode 100644 index 0000000000..04cfe6a476 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h @@ -0,0 +1,34 @@ +// +// 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. +// + +#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_REGENERATESTRUCTNAMES_H_ +#define COMPILER_TRANSLATOR_TREEOPS_GL_REGENERATESTRUCTNAMES_H_ + +#include "common/angleutils.h" + +namespace sh +{ +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +#if defined(ANGLE_ENABLE_GLSL) +[[nodiscard]] bool RegenerateStructNames(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable); +#else +[[nodiscard]] ANGLE_INLINE bool RegenerateStructNames(TCompiler *compiler, + TIntermBlock *root, + TSymbolTable *symbolTable) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_GL_REGENERATESTRUCTNAMES_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp new file mode 100644 index 0000000000..83a0f029b8 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp @@ -0,0 +1,97 @@ +// +// Copyright 2018 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. +// +// RewriteRepeatedAssignToSwizzled.cpp: Rewrite expressions that assign an assignment to a swizzled +// vector, like: +// v.x = z = expression; +// to: +// z = expression; +// v.x = z; +// +// Note that this doesn't handle some corner cases: expressions nested inside other expressions, +// inside loop headers, or inside if conditions. + +#include "compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h" + +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/tree_util/IntermTraverse.h" + +namespace sh +{ + +namespace +{ + +class RewriteAssignToSwizzledTraverser : public TIntermTraverser +{ + public: + [[nodiscard]] static bool rewrite(TCompiler *compiler, TIntermBlock *root); + + private: + RewriteAssignToSwizzledTraverser(); + + bool visitBinary(Visit, TIntermBinary *node) override; + + void nextIteration(); + + bool didRewrite() { return mDidRewrite; } + + bool mDidRewrite; +}; + +// static +bool RewriteAssignToSwizzledTraverser::rewrite(TCompiler *compiler, TIntermBlock *root) +{ + RewriteAssignToSwizzledTraverser rewrite; + do + { + rewrite.nextIteration(); + root->traverse(&rewrite); + if (!rewrite.updateTree(compiler, root)) + { + return false; + } + } while (rewrite.didRewrite()); + + return true; +} + +RewriteAssignToSwizzledTraverser::RewriteAssignToSwizzledTraverser() + : TIntermTraverser(true, false, false), mDidRewrite(false) +{} + +void RewriteAssignToSwizzledTraverser::nextIteration() +{ + mDidRewrite = false; +} + +bool RewriteAssignToSwizzledTraverser::visitBinary(Visit, TIntermBinary *node) +{ + TIntermBinary *rightBinary = node->getRight()->getAsBinaryNode(); + TIntermBlock *parentBlock = getParentNode()->getAsBlock(); + if (parentBlock && node->isAssignment() && node->getLeft()->getAsSwizzleNode() && rightBinary && + rightBinary->isAssignment()) + { + TIntermSequence replacements; + replacements.push_back(rightBinary); + TIntermTyped *rightAssignmentTargetCopy = rightBinary->getLeft()->deepCopy(); + TIntermBinary *lastAssign = + new TIntermBinary(EOpAssign, node->getLeft(), rightAssignmentTargetCopy); + replacements.push_back(lastAssign); + mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements)); + mDidRewrite = true; + return false; + } + return true; +} + +} // anonymous namespace + +bool RewriteRepeatedAssignToSwizzled(TCompiler *compiler, TIntermBlock *root) +{ + return RewriteAssignToSwizzledTraverser::rewrite(compiler, root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h new file mode 100644 index 0000000000..1ab9b7ebb8 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h @@ -0,0 +1,40 @@ +// +// Copyright 2018 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. +// +// RewriteRepeatedAssignToSwizzled.h: Rewrite expressions that assign an assignment to a swizzled +// vector, like: +// v.x = z = expression; +// to: +// z = expression; +// v.x = z; +// +// Note that this doesn't handle some corner cases: expressions nested inside other expressions, +// inside loop headers, or inside if conditions. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_REWRITEREPEATEDASSIGNTOSWIZZLED_H_ +#define COMPILER_TRANSLATOR_TREEOPS_GL_REWRITEREPEATEDASSIGNTOSWIZZLED_H_ + +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; + +#ifdef ANGLE_ENABLE_GLSL +[[nodiscard]] bool RewriteRepeatedAssignToSwizzled(TCompiler *compiler, TIntermBlock *root); +#else +[[nodiscard]] ANGLE_INLINE bool RewriteRepeatedAssignToSwizzled(TCompiler *compiler, + TIntermBlock *root) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_GL_REWRITEREPEATEDASSIGNTOSWIZZLED_H_ diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp new file mode 100644 index 0000000000..e94ca2fd17 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp @@ -0,0 +1,108 @@ +// +// Copyright 2016 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. +// + +// UseInterfaceBlockFields.cpp: insert statements to reference all members in InterfaceBlock list at +// the beginning of main. This is to work around a Mac driver that treats unused standard/shared +// uniform blocks as inactive. + +#include "compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h" + +#include "compiler/translator/Compiler.h" +#include "compiler/translator/IntermNode.h" +#include "compiler/translator/SymbolTable.h" +#include "compiler/translator/tree_util/FindMain.h" +#include "compiler/translator/tree_util/IntermNode_util.h" +#include "compiler/translator/util.h" + +namespace sh +{ + +namespace +{ + +void AddNodeUseStatements(TIntermTyped *node, TIntermSequence *sequence) +{ + if (node->isArray()) + { + for (unsigned int i = 0u; i < node->getOutermostArraySize(); ++i) + { + TIntermBinary *element = + new TIntermBinary(EOpIndexDirect, node->deepCopy(), CreateIndexNode(i)); + AddNodeUseStatements(element, sequence); + } + } + else + { + sequence->insert(sequence->begin(), node); + } +} + +void AddFieldUseStatements(const ShaderVariable &var, + TIntermSequence *sequence, + const TSymbolTable &symbolTable) +{ + ASSERT(var.name.find_last_of('[') == std::string::npos); + TIntermSymbol *symbol = ReferenceGlobalVariable(ImmutableString(var.name), symbolTable); + AddNodeUseStatements(symbol, sequence); +} + +void InsertUseCode(const InterfaceBlock &block, TIntermTyped *blockNode, TIntermSequence *sequence) +{ + for (unsigned int i = 0; i < block.fields.size(); ++i) + { + TIntermBinary *element = new TIntermBinary(EOpIndexDirectInterfaceBlock, + blockNode->deepCopy(), CreateIndexNode(i)); + sequence->insert(sequence->begin(), element); + } +} + +void InsertUseCode(TIntermSequence *sequence, + const InterfaceBlockList &blocks, + const TSymbolTable &symbolTable) +{ + for (const auto &block : blocks) + { + if (block.instanceName.empty()) + { + for (const auto &var : block.fields) + { + AddFieldUseStatements(var, sequence, symbolTable); + } + } + else if (block.arraySize > 0u) + { + TIntermSymbol *arraySymbol = + ReferenceGlobalVariable(ImmutableString(block.instanceName), symbolTable); + for (unsigned int i = 0u; i < block.arraySize; ++i) + { + TIntermBinary *elementSymbol = + new TIntermBinary(EOpIndexDirect, arraySymbol->deepCopy(), CreateIndexNode(i)); + InsertUseCode(block, elementSymbol, sequence); + } + } + else + { + TIntermSymbol *blockSymbol = + ReferenceGlobalVariable(ImmutableString(block.instanceName), symbolTable); + InsertUseCode(block, blockSymbol, sequence); + } + } +} + +} // namespace + +bool UseInterfaceBlockFields(TCompiler *compiler, + TIntermBlock *root, + const InterfaceBlockList &blocks, + const TSymbolTable &symbolTable) +{ + TIntermBlock *mainBody = FindMainBody(root); + InsertUseCode(mainBody->getSequence(), blocks, symbolTable); + + return compiler->validateAST(root); +} + +} // namespace sh diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h new file mode 100644 index 0000000000..a4f5f3e5e6 --- /dev/null +++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h @@ -0,0 +1,44 @@ +// +// Copyright 2016 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. +// + +// UseInterfaceBlockFields.h: insert statements to reference all members in InterfaceBlock list at +// the beginning of main. This is to work around a Mac driver that treats unused standard/shared +// uniform blocks as inactive. + +#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_USEINTERFACEBLOCKFIELDS_H_ +#define COMPILER_TRANSLATOR_TREEOPS_GL_USEINTERFACEBLOCKFIELDS_H_ + +#include <GLSLANG/ShaderLang.h> +#include "common/angleutils.h" + +namespace sh +{ + +class TCompiler; +class TIntermBlock; +class TSymbolTable; + +using InterfaceBlockList = std::vector<sh::InterfaceBlock>; + +#ifdef ANGLE_ENABLE_GLSL +[[nodiscard]] bool UseInterfaceBlockFields(TCompiler *compiler, + TIntermBlock *root, + const InterfaceBlockList &blocks, + const TSymbolTable &symbolTable); +#else +[[nodiscard]] ANGLE_INLINE bool UseInterfaceBlockFields(TCompiler *compiler, + TIntermBlock *root, + const InterfaceBlockList &blocks, + const TSymbolTable &symbolTable) +{ + UNREACHABLE(); + return false; +} +#endif + +} // namespace sh + +#endif // COMPILER_TRANSLATOR_TREEOPS_GL_USEINTERFACEBLOCKFIELDS_H_ |