summaryrefslogtreecommitdiffstats
path: root/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp')
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp1595
1 files changed, 1595 insertions, 0 deletions
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