diff options
Diffstat (limited to '')
10 files changed, 2143 insertions, 0 deletions
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_ |