summaryrefslogtreecommitdiffstats
path: root/gfx/angle/checkout/src/compiler/translator/tree_ops
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/angle/checkout/src/compiler/translator/tree_ops')
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp136
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h27
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp52
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h28
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp335
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h25
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp196
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h52
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp180
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h38
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp142
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h35
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp274
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h47
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp120
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h29
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp101
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h17
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp359
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h60
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp613
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h54
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp127
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h32
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp127
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h22
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp215
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h29
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp119
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h33
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp109
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h37
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp74
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h24
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp597
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h41
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp209
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h42
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp47
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h21
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp371
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h29
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp348
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h25
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp328
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h28
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp984
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h29
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp141
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h32
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp861
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h29
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp673
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h38
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp168
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h34
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp223
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h28
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp199
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h32
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp115
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h39
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp499
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h32
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp173
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h30
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp60
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h31
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp147
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h38
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp1595
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h37
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp97
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h31
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp74
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h33
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp61
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h24
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp82
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h23
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp76
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h23
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp233
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h27
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp110
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h25
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp152
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h34
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp385
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h28
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp270
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h27
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp183
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h42
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp123
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h27
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp420
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h37
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp117
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h23
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp83
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h25
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp89
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h32
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp138
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h28
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp200
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h30
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp126
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h25
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp54
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h39
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp119
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h34
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp97
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h40
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp108
-rw-r--r--gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h44
118 files changed, 16319 insertions, 0 deletions
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp
new file mode 100644
index 0000000000..bd1997a21d
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.cpp
@@ -0,0 +1,136 @@
+//
+// Copyright 2021 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ClampIndirectIndices.h: Add clamp to the indirect indices used on arrays.
+//
+
+#include "compiler/translator/tree_ops/ClampIndirectIndices.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+// Traverser that finds EOpIndexIndirect nodes and applies a clamp to their right-hand side
+// expression.
+class ClampIndirectIndicesTraverser : public TIntermTraverser
+{
+ public:
+ ClampIndirectIndicesTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable), mCompiler(compiler)
+ {}
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ ASSERT(visit == PreVisit);
+
+ // Only interested in EOpIndexIndirect nodes.
+ if (node->getOp() != EOpIndexIndirect)
+ {
+ return true;
+ }
+
+ // Apply the transformation to the left and right nodes
+ bool valid = ClampIndirectIndices(mCompiler, node->getLeft(), mSymbolTable);
+ ASSERT(valid);
+ valid = ClampIndirectIndices(mCompiler, node->getRight(), mSymbolTable);
+ ASSERT(valid);
+
+ // Generate clamp(right, 0, N), where N is the size of the array being indexed minus 1. If
+ // the array is runtime-sized, the length() method is called on it.
+ const TType &leftType = node->getLeft()->getType();
+ const TType &rightType = node->getRight()->getType();
+
+ // Don't clamp indirect indices on unsized arrays in buffer blocks. They are covered by the
+ // relevant robust access behavior of the backend.
+ if (leftType.isUnsizedArray())
+ {
+ return true;
+ }
+
+ // On GLSL es 100, clamp is only defined for float, so float arguments are used.
+ //
+ // However, float clamp is unconditionally emitted to workaround driver bugs with integer
+ // clamp on Qualcomm. http://crbug.com/1217167
+ //
+ // const bool useFloatClamp = mCompiler->getShaderVersion() == 100;
+ const bool useFloatClamp = true;
+
+ TIntermConstantUnion *zero = createClampValue(0, useFloatClamp);
+ TIntermTyped *max;
+
+ if (leftType.isArray())
+ {
+ max = createClampValue(static_cast<int>(leftType.getOutermostArraySize()) - 1,
+ useFloatClamp);
+ }
+ else
+ {
+ ASSERT(leftType.isVector() || leftType.isMatrix());
+ max = createClampValue(leftType.getNominalSize() - 1, useFloatClamp);
+ }
+
+ TIntermTyped *index = node->getRight();
+ // If the index node is not an int (i.e. it's a uint), or a float (if using float clamp),
+ // cast it.
+ const TBasicType requiredBasicType = useFloatClamp ? EbtFloat : EbtInt;
+ if (rightType.getBasicType() != requiredBasicType)
+ {
+ const TType *clampType = useFloatClamp ? StaticType::GetBasic<EbtFloat, EbpHigh>()
+ : StaticType::GetBasic<EbtInt, EbpHigh>();
+ TIntermSequence constructorArgs = {index};
+ index = TIntermAggregate::CreateConstructor(*clampType, &constructorArgs);
+ }
+
+ // min(gl_PointSize, maxPointSize)
+ TIntermSequence args;
+ args.push_back(index);
+ args.push_back(zero);
+ args.push_back(max);
+ TIntermTyped *clamped =
+ CreateBuiltInFunctionCallNode("clamp", &args, *mSymbolTable, useFloatClamp ? 100 : 300);
+
+ // Cast back to int if float clamp was used.
+ if (useFloatClamp)
+ {
+ TIntermSequence constructorArgs = {clamped};
+ clamped = TIntermAggregate::CreateConstructor(*StaticType::GetBasic<EbtInt, EbpHigh>(),
+ &constructorArgs);
+ }
+
+ // Replace the right node (the index) with the clamped result.
+ queueReplacementWithParent(node, node->getRight(), clamped, OriginalNode::IS_DROPPED);
+
+ // Don't recurse as left and right nodes are already processed.
+ return false;
+ }
+
+ private:
+ TIntermConstantUnion *createClampValue(int value, bool useFloat)
+ {
+ if (useFloat)
+ {
+ return CreateFloatNode(static_cast<float>(value), EbpHigh);
+ }
+ return CreateIndexNode(value);
+ }
+
+ TCompiler *mCompiler;
+};
+} // anonymous namespace
+
+bool ClampIndirectIndices(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ ClampIndirectIndicesTraverser traverser(compiler, symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h
new file mode 100644
index 0000000000..eebbab02e6
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampIndirectIndices.h
@@ -0,0 +1,27 @@
+//
+// Copyright 2021 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ClampIndirectIndices.h: Add clamp to the indirect indices used on arrays.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_CLAMPINDIRECTINDICES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_CLAMPINDIRECTINDICES_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool ClampIndirectIndices(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_CLAMPINDIRECTINDICES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp
new file mode 100644
index 0000000000..db006989e9
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.cpp
@@ -0,0 +1,52 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ClampPointSize.cpp: Limit the value that is written to gl_PointSize.
+//
+
+#include "compiler/translator/tree_ops/ClampPointSize.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/BuiltIn.h"
+#include "compiler/translator/tree_util/FindSymbolNode.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
+
+namespace sh
+{
+
+bool ClampPointSize(TCompiler *compiler,
+ TIntermBlock *root,
+ float maxPointSize,
+ TSymbolTable *symbolTable)
+{
+ // Only clamp gl_PointSize if it's used in the shader.
+ const TIntermSymbol *glPointSize = FindSymbolNode(root, ImmutableString("gl_PointSize"));
+ if (glPointSize == nullptr)
+ {
+ return true;
+ }
+
+ TIntermTyped *pointSizeNode = glPointSize->deepCopy();
+
+ TConstantUnion *maxPointSizeConstant = new TConstantUnion();
+ maxPointSizeConstant->setFConst(maxPointSize);
+ TIntermConstantUnion *maxPointSizeNode =
+ new TIntermConstantUnion(maxPointSizeConstant, TType(EbtFloat, EbpHigh, EvqConst));
+
+ // min(gl_PointSize, maxPointSize)
+ TIntermSequence minArguments;
+ minArguments.push_back(pointSizeNode->deepCopy());
+ minArguments.push_back(maxPointSizeNode);
+ TIntermTyped *clampedPointSize =
+ CreateBuiltInFunctionCallNode("min", &minArguments, *symbolTable, 100);
+
+ // gl_PointSize = min(gl_PointSize, maxPointSize)
+ TIntermBinary *assignPointSize = new TIntermBinary(EOpAssign, pointSizeNode, clampedPointSize);
+
+ return RunAtTheEndOfShader(compiler, root, assignPointSize, symbolTable);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h
new file mode 100644
index 0000000000..c42f32bde2
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ClampPointSize.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ClampPointSize.h: Limit the value that is written to gl_PointSize.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_CLAMPPOINTSIZE_H_
+#define COMPILER_TRANSLATOR_TREEOPS_CLAMPPOINTSIZE_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool ClampPointSize(TCompiler *compiler,
+ TIntermBlock *root,
+ float maxPointSize,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_CLAMPPOINTSIZE_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp
new file mode 100644
index 0000000000..5c45e1fb59
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.cpp
@@ -0,0 +1,335 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h"
+#include "compiler/translator/ImmutableString.h"
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/FindFunction.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermRebuild.h"
+
+using namespace sh;
+
+namespace
+{
+
+void AppendMatrixElementArgument(TIntermSymbol *parameter,
+ int colIndex,
+ int rowIndex,
+ TIntermSequence *returnCtorArgs)
+{
+ TIntermBinary *matColN =
+ new TIntermBinary(EOpIndexDirect, parameter->deepCopy(), CreateIndexNode(colIndex));
+ TIntermSwizzle *matElem = new TIntermSwizzle(matColN, {rowIndex});
+ returnCtorArgs->push_back(matElem);
+}
+
+// Adds the argument to sequence for a scalar constructor.
+// Given scalar(scalarA) appends scalarA
+// Given scalar(vecA) appends vecA.x
+// Given scalar(matA) appends matA[0].x
+void AppendScalarFromNonScalarArguments(TFunction &function, TIntermSequence *returnCtorArgs)
+{
+ const TVariable *var = function.getParam(0);
+ TIntermSymbol *arg0 = new TIntermSymbol(var);
+
+ const TType &type = arg0->getType();
+
+ if (type.isScalar())
+ {
+ returnCtorArgs->push_back(arg0);
+ }
+ else if (type.isVector())
+ {
+ TIntermSwizzle *vecX = new TIntermSwizzle(arg0, {0});
+ returnCtorArgs->push_back(vecX);
+ }
+ else if (type.isMatrix())
+ {
+ AppendMatrixElementArgument(arg0, 0, 0, returnCtorArgs);
+ }
+}
+
+// Adds the arguments to sequence for a vector constructor from a scalar.
+// Given vecN(scalarA) appends scalarA, scalarA, ... n times
+void AppendVectorFromScalarArgument(const TType &type,
+ TFunction &function,
+ TIntermSequence *returnCtorArgs)
+{
+ const uint8_t vectorSize = type.getNominalSize();
+ const TVariable *var = function.getParam(0);
+ TIntermSymbol *v = new TIntermSymbol(var);
+ for (uint8_t i = 0; i < vectorSize; ++i)
+ {
+ returnCtorArgs->push_back(v->deepCopy());
+ }
+}
+
+// Adds the arguments to sequence for a vector or matrix constructor from the available arguments
+// applying arguments in order until the requested number of values have been extracted from the
+// given arguments or until there are no more arguments.
+void AppendValuesFromMultipleArguments(int numValuesNeeded,
+ TFunction &function,
+ TIntermSequence *returnCtorArgs)
+{
+ size_t numParameters = function.getParamCount();
+ size_t paramIndex = 0;
+ uint8_t colIndex = 0;
+ uint8_t rowIndex = 0;
+
+ for (int i = 0; i < numValuesNeeded && paramIndex < numParameters; ++i)
+ {
+ const TVariable *p = function.getParam(paramIndex);
+ TIntermSymbol *parameter = new TIntermSymbol(p);
+ if (parameter->isScalar())
+ {
+ returnCtorArgs->push_back(parameter);
+ ++paramIndex;
+ }
+ else if (parameter->isVector())
+ {
+ TIntermSwizzle *vecS = new TIntermSwizzle(parameter->deepCopy(), {rowIndex++});
+ returnCtorArgs->push_back(vecS);
+ if (rowIndex == parameter->getNominalSize())
+ {
+ ++paramIndex;
+ rowIndex = 0;
+ }
+ }
+ else if (parameter->isMatrix())
+ {
+ AppendMatrixElementArgument(parameter, colIndex, rowIndex++, returnCtorArgs);
+ if (rowIndex == parameter->getSecondarySize())
+ {
+ rowIndex = 0;
+ ++colIndex;
+ if (colIndex == parameter->getNominalSize())
+ {
+ colIndex = 0;
+ ++paramIndex;
+ }
+ }
+ }
+ }
+}
+
+// Adds the arguments for a matrix constructor from a scalar
+// putting the scalar along the diagonal and 0 everywhere else.
+void AppendMatrixFromScalarArgument(const TType &type,
+ TFunction &function,
+ TIntermSequence *returnCtorArgs)
+{
+ const TVariable *var = function.getParam(0);
+ TIntermSymbol *v = new TIntermSymbol(var);
+ const uint8_t numCols = type.getNominalSize();
+ const uint8_t numRows = type.getSecondarySize();
+ for (uint8_t col = 0; col < numCols; ++col)
+ {
+ for (uint8_t row = 0; row < numRows; ++row)
+ {
+ if (col == row)
+ {
+ returnCtorArgs->push_back(v->deepCopy());
+ }
+ else
+ {
+ returnCtorArgs->push_back(CreateFloatNode(0.0f, sh::EbpUndefined));
+ }
+ }
+ }
+}
+
+// Add the argument for a matrix constructor from a matrix
+// copying elements from the same column/row and otherwise
+// initialize to the identity matrix.
+void AppendMatrixFromMatrixArgument(const TType &type,
+ TFunction &function,
+ TIntermSequence *returnCtorArgs)
+{
+ const TVariable *var = function.getParam(0);
+ TIntermSymbol *v = new TIntermSymbol(var);
+ const uint8_t dstCols = type.getNominalSize();
+ const uint8_t dstRows = type.getSecondarySize();
+ const uint8_t srcCols = v->getNominalSize();
+ const uint8_t srcRows = v->getSecondarySize();
+ for (uint8_t dstCol = 0; dstCol < dstCols; ++dstCol)
+ {
+ for (uint8_t dstRow = 0; dstRow < dstRows; ++dstRow)
+ {
+ if (dstRow < srcRows && dstCol < srcCols)
+ {
+ AppendMatrixElementArgument(v, dstCol, dstRow, returnCtorArgs);
+ }
+ else
+ {
+ returnCtorArgs->push_back(
+ CreateFloatNode(dstRow == dstCol ? 1.0f : 0.0f, sh::EbpUndefined));
+ }
+ }
+ }
+}
+
+class Rebuild : public TIntermRebuild
+{
+ public:
+ explicit Rebuild(TCompiler &compiler) : TIntermRebuild(compiler, false, true) {}
+ PostResult visitAggregatePost(TIntermAggregate &node) override
+ {
+ if (!node.isConstructor())
+ {
+ return node;
+ }
+
+ TIntermSequence &arguments = *node.getSequence();
+ if (arguments.empty())
+ {
+ return node;
+ }
+
+ const TType &type = node.getType();
+ const TType &arg0Type = arguments[0]->getAsTyped()->getType();
+
+ if (!type.isScalar() && !type.isVector() && !type.isMatrix())
+ {
+ return node;
+ }
+
+ if (type.isArray())
+ {
+ return node;
+ }
+
+ // check for type_ctor(sameType)
+ // scalar(scalar) -> passthrough
+ // vecN(vecN) -> passthrough
+ // matN(matN) -> passthrough
+ if (arguments.size() == 1 && arg0Type == type)
+ {
+ return node;
+ }
+
+ // The following are simple casts:
+ //
+ // - basic(s) (where basic is int, uint, float or bool, and s is scalar).
+ // - gvecN(vN) (where the argument is a single vector with the same number of components).
+ // - matNxM(mNxM) (where the argument is a single matrix with the same dimensions). Note
+ // that
+ // matrices are always float, so there's no actual cast and this would be a no-op.
+ //
+ const bool isSingleScalarCast =
+ arguments.size() == 1 && type.isScalar() && arg0Type.isScalar();
+ const bool isSingleVectorCast = arguments.size() == 1 && type.isVector() &&
+ arg0Type.isVector() &&
+ type.getNominalSize() == arg0Type.getNominalSize();
+ const bool isSingleMatrixCast =
+ arguments.size() == 1 && type.isMatrix() && arg0Type.isMatrix() &&
+ type.getCols() == arg0Type.getCols() && type.getRows() == arg0Type.getRows();
+ if (isSingleScalarCast || isSingleVectorCast || isSingleMatrixCast)
+ {
+ return node;
+ }
+
+ // Cases we need to handle:
+ // scalar(vec)
+ // scalar(mat)
+ // vecN(scalar)
+ // vecN(vecM)
+ // vecN(a,...)
+ // matN(scalar) -> diag
+ // matN(vec) -> fail!
+ // manN(matM) -> corner + ident
+ // matN(a, ...)
+
+ // Build a function and pass all the constructor's arguments to it.
+ TIntermBlock *body = new TIntermBlock;
+ TFunction *function = new TFunction(&mSymbolTable, ImmutableString(""),
+ SymbolType::AngleInternal, &type, true);
+
+ for (size_t i = 0; i < arguments.size(); ++i)
+ {
+ TIntermTyped &arg = *arguments[i]->getAsTyped();
+ TType *argType = new TType(arg.getBasicType(), arg.getPrecision(), EvqParamIn,
+ arg.getNominalSize(), arg.getSecondarySize());
+ TVariable *var = CreateTempVariable(&mSymbolTable, argType);
+ function->addParameter(var);
+ }
+
+ // Build a return statement for the function that
+ // converts the arguments into the required type.
+ TIntermSequence *returnCtorArgs = new TIntermSequence();
+
+ if (type.isScalar())
+ {
+ AppendScalarFromNonScalarArguments(*function, returnCtorArgs);
+ }
+ else if (type.isVector())
+ {
+ if (arguments.size() == 1 && arg0Type.isScalar())
+ {
+ AppendVectorFromScalarArgument(type, *function, returnCtorArgs);
+ }
+ else
+ {
+ AppendValuesFromMultipleArguments(type.getNominalSize(), *function, returnCtorArgs);
+ }
+ }
+ else if (type.isMatrix())
+ {
+ if (arguments.size() == 1 && arg0Type.isScalar())
+ {
+ // MSL already handles this case
+ AppendMatrixFromScalarArgument(type, *function, returnCtorArgs);
+ }
+ else if (arg0Type.isMatrix())
+ {
+ AppendMatrixFromMatrixArgument(type, *function, returnCtorArgs);
+ }
+ else
+ {
+ AppendValuesFromMultipleArguments(type.getNominalSize() * type.getSecondarySize(),
+ *function, returnCtorArgs);
+ }
+ }
+
+ TIntermBranch *returnStatement =
+ new TIntermBranch(EOpReturn, TIntermAggregate::CreateConstructor(type, returnCtorArgs));
+ body->appendStatement(returnStatement);
+
+ TIntermFunctionDefinition *functionDefinition =
+ CreateInternalFunctionDefinitionNode(*function, body);
+ mFunctionDefs.push_back(functionDefinition);
+
+ TIntermTyped *functionCall = TIntermAggregate::CreateFunctionCall(*function, &arguments);
+
+ return *functionCall;
+ }
+
+ bool rewrite(TIntermBlock &root)
+ {
+ if (!rebuildInPlace(root))
+ {
+ return true;
+ }
+
+ size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(&root);
+ for (TIntermFunctionDefinition *functionDefinition : mFunctionDefs)
+ {
+ root.insertChildNodes(firstFunctionIndex, TIntermSequence({functionDefinition}));
+ }
+
+ return mCompiler.validateAST(&root);
+ }
+
+ private:
+ TVector<TIntermFunctionDefinition *> mFunctionDefs;
+};
+
+} // anonymous namespace
+
+bool sh::ConvertUnsupportedConstructorsToFunctionCalls(TCompiler &compiler, TIntermBlock &root)
+{
+ return Rebuild(compiler).rewrite(root);
+}
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h
new file mode 100644
index 0000000000..720a02c9a6
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ConvertUnsupportedConstructorsToFunctionCalls.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREE_OPS_CONVERTUNSUPPORTEDCONSTRUCTORSTOFUNCTIONCALLS_H_
+#define COMPILER_TRANSLATOR_TREE_OPS_CONVERTUNSUPPORTEDCONSTRUCTORSTOFUNCTIONCALLS_H_
+
+#include "compiler/translator/Compiler.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TInterBlock;
+class SymbolEnv;
+
+// Adds explicit type casts into the AST where casting is done implicitly.
+[[nodiscard]] bool ConvertUnsupportedConstructorsToFunctionCalls(TCompiler &compiler,
+ TIntermBlock &root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREE_OPS_CONVERTUNSUPPORTEDCONSTRUCTORSTOFUNCTIONCALLS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp
new file mode 100644
index 0000000000..8c2925949d
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.cpp
@@ -0,0 +1,196 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Applies the necessary AST transformations to support multiview rendering through instancing.
+// Check the header file For more information.
+//
+
+#include "compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_ops/InitializeVariables.h"
+#include "compiler/translator/tree_util/BuiltIn.h"
+#include "compiler/translator/tree_util/FindMain.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+constexpr const ImmutableString kViewIDVariableName("ViewID_OVR");
+constexpr const ImmutableString kInstanceIDVariableName("InstanceID");
+constexpr const ImmutableString kMultiviewBaseViewLayerIndexVariableName(
+ "multiviewBaseViewLayerIndex");
+
+// Adds the InstanceID and ViewID_OVR initializers to the end of the initializers' sequence.
+void InitializeViewIDAndInstanceID(const TVariable *viewID,
+ const TVariable *instanceID,
+ unsigned numberOfViews,
+ const TSymbolTable &symbolTable,
+ TIntermSequence *initializers)
+{
+ // Create an unsigned numberOfViews node.
+ TConstantUnion *numberOfViewsUnsignedConstant = new TConstantUnion();
+ numberOfViewsUnsignedConstant->setUConst(numberOfViews);
+ TIntermConstantUnion *numberOfViewsUint =
+ new TIntermConstantUnion(numberOfViewsUnsignedConstant, TType(EbtUInt, EbpLow, EvqConst));
+
+ // Create a uint(gl_InstanceID) node.
+ TIntermSequence glInstanceIDSymbolCastArguments;
+ glInstanceIDSymbolCastArguments.push_back(new TIntermSymbol(BuiltInVariable::gl_InstanceID()));
+ TIntermAggregate *glInstanceIDAsUint = TIntermAggregate::CreateConstructor(
+ TType(EbtUInt, EbpHigh, EvqTemporary), &glInstanceIDSymbolCastArguments);
+
+ // Create a uint(gl_InstanceID) / numberOfViews node.
+ TIntermBinary *normalizedInstanceID =
+ new TIntermBinary(EOpDiv, glInstanceIDAsUint, numberOfViewsUint);
+
+ // Create an int(uint(gl_InstanceID) / numberOfViews) node.
+ TIntermSequence normalizedInstanceIDCastArguments;
+ normalizedInstanceIDCastArguments.push_back(normalizedInstanceID);
+ TIntermAggregate *normalizedInstanceIDAsInt = TIntermAggregate::CreateConstructor(
+ TType(EbtInt, EbpHigh, EvqTemporary), &normalizedInstanceIDCastArguments);
+
+ // Create an InstanceID = int(uint(gl_InstanceID) / numberOfViews) node.
+ TIntermBinary *instanceIDInitializer =
+ new TIntermBinary(EOpAssign, new TIntermSymbol(instanceID), normalizedInstanceIDAsInt);
+ initializers->push_back(instanceIDInitializer);
+
+ // Create a uint(gl_InstanceID) % numberOfViews node.
+ TIntermBinary *normalizedViewID =
+ new TIntermBinary(EOpIMod, glInstanceIDAsUint->deepCopy(), numberOfViewsUint->deepCopy());
+
+ // Create a ViewID_OVR = uint(gl_InstanceID) % numberOfViews node.
+ TIntermBinary *viewIDInitializer =
+ new TIntermBinary(EOpAssign, new TIntermSymbol(viewID), normalizedViewID);
+ initializers->push_back(viewIDInitializer);
+}
+
+// Adds a branch to write int(ViewID_OVR) to either gl_ViewportIndex or gl_Layer. The branch is
+// added to the end of the initializers' sequence.
+void SelectViewIndexInVertexShader(const TVariable *viewID,
+ const TVariable *multiviewBaseViewLayerIndex,
+ TIntermSequence *initializers,
+ const TSymbolTable &symbolTable)
+{
+ // Create an int(ViewID_OVR) node.
+ TIntermSequence viewIDSymbolCastArguments;
+ viewIDSymbolCastArguments.push_back(new TIntermSymbol(viewID));
+ TIntermAggregate *viewIDAsInt = TIntermAggregate::CreateConstructor(
+ TType(EbtInt, EbpHigh, EvqTemporary), &viewIDSymbolCastArguments);
+
+ // Create a gl_ViewportIndex node.
+ TIntermSymbol *viewportIndexSymbol = new TIntermSymbol(BuiltInVariable::gl_ViewportIndex());
+
+ // Create a { gl_ViewportIndex = int(ViewID_OVR) } node.
+ TIntermBlock *viewportIndexInitializerInBlock = new TIntermBlock();
+ viewportIndexInitializerInBlock->appendStatement(
+ new TIntermBinary(EOpAssign, viewportIndexSymbol, viewIDAsInt));
+
+ // Create a gl_Layer node.
+ TIntermSymbol *layerSymbol = new TIntermSymbol(BuiltInVariable::gl_LayerVS());
+
+ // Create an int(ViewID_OVR) + multiviewBaseViewLayerIndex node
+ TIntermBinary *sumOfViewIDAndBaseViewIndex = new TIntermBinary(
+ EOpAdd, viewIDAsInt->deepCopy(), new TIntermSymbol(multiviewBaseViewLayerIndex));
+
+ // Create a { gl_Layer = int(ViewID_OVR) + multiviewBaseViewLayerIndex } node.
+ TIntermBlock *layerInitializerInBlock = new TIntermBlock();
+ layerInitializerInBlock->appendStatement(
+ new TIntermBinary(EOpAssign, layerSymbol, sumOfViewIDAndBaseViewIndex));
+
+ // Create a node to compare whether the base view index uniform is less than zero.
+ TIntermBinary *multiviewBaseViewLayerIndexZeroComparison =
+ new TIntermBinary(EOpLessThan, new TIntermSymbol(multiviewBaseViewLayerIndex),
+ CreateZeroNode(TType(EbtInt, EbpHigh, EvqConst)));
+
+ // Create an if-else statement to select the code path.
+ TIntermIfElse *multiviewBranch =
+ new TIntermIfElse(multiviewBaseViewLayerIndexZeroComparison,
+ viewportIndexInitializerInBlock, layerInitializerInBlock);
+
+ initializers->push_back(multiviewBranch);
+}
+
+} // namespace
+
+bool DeclareAndInitBuiltinsForInstancedMultiview(TCompiler *compiler,
+ TIntermBlock *root,
+ unsigned numberOfViews,
+ GLenum shaderType,
+ const ShCompileOptions &compileOptions,
+ ShShaderOutput shaderOutput,
+ TSymbolTable *symbolTable)
+{
+ ASSERT(shaderType == GL_VERTEX_SHADER || shaderType == GL_FRAGMENT_SHADER);
+
+ TQualifier viewIDQualifier = (shaderType == GL_VERTEX_SHADER) ? EvqFlatOut : EvqFlatIn;
+ const TVariable *viewID =
+ new TVariable(symbolTable, kViewIDVariableName,
+ new TType(EbtUInt, EbpHigh, viewIDQualifier), SymbolType::AngleInternal);
+
+ DeclareGlobalVariable(root, viewID);
+ if (!ReplaceVariable(compiler, root, BuiltInVariable::gl_ViewID_OVR(), viewID))
+ {
+ return false;
+ }
+ if (shaderType == GL_VERTEX_SHADER)
+ {
+ // Replacing gl_InstanceID with InstanceID should happen before adding the initializers of
+ // InstanceID and ViewID.
+ const TType *instanceIDVariableType = StaticType::Get<EbtInt, EbpHigh, EvqGlobal, 1, 1>();
+ const TVariable *instanceID =
+ new TVariable(symbolTable, kInstanceIDVariableName, instanceIDVariableType,
+ SymbolType::AngleInternal);
+ DeclareGlobalVariable(root, instanceID);
+ if (!ReplaceVariable(compiler, root, BuiltInVariable::gl_InstanceID(), instanceID))
+ {
+ return false;
+ }
+
+ TIntermSequence initializers;
+ InitializeViewIDAndInstanceID(viewID, instanceID, numberOfViews, *symbolTable,
+ &initializers);
+
+ // The AST transformation which adds the expression to select the viewport index should
+ // be done only for the GLSL and ESSL output.
+ const bool selectView = compileOptions.selectViewInNvGLSLVertexShader;
+ // Assert that if the view is selected in the vertex shader, then the output is
+ // either GLSL or ESSL.
+ ASSERT(!selectView || IsOutputGLSL(shaderOutput) || IsOutputESSL(shaderOutput));
+ if (selectView)
+ {
+ // Add a uniform to switch between side-by-side and layered rendering.
+ const TType *baseLayerIndexVariableType =
+ StaticType::Get<EbtInt, EbpHigh, EvqUniform, 1, 1>();
+ const TVariable *multiviewBaseViewLayerIndex =
+ new TVariable(symbolTable, kMultiviewBaseViewLayerIndexVariableName,
+ baseLayerIndexVariableType, SymbolType::AngleInternal);
+ DeclareGlobalVariable(root, multiviewBaseViewLayerIndex);
+
+ // Setting a value to gl_ViewportIndex or gl_Layer should happen after ViewID_OVR's
+ // initialization.
+ SelectViewIndexInVertexShader(viewID, multiviewBaseViewLayerIndex, &initializers,
+ *symbolTable);
+ }
+
+ // Insert initializers at the beginning of main().
+ TIntermBlock *initializersBlock = new TIntermBlock();
+ initializersBlock->getSequence()->swap(initializers);
+ TIntermBlock *mainBody = FindMainBody(root);
+ mainBody->getSequence()->insert(mainBody->getSequence()->begin(), initializersBlock);
+ }
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h
new file mode 100644
index 0000000000..49a964d234
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h
@@ -0,0 +1,52 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Regardless of the shader type, the following AST transformations are applied:
+// - Add declaration of View_ID_OVR.
+// - Replace every occurrence of gl_ViewID_OVR with ViewID_OVR, mark ViewID_OVR as internal and
+// declare it as a flat varying.
+//
+// If the shader type is a vertex shader, the following AST transformations are applied:
+// - Replace every occurrence of gl_InstanceID with InstanceID, mark InstanceID as internal and set
+// its qualifier to EvqTemporary.
+// - Add initializers of ViewID_OVR and InstanceID to the beginning of the body of main. The pass
+// should be executed before any variables get collected so that usage of gl_InstanceID is recorded.
+// - If the output is ESSL or GLSL and the selectViewInNvGLSLVertexShader option is
+// enabled, the expression
+// "if (multiviewBaseViewLayerIndex < 0) {
+// gl_ViewportIndex = int(ViewID_OVR);
+// } else {
+// gl_Layer = int(ViewID_OVR) + multiviewBaseViewLayerIndex;
+// }"
+// is added after ViewID and InstanceID are initialized. Also, MultiviewRenderPath is added as a
+// uniform.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_
+#define COMPILER_TRANSLATOR_TREEOPS_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_
+
+#include "GLSLANG/ShaderLang.h"
+#include "angle_gl.h"
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool DeclareAndInitBuiltinsForInstancedMultiview(
+ TCompiler *compiler,
+ TIntermBlock *root,
+ unsigned numberOfViews,
+ GLenum shaderType,
+ const ShCompileOptions &compileOptions,
+ ShShaderOutput shaderOutput,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_DECLAREANDINITBUILTINSFORINSTANCEDMULTIVIEW_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp
new file mode 100644
index 0000000000..035de73431
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.cpp
@@ -0,0 +1,180 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// DeferGlobalInitializers is an AST traverser that moves global initializers into a separate
+// function that is called in the beginning of main(). This enables initialization of globals with
+// uniforms or non-constant globals, as allowed by the WebGL spec. Some initializers referencing
+// non-constants may need to be unfolded into if statements in HLSL - this kind of steps should be
+// done after DeferGlobalInitializers is run. Note that it's important that the function definition
+// is at the end of the shader, as some globals may be declared after main().
+//
+// It can also initialize all uninitialized globals.
+//
+
+#include "compiler/translator/tree_ops/DeferGlobalInitializers.h"
+
+#include <vector>
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_ops/InitializeVariables.h"
+#include "compiler/translator/tree_util/FindMain.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+
+namespace
+{
+
+constexpr const ImmutableString kInitGlobalsString("initGlobals");
+
+void GetDeferredInitializers(TIntermDeclaration *declaration,
+ bool initializeUninitializedGlobals,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ bool forceDeferGlobalInitializers,
+ TIntermSequence *deferredInitializersOut,
+ std::vector<const TVariable *> *variablesToReplaceOut,
+ TSymbolTable *symbolTable)
+{
+ // SeparateDeclarations should have already been run.
+ ASSERT(declaration->getSequence()->size() == 1);
+
+ TIntermNode *declarator = declaration->getSequence()->back();
+ TIntermBinary *init = declarator->getAsBinaryNode();
+ if (init)
+ {
+ TIntermSymbol *symbolNode = init->getLeft()->getAsSymbolNode();
+ ASSERT(symbolNode);
+ TIntermTyped *expression = init->getRight();
+
+ if (expression->getQualifier() != EvqConst || !expression->hasConstantValue() ||
+ forceDeferGlobalInitializers)
+ {
+ // For variables which are not constant, defer their real initialization until
+ // after we initialize uniforms.
+ // Deferral is done also in any cases where the variable can not be converted to a
+ // constant union, since otherwise there's a chance that HLSL output will generate extra
+ // statements from the initializer expression.
+
+ // Change const global to a regular global if its initialization is deferred.
+ // This can happen if ANGLE has not been able to fold the constant expression used
+ // as an initializer.
+ ASSERT(symbolNode->getQualifier() == EvqConst ||
+ symbolNode->getQualifier() == EvqGlobal);
+ if (symbolNode->getQualifier() == EvqConst)
+ {
+ variablesToReplaceOut->push_back(&symbolNode->variable());
+ }
+
+ TIntermBinary *deferredInit =
+ new TIntermBinary(EOpAssign, symbolNode->deepCopy(), init->getRight());
+ deferredInitializersOut->push_back(deferredInit);
+
+ // Remove the initializer from the global scope and just declare the global instead.
+ declaration->replaceChildNode(init, symbolNode);
+ }
+ }
+ else if (initializeUninitializedGlobals)
+ {
+ TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
+ ASSERT(symbolNode);
+
+ // Ignore ANGLE internal variables and nameless declarations.
+ if (symbolNode->variable().symbolType() == SymbolType::AngleInternal ||
+ symbolNode->variable().symbolType() == SymbolType::Empty)
+ return;
+
+ if (symbolNode->getQualifier() == EvqGlobal)
+ {
+ TIntermSequence initCode;
+ CreateInitCode(symbolNode, canUseLoopsToInitialize, highPrecisionSupported, &initCode,
+ symbolTable);
+ deferredInitializersOut->insert(deferredInitializersOut->end(), initCode.begin(),
+ initCode.end());
+ }
+ }
+}
+
+void InsertInitCallToMain(TIntermBlock *root,
+ TIntermSequence *deferredInitializers,
+ TSymbolTable *symbolTable)
+{
+ TIntermBlock *initGlobalsBlock = new TIntermBlock();
+ initGlobalsBlock->getSequence()->swap(*deferredInitializers);
+
+ TFunction *initGlobalsFunction =
+ new TFunction(symbolTable, kInitGlobalsString, SymbolType::AngleInternal,
+ StaticType::GetBasic<EbtVoid, EbpUndefined>(), false);
+
+ TIntermFunctionPrototype *initGlobalsFunctionPrototype =
+ CreateInternalFunctionPrototypeNode(*initGlobalsFunction);
+ root->getSequence()->insert(root->getSequence()->begin(), initGlobalsFunctionPrototype);
+ TIntermFunctionDefinition *initGlobalsFunctionDefinition =
+ CreateInternalFunctionDefinitionNode(*initGlobalsFunction, initGlobalsBlock);
+ root->appendStatement(initGlobalsFunctionDefinition);
+
+ TIntermSequence emptySequence;
+ TIntermAggregate *initGlobalsCall =
+ TIntermAggregate::CreateFunctionCall(*initGlobalsFunction, &emptySequence);
+
+ TIntermBlock *mainBody = FindMainBody(root);
+ mainBody->getSequence()->insert(mainBody->getSequence()->begin(), initGlobalsCall);
+}
+
+} // namespace
+
+bool DeferGlobalInitializers(TCompiler *compiler,
+ TIntermBlock *root,
+ bool initializeUninitializedGlobals,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ bool forceDeferGlobalInitializers,
+ TSymbolTable *symbolTable)
+{
+ TIntermSequence deferredInitializers;
+ std::vector<const TVariable *> variablesToReplace;
+
+ // Loop over all global statements and process the declarations. This is simpler than using a
+ // traverser.
+ for (TIntermNode *statement : *root->getSequence())
+ {
+ TIntermDeclaration *declaration = statement->getAsDeclarationNode();
+ if (declaration)
+ {
+ GetDeferredInitializers(declaration, initializeUninitializedGlobals,
+ canUseLoopsToInitialize, highPrecisionSupported,
+ forceDeferGlobalInitializers, &deferredInitializers,
+ &variablesToReplace, symbolTable);
+ }
+ }
+
+ // Add the function with initialization and the call to that.
+ if (!deferredInitializers.empty())
+ {
+ InsertInitCallToMain(root, &deferredInitializers, symbolTable);
+ }
+
+ // Replace constant variables with non-constant global variables.
+ for (const TVariable *var : variablesToReplace)
+ {
+ TType *replacementType = new TType(var->getType());
+ replacementType->setQualifier(EvqGlobal);
+ TVariable *replacement =
+ new TVariable(symbolTable, var->name(), replacementType, var->symbolType());
+ if (!ReplaceVariable(compiler, root, var, replacement))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h
new file mode 100644
index 0000000000..58a38a982b
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/DeferGlobalInitializers.h
@@ -0,0 +1,38 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// DeferGlobalInitializers is an AST traverser that moves global initializers into a separate
+// function that is called in the beginning of main(). This enables initialization of globals with
+// uniforms or non-constant globals, as allowed by the WebGL spec. Some initializers referencing
+// non-constants may need to be unfolded into if statements in HLSL - this kind of steps should be
+// done after DeferGlobalInitializers is run. Note that it's important that the function definition
+// is at the end of the shader, as some globals may be declared after main().
+//
+// It can also initialize all uninitialized globals.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_DEFERGLOBALINITIALIZERS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_DEFERGLOBALINITIALIZERS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool DeferGlobalInitializers(TCompiler *compiler,
+ TIntermBlock *root,
+ bool initializeUninitializedGlobals,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ bool forceDeferGlobalInitializers,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_DEFERGLOBALINITIALIZERS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp
new file mode 100644
index 0000000000..25e1fde29a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.cpp
@@ -0,0 +1,142 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// gl_FragColor needs to broadcast to all color buffers in ES2 if
+// GL_EXT_draw_buffers is explicitly enabled in a fragment shader.
+//
+// We emulate this by replacing all gl_FragColor with gl_FragData[0], and in the end
+// of main() function, assigning gl_FragData[1], ..., gl_FragData[maxDrawBuffers-1]
+// with gl_FragData[0].
+//
+
+#include "compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
+
+namespace sh
+{
+
+namespace
+{
+
+constexpr const ImmutableString kGlFragDataString("gl_FragData");
+
+class GLFragColorBroadcastTraverser : public TIntermTraverser
+{
+ public:
+ GLFragColorBroadcastTraverser(int maxDrawBuffers, TSymbolTable *symbolTable, int shaderVersion)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mGLFragColorUsed(false),
+ mMaxDrawBuffers(maxDrawBuffers),
+ mShaderVersion(shaderVersion)
+ {}
+
+ [[nodiscard]] bool broadcastGLFragColor(TCompiler *compiler, TIntermBlock *root);
+
+ bool isGLFragColorUsed() const { return mGLFragColorUsed; }
+
+ protected:
+ void visitSymbol(TIntermSymbol *node) override;
+
+ TIntermBinary *constructGLFragDataNode(int index) const;
+ TIntermBinary *constructGLFragDataAssignNode(int index) const;
+
+ private:
+ bool mGLFragColorUsed;
+ int mMaxDrawBuffers;
+ const int mShaderVersion;
+};
+
+TIntermBinary *GLFragColorBroadcastTraverser::constructGLFragDataNode(int index) const
+{
+ TIntermSymbol *symbol =
+ ReferenceBuiltInVariable(kGlFragDataString, *mSymbolTable, mShaderVersion);
+ TIntermTyped *indexNode = CreateIndexNode(index);
+
+ TIntermBinary *binary = new TIntermBinary(EOpIndexDirect, symbol, indexNode);
+ return binary;
+}
+
+TIntermBinary *GLFragColorBroadcastTraverser::constructGLFragDataAssignNode(int index) const
+{
+ TIntermTyped *fragDataIndex = constructGLFragDataNode(index);
+ TIntermTyped *fragDataZero = constructGLFragDataNode(0);
+
+ return new TIntermBinary(EOpAssign, fragDataIndex, fragDataZero);
+}
+
+void GLFragColorBroadcastTraverser::visitSymbol(TIntermSymbol *node)
+{
+ if (node->variable().symbolType() == SymbolType::BuiltIn && node->getName() == "gl_FragColor")
+ {
+ queueReplacement(constructGLFragDataNode(0), OriginalNode::IS_DROPPED);
+ mGLFragColorUsed = true;
+ }
+}
+
+bool GLFragColorBroadcastTraverser::broadcastGLFragColor(TCompiler *compiler, TIntermBlock *root)
+{
+ ASSERT(mMaxDrawBuffers > 1);
+ if (!mGLFragColorUsed)
+ {
+ return true;
+ }
+
+ TIntermBlock *broadcastBlock = new TIntermBlock();
+ // Now insert statements
+ // gl_FragData[1] = gl_FragData[0];
+ // ...
+ // gl_FragData[maxDrawBuffers - 1] = gl_FragData[0];
+ for (int colorIndex = 1; colorIndex < mMaxDrawBuffers; ++colorIndex)
+ {
+ broadcastBlock->appendStatement(constructGLFragDataAssignNode(colorIndex));
+ }
+ return RunAtTheEndOfShader(compiler, root, broadcastBlock, mSymbolTable);
+}
+
+} // namespace
+
+bool EmulateGLFragColorBroadcast(TCompiler *compiler,
+ TIntermBlock *root,
+ int maxDrawBuffers,
+ std::vector<sh::ShaderVariable> *outputVariables,
+ TSymbolTable *symbolTable,
+ int shaderVersion)
+{
+ ASSERT(maxDrawBuffers > 1);
+ GLFragColorBroadcastTraverser traverser(maxDrawBuffers, symbolTable, shaderVersion);
+ root->traverse(&traverser);
+ if (traverser.isGLFragColorUsed())
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ if (!traverser.broadcastGLFragColor(compiler, root))
+ {
+ return false;
+ }
+
+ for (auto &var : *outputVariables)
+ {
+ if (var.name == "gl_FragColor")
+ {
+ // TODO(zmo): Find a way to keep the original variable information.
+ var.name = "gl_FragData";
+ var.mappedName = "gl_FragData";
+ var.arraySizes.push_back(maxDrawBuffers);
+ ASSERT(var.arraySizes.size() == 1u);
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h
new file mode 100644
index 0000000000..c71ba7c2be
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h
@@ -0,0 +1,35 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Emulate gl_FragColor broadcast behaviors in ES2 where
+// GL_EXT_draw_buffers is explicitly enabled in a fragment shader.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_EMULATEGLFRAGCOLORBROADCAST_H_
+#define COMPILER_TRANSLATOR_TREEOPS_EMULATEGLFRAGCOLORBROADCAST_H_
+
+#include <vector>
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+struct ShaderVariable;
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+// Replace all gl_FragColor with gl_FragData[0], and in the end of main() function,
+// assign gl_FragData[1] ... gl_FragData[maxDrawBuffers - 1] with gl_FragData[0].
+// If gl_FragColor is in outputVariables, it is replaced by gl_FragData.
+[[nodiscard]] bool EmulateGLFragColorBroadcast(TCompiler *compiler,
+ TIntermBlock *root,
+ int maxDrawBuffers,
+ std::vector<ShaderVariable> *outputVariables,
+ TSymbolTable *symbolTable,
+ int shaderVersion);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_EMULATEGLFRAGCOLORBROADCAST_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp
new file mode 100644
index 0000000000..dfa36ad145
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.cpp
@@ -0,0 +1,274 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// EmulateGLDrawID is an AST traverser to convert the gl_DrawID builtin
+// to a uniform int
+//
+// EmulateGLBaseVertex is an AST traverser to convert the gl_BaseVertex builtin
+// to a uniform int
+//
+// EmulateGLBaseInstance is an AST traverser to convert the gl_BaseInstance builtin
+// to a uniform int
+//
+
+#include "compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h"
+
+#include "angle_gl.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/BuiltIn.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+constexpr const ImmutableString kEmulatedGLDrawIDName("angle_DrawID");
+
+class FindGLDrawIDTraverser : public TIntermTraverser
+{
+ public:
+ FindGLDrawIDTraverser() : TIntermTraverser(true, false, false), mVariable(nullptr) {}
+
+ const TVariable *getGLDrawIDBuiltinVariable() { return mVariable; }
+
+ protected:
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ if (&node->variable() == BuiltInVariable::gl_DrawID())
+ {
+ mVariable = &node->variable();
+ }
+ }
+
+ private:
+ const TVariable *mVariable;
+};
+
+class AddBaseVertexToGLVertexIDTraverser : public TIntermTraverser
+{
+ public:
+ AddBaseVertexToGLVertexIDTraverser() : TIntermTraverser(true, false, false) {}
+
+ protected:
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ if (&node->variable() == BuiltInVariable::gl_VertexID())
+ {
+
+ TIntermSymbol *baseVertexRef = new TIntermSymbol(BuiltInVariable::gl_BaseVertex());
+
+ TIntermBinary *addBaseVertex = new TIntermBinary(EOpAdd, node, baseVertexRef);
+ queueReplacement(addBaseVertex, OriginalNode::BECOMES_CHILD);
+ }
+ }
+};
+
+constexpr const ImmutableString kEmulatedGLBaseVertexName("angle_BaseVertex");
+
+class FindGLBaseVertexTraverser : public TIntermTraverser
+{
+ public:
+ FindGLBaseVertexTraverser() : TIntermTraverser(true, false, false), mVariable(nullptr) {}
+
+ const TVariable *getGLBaseVertexBuiltinVariable() { return mVariable; }
+
+ protected:
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ if (&node->variable() == BuiltInVariable::gl_BaseVertex())
+ {
+ mVariable = &node->variable();
+ }
+ }
+
+ private:
+ const TVariable *mVariable;
+};
+
+constexpr const ImmutableString kEmulatedGLBaseInstanceName("angle_BaseInstance");
+
+class FindGLBaseInstanceTraverser : public TIntermTraverser
+{
+ public:
+ FindGLBaseInstanceTraverser() : TIntermTraverser(true, false, false), mVariable(nullptr) {}
+
+ const TVariable *getGLBaseInstanceBuiltinVariable() { return mVariable; }
+
+ protected:
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ if (&node->variable() == BuiltInVariable::gl_BaseInstance())
+ {
+ mVariable = &node->variable();
+ }
+ }
+
+ private:
+ const TVariable *mVariable;
+};
+
+} // namespace
+
+bool EmulateGLDrawID(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ std::vector<sh::ShaderVariable> *uniforms,
+ bool shouldCollect)
+{
+ FindGLDrawIDTraverser traverser;
+ root->traverse(&traverser);
+ const TVariable *builtInVariable = traverser.getGLDrawIDBuiltinVariable();
+ if (builtInVariable)
+ {
+ const TType *type = StaticType::Get<EbtInt, EbpHigh, EvqUniform, 1, 1>();
+ const TVariable *drawID =
+ new TVariable(symbolTable, kEmulatedGLDrawIDName, type, SymbolType::AngleInternal);
+ const TIntermSymbol *drawIDSymbol = new TIntermSymbol(drawID);
+
+ // AngleInternal variables don't get collected
+ if (shouldCollect)
+ {
+ ShaderVariable uniform;
+ uniform.name = kEmulatedGLDrawIDName.data();
+ uniform.mappedName = kEmulatedGLDrawIDName.data();
+ uniform.type = GLVariableType(*type);
+ uniform.precision = GLVariablePrecision(*type);
+ uniform.staticUse = symbolTable->isStaticallyUsed(*builtInVariable);
+ uniform.active = true;
+ uniform.binding = type->getLayoutQualifier().binding;
+ uniform.location = type->getLayoutQualifier().location;
+ uniform.offset = type->getLayoutQualifier().offset;
+ uniform.rasterOrdered = type->getLayoutQualifier().rasterOrdered;
+ uniform.readonly = type->getMemoryQualifier().readonly;
+ uniform.writeonly = type->getMemoryQualifier().writeonly;
+ uniforms->push_back(uniform);
+ }
+
+ DeclareGlobalVariable(root, drawID);
+ if (!ReplaceVariableWithTyped(compiler, root, builtInVariable, drawIDSymbol))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool EmulateGLBaseVertexBaseInstance(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ std::vector<sh::ShaderVariable> *uniforms,
+ bool shouldCollect,
+ bool addBaseVertexToVertexID)
+{
+ bool addBaseVertex = false, addBaseInstance = false;
+ ShaderVariable uniformBaseVertex, uniformBaseInstance;
+
+ if (addBaseVertexToVertexID)
+ {
+ // This is a workaround for Mac AMD GPU
+ // Replace gl_VertexID with (gl_VertexID + gl_BaseVertex)
+ AddBaseVertexToGLVertexIDTraverser traverserVertexID;
+ root->traverse(&traverserVertexID);
+ if (!traverserVertexID.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+
+ FindGLBaseVertexTraverser traverserBaseVertex;
+ root->traverse(&traverserBaseVertex);
+ const TVariable *builtInVariableBaseVertex =
+ traverserBaseVertex.getGLBaseVertexBuiltinVariable();
+
+ if (builtInVariableBaseVertex)
+ {
+ const TVariable *baseVertex = BuiltInVariable::angle_BaseVertex();
+ const TType &type = baseVertex->getType();
+ const TIntermSymbol *baseVertexSymbol = new TIntermSymbol(baseVertex);
+
+ // AngleInternal variables don't get collected
+ if (shouldCollect)
+ {
+ uniformBaseVertex.name = kEmulatedGLBaseVertexName.data();
+ uniformBaseVertex.mappedName = kEmulatedGLBaseVertexName.data();
+ uniformBaseVertex.type = GLVariableType(type);
+ uniformBaseVertex.precision = GLVariablePrecision(type);
+ uniformBaseVertex.staticUse = symbolTable->isStaticallyUsed(*builtInVariableBaseVertex);
+ uniformBaseVertex.active = true;
+ uniformBaseVertex.binding = type.getLayoutQualifier().binding;
+ uniformBaseVertex.location = type.getLayoutQualifier().location;
+ uniformBaseVertex.offset = type.getLayoutQualifier().offset;
+ uniformBaseVertex.rasterOrdered = type.getLayoutQualifier().rasterOrdered;
+ uniformBaseVertex.readonly = type.getMemoryQualifier().readonly;
+ uniformBaseVertex.writeonly = type.getMemoryQualifier().writeonly;
+ addBaseVertex = true;
+ }
+
+ DeclareGlobalVariable(root, baseVertex);
+ if (!ReplaceVariableWithTyped(compiler, root, builtInVariableBaseVertex, baseVertexSymbol))
+ {
+ return false;
+ }
+ }
+
+ FindGLBaseInstanceTraverser traverserInstance;
+ root->traverse(&traverserInstance);
+ const TVariable *builtInVariableBaseInstance =
+ traverserInstance.getGLBaseInstanceBuiltinVariable();
+
+ if (builtInVariableBaseInstance)
+ {
+ const TVariable *baseInstance = BuiltInVariable::angle_BaseInstance();
+ const TType &type = baseInstance->getType();
+ const TIntermSymbol *baseInstanceSymbol = new TIntermSymbol(baseInstance);
+
+ // AngleInternal variables don't get collected
+ if (shouldCollect)
+ {
+ uniformBaseInstance.name = kEmulatedGLBaseInstanceName.data();
+ uniformBaseInstance.mappedName = kEmulatedGLBaseInstanceName.data();
+ uniformBaseInstance.type = GLVariableType(type);
+ uniformBaseInstance.precision = GLVariablePrecision(type);
+ uniformBaseInstance.staticUse =
+ symbolTable->isStaticallyUsed(*builtInVariableBaseInstance);
+ uniformBaseInstance.active = true;
+ uniformBaseInstance.binding = type.getLayoutQualifier().binding;
+ uniformBaseInstance.location = type.getLayoutQualifier().location;
+ uniformBaseInstance.offset = type.getLayoutQualifier().offset;
+ uniformBaseInstance.rasterOrdered = type.getLayoutQualifier().rasterOrdered;
+ uniformBaseInstance.readonly = type.getMemoryQualifier().readonly;
+ uniformBaseInstance.writeonly = type.getMemoryQualifier().writeonly;
+ addBaseInstance = true;
+ }
+
+ DeclareGlobalVariable(root, baseInstance);
+ if (!ReplaceVariableWithTyped(compiler, root, builtInVariableBaseInstance,
+ baseInstanceSymbol))
+ {
+ return false;
+ }
+ }
+
+ // Make sure the order in uniforms is the same as the traverse order
+ if (addBaseInstance)
+ {
+ uniforms->push_back(uniformBaseInstance);
+ }
+ if (addBaseVertex)
+ {
+ uniforms->push_back(uniformBaseVertex);
+ }
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h
new file mode 100644
index 0000000000..792a7c96b2
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/EmulateMultiDrawShaderBuiltins.h
@@ -0,0 +1,47 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// EmulateGLDrawID is an AST traverser to convert the gl_DrawID builtin
+// to a uniform int
+//
+// EmulateGLBaseVertexBaseInstance is an AST traverser to convert the gl_BaseVertex and
+// gl_BaseInstance builtin to uniform ints
+//
+// EmulateGLBaseInstance is an AST traverser to convert the gl_BaseInstance builtin
+// to a uniform int
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_EMULATEMULTIDRAWSHADERBUILTINS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_EMULATEMULTIDRAWSHADERBUILTINS_H_
+
+#include <GLSLANG/ShaderLang.h>
+#include <vector>
+
+#include "common/angleutils.h"
+#include "compiler/translator/HashNames.h"
+
+namespace sh
+{
+struct ShaderVariable;
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool EmulateGLDrawID(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ std::vector<sh::ShaderVariable> *uniforms,
+ bool shouldCollect);
+
+[[nodiscard]] bool EmulateGLBaseVertexBaseInstance(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ std::vector<sh::ShaderVariable> *uniforms,
+ bool shouldCollect,
+ bool addBaseVertexToVertexID);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_EMULATEMULTIDRAWSHADERBUILTINS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp
new file mode 100644
index 0000000000..1ede8c1a46
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.cpp
@@ -0,0 +1,120 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// FoldExpressions.cpp: Fold expressions. This may fold expressions so that the qualifier of the
+// folded node differs from the qualifier of the original expression, so it needs to be done after
+// parsing and validation of qualifiers is complete. Expressions that are folded:
+// 1. Ternary ops with a constant condition.
+// 2. Sequence aka comma ops where the left side has no side effects.
+// 3. Any expressions containing any of the above.
+
+#include "compiler/translator/tree_ops/FoldExpressions.h"
+
+#include "compiler/translator/Diagnostics.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class FoldExpressionsTraverser : public TIntermTraverser
+{
+ public:
+ FoldExpressionsTraverser(TDiagnostics *diagnostics)
+ : TIntermTraverser(true, false, false), mDiagnostics(diagnostics), mDidReplace(false)
+ {}
+
+ bool didReplace() { return mDidReplace; }
+
+ void nextIteration() { mDidReplace = false; }
+
+ protected:
+ bool visitTernary(Visit visit, TIntermTernary *node) override
+ {
+ TIntermTyped *folded = node->fold(mDiagnostics);
+ if (folded != node)
+ {
+ queueReplacement(folded, OriginalNode::IS_DROPPED);
+ mDidReplace = true;
+ return false;
+ }
+ return true;
+ }
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override
+ {
+ TIntermTyped *folded = node->fold(mDiagnostics);
+ if (folded != node)
+ {
+ queueReplacement(folded, OriginalNode::IS_DROPPED);
+ mDidReplace = true;
+ return false;
+ }
+ return true;
+ }
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ TIntermTyped *folded = node->fold(mDiagnostics);
+ if (folded != node)
+ {
+ queueReplacement(folded, OriginalNode::IS_DROPPED);
+ mDidReplace = true;
+ return false;
+ }
+ return true;
+ }
+
+ bool visitUnary(Visit visit, TIntermUnary *node) override
+ {
+ TIntermTyped *folded = node->fold(mDiagnostics);
+ if (folded != node)
+ {
+ queueReplacement(folded, OriginalNode::IS_DROPPED);
+ mDidReplace = true;
+ return false;
+ }
+ return true;
+ }
+
+ bool visitSwizzle(Visit visit, TIntermSwizzle *node) override
+ {
+ TIntermTyped *folded = node->fold(mDiagnostics);
+ if (folded != node)
+ {
+ queueReplacement(folded, OriginalNode::IS_DROPPED);
+ mDidReplace = true;
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ TDiagnostics *mDiagnostics;
+ bool mDidReplace;
+};
+
+} // anonymous namespace
+
+bool FoldExpressions(TCompiler *compiler, TIntermBlock *root, TDiagnostics *diagnostics)
+{
+ FoldExpressionsTraverser traverser(diagnostics);
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ } while (traverser.didReplace());
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h
new file mode 100644
index 0000000000..1592444c50
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/FoldExpressions.h
@@ -0,0 +1,29 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// FoldExpressions.h: Fold expressions. This may fold expressions so that the qualifier of the
+// folded node differs from the qualifier of the original expression, so it needs to be done after
+// parsing and validation of qualifiers is complete. Expressions that are folded: 1. Ternary ops
+// with a constant condition.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_FOLDEXPRESSIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_FOLDEXPRESSIONS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TDiagnostics;
+
+[[nodiscard]] bool FoldExpressions(TCompiler *compiler,
+ TIntermBlock *root,
+ TDiagnostics *diagnostics);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_FOLDEXPRESSIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp
new file mode 100644
index 0000000000..00dd0131c6
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.cpp
@@ -0,0 +1,101 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/ForcePrecisionQualifier.h"
+#include "angle_gl.h"
+#include "common/debug.h"
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+class TPrecisionTraverser : public TIntermTraverser
+{
+ public:
+ TPrecisionTraverser(TSymbolTable *symbolTable);
+
+ protected:
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
+
+ void overwriteVariablePrecision(TType *type) const;
+};
+
+TPrecisionTraverser::TPrecisionTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, true, true, symbolTable)
+{}
+
+void TPrecisionTraverser::overwriteVariablePrecision(TType *type) const
+{
+ if (type->getPrecision() == EbpHigh)
+ {
+ type->setPrecision(EbpMedium);
+ }
+}
+
+bool TPrecisionTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
+{
+ // Variable declaration.
+ if (visit == PreVisit)
+ {
+ const TIntermSequence &sequence = *(node->getSequence());
+ TIntermTyped *variable = sequence.front()->getAsTyped();
+ const TType &type = variable->getType();
+ TQualifier qualifier = variable->getQualifier();
+
+ // Don't modify uniform since it might be shared between vertex and fragment shader
+ if (qualifier == EvqUniform)
+ {
+ return true;
+ }
+
+ // Visit the struct.
+ if (type.isStructSpecifier())
+ {
+ const TStructure *structure = type.getStruct();
+ const TFieldList &fields = structure->fields();
+ for (size_t i = 0; i < fields.size(); ++i)
+ {
+ const TField *field = fields[i];
+ const TType *fieldType = field->type();
+ overwriteVariablePrecision((TType *)fieldType);
+ }
+ }
+ else if (type.getBasicType() == EbtInterfaceBlock)
+ {
+ const TInterfaceBlock *interfaceBlock = type.getInterfaceBlock();
+ const TFieldList &fields = interfaceBlock->fields();
+ for (const TField *field : fields)
+ {
+ const TType *fieldType = field->type();
+ overwriteVariablePrecision((TType *)fieldType);
+ }
+ }
+ else
+ {
+ overwriteVariablePrecision((TType *)&type);
+ }
+ }
+ return true;
+}
+} // namespace
+
+bool ForceShaderPrecisionToMediump(TIntermNode *root, TSymbolTable *symbolTable, GLenum shaderType)
+{
+ if (shaderType != GL_FRAGMENT_SHADER)
+ {
+ return true;
+ }
+
+ TPrecisionTraverser traverser(symbolTable);
+ root->traverse(&traverser);
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h
new file mode 100644
index 0000000000..c4cde25415
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ForcePrecisionQualifier.h
@@ -0,0 +1,17 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_FORCEPRECISIONQUALIFIER_H_
+#define COMPILER_TRANSLATOR_TREEOPS_FORCEPRECISIONQUALIFIER_H_
+
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+bool ForceShaderPrecisionToMediump(TIntermNode *root, TSymbolTable *symbolTable, GLenum shaderType);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_FORCEPRECISIONQUALIFIER_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp
new file mode 100644
index 0000000000..789610ecc0
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.cpp
@@ -0,0 +1,359 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/InitializeVariables.h"
+
+#include "angle_gl.h"
+#include "common/debug.h"
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/FindMain.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable);
+
+void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable);
+
+TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode)
+{
+ TIntermTyped *zero = CreateZeroNode(initializedNode->getType());
+ return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero);
+}
+
+void AddZeroInitSequence(const TIntermTyped *initializedNode,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable)
+{
+ if (initializedNode->isArray())
+ {
+ AddArrayZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported,
+ initSequenceOut, symbolTable);
+ }
+ else if (initializedNode->getType().isStructureContainingArrays() ||
+ initializedNode->getType().isNamelessStruct())
+ {
+ AddStructZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported,
+ initSequenceOut, symbolTable);
+ }
+ else if (initializedNode->getType().isInterfaceBlock())
+ {
+ const TType &type = initializedNode->getType();
+ const TInterfaceBlock &interfaceBlock = *type.getInterfaceBlock();
+ const TFieldList &fieldList = interfaceBlock.fields();
+ for (size_t fieldIndex = 0; fieldIndex < fieldList.size(); ++fieldIndex)
+ {
+ const TField &field = *fieldList[fieldIndex];
+ TIntermTyped *fieldIndexRef = CreateIndexNode(static_cast<int>(fieldIndex));
+ TIntermTyped *fieldReference =
+ new TIntermBinary(TOperator::EOpIndexDirectInterfaceBlock,
+ initializedNode->deepCopy(), fieldIndexRef);
+ TIntermTyped *fieldZero = CreateZeroNode(*field.type());
+ TIntermTyped *assignment =
+ new TIntermBinary(TOperator::EOpAssign, fieldReference, fieldZero);
+ initSequenceOut->push_back(assignment);
+ }
+ }
+ else
+ {
+ initSequenceOut->push_back(CreateZeroInitAssignment(initializedNode));
+ }
+}
+
+void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable)
+{
+ ASSERT(initializedNode->getBasicType() == EbtStruct);
+ const TStructure *structType = initializedNode->getType().getStruct();
+ for (int i = 0; i < static_cast<int>(structType->fields().size()); ++i)
+ {
+ TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct,
+ initializedNode->deepCopy(), CreateIndexNode(i));
+ // Structs can't be defined inside structs, so the type of a struct field can't be a
+ // nameless struct.
+ ASSERT(!element->getType().isNamelessStruct());
+ AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported,
+ initSequenceOut, symbolTable);
+ }
+}
+
+void AddArrayZeroInitStatementList(const TIntermTyped *initializedNode,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable)
+{
+ for (unsigned int i = 0; i < initializedNode->getOutermostArraySize(); ++i)
+ {
+ TIntermBinary *element =
+ new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i));
+ AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported,
+ initSequenceOut, symbolTable);
+ }
+}
+
+void AddArrayZeroInitForLoop(const TIntermTyped *initializedNode,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable)
+{
+ ASSERT(initializedNode->isArray());
+ const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>();
+ const TType *highpIndexType = StaticType::Get<EbtInt, EbpHigh, EvqTemporary, 1, 1>();
+ TVariable *indexVariable =
+ CreateTempVariable(symbolTable, highPrecisionSupported ? highpIndexType : mediumpIndexType);
+
+ TIntermSymbol *indexSymbolNode = CreateTempSymbolNode(indexVariable);
+ TIntermDeclaration *indexInit =
+ CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType()));
+ TIntermConstantUnion *arraySizeNode = CreateIndexNode(initializedNode->getOutermostArraySize());
+ TIntermBinary *indexSmallerThanSize =
+ new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode);
+ TIntermUnary *indexIncrement =
+ new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr);
+
+ TIntermBlock *forLoopBody = new TIntermBlock();
+ TIntermSequence *forLoopBodySeq = forLoopBody->getSequence();
+
+ TIntermBinary *element = new TIntermBinary(EOpIndexIndirect, initializedNode->deepCopy(),
+ indexSymbolNode->deepCopy());
+ AddZeroInitSequence(element, true, highPrecisionSupported, forLoopBodySeq, symbolTable);
+
+ TIntermLoop *forLoop =
+ new TIntermLoop(ELoopFor, indexInit, indexSmallerThanSize, indexIncrement, forLoopBody);
+ initSequenceOut->push_back(forLoop);
+}
+
+void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initSequenceOut,
+ TSymbolTable *symbolTable)
+{
+ // The array elements are assigned one by one to keep the AST compatible with ESSL 1.00 which
+ // doesn't have array assignment. We'll do this either with a for loop or just a list of
+ // statements assigning to each array index. Note that it is important to have the array init in
+ // the right order to workaround http://crbug.com/709317
+ bool isSmallArray = initializedNode->getOutermostArraySize() <= 1u ||
+ (initializedNode->getBasicType() != EbtStruct &&
+ !initializedNode->getType().isArrayOfArrays() &&
+ initializedNode->getOutermostArraySize() <= 3u);
+ if (initializedNode->getQualifier() == EvqFragData ||
+ initializedNode->getQualifier() == EvqFragmentOut || isSmallArray ||
+ !canUseLoopsToInitialize)
+ {
+ // Fragment outputs should not be indexed by non-constant indices.
+ // Also it doesn't make sense to use loops to initialize very small arrays.
+ AddArrayZeroInitStatementList(initializedNode, canUseLoopsToInitialize,
+ highPrecisionSupported, initSequenceOut, symbolTable);
+ }
+ else
+ {
+ AddArrayZeroInitForLoop(initializedNode, highPrecisionSupported, initSequenceOut,
+ symbolTable);
+ }
+}
+
+void InsertInitCode(TCompiler *compiler,
+ TIntermSequence *mainBody,
+ const InitVariableList &variables,
+ TSymbolTable *symbolTable,
+ int shaderVersion,
+ const TExtensionBehavior &extensionBehavior,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported)
+{
+ for (const ShaderVariable &var : variables)
+ {
+ // Note that tempVariableName will reference a short-lived char array here - that's fine
+ // since we're only using it to find symbols.
+ ImmutableString tempVariableName(var.name.c_str(), var.name.length());
+
+ TIntermTyped *initializedSymbol = nullptr;
+ if (var.isBuiltIn() && !symbolTable->findUserDefined(tempVariableName))
+ {
+ initializedSymbol =
+ ReferenceBuiltInVariable(tempVariableName, *symbolTable, shaderVersion);
+ if (initializedSymbol->getQualifier() == EvqFragData &&
+ !IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
+ {
+ // If GL_EXT_draw_buffers is disabled, only the 0th index of gl_FragData can be
+ // written to.
+ // TODO(oetuaho): This is a bit hacky and would be better to remove, if we came up
+ // with a good way to do it. Right now "gl_FragData" in symbol table is initialized
+ // to have the array size of MaxDrawBuffers, and the initialization happens before
+ // the shader sets the extensions it is using.
+ initializedSymbol =
+ new TIntermBinary(EOpIndexDirect, initializedSymbol, CreateIndexNode(0));
+ }
+ }
+ else
+ {
+ if (tempVariableName != "")
+ {
+ initializedSymbol = ReferenceGlobalVariable(tempVariableName, *symbolTable);
+ }
+ else
+ {
+ // Must be a nameless interface block.
+ ASSERT(var.structOrBlockName != "");
+ const TSymbol *symbol = symbolTable->findGlobal(var.structOrBlockName);
+ ASSERT(symbol && symbol->isInterfaceBlock());
+ const TInterfaceBlock *block = static_cast<const TInterfaceBlock *>(symbol);
+
+ for (const TField *field : block->fields())
+ {
+ initializedSymbol = ReferenceGlobalVariable(field->name(), *symbolTable);
+
+ TIntermSequence initCode;
+ CreateInitCode(initializedSymbol, canUseLoopsToInitialize,
+ highPrecisionSupported, &initCode, symbolTable);
+ mainBody->insert(mainBody->begin(), initCode.begin(), initCode.end());
+ }
+ // Already inserted init code in this case
+ continue;
+ }
+ }
+ ASSERT(initializedSymbol != nullptr);
+
+ TIntermSequence initCode;
+ CreateInitCode(initializedSymbol, canUseLoopsToInitialize, highPrecisionSupported,
+ &initCode, symbolTable);
+ mainBody->insert(mainBody->begin(), initCode.begin(), initCode.end());
+ }
+}
+
+class InitializeLocalsTraverser : public TIntermTraverser
+{
+ public:
+ InitializeLocalsTraverser(int shaderVersion,
+ TSymbolTable *symbolTable,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mShaderVersion(shaderVersion),
+ mCanUseLoopsToInitialize(canUseLoopsToInitialize),
+ mHighPrecisionSupported(highPrecisionSupported)
+ {}
+
+ protected:
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+ {
+ for (TIntermNode *declarator : *node->getSequence())
+ {
+ if (!mInGlobalScope && !declarator->getAsBinaryNode())
+ {
+ TIntermSymbol *symbol = declarator->getAsSymbolNode();
+ ASSERT(symbol);
+ if (symbol->variable().symbolType() == SymbolType::Empty)
+ {
+ continue;
+ }
+
+ // Arrays may need to be initialized one element at a time, since ESSL 1.00 does not
+ // support array constructors or assigning arrays.
+ bool arrayConstructorUnavailable =
+ (symbol->isArray() || symbol->getType().isStructureContainingArrays()) &&
+ mShaderVersion == 100;
+ // Nameless struct constructors can't be referred to, so they also need to be
+ // initialized one element at a time.
+ // TODO(oetuaho): Check if it makes sense to initialize using a loop, even if we
+ // could use an initializer. It could at least reduce code size for very large
+ // arrays, but could hurt runtime performance.
+ if (arrayConstructorUnavailable || symbol->getType().isNamelessStruct())
+ {
+ // SimplifyLoopConditions should have been run so the parent node of this node
+ // should not be a loop.
+ ASSERT(getParentNode()->getAsLoopNode() == nullptr);
+ // SeparateDeclarations should have already been run, so we don't need to worry
+ // about further declarators in this declaration depending on the effects of
+ // this declarator.
+ ASSERT(node->getSequence()->size() == 1);
+ TIntermSequence initCode;
+ CreateInitCode(symbol, mCanUseLoopsToInitialize, mHighPrecisionSupported,
+ &initCode, mSymbolTable);
+ insertStatementsInParentBlock(TIntermSequence(), initCode);
+ }
+ else
+ {
+ TIntermBinary *init =
+ new TIntermBinary(EOpInitialize, symbol, CreateZeroNode(symbol->getType()));
+ queueReplacementWithParent(node, symbol, init, OriginalNode::BECOMES_CHILD);
+ }
+ }
+ }
+ return false;
+ }
+
+ private:
+ int mShaderVersion;
+ bool mCanUseLoopsToInitialize;
+ bool mHighPrecisionSupported;
+};
+
+} // namespace
+
+void CreateInitCode(const TIntermTyped *initializedSymbol,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initCode,
+ TSymbolTable *symbolTable)
+{
+ AddZeroInitSequence(initializedSymbol, canUseLoopsToInitialize, highPrecisionSupported,
+ initCode, symbolTable);
+}
+
+bool InitializeUninitializedLocals(TCompiler *compiler,
+ TIntermBlock *root,
+ int shaderVersion,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TSymbolTable *symbolTable)
+{
+ InitializeLocalsTraverser traverser(shaderVersion, symbolTable, canUseLoopsToInitialize,
+ highPrecisionSupported);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+bool InitializeVariables(TCompiler *compiler,
+ TIntermBlock *root,
+ const InitVariableList &vars,
+ TSymbolTable *symbolTable,
+ int shaderVersion,
+ const TExtensionBehavior &extensionBehavior,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported)
+{
+ TIntermBlock *body = FindMainBody(root);
+ InsertInitCode(compiler, body->getSequence(), vars, symbolTable, shaderVersion,
+ extensionBehavior, canUseLoopsToInitialize, highPrecisionSupported);
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h
new file mode 100644
index 0000000000..755b8d72eb
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/InitializeVariables.h
@@ -0,0 +1,60 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_INITIALIZEVARIABLES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_INITIALIZEVARIABLES_H_
+
+#include <GLSLANG/ShaderLang.h>
+
+#include "compiler/translator/ExtensionBehavior.h"
+#include "compiler/translator/IntermNode.h"
+
+namespace sh
+{
+class TCompiler;
+class TSymbolTable;
+
+typedef std::vector<sh::ShaderVariable> InitVariableList;
+
+// For all of the functions below: If canUseLoopsToInitialize is set, for loops are used instead of
+// a large number of initializers where it can make sense, such as for initializing large arrays.
+
+// Populate a sequence of assignment operations to initialize "initializedSymbol". initializedSymbol
+// may be an array, struct or any combination of these, as long as it contains only basic types.
+void CreateInitCode(const TIntermTyped *initializedSymbol,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TIntermSequence *initCode,
+ TSymbolTable *symbolTable);
+
+// Initialize all uninitialized local variables, so that undefined behavior is avoided.
+[[nodiscard]] bool InitializeUninitializedLocals(TCompiler *compiler,
+ TIntermBlock *root,
+ int shaderVersion,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported,
+ TSymbolTable *symbolTable);
+
+// This function can initialize all the types that CreateInitCode is able to initialize. All
+// variables must be globals which can be found in the symbol table. For now it is used for the
+// following two scenarios:
+// 1. Initializing gl_Position;
+// 2. Initializing output variables referred to in the shader source.
+// Note: The type of each lvalue in an initializer is retrieved from the symbol table. gl_FragData
+// requires special handling because the number of indices which can be initialized is determined by
+// enabled extensions.
+[[nodiscard]] bool InitializeVariables(TCompiler *compiler,
+ TIntermBlock *root,
+ const InitVariableList &vars,
+ TSymbolTable *symbolTable,
+ int shaderVersion,
+ const TExtensionBehavior &extensionBehavior,
+ bool canUseLoopsToInitialize,
+ bool highPrecisionSupported);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_INITIALIZEVARIABLES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp
new file mode 100644
index 0000000000..11c8b72002
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.cpp
@@ -0,0 +1,613 @@
+//
+// Copyright 2021 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// MonomorphizeUnsupportedFunctions: Monomorphize functions that are called with
+// parameters that are incompatible with both Vulkan GLSL and Metal.
+//
+
+#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h"
+
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+namespace
+{
+struct Argument
+{
+ size_t argumentIndex;
+ TIntermTyped *argument;
+};
+
+struct FunctionData
+{
+ // Whether the original function is used. If this is false, the function can be removed because
+ // all callers have been modified.
+ bool isOriginalUsed;
+ // The original definition of the function, used to create the monomorphized version.
+ TIntermFunctionDefinition *originalDefinition;
+ // List of monomorphized versions of this function. They will be added next to the original
+ // version (or replace it).
+ TVector<TIntermFunctionDefinition *> monomorphizedDefinitions;
+};
+
+using FunctionMap = angle::HashMap<const TFunction *, FunctionData>;
+
+// Traverse the function definitions and initialize the map. Allows visitAggregate to have access
+// to TIntermFunctionDefinition even when the function is only forward declared at that point.
+void InitializeFunctionMap(TIntermBlock *root, FunctionMap *functionMapOut)
+{
+ TIntermSequence &sequence = *root->getSequence();
+
+ for (TIntermNode *node : sequence)
+ {
+ TIntermFunctionDefinition *asFuncDef = node->getAsFunctionDefinition();
+ if (asFuncDef != nullptr)
+ {
+ const TFunction *function = asFuncDef->getFunction();
+ ASSERT(function && functionMapOut->find(function) == functionMapOut->end());
+ (*functionMapOut)[function] = FunctionData{false, asFuncDef, {}};
+ }
+ }
+}
+
+const TVariable *GetBaseUniform(TIntermTyped *node, bool *isSamplerInStructOut)
+{
+ *isSamplerInStructOut = false;
+
+ while (node->getAsBinaryNode())
+ {
+ TIntermBinary *asBinary = node->getAsBinaryNode();
+
+ TOperator op = asBinary->getOp();
+
+ // No opaque uniform can be inside an interface block.
+ if (op == EOpIndexDirectInterfaceBlock)
+ {
+ return nullptr;
+ }
+
+ if (op == EOpIndexDirectStruct)
+ {
+ *isSamplerInStructOut = true;
+ }
+
+ node = asBinary->getLeft();
+ }
+
+ // Only interested in uniform opaque types. If a function call within another function uses
+ // opaque uniforms in an unsupported way, it will be replaced in a follow up pass after the
+ // calling function is monomorphized.
+ if (node->getType().getQualifier() != EvqUniform)
+ {
+ return nullptr;
+ }
+
+ ASSERT(IsOpaqueType(node->getType().getBasicType()) ||
+ node->getType().isStructureContainingSamplers());
+
+ TIntermSymbol *asSymbol = node->getAsSymbolNode();
+ ASSERT(asSymbol);
+
+ return &asSymbol->variable();
+}
+
+TIntermTyped *ExtractSideEffects(TSymbolTable *symbolTable,
+ TIntermTyped *node,
+ TIntermSequence *replacementIndices)
+{
+ TIntermTyped *withoutSideEffects = node->deepCopy();
+
+ for (TIntermBinary *asBinary = withoutSideEffects->getAsBinaryNode(); asBinary;
+ asBinary = asBinary->getLeft()->getAsBinaryNode())
+ {
+ TOperator op = asBinary->getOp();
+ TIntermTyped *index = asBinary->getRight();
+
+ if (op == EOpIndexDirectStruct)
+ {
+ break;
+ }
+
+ // No side effects with constant expressions.
+ if (op == EOpIndexDirect)
+ {
+ ASSERT(index->getAsConstantUnion());
+ continue;
+ }
+
+ ASSERT(op == EOpIndexIndirect);
+
+ // If the index is a symbol, there's no side effect, so leave it as-is.
+ if (index->getAsSymbolNode())
+ {
+ continue;
+ }
+
+ // Otherwise create a temp variable initialized with the index and use that temp variable as
+ // the index.
+ TIntermDeclaration *tempDecl = nullptr;
+ TVariable *tempVar = DeclareTempVariable(symbolTable, index, EvqTemporary, &tempDecl);
+
+ replacementIndices->push_back(tempDecl);
+ asBinary->replaceChildNode(index, new TIntermSymbol(tempVar));
+ }
+
+ return withoutSideEffects;
+}
+
+void CreateMonomorphizedFunctionCallArgs(const TIntermSequence &originalCallArguments,
+ const TVector<Argument> &replacedArguments,
+ TIntermSequence *substituteArgsOut)
+{
+ size_t nextReplacedArg = 0;
+ for (size_t argIndex = 0; argIndex < originalCallArguments.size(); ++argIndex)
+ {
+ if (nextReplacedArg >= replacedArguments.size() ||
+ argIndex != replacedArguments[nextReplacedArg].argumentIndex)
+ {
+ // Not replaced, keep argument as is.
+ substituteArgsOut->push_back(originalCallArguments[argIndex]);
+ }
+ else
+ {
+ TIntermTyped *argument = replacedArguments[nextReplacedArg].argument;
+
+ // Iterate over indices of the argument and create a new arg for every non-const
+ // index. Note that the index itself may be an expression, and it may require further
+ // substitution in the next pass.
+ while (argument->getAsBinaryNode())
+ {
+ TIntermBinary *asBinary = argument->getAsBinaryNode();
+ if (asBinary->getOp() == EOpIndexIndirect)
+ {
+ TIntermTyped *index = asBinary->getRight();
+ substituteArgsOut->push_back(index->deepCopy());
+ }
+ argument = asBinary->getLeft();
+ }
+
+ ++nextReplacedArg;
+ }
+ }
+}
+
+const TFunction *MonomorphizeFunction(TSymbolTable *symbolTable,
+ const TFunction *original,
+ TVector<Argument> *replacedArguments,
+ VariableReplacementMap *argumentMapOut)
+{
+ TFunction *substituteFunction =
+ new TFunction(symbolTable, kEmptyImmutableString, SymbolType::AngleInternal,
+ &original->getReturnType(), original->isKnownToNotHaveSideEffects());
+
+ size_t nextReplacedArg = 0;
+ for (size_t paramIndex = 0; paramIndex < original->getParamCount(); ++paramIndex)
+ {
+ const TVariable *originalParam = original->getParam(paramIndex);
+
+ if (nextReplacedArg >= replacedArguments->size() ||
+ paramIndex != (*replacedArguments)[nextReplacedArg].argumentIndex)
+ {
+ TVariable *substituteArgument =
+ new TVariable(symbolTable, originalParam->name(), &originalParam->getType(),
+ originalParam->symbolType());
+ // Not replaced, add an identical parameter.
+ substituteFunction->addParameter(substituteArgument);
+ (*argumentMapOut)[originalParam] = new TIntermSymbol(substituteArgument);
+ }
+ else
+ {
+ TIntermTyped *substituteArgument = (*replacedArguments)[nextReplacedArg].argument;
+ (*argumentMapOut)[originalParam] = substituteArgument;
+
+ // Iterate over indices of the argument and create a new parameter for every non-const
+ // index (which may be an expression). Replace the symbol in the argument with a
+ // variable of the index type. This is later used to replace the parameter in the
+ // function body.
+ while (substituteArgument->getAsBinaryNode())
+ {
+ TIntermBinary *asBinary = substituteArgument->getAsBinaryNode();
+ if (asBinary->getOp() == EOpIndexIndirect)
+ {
+ TIntermTyped *index = asBinary->getRight();
+ TType *indexType = new TType(index->getType());
+ indexType->setQualifier(EvqParamIn);
+
+ TVariable *param = new TVariable(symbolTable, kEmptyImmutableString, indexType,
+ SymbolType::AngleInternal);
+ substituteFunction->addParameter(param);
+
+ // The argument now uses the function parameters as indices.
+ asBinary->replaceChildNode(asBinary->getRight(), new TIntermSymbol(param));
+ }
+ substituteArgument = asBinary->getLeft();
+ }
+
+ ++nextReplacedArg;
+ }
+ }
+
+ return substituteFunction;
+}
+
+class MonomorphizeTraverser final : public TIntermTraverser
+{
+ public:
+ explicit MonomorphizeTraverser(TCompiler *compiler,
+ TSymbolTable *symbolTable,
+ const ShCompileOptions &compileOptions,
+ UnsupportedFunctionArgsBitSet unsupportedFunctionArgs,
+ FunctionMap *functionMap)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mCompiler(compiler),
+ mCompileOptions(compileOptions),
+ mUnsupportedFunctionArgs(unsupportedFunctionArgs),
+ mFunctionMap(functionMap)
+ {}
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override
+ {
+ if (node->getOp() != EOpCallFunctionInAST)
+ {
+ return true;
+ }
+
+ const TFunction *function = node->getFunction();
+ ASSERT(function && mFunctionMap->find(function) != mFunctionMap->end());
+
+ FunctionData &data = (*mFunctionMap)[function];
+
+ TIntermFunctionDefinition *monomorphized =
+ processFunctionCall(node, data.originalDefinition, &data.isOriginalUsed);
+ if (monomorphized)
+ {
+ data.monomorphizedDefinitions.push_back(monomorphized);
+ }
+
+ return true;
+ }
+
+ bool getAnyMonomorphized() const { return mAnyMonomorphized; }
+
+ private:
+ bool isUnsupportedArgument(TIntermTyped *callArgument, const TVariable *funcArgument) const
+ {
+ // Only interested in opaque uniforms and structs that contain samplers.
+ const bool isOpaqueType = IsOpaqueType(funcArgument->getType().getBasicType());
+ const bool isStructContainingSamplers =
+ funcArgument->getType().isStructureContainingSamplers();
+ if (!isOpaqueType && !isStructContainingSamplers)
+ {
+ return false;
+ }
+
+ // If not uniform (the variable was itself a function parameter), don't process it in
+ // this pass, as we don't know which actual uniform it corresponds to.
+ bool isSamplerInStruct = false;
+ const TVariable *uniform = GetBaseUniform(callArgument, &isSamplerInStruct);
+ if (uniform == nullptr)
+ {
+ return false;
+ }
+
+ const TType &type = uniform->getType();
+
+ if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::StructContainingSamplers])
+ {
+ // Monomorphize if the parameter is a structure that contains samplers (so in
+ // RewriteStructSamplers we don't need to rewrite the functions to accept multiple
+ // parameters split from the struct).
+ if (isStructContainingSamplers)
+ {
+ return true;
+ }
+ }
+
+ if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::ArrayOfArrayOfSamplerOrImage])
+ {
+ // Monomorphize if:
+ //
+ // - The opaque uniform is a sampler in a struct (which can create an array-of-array
+ // situation), and the function expects an array of samplers, or
+ //
+ // - The opaque uniform is an array of array of sampler or image, and it's partially
+ // subscripted (i.e. the function itself expects an array)
+ //
+ const bool isParameterArrayOfOpaqueType = funcArgument->getType().isArray();
+ const bool isArrayOfArrayOfSamplerOrImage =
+ (type.isSampler() || type.isImage()) && type.isArrayOfArrays();
+ if (isSamplerInStruct && isParameterArrayOfOpaqueType)
+ {
+ return true;
+ }
+ if (isArrayOfArrayOfSamplerOrImage && isParameterArrayOfOpaqueType)
+ {
+ return true;
+ }
+ }
+
+ if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::AtomicCounter])
+ {
+ if (type.isAtomicCounter())
+ {
+ return true;
+ }
+ }
+
+ if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::SamplerCubeEmulation])
+ {
+ // Monomorphize if the opaque uniform is a samplerCube and ES2's cube sampling emulation
+ // is requested.
+ if (type.isSamplerCube() && mCompileOptions.emulateSeamfulCubeMapSampling)
+ {
+ return true;
+ }
+ }
+
+ if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::Image])
+ {
+ if (type.isImage())
+ {
+ return true;
+ }
+ }
+
+ if (mUnsupportedFunctionArgs[UnsupportedFunctionArgs::PixelLocalStorage])
+ {
+ if (type.isPixelLocal())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ TIntermFunctionDefinition *processFunctionCall(TIntermAggregate *functionCall,
+ TIntermFunctionDefinition *originalDefinition,
+ bool *isOriginalUsedOut)
+ {
+ const TFunction *function = functionCall->getFunction();
+ const TIntermSequence &callArguments = *functionCall->getSequence();
+
+ TVector<Argument> replacedArguments;
+ TIntermSequence replacementIndices;
+
+ // Go through function call arguments, and see if any is used in an unsupported way.
+ for (size_t argIndex = 0; argIndex < callArguments.size(); ++argIndex)
+ {
+ TIntermTyped *callArgument = callArguments[argIndex]->getAsTyped();
+ const TVariable *funcArgument = function->getParam(argIndex);
+ if (isUnsupportedArgument(callArgument, funcArgument))
+ {
+ // Copy the argument and extract the side effects.
+ TIntermTyped *argument =
+ ExtractSideEffects(mSymbolTable, callArgument, &replacementIndices);
+
+ replacedArguments.push_back({argIndex, argument});
+ }
+ }
+
+ if (replacedArguments.empty())
+ {
+ *isOriginalUsedOut = true;
+ return nullptr;
+ }
+
+ mAnyMonomorphized = true;
+
+ insertStatementsInParentBlock(replacementIndices);
+
+ // Create the arguments for the substitute function call. Done before monomorphizing the
+ // function, which transforms the arguments to what needs to be replaced in the function
+ // body.
+ TIntermSequence newCallArgs;
+ CreateMonomorphizedFunctionCallArgs(callArguments, replacedArguments, &newCallArgs);
+
+ // Duplicate the function and substitute the replaced arguments with only the non-const
+ // indices. Additionally, substitute the non-const indices of arguments with the new
+ // function parameters.
+ VariableReplacementMap argumentMap;
+ const TFunction *monomorphized =
+ MonomorphizeFunction(mSymbolTable, function, &replacedArguments, &argumentMap);
+
+ // Replace this function call with a call to the new one.
+ queueReplacement(TIntermAggregate::CreateFunctionCall(*monomorphized, &newCallArgs),
+ OriginalNode::IS_DROPPED);
+
+ // Create a new function definition, with the body of the old function but with the replaced
+ // parameters substituted with the calling expressions.
+ TIntermFunctionPrototype *substitutePrototype = new TIntermFunctionPrototype(monomorphized);
+ TIntermBlock *substituteBlock = originalDefinition->getBody()->deepCopy();
+ GetDeclaratorReplacements(mSymbolTable, substituteBlock, &argumentMap);
+ bool valid = ReplaceVariables(mCompiler, substituteBlock, argumentMap);
+ ASSERT(valid);
+
+ return new TIntermFunctionDefinition(substitutePrototype, substituteBlock);
+ }
+
+ TCompiler *mCompiler;
+ const ShCompileOptions &mCompileOptions;
+ UnsupportedFunctionArgsBitSet mUnsupportedFunctionArgs;
+ bool mAnyMonomorphized = false;
+
+ // Map of original to monomorphized functions.
+ FunctionMap *mFunctionMap;
+};
+
+class UpdateFunctionsDefinitionsTraverser final : public TIntermTraverser
+{
+ public:
+ explicit UpdateFunctionsDefinitionsTraverser(TSymbolTable *symbolTable,
+ const FunctionMap &functionMap)
+ : TIntermTraverser(true, false, false, symbolTable), mFunctionMap(functionMap)
+ {}
+
+ void visitFunctionPrototype(TIntermFunctionPrototype *node) override
+ {
+ const bool isInFunctionDefinition = getParentNode()->getAsFunctionDefinition() != nullptr;
+ if (isInFunctionDefinition)
+ {
+ return;
+ }
+
+ // Add to and possibly replace the function prototype with replacement prototypes.
+ const TFunction *function = node->getFunction();
+ ASSERT(function && mFunctionMap.find(function) != mFunctionMap.end());
+
+ const FunctionData &data = mFunctionMap.at(function);
+
+ // If nothing to do, leave it be.
+ if (data.monomorphizedDefinitions.empty())
+ {
+ ASSERT(data.isOriginalUsed);
+ return;
+ }
+
+ // Replace the prototype with itself (if function is still used) as well as any
+ // monomorphized versions.
+ TIntermSequence replacement;
+ if (data.isOriginalUsed)
+ {
+ replacement.push_back(node);
+ }
+ for (TIntermFunctionDefinition *monomorphizedDefinition : data.monomorphizedDefinitions)
+ {
+ replacement.push_back(new TIntermFunctionPrototype(
+ monomorphizedDefinition->getFunctionPrototype()->getFunction()));
+ }
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(replacement));
+ }
+
+ bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override
+ {
+ // Add to and possibly replace the function definition with replacement definitions.
+ const TFunction *function = node->getFunction();
+ ASSERT(function && mFunctionMap.find(function) != mFunctionMap.end());
+
+ const FunctionData &data = mFunctionMap.at(function);
+
+ // If nothing to do, leave it be.
+ if (data.monomorphizedDefinitions.empty())
+ {
+ ASSERT(data.isOriginalUsed || function->name() == "main");
+ return false;
+ }
+
+ // Replace the definition with itself (if function is still used) as well as any
+ // monomorphized versions.
+ TIntermSequence replacement;
+ if (data.isOriginalUsed)
+ {
+ replacement.push_back(node);
+ }
+ for (TIntermFunctionDefinition *monomorphizedDefinition : data.monomorphizedDefinitions)
+ {
+ replacement.push_back(monomorphizedDefinition);
+ }
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(replacement));
+
+ return false;
+ }
+
+ private:
+ const FunctionMap &mFunctionMap;
+};
+
+void SortDeclarations(TIntermBlock *root)
+{
+ TIntermSequence *original = root->getSequence();
+
+ TIntermSequence replacement;
+ TIntermSequence functionDefs;
+
+ // Accumulate non-function-definition declarations in |replacement| and function definitions in
+ // |functionDefs|.
+ for (TIntermNode *node : *original)
+ {
+ if (node->getAsFunctionDefinition() || node->getAsFunctionPrototypeNode())
+ {
+ functionDefs.push_back(node);
+ }
+ else
+ {
+ replacement.push_back(node);
+ }
+ }
+
+ // Append function definitions to |replacement|.
+ replacement.insert(replacement.end(), functionDefs.begin(), functionDefs.end());
+
+ // Replace root's sequence with |replacement|.
+ root->replaceAllChildren(replacement);
+}
+
+bool MonomorphizeUnsupportedFunctionsImpl(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const ShCompileOptions &compileOptions,
+ UnsupportedFunctionArgsBitSet unsupportedFunctionArgs)
+{
+ // First, sort out the declarations such that all non-function declarations are placed before
+ // function definitions. This way when the function is replaced with one that references said
+ // declarations (i.e. uniforms), the uniform declaration is already present above it.
+ SortDeclarations(root);
+
+ while (true)
+ {
+ FunctionMap functionMap;
+ InitializeFunctionMap(root, &functionMap);
+
+ MonomorphizeTraverser monomorphizer(compiler, symbolTable, compileOptions,
+ unsupportedFunctionArgs, &functionMap);
+ root->traverse(&monomorphizer);
+
+ if (!monomorphizer.getAnyMonomorphized())
+ {
+ break;
+ }
+
+ if (!monomorphizer.updateTree(compiler, root))
+ {
+ return false;
+ }
+
+ UpdateFunctionsDefinitionsTraverser functionUpdater(symbolTable, functionMap);
+ root->traverse(&functionUpdater);
+
+ if (!functionUpdater.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+
+ return true;
+}
+} // anonymous namespace
+
+bool MonomorphizeUnsupportedFunctions(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const ShCompileOptions &compileOptions,
+ UnsupportedFunctionArgsBitSet unsupportedFunctionArgs)
+{
+ // This function actually applies multiple transformation, and the AST may not be valid until
+ // the transformations are entirely done. Some validation is momentarily disabled.
+ bool enableValidateFunctionCall = compiler->disableValidateFunctionCall();
+
+ bool result = MonomorphizeUnsupportedFunctionsImpl(compiler, root, symbolTable, compileOptions,
+ unsupportedFunctionArgs);
+
+ compiler->restoreValidateFunctionCall(enableValidateFunctionCall);
+ return result && compiler->validateAST(root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h
new file mode 100644
index 0000000000..a9a212704b
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h
@@ -0,0 +1,54 @@
+//
+// Copyright 2021 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// MonomorphizeUnsupportedFunctions: Monomorphize functions that are called with
+// parameters that are incompatible with both Vulkan GLSL and Metal:
+//
+// - Samplers in structs
+// - Structs that have samplers
+// - Partially subscripted array of array of samplers
+// - Partially subscripted array of array of images
+// - Atomic counters
+// - samplerCube variables when emulating ES2's cube map sampling
+// - image* variables with r32f formats (to emulate imageAtomicExchange)
+//
+// This transformation basically duplicates such functions, removes the
+// sampler/image/atomic_counter parameters and uses the opaque uniforms used by the caller.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_VULKAN_MONOMORPHIZEUNSUPPORTEDFUNCTIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_VULKAN_MONOMORPHIZEUNSUPPORTEDFUNCTIONS_H_
+
+#include "common/angleutils.h"
+#include "compiler/translator/Compiler.h"
+
+namespace sh
+{
+class TIntermBlock;
+class TSymbolTable;
+
+// Types of function prameters that should trigger monomorphization.
+enum class UnsupportedFunctionArgs
+{
+ StructContainingSamplers = 0,
+ ArrayOfArrayOfSamplerOrImage = 1,
+ AtomicCounter = 2,
+ SamplerCubeEmulation = 3,
+ Image = 4,
+ PixelLocalStorage = 5,
+
+ InvalidEnum = 6,
+ EnumCount = 6,
+};
+
+using UnsupportedFunctionArgsBitSet = angle::PackedEnumBitSet<UnsupportedFunctionArgs>;
+
+[[nodiscard]] bool MonomorphizeUnsupportedFunctions(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const ShCompileOptions &compileOptions,
+ UnsupportedFunctionArgsBitSet);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_VULKAN_MONOMORPHIZEUNSUPPORTEDFUNCTIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp
new file mode 100644
index 0000000000..a23d0770ef
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.cpp
@@ -0,0 +1,127 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// NameNamelessUniformBuffers: Gives nameless uniform buffer variables internal names.
+//
+
+#include "compiler/translator/tree_ops/NameNamelessUniformBuffers.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+// Traverse uniform buffer declarations and give name to nameless declarations. Keeps track of
+// the interface fields which will be used in the source without the interface block variable name
+// and replaces them with name.field.
+class NameUniformBufferVariablesTraverser : public TIntermTraverser
+{
+ public:
+ explicit NameUniformBufferVariablesTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable)
+ {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
+ {
+ ASSERT(visit == PreVisit);
+
+ const TIntermSequence &sequence = *(decl->getSequence());
+
+ TIntermTyped *variableNode = sequence.front()->getAsTyped();
+ const TType &type = variableNode->getType();
+
+ // If it's an interface block, it may have to be converted if it contains any row-major
+ // fields.
+ if (!type.isInterfaceBlock())
+ {
+ return true;
+ }
+
+ // Multi declaration statements are already separated, so there can only be one variable
+ // here.
+ ASSERT(sequence.size() == 1);
+ const TVariable *variable = &variableNode->getAsSymbolNode()->variable();
+ if (variable->symbolType() != SymbolType::Empty)
+ {
+ return false;
+ }
+
+ TIntermDeclaration *newDeclaration = new TIntermDeclaration;
+ TVariable *newVariable = new TVariable(mSymbolTable, kEmptyImmutableString, &type,
+ SymbolType::AngleInternal, variable->extensions());
+ newDeclaration->appendDeclarator(new TIntermSymbol(newVariable));
+
+ queueReplacement(newDeclaration, OriginalNode::IS_DROPPED);
+
+ // It's safe to key the map with the interface block, as there couldn't have been multiple
+ // declarations with this interface block (as the variable is nameless), so for nameless
+ // uniform buffers, the interface block is unique.
+ mNamelessUniformBuffersMap[type.getInterfaceBlock()] = newVariable;
+
+ return false;
+ }
+
+ void visitSymbol(TIntermSymbol *symbol) override
+ {
+ const TType &type = symbol->getType();
+
+ // The symbols we are looking for have the interface block pointer set, but are not
+ // interface blocks. These are references to fields of nameless uniform buffers.
+ if (type.isInterfaceBlock() || type.getInterfaceBlock() == nullptr)
+ {
+ return;
+ }
+
+ const TInterfaceBlock *block = type.getInterfaceBlock();
+
+ // If block variable is not nameless, there's nothing to do.
+ if (mNamelessUniformBuffersMap.count(block) == 0)
+ {
+ return;
+ }
+
+ const ImmutableString symbolName = symbol->getName();
+
+ // Find which field it is
+ const TVector<TField *> fields = block->fields();
+ for (size_t fieldIndex = 0; fieldIndex < fields.size(); ++fieldIndex)
+ {
+ const TField *field = fields[fieldIndex];
+ if (field->name() != symbolName)
+ {
+ continue;
+ }
+
+ // Replace this node with a binary node that indexes the named uniform buffer.
+ TIntermSymbol *namedUniformBuffer =
+ new TIntermSymbol(mNamelessUniformBuffersMap[block]);
+ TIntermBinary *replacement =
+ new TIntermBinary(EOpIndexDirectInterfaceBlock, namedUniformBuffer,
+ CreateIndexNode(static_cast<uint32_t>(fieldIndex)));
+
+ queueReplacement(replacement, OriginalNode::IS_DROPPED);
+
+ return;
+ }
+
+ UNREACHABLE();
+ }
+
+ private:
+ // A map from nameless uniform buffers to their named replacements.
+ std::unordered_map<const TInterfaceBlock *, const TVariable *> mNamelessUniformBuffersMap;
+};
+} // anonymous namespace
+
+bool NameNamelessUniformBuffers(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ NameUniformBufferVariablesTraverser nameUniformBufferVariables(symbolTable);
+ root->traverse(&nameUniformBufferVariables);
+ return nameUniformBufferVariables.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h
new file mode 100644
index 0000000000..23964a5846
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/NameNamelessUniformBuffers.h
@@ -0,0 +1,32 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// NameNamelessUniformBuffers: Gives nameless uniform buffer variables internal names.
+//
+// For example:
+// uniform UniformBuffer { int a; };
+// x = a;
+// becomes:
+// uniform UniformBuffer { int a; } s123;
+// x = s123.a;
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_NAMENAMELESSUNIFORMBUFFERS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_NAMENAMELESSUNIFORMBUFFERS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool NameNamelessUniformBuffers(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_NAMENAMELESSUNIFORMBUFFERS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp
new file mode 100644
index 0000000000..276c8c98ed
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.cpp
@@ -0,0 +1,127 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PruneEmptyCases.cpp: The PruneEmptyCases function prunes cases that are followed by nothing from
+// the AST.
+
+#include "compiler/translator/tree_ops/PruneEmptyCases.h"
+
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+bool AreEmptyBlocks(const TIntermSequence *statements);
+
+bool IsEmptyBlock(TIntermNode *node)
+{
+ TIntermBlock *asBlock = node->getAsBlock();
+ if (asBlock)
+ {
+ return AreEmptyBlocks(asBlock->getSequence());
+ }
+ // Empty declarations should have already been pruned, otherwise they would need to be handled
+ // here. Note that declarations for struct types do contain a nameless child node.
+ ASSERT(node->getAsDeclarationNode() == nullptr ||
+ !node->getAsDeclarationNode()->getSequence()->empty());
+ // Pure literal statements should also already be pruned.
+ ASSERT(node->getAsConstantUnion() == nullptr);
+ return false;
+}
+
+// Return true if all statements in "statements" consist only of empty blocks and no-op statements.
+// Returns true also if there are no statements.
+bool AreEmptyBlocks(const TIntermSequence *statements)
+{
+ for (size_t i = 0u; i < statements->size(); ++i)
+ {
+ if (!IsEmptyBlock(statements->at(i)))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+class PruneEmptyCasesTraverser : private TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool apply(TCompiler *compiler, TIntermBlock *root);
+
+ private:
+ PruneEmptyCasesTraverser();
+ bool visitSwitch(Visit visit, TIntermSwitch *node) override;
+};
+
+bool PruneEmptyCasesTraverser::apply(TCompiler *compiler, TIntermBlock *root)
+{
+ PruneEmptyCasesTraverser prune;
+ root->traverse(&prune);
+ return prune.updateTree(compiler, root);
+}
+
+PruneEmptyCasesTraverser::PruneEmptyCasesTraverser() : TIntermTraverser(true, false, false) {}
+
+bool PruneEmptyCasesTraverser::visitSwitch(Visit visit, TIntermSwitch *node)
+{
+ // This may mutate the statementList, but that's okay, since traversal has not yet reached
+ // there.
+ TIntermBlock *statementList = node->getStatementList();
+ TIntermSequence *statements = statementList->getSequence();
+
+ // Iterate block children in reverse order. Cases that are only followed by other cases or empty
+ // blocks are marked for pruning.
+ size_t i = statements->size();
+ size_t lastNoOpInStatementList = i;
+ while (i > 0)
+ {
+ --i;
+ TIntermNode *statement = statements->at(i);
+ if (statement->getAsCaseNode() || IsEmptyBlock(statement))
+ {
+ lastNoOpInStatementList = i;
+ }
+ else
+ {
+ break;
+ }
+ }
+ if (lastNoOpInStatementList == 0)
+ {
+ // Remove the entire switch statement, extracting the init expression if needed.
+ TIntermTyped *init = node->getInit();
+ if (init->hasSideEffects())
+ {
+ queueReplacement(init, OriginalNode::IS_DROPPED);
+ }
+ else
+ {
+ TIntermSequence emptyReplacement;
+ ASSERT(getParentNode()->getAsBlock());
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(emptyReplacement));
+ }
+ return false;
+ }
+ if (lastNoOpInStatementList < statements->size())
+ {
+ statements->erase(statements->begin() + lastNoOpInStatementList, statements->end());
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool PruneEmptyCases(TCompiler *compiler, TIntermBlock *root)
+{
+ return PruneEmptyCasesTraverser::apply(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h
new file mode 100644
index 0000000000..5098fec9b7
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneEmptyCases.h
@@ -0,0 +1,22 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PruneEmptyCases.h: The PruneEmptyCases function prunes cases that are followed by nothing from
+// the AST.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_PRUNEEMPTYCASES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_PRUNEEMPTYCASES_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+
+[[nodiscard]] bool PruneEmptyCases(TCompiler *compiler, TIntermBlock *root);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_PRUNEEMPTYCASES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp
new file mode 100644
index 0000000000..fdc6070660
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.cpp
@@ -0,0 +1,215 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PruneNoOps.cpp: The PruneNoOps function prunes:
+// 1. Empty declarations "int;". Empty declarators will be pruned as well, so for example:
+// int , a;
+// is turned into
+// int a;
+// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision for float,
+// so float literal statements would end up with no precision which is invalid ESSL.
+// 3. Statements after discard, return, break and continue.
+
+#include "compiler/translator/tree_ops/PruneNoOps.h"
+
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+bool IsNoOp(TIntermNode *node)
+{
+ if (node->getAsConstantUnion() != nullptr)
+ {
+ return true;
+ }
+ bool isEmptyDeclaration = node->getAsDeclarationNode() != nullptr &&
+ node->getAsDeclarationNode()->getSequence()->empty();
+ if (isEmptyDeclaration)
+ {
+ return true;
+ }
+ return false;
+}
+
+class PruneNoOpsTraverser : private TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool apply(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+
+ private:
+ PruneNoOpsTraverser(TSymbolTable *symbolTable);
+ bool visitDeclaration(Visit, TIntermDeclaration *node) override;
+ bool visitBlock(Visit visit, TIntermBlock *node) override;
+ bool visitLoop(Visit visit, TIntermLoop *loop) override;
+ bool visitBranch(Visit visit, TIntermBranch *node) override;
+
+ bool mIsBranchVisited = false;
+};
+
+bool PruneNoOpsTraverser::apply(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ PruneNoOpsTraverser prune(symbolTable);
+ root->traverse(&prune);
+ return prune.updateTree(compiler, root);
+}
+
+PruneNoOpsTraverser::PruneNoOpsTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, true, true, symbolTable)
+{}
+
+bool PruneNoOpsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
+{
+ if (visit != PreVisit)
+ {
+ return true;
+ }
+
+ TIntermSequence *sequence = node->getSequence();
+ if (sequence->size() >= 1)
+ {
+ TIntermSymbol *declaratorSymbol = sequence->front()->getAsSymbolNode();
+ // Prune declarations without a variable name, unless it's an interface block declaration.
+ if (declaratorSymbol != nullptr &&
+ declaratorSymbol->variable().symbolType() == SymbolType::Empty &&
+ !declaratorSymbol->isInterfaceBlock())
+ {
+ if (sequence->size() > 1)
+ {
+ // Generate a replacement that will remove the empty declarator in the beginning of
+ // a declarator list. Example of a declaration that will be changed:
+ // float, a;
+ // will be changed to
+ // float a;
+ // This applies also to struct declarations.
+ TIntermSequence emptyReplacement;
+ mMultiReplacements.emplace_back(node, declaratorSymbol,
+ std::move(emptyReplacement));
+ }
+ else if (declaratorSymbol->getBasicType() != EbtStruct)
+ {
+ // If there are entirely empty non-struct declarations, they result in
+ // TIntermDeclaration nodes without any children in the parsing stage. These are
+ // handled in visitBlock and visitLoop.
+ UNREACHABLE();
+ }
+ else if (declaratorSymbol->getQualifier() != EvqGlobal &&
+ declaratorSymbol->getQualifier() != EvqTemporary)
+ {
+ // Single struct declarations may just declare the struct type and no variables, so
+ // they should not be pruned. Here we handle an empty struct declaration with a
+ // qualifier, for example like this:
+ // const struct a { int i; };
+ // NVIDIA GL driver version 367.27 doesn't accept this kind of declarations, so we
+ // convert the declaration to a regular struct declaration. This is okay, since ESSL
+ // 1.00 spec section 4.1.8 says about structs that "The optional qualifiers only
+ // apply to any declarators, and are not part of the type being defined for name."
+
+ // Create a new variable to use in the declarator so that the variable and node
+ // types are kept consistent.
+ TType *type = new TType(declaratorSymbol->getType());
+ if (mInGlobalScope)
+ {
+ type->setQualifier(EvqGlobal);
+ }
+ else
+ {
+ type->setQualifier(EvqTemporary);
+ }
+ TVariable *variable =
+ new TVariable(mSymbolTable, kEmptyImmutableString, type, SymbolType::Empty);
+ queueReplacementWithParent(node, declaratorSymbol, new TIntermSymbol(variable),
+ OriginalNode::IS_DROPPED);
+ }
+ }
+ }
+ return false;
+}
+
+bool PruneNoOpsTraverser::visitBlock(Visit visit, TIntermBlock *node)
+{
+ ASSERT(visit == PreVisit);
+
+ TIntermSequence &statements = *node->getSequence();
+
+ // Visit each statement in the block one by one. Once a branch is visited (break, continue,
+ // return or discard), drop the rest of the statements.
+ for (size_t statementIndex = 0; statementIndex < statements.size(); ++statementIndex)
+ {
+ TIntermNode *statement = statements[statementIndex];
+
+ // If the statement is a switch case label, stop pruning and continue visiting the children.
+ if (statement->getAsCaseNode() != nullptr)
+ {
+ mIsBranchVisited = false;
+ }
+
+ // If a branch is visited, prune the statement. If the statement is a no-op, also prune it.
+ if (mIsBranchVisited || IsNoOp(statement))
+ {
+ TIntermSequence emptyReplacement;
+ mMultiReplacements.emplace_back(node, statement, std::move(emptyReplacement));
+ continue;
+ }
+
+ // Visit the statement if not pruned.
+ statement->traverse(this);
+ }
+
+ // If the parent is a block and mIsBranchVisited is set, this is a nested block without any
+ // condition (like if, loop or switch), so the rest of the parent block should also be pruned.
+ // Otherwise the parent block should be unaffected.
+ if (mIsBranchVisited && getParentNode()->getAsBlock() == nullptr)
+ {
+ mIsBranchVisited = false;
+ }
+
+ return false;
+}
+
+bool PruneNoOpsTraverser::visitLoop(Visit visit, TIntermLoop *loop)
+{
+ if (visit != PreVisit)
+ {
+ return true;
+ }
+
+ TIntermTyped *expr = loop->getExpression();
+ if (expr != nullptr && IsNoOp(expr))
+ {
+ loop->setExpression(nullptr);
+ }
+ TIntermNode *init = loop->getInit();
+ if (init != nullptr && IsNoOp(init))
+ {
+ loop->setInit(nullptr);
+ }
+
+ return true;
+}
+
+bool PruneNoOpsTraverser::visitBranch(Visit visit, TIntermBranch *node)
+{
+ ASSERT(visit == PreVisit);
+
+ mIsBranchVisited = true;
+
+ // Only possible child is the value of a return statement, which has nothing to prune.
+ return false;
+}
+} // namespace
+
+bool PruneNoOps(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ return PruneNoOpsTraverser::apply(compiler, root, symbolTable);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h
new file mode 100644
index 0000000000..a73cb71d08
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/PruneNoOps.h
@@ -0,0 +1,29 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// PruneNoOps.h: The PruneNoOps function prunes:
+// 1. Empty declarations "int;". Empty declarators will be pruned as well, so for example:
+// int , a;
+// is turned into
+// int a;
+// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision for float,
+// so float literal statements would end up with no precision which is invalid ESSL.
+// 3. Statements after discard, return, break and continue.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_PRUNENOOPS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_PRUNENOOPS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool PruneNoOps(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_PRUNENOOPS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp
new file mode 100644
index 0000000000..ea15ab7844
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.cpp
@@ -0,0 +1,119 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// During parsing, all constant expressions are folded to constant union nodes. The expressions that
+// have been folded may have had precision qualifiers, which should affect the precision of the
+// consuming operation. If the folded constant union nodes are written to output as such they won't
+// have any precision qualifiers, and their effect on the precision of the consuming operation is
+// lost.
+//
+// RecordConstantPrecision is an AST traverser that inspects the precision qualifiers of constants
+// and hoists the constants outside the containing expression as precision qualified named variables
+// in case that is required for correct precision propagation.
+//
+
+#include "compiler/translator/tree_ops/RecordConstantPrecision.h"
+
+#include "compiler/translator/InfoSink.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class RecordConstantPrecisionTraverser : public TIntermTraverser
+{
+ public:
+ RecordConstantPrecisionTraverser(TSymbolTable *symbolTable);
+
+ void visitConstantUnion(TIntermConstantUnion *node) override;
+
+ protected:
+ bool operandAffectsParentOperationPrecision(TIntermTyped *operand);
+};
+
+RecordConstantPrecisionTraverser::RecordConstantPrecisionTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, true, symbolTable)
+{}
+
+bool RecordConstantPrecisionTraverser::operandAffectsParentOperationPrecision(TIntermTyped *operand)
+{
+ if (getParentNode()->getAsCaseNode() || getParentNode()->getAsBlock())
+ {
+ return false;
+ }
+
+ if (operand->getBasicType() == EbtBool || operand->getBasicType() == EbtStruct)
+ {
+ return false;
+ }
+
+ const TIntermBinary *parentAsBinary = getParentNode()->getAsBinaryNode();
+ if (parentAsBinary != nullptr)
+ {
+ // If the constant is assigned or is used to initialize a variable, or if it's an index,
+ // its precision has no effect.
+ switch (parentAsBinary->getOp())
+ {
+ case EOpInitialize:
+ case EOpAssign:
+ case EOpIndexDirect:
+ case EOpIndexDirectStruct:
+ case EOpIndexDirectInterfaceBlock:
+ case EOpIndexIndirect:
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ TIntermAggregate *parentAsAggregate = getParentNode()->getAsAggregate();
+ if (parentAsAggregate != nullptr)
+ {
+ // The precision of an aggregate is derived from children only in the following conditions:
+ //
+ // - Built-in math operations
+ // - Constructors
+ //
+ return parentAsAggregate->isConstructor() ||
+ BuiltInGroup::IsMath(parentAsAggregate->getOp());
+ }
+
+ return true;
+}
+
+void RecordConstantPrecisionTraverser::visitConstantUnion(TIntermConstantUnion *node)
+{
+ // If the constant has lowp or undefined precision, it can't increase the precision of consuming
+ // operations.
+ if (node->getPrecision() < EbpMedium)
+ return;
+
+ // It's possible the node has no effect on the precision of the consuming expression, depending
+ // on the consuming expression, and the precision of the other parameters of the expression.
+ if (!operandAffectsParentOperationPrecision(node))
+ return;
+
+ // Make the constant a precision-qualified named variable to make sure it affects the precision
+ // of the consuming expression.
+ TIntermDeclaration *variableDeclaration = nullptr;
+ TVariable *variable = DeclareTempVariable(mSymbolTable, node, EvqConst, &variableDeclaration);
+ insertStatementInParentBlock(variableDeclaration);
+ queueReplacement(CreateTempSymbolNode(variable), OriginalNode::IS_DROPPED);
+}
+
+} // namespace
+
+bool RecordConstantPrecision(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ RecordConstantPrecisionTraverser traverser(symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h
new file mode 100644
index 0000000000..f615b11221
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RecordConstantPrecision.h
@@ -0,0 +1,33 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// During parsing, all constant expressions are folded to constant union nodes. The expressions that
+// have been folded may have had precision qualifiers, which should affect the precision of the
+// consuming operation. If the folded constant union nodes are written to output as such they won't
+// have any precision qualifiers, and their effect on the precision of the consuming operation is
+// lost.
+//
+// RecordConstantPrecision is an AST traverser that inspects the precision qualifiers of constants
+// and hoists the constants outside the containing expression as precision qualified named variables
+// in case that is required for correct precision propagation.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_RECORDCONSTANTPRECISION_H_
+#define COMPILER_TRANSLATOR_TREEOPS_RECORDCONSTANTPRECISION_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool RecordConstantPrecision(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_RECORDCONSTANTPRECISION_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp
new file mode 100644
index 0000000000..85ecb9346b
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.cpp
@@ -0,0 +1,109 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveArrayLengthMethod.cpp:
+// Fold array length expressions, including cases where the "this" node has side effects.
+// Example:
+// int i = (a = b).length();
+// int j = (func()).length();
+// becomes:
+// (a = b);
+// int i = <constant array length>;
+// func();
+// int j = <constant array length>;
+//
+// Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps
+// have been done to expressions containing calls of the array length method.
+//
+// Does nothing to length method calls done on runtime-sized arrays.
+
+#include "compiler/translator/tree_ops/RemoveArrayLengthMethod.h"
+
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class RemoveArrayLengthTraverser : public TIntermTraverser
+{
+ public:
+ RemoveArrayLengthTraverser() : TIntermTraverser(true, false, false), mFoundArrayLength(false) {}
+
+ bool visitUnary(Visit visit, TIntermUnary *node) override;
+
+ void nextIteration() { mFoundArrayLength = false; }
+
+ bool foundArrayLength() const { return mFoundArrayLength; }
+
+ private:
+ void insertSideEffectsInParentBlock(TIntermTyped *node);
+
+ bool mFoundArrayLength;
+};
+
+bool RemoveArrayLengthTraverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+ // The only case where we leave array length() in place is for runtime-sized arrays.
+ if (node->getOp() == EOpArrayLength && !node->getOperand()->getType().isUnsizedArray())
+ {
+ mFoundArrayLength = true;
+ insertSideEffectsInParentBlock(node->getOperand());
+ TConstantUnion *constArray = new TConstantUnion[1];
+ constArray->setIConst(node->getOperand()->getOutermostArraySize());
+ queueReplacement(new TIntermConstantUnion(constArray, node->getType()),
+ OriginalNode::IS_DROPPED);
+ return false;
+ }
+ return true;
+}
+
+void RemoveArrayLengthTraverser::insertSideEffectsInParentBlock(TIntermTyped *node)
+{
+ // If the node is an index type, traverse it and add the indices as side effects. If at the end
+ // an expression without side effect is encountered, such as an opaque uniform or a lone symbol,
+ // don't generate a statement for it.
+ if (!node->hasSideEffects())
+ {
+ return;
+ }
+
+ TIntermBinary *asBinary = node->getAsBinaryNode();
+ if (asBinary && !asBinary->isAssignment())
+ {
+ insertSideEffectsInParentBlock(asBinary->getLeft());
+ insertSideEffectsInParentBlock(asBinary->getRight());
+ }
+ else
+ {
+ insertStatementInParentBlock(node);
+ }
+}
+
+} // anonymous namespace
+
+bool RemoveArrayLengthMethod(TCompiler *compiler, TIntermBlock *root)
+{
+ RemoveArrayLengthTraverser traverser;
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.foundArrayLength())
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.foundArrayLength());
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h
new file mode 100644
index 0000000000..8468e1e635
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveArrayLengthMethod.h
@@ -0,0 +1,37 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveArrayLengthMethod.h:
+// Fold array length expressions, including cases where the "this" node has side effects.
+// Example:
+// int i = (a = b).length();
+// int j = (func()).length();
+// becomes:
+// (a = b);
+// int i = <constant array length>;
+// func();
+// int j = <constant array length>;
+//
+// Must be run after SplitSequenceOperator, SimplifyLoopConditions and SeparateDeclarations steps
+// have been done to expressions containing calls of the array length method.
+//
+// Does nothing to length method calls done on runtime-sized arrays.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEARRAYLENGTHMETHOD_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REMOVEARRAYLENGTHMETHOD_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+
+[[nodiscard]] bool RemoveArrayLengthMethod(TCompiler *compiler, TIntermBlock *root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEARRAYLENGTHMETHOD_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp
new file mode 100644
index 0000000000..ed0ba3fed8
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.cpp
@@ -0,0 +1,74 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveAtomicCounterBuiltins: Remove atomic counter builtins.
+//
+
+#include "compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+
+bool IsAtomicCounterDecl(const TIntermDeclaration *node)
+{
+ const TIntermSequence &sequence = *(node->getSequence());
+ TIntermTyped *variable = sequence.front()->getAsTyped();
+ const TType &type = variable->getType();
+ return type.getQualifier() == EvqUniform && type.isAtomicCounter();
+}
+
+// Traverser that removes all GLSL built-ins that use AtomicCounters
+// Only called when the builtins are in use, but no atomic counters have been declared
+class RemoveAtomicCounterBuiltinsTraverser : public TIntermTraverser
+{
+ public:
+ RemoveAtomicCounterBuiltinsTraverser() : TIntermTraverser(true, false, false) {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+ {
+ ASSERT(visit == PreVisit);
+
+ // Active atomic counters should have been removed by RewriteAtomicCounters, and this
+ // traversal should not have been invoked
+ ASSERT(!IsAtomicCounterDecl(node));
+ return false;
+ }
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override
+ {
+ if (node->getOp() == EOpMemoryBarrierAtomicCounter)
+ {
+ // Vulkan does not support atomic counters, so if this builtin finds its way here,
+ // we need to remove it.
+ TIntermSequence emptySequence;
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(emptySequence));
+ return true;
+ }
+
+ // We shouldn't see any other builtins because they cannot be present without an active
+ // atomic counter, and should have been removed by RewriteAtomicCounters. If this fires,
+ // this traversal should not have been called.
+ ASSERT(!(BuiltInGroup::IsBuiltIn(node->getOp()) &&
+ node->getFunction()->isAtomicCounterFunction()));
+
+ return false;
+ }
+};
+
+} // anonymous namespace
+
+bool RemoveAtomicCounterBuiltins(TCompiler *compiler, TIntermBlock *root)
+{
+ RemoveAtomicCounterBuiltinsTraverser traverser;
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h
new file mode 100644
index 0000000000..efe8a53595
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveAtomicCounterBuiltins.h
@@ -0,0 +1,24 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveAtomicCounterBuiltins: Remove atomic counter builtins.
+// Normally handled by RewriteAtomicCounters, but that is only invoked when
+// atomic counters are actually in use. This pass removes the builtins and
+// asserts no atomic counters are declared.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEATOMICCOUNTERBUILTINS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REMOVEATOMICCOUNTERBUILTINS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+
+[[nodiscard]] bool RemoveAtomicCounterBuiltins(TCompiler *compiler, TIntermBlock *root);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEATOMICCOUNTERBUILTINS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp
new file mode 100644
index 0000000000..fda6de6f48
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.cpp
@@ -0,0 +1,597 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of non-SSBO vectors and
+// matrices, replacing them with calls to functions that choose which component to return or write.
+// We don't need to consider dynamic indexing in SSBO since it can be directly as part of the offset
+// of RWByteAddressBuffer.
+//
+
+#include "compiler/translator/tree_ops/RemoveDynamicIndexing.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/Diagnostics.h"
+#include "compiler/translator/InfoSink.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+using DynamicIndexingNodeMatcher = std::function<bool(TIntermBinary *)>;
+
+const TType *kIndexType = StaticType::Get<EbtInt, EbpHigh, EvqParamIn, 1, 1>();
+
+constexpr const ImmutableString kBaseName("base");
+constexpr const ImmutableString kIndexName("index");
+constexpr const ImmutableString kValueName("value");
+
+std::string GetIndexFunctionName(const TType &type, bool write)
+{
+ TInfoSinkBase nameSink;
+ nameSink << "dyn_index_";
+ if (write)
+ {
+ nameSink << "write_";
+ }
+ if (type.isMatrix())
+ {
+ nameSink << "mat" << static_cast<uint32_t>(type.getCols()) << "x"
+ << static_cast<uint32_t>(type.getRows());
+ }
+ else
+ {
+ switch (type.getBasicType())
+ {
+ case EbtInt:
+ nameSink << "ivec";
+ break;
+ case EbtBool:
+ nameSink << "bvec";
+ break;
+ case EbtUInt:
+ nameSink << "uvec";
+ break;
+ case EbtFloat:
+ nameSink << "vec";
+ break;
+ default:
+ UNREACHABLE();
+ }
+ nameSink << static_cast<uint32_t>(type.getNominalSize());
+ }
+ return nameSink.str();
+}
+
+TIntermConstantUnion *CreateIntConstantNode(int i)
+{
+ TConstantUnion *constant = new TConstantUnion();
+ constant->setIConst(i);
+ return new TIntermConstantUnion(constant, TType(EbtInt, EbpHigh));
+}
+
+TIntermTyped *EnsureSignedInt(TIntermTyped *node)
+{
+ if (node->getBasicType() == EbtInt)
+ return node;
+
+ TIntermSequence arguments;
+ arguments.push_back(node);
+ return TIntermAggregate::CreateConstructor(TType(EbtInt), &arguments);
+}
+
+TType *GetFieldType(const TType &indexedType)
+{
+ TType *fieldType = new TType(indexedType);
+ if (indexedType.isMatrix())
+ {
+ fieldType->toMatrixColumnType();
+ }
+ else
+ {
+ ASSERT(indexedType.isVector());
+ fieldType->toComponentType();
+ }
+ // Default precision to highp if not specified. For example in |vec3(0)[i], i < 0|, there is no
+ // precision assigned to vec3(0).
+ if (fieldType->getPrecision() == EbpUndefined)
+ {
+ fieldType->setPrecision(EbpHigh);
+ }
+ return fieldType;
+}
+
+const TType *GetBaseType(const TType &type, bool write)
+{
+ TType *baseType = new TType(type);
+ // Conservatively use highp here, even if the indexed type is not highp. That way the code can't
+ // end up using mediump version of an indexing function for a highp value, if both mediump and
+ // highp values are being indexed in the shader. For HLSL precision doesn't matter, but in
+ // principle this code could be used with multiple backends.
+ baseType->setPrecision(EbpHigh);
+ baseType->setQualifier(EvqParamInOut);
+ if (!write)
+ baseType->setQualifier(EvqParamIn);
+ return baseType;
+}
+
+// Generate a read or write function for one field in a vector/matrix.
+// Out-of-range indices are clamped. This is consistent with how ANGLE handles out-of-range
+// indices in other places.
+// Note that indices can be either int or uint. We create only int versions of the functions,
+// and convert uint indices to int at the call site.
+// read function example:
+// float dyn_index_vec2(in vec2 base, in int index)
+// {
+// switch(index)
+// {
+// case (0):
+// return base[0];
+// case (1):
+// return base[1];
+// default:
+// break;
+// }
+// if (index < 0)
+// return base[0];
+// return base[1];
+// }
+// write function example:
+// void dyn_index_write_vec2(inout vec2 base, in int index, in float value)
+// {
+// switch(index)
+// {
+// case (0):
+// base[0] = value;
+// return;
+// case (1):
+// base[1] = value;
+// return;
+// default:
+// break;
+// }
+// if (index < 0)
+// {
+// base[0] = value;
+// return;
+// }
+// base[1] = value;
+// }
+// Note that else is not used in above functions to avoid the RewriteElseBlocks transformation.
+TIntermFunctionDefinition *GetIndexFunctionDefinition(const TType &type,
+ bool write,
+ const TFunction &func,
+ TSymbolTable *symbolTable)
+{
+ ASSERT(!type.isArray());
+
+ uint8_t numCases = 0;
+ if (type.isMatrix())
+ {
+ numCases = type.getCols();
+ }
+ else
+ {
+ numCases = type.getNominalSize();
+ }
+
+ std::string functionName = GetIndexFunctionName(type, write);
+ TIntermFunctionPrototype *prototypeNode = CreateInternalFunctionPrototypeNode(func);
+
+ TIntermSymbol *baseParam = new TIntermSymbol(func.getParam(0));
+ TIntermSymbol *indexParam = new TIntermSymbol(func.getParam(1));
+ TIntermSymbol *valueParam = nullptr;
+ if (write)
+ {
+ valueParam = new TIntermSymbol(func.getParam(2));
+ }
+
+ TIntermBlock *statementList = new TIntermBlock();
+ for (uint8_t i = 0; i < numCases; ++i)
+ {
+ TIntermCase *caseNode = new TIntermCase(CreateIntConstantNode(i));
+ statementList->getSequence()->push_back(caseNode);
+
+ TIntermBinary *indexNode =
+ new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(i));
+ if (write)
+ {
+ TIntermBinary *assignNode =
+ new TIntermBinary(EOpAssign, indexNode, valueParam->deepCopy());
+ statementList->getSequence()->push_back(assignNode);
+ TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr);
+ statementList->getSequence()->push_back(returnNode);
+ }
+ else
+ {
+ TIntermBranch *returnNode = new TIntermBranch(EOpReturn, indexNode);
+ statementList->getSequence()->push_back(returnNode);
+ }
+ }
+
+ // Default case
+ TIntermCase *defaultNode = new TIntermCase(nullptr);
+ statementList->getSequence()->push_back(defaultNode);
+ TIntermBranch *breakNode = new TIntermBranch(EOpBreak, nullptr);
+ statementList->getSequence()->push_back(breakNode);
+
+ TIntermSwitch *switchNode = new TIntermSwitch(indexParam->deepCopy(), statementList);
+
+ TIntermBlock *bodyNode = new TIntermBlock();
+ bodyNode->getSequence()->push_back(switchNode);
+
+ TIntermBinary *cond =
+ new TIntermBinary(EOpLessThan, indexParam->deepCopy(), CreateIntConstantNode(0));
+
+ // Two blocks: one accesses (either reads or writes) the first element and returns,
+ // the other accesses the last element.
+ TIntermBlock *useFirstBlock = new TIntermBlock();
+ TIntermBlock *useLastBlock = new TIntermBlock();
+ TIntermBinary *indexFirstNode =
+ new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(0));
+ TIntermBinary *indexLastNode =
+ new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(numCases - 1));
+ if (write)
+ {
+ TIntermBinary *assignFirstNode =
+ new TIntermBinary(EOpAssign, indexFirstNode, valueParam->deepCopy());
+ useFirstBlock->getSequence()->push_back(assignFirstNode);
+ TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr);
+ useFirstBlock->getSequence()->push_back(returnNode);
+
+ TIntermBinary *assignLastNode =
+ new TIntermBinary(EOpAssign, indexLastNode, valueParam->deepCopy());
+ useLastBlock->getSequence()->push_back(assignLastNode);
+ }
+ else
+ {
+ TIntermBranch *returnFirstNode = new TIntermBranch(EOpReturn, indexFirstNode);
+ useFirstBlock->getSequence()->push_back(returnFirstNode);
+
+ TIntermBranch *returnLastNode = new TIntermBranch(EOpReturn, indexLastNode);
+ useLastBlock->getSequence()->push_back(returnLastNode);
+ }
+ TIntermIfElse *ifNode = new TIntermIfElse(cond, useFirstBlock, nullptr);
+ bodyNode->getSequence()->push_back(ifNode);
+ bodyNode->getSequence()->push_back(useLastBlock);
+
+ TIntermFunctionDefinition *indexingFunction =
+ new TIntermFunctionDefinition(prototypeNode, bodyNode);
+ return indexingFunction;
+}
+
+class RemoveDynamicIndexingTraverser : public TLValueTrackingTraverser
+{
+ public:
+ RemoveDynamicIndexingTraverser(DynamicIndexingNodeMatcher &&matcher,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics);
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+
+ void insertHelperDefinitions(TIntermNode *root);
+
+ void nextIteration();
+
+ bool usedTreeInsertion() const { return mUsedTreeInsertion; }
+
+ protected:
+ // Maps of types that are indexed to the indexing function ids used for them. Note that these
+ // can not store multiple variants of the same type with different precisions - only one
+ // precision gets stored.
+ std::map<TType, TFunction *> mIndexedVecAndMatrixTypes;
+ std::map<TType, TFunction *> mWrittenVecAndMatrixTypes;
+
+ bool mUsedTreeInsertion;
+
+ // When true, the traverser will remove side effects from any indexing expression.
+ // This is done so that in code like
+ // V[j++][i]++.
+ // where V is an array of vectors, j++ will only be evaluated once.
+ bool mRemoveIndexSideEffectsInSubtree;
+
+ DynamicIndexingNodeMatcher mMatcher;
+ PerformanceDiagnostics *mPerfDiagnostics;
+};
+
+RemoveDynamicIndexingTraverser::RemoveDynamicIndexingTraverser(
+ DynamicIndexingNodeMatcher &&matcher,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics)
+ : TLValueTrackingTraverser(true, false, false, symbolTable),
+ mUsedTreeInsertion(false),
+ mRemoveIndexSideEffectsInSubtree(false),
+ mMatcher(matcher),
+ mPerfDiagnostics(perfDiagnostics)
+{}
+
+void RemoveDynamicIndexingTraverser::insertHelperDefinitions(TIntermNode *root)
+{
+ TIntermBlock *rootBlock = root->getAsBlock();
+ ASSERT(rootBlock != nullptr);
+ TIntermSequence insertions;
+ for (auto &type : mIndexedVecAndMatrixTypes)
+ {
+ insertions.push_back(
+ GetIndexFunctionDefinition(type.first, false, *type.second, mSymbolTable));
+ }
+ for (auto &type : mWrittenVecAndMatrixTypes)
+ {
+ insertions.push_back(
+ GetIndexFunctionDefinition(type.first, true, *type.second, mSymbolTable));
+ }
+ rootBlock->insertChildNodes(0, insertions);
+}
+
+// Create a call to dyn_index_*() based on an indirect indexing op node
+TIntermAggregate *CreateIndexFunctionCall(TIntermBinary *node,
+ TIntermTyped *index,
+ TFunction *indexingFunction)
+{
+ ASSERT(node->getOp() == EOpIndexIndirect);
+ TIntermSequence arguments;
+ arguments.push_back(node->getLeft());
+ arguments.push_back(index);
+
+ TIntermAggregate *indexingCall =
+ TIntermAggregate::CreateFunctionCall(*indexingFunction, &arguments);
+ indexingCall->setLine(node->getLine());
+ return indexingCall;
+}
+
+TIntermAggregate *CreateIndexedWriteFunctionCall(TIntermBinary *node,
+ TVariable *index,
+ TVariable *writtenValue,
+ TFunction *indexedWriteFunction)
+{
+ ASSERT(node->getOp() == EOpIndexIndirect);
+ TIntermSequence arguments;
+ // Deep copy the child nodes so that two pointers to the same node don't end up in the tree.
+ arguments.push_back(node->getLeft()->deepCopy());
+ arguments.push_back(CreateTempSymbolNode(index));
+ arguments.push_back(CreateTempSymbolNode(writtenValue));
+
+ TIntermAggregate *indexedWriteCall =
+ TIntermAggregate::CreateFunctionCall(*indexedWriteFunction, &arguments);
+ indexedWriteCall->setLine(node->getLine());
+ return indexedWriteCall;
+}
+
+bool RemoveDynamicIndexingTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (mUsedTreeInsertion)
+ return false;
+
+ if (node->getOp() == EOpIndexIndirect)
+ {
+ if (mRemoveIndexSideEffectsInSubtree)
+ {
+ ASSERT(node->getRight()->hasSideEffects());
+ // In case we're just removing index side effects, convert
+ // v_expr[index_expr]
+ // to this:
+ // int s0 = index_expr; v_expr[s0];
+ // Now v_expr[s0] can be safely executed several times without unintended side effects.
+ TIntermDeclaration *indexVariableDeclaration = nullptr;
+ TVariable *indexVariable = DeclareTempVariable(mSymbolTable, node->getRight(),
+ EvqTemporary, &indexVariableDeclaration);
+ insertStatementInParentBlock(indexVariableDeclaration);
+ mUsedTreeInsertion = true;
+
+ // Replace the index with the temp variable
+ TIntermSymbol *tempIndex = CreateTempSymbolNode(indexVariable);
+ queueReplacementWithParent(node, node->getRight(), tempIndex, OriginalNode::IS_DROPPED);
+ }
+ else if (mMatcher(node))
+ {
+ if (mPerfDiagnostics)
+ {
+ mPerfDiagnostics->warning(node->getLine(),
+ "Performance: dynamic indexing of vectors and "
+ "matrices is emulated and can be slow.",
+ "[]");
+ }
+ bool write = isLValueRequiredHere();
+
+#if defined(ANGLE_ENABLE_ASSERTS)
+ // Make sure that IntermNodePatternMatcher is consistent with the slightly differently
+ // implemented checks in this traverser.
+ IntermNodePatternMatcher matcher(
+ IntermNodePatternMatcher::kDynamicIndexingOfVectorOrMatrixInLValue);
+ ASSERT(matcher.match(node, getParentNode(), isLValueRequiredHere()) == write);
+#endif
+
+ const TType &type = node->getLeft()->getType();
+ ImmutableString indexingFunctionName(GetIndexFunctionName(type, false));
+ TFunction *indexingFunction = nullptr;
+ if (mIndexedVecAndMatrixTypes.find(type) == mIndexedVecAndMatrixTypes.end())
+ {
+ indexingFunction =
+ new TFunction(mSymbolTable, indexingFunctionName, SymbolType::AngleInternal,
+ GetFieldType(type), true);
+ indexingFunction->addParameter(new TVariable(
+ mSymbolTable, kBaseName, GetBaseType(type, false), SymbolType::AngleInternal));
+ indexingFunction->addParameter(
+ new TVariable(mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal));
+ mIndexedVecAndMatrixTypes[type] = indexingFunction;
+ }
+ else
+ {
+ indexingFunction = mIndexedVecAndMatrixTypes[type];
+ }
+
+ if (write)
+ {
+ // Convert:
+ // v_expr[index_expr]++;
+ // to this:
+ // int s0 = index_expr; float s1 = dyn_index(v_expr, s0); s1++;
+ // dyn_index_write(v_expr, s0, s1);
+ // This works even if index_expr has some side effects.
+ if (node->getLeft()->hasSideEffects())
+ {
+ // If v_expr has side effects, those need to be removed before proceeding.
+ // Otherwise the side effects of v_expr would be evaluated twice.
+ // The only case where an l-value can have side effects is when it is
+ // indexing. For example, it can be V[j++] where V is an array of vectors.
+ mRemoveIndexSideEffectsInSubtree = true;
+ return true;
+ }
+
+ TIntermBinary *leftBinary = node->getLeft()->getAsBinaryNode();
+ if (leftBinary != nullptr && mMatcher(leftBinary))
+ {
+ // This is a case like:
+ // mat2 m;
+ // m[a][b]++;
+ // Process the child node m[a] first.
+ return true;
+ }
+
+ // TODO(oetuaho@nvidia.com): This is not optimal if the expression using the value
+ // only writes it and doesn't need the previous value. http://anglebug.com/1116
+
+ TFunction *indexedWriteFunction = nullptr;
+ if (mWrittenVecAndMatrixTypes.find(type) == mWrittenVecAndMatrixTypes.end())
+ {
+ ImmutableString functionName(
+ GetIndexFunctionName(node->getLeft()->getType(), true));
+ indexedWriteFunction =
+ new TFunction(mSymbolTable, functionName, SymbolType::AngleInternal,
+ StaticType::GetBasic<EbtVoid, EbpUndefined>(), false);
+ indexedWriteFunction->addParameter(new TVariable(mSymbolTable, kBaseName,
+ GetBaseType(type, true),
+ SymbolType::AngleInternal));
+ indexedWriteFunction->addParameter(new TVariable(
+ mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal));
+ TType *valueType = GetFieldType(type);
+ valueType->setQualifier(EvqParamIn);
+ indexedWriteFunction->addParameter(new TVariable(
+ mSymbolTable, kValueName, static_cast<const TType *>(valueType),
+ SymbolType::AngleInternal));
+ mWrittenVecAndMatrixTypes[type] = indexedWriteFunction;
+ }
+ else
+ {
+ indexedWriteFunction = mWrittenVecAndMatrixTypes[type];
+ }
+
+ TIntermSequence insertionsBefore;
+ TIntermSequence insertionsAfter;
+
+ // Store the index in a temporary signed int variable.
+ // s0 = index_expr;
+ TIntermTyped *indexInitializer = EnsureSignedInt(node->getRight());
+ TIntermDeclaration *indexVariableDeclaration = nullptr;
+ TVariable *indexVariable = DeclareTempVariable(
+ mSymbolTable, indexInitializer, EvqTemporary, &indexVariableDeclaration);
+ insertionsBefore.push_back(indexVariableDeclaration);
+
+ // s1 = dyn_index(v_expr, s0);
+ TIntermAggregate *indexingCall = CreateIndexFunctionCall(
+ node, CreateTempSymbolNode(indexVariable), indexingFunction);
+ TIntermDeclaration *fieldVariableDeclaration = nullptr;
+ TVariable *fieldVariable = DeclareTempVariable(
+ mSymbolTable, indexingCall, EvqTemporary, &fieldVariableDeclaration);
+ insertionsBefore.push_back(fieldVariableDeclaration);
+
+ // dyn_index_write(v_expr, s0, s1);
+ TIntermAggregate *indexedWriteCall = CreateIndexedWriteFunctionCall(
+ node, indexVariable, fieldVariable, indexedWriteFunction);
+ insertionsAfter.push_back(indexedWriteCall);
+ insertStatementsInParentBlock(insertionsBefore, insertionsAfter);
+
+ // replace the node with s1
+ queueReplacement(CreateTempSymbolNode(fieldVariable), OriginalNode::IS_DROPPED);
+ mUsedTreeInsertion = true;
+ }
+ else
+ {
+ // The indexed value is not being written, so we can simply convert
+ // v_expr[index_expr]
+ // into
+ // dyn_index(v_expr, index_expr)
+ // If the index_expr is unsigned, we'll convert it to signed.
+ ASSERT(!mRemoveIndexSideEffectsInSubtree);
+ TIntermAggregate *indexingCall = CreateIndexFunctionCall(
+ node, EnsureSignedInt(node->getRight()), indexingFunction);
+ queueReplacement(indexingCall, OriginalNode::IS_DROPPED);
+ }
+ }
+ }
+ return !mUsedTreeInsertion;
+}
+
+void RemoveDynamicIndexingTraverser::nextIteration()
+{
+ mUsedTreeInsertion = false;
+ mRemoveIndexSideEffectsInSubtree = false;
+}
+
+bool RemoveDynamicIndexingIf(DynamicIndexingNodeMatcher &&matcher,
+ TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics)
+{
+ // This transformation adds function declarations after the fact and so some validation is
+ // momentarily disabled.
+ bool enableValidateFunctionCall = compiler->disableValidateFunctionCall();
+
+ RemoveDynamicIndexingTraverser traverser(std::move(matcher), symbolTable, perfDiagnostics);
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ } while (traverser.usedTreeInsertion());
+ // TODO(oetuaho@nvidia.com): It might be nicer to add the helper definitions also in the middle
+ // of traversal. Now the tree ends up in an inconsistent state in the middle, since there are
+ // function call nodes with no corresponding definition nodes. This needs special handling in
+ // TIntermLValueTrackingTraverser, and creates intricacies that are not easily apparent from a
+ // superficial reading of the code.
+ traverser.insertHelperDefinitions(root);
+
+ compiler->restoreValidateFunctionCall(enableValidateFunctionCall);
+ return compiler->validateAST(root);
+}
+
+} // namespace
+
+[[nodiscard]] bool RemoveDynamicIndexingOfNonSSBOVectorOrMatrix(
+ TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics)
+{
+ DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) {
+ return IntermNodePatternMatcher::IsDynamicIndexingOfNonSSBOVectorOrMatrix(node);
+ };
+ return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable,
+ perfDiagnostics);
+}
+
+[[nodiscard]] bool RemoveDynamicIndexingOfSwizzledVector(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics)
+{
+ DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) {
+ return IntermNodePatternMatcher::IsDynamicIndexingOfSwizzledVector(node);
+ };
+ return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable,
+ perfDiagnostics);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h
new file mode 100644
index 0000000000..9693f55c40
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveDynamicIndexing.h
@@ -0,0 +1,41 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of non-SSBO vectors and
+// matrices, replacing them with calls to functions that choose which component to return or write.
+// We don't need to consider dynamic indexing in SSBO since it can be directly as part of the offset
+// of RWByteAddressBuffer.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEDYNAMICINDEXING_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REMOVEDYNAMICINDEXING_H_
+
+#include "common/angleutils.h"
+
+#include <functional>
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TIntermBinary;
+class TSymbolTable;
+class PerformanceDiagnostics;
+
+[[nodiscard]] bool RemoveDynamicIndexingOfNonSSBOVectorOrMatrix(
+ TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics);
+
+[[nodiscard]] bool RemoveDynamicIndexingOfSwizzledVector(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ PerformanceDiagnostics *perfDiagnostics);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEDYNAMICINDEXING_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp
new file mode 100644
index 0000000000..a5e20ebcb0
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.cpp
@@ -0,0 +1,209 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveInactiveInterfaceVariables.h:
+// Drop shader interface variable declarations for those that are inactive.
+//
+
+#include "compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// Traverser that removes all declarations that correspond to inactive variables.
+class RemoveInactiveInterfaceVariablesTraverser : public TIntermTraverser
+{
+ public:
+ RemoveInactiveInterfaceVariablesTraverser(
+ TSymbolTable *symbolTable,
+ const std::vector<sh::ShaderVariable> &attributes,
+ const std::vector<sh::ShaderVariable> &inputVaryings,
+ const std::vector<sh::ShaderVariable> &outputVariables,
+ const std::vector<sh::ShaderVariable> &uniforms,
+ const std::vector<sh::InterfaceBlock> &interfaceBlocks,
+ bool removeFragmentOutputs);
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+
+ private:
+ const std::vector<sh::ShaderVariable> &mAttributes;
+ const std::vector<sh::ShaderVariable> &mInputVaryings;
+ const std::vector<sh::ShaderVariable> &mOutputVariables;
+ const std::vector<sh::ShaderVariable> &mUniforms;
+ const std::vector<sh::InterfaceBlock> &mInterfaceBlocks;
+ bool mRemoveFragmentOutputs;
+};
+
+RemoveInactiveInterfaceVariablesTraverser::RemoveInactiveInterfaceVariablesTraverser(
+ TSymbolTable *symbolTable,
+ const std::vector<sh::ShaderVariable> &attributes,
+ const std::vector<sh::ShaderVariable> &inputVaryings,
+ const std::vector<sh::ShaderVariable> &outputVariables,
+ const std::vector<sh::ShaderVariable> &uniforms,
+ const std::vector<sh::InterfaceBlock> &interfaceBlocks,
+ bool removeFragmentOutputs)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mAttributes(attributes),
+ mInputVaryings(inputVaryings),
+ mOutputVariables(outputVariables),
+ mUniforms(uniforms),
+ mInterfaceBlocks(interfaceBlocks),
+ mRemoveFragmentOutputs(removeFragmentOutputs)
+{}
+
+template <typename Variable>
+bool IsVariableActive(const std::vector<Variable> &mVars, const ImmutableString &name)
+{
+ for (const Variable &var : mVars)
+ {
+ if (name == var.name)
+ {
+ return var.active;
+ }
+ }
+ UNREACHABLE();
+ return true;
+}
+
+bool RemoveInactiveInterfaceVariablesTraverser::visitDeclaration(Visit visit,
+ TIntermDeclaration *node)
+{
+ // SeparateDeclarations should have already been run.
+ ASSERT(node->getSequence()->size() == 1u);
+
+ TIntermTyped *declarator = node->getSequence()->front()->getAsTyped();
+ ASSERT(declarator);
+
+ TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
+ if (!asSymbol)
+ {
+ return false;
+ }
+
+ const TType &type = declarator->getType();
+
+ // Remove all shader interface variables except outputs, i.e. uniforms, interface blocks and
+ // inputs.
+ //
+ // Imagine a situation where the VS doesn't write to a varying but the FS reads from it. This
+ // is allowed, though the value of the varying is undefined. If the varying is removed here,
+ // the situation is changed to VS not declaring the varying, but the FS reading from it, which
+ // is not allowed. That's why inactive shader outputs are not removed.
+ //
+ // Inactive fragment shader outputs can be removed though, as there is no next stage.
+ bool removeDeclaration = false;
+ const TQualifier qualifier = type.getQualifier();
+
+ if (type.isInterfaceBlock())
+ {
+ // When a member has an explicit location, interface block should not be removed.
+ // If the member or interface would be removed, GetProgramResource could not return the
+ // location.
+ if (!IsShaderIoBlock(type.getQualifier()) && type.getQualifier() != EvqPatchIn &&
+ type.getQualifier() != EvqPatchOut)
+ {
+ removeDeclaration =
+ !IsVariableActive(mInterfaceBlocks, type.getInterfaceBlock()->name());
+ }
+ }
+ else if (qualifier == EvqUniform)
+ {
+ removeDeclaration = !IsVariableActive(mUniforms, asSymbol->getName());
+ }
+ else if (qualifier == EvqAttribute || qualifier == EvqVertexIn)
+ {
+ removeDeclaration = !IsVariableActive(mAttributes, asSymbol->getName());
+ }
+ else if (IsShaderIn(qualifier))
+ {
+ removeDeclaration = !IsVariableActive(mInputVaryings, asSymbol->getName());
+ }
+ else if (qualifier == EvqFragmentOut)
+ {
+ removeDeclaration =
+ !IsVariableActive(mOutputVariables, asSymbol->getName()) && mRemoveFragmentOutputs;
+ }
+
+ if (removeDeclaration)
+ {
+ TIntermSequence replacement;
+
+ // If the declaration was of a struct, keep the struct declaration itself.
+ if (type.isStructSpecifier())
+ {
+ TType *structSpecifierType = new TType(type.getStruct(), true);
+ TVariable *emptyVariable = new TVariable(mSymbolTable, kEmptyImmutableString,
+ structSpecifierType, SymbolType::Empty);
+ TIntermDeclaration *declaration = new TIntermDeclaration();
+ declaration->appendDeclarator(new TIntermSymbol(emptyVariable));
+ replacement.push_back(declaration);
+ }
+
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(replacement));
+ }
+
+ return false;
+}
+
+bool RemoveInactiveInterfaceVariablesTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ // Remove any code that initOutputVariables might have added corresponding to inactive
+ // output variables. This code is always in the form of `variable = ...;`.
+ if (node->getOp() != EOpAssign)
+ {
+ // Don't recurse, won't find the initialization nested in another expression.
+ return false;
+ }
+
+ // Get the symbol being initialized, and check if it's an inactive output. If it is, this must
+ // necessarily be initialization code that ANGLE has added (and wasn't there in the original
+ // shader; if it was, the symbol wouldn't have been inactive).
+ TIntermSymbol *symbol = node->getLeft()->getAsSymbolNode();
+ if (symbol == nullptr)
+ {
+ return false;
+ }
+
+ const TQualifier qualifier = symbol->getType().getQualifier();
+ if (qualifier != EvqFragmentOut || IsVariableActive(mOutputVariables, symbol->getName()))
+ {
+ return false;
+ }
+
+ // Drop the initialization code.
+ TIntermSequence replacement;
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node, std::move(replacement));
+ return false;
+}
+
+} // namespace
+
+bool RemoveInactiveInterfaceVariables(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const std::vector<sh::ShaderVariable> &attributes,
+ const std::vector<sh::ShaderVariable> &inputVaryings,
+ const std::vector<sh::ShaderVariable> &outputVariables,
+ const std::vector<sh::ShaderVariable> &uniforms,
+ const std::vector<sh::InterfaceBlock> &interfaceBlocks,
+ bool removeFragmentOutputs)
+{
+ RemoveInactiveInterfaceVariablesTraverser traverser(symbolTable, attributes, inputVaryings,
+ outputVariables, uniforms, interfaceBlocks,
+ removeFragmentOutputs);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h
new file mode 100644
index 0000000000..ddbdea2ced
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInactiveInterfaceVariables.h
@@ -0,0 +1,42 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveInactiveInterfaceVariables.h:
+// Drop shader interface variable declarations for those that are inactive. This step needs to be
+// done after CollectVariables. This avoids having to emulate them (e.g. atomic counters for
+// Vulkan) or remove them in glslang wrapper (again, for Vulkan).
+//
+// Shouldn't be run for the GL backend, as it queries shader interface variables from GL itself,
+// instead of relying on what was gathered during CollectVariables.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEINACTIVEVARIABLES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REMOVEINACTIVEVARIABLES_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+struct InterfaceBlock;
+struct ShaderVariable;
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool RemoveInactiveInterfaceVariables(
+ TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const std::vector<sh::ShaderVariable> &attributes,
+ const std::vector<sh::ShaderVariable> &inputVaryings,
+ const std::vector<sh::ShaderVariable> &outputVariables,
+ const std::vector<sh::ShaderVariable> &uniforms,
+ const std::vector<sh::InterfaceBlock> &interfaceBlocks,
+ bool removeFragmentOutputs);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEINACTIVEVARIABLES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp
new file mode 100644
index 0000000000..5a8ca44150
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.cpp
@@ -0,0 +1,47 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/RemoveInvariantDeclaration.h"
+
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// An AST traverser that removes invariant declaration for input in fragment shader
+// when GLSL >= 4.20 and for output in vertex shader when GLSL < 4.2.
+class RemoveInvariantDeclarationTraverser : public TIntermTraverser
+{
+ public:
+ RemoveInvariantDeclarationTraverser() : TIntermTraverser(true, false, false) {}
+
+ private:
+ bool visitGlobalQualifierDeclaration(Visit visit,
+ TIntermGlobalQualifierDeclaration *node) override
+ {
+ if (node->isInvariant())
+ {
+ TIntermSequence emptyReplacement;
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(emptyReplacement));
+ }
+ return false;
+ }
+};
+
+} // anonymous namespace
+
+bool RemoveInvariantDeclaration(TCompiler *compiler, TIntermNode *root)
+{
+ RemoveInvariantDeclarationTraverser traverser;
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h
new file mode 100644
index 0000000000..0fee87b3b1
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveInvariantDeclaration.h
@@ -0,0 +1,21 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEINVARIANTDECLARATION_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REMOVEINVARIANTDECLARATION_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+
+[[nodiscard]] bool RemoveInvariantDeclaration(TCompiler *compiler, TIntermNode *root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEINVARIANTDECLARATION_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp
new file mode 100644
index 0000000000..b87ad33633
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.cpp
@@ -0,0 +1,371 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveUnreferencedVariables.cpp:
+// Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary
+// initialization code for them. Also removes unreferenced struct types.
+//
+
+#include "compiler/translator/tree_ops/RemoveUnreferencedVariables.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class CollectVariableRefCountsTraverser : public TIntermTraverser
+{
+ public:
+ CollectVariableRefCountsTraverser();
+
+ using RefCountMap = angle::HashMap<int, unsigned int>;
+ RefCountMap &getSymbolIdRefCounts() { return mSymbolIdRefCounts; }
+ RefCountMap &getStructIdRefCounts() { return mStructIdRefCounts; }
+
+ void visitSymbol(TIntermSymbol *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ void visitFunctionPrototype(TIntermFunctionPrototype *node) override;
+
+ private:
+ void incrementStructTypeRefCount(const TType &type);
+
+ RefCountMap mSymbolIdRefCounts;
+
+ // Structure reference counts are counted from symbols, constructors, function calls, function
+ // return values and from interface block and structure fields. We need to track both function
+ // calls and function return values since there's a compiler option not to prune unused
+ // functions. The type of a constant union may also be a struct, but statements that are just a
+ // constant union are always pruned, and if the constant union is used somehow it will get
+ // counted by something else.
+ RefCountMap mStructIdRefCounts;
+};
+
+CollectVariableRefCountsTraverser::CollectVariableRefCountsTraverser()
+ : TIntermTraverser(true, false, false)
+{}
+
+void CollectVariableRefCountsTraverser::incrementStructTypeRefCount(const TType &type)
+{
+ if (type.isInterfaceBlock())
+ {
+ const auto *block = type.getInterfaceBlock();
+ ASSERT(block);
+
+ // We can end up incrementing ref counts of struct types referenced from an interface block
+ // multiple times for the same block. This doesn't matter, because interface blocks can't be
+ // pruned so we'll never do the reverse operation.
+ for (const auto &field : block->fields())
+ {
+ ASSERT(!field->type()->isInterfaceBlock());
+ incrementStructTypeRefCount(*field->type());
+ }
+ return;
+ }
+
+ const auto *structure = type.getStruct();
+ if (structure != nullptr)
+ {
+ auto structIter = mStructIdRefCounts.find(structure->uniqueId().get());
+ if (structIter == mStructIdRefCounts.end())
+ {
+ mStructIdRefCounts[structure->uniqueId().get()] = 1u;
+
+ for (const auto &field : structure->fields())
+ {
+ incrementStructTypeRefCount(*field->type());
+ }
+
+ return;
+ }
+ ++(structIter->second);
+ }
+}
+
+void CollectVariableRefCountsTraverser::visitSymbol(TIntermSymbol *node)
+{
+ incrementStructTypeRefCount(node->getType());
+
+ auto iter = mSymbolIdRefCounts.find(node->uniqueId().get());
+ if (iter == mSymbolIdRefCounts.end())
+ {
+ mSymbolIdRefCounts[node->uniqueId().get()] = 1u;
+ return;
+ }
+ ++(iter->second);
+}
+
+bool CollectVariableRefCountsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ // This tracks struct references in both function calls and constructors.
+ incrementStructTypeRefCount(node->getType());
+ return true;
+}
+
+void CollectVariableRefCountsTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node)
+{
+ incrementStructTypeRefCount(node->getType());
+ size_t paramCount = node->getFunction()->getParamCount();
+ for (size_t i = 0; i < paramCount; ++i)
+ {
+ incrementStructTypeRefCount(node->getFunction()->getParam(i)->getType());
+ }
+}
+
+// Traverser that removes all unreferenced variables on one traversal.
+class RemoveUnreferencedVariablesTraverser : public TIntermTraverser
+{
+ public:
+ RemoveUnreferencedVariablesTraverser(
+ CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts,
+ CollectVariableRefCountsTraverser::RefCountMap *structIdRefCounts,
+ TSymbolTable *symbolTable);
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
+ void visitSymbol(TIntermSymbol *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+
+ // Traverse loop and block nodes in reverse order. Note that this traverser does not track
+ // parent block positions, so insertStatementInParentBlock is unusable!
+ void traverseBlock(TIntermBlock *block) override;
+ void traverseLoop(TIntermLoop *loop) override;
+
+ private:
+ void removeVariableDeclaration(TIntermDeclaration *node, TIntermTyped *declarator);
+ void decrementStructTypeRefCount(const TType &type);
+
+ CollectVariableRefCountsTraverser::RefCountMap *mSymbolIdRefCounts;
+ CollectVariableRefCountsTraverser::RefCountMap *mStructIdRefCounts;
+ bool mRemoveReferences;
+};
+
+RemoveUnreferencedVariablesTraverser::RemoveUnreferencedVariablesTraverser(
+ CollectVariableRefCountsTraverser::RefCountMap *symbolIdRefCounts,
+ CollectVariableRefCountsTraverser::RefCountMap *structIdRefCounts,
+ TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, true, symbolTable),
+ mSymbolIdRefCounts(symbolIdRefCounts),
+ mStructIdRefCounts(structIdRefCounts),
+ mRemoveReferences(false)
+{}
+
+void RemoveUnreferencedVariablesTraverser::decrementStructTypeRefCount(const TType &type)
+{
+ auto *structure = type.getStruct();
+ if (structure != nullptr)
+ {
+ ASSERT(mStructIdRefCounts->find(structure->uniqueId().get()) != mStructIdRefCounts->end());
+ unsigned int structRefCount = --(*mStructIdRefCounts)[structure->uniqueId().get()];
+
+ if (structRefCount == 0)
+ {
+ for (const auto &field : structure->fields())
+ {
+ decrementStructTypeRefCount(*field->type());
+ }
+ }
+ }
+}
+
+void RemoveUnreferencedVariablesTraverser::removeVariableDeclaration(TIntermDeclaration *node,
+ TIntermTyped *declarator)
+{
+ if (declarator->getType().isStructSpecifier() && !declarator->getType().isNamelessStruct())
+ {
+ unsigned int structId = declarator->getType().getStruct()->uniqueId().get();
+ unsigned int structRefCountInThisDeclarator = 1u;
+ if (declarator->getAsBinaryNode() &&
+ declarator->getAsBinaryNode()->getRight()->getAsAggregate())
+ {
+ ASSERT(declarator->getAsBinaryNode()->getLeft()->getType().getStruct() ==
+ declarator->getType().getStruct());
+ ASSERT(declarator->getAsBinaryNode()->getRight()->getType().getStruct() ==
+ declarator->getType().getStruct());
+ structRefCountInThisDeclarator = 2u;
+ }
+ if ((*mStructIdRefCounts)[structId] > structRefCountInThisDeclarator)
+ {
+ // If this declaration declares a named struct type that is used elsewhere, we need to
+ // keep it. We can still change the declarator though so that it doesn't declare an
+ // unreferenced variable.
+
+ // Note that since we're not removing the entire declaration, the struct's reference
+ // count will end up being one less than the correct refcount. But since the struct
+ // declaration is kept, the incorrect refcount can't cause any other problems.
+
+ if (declarator->getAsSymbolNode() &&
+ declarator->getAsSymbolNode()->variable().symbolType() == SymbolType::Empty)
+ {
+ // Already an empty declaration - nothing to do.
+ return;
+ }
+ TVariable *emptyVariable =
+ new TVariable(mSymbolTable, kEmptyImmutableString, new TType(declarator->getType()),
+ SymbolType::Empty);
+ queueReplacementWithParent(node, declarator, new TIntermSymbol(emptyVariable),
+ OriginalNode::IS_DROPPED);
+ return;
+ }
+ }
+
+ if (getParentNode()->getAsBlock())
+ {
+ TIntermSequence emptyReplacement;
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(emptyReplacement));
+ }
+ else
+ {
+ ASSERT(getParentNode()->getAsLoopNode());
+ queueReplacement(nullptr, OriginalNode::IS_DROPPED);
+ }
+}
+
+bool RemoveUnreferencedVariablesTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
+{
+ if (visit == PreVisit)
+ {
+ // SeparateDeclarations should have already been run.
+ ASSERT(node->getSequence()->size() == 1u);
+
+ TIntermTyped *declarator = node->getSequence()->back()->getAsTyped();
+ ASSERT(declarator);
+
+ // We can only remove variables that are not a part of the shader interface.
+ TQualifier qualifier = declarator->getQualifier();
+ if (qualifier != EvqTemporary && qualifier != EvqGlobal && qualifier != EvqConst)
+ {
+ return true;
+ }
+
+ bool canRemoveVariable = false;
+ TIntermSymbol *symbolNode = declarator->getAsSymbolNode();
+ if (symbolNode != nullptr)
+ {
+ canRemoveVariable = (*mSymbolIdRefCounts)[symbolNode->uniqueId().get()] == 1u ||
+ symbolNode->variable().symbolType() == SymbolType::Empty;
+ }
+ TIntermBinary *initNode = declarator->getAsBinaryNode();
+ if (initNode != nullptr)
+ {
+ ASSERT(initNode->getLeft()->getAsSymbolNode());
+ int symbolId = initNode->getLeft()->getAsSymbolNode()->uniqueId().get();
+ canRemoveVariable =
+ (*mSymbolIdRefCounts)[symbolId] == 1u && !initNode->getRight()->hasSideEffects();
+ }
+
+ if (canRemoveVariable)
+ {
+ removeVariableDeclaration(node, declarator);
+ mRemoveReferences = true;
+ }
+ return true;
+ }
+ ASSERT(visit == PostVisit);
+ mRemoveReferences = false;
+ return true;
+}
+
+void RemoveUnreferencedVariablesTraverser::visitSymbol(TIntermSymbol *node)
+{
+ if (mRemoveReferences)
+ {
+ ASSERT(mSymbolIdRefCounts->find(node->uniqueId().get()) != mSymbolIdRefCounts->end());
+ --(*mSymbolIdRefCounts)[node->uniqueId().get()];
+
+ decrementStructTypeRefCount(node->getType());
+ }
+}
+
+bool RemoveUnreferencedVariablesTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ if (visit == PreVisit && mRemoveReferences)
+ {
+ decrementStructTypeRefCount(node->getType());
+ }
+ return true;
+}
+
+void RemoveUnreferencedVariablesTraverser::traverseBlock(TIntermBlock *node)
+{
+ // We traverse blocks in reverse order. This way reference counts can be decremented when
+ // removing initializers, and variables that become unused when initializers are removed can be
+ // removed on the same traversal.
+
+ ScopedNodeInTraversalPath addToPath(this, node);
+
+ bool visit = true;
+
+ TIntermSequence *sequence = node->getSequence();
+
+ if (preVisit)
+ visit = visitBlock(PreVisit, node);
+
+ if (visit)
+ {
+ for (auto iter = sequence->rbegin(); iter != sequence->rend(); ++iter)
+ {
+ (*iter)->traverse(this);
+ if (visit && inVisit)
+ {
+ if ((iter + 1) != sequence->rend())
+ visit = visitBlock(InVisit, node);
+ }
+ }
+ }
+
+ if (visit && postVisit)
+ visitBlock(PostVisit, node);
+}
+
+void RemoveUnreferencedVariablesTraverser::traverseLoop(TIntermLoop *node)
+{
+ // We traverse loops in reverse order as well. The loop body gets traversed before the init
+ // node.
+
+ ScopedNodeInTraversalPath addToPath(this, node);
+
+ bool visit = true;
+
+ if (preVisit)
+ visit = visitLoop(PreVisit, node);
+
+ if (visit)
+ {
+ // We don't need to traverse loop expressions or conditions since they can't be declarations
+ // in the AST (loops which have a declaration in their condition get transformed in the
+ // parsing stage).
+ ASSERT(node->getExpression() == nullptr ||
+ node->getExpression()->getAsDeclarationNode() == nullptr);
+ ASSERT(node->getCondition() == nullptr ||
+ node->getCondition()->getAsDeclarationNode() == nullptr);
+
+ if (node->getBody())
+ node->getBody()->traverse(this);
+
+ if (node->getInit())
+ node->getInit()->traverse(this);
+ }
+
+ if (visit && postVisit)
+ visitLoop(PostVisit, node);
+}
+
+} // namespace
+
+bool RemoveUnreferencedVariables(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ CollectVariableRefCountsTraverser collector;
+ root->traverse(&collector);
+ RemoveUnreferencedVariablesTraverser traverser(&collector.getSymbolIdRefCounts(),
+ &collector.getStructIdRefCounts(), symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h
new file mode 100644
index 0000000000..071d19e51e
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RemoveUnreferencedVariables.h
@@ -0,0 +1,29 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveUnreferencedVariables.h:
+// Drop variables that are declared but never referenced in the AST. This avoids adding unnecessary
+// initialization code for them. Also removes unreferenced struct types.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REMOVEUNREFERENCEDVARIABLES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REMOVEUNREFERENCEDVARIABLES_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool RemoveUnreferencedVariables(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REMOVEUNREFERENCEDVARIABLES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp
new file mode 100644
index 0000000000..bd12cd1006
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.cpp
@@ -0,0 +1,348 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteAtomicCounters: Emulate atomic counter buffers with storage buffers.
+//
+
+#include "compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+namespace
+{
+struct UniformData
+{
+ // Corresponding to an array of array of opaque uniform variable, this is the flattened variable
+ // that is replacing it.
+ const TVariable *flattened;
+ // Assume a general case of array declaration with N dimensions:
+ //
+ // uniform type u[Dn]..[D2][D1];
+ //
+ // Let's define
+ //
+ // Pn = D(n-1)*...*D2*D1
+ //
+ // In that case, we have:
+ //
+ // u[In] = ac + In*Pn
+ // u[In][I(n-1)] = ac + In*Pn + I(n-1)*P(n-1)
+ // u[In]...[Ii] = ac + In*Pn + ... + Ii*Pi
+ //
+ // This array contains Pi. Note that the like TType::mArraySizes, the last element is the
+ // outermost dimension. Element 0 is necessarily 1.
+ TVector<unsigned int> mSubArraySizes;
+};
+
+using UniformMap = angle::HashMap<const TVariable *, UniformData>;
+
+TIntermTyped *RewriteArrayOfArraySubscriptExpression(TCompiler *compiler,
+ TIntermBinary *node,
+ const UniformMap &uniformMap);
+
+// Given an expression, this traverser calculates a new expression where array of array of opaque
+// uniforms are replaced with their flattened ones. In particular, this is run on the right node of
+// EOpIndexIndirect binary nodes, so that the expression in the index gets a chance to go through
+// this transformation.
+class RewriteExpressionTraverser final : public TIntermTraverser
+{
+ public:
+ explicit RewriteExpressionTraverser(TCompiler *compiler, const UniformMap &uniformMap)
+ : TIntermTraverser(true, false, false), mCompiler(compiler), mUniformMap(uniformMap)
+ {}
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ TIntermTyped *rewritten =
+ RewriteArrayOfArraySubscriptExpression(mCompiler, node, mUniformMap);
+ if (rewritten == nullptr)
+ {
+ return true;
+ }
+
+ queueReplacement(rewritten, OriginalNode::IS_DROPPED);
+
+ // Don't iterate as the expression is rewritten.
+ return false;
+ }
+
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ // We cannot reach here for an opaque uniform that is being replaced. visitBinary should
+ // have taken care of it.
+ ASSERT(!IsOpaqueType(node->getType().getBasicType()) ||
+ mUniformMap.find(&node->variable()) == mUniformMap.end());
+ }
+
+ private:
+ TCompiler *mCompiler;
+
+ const UniformMap &mUniformMap;
+};
+
+// Rewrite the index of an EOpIndexIndirect expression. The root can never need replacing, because
+// it cannot be an opaque uniform itself.
+void RewriteIndexExpression(TCompiler *compiler,
+ TIntermTyped *expression,
+ const UniformMap &uniformMap)
+{
+ RewriteExpressionTraverser traverser(compiler, uniformMap);
+ expression->traverse(&traverser);
+ bool valid = traverser.updateTree(compiler, expression);
+ ASSERT(valid);
+}
+
+// Given an expression such as the following:
+//
+// EOpIndex(In)Direct (opaque uniform)
+// / \
+// EOpIndex(In)Direct I1
+// / \
+// ... I2
+// /
+// EOpIndex(In)Direct
+// / \
+// uniform In
+//
+// produces:
+//
+// EOpIndex(In)Direct
+// / \
+// uniform In*Pn + ... + I2*P2 + I1*P1
+//
+TIntermTyped *RewriteArrayOfArraySubscriptExpression(TCompiler *compiler,
+ TIntermBinary *node,
+ const UniformMap &uniformMap)
+{
+ // Only interested in opaque uniforms.
+ if (!IsOpaqueType(node->getType().getBasicType()))
+ {
+ return nullptr;
+ }
+
+ TIntermSymbol *opaqueUniform = nullptr;
+
+ // Iterate once and find the opaque uniform that's being indexed.
+ TIntermBinary *iter = node;
+ while (opaqueUniform == nullptr)
+ {
+ ASSERT(iter->getOp() == EOpIndexDirect || iter->getOp() == EOpIndexIndirect);
+
+ opaqueUniform = iter->getLeft()->getAsSymbolNode();
+ iter = iter->getLeft()->getAsBinaryNode();
+ }
+
+ // If not being replaced, there's nothing to do.
+ auto flattenedIter = uniformMap.find(&opaqueUniform->variable());
+ if (flattenedIter == uniformMap.end())
+ {
+ return nullptr;
+ }
+
+ const UniformData &data = flattenedIter->second;
+
+ // Iterate again and build the index expression. The index expression constitutes the sum of
+ // the variable indices plus a constant offset calculated from the constant indices. For
+ // example, smplr[1][x][2][y] will have an index of x*P3 + y*P1 + c, where c = (1*P4 + 2*P2).
+ unsigned int constantOffset = 0;
+ TIntermTyped *variableIndex = nullptr;
+
+ // Since the opaque uniforms are fully subscripted, we know exactly how many EOpIndex* nodes
+ // there should be.
+ for (size_t dimIndex = 0; dimIndex < data.mSubArraySizes.size(); ++dimIndex)
+ {
+ ASSERT(node);
+
+ unsigned int subArraySize = data.mSubArraySizes[dimIndex];
+
+ switch (node->getOp())
+ {
+ case EOpIndexDirect:
+ // Accumulate the constant index.
+ constantOffset +=
+ node->getRight()->getAsConstantUnion()->getIConst(0) * subArraySize;
+ break;
+ case EOpIndexIndirect:
+ {
+ // Run RewriteExpressionTraverser on the right node. It may itself be an expression
+ // with an array of array of opaque uniform inside that needs to be rewritten.
+ TIntermTyped *indexExpression = node->getRight();
+ RewriteIndexExpression(compiler, indexExpression, uniformMap);
+
+ // Scale and accumulate.
+ if (subArraySize != 1)
+ {
+ indexExpression =
+ new TIntermBinary(EOpMul, indexExpression, CreateIndexNode(subArraySize));
+ }
+
+ if (variableIndex == nullptr)
+ {
+ variableIndex = indexExpression;
+ }
+ else
+ {
+ variableIndex = new TIntermBinary(EOpAdd, variableIndex, indexExpression);
+ }
+ break;
+ }
+ default:
+ UNREACHABLE();
+ break;
+ }
+
+ node = node->getLeft()->getAsBinaryNode();
+ }
+
+ // Add the two accumulated indices together.
+ TIntermTyped *index = nullptr;
+ if (constantOffset == 0 && variableIndex != nullptr)
+ {
+ // No constant offset, but there's variable offset. Take that as offset.
+ index = variableIndex;
+ }
+ else
+ {
+ // Either the constant offset is non zero, or there's no variable offset (so constant 0
+ // should be used).
+ index = CreateIndexNode(constantOffset);
+
+ if (variableIndex)
+ {
+ index = new TIntermBinary(EOpAdd, index, variableIndex);
+ }
+ }
+
+ // Create an index into the flattened uniform.
+ TOperator op = variableIndex ? EOpIndexIndirect : EOpIndexDirect;
+ return new TIntermBinary(op, new TIntermSymbol(data.flattened), index);
+}
+
+// Traverser that takes:
+//
+// uniform sampler/image/atomic_uint u[N][M]..
+//
+// and transforms it to:
+//
+// uniform sampler/image/atomic_uint u[N * M * ..]
+//
+// MonomorphizeUnsupportedFunctions makes it impossible for this array to be partially
+// subscripted, or passed as argument to a function unsubscripted. This means that every encounter
+// of this uniform can be expected to be fully subscripted.
+//
+class RewriteArrayOfArrayOfOpaqueUniformsTraverser : public TIntermTraverser
+{
+ public:
+ RewriteArrayOfArrayOfOpaqueUniformsTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable), mCompiler(compiler)
+ {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+ {
+ if (!mInGlobalScope)
+ {
+ return true;
+ }
+
+ const TIntermSequence &sequence = *(node->getSequence());
+
+ TIntermTyped *variable = sequence.front()->getAsTyped();
+ const TType &type = variable->getType();
+ bool isOpaqueUniform =
+ type.getQualifier() == EvqUniform && IsOpaqueType(type.getBasicType());
+
+ // Only interested in array of array of opaque uniforms.
+ if (!isOpaqueUniform || !type.isArrayOfArrays())
+ {
+ return false;
+ }
+
+ // Opaque uniforms cannot have initializers, so the declaration must necessarily be a
+ // symbol.
+ TIntermSymbol *symbol = variable->getAsSymbolNode();
+ ASSERT(symbol != nullptr);
+
+ const TVariable *uniformVariable = &symbol->variable();
+
+ // Create an entry in the map.
+ ASSERT(mUniformMap.find(uniformVariable) == mUniformMap.end());
+ UniformData &data = mUniformMap[uniformVariable];
+
+ // Calculate the accumulated dimension products. See UniformData::mSubArraySizes.
+ const TSpan<const unsigned int> &arraySizes = type.getArraySizes();
+ mUniformMap[uniformVariable].mSubArraySizes.resize(arraySizes.size());
+ unsigned int runningProduct = 1;
+ for (size_t dimension = 0; dimension < arraySizes.size(); ++dimension)
+ {
+ data.mSubArraySizes[dimension] = runningProduct;
+ runningProduct *= arraySizes[dimension];
+ }
+
+ // Create a replacement variable with the array flattened.
+ TType *newType = new TType(type);
+ newType->toArrayBaseType();
+ newType->makeArray(runningProduct);
+
+ data.flattened = new TVariable(mSymbolTable, uniformVariable->name(), newType,
+ uniformVariable->symbolType());
+
+ TIntermDeclaration *decl = new TIntermDeclaration;
+ decl->appendDeclarator(new TIntermSymbol(data.flattened));
+
+ queueReplacement(decl, OriginalNode::IS_DROPPED);
+ return false;
+ }
+
+ bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override
+ {
+ // As an optimization, don't bother inspecting functions if there aren't any opaque uniforms
+ // to replace.
+ return !mUniformMap.empty();
+ }
+
+ // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root.
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ TIntermTyped *rewritten =
+ RewriteArrayOfArraySubscriptExpression(mCompiler, node, mUniformMap);
+ if (rewritten == nullptr)
+ {
+ return true;
+ }
+
+ queueReplacement(rewritten, OriginalNode::IS_DROPPED);
+
+ // Don't iterate as the expression is rewritten.
+ return false;
+ }
+
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ ASSERT(!IsOpaqueType(node->getType().getBasicType()) ||
+ mUniformMap.find(&node->variable()) == mUniformMap.end());
+ }
+
+ private:
+ TCompiler *mCompiler;
+ UniformMap mUniformMap;
+};
+} // anonymous namespace
+
+bool RewriteArrayOfArrayOfOpaqueUniforms(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ RewriteArrayOfArrayOfOpaqueUniformsTraverser traverser(compiler, symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h
new file mode 100644
index 0000000000..859ce16dc8
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteArrayOfArrayOfOpaqueUniforms.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2021 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteArrayOfArrayOfOpaqueUniforms: Flatten array of array of opaque uniforms. Requires
+// MonomorphizeUnsupportedFunctions and RewriteStructSamplers to have been run.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITEARRAYOFARRAYOFOPAQUEUNIFORMS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITEARRAYOFARRAYOFOPAQUEUNIFORMS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteArrayOfArrayOfOpaqueUniforms(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEARRAYOFARRAYOFOPAQUEUNIFORMS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp
new file mode 100644
index 0000000000..defc59bcc0
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.cpp
@@ -0,0 +1,328 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteAtomicCounters: Emulate atomic counter buffers with storage buffers.
+//
+
+#include "compiler/translator/tree_ops/RewriteAtomicCounters.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+namespace
+{
+constexpr ImmutableString kAtomicCountersVarName = ImmutableString("atomicCounters");
+constexpr ImmutableString kAtomicCounterFieldName = ImmutableString("counters");
+
+// DeclareAtomicCountersBuffer adds a storage buffer array that's used with atomic counters.
+const TVariable *DeclareAtomicCountersBuffers(TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ // Define `uint counters[];` as the only field in the interface block.
+ TFieldList *fieldList = new TFieldList;
+ TType *counterType = new TType(EbtUInt, EbpHigh, EvqGlobal);
+ counterType->makeArray(0);
+
+ TField *countersField =
+ new TField(counterType, kAtomicCounterFieldName, TSourceLoc(), SymbolType::AngleInternal);
+
+ fieldList->push_back(countersField);
+
+ TMemoryQualifier coherentMemory = TMemoryQualifier::Create();
+ coherentMemory.coherent = true;
+
+ // There are a maximum of 8 atomic counter buffers per IMPLEMENTATION_MAX_ATOMIC_COUNTER_BUFFERS
+ // in libANGLE/Constants.h.
+ constexpr uint32_t kMaxAtomicCounterBuffers = 8;
+
+ // Define a storage block "ANGLEAtomicCounters" with instance name "atomicCounters".
+ TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
+ layoutQualifier.blockStorage = EbsStd430;
+
+ return DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, layoutQualifier,
+ coherentMemory, kMaxAtomicCounterBuffers,
+ ImmutableString(vk::kAtomicCountersBlockName),
+ kAtomicCountersVarName);
+}
+
+TIntermTyped *CreateUniformBufferOffset(const TIntermTyped *uniformBufferOffsets, int binding)
+{
+ // Each uint in the |acbBufferOffsets| uniform contains offsets for 4 bindings. Therefore, the
+ // expression to get the uniform offset for the binding is:
+ //
+ // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF
+
+ // acbBufferOffsets[binding / 4]
+ TIntermBinary *uniformBufferOffsetUint = new TIntermBinary(
+ EOpIndexDirect, uniformBufferOffsets->deepCopy(), CreateIndexNode(binding / 4));
+
+ // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8)
+ TIntermBinary *uniformBufferOffsetShifted = uniformBufferOffsetUint;
+ if (binding % 4 != 0)
+ {
+ uniformBufferOffsetShifted = new TIntermBinary(EOpBitShiftRight, uniformBufferOffsetUint,
+ CreateUIntNode((binding % 4) * 8));
+ }
+
+ // acbBufferOffsets[binding / 4] >> ((binding % 4) * 8) & 0xFF
+ return new TIntermBinary(EOpBitwiseAnd, uniformBufferOffsetShifted, CreateUIntNode(0xFF));
+}
+
+TIntermBinary *CreateAtomicCounterRef(TIntermTyped *atomicCounterExpression,
+ const TVariable *atomicCounters,
+ const TIntermTyped *uniformBufferOffsets)
+{
+ // The atomic counters storage buffer declaration looks as such:
+ //
+ // layout(...) buffer ANGLEAtomicCounters
+ // {
+ // uint counters[];
+ // } atomicCounters[N];
+ //
+ // Where N is large enough to accommodate atomic counter buffer bindings used in the shader.
+ //
+ // This function takes an expression that uses an atomic counter, which can either be:
+ //
+ // - ac
+ // - acArray[index]
+ //
+ // Note that RewriteArrayOfArrayOfOpaqueUniforms has already flattened array of array of atomic
+ // counters.
+ //
+ // For the first case (ac), the following code is generated:
+ //
+ // atomicCounters[binding].counters[offset]
+ //
+ // For the second case (acArray[index]), the following code is generated:
+ //
+ // atomicCounters[binding].counters[offset + index]
+ //
+ // In either case, an offset given through uniforms is also added to |offset|. The binding is
+ // necessarily a constant thanks to MonomorphizeUnsupportedFunctions.
+
+ // First determine if there's an index, and extract the atomic counter symbol out of the
+ // expression.
+ TIntermSymbol *atomicCounterSymbol = atomicCounterExpression->getAsSymbolNode();
+ TIntermTyped *atomicCounterIndex = nullptr;
+ int atomicCounterConstIndex = 0;
+ TIntermBinary *asBinary = atomicCounterExpression->getAsBinaryNode();
+ if (asBinary != nullptr)
+ {
+ atomicCounterSymbol = asBinary->getLeft()->getAsSymbolNode();
+
+ switch (asBinary->getOp())
+ {
+ case EOpIndexDirect:
+ atomicCounterConstIndex = asBinary->getRight()->getAsConstantUnion()->getIConst(0);
+ break;
+ case EOpIndexIndirect:
+ atomicCounterIndex = asBinary->getRight();
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ // Extract binding and offset information out of the atomic counter symbol.
+ ASSERT(atomicCounterSymbol);
+ const TVariable *atomicCounterVar = &atomicCounterSymbol->variable();
+ const TType &atomicCounterType = atomicCounterVar->getType();
+
+ const int binding = atomicCounterType.getLayoutQualifier().binding;
+ int offset = atomicCounterType.getLayoutQualifier().offset / 4;
+
+ // Create the expression:
+ //
+ // offset + arrayIndex + uniformOffset
+ //
+ // If arrayIndex is a constant, it's added with offset right here.
+
+ offset += atomicCounterConstIndex;
+
+ TIntermTyped *index = CreateUniformBufferOffset(uniformBufferOffsets, binding);
+ if (atomicCounterIndex != nullptr)
+ {
+ index = new TIntermBinary(EOpAdd, index, atomicCounterIndex);
+ }
+ if (offset != 0)
+ {
+ index = new TIntermBinary(EOpAdd, index, CreateIndexNode(offset));
+ }
+
+ // Finally, create the complete expression:
+ //
+ // atomicCounters[binding].counters[index]
+
+ TIntermSymbol *atomicCountersRef = new TIntermSymbol(atomicCounters);
+
+ // atomicCounters[binding]
+ TIntermBinary *countersBlock =
+ new TIntermBinary(EOpIndexDirect, atomicCountersRef, CreateIndexNode(binding));
+
+ // atomicCounters[binding].counters
+ TIntermBinary *counters =
+ new TIntermBinary(EOpIndexDirectInterfaceBlock, countersBlock, CreateIndexNode(0));
+
+ return new TIntermBinary(EOpIndexIndirect, counters, index);
+}
+
+// Traverser that:
+//
+// 1. Removes the |uniform atomic_uint| declarations and remembers the binding and offset.
+// 2. Substitutes |atomicVar[n]| with |buffer[binding].counters[offset + n]|.
+class RewriteAtomicCountersTraverser : public TIntermTraverser
+{
+ public:
+ RewriteAtomicCountersTraverser(TSymbolTable *symbolTable,
+ const TVariable *atomicCounters,
+ const TIntermTyped *acbBufferOffsets)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mAtomicCounters(atomicCounters),
+ mAcbBufferOffsets(acbBufferOffsets)
+ {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+ {
+ if (!mInGlobalScope)
+ {
+ return true;
+ }
+
+ const TIntermSequence &sequence = *(node->getSequence());
+
+ TIntermTyped *variable = sequence.front()->getAsTyped();
+ const TType &type = variable->getType();
+ bool isAtomicCounter = type.isAtomicCounter();
+
+ if (isAtomicCounter)
+ {
+ ASSERT(type.getQualifier() == EvqUniform);
+ TIntermSequence emptySequence;
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(emptySequence));
+
+ return false;
+ }
+
+ return true;
+ }
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override
+ {
+ if (BuiltInGroup::IsBuiltIn(node->getOp()))
+ {
+ bool converted = convertBuiltinFunction(node);
+ return !converted;
+ }
+
+ // AST functions don't require modification as atomic counter function parameters are
+ // removed by MonomorphizeUnsupportedFunctions.
+ return true;
+ }
+
+ void visitSymbol(TIntermSymbol *symbol) override
+ {
+ // Cannot encounter the atomic counter symbol directly. It can only be used with functions,
+ // and therefore it's handled by visitAggregate.
+ ASSERT(!symbol->getType().isAtomicCounter());
+ }
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ // Cannot encounter an atomic counter expression directly. It can only be used with
+ // functions, and therefore it's handled by visitAggregate.
+ ASSERT(!node->getType().isAtomicCounter());
+ return true;
+ }
+
+ private:
+ bool convertBuiltinFunction(TIntermAggregate *node)
+ {
+ const TOperator op = node->getOp();
+
+ // If the function is |memoryBarrierAtomicCounter|, simply replace it with
+ // |memoryBarrierBuffer|.
+ if (op == EOpMemoryBarrierAtomicCounter)
+ {
+ TIntermSequence emptySequence;
+ TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
+ "memoryBarrierBuffer", &emptySequence, *mSymbolTable, 310);
+ queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
+ return true;
+ }
+
+ // If it's an |atomicCounter*| function, replace the function with an |atomic*| equivalent.
+ if (!node->getFunction()->isAtomicCounterFunction())
+ {
+ return false;
+ }
+
+ // Note: atomicAdd(0) is used for atomic reads.
+ uint32_t valueChange = 0;
+ constexpr char kAtomicAddFunction[] = "atomicAdd";
+ bool isDecrement = false;
+
+ if (op == EOpAtomicCounterIncrement)
+ {
+ valueChange = 1;
+ }
+ else if (op == EOpAtomicCounterDecrement)
+ {
+ // uint values are required to wrap around, so 0xFFFFFFFFu is used as -1.
+ valueChange = std::numeric_limits<uint32_t>::max();
+ static_assert(static_cast<uint32_t>(-1) == std::numeric_limits<uint32_t>::max(),
+ "uint32_t max is not -1");
+
+ isDecrement = true;
+ }
+ else
+ {
+ ASSERT(op == EOpAtomicCounter);
+ }
+
+ TIntermTyped *param = (*node->getSequence())[0]->getAsTyped();
+
+ TIntermSequence substituteArguments;
+ substituteArguments.push_back(
+ CreateAtomicCounterRef(param, mAtomicCounters, mAcbBufferOffsets));
+ substituteArguments.push_back(CreateUIntNode(valueChange));
+
+ TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
+ kAtomicAddFunction, &substituteArguments, *mSymbolTable, 310);
+
+ // Note that atomicCounterDecrement returns the *new* value instead of the prior value,
+ // unlike atomicAdd. So we need to do a -1 on the result as well.
+ if (isDecrement)
+ {
+ substituteCall = new TIntermBinary(EOpSub, substituteCall, CreateUIntNode(1));
+ }
+
+ queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
+ return true;
+ }
+
+ const TVariable *mAtomicCounters;
+ const TIntermTyped *mAcbBufferOffsets;
+};
+
+} // anonymous namespace
+
+bool RewriteAtomicCounters(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const TIntermTyped *acbBufferOffsets)
+{
+ const TVariable *atomicCounters = DeclareAtomicCountersBuffers(root, symbolTable);
+
+ RewriteAtomicCountersTraverser traverser(symbolTable, atomicCounters, acbBufferOffsets);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h
new file mode 100644
index 0000000000..8a94f84b3a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteAtomicCounters.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteAtomicCounters: Change atomic counter buffers to storage buffers, with atomic counter
+// variables being offsets into the uint array of that storage buffer.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TIntermTyped;
+class TSymbolTable;
+class TVariable;
+
+[[nodiscard]] bool RewriteAtomicCounters(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ const TIntermTyped *acbBufferOffsets);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEATOMICCOUNTERS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp
new file mode 100644
index 0000000000..f7e5814361
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.cpp
@@ -0,0 +1,984 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteCubeMapSamplersAs2DArray: Change samplerCube samplers to sampler2DArray for seamful cube
+// map emulation.
+//
+// Relies on MonomorphizeUnsupportedFunctions to ensure samplerCube variables are not
+// passed to functions (for simplicity).
+//
+
+#include "compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+namespace
+{
+constexpr ImmutableString kCoordTransformFuncName("ANGLECubeMapCoordTransform");
+constexpr ImmutableString kCoordTransformFuncNameImplicit("ANGLECubeMapCoordTransformImplicit");
+
+TIntermTyped *DerivativeQuotient(TIntermTyped *u,
+ TIntermTyped *du,
+ TIntermTyped *v,
+ TIntermTyped *dv,
+ TIntermTyped *vRecip)
+{
+ // (du v - dv u) / v^2
+ return new TIntermBinary(
+ EOpMul,
+ new TIntermBinary(EOpSub, new TIntermBinary(EOpMul, du->deepCopy(), v->deepCopy()),
+ new TIntermBinary(EOpMul, dv->deepCopy(), u->deepCopy())),
+ new TIntermBinary(EOpMul, vRecip->deepCopy(), vRecip->deepCopy()));
+}
+
+TIntermTyped *Swizzle1(TIntermTyped *array, int i)
+{
+ return new TIntermSwizzle(array, {i});
+}
+
+TIntermTyped *IndexDirect(TIntermTyped *array, int i)
+{
+ return new TIntermBinary(EOpIndexDirect, array, CreateIndexNode(i));
+}
+
+// Generated the common transformation in each coord transformation case. See comment in
+// declareCoordTranslationFunction(). Called with P, dPdx and dPdy.
+void TransformXMajor(const TSymbolTable &symbolTable,
+ TIntermBlock *block,
+ TIntermTyped *x,
+ TIntermTyped *y,
+ TIntermTyped *z,
+ TIntermTyped *uc,
+ TIntermTyped *vc)
+{
+ // uc = -sign(x)*z
+ // vc = -y
+ TIntermTyped *signX =
+ CreateBuiltInUnaryFunctionCallNode("sign", x->deepCopy(), symbolTable, 100);
+
+ TIntermTyped *ucValue =
+ new TIntermUnary(EOpNegative, new TIntermBinary(EOpMul, signX, z->deepCopy()), nullptr);
+ TIntermTyped *vcValue = new TIntermUnary(EOpNegative, y->deepCopy(), nullptr);
+
+ block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue));
+ block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue));
+}
+
+void TransformDerivativeXMajor(TIntermBlock *block,
+ TSymbolTable *symbolTable,
+ TIntermTyped *x,
+ TIntermTyped *y,
+ TIntermTyped *z,
+ TIntermTyped *dx,
+ TIntermTyped *dy,
+ TIntermTyped *dz,
+ TIntermTyped *du,
+ TIntermTyped *dv,
+ TIntermTyped *xRecip)
+{
+ // Only the magnitude of the derivative matters, so we ignore the sign(x)
+ // and the negations.
+ TIntermTyped *duValue = DerivativeQuotient(z, dz, x, dx, xRecip);
+ TIntermTyped *dvValue = DerivativeQuotient(y, dy, x, dx, xRecip);
+ duValue = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f, EbpMedium));
+ dvValue = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f, EbpMedium));
+ block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue));
+ block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue));
+}
+
+void TransformImplicitDerivativeXMajor(TIntermBlock *block,
+ TIntermTyped *dOuter,
+ TIntermTyped *du,
+ TIntermTyped *dv)
+{
+ block->appendStatement(
+ new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 2)));
+ block->appendStatement(
+ new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 1)));
+}
+
+void TransformYMajor(const TSymbolTable &symbolTable,
+ TIntermBlock *block,
+ TIntermTyped *x,
+ TIntermTyped *y,
+ TIntermTyped *z,
+ TIntermTyped *uc,
+ TIntermTyped *vc)
+{
+ // uc = x
+ // vc = sign(y)*z
+ TIntermTyped *signY =
+ CreateBuiltInUnaryFunctionCallNode("sign", y->deepCopy(), symbolTable, 100);
+
+ TIntermTyped *ucValue = x->deepCopy();
+ TIntermTyped *vcValue = new TIntermBinary(EOpMul, signY, z->deepCopy());
+
+ block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue));
+ block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue));
+}
+
+void TransformDerivativeYMajor(TIntermBlock *block,
+ TSymbolTable *symbolTable,
+ TIntermTyped *x,
+ TIntermTyped *y,
+ TIntermTyped *z,
+ TIntermTyped *dx,
+ TIntermTyped *dy,
+ TIntermTyped *dz,
+ TIntermTyped *du,
+ TIntermTyped *dv,
+ TIntermTyped *yRecip)
+{
+ // Only the magnitude of the derivative matters, so we ignore the sign(x)
+ // and the negations.
+ TIntermTyped *duValue = DerivativeQuotient(x, dx, y, dy, yRecip);
+ TIntermTyped *dvValue = DerivativeQuotient(z, dz, y, dy, yRecip);
+ duValue = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f, EbpMedium));
+ dvValue = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f, EbpMedium));
+ block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue));
+ block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue));
+}
+
+void TransformImplicitDerivativeYMajor(TIntermBlock *block,
+ TIntermTyped *dOuter,
+ TIntermTyped *du,
+ TIntermTyped *dv)
+{
+ block->appendStatement(
+ new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 0)));
+ block->appendStatement(
+ new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 2)));
+}
+
+void TransformZMajor(const TSymbolTable &symbolTable,
+ TIntermBlock *block,
+ TIntermTyped *x,
+ TIntermTyped *y,
+ TIntermTyped *z,
+ TIntermTyped *uc,
+ TIntermTyped *vc)
+{
+ // uc = size(z)*x
+ // vc = -y
+ TIntermTyped *signZ =
+ CreateBuiltInUnaryFunctionCallNode("sign", z->deepCopy(), symbolTable, 100);
+
+ TIntermTyped *ucValue = new TIntermBinary(EOpMul, signZ, x->deepCopy());
+ TIntermTyped *vcValue = new TIntermUnary(EOpNegative, y->deepCopy(), nullptr);
+
+ block->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), ucValue));
+ block->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vcValue));
+}
+
+void TransformDerivativeZMajor(TIntermBlock *block,
+ TSymbolTable *symbolTable,
+ TIntermTyped *x,
+ TIntermTyped *y,
+ TIntermTyped *z,
+ TIntermTyped *dx,
+ TIntermTyped *dy,
+ TIntermTyped *dz,
+ TIntermTyped *du,
+ TIntermTyped *dv,
+ TIntermTyped *zRecip)
+{
+ // Only the magnitude of the derivative matters, so we ignore the sign(x)
+ // and the negations.
+ TIntermTyped *duValue = DerivativeQuotient(x, dx, z, dz, zRecip);
+ TIntermTyped *dvValue = DerivativeQuotient(y, dy, z, dz, zRecip);
+ duValue = new TIntermBinary(EOpMul, duValue, CreateFloatNode(0.5f, EbpMedium));
+ dvValue = new TIntermBinary(EOpMul, dvValue, CreateFloatNode(0.5f, EbpMedium));
+ block->appendStatement(new TIntermBinary(EOpAssign, du->deepCopy(), duValue));
+ block->appendStatement(new TIntermBinary(EOpAssign, dv->deepCopy(), dvValue));
+}
+
+void TransformImplicitDerivativeZMajor(TIntermBlock *block,
+ TIntermTyped *dOuter,
+ TIntermTyped *du,
+ TIntermTyped *dv)
+{
+ block->appendStatement(
+ new TIntermBinary(EOpAssign, du->deepCopy(), Swizzle1(dOuter->deepCopy(), 0)));
+ block->appendStatement(
+ new TIntermBinary(EOpAssign, dv->deepCopy(), Swizzle1(dOuter->deepCopy(), 1)));
+}
+
+class RewriteCubeMapSamplersAs2DArrayTraverser : public TIntermTraverser
+{
+ public:
+ RewriteCubeMapSamplersAs2DArrayTraverser(TSymbolTable *symbolTable, bool isFragmentShader)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mCubeXYZToArrayUVL(nullptr),
+ mCubeXYZToArrayUVLImplicit(nullptr),
+ mIsFragmentShader(isFragmentShader),
+ mCoordTranslationFunctionDecl(nullptr),
+ mCoordTranslationFunctionImplicitDecl(nullptr)
+ {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+ {
+ const TIntermSequence &sequence = *(node->getSequence());
+
+ TIntermTyped *variable = sequence.front()->getAsTyped();
+ const TType &type = variable->getType();
+ bool isSamplerCube = type.getQualifier() == EvqUniform && type.isSamplerCube();
+
+ if (isSamplerCube)
+ {
+ // Samplers cannot have initializers, so the declaration must necessarily be a symbol.
+ TIntermSymbol *samplerVariable = variable->getAsSymbolNode();
+ ASSERT(samplerVariable != nullptr);
+
+ declareSampler2DArray(&samplerVariable->variable(), node);
+ return false;
+ }
+
+ return true;
+ }
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override
+ {
+ if (BuiltInGroup::IsBuiltIn(node->getOp()))
+ {
+ bool converted = convertBuiltinFunction(node);
+ return !converted;
+ }
+
+ // AST functions don't require modification as samplerCube function parameters are removed
+ // by MonomorphizeUnsupportedFunctions.
+ return true;
+ }
+
+ TIntermFunctionDefinition *getCoordTranslationFunctionDecl()
+ {
+ return mCoordTranslationFunctionDecl;
+ }
+
+ TIntermFunctionDefinition *getCoordTranslationFunctionDeclImplicit()
+ {
+ return mCoordTranslationFunctionImplicitDecl;
+ }
+
+ private:
+ void declareSampler2DArray(const TVariable *samplerCubeVar, TIntermDeclaration *node)
+ {
+ if (mCubeXYZToArrayUVL == nullptr)
+ {
+ // If not done yet, declare the function that transforms cube map texture sampling
+ // coordinates to face index and uv coordinates.
+ declareCoordTranslationFunction(false, kCoordTransformFuncName, &mCubeXYZToArrayUVL,
+ &mCoordTranslationFunctionDecl);
+ }
+ if (mCubeXYZToArrayUVLImplicit == nullptr && mIsFragmentShader)
+ {
+ declareCoordTranslationFunction(true, kCoordTransformFuncNameImplicit,
+ &mCubeXYZToArrayUVLImplicit,
+ &mCoordTranslationFunctionImplicitDecl);
+ }
+
+ TType *newType = new TType(samplerCubeVar->getType());
+ newType->setBasicType(EbtSampler2DArray);
+
+ TVariable *sampler2DArrayVar = new TVariable(mSymbolTable, samplerCubeVar->name(), newType,
+ samplerCubeVar->symbolType());
+
+ TIntermDeclaration *sampler2DArrayDecl = new TIntermDeclaration();
+ sampler2DArrayDecl->appendDeclarator(new TIntermSymbol(sampler2DArrayVar));
+
+ queueReplacement(sampler2DArrayDecl, OriginalNode::IS_DROPPED);
+
+ // Remember the sampler2DArray variable.
+ mSamplerMap[samplerCubeVar] = sampler2DArrayVar;
+ }
+
+ void declareCoordTranslationFunction(bool implicit,
+ const ImmutableString &name,
+ TFunction **functionOut,
+ TIntermFunctionDefinition **declOut)
+ {
+ // GLES2.0 (as well as desktop OpenGL 2.0) define the coordination transformation as
+ // follows. Given xyz cube coordinates, where each channel is in [-1, 1], the following
+ // table calculates uc, vc and ma as well as the cube map face.
+ //
+ // Major Axis Direction Target uc vc ma
+ // +x TEXTURE_CUBE_MAP_POSITIVE_X -z -y |x|
+ // -x TEXTURE_CUBE_MAP_NEGATIVE_X z -y |x|
+ // +y TEXTURE_CUBE_MAP_POSITIVE_Y x z |y|
+ // -y TEXTURE_CUBE_MAP_NEGATIVE_Y x -z |y|
+ // +z TEXTURE_CUBE_MAP_POSITIVE_Z x -y |z|
+ // -z TEXTURE_CUBE_MAP_NEGATIVE_Z -x -y |z|
+ //
+ // "Major" is an indication of the axis with the largest value. The cube map face indicates
+ // the layer to sample from. The uv coordinates to sample from are calculated as,
+ // effectively transforming the uv values to [0, 1]:
+ //
+ // u = (1 + uc/ma) / 2
+ // v = (1 + vc/ma) / 2
+ //
+ // The function can be implemented as 6 ifs, though it would be far from efficient. The
+ // following calculations implement the table above in a smaller number of instructions.
+ //
+ // First, ma can be calculated as the max of the three axes.
+ //
+ // ma = max3(|x|, |y|, |z|)
+ //
+ // We have three cases:
+ //
+ // ma == |x|: uc = -sign(x)*z
+ // vc = -y
+ // layer = float(x < 0)
+ //
+ // ma == |y|: uc = x
+ // vc = sign(y)*z
+ // layer = 2 + float(y < 0)
+ //
+ // ma == |z|: uc = size(z)*x
+ // vc = -y
+ // layer = 4 + float(z < 0)
+ //
+ // This can be implemented with a number of ?: instructions or 3 ifs. ?: would require all
+ // expressions to be evaluated (vector ALU) while if would require exec mask and jumps
+ // (scalar operations). We implement this using ifs as there would otherwise be many vector
+ // operations and not much of anything else.
+ //
+ // If textureCubeGrad is used, we also need to transform the provided dPdx and dPdy (both
+ // vec3) to a dUVdx and dUVdy. Assume P=(r,s,t) and we are investigating dx (note the
+ // change from xyz to rst to not confuse with dx and dy):
+ //
+ // uv = (f(r,s,t)/ma + 1)/2
+ //
+ // Where f is one of the transformations above for uc and vc. Between two neighbors along
+ // the x axis, we have P0=(r0,s0,t0) and P1=(r1,s1,t1)
+ //
+ // dP = (r1-r0, s1-s0, t1-t0)
+ // dUV = (f(r1,s1,t1)/ma1 - g(r0,s0,t0)/ma0) / 2
+ //
+ // f and g may not necessarily be the same because the two points may have different major
+ // axes. Even with the same major access, the sign that's used in the formulas may not be
+ // the same. Furthermore, ma0 and ma1 may not be the same. This makes it impossible to
+ // derive dUV from dP exactly.
+ //
+ // However, gradient transformation is implementation dependant, so we will simplify and
+ // assume all the above complications are non-existent. We therefore have:
+ //
+ // dUV = (f(r1,s1,t1)/ma0 - f(r0,s0,t0)/ma0)/2
+ //
+ // Given that we assumed the sign functions are returning identical results for the two
+ // points, f becomes a linear transformation. Thus:
+ //
+ // dUV = f(r1-r0,s1-0,t1-t0)/ma0/2
+ //
+ // In other words, we use the same formulae that transform XYZ (RST here) to UV to
+ // transform the derivatives.
+ //
+ // ma == |x|: dUdx = -sign(x)*dPdx.z / ma / 2
+ // dVdx = -dPdx.y / ma / 2
+ //
+ // ma == |y|: dUdx = dPdx.x / ma / 2
+ // dVdx = sign(y)*dPdx.z / ma / 2
+ //
+ // ma == |z|: dUdx = size(z)*dPdx.x / ma / 2
+ // dVdx = -dPdx.y / ma / 2
+ //
+ // Similarly for dy.
+
+ // Create the function parameters: vec3 P, vec3 dPdx, vec3 dPdy,
+ // out vec2 dUVdx, out vec2 dUVdy
+ const TType *vec3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3>();
+ TType *inVec3Type = new TType(*vec3Type);
+ inVec3Type->setQualifier(EvqParamIn);
+
+ TVariable *pVar = new TVariable(mSymbolTable, ImmutableString("P"), inVec3Type,
+ SymbolType::AngleInternal);
+ TVariable *dPdxVar = new TVariable(mSymbolTable, ImmutableString("dPdx"), inVec3Type,
+ SymbolType::AngleInternal);
+ TVariable *dPdyVar = new TVariable(mSymbolTable, ImmutableString("dPdy"), inVec3Type,
+ SymbolType::AngleInternal);
+
+ const TType *vec2Type = StaticType::GetBasic<EbtFloat, EbpHigh, 2>();
+ TType *outVec2Type = new TType(*vec2Type);
+ outVec2Type->setQualifier(EvqParamOut);
+
+ TVariable *dUVdxVar = new TVariable(mSymbolTable, ImmutableString("dUVdx"), outVec2Type,
+ SymbolType::AngleInternal);
+ TVariable *dUVdyVar = new TVariable(mSymbolTable, ImmutableString("dUVdy"), outVec2Type,
+ SymbolType::AngleInternal);
+
+ TIntermSymbol *p = new TIntermSymbol(pVar);
+ TIntermSymbol *dPdx = new TIntermSymbol(dPdxVar);
+ TIntermSymbol *dPdy = new TIntermSymbol(dPdyVar);
+ TIntermSymbol *dUVdx = new TIntermSymbol(dUVdxVar);
+ TIntermSymbol *dUVdy = new TIntermSymbol(dUVdyVar);
+
+ // Create the function body as statements are generated.
+ TIntermBlock *body = new TIntermBlock;
+
+ // Create the swizzle nodes that will be used in multiple expressions:
+ TIntermSwizzle *x = new TIntermSwizzle(p->deepCopy(), {0});
+ TIntermSwizzle *y = new TIntermSwizzle(p->deepCopy(), {1});
+ TIntermSwizzle *z = new TIntermSwizzle(p->deepCopy(), {2});
+
+ // Create abs and "< 0" expressions from the channels.
+ const TType *floatType = StaticType::GetBasic<EbtFloat, EbpHigh>();
+
+ TIntermTyped *isNegX = new TIntermBinary(EOpLessThan, x, CreateZeroNode(*floatType));
+ TIntermTyped *isNegY = new TIntermBinary(EOpLessThan, y, CreateZeroNode(*floatType));
+ TIntermTyped *isNegZ = new TIntermBinary(EOpLessThan, z, CreateZeroNode(*floatType));
+
+ TIntermSymbol *absX = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *absY = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *absZ = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+
+ TIntermDeclaration *absXDecl = CreateTempInitDeclarationNode(
+ &absX->variable(),
+ CreateBuiltInUnaryFunctionCallNode("abs", x->deepCopy(), *mSymbolTable, 100));
+ TIntermDeclaration *absYDecl = CreateTempInitDeclarationNode(
+ &absY->variable(),
+ CreateBuiltInUnaryFunctionCallNode("abs", y->deepCopy(), *mSymbolTable, 100));
+ TIntermDeclaration *absZDecl = CreateTempInitDeclarationNode(
+ &absZ->variable(),
+ CreateBuiltInUnaryFunctionCallNode("abs", z->deepCopy(), *mSymbolTable, 100));
+
+ body->appendStatement(absXDecl);
+ body->appendStatement(absYDecl);
+ body->appendStatement(absZDecl);
+
+ // Create temporary variable for division outer product matrix and its
+ // derivatives.
+ // recipOuter[i][j] = 0.5 * P[j] / P[i]
+ const TType *mat3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3, 3>();
+ TIntermSymbol *recipOuter = new TIntermSymbol(CreateTempVariable(mSymbolTable, mat3Type));
+
+ TIntermTyped *pRecip =
+ new TIntermBinary(EOpDiv, CreateFloatNode(1.0, EbpMedium), p->deepCopy());
+ TIntermSymbol *pRecipVar = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+
+ body->appendStatement(CreateTempInitDeclarationNode(&pRecipVar->variable(), pRecip));
+
+ TIntermSequence args = {
+ p->deepCopy(), new TIntermBinary(EOpVectorTimesScalar, CreateFloatNode(0.5, EbpMedium),
+ pRecipVar->deepCopy())};
+ TIntermDeclaration *recipOuterDecl = CreateTempInitDeclarationNode(
+ &recipOuter->variable(),
+ CreateBuiltInFunctionCallNode("outerProduct", &args, *mSymbolTable, 300));
+ body->appendStatement(recipOuterDecl);
+
+ TIntermSymbol *dPDXdx = nullptr;
+ TIntermSymbol *dPDYdx = nullptr;
+ TIntermSymbol *dPDZdx = nullptr;
+ TIntermSymbol *dPDXdy = nullptr;
+ TIntermSymbol *dPDYdy = nullptr;
+ TIntermSymbol *dPDZdy = nullptr;
+ if (implicit)
+ {
+ dPDXdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+ dPDYdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+ dPDZdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+ dPDXdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+ dPDYdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+ dPDZdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+
+ TIntermDeclaration *dPDXdxDecl = CreateTempInitDeclarationNode(
+ &dPDXdx->variable(),
+ CreateBuiltInUnaryFunctionCallNode("dFdx", IndexDirect(recipOuter, 0)->deepCopy(),
+ *mSymbolTable, 300));
+ TIntermDeclaration *dPDYdxDecl = CreateTempInitDeclarationNode(
+ &dPDYdx->variable(),
+ CreateBuiltInUnaryFunctionCallNode("dFdx", IndexDirect(recipOuter, 1)->deepCopy(),
+ *mSymbolTable, 300));
+ TIntermDeclaration *dPDZdxDecl = CreateTempInitDeclarationNode(
+ &dPDZdx->variable(),
+ CreateBuiltInUnaryFunctionCallNode("dFdx", IndexDirect(recipOuter, 2)->deepCopy(),
+ *mSymbolTable, 300));
+ TIntermDeclaration *dPDXdyDecl = CreateTempInitDeclarationNode(
+ &dPDXdy->variable(),
+ CreateBuiltInUnaryFunctionCallNode("dFdy", IndexDirect(recipOuter, 0)->deepCopy(),
+ *mSymbolTable, 300));
+ TIntermDeclaration *dPDYdyDecl = CreateTempInitDeclarationNode(
+ &dPDYdy->variable(),
+ CreateBuiltInUnaryFunctionCallNode("dFdy", IndexDirect(recipOuter, 1)->deepCopy(),
+ *mSymbolTable, 300));
+ TIntermDeclaration *dPDZdyDecl = CreateTempInitDeclarationNode(
+ &dPDZdy->variable(),
+ CreateBuiltInUnaryFunctionCallNode("dFdy", IndexDirect(recipOuter, 2)->deepCopy(),
+ *mSymbolTable, 300));
+
+ body->appendStatement(dPDXdxDecl);
+ body->appendStatement(dPDYdxDecl);
+ body->appendStatement(dPDZdxDecl);
+ body->appendStatement(dPDXdyDecl);
+ body->appendStatement(dPDYdyDecl);
+ body->appendStatement(dPDZdyDecl);
+ }
+
+ // Create temporary variables for ma, uc, vc, and l (layer), as well as dUdx, dVdx, dUdy
+ // and dVdy.
+ TIntermSymbol *ma = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *l = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *uc = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *vc = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *dUdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *dVdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *dUdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ TIntermSymbol *dVdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+
+ body->appendStatement(CreateTempDeclarationNode(&ma->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&l->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&uc->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&vc->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&dUdx->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&dVdx->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&dUdy->variable()));
+ body->appendStatement(CreateTempDeclarationNode(&dVdy->variable()));
+
+ // ma = max(|x|, max(|y|, |z|))
+ TIntermSequence argsMaxYZ = {absY->deepCopy(), absZ->deepCopy()};
+ TIntermTyped *maxYZ = CreateBuiltInFunctionCallNode("max", &argsMaxYZ, *mSymbolTable, 100);
+ TIntermSequence argsMaxValue = {absX->deepCopy(), maxYZ};
+ TIntermTyped *maValue =
+ CreateBuiltInFunctionCallNode("max", &argsMaxValue, *mSymbolTable, 100);
+ body->appendStatement(new TIntermBinary(EOpAssign, ma, maValue));
+
+ // ma == |x| and ma == |y| expressions
+ TIntermTyped *isXMajor = new TIntermBinary(EOpEqual, ma->deepCopy(), absX->deepCopy());
+ TIntermTyped *isYMajor = new TIntermBinary(EOpEqual, ma->deepCopy(), absY->deepCopy());
+
+ // Determine the cube face:
+
+ // The case where x is major:
+ // layer = float(x < 0)
+ TIntermSequence argsNegX = {isNegX};
+ TIntermTyped *xl = TIntermAggregate::CreateConstructor(*floatType, &argsNegX);
+
+ TIntermBlock *calculateXL = new TIntermBlock;
+ calculateXL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), xl));
+
+ // The case where y is major:
+ // layer = 2 + float(y < 0)
+ TIntermSequence argsNegY = {isNegY};
+ TIntermTyped *yl =
+ new TIntermBinary(EOpAdd, CreateFloatNode(2.0f, EbpMedium),
+ TIntermAggregate::CreateConstructor(*floatType, &argsNegY));
+
+ TIntermBlock *calculateYL = new TIntermBlock;
+ calculateYL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), yl));
+
+ // The case where z is major:
+ // layer = 4 + float(z < 0)
+ TIntermSequence argsNegZ = {isNegZ};
+ TIntermTyped *zl =
+ new TIntermBinary(EOpAdd, CreateFloatNode(4.0f, EbpMedium),
+ TIntermAggregate::CreateConstructor(*floatType, &argsNegZ));
+
+ TIntermBlock *calculateZL = new TIntermBlock;
+ calculateZL->appendStatement(new TIntermBinary(EOpAssign, l->deepCopy(), zl));
+
+ // Create the if-else paths:
+ TIntermIfElse *calculateYZL = new TIntermIfElse(isYMajor, calculateYL, calculateZL);
+ TIntermBlock *calculateYZLBlock = new TIntermBlock;
+ calculateYZLBlock->appendStatement(calculateYZL);
+ TIntermIfElse *calculateXYZL = new TIntermIfElse(isXMajor, calculateXL, calculateYZLBlock);
+ body->appendStatement(calculateXYZL);
+
+ // layer < 1.5 (covering faces 0 and 1, corresponding to major axis being X) and layer < 3.5
+ // (covering faces 2 and 3, corresponding to major axis being Y). Used to determine which
+ // of the three transformations to apply. Previously, ma == |X| and ma == |Y| was used,
+ // which is no longer correct for helper invocations. The value of ma is updated in each
+ // case for these invocations.
+ isXMajor = new TIntermBinary(EOpLessThan, l->deepCopy(), CreateFloatNode(1.5f, EbpMedium));
+ isYMajor = new TIntermBinary(EOpLessThan, l->deepCopy(), CreateFloatNode(3.5f, EbpMedium));
+
+ TIntermSwizzle *dPdxX = new TIntermSwizzle(dPdx->deepCopy(), {0});
+ TIntermSwizzle *dPdxY = new TIntermSwizzle(dPdx->deepCopy(), {1});
+ TIntermSwizzle *dPdxZ = new TIntermSwizzle(dPdx->deepCopy(), {2});
+
+ TIntermSwizzle *dPdyX = new TIntermSwizzle(dPdy->deepCopy(), {0});
+ TIntermSwizzle *dPdyY = new TIntermSwizzle(dPdy->deepCopy(), {1});
+ TIntermSwizzle *dPdyZ = new TIntermSwizzle(dPdy->deepCopy(), {2});
+
+ TIntermBlock *calculateXUcVc = new TIntermBlock;
+ calculateXUcVc->appendStatement(
+ new TIntermBinary(EOpAssign, ma->deepCopy(), absX->deepCopy()));
+ TransformXMajor(*mSymbolTable, calculateXUcVc, x, y, z, uc, vc);
+
+ TIntermBlock *calculateYUcVc = new TIntermBlock;
+ calculateYUcVc->appendStatement(
+ new TIntermBinary(EOpAssign, ma->deepCopy(), absY->deepCopy()));
+ TransformYMajor(*mSymbolTable, calculateYUcVc, x, y, z, uc, vc);
+
+ TIntermBlock *calculateZUcVc = new TIntermBlock;
+ calculateZUcVc->appendStatement(
+ new TIntermBinary(EOpAssign, ma->deepCopy(), absZ->deepCopy()));
+ TransformZMajor(*mSymbolTable, calculateZUcVc, x, y, z, uc, vc);
+
+ // Compute derivatives.
+ if (implicit)
+ {
+ TransformImplicitDerivativeXMajor(calculateXUcVc, dPDXdx, dUdx, dVdx);
+ TransformImplicitDerivativeXMajor(calculateXUcVc, dPDXdy, dUdy, dVdy);
+ TransformImplicitDerivativeYMajor(calculateYUcVc, dPDYdx, dUdx, dVdx);
+ TransformImplicitDerivativeYMajor(calculateYUcVc, dPDYdy, dUdy, dVdy);
+ TransformImplicitDerivativeZMajor(calculateZUcVc, dPDZdx, dUdx, dVdx);
+ TransformImplicitDerivativeZMajor(calculateZUcVc, dPDZdy, dUdy, dVdy);
+ }
+ else
+ {
+ TransformDerivativeXMajor(calculateXUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ,
+ dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 0));
+ TransformDerivativeXMajor(calculateXUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ,
+ dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 0));
+ TransformDerivativeYMajor(calculateYUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ,
+ dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 1));
+ TransformDerivativeYMajor(calculateYUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ,
+ dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 1));
+ TransformDerivativeZMajor(calculateZUcVc, mSymbolTable, x, y, z, dPdxX, dPdxY, dPdxZ,
+ dUdx, dVdx, Swizzle1(pRecipVar->deepCopy(), 2));
+ TransformDerivativeZMajor(calculateZUcVc, mSymbolTable, x, y, z, dPdyX, dPdyY, dPdyZ,
+ dUdy, dVdy, Swizzle1(pRecipVar->deepCopy(), 2));
+ }
+
+ // Create the if-else paths:
+ TIntermIfElse *calculateYZUcVc =
+ new TIntermIfElse(isYMajor, calculateYUcVc, calculateZUcVc);
+ TIntermBlock *calculateYZUcVcBlock = new TIntermBlock;
+ calculateYZUcVcBlock->appendStatement(calculateYZUcVc);
+ TIntermIfElse *calculateXYZUcVc =
+ new TIntermIfElse(isXMajor, calculateXUcVc, calculateYZUcVcBlock);
+ body->appendStatement(calculateXYZUcVc);
+
+ // u = (1 + uc/|ma|) / 2
+ // v = (1 + vc/|ma|) / 2
+ TIntermTyped *maTimesTwoRecip = new TIntermBinary(
+ EOpAssign, ma->deepCopy(),
+ new TIntermBinary(EOpDiv, CreateFloatNode(0.5f, EbpMedium), ma->deepCopy()));
+ body->appendStatement(maTimesTwoRecip);
+
+ TIntermTyped *ucDivMa = new TIntermBinary(EOpMul, uc, ma->deepCopy());
+ TIntermTyped *vcDivMa = new TIntermBinary(EOpMul, vc, ma->deepCopy());
+ TIntermTyped *uNormalized =
+ new TIntermBinary(EOpAdd, CreateFloatNode(0.5f, EbpMedium), ucDivMa);
+ TIntermTyped *vNormalized =
+ new TIntermBinary(EOpAdd, CreateFloatNode(0.5f, EbpMedium), vcDivMa);
+
+ body->appendStatement(new TIntermBinary(EOpAssign, uc->deepCopy(), uNormalized));
+ body->appendStatement(new TIntermBinary(EOpAssign, vc->deepCopy(), vNormalized));
+
+ TIntermSequence argsDUVdx = {dUdx, dVdx};
+ TIntermTyped *dUVdxValue = TIntermAggregate::CreateConstructor(*vec2Type, &argsDUVdx);
+
+ TIntermSequence argsDUVdy = {dUdy, dVdy};
+ TIntermTyped *dUVdyValue = TIntermAggregate::CreateConstructor(*vec2Type, &argsDUVdy);
+
+ body->appendStatement(new TIntermBinary(EOpAssign, dUVdx, dUVdxValue));
+ body->appendStatement(new TIntermBinary(EOpAssign, dUVdy, dUVdyValue));
+
+ // return vec3(u, v, l)
+ TIntermSequence argsUVL = {uc->deepCopy(), vc->deepCopy(), l};
+ TIntermBranch *returnStatement =
+ new TIntermBranch(EOpReturn, TIntermAggregate::CreateConstructor(*vec3Type, &argsUVL));
+ body->appendStatement(returnStatement);
+
+ TFunction *function;
+ function = new TFunction(mSymbolTable, name, SymbolType::AngleInternal, vec3Type, true);
+ function->addParameter(pVar);
+ function->addParameter(dPdxVar);
+ function->addParameter(dPdyVar);
+ function->addParameter(dUVdxVar);
+ function->addParameter(dUVdyVar);
+
+ *functionOut = function;
+
+ *declOut = CreateInternalFunctionDefinitionNode(*function, body);
+ }
+
+ TIntermTyped *createCoordTransformationCall(TIntermTyped *P,
+ TIntermTyped *dPdx,
+ TIntermTyped *dPdy,
+ TIntermTyped *dUVdx,
+ TIntermTyped *dUVdy)
+ {
+ TIntermSequence args = {P, dPdx, dPdy, dUVdx, dUVdy};
+ return TIntermAggregate::CreateFunctionCall(*mCubeXYZToArrayUVL, &args);
+ }
+
+ TIntermTyped *createImplicitCoordTransformationCall(TIntermTyped *P,
+ TIntermTyped *dUVdx,
+ TIntermTyped *dUVdy)
+ {
+ const TType *vec3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3>();
+ TIntermTyped *dPdx = CreateZeroNode(*vec3Type);
+ TIntermTyped *dPdy = CreateZeroNode(*vec3Type);
+ TIntermSequence args = {P, dPdx, dPdy, dUVdx, dUVdy};
+ return TIntermAggregate::CreateFunctionCall(*mCubeXYZToArrayUVLImplicit, &args);
+ }
+
+ TIntermTyped *getMappedSamplerExpression(TIntermNode *samplerCubeExpression)
+ {
+ // The argument passed to a function can either be the sampler, if not array, or a subscript
+ // into the sampler array.
+ TIntermSymbol *asSymbol = samplerCubeExpression->getAsSymbolNode();
+ TIntermBinary *asBinary = samplerCubeExpression->getAsBinaryNode();
+
+ if (asBinary)
+ {
+ // Only constant indexing is supported in ES2.0.
+ ASSERT(asBinary->getOp() == EOpIndexDirect);
+ asSymbol = asBinary->getLeft()->getAsSymbolNode();
+ }
+
+ // Arrays of arrays are not available in ES2.0.
+ ASSERT(asSymbol != nullptr);
+ const TVariable *samplerCubeVar = &asSymbol->variable();
+
+ ASSERT(mSamplerMap.find(samplerCubeVar) != mSamplerMap.end());
+ const TVariable *mappedSamplerVar = mSamplerMap.at(samplerCubeVar);
+
+ TIntermTyped *mappedExpression = new TIntermSymbol(mappedSamplerVar);
+ if (asBinary)
+ {
+ mappedExpression =
+ new TIntermBinary(asBinary->getOp(), mappedExpression, asBinary->getRight());
+ }
+
+ return mappedExpression;
+ }
+
+ bool convertBuiltinFunction(TIntermAggregate *node)
+ {
+ const TFunction *function = node->getFunction();
+ if (!function->name().beginsWith("textureCube"))
+ {
+ return false;
+ }
+
+ // All textureCube* functions are in the form:
+ //
+ // textureCube??(samplerCube, vec3, ??)
+ //
+ // They should be converted to:
+ //
+ // texture??(sampler2DArray, convertCoords(vec3), ??)
+ //
+ // We assume the target platform supports texture() functions (currently only used in
+ // Vulkan).
+ //
+ // The intrinsics map as follows:
+ //
+ // textureCube -> textureGrad
+ // textureCubeLod -> textureLod
+ // textureCubeLodEXT -> textureLod
+ // textureCubeGrad -> textureGrad
+ // textureCubeGradEXT -> textureGrad
+ //
+ // Note that dPdx and dPdy in textureCubeGrad* are vec3, while the textureGrad equivalent
+ // for sampler2DArray is vec2. The EXT_shader_texture_lod that introduces thid function
+ // says:
+ //
+ // > For the "Grad" functions, dPdx is the explicit derivative of P with respect
+ // > to window x, and similarly dPdy with respect to window y. ... For a cube map texture,
+ // > dPdx and dPdy are vec3.
+ // >
+ // > Let
+ // >
+ // > dSdx = dPdx.s;
+ // > dSdy = dPdy.s;
+ // > dTdx = dPdx.t;
+ // > dTdy = dPdy.t;
+ // >
+ // > and
+ // >
+ // > / 0.0; for two-dimensional texture
+ // > dRdx = (
+ // > \ dPdx.p; for cube map texture
+ // >
+ // > / 0.0; for two-dimensional texture
+ // > dRdy = (
+ // > \ dPdy.p; for cube map texture
+ // >
+ // > (See equation 3.12a in The OpenGL ES 2.0 Specification.)
+ //
+ // It's unclear to me what dRdx and dRdy are. EXT_gpu_shader4 that promotes this function
+ // has the following additional information:
+ //
+ // > For the "Cube" versions, the partial
+ // > derivatives ddx and ddy are assumed to be in the coordinate system used
+ // > before texture coordinates are projected onto the appropriate cube
+ // > face. The partial derivatives of the post-projection texture coordinates,
+ // > which are used for level-of-detail and anisotropic filtering
+ // > calculations, are derived from coord, ddx and ddy in an
+ // > implementation-dependent manner.
+ //
+ // The calculation of dPdx and dPdy is declared as implementation-dependent, so we have
+ // freedom to calculate it as fit, even if not precisely the same as hardware might.
+
+ const char *substituteFunctionName = "textureGrad";
+ bool isGrad = false;
+ bool isTranslatedGrad = true;
+ bool hasBias = false;
+ if (function->name().beginsWith("textureCubeLod"))
+ {
+ substituteFunctionName = "textureLod";
+ isTranslatedGrad = false;
+ }
+ else if (function->name().beginsWith("textureCubeGrad"))
+ {
+ isGrad = true;
+ }
+ else if (!mIsFragmentShader)
+ {
+ substituteFunctionName = "texture";
+ isTranslatedGrad = false;
+ }
+
+ TIntermSequence *arguments = node->getSequence();
+ ASSERT(arguments->size() >= 2);
+
+ const TType *vec2Type = StaticType::GetBasic<EbtFloat, EbpHigh, 2>();
+ const TType *vec3Type = StaticType::GetBasic<EbtFloat, EbpHigh, 3>();
+ TIntermSymbol *uvl = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec3Type));
+ TIntermSymbol *dUVdx = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec2Type));
+ TIntermSymbol *dUVdy = new TIntermSymbol(CreateTempVariable(mSymbolTable, vec2Type));
+
+ TIntermTyped *dPdx = nullptr;
+ TIntermTyped *dPdy = nullptr;
+ if (isGrad)
+ {
+ ASSERT(arguments->size() == 4);
+ dPdx = (*arguments)[2]->getAsTyped()->deepCopy();
+ dPdy = (*arguments)[3]->getAsTyped()->deepCopy();
+ }
+ else if (isTranslatedGrad && mIsFragmentShader && arguments->size() == 3)
+ {
+ hasBias = true;
+ }
+ else
+ {
+ dPdx = CreateZeroNode(*vec3Type);
+ dPdy = CreateZeroNode(*vec3Type);
+ }
+
+ if (isTranslatedGrad && !mIsFragmentShader)
+ {
+ substituteFunctionName = "texture";
+ isTranslatedGrad = false;
+ }
+
+ // The function call to transform the coordinates, dPdx and dPdy. If not textureCubeGrad,
+ // the driver compiler will optimize out the unnecessary calculations.
+ TIntermSequence coordTransform;
+ coordTransform.push_back(CreateTempDeclarationNode(&dUVdx->variable()));
+ coordTransform.push_back(CreateTempDeclarationNode(&dUVdy->variable()));
+ TIntermTyped *coordTransformCall;
+ if (isGrad || !isTranslatedGrad)
+ {
+ coordTransformCall = createCoordTransformationCall(
+ (*arguments)[1]->getAsTyped()->deepCopy(), dPdx, dPdy, dUVdx, dUVdy);
+ }
+ else
+ {
+ coordTransformCall = createImplicitCoordTransformationCall(
+ (*arguments)[1]->getAsTyped()->deepCopy(), dUVdx, dUVdy);
+ }
+ coordTransform.push_back(
+ CreateTempInitDeclarationNode(&uvl->variable(), coordTransformCall));
+
+ TIntermTyped *dUVdxArg = dUVdx;
+ TIntermTyped *dUVdyArg = dUVdy;
+ if (hasBias)
+ {
+ const TType *floatType = StaticType::GetBasic<EbtFloat, EbpHigh>();
+ TIntermTyped *bias = (*arguments)[2]->getAsTyped()->deepCopy();
+ TIntermSequence exp2Args = {bias};
+ TIntermTyped *exp2Call =
+ CreateBuiltInFunctionCallNode("exp2", &exp2Args, *mSymbolTable, 100);
+ TIntermSymbol *biasFac = new TIntermSymbol(CreateTempVariable(mSymbolTable, floatType));
+ coordTransform.push_back(CreateTempInitDeclarationNode(&biasFac->variable(), exp2Call));
+ dUVdxArg =
+ new TIntermBinary(EOpVectorTimesScalar, biasFac->deepCopy(), dUVdx->deepCopy());
+ dUVdyArg =
+ new TIntermBinary(EOpVectorTimesScalar, biasFac->deepCopy(), dUVdy->deepCopy());
+ }
+
+ insertStatementsInParentBlock(coordTransform);
+
+ TIntermSequence substituteArguments;
+ // Replace the first argument (samplerCube) with the sampler2DArray.
+ substituteArguments.push_back(getMappedSamplerExpression((*arguments)[0]));
+ // Replace the second argument with the coordination transformation.
+ substituteArguments.push_back(uvl->deepCopy());
+ if (isTranslatedGrad)
+ {
+ substituteArguments.push_back(dUVdxArg->deepCopy());
+ substituteArguments.push_back(dUVdyArg->deepCopy());
+ }
+ else
+ {
+ // Pass the rest of the parameters as is.
+ for (size_t argIndex = 2; argIndex < arguments->size(); ++argIndex)
+ {
+ substituteArguments.push_back((*arguments)[argIndex]->getAsTyped()->deepCopy());
+ }
+ }
+
+ TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
+ substituteFunctionName, &substituteArguments, *mSymbolTable, 300);
+
+ queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
+
+ return true;
+ }
+
+ // A map from the samplerCube variable to the sampler2DArray one.
+ angle::HashMap<const TVariable *, const TVariable *> mSamplerMap;
+
+ // A helper function to convert xyz coordinates passed to a cube map sampling function into the
+ // array layer (cube map face) and uv coordinates.
+ TFunction *mCubeXYZToArrayUVL;
+ // A specialized version of the same function which uses implicit derivatives.
+ TFunction *mCubeXYZToArrayUVLImplicit;
+
+ bool mIsFragmentShader;
+
+ // Stored to be put before the first function after the pass.
+ TIntermFunctionDefinition *mCoordTranslationFunctionDecl;
+ TIntermFunctionDefinition *mCoordTranslationFunctionImplicitDecl;
+};
+} // anonymous namespace
+
+bool RewriteCubeMapSamplersAs2DArray(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ bool isFragmentShader)
+{
+ RewriteCubeMapSamplersAs2DArrayTraverser traverser(symbolTable, isFragmentShader);
+ root->traverse(&traverser);
+
+ TIntermFunctionDefinition *coordTranslationFunctionDecl =
+ traverser.getCoordTranslationFunctionDecl();
+ TIntermFunctionDefinition *coordTranslationFunctionDeclImplicit =
+ traverser.getCoordTranslationFunctionDeclImplicit();
+ size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root);
+ if (coordTranslationFunctionDecl)
+ {
+ root->insertChildNodes(firstFunctionIndex, TIntermSequence({coordTranslationFunctionDecl}));
+ }
+ if (coordTranslationFunctionDeclImplicit)
+ {
+ root->insertChildNodes(firstFunctionIndex,
+ TIntermSequence({coordTranslationFunctionDeclImplicit}));
+ }
+
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h
new file mode 100644
index 0000000000..515c1de103
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteCubeMapSamplersAs2DArray.h
@@ -0,0 +1,29 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteCubeMapSamplersAs2DArray: Change samplerCube samplers to sampler2DArray, and the
+// textureCube* function calls to calls to helper functions that select the cube map face and sample
+// from the face as a 2D texture. This emulates seamful cube map sampling in ES2 (or desktop GL 2)
+// and therefore only looks at samplerCube (i.e. not integer variants or cube arrays) and sampling
+// functions that are defined in ES GLSL 1.0 (i.e. not the cube overload of texture()).
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITECUBEMAPSAMPLERSAS2DARRAY_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITECUBEMAPSAMPLERSAS2DARRAY_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteCubeMapSamplersAs2DArray(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ bool isFragmentShader);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITECUBEMAPSAMPLERSAS2DARRAY_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp
new file mode 100644
index 0000000000..1a2aabf7dc
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.cpp
@@ -0,0 +1,141 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Implementation of dFdy viewport transformation.
+// See header for more info.
+
+#include "compiler/translator/tree_ops/RewriteDfdy.h"
+
+#include "common/angleutils.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/TranslatorVulkan.h"
+#include "compiler/translator/tree_util/DriverUniform.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/SpecializationConstant.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class Traverser : public TIntermTraverser
+{
+ public:
+ Traverser(TSymbolTable *symbolTable, SpecConst *specConst, const DriverUniform *driverUniforms);
+
+ private:
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+
+ SpecConst *mSpecConst = nullptr;
+ const DriverUniform *mDriverUniforms = nullptr;
+};
+
+Traverser::Traverser(TSymbolTable *symbolTable,
+ SpecConst *specConst,
+ const DriverUniform *driverUniforms)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mSpecConst(specConst),
+ mDriverUniforms(driverUniforms)
+{}
+
+bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ // Decide if the node represents a call to dFdx() or dFdy()
+ if (node->getOp() != EOpDFdx && node->getOp() != EOpDFdy)
+ {
+ return true;
+ }
+
+ const bool isDFdx = node->getOp() == EOpDFdx;
+
+ // Two transformations are done on dFdx and dFdy:
+ //
+ // - If pre-rotation is applied, dFdx and dFdy may need to swap their axis based on the degree
+ // of rotation. dFdx becomes dFdy if rotation is 90 or 270 degrees. Similarly, dFdy becomes
+ // dFdx.
+ // - The result is potentially negated. This could be due to viewport y-flip or pre-rotation.
+ //
+ // Accordingly, there are two variables controlling the above transformations:
+ //
+ // - Rotation: A vec2 that is either (0, 1) or (1, 0). dFdx and dFdy are replaced with:
+ //
+ // dFdx * Rotation.x + dFdy * Rotation.y
+ //
+ // - Scale: A vec2 with -1 or 1 for either x or y components. The previous result is multiplied
+ // by this.
+ //
+ // Together, the above operations account for the combinations of 4 possible rotations and
+ // y-flip.
+
+ // Get the results of dFdx(operand) and dFdy(operand), and multiply them by the swizzles
+ TIntermTyped *operand = node->getChildNode(0)->getAsTyped();
+
+ TIntermTyped *dFdx = CreateBuiltInUnaryFunctionCallNode("dFdx", operand, *mSymbolTable, 300);
+ TIntermTyped *dFdy =
+ CreateBuiltInUnaryFunctionCallNode("dFdy", operand->deepCopy(), *mSymbolTable, 300);
+
+ // Get rotation multiplier
+ TIntermTyped *swapXY = mSpecConst->getSwapXY();
+ if (swapXY == nullptr)
+ {
+ swapXY = mDriverUniforms->getSwapXY();
+ }
+
+ TIntermTyped *swapXMultiplier = MakeSwapXMultiplier(swapXY);
+ TIntermTyped *swapYMultiplier = MakeSwapYMultiplier(swapXY->deepCopy());
+
+ // Get flip multiplier
+ TIntermTyped *flipXY = mDriverUniforms->getFlipXY(mSymbolTable, DriverUniformFlip::Fragment);
+
+ // Multiply the flip and rotation multipliers
+ TIntermTyped *xMultiplier =
+ new TIntermBinary(EOpMul, isDFdx ? swapXMultiplier : swapYMultiplier,
+ (new TIntermSwizzle(flipXY->deepCopy(), {0}))->fold(nullptr));
+ TIntermTyped *yMultiplier =
+ new TIntermBinary(EOpMul, isDFdx ? swapYMultiplier : swapXMultiplier,
+ (new TIntermSwizzle(flipXY->deepCopy(), {1}))->fold(nullptr));
+
+ const TOperator mulOp = dFdx->getType().isVector() ? EOpVectorTimesScalar : EOpMul;
+ TIntermTyped *rotatedFlippedDfdx = new TIntermBinary(mulOp, dFdx, xMultiplier);
+ TIntermTyped *rotatedFlippedDfdy = new TIntermBinary(mulOp, dFdy, yMultiplier);
+
+ // Sum them together into the result
+ TIntermBinary *rotatedFlippedResult =
+ new TIntermBinary(EOpAdd, rotatedFlippedDfdx, rotatedFlippedDfdy);
+
+ // Replace the old dFdx() or dFdy() node with the new node that contains the corrected value
+ //
+ // Note the following bugs (anglebug.com/7346):
+ //
+ // - Side effects of operand are duplicated with the above
+ // - If the direct child of this node is itself dFdx/y, its queueReplacement will not be
+ // effective as the parent is also replaced.
+ queueReplacement(rotatedFlippedResult, OriginalNode::IS_DROPPED);
+
+ return true;
+}
+} // anonymous namespace
+
+bool RewriteDfdy(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ int shaderVersion,
+ SpecConst *specConst,
+ const DriverUniform *driverUniforms)
+{
+ // dFdx/dFdy is only valid in GLSL 3.0 and later.
+ if (shaderVersion < 300)
+ {
+ return true;
+ }
+
+ Traverser traverser(symbolTable, specConst, driverUniforms);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h
new file mode 100644
index 0000000000..d1a6399696
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteDfdy.h
@@ -0,0 +1,32 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteDfdy: Transform dFdx and dFdy according to pre-rotation and viewport y-flip.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITEDFDY_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITEDFDY_H_
+
+#include "common/angleutils.h"
+#include "compiler/translator/Compiler.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+class SpecConst;
+class DriverUniform;
+
+[[nodiscard]] bool RewriteDfdy(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ int shaderVersion,
+ SpecConst *specConst,
+ const DriverUniform *driverUniforms);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITEDFDY_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp
new file mode 100644
index 0000000000..1c86cafe03
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.cpp
@@ -0,0 +1,861 @@
+//
+// Copyright 2022 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/RewritePixelLocalStorage.h"
+
+#include "common/angleutils.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_ops/MonomorphizeUnsupportedFunctions.h"
+#include "compiler/translator/tree_util/BuiltIn.h"
+#include "compiler/translator/tree_util/FindMain.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+constexpr static TBasicType DataTypeOfPLSType(TBasicType plsType)
+{
+ switch (plsType)
+ {
+ case EbtPixelLocalANGLE:
+ return EbtFloat;
+ case EbtIPixelLocalANGLE:
+ return EbtInt;
+ case EbtUPixelLocalANGLE:
+ return EbtUInt;
+ default:
+ UNREACHABLE();
+ return EbtVoid;
+ }
+}
+
+constexpr static TBasicType DataTypeOfImageType(TBasicType imageType)
+{
+ switch (imageType)
+ {
+ case EbtImage2D:
+ return EbtFloat;
+ case EbtIImage2D:
+ return EbtInt;
+ case EbtUImage2D:
+ return EbtUInt;
+ default:
+ UNREACHABLE();
+ return EbtVoid;
+ }
+}
+
+// Maps PLS symbols to a backing store.
+template <typename T>
+class PLSBackingStoreMap
+{
+ public:
+ // Sets the given variable as the backing storage for the plsSymbol's binding point. An entry
+ // must not already exist in the map for this binding point.
+ void insertNew(TIntermSymbol *plsSymbol, const T &backingStore)
+ {
+ ASSERT(plsSymbol);
+ ASSERT(IsPixelLocal(plsSymbol->getBasicType()));
+ int binding = plsSymbol->getType().getLayoutQualifier().binding;
+ ASSERT(binding >= 0);
+ auto result = mMap.insert({binding, backingStore});
+ ASSERT(result.second); // Ensure an image didn't already exist for this symbol.
+ }
+
+ // Looks up the backing store for the given plsSymbol's binding point. An entry must already
+ // exist in the map for this binding point.
+ const T &find(TIntermSymbol *plsSymbol)
+ {
+ ASSERT(plsSymbol);
+ ASSERT(IsPixelLocal(plsSymbol->getBasicType()));
+ int binding = plsSymbol->getType().getLayoutQualifier().binding;
+ ASSERT(binding >= 0);
+ auto iter = mMap.find(binding);
+ ASSERT(iter != mMap.end()); // Ensure PLSImages already exist for this symbol.
+ return iter->second;
+ }
+
+ const std::map<int, T> &bindingOrderedMap() const { return mMap; }
+
+ private:
+ // Use std::map so the backing stores are ordered by binding when we iterate.
+ std::map<int, T> mMap;
+};
+
+// Base class for rewriting high level PLS operations to AST operations specified by
+// ShPixelLocalStorageType.
+class RewritePLSTraverser : public TIntermTraverser
+{
+ public:
+ RewritePLSTraverser(TCompiler *compiler,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ int shaderVersion)
+ : TIntermTraverser(true, false, false, &symbolTable),
+ mCompiler(compiler),
+ mCompileOptions(&compileOptions),
+ mShaderVersion(shaderVersion)
+ {}
+
+ bool visitDeclaration(Visit, TIntermDeclaration *decl) override
+ {
+ TIntermTyped *declVariable = (decl->getSequence())->front()->getAsTyped();
+ ASSERT(declVariable);
+
+ if (!IsPixelLocal(declVariable->getBasicType()))
+ {
+ return true;
+ }
+
+ // PLS is not allowed in arrays.
+ ASSERT(!declVariable->isArray());
+
+ // This visitDeclaration doesn't get called for function arguments, and opaque types can
+ // otherwise only be uniforms.
+ ASSERT(declVariable->getQualifier() == EvqUniform);
+
+ TIntermSymbol *plsSymbol = declVariable->getAsSymbolNode();
+ ASSERT(plsSymbol);
+
+ visitPLSDeclaration(plsSymbol);
+
+ return false;
+ }
+
+ bool visitAggregate(Visit, TIntermAggregate *aggregate) override
+ {
+ if (!BuiltInGroup::IsPixelLocal(aggregate->getOp()))
+ {
+ return true;
+ }
+
+ const TIntermSequence &args = *aggregate->getSequence();
+ ASSERT(args.size() >= 1);
+ TIntermSymbol *plsSymbol = args[0]->getAsSymbolNode();
+
+ // Rewrite pixelLocalLoadANGLE -> imageLoad.
+ if (aggregate->getOp() == EOpPixelLocalLoadANGLE)
+ {
+ visitPLSLoad(plsSymbol);
+ return false; // No need to recurse since this node is being dropped.
+ }
+
+ // Rewrite pixelLocalStoreANGLE -> imageStore.
+ if (aggregate->getOp() == EOpPixelLocalStoreANGLE)
+ {
+ // Also hoist the 'value' expression into a temp. In the event of
+ // "pixelLocalStoreANGLE(..., pixelLocalLoadANGLE(...))", this ensures the load occurs
+ // _before_ any potential barriers required by the subclass.
+ //
+ // NOTE: It is generally unsafe to hoist function arguments due to short circuiting,
+ // e.g., "if (false && function(...))", but pixelLocalStoreANGLE returns type void, so
+ // it is safe in this particular case.
+ TType *valueType = new TType(DataTypeOfPLSType(plsSymbol->getBasicType()),
+ plsSymbol->getPrecision(), EvqTemporary, 4);
+ TVariable *valueVar = CreateTempVariable(mSymbolTable, valueType);
+ TIntermDeclaration *valueDecl =
+ CreateTempInitDeclarationNode(valueVar, args[1]->getAsTyped());
+ valueDecl->traverse(this); // Rewrite any potential pixelLocalLoadANGLEs in valueDecl.
+ insertStatementInParentBlock(valueDecl);
+
+ visitPLSStore(plsSymbol, valueVar);
+ return false; // No need to recurse since this node is being dropped.
+ }
+
+ return true;
+ }
+
+ // Called after rewrite. Injects one-time setup code that needs to run before any PLS accesses.
+ virtual void injectSetupCode(TCompiler *,
+ TSymbolTable &,
+ const ShCompileOptions &,
+ TIntermBlock *mainBody,
+ size_t plsBeginPosition)
+ {}
+
+ // Called after rewrite. Injects one-time finalization code that needs to run after all PLS.
+ virtual void injectFinalizeCode(TCompiler *,
+ TSymbolTable &,
+ const ShCompileOptions &,
+ TIntermBlock *mainBody,
+ size_t plsEndPosition)
+ {}
+
+ TVariable *globalPixelCoord() const { return mGlobalPixelCoord; }
+
+ protected:
+ virtual void visitPLSDeclaration(TIntermSymbol *plsSymbol) = 0;
+ virtual void visitPLSLoad(TIntermSymbol *plsSymbol) = 0;
+ virtual void visitPLSStore(TIntermSymbol *plsSymbol, TVariable *value) = 0;
+
+ void ensureGlobalPixelCoordDeclared()
+ {
+ // Insert a global to hold the pixel coordinate as soon as we see PLS declared. This will be
+ // initialized at the beginning of main().
+ if (!mGlobalPixelCoord)
+ {
+ TType *coordType = new TType(EbtInt, EbpHigh, EvqGlobal, 2);
+ mGlobalPixelCoord = CreateTempVariable(mSymbolTable, coordType);
+ insertStatementInParentBlock(CreateTempDeclarationNode(mGlobalPixelCoord));
+ }
+ }
+
+ const TCompiler *const mCompiler;
+ const ShCompileOptions *const mCompileOptions;
+ const int mShaderVersion;
+
+ // Stores the shader invocation's pixel coordinate as "ivec2(floor(gl_FragCoord.xy))".
+ TVariable *mGlobalPixelCoord = nullptr;
+};
+
+// Rewrites high level PLS operations to shader image operations.
+class RewritePLSToImagesTraverser : public RewritePLSTraverser
+{
+ public:
+ RewritePLSToImagesTraverser(TCompiler *compiler,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ int shaderVersion)
+ : RewritePLSTraverser(compiler, symbolTable, compileOptions, shaderVersion)
+ {}
+
+ private:
+ void visitPLSDeclaration(TIntermSymbol *plsSymbol) override
+ {
+ // Replace the PLS declaration with an image2D.
+ ensureGlobalPixelCoordDeclared();
+ TVariable *image2D = createPLSImageReplacement(plsSymbol);
+ mImages.insertNew(plsSymbol, image2D);
+ queueReplacement(new TIntermDeclaration({new TIntermSymbol(image2D)}),
+ OriginalNode::IS_DROPPED);
+ }
+
+ // Do all PLS formats need to be packed into r32f, r32i, or r32ui image2Ds?
+ bool needsR32Packing() const
+ {
+ return mCompileOptions->pls.type == ShPixelLocalStorageType::ImageStoreR32PackedFormats;
+ }
+
+ // Creates an image2D that replaces a pixel local storage handle.
+ TVariable *createPLSImageReplacement(const TIntermSymbol *plsSymbol)
+ {
+ ASSERT(plsSymbol);
+ ASSERT(IsPixelLocal(plsSymbol->getBasicType()));
+
+ TType *imageType = new TType(plsSymbol->getType());
+
+ TLayoutQualifier layoutQualifier = imageType->getLayoutQualifier();
+ switch (layoutQualifier.imageInternalFormat)
+ {
+ case TLayoutImageInternalFormat::EiifRGBA8:
+ if (needsR32Packing())
+ {
+ layoutQualifier.imageInternalFormat = EiifR32UI;
+ imageType->setPrecision(EbpHigh);
+ imageType->setBasicType(EbtUImage2D);
+ }
+ else
+ {
+ imageType->setBasicType(EbtImage2D);
+ }
+ break;
+ case TLayoutImageInternalFormat::EiifRGBA8I:
+ if (needsR32Packing())
+ {
+ layoutQualifier.imageInternalFormat = EiifR32I;
+ imageType->setPrecision(EbpHigh);
+ }
+ imageType->setBasicType(EbtIImage2D);
+ break;
+ case TLayoutImageInternalFormat::EiifRGBA8UI:
+ if (needsR32Packing())
+ {
+ layoutQualifier.imageInternalFormat = EiifR32UI;
+ imageType->setPrecision(EbpHigh);
+ }
+ imageType->setBasicType(EbtUImage2D);
+ break;
+ case TLayoutImageInternalFormat::EiifR32F:
+ imageType->setBasicType(EbtImage2D);
+ break;
+ case TLayoutImageInternalFormat::EiifR32UI:
+ imageType->setBasicType(EbtUImage2D);
+ break;
+ default:
+ UNREACHABLE();
+ }
+ layoutQualifier.rasterOrdered = mCompileOptions->pls.fragmentSynchronizationType ==
+ ShFragmentSynchronizationType::RasterizerOrderViews_D3D;
+ imageType->setLayoutQualifier(layoutQualifier);
+
+ TMemoryQualifier memoryQualifier{};
+ memoryQualifier.coherent = true;
+ memoryQualifier.restrictQualifier = true;
+ memoryQualifier.volatileQualifier = false;
+ // TODO(anglebug.com/7279): Maybe we could walk the tree first and see which PLS is used
+ // how. If the PLS is never loaded, we could add a writeonly qualifier, for example.
+ memoryQualifier.readonly = false;
+ memoryQualifier.writeonly = false;
+ imageType->setMemoryQualifier(memoryQualifier);
+
+ const TVariable &plsVar = plsSymbol->variable();
+ return new TVariable(plsVar.uniqueId(), plsVar.name(), plsVar.symbolType(),
+ plsVar.extensions(), imageType);
+ }
+
+ void visitPLSLoad(TIntermSymbol *plsSymbol) override
+ {
+ // Replace the pixelLocalLoadANGLE with imageLoad.
+ TVariable *image2D = mImages.find(plsSymbol);
+ ASSERT(mGlobalPixelCoord);
+ TIntermTyped *pls = CreateBuiltInFunctionCallNode(
+ "imageLoad", {new TIntermSymbol(image2D), new TIntermSymbol(mGlobalPixelCoord)},
+ *mSymbolTable, 310);
+ pls = unpackImageDataIfNecessary(pls, plsSymbol, image2D);
+ queueReplacement(pls, OriginalNode::IS_DROPPED);
+ }
+
+ // Unpacks the raw PLS data if the output shader language needs r32* packing.
+ TIntermTyped *unpackImageDataIfNecessary(TIntermTyped *data,
+ TIntermSymbol *plsSymbol,
+ TVariable *image2D)
+ {
+ TLayoutImageInternalFormat plsFormat =
+ plsSymbol->getType().getLayoutQualifier().imageInternalFormat;
+ TLayoutImageInternalFormat imageFormat =
+ image2D->getType().getLayoutQualifier().imageInternalFormat;
+ if (plsFormat == imageFormat)
+ {
+ return data; // This PLS storage isn't packed.
+ }
+ ASSERT(needsR32Packing());
+ switch (plsFormat)
+ {
+ case EiifRGBA8:
+ // Unpack and normalize r,g,b,a from a single 32-bit unsigned int:
+ //
+ // unpackUnorm4x8(data.r)
+ //
+ data = CreateBuiltInFunctionCallNode("unpackUnorm4x8", {CreateSwizzle(data, 0)},
+ *mSymbolTable, 310);
+ break;
+ case EiifRGBA8I:
+ case EiifRGBA8UI:
+ {
+ constexpr unsigned shifts[] = {24, 16, 8, 0};
+ // Unpack r,g,b,a form a single (signed or unsigned) 32-bit int. Shift left,
+ // then right, to preserve the sign for ints. (highp integers are exactly
+ // 32-bit, two's compliment.)
+ //
+ // data.rrrr << uvec4(24, 16, 8, 0) >> 24u
+ //
+ data = CreateSwizzle(data, 0, 0, 0, 0);
+ data = new TIntermBinary(EOpBitShiftLeft, data, CreateUVecNode(shifts, 4, EbpHigh));
+ data = new TIntermBinary(EOpBitShiftRight, data, CreateUIntNode(24));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ return data;
+ }
+
+ void visitPLSStore(TIntermSymbol *plsSymbol, TVariable *value) override
+ {
+ TVariable *image2D = mImages.find(plsSymbol);
+ TIntermTyped *packedData = clampAndPackPLSDataIfNecessary(value, plsSymbol, image2D);
+
+ // Surround the store with memoryBarrierImage calls in order to ensure dependent stores and
+ // loads in a single shader invocation are coherent. From the ES 3.1 spec:
+ //
+ // Using variables declared as "coherent" guarantees only that the results of stores will
+ // be immediately visible to shader invocations using similarly-declared variables;
+ // calling MemoryBarrier is required to ensure that the stores are visible to other
+ // operations.
+ //
+ insertStatementsInParentBlock(
+ {CreateBuiltInFunctionCallNode("memoryBarrierImage", {}, *mSymbolTable,
+ 310)}, // Before.
+ {CreateBuiltInFunctionCallNode("memoryBarrierImage", {}, *mSymbolTable,
+ 310)}); // After.
+
+ // Rewrite the pixelLocalStoreANGLE with imageStore.
+ ASSERT(mGlobalPixelCoord);
+ queueReplacement(
+ CreateBuiltInFunctionCallNode(
+ "imageStore",
+ {new TIntermSymbol(image2D), new TIntermSymbol(mGlobalPixelCoord), packedData},
+ *mSymbolTable, 310),
+ OriginalNode::IS_DROPPED);
+ }
+
+ // Packs the PLS to raw data if the output shader language needs r32* packing.
+ TIntermTyped *clampAndPackPLSDataIfNecessary(TVariable *plsVar,
+ TIntermSymbol *plsSymbol,
+ TVariable *image2D)
+ {
+ TLayoutImageInternalFormat plsFormat =
+ plsSymbol->getType().getLayoutQualifier().imageInternalFormat;
+ // anglebug.com/7524: Storing to integer formats with values larger than can be represented
+ // is specified differently on different APIs. Clamp integer formats here to make it uniform
+ // and more GL-like.
+ switch (plsFormat)
+ {
+ case EiifRGBA8I:
+ {
+ // Clamp r,g,b,a to their min/max 8-bit values:
+ //
+ // plsVar = clamp(plsVar, -128, 127) & 0xff
+ //
+ TIntermTyped *newPLSValue = CreateBuiltInFunctionCallNode(
+ "clamp",
+ {new TIntermSymbol(plsVar), CreateIndexNode(-128), CreateIndexNode(127)},
+ *mSymbolTable, mShaderVersion);
+ insertStatementInParentBlock(CreateTempAssignmentNode(plsVar, newPLSValue));
+ break;
+ }
+ case EiifRGBA8UI:
+ {
+ // Clamp r,g,b,a to their max 8-bit values:
+ //
+ // plsVar = min(plsVar, 255)
+ //
+ TIntermTyped *newPLSValue = CreateBuiltInFunctionCallNode(
+ "min", {new TIntermSymbol(plsVar), CreateUIntNode(255)}, *mSymbolTable,
+ mShaderVersion);
+ insertStatementInParentBlock(CreateTempAssignmentNode(plsVar, newPLSValue));
+ break;
+ }
+ default:
+ break;
+ }
+ TIntermTyped *result = new TIntermSymbol(plsVar);
+ TLayoutImageInternalFormat imageFormat =
+ image2D->getType().getLayoutQualifier().imageInternalFormat;
+ if (plsFormat == imageFormat)
+ {
+ return result; // This PLS storage isn't packed.
+ }
+ ASSERT(needsR32Packing());
+ switch (plsFormat)
+ {
+ case EiifRGBA8:
+ {
+ if (mCompileOptions->passHighpToPackUnormSnormBuiltins)
+ {
+ // anglebug.com/7527: unpackUnorm4x8 doesn't work on Pixel 4 when passed
+ // a mediump vec4. Use an intermediate highp vec4.
+ //
+ // It's safe to inject a variable here because it happens right before
+ // pixelLocalStoreANGLE, which returns type void. (See visitAggregate.)
+ TType *highpType = new TType(EbtFloat, EbpHigh, EvqTemporary, 4);
+ TVariable *workaroundHighpVar = CreateTempVariable(mSymbolTable, highpType);
+ insertStatementInParentBlock(
+ CreateTempInitDeclarationNode(workaroundHighpVar, result));
+ result = new TIntermSymbol(workaroundHighpVar);
+ }
+
+ // Denormalize and pack r,g,b,a into a single 32-bit unsigned int:
+ //
+ // packUnorm4x8(workaroundHighpVar)
+ //
+ result =
+ CreateBuiltInFunctionCallNode("packUnorm4x8", {result}, *mSymbolTable, 310);
+ break;
+ }
+ case EiifRGBA8I:
+ case EiifRGBA8UI:
+ {
+ if (plsFormat == EiifRGBA8I)
+ {
+ // Mask off extra sign bits beyond 8.
+ //
+ // plsVar &= 0xff
+ //
+ insertStatementInParentBlock(new TIntermBinary(
+ EOpBitwiseAndAssign, new TIntermSymbol(plsVar), CreateIndexNode(0xff)));
+ }
+ // Pack r,g,b,a into a single 32-bit (signed or unsigned) int:
+ //
+ // r | (g << 8) | (b << 16) | (a << 24)
+ //
+ auto shiftComponent = [=](int componentIdx) {
+ return new TIntermBinary(EOpBitShiftLeft,
+ CreateSwizzle(new TIntermSymbol(plsVar), componentIdx),
+ CreateUIntNode(componentIdx * 8));
+ };
+ result = CreateSwizzle(result, 0);
+ result = new TIntermBinary(EOpBitwiseOr, result, shiftComponent(1));
+ result = new TIntermBinary(EOpBitwiseOr, result, shiftComponent(2));
+ result = new TIntermBinary(EOpBitwiseOr, result, shiftComponent(3));
+ break;
+ }
+ default:
+ UNREACHABLE();
+ }
+ // Convert the packed data to a {u,i}vec4 for imageStore.
+ TType imageStoreType(DataTypeOfImageType(image2D->getType().getBasicType()), 4);
+ return TIntermAggregate::CreateConstructor(imageStoreType, {result});
+ }
+
+ void injectSetupCode(TCompiler *compiler,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ TIntermBlock *mainBody,
+ size_t plsBeginPosition) override
+ {
+ // When PLS is implemented with images, early_fragment_tests ensure that depth/stencil
+ // can also block stores to PLS.
+ compiler->specifyEarlyFragmentTests();
+
+ // Delimit the beginning of a per-pixel critical section, if supported. This makes pixel
+ // local storage coherent.
+ //
+ // Either: GL_NV_fragment_shader_interlock
+ // GL_INTEL_fragment_shader_ordering
+ // GL_ARB_fragment_shader_interlock (may compile to
+ // SPV_EXT_fragment_shader_interlock)
+ switch (compileOptions.pls.fragmentSynchronizationType)
+ {
+ // ROVs don't need explicit synchronization calls.
+ case ShFragmentSynchronizationType::RasterizerOrderViews_D3D:
+ case ShFragmentSynchronizationType::NotSupported:
+ break;
+ case ShFragmentSynchronizationType::FragmentShaderInterlock_NV_GL:
+ mainBody->insertStatement(
+ plsBeginPosition,
+ CreateBuiltInFunctionCallNode("beginInvocationInterlockNV", {}, symbolTable,
+ kESSLInternalBackendBuiltIns));
+ break;
+ case ShFragmentSynchronizationType::FragmentShaderOrdering_INTEL_GL:
+ mainBody->insertStatement(
+ plsBeginPosition,
+ CreateBuiltInFunctionCallNode("beginFragmentShaderOrderingINTEL", {},
+ symbolTable, kESSLInternalBackendBuiltIns));
+ break;
+ case ShFragmentSynchronizationType::FragmentShaderInterlock_ARB_GL:
+ mainBody->insertStatement(
+ plsBeginPosition,
+ CreateBuiltInFunctionCallNode("beginInvocationInterlockARB", {}, symbolTable,
+ kESSLInternalBackendBuiltIns));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ void injectFinalizeCode(TCompiler *,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ TIntermBlock *mainBody,
+ size_t plsEndPosition) override
+ {
+ // Delimit the end of the PLS critical section, if required.
+ //
+ // Either: GL_NV_fragment_shader_interlock
+ // GL_ARB_fragment_shader_interlock (may compile to
+ // SPV_EXT_fragment_shader_interlock)
+ switch (compileOptions.pls.fragmentSynchronizationType)
+ {
+ // ROVs don't need explicit synchronization calls.
+ case ShFragmentSynchronizationType::RasterizerOrderViews_D3D:
+ // GL_INTEL_fragment_shader_ordering doesn't have an "end()" call.
+ case ShFragmentSynchronizationType::FragmentShaderOrdering_INTEL_GL:
+ case ShFragmentSynchronizationType::NotSupported:
+ break;
+ case ShFragmentSynchronizationType::FragmentShaderInterlock_NV_GL:
+
+ mainBody->insertStatement(
+ plsEndPosition,
+ CreateBuiltInFunctionCallNode("endInvocationInterlockNV", {}, symbolTable,
+ kESSLInternalBackendBuiltIns));
+ break;
+ case ShFragmentSynchronizationType::FragmentShaderInterlock_ARB_GL:
+ mainBody->insertStatement(
+ plsEndPosition,
+ CreateBuiltInFunctionCallNode("endInvocationInterlockARB", {}, symbolTable,
+ kESSLInternalBackendBuiltIns));
+ break;
+ default:
+ UNREACHABLE();
+ }
+ }
+
+ PLSBackingStoreMap<TVariable *> mImages;
+};
+
+// Rewrites high level PLS operations to framebuffer fetch operations.
+class RewritePLSToFramebufferFetchTraverser : public RewritePLSTraverser
+{
+ public:
+ RewritePLSToFramebufferFetchTraverser(TCompiler *compiler,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ int shaderVersion)
+ : RewritePLSTraverser(compiler, symbolTable, compileOptions, shaderVersion)
+ {}
+
+ void visitPLSDeclaration(TIntermSymbol *plsSymbol) override
+ {
+ // Replace the PLS declaration with a framebuffer attachment.
+ PLSAttachment attachment(mCompiler, mSymbolTable, *mCompileOptions, plsSymbol->variable());
+ mPLSAttachments.insertNew(plsSymbol, attachment);
+ insertStatementInParentBlock(
+ new TIntermDeclaration({new TIntermSymbol(attachment.fragmentVar)}));
+ queueReplacement(CreateTempDeclarationNode(attachment.accessVar), OriginalNode::IS_DROPPED);
+ }
+
+ void visitPLSLoad(TIntermSymbol *plsSymbol) override
+ {
+ // Read our temporary accessVar.
+ const PLSAttachment &attachment = mPLSAttachments.find(plsSymbol);
+ queueReplacement(attachment.expandAccessVar(), OriginalNode::IS_DROPPED);
+ }
+
+ void visitPLSStore(TIntermSymbol *plsSymbol, TVariable *value) override
+ {
+ // Set our temporary accessVar.
+ const PLSAttachment &attachment = mPLSAttachments.find(plsSymbol);
+ queueReplacement(CreateTempAssignmentNode(attachment.accessVar, attachment.swizzle(value)),
+ OriginalNode::IS_DROPPED);
+ }
+
+ void injectSetupCode(TCompiler *compiler,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ TIntermBlock *mainBody,
+ size_t plsBeginPosition) override
+ {
+ // [OpenGL ES Version 3.0.6, 3.9.2.3 "Shader Output"]: Any colors, or color components,
+ // associated with a fragment that are not written by the fragment shader are undefined.
+ //
+ // [EXT_shader_framebuffer_fetch]: Prior to fragment shading, fragment outputs declared
+ // inout are populated with the value last written to the framebuffer at the same(x, y,
+ // sample) position.
+ //
+ // It's unclear from the EXT_shader_framebuffer_fetch spec whether inout fragment variables
+ // become undefined if not explicitly written, but either way, when this compiles to subpass
+ // loads in Vulkan, we definitely get undefined behavior if PLS variables are not written.
+ //
+ // To make sure every PLS variable gets written, we read them all before PLS operations,
+ // then write them all back out after all PLS is complete.
+ std::vector<TIntermNode *> plsPreloads;
+ plsPreloads.reserve(mPLSAttachments.bindingOrderedMap().size());
+ for (const auto &entry : mPLSAttachments.bindingOrderedMap())
+ {
+ const PLSAttachment &attachment = entry.second;
+ plsPreloads.push_back(
+ CreateTempAssignmentNode(attachment.accessVar, attachment.swizzleFragmentVar()));
+ }
+ mainBody->getSequence()->insert(mainBody->getSequence()->begin() + plsBeginPosition,
+ plsPreloads.begin(), plsPreloads.end());
+ }
+
+ void injectFinalizeCode(TCompiler *,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ TIntermBlock *mainBody,
+ size_t plsEndPosition) override
+ {
+ std::vector<TIntermNode *> plsWrites;
+ plsWrites.reserve(mPLSAttachments.bindingOrderedMap().size());
+ for (const auto &entry : mPLSAttachments.bindingOrderedMap())
+ {
+ const PLSAttachment &attachment = entry.second;
+ plsWrites.push_back(new TIntermBinary(EOpAssign, attachment.swizzleFragmentVar(),
+ new TIntermSymbol(attachment.accessVar)));
+ }
+ mainBody->getSequence()->insert(mainBody->getSequence()->begin() + plsEndPosition,
+ plsWrites.begin(), plsWrites.end());
+ }
+
+ private:
+ struct PLSAttachment
+ {
+ PLSAttachment(const TCompiler *compiler,
+ TSymbolTable *symbolTable,
+ const ShCompileOptions &compileOptions,
+ const TVariable &plsVar)
+ {
+ const TType &plsType = plsVar.getType();
+
+ TType *accessVarType;
+ switch (plsType.getLayoutQualifier().imageInternalFormat)
+ {
+ default:
+ UNREACHABLE();
+ [[fallthrough]];
+ case EiifRGBA8:
+ accessVarType = new TType(EbtFloat, 4);
+ break;
+ case EiifRGBA8I:
+ accessVarType = new TType(EbtInt, 4);
+ break;
+ case EiifRGBA8UI:
+ accessVarType = new TType(EbtUInt, 4);
+ break;
+ case EiifR32F:
+ accessVarType = new TType(EbtFloat, 1);
+ break;
+ case EiifR32UI:
+ accessVarType = new TType(EbtUInt, 1);
+ break;
+ }
+ accessVarType->setPrecision(plsType.getPrecision());
+ accessVar = CreateTempVariable(symbolTable, accessVarType);
+
+ // Qualcomm seems to want fragment outputs to be 4-component vectors, and produces a
+ // compile error from "inout uint". Our Metal translator also saturates color outputs to
+ // 4 components. And since the spec also seems silent on how many components an output
+ // must have, we always use 4.
+ TType *fragmentVarType = new TType(accessVarType->getBasicType(), 4);
+ fragmentVarType->setPrecision(plsType.getPrecision());
+ fragmentVarType->setQualifier(EvqFragmentInOut);
+
+ // PLS attachments are bound in reverse order from the rear.
+ TLayoutQualifier layoutQualifier = TLayoutQualifier::Create();
+ layoutQualifier.location =
+ compiler->getResources().MaxCombinedDrawBuffersAndPixelLocalStoragePlanes -
+ plsType.getLayoutQualifier().binding - 1;
+ layoutQualifier.locationsSpecified = 1;
+ if (compileOptions.pls.fragmentSynchronizationType ==
+ ShFragmentSynchronizationType::NotSupported)
+ {
+ // We're using EXT_shader_framebuffer_fetch_non_coherent, which requires the
+ // "noncoherent" qualifier.
+ layoutQualifier.noncoherent = true;
+ }
+ fragmentVarType->setLayoutQualifier(layoutQualifier);
+
+ fragmentVar = new TVariable(plsVar.uniqueId(), plsVar.name(), plsVar.symbolType(),
+ plsVar.extensions(), fragmentVarType);
+ }
+
+ // Expands our accessVar to 4 components, regardless of the size of the pixel local storage
+ // internalformat.
+ TIntermTyped *expandAccessVar() const
+ {
+ TIntermTyped *expanded = new TIntermSymbol(accessVar);
+ if (accessVar->getType().getNominalSize() == 1)
+ {
+ switch (accessVar->getType().getBasicType())
+ {
+ case EbtFloat:
+ expanded = TIntermAggregate::CreateConstructor( // "vec4(r, 0, 0, 1)"
+ TType(EbtFloat, 4),
+ {expanded, CreateFloatNode(0, EbpHigh), CreateFloatNode(0, EbpHigh),
+ CreateFloatNode(1, EbpHigh)});
+ break;
+ case EbtUInt:
+ expanded = TIntermAggregate::CreateConstructor( // "uvec4(r, 0, 0, 1)"
+ TType(EbtUInt, 4),
+ {expanded, CreateUIntNode(0), CreateUIntNode(0), CreateUIntNode(1)});
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+ return expanded;
+ }
+
+ // Swizzles a variable down to the same number of components as the PLS internalformat.
+ TIntermTyped *swizzle(TVariable *var) const
+ {
+ TIntermTyped *swizzled = new TIntermSymbol(var);
+ if (var->getType().getNominalSize() != accessVar->getType().getNominalSize())
+ {
+ ASSERT(var->getType().getNominalSize() > accessVar->getType().getNominalSize());
+ TVector swizzleOffsets{0, 1, 2, 3};
+ swizzleOffsets.resize(accessVar->getType().getNominalSize());
+ swizzled = new TIntermSwizzle(swizzled, swizzleOffsets);
+ }
+ return swizzled;
+ }
+
+ TIntermTyped *swizzleFragmentVar() const { return swizzle(fragmentVar); }
+
+ TVariable *fragmentVar;
+ TVariable *accessVar;
+ };
+
+ PLSBackingStoreMap<PLSAttachment> mPLSAttachments;
+};
+} // anonymous namespace
+
+bool RewritePixelLocalStorage(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ int shaderVersion)
+{
+ // If any functions take PLS arguments, monomorphize the functions by removing said parameters
+ // and making the PLS calls from main() instead, using the global uniform from the call site
+ // instead of the function argument. This is necessary because function arguments don't carry
+ // the necessary "binding" or "format" layout qualifiers.
+ if (!MonomorphizeUnsupportedFunctions(
+ compiler, root, &symbolTable, compileOptions,
+ UnsupportedFunctionArgsBitSet{UnsupportedFunctionArgs::PixelLocalStorage}))
+ {
+ return false;
+ }
+
+ TIntermBlock *mainBody = FindMainBody(root);
+
+ std::unique_ptr<RewritePLSTraverser> traverser;
+ switch (compileOptions.pls.type)
+ {
+ case ShPixelLocalStorageType::ImageStoreR32PackedFormats:
+ case ShPixelLocalStorageType::ImageStoreNativeFormats:
+ traverser = std::make_unique<RewritePLSToImagesTraverser>(
+ compiler, symbolTable, compileOptions, shaderVersion);
+ break;
+ case ShPixelLocalStorageType::FramebufferFetch:
+ traverser = std::make_unique<RewritePLSToFramebufferFetchTraverser>(
+ compiler, symbolTable, compileOptions, shaderVersion);
+ break;
+ default:
+ UNREACHABLE();
+ return false;
+ }
+
+ // Rewrite PLS operations to image operations.
+ root->traverse(traverser.get());
+ if (!traverser->updateTree(compiler, root))
+ {
+ return false;
+ }
+
+ // Inject the code that needs to run before and after all PLS operations.
+ // TODO(anglebug.com/7279): Inject these functions in a tight critical section, instead of
+ // just locking the entire main() function:
+ // - Monomorphize all PLS calls into main().
+ // - Insert begin/end calls around the first/last PLS calls (and outside of flow control).
+ traverser->injectSetupCode(compiler, symbolTable, compileOptions, mainBody, 0);
+ traverser->injectFinalizeCode(compiler, symbolTable, compileOptions, mainBody,
+ mainBody->getChildCount());
+
+ if (traverser->globalPixelCoord())
+ {
+ // Initialize the global pixel coord at the beginning of main():
+ //
+ // pixelCoord = ivec2(floor(gl_FragCoord.xy));
+ //
+ TIntermTyped *exp;
+ exp = ReferenceBuiltInVariable(ImmutableString("gl_FragCoord"), symbolTable, shaderVersion);
+ exp = CreateSwizzle(exp, 0, 1);
+ exp = CreateBuiltInFunctionCallNode("floor", {exp}, symbolTable, shaderVersion);
+ exp = TIntermAggregate::CreateConstructor(TType(EbtInt, 2), {exp});
+ exp = CreateTempAssignmentNode(traverser->globalPixelCoord(), exp);
+ mainBody->insertStatement(0, exp);
+ }
+
+ return compiler->validateAST(root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h
new file mode 100644
index 0000000000..9bcfcbb62e
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewritePixelLocalStorage.h
@@ -0,0 +1,29 @@
+//
+// Copyright 2022 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITE_PIXELLOCALSTORAGE_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITE_PIXELLOCALSTORAGE_H_
+
+#include <GLSLANG/ShaderLang.h>
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+// This mutating tree traversal rewrites high level ANGLE_shader_pixel_local_storage operations to
+// the type of shader operations specified by ShPixelLocalStorageType, found in ShCompileOptions.
+[[nodiscard]] bool RewritePixelLocalStorage(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable &symbolTable,
+ const ShCompileOptions &compileOptions,
+ int shaderVersion);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITE_PIXELLOCALSTORAGE_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp
new file mode 100644
index 0000000000..219a6b31fc
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.cpp
@@ -0,0 +1,673 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteStructSamplers: Extract samplers from structs.
+//
+
+#include "compiler/translator/tree_ops/RewriteStructSamplers.h"
+
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+namespace
+{
+
+// Used to map one structure type to another (one where the samplers are removed).
+struct StructureData
+{
+ // The structure this was replaced with. If nullptr, it means the structure is removed (because
+ // it had all samplers).
+ const TStructure *modified;
+ // Indexed by the field index of original structure, to get the field index of the modified
+ // structure. For example:
+ //
+ // struct Original
+ // {
+ // sampler2D s1;
+ // vec4 f1;
+ // sampler2D s2;
+ // sampler2D s3;
+ // vec4 f2;
+ // };
+ //
+ // struct Modified
+ // {
+ // vec4 f1;
+ // vec4 f2;
+ // };
+ //
+ // fieldMap:
+ // 0 -> Invalid
+ // 1 -> 0
+ // 2 -> Invalid
+ // 3 -> Invalid
+ // 4 -> 1
+ //
+ TVector<int> fieldMap;
+};
+
+using StructureMap = angle::HashMap<const TStructure *, StructureData>;
+using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>;
+using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>;
+
+TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
+ TCompiler *compiler,
+ TIntermBinary *node,
+ const StructureMap &structureMap,
+ const StructureUniformMap &structureUniformMap,
+ const ExtractedSamplerMap &extractedSamplers);
+
+TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
+ TIntermBinary *node,
+ const StructureMap &structureMap,
+ const StructureUniformMap &structureUniformMap,
+ const ExtractedSamplerMap &extractedSamplers)
+{
+ // Only interested in EOpIndexDirectStruct binary nodes.
+ if (node->getOp() != EOpIndexDirectStruct)
+ {
+ return nullptr;
+ }
+
+ const TStructure *structure = node->getLeft()->getType().getStruct();
+ ASSERT(structure);
+
+ // If the result of the index is not a sampler and the struct is not replaced, there's nothing
+ // to do.
+ if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end())
+ {
+ return nullptr;
+ }
+
+ // Otherwise, replace the whole expression such that:
+ //
+ // - if sampler, it's indexed with whatever indices the parent structs were indexed with,
+ // - otherwise, the chain of field selections is rewritten by modifying the base uniform so all
+ // the intermediate nodes would have the correct type (and therefore fields).
+ ASSERT(structureMap.find(structure) != structureMap.end());
+
+ return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
+ structureUniformMap, extractedSamplers);
+}
+
+// Given an expression, this traverser calculates a new expression where sampler-in-structs are
+// replaced with their extracted ones, and field indices are adjusted for the rest of the fields.
+// In particular, this is run on the right node of EOpIndexIndirect binary nodes, so that the
+// expression in the index gets a chance to go through this transformation.
+class RewriteExpressionTraverser final : public TIntermTraverser
+{
+ public:
+ explicit RewriteExpressionTraverser(TCompiler *compiler,
+ const StructureMap &structureMap,
+ const StructureUniformMap &structureUniformMap,
+ const ExtractedSamplerMap &extractedSamplers)
+ : TIntermTraverser(true, false, false),
+ mCompiler(compiler),
+ mStructureMap(structureMap),
+ mStructureUniformMap(structureUniformMap),
+ mExtractedSamplers(extractedSamplers)
+ {}
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
+ mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
+
+ if (rewritten == nullptr)
+ {
+ return true;
+ }
+
+ queueReplacement(rewritten, OriginalNode::IS_DROPPED);
+
+ // Don't iterate as the expression is rewritten.
+ return false;
+ }
+
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ // It's impossible to reach here with a symbol that needs replacement.
+ // MonomorphizeUnsupportedFunctions makes sure that whole structs containing
+ // samplers are not passed to functions, so any instance of the struct uniform is
+ // necessarily indexed right away. visitBinary should have already taken care of it.
+ ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
+ }
+
+ private:
+ TCompiler *mCompiler;
+
+ // See RewriteStructSamplersTraverser.
+ const StructureMap &mStructureMap;
+ const StructureUniformMap &mStructureUniformMap;
+ const ExtractedSamplerMap &mExtractedSamplers;
+};
+
+// Rewrite the index of an EOpIndexIndirect expression. The root can never need replacing, because
+// it cannot be a sampler itself or of a struct type.
+void RewriteIndexExpression(TCompiler *compiler,
+ TIntermTyped *expression,
+ const StructureMap &structureMap,
+ const StructureUniformMap &structureUniformMap,
+ const ExtractedSamplerMap &extractedSamplers)
+{
+ RewriteExpressionTraverser traverser(compiler, structureMap, structureUniformMap,
+ extractedSamplers);
+ expression->traverse(&traverser);
+ bool valid = traverser.updateTree(compiler, expression);
+ ASSERT(valid);
+}
+
+// Given an expression such as the following:
+//
+// EOpIndexDirectStruct (sampler)
+// / \
+// EOpIndex* field index
+// / \
+// EOpIndexDirectStruct index 2
+// / \
+// EOpIndex* field index
+// / \
+// EOpIndexDirectStruct index 1
+// / \
+// Uniform Struct field index
+//
+// produces:
+//
+// EOpIndex*
+// / \
+// EOpIndex* index 2
+// / \
+// sampler index 1
+//
+// Alternatively, if the expression is as such:
+//
+// EOpIndexDirectStruct
+// / \
+// (modified struct type) EOpIndex* field index
+// / \
+// EOpIndexDirectStruct index 2
+// / \
+// EOpIndex* field index
+// / \
+// EOpIndexDirectStruct index 1
+// / \
+// Uniform Struct field index
+//
+// produces:
+//
+// EOpIndexDirectStruct
+// / \
+// EOpIndex* mapped field index
+// / \
+// EOpIndexDirectStruct index 2
+// / \
+// EOpIndex* mapped field index
+// / \
+// EOpIndexDirectStruct index 1
+// / \
+// Uniform Struct mapped field index
+//
+TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
+ TCompiler *compiler,
+ TIntermBinary *node,
+ const StructureMap &structureMap,
+ const StructureUniformMap &structureUniformMap,
+ const ExtractedSamplerMap &extractedSamplers)
+{
+ ASSERT(node->getOp() == EOpIndexDirectStruct);
+
+ const bool isSampler = node->getType().isSampler();
+
+ TIntermSymbol *baseUniform = nullptr;
+ std::string samplerName;
+
+ TVector<TIntermBinary *> indexNodeStack;
+
+ // Iterate once and build the name of the sampler.
+ TIntermBinary *iter = node;
+ while (baseUniform == nullptr)
+ {
+ indexNodeStack.push_back(iter);
+ baseUniform = iter->getLeft()->getAsSymbolNode();
+
+ if (isSampler)
+ {
+ if (iter->getOp() == EOpIndexDirectStruct)
+ {
+ // When indexed into a struct, get the field name instead and construct the sampler
+ // name.
+ samplerName.insert(0, iter->getIndexStructFieldName().data());
+ samplerName.insert(0, "_");
+ }
+
+ if (baseUniform)
+ {
+ // If left is a symbol, we have reached the end of the chain. Use the struct name
+ // to finish building the name of the sampler.
+ samplerName.insert(0, baseUniform->variable().name().data());
+ }
+ }
+
+ iter = iter->getLeft()->getAsBinaryNode();
+ }
+
+ TIntermTyped *rewritten = nullptr;
+
+ if (isSampler)
+ {
+ ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end());
+ rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
+ }
+ else
+ {
+ const TVariable *baseUniformVar = &baseUniform->variable();
+ ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end());
+ rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
+ }
+
+ // Iterate again and build the expression from bottom up.
+ for (auto it = indexNodeStack.rbegin(); it != indexNodeStack.rend(); ++it)
+ {
+ TIntermBinary *indexNode = *it;
+
+ switch (indexNode->getOp())
+ {
+ case EOpIndexDirectStruct:
+ if (!isSampler)
+ {
+ // Remap the field.
+ const TStructure *structure = indexNode->getLeft()->getType().getStruct();
+ ASSERT(structureMap.find(structure) != structureMap.end());
+
+ TIntermConstantUnion *asConstantUnion =
+ indexNode->getRight()->getAsConstantUnion();
+ ASSERT(asConstantUnion);
+
+ const int fieldIndex = asConstantUnion->getIConst(0);
+ ASSERT(fieldIndex <
+ static_cast<int>(structureMap.at(structure).fieldMap.size()));
+
+ const int mappedFieldIndex = structureMap.at(structure).fieldMap[fieldIndex];
+
+ rewritten = new TIntermBinary(EOpIndexDirectStruct, rewritten,
+ CreateIndexNode(mappedFieldIndex));
+ }
+ break;
+
+ case EOpIndexDirect:
+ rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight());
+ break;
+
+ case EOpIndexIndirect:
+ {
+ // Run RewriteExpressionTraverser on the right node. It may itself be an expression
+ // with a sampler inside that needs to be rewritten, or simply use a field of a
+ // struct that's remapped.
+ TIntermTyped *indexExpression = indexNode->getRight();
+ RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap,
+ extractedSamplers);
+ rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression);
+ break;
+ }
+
+ default:
+ UNREACHABLE();
+ break;
+ }
+ }
+
+ return rewritten;
+}
+
+class RewriteStructSamplersTraverser final : public TIntermTraverser
+{
+ public:
+ explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mCompiler(compiler),
+ mRemovedUniformsCount(0)
+ {}
+
+ int removedUniformsCount() const { return mRemovedUniformsCount; }
+
+ // Each struct sampler declaration is stripped of its samplers. New uniforms are added for each
+ // stripped struct sampler.
+ bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
+ {
+ if (!mInGlobalScope)
+ {
+ return true;
+ }
+
+ const TIntermSequence &sequence = *(decl->getSequence());
+ TIntermTyped *declarator = sequence.front()->getAsTyped();
+ const TType &type = declarator->getType();
+
+ if (!type.isStructureContainingSamplers())
+ {
+ return false;
+ }
+
+ TIntermSequence newSequence;
+
+ if (type.isStructSpecifier())
+ {
+ // If this is just a struct definition (not a uniform variable declaration of a
+ // struct type), just remove the samplers. They are not instantiated yet.
+ const TStructure *structure = type.getStruct();
+ ASSERT(structure && mStructureMap.find(structure) == mStructureMap.end());
+
+ stripStructSpecifierSamplers(structure, &newSequence);
+ }
+ else
+ {
+ const TStructure *structure = type.getStruct();
+
+ // If the structure is defined at the same time, create the mapping to the stripped
+ // version first.
+ if (mStructureMap.find(structure) == mStructureMap.end())
+ {
+ stripStructSpecifierSamplers(structure, &newSequence);
+ }
+
+ // Then, extract the samplers from the struct and create global-scope variables instead.
+ TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
+ ASSERT(asSymbol);
+ const TVariable &variable = asSymbol->variable();
+ ASSERT(variable.symbolType() != SymbolType::Empty);
+
+ extractStructSamplerUniforms(variable, structure, &newSequence);
+ }
+
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl,
+ std::move(newSequence));
+
+ return false;
+ }
+
+ // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root.
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
+ mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
+
+ if (rewritten == nullptr)
+ {
+ return true;
+ }
+
+ queueReplacement(rewritten, OriginalNode::IS_DROPPED);
+
+ // Don't iterate as the expression is rewritten.
+ return false;
+ }
+
+ // Same implementation as in RewriteExpressionTraverser. That traverser cannot replace root.
+ void visitSymbol(TIntermSymbol *node) override
+ {
+ ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
+ }
+
+ private:
+ // Removes all samplers from a struct specifier.
+ void stripStructSpecifierSamplers(const TStructure *structure, TIntermSequence *newSequence)
+ {
+ TFieldList *newFieldList = new TFieldList;
+ ASSERT(structure->containsSamplers());
+
+ // Add this struct to the struct map
+ ASSERT(mStructureMap.find(structure) == mStructureMap.end());
+ StructureData *modifiedData = &mStructureMap[structure];
+
+ modifiedData->modified = nullptr;
+ modifiedData->fieldMap.resize(structure->fields().size(), std::numeric_limits<int>::max());
+
+ for (size_t fieldIndex = 0; fieldIndex < structure->fields().size(); ++fieldIndex)
+ {
+ const TField *field = structure->fields()[fieldIndex];
+ const TType &fieldType = *field->type();
+
+ // If the field is a sampler, or a struct that's entirely removed, skip it.
+ if (!fieldType.isSampler() && !isRemovedStructType(fieldType))
+ {
+ TType *newType = nullptr;
+
+ // Otherwise, if it's a struct that's replaced, create a new field of the replaced
+ // type.
+ if (fieldType.isStructureContainingSamplers())
+ {
+ const TStructure *fieldStruct = fieldType.getStruct();
+ ASSERT(mStructureMap.find(fieldStruct) != mStructureMap.end());
+
+ const TStructure *modifiedStruct = mStructureMap[fieldStruct].modified;
+ ASSERT(modifiedStruct);
+
+ newType = new TType(modifiedStruct, true);
+ if (fieldType.isArray())
+ {
+ newType->makeArrays(fieldType.getArraySizes());
+ }
+ }
+ else
+ {
+ // If not, duplicate the field as is.
+ newType = new TType(fieldType);
+ }
+
+ // Record the mapping of the field indices, so future EOpIndexDirectStruct's into
+ // this struct can be fixed up.
+ modifiedData->fieldMap[fieldIndex] = static_cast<int>(newFieldList->size());
+
+ TField *newField =
+ new TField(newType, field->name(), field->line(), field->symbolType());
+ newFieldList->push_back(newField);
+ }
+ }
+
+ // Prune empty structs.
+ if (newFieldList->empty())
+ {
+ return;
+ }
+
+ // Declare a new struct with the same name and the new fields.
+ modifiedData->modified =
+ new TStructure(mSymbolTable, structure->name(), newFieldList, structure->symbolType());
+ TType *newStructType = new TType(modifiedData->modified, true);
+ TVariable *newStructVar =
+ new TVariable(mSymbolTable, kEmptyImmutableString, newStructType, SymbolType::Empty);
+ TIntermSymbol *newStructRef = new TIntermSymbol(newStructVar);
+
+ TIntermDeclaration *structDecl = new TIntermDeclaration;
+ structDecl->appendDeclarator(newStructRef);
+
+ newSequence->push_back(structDecl);
+ }
+
+ // Returns true if the type is a struct that was removed because we extracted all the members.
+ bool isRemovedStructType(const TType &type) const
+ {
+ const TStructure *structure = type.getStruct();
+ if (structure == nullptr)
+ {
+ // Not a struct
+ return false;
+ }
+
+ // A struct is removed if it is in the map, but doesn't have a replacement struct.
+ auto iter = mStructureMap.find(structure);
+ return iter != mStructureMap.end() && iter->second.modified == nullptr;
+ }
+
+ // Removes samplers from struct uniforms. For each sampler removed also adds a new globally
+ // defined sampler uniform.
+ void extractStructSamplerUniforms(const TVariable &variable,
+ const TStructure *structure,
+ TIntermSequence *newSequence)
+ {
+ ASSERT(structure->containsSamplers());
+ ASSERT(mStructureMap.find(structure) != mStructureMap.end());
+
+ const TType &type = variable.getType();
+ enterArray(type);
+
+ for (const TField *field : structure->fields())
+ {
+ extractFieldSamplers(variable.name().data(), field, newSequence);
+ }
+
+ // If there's a replacement structure (because there are non-sampler fields in the struct),
+ // add a declaration with that type.
+ const TStructure *modified = mStructureMap[structure].modified;
+ if (modified != nullptr)
+ {
+ TType *newType = new TType(modified, false);
+ if (type.isArray())
+ {
+ newType->makeArrays(type.getArraySizes());
+ }
+ newType->setQualifier(EvqUniform);
+ const TVariable *newVariable =
+ new TVariable(mSymbolTable, variable.name(), newType, variable.symbolType());
+
+ TIntermDeclaration *newDecl = new TIntermDeclaration();
+ newDecl->appendDeclarator(new TIntermSymbol(newVariable));
+
+ newSequence->push_back(newDecl);
+
+ ASSERT(mStructureUniformMap.find(&variable) == mStructureUniformMap.end());
+ mStructureUniformMap[&variable] = newVariable;
+ }
+ else
+ {
+ mRemovedUniformsCount++;
+ }
+
+ exitArray(type);
+ }
+
+ // Extracts samplers from a field of a struct. Works with nested structs and arrays.
+ void extractFieldSamplers(const std::string &prefix,
+ const TField *field,
+ TIntermSequence *newSequence)
+ {
+ const TType &fieldType = *field->type();
+ if (fieldType.isSampler() || fieldType.isStructureContainingSamplers())
+ {
+ std::string newPrefix = prefix + "_" + field->name().data();
+
+ if (fieldType.isSampler())
+ {
+ extractSampler(newPrefix, fieldType, newSequence);
+ }
+ else
+ {
+ enterArray(fieldType);
+ const TStructure *structure = fieldType.getStruct();
+ for (const TField *nestedField : structure->fields())
+ {
+ extractFieldSamplers(newPrefix, nestedField, newSequence);
+ }
+ exitArray(fieldType);
+ }
+ }
+ }
+
+ void GenerateArraySizesFromStack(TVector<unsigned int> *sizesOut)
+ {
+ sizesOut->reserve(mArraySizeStack.size());
+
+ for (auto it = mArraySizeStack.rbegin(); it != mArraySizeStack.rend(); ++it)
+ {
+ sizesOut->push_back(*it);
+ }
+ }
+
+ // Extracts a sampler from a struct. Declares the new extracted sampler.
+ void extractSampler(const std::string &newName,
+ const TType &fieldType,
+ TIntermSequence *newSequence)
+ {
+ ASSERT(fieldType.isSampler());
+
+ TType *newType = new TType(fieldType);
+
+ // Add array dimensions accumulated so far due to struct arrays. Note that to support
+ // nested arrays, mArraySizeStack has the outermost size in the front. |makeArrays| thus
+ // expects this in reverse order.
+ TVector<unsigned int> parentArraySizes;
+ GenerateArraySizesFromStack(&parentArraySizes);
+ newType->makeArrays(parentArraySizes);
+
+ ImmutableStringBuilder nameBuilder(newName.size() + 1);
+ nameBuilder << newName;
+
+ newType->setQualifier(EvqUniform);
+ TVariable *newVariable =
+ new TVariable(mSymbolTable, nameBuilder, newType, SymbolType::AngleInternal);
+ TIntermSymbol *newSymbol = new TIntermSymbol(newVariable);
+
+ TIntermDeclaration *samplerDecl = new TIntermDeclaration;
+ samplerDecl->appendDeclarator(newSymbol);
+
+ newSequence->push_back(samplerDecl);
+
+ // TODO: Use a temp name instead of generating a name as currently done. There is no
+ // guarantee that these generated names cannot clash. Create a mapping from the previous
+ // name to the name assigned to the temp variable so ShaderVariable::mappedName can be
+ // updated post-transformation. http://anglebug.com/4301
+ ASSERT(mExtractedSamplers.find(newName) == mExtractedSamplers.end());
+ mExtractedSamplers[newName] = newVariable;
+ }
+
+ void enterArray(const TType &arrayType)
+ {
+ const TSpan<const unsigned int> &arraySizes = arrayType.getArraySizes();
+ for (auto it = arraySizes.rbegin(); it != arraySizes.rend(); ++it)
+ {
+ unsigned int arraySize = *it;
+ mArraySizeStack.push_back(arraySize);
+ }
+ }
+
+ void exitArray(const TType &arrayType)
+ {
+ mArraySizeStack.resize(mArraySizeStack.size() - arrayType.getNumArraySizes());
+ }
+
+ TCompiler *mCompiler;
+ int mRemovedUniformsCount;
+
+ // Map structures with samplers to ones that have their samplers removed.
+ StructureMap mStructureMap;
+
+ // Map uniform variables of structure type that are replaced with another variable.
+ StructureUniformMap mStructureUniformMap;
+
+ // Map a constructed sampler name to its variable. Used to replace an expression that uses this
+ // sampler with the extracted one.
+ ExtractedSamplerMap mExtractedSamplers;
+
+ // A stack of array sizes. Used to figure out the array dimensions of the extracted sampler,
+ // for example when it's nested in an array of structs in an array of structs.
+ TVector<unsigned int> mArraySizeStack;
+};
+} // anonymous namespace
+
+bool RewriteStructSamplers(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ int *removedUniformsCountOut)
+{
+ RewriteStructSamplersTraverser traverser(compiler, symbolTable);
+ root->traverse(&traverser);
+ *removedUniformsCountOut = traverser.removedUniformsCount();
+ return traverser.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h
new file mode 100644
index 0000000000..f4c73a27fc
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteStructSamplers.h
@@ -0,0 +1,38 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteStructSamplers: Extract structs from samplers.
+//
+// This traverser is designed to strip out samplers from structs. It moves them into separate
+// uniform sampler declarations. This allows the struct to be stored in the default uniform block.
+// This transformation requires MonomorphizeUnsupportedFunctions to have been run so it
+// wouldn't need to deal with functions that are passed such structs.
+//
+// For example:
+// struct S { sampler2D samp; int i; };
+// uniform S uni;
+// Is rewritten as:
+// struct S { int i; };
+// uniform S uni;
+// uniform sampler2D uni_i;
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITESTRUCTSAMPLERS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITESTRUCTSAMPLERS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteStructSamplers(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable,
+ int *removedUniformsCountOut);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_VULKAN_REWRITESTRUCTSAMPLERS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp
new file mode 100644
index 0000000000..71284ecc1b
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.cpp
@@ -0,0 +1,168 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Implementation of texelFetchOffset translation issue workaround.
+// See header for more info.
+
+#include "compiler/translator/tree_ops/RewriteTexelFetchOffset.h"
+
+#include "common/angleutils.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class Traverser : public TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool Apply(TCompiler *compiler,
+ TIntermNode *root,
+ const TSymbolTable &symbolTable,
+ int shaderVersion);
+
+ private:
+ Traverser(const TSymbolTable &symbolTable, int shaderVersion);
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ void nextIteration();
+
+ const TSymbolTable *symbolTable;
+ const int shaderVersion;
+ bool mFound = false;
+};
+
+Traverser::Traverser(const TSymbolTable &symbolTable, int shaderVersion)
+ : TIntermTraverser(true, false, false), symbolTable(&symbolTable), shaderVersion(shaderVersion)
+{}
+
+// static
+bool Traverser::Apply(TCompiler *compiler,
+ TIntermNode *root,
+ const TSymbolTable &symbolTable,
+ int shaderVersion)
+{
+ Traverser traverser(symbolTable, shaderVersion);
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.mFound)
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.mFound);
+
+ return true;
+}
+
+void Traverser::nextIteration()
+{
+ mFound = false;
+}
+
+bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ if (mFound)
+ {
+ return false;
+ }
+
+ // Decide if the node represents the call of texelFetchOffset.
+ if (!BuiltInGroup::IsBuiltIn(node->getOp()))
+ {
+ return true;
+ }
+
+ ASSERT(node->getFunction()->symbolType() == SymbolType::BuiltIn);
+ if (node->getFunction()->name() != "texelFetchOffset")
+ {
+ return true;
+ }
+
+ // Potential problem case detected, apply workaround.
+ const TIntermSequence *sequence = node->getSequence();
+ ASSERT(sequence->size() == 4u);
+
+ // Decide if the sampler is a 2DArray sampler. In that case position is ivec3 and offset is
+ // ivec2.
+ bool is2DArray = sequence->at(1)->getAsTyped()->getNominalSize() == 3 &&
+ sequence->at(3)->getAsTyped()->getNominalSize() == 2;
+
+ // Create new node that represents the call of function texelFetch.
+ // Its argument list will be: texelFetch(sampler, Position+offset, lod).
+
+ TIntermSequence texelFetchArguments;
+
+ // sampler
+ texelFetchArguments.push_back(sequence->at(0));
+
+ // Position
+ TIntermTyped *texCoordNode = sequence->at(1)->getAsTyped();
+ ASSERT(texCoordNode);
+
+ // offset
+ TIntermTyped *offsetNode = nullptr;
+ ASSERT(sequence->at(3)->getAsTyped());
+ if (is2DArray)
+ {
+ // For 2DArray samplers, Position is ivec3 and offset is ivec2;
+ // So offset must be converted into an ivec3 before being added to Position.
+ TIntermSequence constructOffsetIvecArguments;
+ constructOffsetIvecArguments.push_back(sequence->at(3)->getAsTyped());
+
+ TIntermTyped *zeroNode = CreateZeroNode(TType(EbtInt));
+ constructOffsetIvecArguments.push_back(zeroNode);
+
+ offsetNode = TIntermAggregate::CreateConstructor(texCoordNode->getType(),
+ &constructOffsetIvecArguments);
+ offsetNode->setLine(texCoordNode->getLine());
+ }
+ else
+ {
+ offsetNode = sequence->at(3)->getAsTyped();
+ }
+
+ // Position+offset
+ TIntermBinary *add = new TIntermBinary(EOpAdd, texCoordNode, offsetNode);
+ add->setLine(texCoordNode->getLine());
+ texelFetchArguments.push_back(add);
+
+ // lod
+ texelFetchArguments.push_back(sequence->at(2));
+
+ ASSERT(texelFetchArguments.size() == 3u);
+
+ TIntermTyped *texelFetchNode = CreateBuiltInFunctionCallNode("texelFetch", &texelFetchArguments,
+ *symbolTable, shaderVersion);
+ texelFetchNode->setLine(node->getLine());
+
+ // Replace the old node by this new node.
+ queueReplacement(texelFetchNode, OriginalNode::IS_DROPPED);
+ mFound = true;
+ return false;
+}
+
+} // anonymous namespace
+
+bool RewriteTexelFetchOffset(TCompiler *compiler,
+ TIntermNode *root,
+ const TSymbolTable &symbolTable,
+ int shaderVersion)
+{
+ // texelFetchOffset is only valid in GLSL 3.0 and later.
+ if (shaderVersion < 300)
+ return true;
+
+ return Traverser::Apply(compiler, root, symbolTable, shaderVersion);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h
new file mode 100644
index 0000000000..c2c3c07aad
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/RewriteTexelFetchOffset.h
@@ -0,0 +1,34 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This mutating tree traversal works around an issue on the translation
+// from texelFetchOffset into HLSL function Load on INTEL drivers. It
+// works by translating texelFetchOffset into texelFetch:
+//
+// - From: texelFetchOffset(sampler, Position, lod, offset)
+// - To: texelFetch(sampler, Position+offset, lod)
+//
+// See http://anglebug.com/1469
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_REWRITE_TEXELFETCHOFFSET_H_
+#define COMPILER_TRANSLATOR_TREEOPS_REWRITE_TEXELFETCHOFFSET_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteTexelFetchOffset(TCompiler *compiler,
+ TIntermNode *root,
+ const TSymbolTable &symbolTable,
+ int shaderVersion);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_REWRITE_TEXELFETCHOFFSET_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp
new file mode 100644
index 0000000000..4eab90e4fa
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.cpp
@@ -0,0 +1,223 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Scalarize vector and matrix constructor args, so that vectors built from components don't have
+// matrix arguments, and matrices built from components don't have vector arguments. This avoids
+// driver bugs around vector and matrix constructors.
+//
+
+#include "compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h"
+#include "common/debug.h"
+
+#include <algorithm>
+
+#include "angle_gl.h"
+#include "common/angleutils.h"
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+TIntermBinary *ConstructVectorIndexBinaryNode(TIntermTyped *symbolNode, int index)
+{
+ return new TIntermBinary(EOpIndexDirect, symbolNode, CreateIndexNode(index));
+}
+
+TIntermBinary *ConstructMatrixIndexBinaryNode(TIntermTyped *symbolNode, int colIndex, int rowIndex)
+{
+ TIntermBinary *colVectorNode = ConstructVectorIndexBinaryNode(symbolNode, colIndex);
+
+ return new TIntermBinary(EOpIndexDirect, colVectorNode, CreateIndexNode(rowIndex));
+}
+
+class ScalarizeArgsTraverser : public TIntermTraverser
+{
+ public:
+ ScalarizeArgsTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mNodesToScalarize(IntermNodePatternMatcher::kScalarizedVecOrMatConstructor)
+ {}
+
+ protected:
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ bool visitBlock(Visit visit, TIntermBlock *node) override;
+
+ private:
+ void scalarizeArgs(TIntermAggregate *aggregate, bool scalarizeVector, bool scalarizeMatrix);
+
+ // If we have the following code:
+ // mat4 m(0);
+ // vec4 v(1, m);
+ // We will rewrite to:
+ // mat4 m(0);
+ // mat4 s0 = m;
+ // vec4 v(1, s0[0][0], s0[0][1], s0[0][2]);
+ // This function is to create nodes for "mat4 s0 = m;" and insert it to the code sequence. This
+ // way the possible side effects of the constructor argument will only be evaluated once.
+ TIntermTyped *createTempVariable(TIntermTyped *original);
+
+ std::vector<TIntermSequence> mBlockStack;
+
+ IntermNodePatternMatcher mNodesToScalarize;
+};
+
+bool ScalarizeArgsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ ASSERT(visit == PreVisit);
+ if (mNodesToScalarize.match(node, getParentNode()))
+ {
+ if (node->getType().isVector())
+ {
+ scalarizeArgs(node, false, true);
+ }
+ else
+ {
+ ASSERT(node->getType().isMatrix());
+ scalarizeArgs(node, true, false);
+ }
+ }
+ return true;
+}
+
+bool ScalarizeArgsTraverser::visitBlock(Visit visit, TIntermBlock *node)
+{
+ mBlockStack.push_back(TIntermSequence());
+ {
+ for (TIntermNode *child : *node->getSequence())
+ {
+ ASSERT(child != nullptr);
+ child->traverse(this);
+ mBlockStack.back().push_back(child);
+ }
+ }
+ if (mBlockStack.back().size() > node->getSequence()->size())
+ {
+ node->getSequence()->clear();
+ *(node->getSequence()) = mBlockStack.back();
+ }
+ mBlockStack.pop_back();
+ return false;
+}
+
+void ScalarizeArgsTraverser::scalarizeArgs(TIntermAggregate *aggregate,
+ bool scalarizeVector,
+ bool scalarizeMatrix)
+{
+ ASSERT(aggregate);
+ ASSERT(!aggregate->isArray());
+ int size = static_cast<int>(aggregate->getType().getObjectSize());
+ TIntermSequence *sequence = aggregate->getSequence();
+ TIntermSequence originalArgs(*sequence);
+ sequence->clear();
+ for (TIntermNode *originalArgNode : originalArgs)
+ {
+ ASSERT(size > 0);
+ TIntermTyped *originalArg = originalArgNode->getAsTyped();
+ ASSERT(originalArg);
+ TIntermTyped *argVariable = createTempVariable(originalArg);
+ if (originalArg->isScalar())
+ {
+ sequence->push_back(argVariable);
+ size--;
+ }
+ else if (originalArg->isVector())
+ {
+ if (scalarizeVector)
+ {
+ int repeat = std::min<int>(size, originalArg->getNominalSize());
+ size -= repeat;
+ for (int index = 0; index < repeat; ++index)
+ {
+ TIntermBinary *newNode =
+ ConstructVectorIndexBinaryNode(argVariable->deepCopy(), index);
+ sequence->push_back(newNode);
+ }
+ }
+ else
+ {
+ sequence->push_back(argVariable);
+ size -= originalArg->getNominalSize();
+ }
+ }
+ else
+ {
+ ASSERT(originalArg->isMatrix());
+ if (scalarizeMatrix)
+ {
+ int colIndex = 0, rowIndex = 0;
+ int repeat = std::min<int>(size, originalArg->getCols() * originalArg->getRows());
+ size -= repeat;
+ while (repeat > 0)
+ {
+ TIntermBinary *newNode =
+ ConstructMatrixIndexBinaryNode(argVariable->deepCopy(), colIndex, rowIndex);
+ sequence->push_back(newNode);
+ rowIndex++;
+ if (rowIndex >= originalArg->getRows())
+ {
+ rowIndex = 0;
+ colIndex++;
+ }
+ repeat--;
+ }
+ }
+ else
+ {
+ sequence->push_back(argVariable);
+ size -= originalArg->getCols() * originalArg->getRows();
+ }
+ }
+ }
+}
+
+TIntermTyped *ScalarizeArgsTraverser::createTempVariable(TIntermTyped *original)
+{
+ ASSERT(original);
+
+ TType *type = new TType(original->getType());
+ type->setQualifier(EvqTemporary);
+
+ // The precision of the constant must have been retained (or derived), which will now apply to
+ // the temp variable. In some cases, the precision cannot be derived, so use the constant as
+ // is. For example, in the following standalone statement, the precision of the constant 0
+ // cannot be determined:
+ //
+ // mat2(0, bvec3(m));
+ //
+ if (IsPrecisionApplicableToType(type->getBasicType()) && type->getPrecision() == EbpUndefined)
+ {
+ return original;
+ }
+
+ TVariable *variable = CreateTempVariable(mSymbolTable, type);
+
+ ASSERT(mBlockStack.size() > 0);
+ TIntermSequence &sequence = mBlockStack.back();
+ TIntermDeclaration *declaration = CreateTempInitDeclarationNode(variable, original);
+ sequence.push_back(declaration);
+
+ return CreateTempSymbolNode(variable);
+}
+
+} // namespace
+
+bool ScalarizeVecAndMatConstructorArgs(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ ScalarizeArgsTraverser scalarizer(symbolTable);
+ root->traverse(&scalarizer);
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h
new file mode 100644
index 0000000000..617cbd7682
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Scalarize vector and matrix constructor args, so that vectors built from components don't have
+// matrix arguments, and matrices built from components don't have vector arguments. This avoids
+// driver bugs around vector and matrix constructors.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_SCALARIZEVECANDMATCONSTRUCTORARGS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_SCALARIZEVECANDMATCONSTRUCTORARGS_H_
+
+#include "GLSLANG/ShaderLang.h"
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool ScalarizeVecAndMatConstructorArgs(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_SCALARIZEVECANDMATCONSTRUCTORARGS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp
new file mode 100644
index 0000000000..6d48449154
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.cpp
@@ -0,0 +1,199 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The SeparateDeclarations function processes declarations, so that in the end each declaration
+// contains only one declarator.
+// This is useful as an intermediate step when initialization needs to be separated from
+// declaration, or when things need to be unfolded out of the initializer.
+// Example:
+// int a[1] = int[1](1), b[1] = int[1](2);
+// gets transformed when run through this class into the AST equivalent of:
+// int a[1] = int[1](1);
+// int b[1] = int[1](2);
+
+#include "compiler/translator/tree_ops/SeparateDeclarations.h"
+
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class SeparateDeclarationsTraverser : private TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool apply(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+ private:
+ SeparateDeclarationsTraverser(TSymbolTable *symbolTable);
+ bool visitDeclaration(Visit, TIntermDeclaration *node) override;
+ void visitSymbol(TIntermSymbol *symbol) override;
+
+ void separateDeclarator(TIntermSequence *sequence,
+ size_t index,
+ TIntermSequence *replacementDeclarations,
+ const TStructure **replacementStructure);
+
+ VariableReplacementMap mVariableMap;
+};
+
+bool SeparateDeclarationsTraverser::apply(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable)
+{
+ SeparateDeclarationsTraverser separateDecl(symbolTable);
+ root->traverse(&separateDecl);
+ return separateDecl.updateTree(compiler, root);
+}
+
+SeparateDeclarationsTraverser::SeparateDeclarationsTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable)
+{}
+
+bool SeparateDeclarationsTraverser::visitDeclaration(Visit, TIntermDeclaration *node)
+{
+ TIntermSequence *sequence = node->getSequence();
+ if (sequence->size() <= 1)
+ {
+ return true;
+ }
+
+ TIntermBlock *parentBlock = getParentNode()->getAsBlock();
+ ASSERT(parentBlock != nullptr);
+
+ TIntermSequence replacementDeclarations;
+ const TStructure *replacementStructure = nullptr;
+ for (size_t ii = 0; ii < sequence->size(); ++ii)
+ {
+ separateDeclarator(sequence, ii, &replacementDeclarations, &replacementStructure);
+ }
+
+ mMultiReplacements.emplace_back(parentBlock, node, std::move(replacementDeclarations));
+ return false;
+}
+
+void SeparateDeclarationsTraverser::visitSymbol(TIntermSymbol *symbol)
+{
+ const TVariable *variable = &symbol->variable();
+ if (mVariableMap.count(variable) > 0)
+ {
+ queueAccessChainReplacement(mVariableMap[variable]->deepCopy());
+ }
+}
+
+void SeparateDeclarationsTraverser::separateDeclarator(TIntermSequence *sequence,
+ size_t index,
+ TIntermSequence *replacementDeclarations,
+ const TStructure **replacementStructure)
+{
+ TIntermTyped *declarator = sequence->at(index)->getAsTyped();
+ const TType &declaratorType = declarator->getType();
+
+ // If the declaration is not simultaneously declaring a struct, can use the same declarator.
+ // Otherwise, the first declarator is taken as-is if the struct has a name.
+ const TStructure *structure = declaratorType.getStruct();
+ const bool isStructSpecifier = declaratorType.isStructSpecifier();
+ if (!isStructSpecifier || (index == 0 && structure->symbolType() != SymbolType::Empty))
+ {
+ TIntermDeclaration *replacementDeclaration = new TIntermDeclaration;
+
+ // Make sure to update the declarator's initializers if any.
+ declarator->traverse(this);
+
+ replacementDeclaration->appendDeclarator(declarator);
+ replacementDeclaration->setLine(declarator->getLine());
+ replacementDeclarations->push_back(replacementDeclaration);
+ return;
+ }
+
+ // If the struct is nameless, split it out first.
+ if (structure->symbolType() == SymbolType::Empty)
+ {
+ if (*replacementStructure == nullptr)
+ {
+ TStructure *newStructure =
+ new TStructure(mSymbolTable, kEmptyImmutableString, &structure->fields(),
+ SymbolType::AngleInternal);
+ newStructure->setAtGlobalScope(structure->atGlobalScope());
+ *replacementStructure = structure = newStructure;
+
+ TType *namedType = new TType(structure, true);
+ namedType->setQualifier(EvqGlobal);
+
+ TVariable *structVariable =
+ new TVariable(mSymbolTable, kEmptyImmutableString, namedType, SymbolType::Empty);
+
+ TIntermDeclaration *structDeclaration = new TIntermDeclaration;
+ structDeclaration->appendDeclarator(new TIntermSymbol(structVariable));
+ structDeclaration->setLine(declarator->getLine());
+ replacementDeclarations->push_back(structDeclaration);
+ }
+ else
+ {
+ structure = *replacementStructure;
+ }
+ }
+
+ // Redeclare the declarator but not as a struct specifier.
+ TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
+ TIntermTyped *initializer = nullptr;
+ if (asSymbol == nullptr)
+ {
+ TIntermBinary *asBinary = declarator->getAsBinaryNode();
+ ASSERT(asBinary->getOp() == EOpInitialize);
+ asSymbol = asBinary->getLeft()->getAsSymbolNode();
+ initializer = asBinary->getRight();
+
+ // Make sure the initializer itself has its variables replaced if necessary.
+ if (initializer->getAsSymbolNode())
+ {
+ const TVariable *initializerVariable = &initializer->getAsSymbolNode()->variable();
+ if (mVariableMap.count(initializerVariable) > 0)
+ {
+ initializer = mVariableMap[initializerVariable]->deepCopy();
+ }
+ }
+ else
+ {
+ initializer->traverse(this);
+ }
+ }
+
+ ASSERT(asSymbol && asSymbol->variable().symbolType() != SymbolType::Empty);
+
+ TType *newType = new TType(structure, false);
+ newType->setQualifier(asSymbol->getType().getQualifier());
+ newType->makeArrays(asSymbol->getType().getArraySizes());
+
+ TVariable *replacementVar = new TVariable(mSymbolTable, asSymbol->getName(), newType,
+ asSymbol->variable().symbolType());
+ TIntermSymbol *replacementSymbol = new TIntermSymbol(replacementVar);
+ TIntermTyped *replacement = replacementSymbol;
+ if (initializer)
+ {
+ replacement = new TIntermBinary(EOpInitialize, replacement, initializer);
+ }
+
+ TIntermDeclaration *replacementDeclaration = new TIntermDeclaration;
+ replacementDeclaration->appendDeclarator(replacement);
+ replacementDeclaration->setLine(declarator->getLine());
+ replacementDeclarations->push_back(replacementDeclaration);
+
+ mVariableMap[&asSymbol->variable()] = replacementSymbol;
+}
+} // namespace
+
+bool SeparateDeclarations(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ return SeparateDeclarationsTraverser::apply(compiler, root, symbolTable);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h
new file mode 100644
index 0000000000..5f55162ef4
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateDeclarations.h
@@ -0,0 +1,32 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The SeparateDeclarations function processes declarations, so that in the end each declaration
+// contains only one declarator.
+// This is useful as an intermediate step when initialization needs to be separated from
+// declaration, or when things need to be unfolded out of the initializer.
+// Example:
+// int a[1] = int[1](1), b[1] = int[1](2);
+// gets transformed when run through this class into the AST equivalent of:
+// int a[1] = int[1](1);
+// int b[1] = int[1](2);
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_SEPARATEDECLARATIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_SEPARATEDECLARATIONS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool SeparateDeclarations(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_SEPARATEDECLARATIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp
new file mode 100644
index 0000000000..fb3f4ba1c1
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.cpp
@@ -0,0 +1,115 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SeparateStructFromUniformDeclarations: Separate struct declarations from uniform declarations.
+//
+
+#include "compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+namespace
+{
+// This traverser translates embedded uniform structs into a specifier and declaration.
+// This makes the declarations easier to move into uniform blocks.
+class Traverser : public TIntermTraverser
+{
+ public:
+ explicit Traverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable)
+ {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
+ {
+ ASSERT(visit == PreVisit);
+
+ if (!mInGlobalScope)
+ {
+ return true;
+ }
+
+ const TIntermSequence &sequence = *(decl->getSequence());
+ ASSERT(sequence.size() == 1);
+ TIntermTyped *declarator = sequence.front()->getAsTyped();
+ const TType &type = declarator->getType();
+
+ if (type.isStructSpecifier() && type.getQualifier() == EvqUniform)
+ {
+ doReplacement(decl, declarator, type);
+ return false;
+ }
+
+ return true;
+ }
+
+ void visitSymbol(TIntermSymbol *symbol) override
+ {
+ const TVariable *variable = &symbol->variable();
+ if (mVariableMap.count(variable) > 0)
+ {
+ queueAccessChainReplacement(mVariableMap[variable]->deepCopy());
+ }
+ }
+
+ private:
+ void doReplacement(TIntermDeclaration *decl, TIntermTyped *declarator, const TType &oldType)
+ {
+ const TStructure *structure = oldType.getStruct();
+ if (structure->symbolType() == SymbolType::Empty)
+ {
+ // Handle nameless structs: uniform struct { ... } variable;
+ structure = new TStructure(mSymbolTable, kEmptyImmutableString, &structure->fields(),
+ SymbolType::AngleInternal);
+ }
+ TType *namedType = new TType(structure, true);
+ namedType->setQualifier(EvqGlobal);
+
+ TVariable *structVariable =
+ new TVariable(mSymbolTable, kEmptyImmutableString, namedType, SymbolType::Empty);
+ TIntermSymbol *structDeclarator = new TIntermSymbol(structVariable);
+ TIntermDeclaration *structDeclaration = new TIntermDeclaration;
+ structDeclaration->appendDeclarator(structDeclarator);
+
+ TIntermSequence newSequence;
+ newSequence.push_back(structDeclaration);
+
+ // Redeclare the uniform with the (potentially) new struct type
+ TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
+ ASSERT(asSymbol && asSymbol->variable().symbolType() != SymbolType::Empty);
+
+ TIntermDeclaration *namedDecl = new TIntermDeclaration;
+ TType *uniformType = new TType(structure, false);
+ uniformType->setQualifier(EvqUniform);
+ uniformType->makeArrays(oldType.getArraySizes());
+
+ TVariable *newVar = new TVariable(mSymbolTable, asSymbol->getName(), uniformType,
+ asSymbol->variable().symbolType());
+ TIntermSymbol *newSymbol = new TIntermSymbol(newVar);
+ namedDecl->appendDeclarator(newSymbol);
+
+ newSequence.push_back(namedDecl);
+
+ mVariableMap[&asSymbol->variable()] = newSymbol;
+
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl,
+ std::move(newSequence));
+ }
+
+ VariableReplacementMap mVariableMap;
+};
+} // anonymous namespace
+
+bool SeparateStructFromUniformDeclarations(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ Traverser separateStructDecls(symbolTable);
+ root->traverse(&separateStructDecls);
+ return separateStructDecls.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h
new file mode 100644
index 0000000000..424a742e54
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SeparateStructFromUniformDeclarations.h
@@ -0,0 +1,39 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SeparateStructFromUniformDeclarations: Separate struct declarations from uniform declarations.
+// It necessarily gives nameless uniform structs internal names.
+//
+// For example:
+// uniform struct { int a; } uni;
+// becomes:
+// struct s1 { int a; };
+// uniform s1 uni;
+//
+// And:
+// uniform struct S { int a; } uni;
+// becomes:
+// struct S { int a; };
+// uniform S uni;
+//
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_SEPARATESTRUCTFROMUNIFORMDECLARATIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_SEPARATESTRUCTFROMUNIFORMDECLARATIONS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool SeparateStructFromUniformDeclarations(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_SEPARATESTRUCTFROMUNIFORMDECLARATIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp
new file mode 100644
index 0000000000..c6a3e0b9ee
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.cpp
@@ -0,0 +1,499 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SimplifyLoopConditions is an AST traverser that converts loop conditions and loop expressions
+// to regular statements inside the loop. This way further transformations that generate statements
+// from loop conditions and loop expressions work correctly.
+//
+
+#include "compiler/translator/tree_ops/SimplifyLoopConditions.h"
+
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+struct LoopInfo
+{
+ const TVariable *conditionVariable = nullptr;
+ TIntermTyped *condition = nullptr;
+ TIntermTyped *expression = nullptr;
+};
+
+class SimplifyLoopConditionsTraverser : public TLValueTrackingTraverser
+{
+ public:
+ SimplifyLoopConditionsTraverser(const IntermNodePatternMatcher *conditionsToSimplify,
+ TSymbolTable *symbolTable);
+
+ void traverseLoop(TIntermLoop *node) override;
+
+ bool visitUnary(Visit visit, TIntermUnary *node) override;
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ bool visitTernary(Visit visit, TIntermTernary *node) override;
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
+ bool visitBranch(Visit visit, TIntermBranch *node) override;
+
+ bool foundLoopToChange() const { return mFoundLoopToChange; }
+
+ protected:
+ // Marked to true once an operation that needs to be hoisted out of a loop expression has been
+ // found.
+ bool mFoundLoopToChange;
+ bool mInsideLoopInitConditionOrExpression;
+ const IntermNodePatternMatcher *mConditionsToSimplify;
+
+ private:
+ LoopInfo mLoop;
+};
+
+SimplifyLoopConditionsTraverser::SimplifyLoopConditionsTraverser(
+ const IntermNodePatternMatcher *conditionsToSimplify,
+ TSymbolTable *symbolTable)
+ : TLValueTrackingTraverser(true, false, false, symbolTable),
+ mFoundLoopToChange(false),
+ mInsideLoopInitConditionOrExpression(false),
+ mConditionsToSimplify(conditionsToSimplify)
+{}
+
+// If we're inside a loop initialization, condition, or expression, we check for expressions that
+// should be moved out of the loop condition or expression. If one is found, the loop is
+// transformed.
+// If we're not inside loop initialization, condition, or expression, we only need to traverse nodes
+// that may contain loops.
+
+bool SimplifyLoopConditionsTraverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+ if (!mInsideLoopInitConditionOrExpression)
+ return false;
+
+ if (mFoundLoopToChange)
+ return false; // Already decided to change this loop.
+
+ ASSERT(mConditionsToSimplify);
+ mFoundLoopToChange = mConditionsToSimplify->match(node);
+ return !mFoundLoopToChange;
+}
+
+bool SimplifyLoopConditionsTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (!mInsideLoopInitConditionOrExpression)
+ return false;
+
+ if (mFoundLoopToChange)
+ return false; // Already decided to change this loop.
+
+ ASSERT(mConditionsToSimplify);
+ mFoundLoopToChange =
+ mConditionsToSimplify->match(node, getParentNode(), isLValueRequiredHere());
+ return !mFoundLoopToChange;
+}
+
+bool SimplifyLoopConditionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ if (!mInsideLoopInitConditionOrExpression)
+ return false;
+
+ if (mFoundLoopToChange)
+ return false; // Already decided to change this loop.
+
+ ASSERT(mConditionsToSimplify);
+ mFoundLoopToChange = mConditionsToSimplify->match(node, getParentNode());
+ return !mFoundLoopToChange;
+}
+
+bool SimplifyLoopConditionsTraverser::visitTernary(Visit visit, TIntermTernary *node)
+{
+ if (!mInsideLoopInitConditionOrExpression)
+ return false;
+
+ if (mFoundLoopToChange)
+ return false; // Already decided to change this loop.
+
+ ASSERT(mConditionsToSimplify);
+ mFoundLoopToChange = mConditionsToSimplify->match(node);
+ return !mFoundLoopToChange;
+}
+
+bool SimplifyLoopConditionsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
+{
+ if (!mInsideLoopInitConditionOrExpression)
+ return false;
+
+ if (mFoundLoopToChange)
+ return false; // Already decided to change this loop.
+
+ ASSERT(mConditionsToSimplify);
+ mFoundLoopToChange = mConditionsToSimplify->match(node);
+ return !mFoundLoopToChange;
+}
+
+bool SimplifyLoopConditionsTraverser::visitBranch(Visit visit, TIntermBranch *node)
+{
+ if (node->getFlowOp() == EOpContinue && (mLoop.condition || mLoop.expression))
+ {
+ TIntermBlock *parent = getParentNode()->getAsBlock();
+ ASSERT(parent);
+ TIntermSequence seq;
+ if (mLoop.expression)
+ {
+ seq.push_back(mLoop.expression->deepCopy());
+ }
+ if (mLoop.condition)
+ {
+ ASSERT(mLoop.conditionVariable);
+ seq.push_back(
+ CreateTempAssignmentNode(mLoop.conditionVariable, mLoop.condition->deepCopy()));
+ }
+ seq.push_back(node);
+ mMultiReplacements.push_back(NodeReplaceWithMultipleEntry(parent, node, std::move(seq)));
+ }
+
+ return true;
+}
+
+TIntermBlock *CreateFromBody(TIntermLoop *node, bool *bodyEndsInBranchOut)
+{
+ TIntermBlock *newBody = new TIntermBlock();
+ *bodyEndsInBranchOut = false;
+
+ TIntermBlock *nodeBody = node->getBody();
+ if (nodeBody != nullptr)
+ {
+ newBody->getSequence()->push_back(nodeBody);
+ *bodyEndsInBranchOut = EndsInBranch(nodeBody);
+ }
+ return newBody;
+}
+
+void SimplifyLoopConditionsTraverser::traverseLoop(TIntermLoop *node)
+{
+ // Mark that we're inside a loop condition or expression, and determine if the loop needs to be
+ // transformed.
+
+ ScopedNodeInTraversalPath addToPath(this, node);
+
+ mInsideLoopInitConditionOrExpression = true;
+ mFoundLoopToChange = !mConditionsToSimplify;
+
+ if (!mFoundLoopToChange && node->getInit())
+ {
+ node->getInit()->traverse(this);
+ }
+
+ if (!mFoundLoopToChange && node->getCondition())
+ {
+ node->getCondition()->traverse(this);
+ }
+
+ if (!mFoundLoopToChange && node->getExpression())
+ {
+ node->getExpression()->traverse(this);
+ }
+
+ mInsideLoopInitConditionOrExpression = false;
+
+ const LoopInfo prevLoop = mLoop;
+
+ if (mFoundLoopToChange)
+ {
+ const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>();
+ mLoop.conditionVariable = CreateTempVariable(mSymbolTable, boolType);
+ mLoop.condition = node->getCondition();
+ mLoop.expression = node->getExpression();
+
+ // Replace the loop condition with a boolean variable that's updated on each iteration.
+ TLoopType loopType = node->getType();
+ if (loopType == ELoopWhile)
+ {
+ ASSERT(!mLoop.expression);
+
+ if (mLoop.condition->getAsSymbolNode())
+ {
+ // Mask continue statement condition variable update.
+ mLoop.condition = nullptr;
+ }
+ else if (mLoop.condition->getAsConstantUnion())
+ {
+ // Transform:
+ // while (expr) { body; }
+ // into
+ // bool s0 = expr;
+ // while (s0) { body; }
+ TIntermDeclaration *tempInitDeclaration =
+ CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition);
+ insertStatementInParentBlock(tempInitDeclaration);
+
+ node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable));
+
+ // Mask continue statement condition variable update.
+ mLoop.condition = nullptr;
+ }
+ else
+ {
+ // Transform:
+ // while (expr) { body; }
+ // into
+ // bool s0 = expr;
+ // while (s0) { { body; } s0 = expr; }
+ //
+ // Local case statements are transformed into:
+ // s0 = expr; continue;
+ TIntermDeclaration *tempInitDeclaration =
+ CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition);
+ insertStatementInParentBlock(tempInitDeclaration);
+
+ bool bodyEndsInBranch;
+ TIntermBlock *newBody = CreateFromBody(node, &bodyEndsInBranch);
+ if (!bodyEndsInBranch)
+ {
+ newBody->getSequence()->push_back(CreateTempAssignmentNode(
+ mLoop.conditionVariable, mLoop.condition->deepCopy()));
+ }
+
+ // Can't use queueReplacement to replace old body, since it may have been nullptr.
+ // It's safe to do the replacements in place here - the new body will still be
+ // traversed, but that won't create any problems.
+ node->setBody(newBody);
+ node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable));
+ }
+ }
+ else if (loopType == ELoopDoWhile)
+ {
+ ASSERT(!mLoop.expression);
+
+ if (mLoop.condition->getAsSymbolNode())
+ {
+ // Mask continue statement condition variable update.
+ mLoop.condition = nullptr;
+ }
+ else if (mLoop.condition->getAsConstantUnion())
+ {
+ // Transform:
+ // do {
+ // body;
+ // } while (expr);
+ // into
+ // bool s0 = expr;
+ // do {
+ // body;
+ // } while (s0);
+ TIntermDeclaration *tempInitDeclaration =
+ CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition);
+ insertStatementInParentBlock(tempInitDeclaration);
+
+ node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable));
+
+ // Mask continue statement condition variable update.
+ mLoop.condition = nullptr;
+ }
+ else
+ {
+ // Transform:
+ // do {
+ // body;
+ // } while (expr);
+ // into
+ // bool s0;
+ // do {
+ // { body; }
+ // s0 = expr;
+ // } while (s0);
+ // Local case statements are transformed into:
+ // s0 = expr; continue;
+ TIntermDeclaration *tempInitDeclaration =
+ CreateTempDeclarationNode(mLoop.conditionVariable);
+ insertStatementInParentBlock(tempInitDeclaration);
+
+ bool bodyEndsInBranch;
+ TIntermBlock *newBody = CreateFromBody(node, &bodyEndsInBranch);
+ if (!bodyEndsInBranch)
+ {
+ newBody->getSequence()->push_back(
+ CreateTempAssignmentNode(mLoop.conditionVariable, mLoop.condition));
+ }
+
+ // Can't use queueReplacement to replace old body, since it may have been nullptr.
+ // It's safe to do the replacements in place here - the new body will still be
+ // traversed, but that won't create any problems.
+ node->setBody(newBody);
+ node->setCondition(CreateTempSymbolNode(mLoop.conditionVariable));
+ }
+ }
+ else if (loopType == ELoopFor)
+ {
+ if (!mLoop.condition)
+ {
+ mLoop.condition = CreateBoolNode(true);
+ }
+
+ TIntermLoop *whileLoop;
+ TIntermBlock *loopScope = new TIntermBlock();
+ TIntermSequence *loopScopeSequence = loopScope->getSequence();
+
+ // Insert "init;"
+ if (node->getInit())
+ {
+ loopScopeSequence->push_back(node->getInit());
+ }
+
+ if (mLoop.condition->getAsSymbolNode())
+ {
+ // Move the loop condition inside the loop.
+ // Transform:
+ // for (init; expr; exprB) { body; }
+ // into
+ // {
+ // init;
+ // while (expr) {
+ // { body; }
+ // exprB;
+ // }
+ // }
+ //
+ // Local case statements are transformed into:
+ // exprB; continue;
+
+ // Insert "{ body; }" in the while loop
+ bool bodyEndsInBranch;
+ TIntermBlock *whileLoopBody = CreateFromBody(node, &bodyEndsInBranch);
+ // Insert "exprB;" in the while loop
+ if (!bodyEndsInBranch && node->getExpression())
+ {
+ whileLoopBody->getSequence()->push_back(node->getExpression());
+ }
+ // Create "while(expr) { whileLoopBody }"
+ whileLoop =
+ new TIntermLoop(ELoopWhile, nullptr, mLoop.condition, nullptr, whileLoopBody);
+
+ // Mask continue statement condition variable update.
+ mLoop.condition = nullptr;
+ }
+ else if (mLoop.condition->getAsConstantUnion())
+ {
+ // Move the loop condition inside the loop.
+ // Transform:
+ // for (init; expr; exprB) { body; }
+ // into
+ // {
+ // init;
+ // bool s0 = expr;
+ // while (s0) {
+ // { body; }
+ // exprB;
+ // }
+ // }
+ //
+ // Local case statements are transformed into:
+ // exprB; continue;
+
+ // Insert "bool s0 = expr;"
+ loopScopeSequence->push_back(
+ CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition));
+ // Insert "{ body; }" in the while loop
+ bool bodyEndsInBranch;
+ TIntermBlock *whileLoopBody = CreateFromBody(node, &bodyEndsInBranch);
+ // Insert "exprB;" in the while loop
+ if (!bodyEndsInBranch && node->getExpression())
+ {
+ whileLoopBody->getSequence()->push_back(node->getExpression());
+ }
+ // Create "while(s0) { whileLoopBody }"
+ whileLoop = new TIntermLoop(ELoopWhile, nullptr,
+ CreateTempSymbolNode(mLoop.conditionVariable), nullptr,
+ whileLoopBody);
+
+ // Mask continue statement condition variable update.
+ mLoop.condition = nullptr;
+ }
+ else
+ {
+ // Move the loop condition inside the loop.
+ // Transform:
+ // for (init; expr; exprB) { body; }
+ // into
+ // {
+ // init;
+ // bool s0 = expr;
+ // while (s0) {
+ // { body; }
+ // exprB;
+ // s0 = expr;
+ // }
+ // }
+ //
+ // Local case statements are transformed into:
+ // exprB; s0 = expr; continue;
+
+ // Insert "bool s0 = expr;"
+ loopScopeSequence->push_back(
+ CreateTempInitDeclarationNode(mLoop.conditionVariable, mLoop.condition));
+ // Insert "{ body; }" in the while loop
+ bool bodyEndsInBranch;
+ TIntermBlock *whileLoopBody = CreateFromBody(node, &bodyEndsInBranch);
+ // Insert "exprB;" in the while loop
+ if (!bodyEndsInBranch && node->getExpression())
+ {
+ whileLoopBody->getSequence()->push_back(node->getExpression());
+ }
+ // Insert "s0 = expr;" in the while loop
+ if (!bodyEndsInBranch)
+ {
+ whileLoopBody->getSequence()->push_back(CreateTempAssignmentNode(
+ mLoop.conditionVariable, mLoop.condition->deepCopy()));
+ }
+ // Create "while(s0) { whileLoopBody }"
+ whileLoop = new TIntermLoop(ELoopWhile, nullptr,
+ CreateTempSymbolNode(mLoop.conditionVariable), nullptr,
+ whileLoopBody);
+ }
+
+ loopScope->getSequence()->push_back(whileLoop);
+ queueReplacement(loopScope, OriginalNode::IS_DROPPED);
+
+ // After this the old body node will be traversed and loops inside it may be
+ // transformed. This is fine, since the old body node will still be in the AST after
+ // the transformation that's queued here, and transforming loops inside it doesn't
+ // need to know the exact post-transform path to it.
+ }
+ }
+
+ mFoundLoopToChange = false;
+
+ // We traverse the body of the loop even if the loop is transformed.
+ if (node->getBody())
+ node->getBody()->traverse(this);
+
+ mLoop = prevLoop;
+}
+
+} // namespace
+
+bool SimplifyLoopConditions(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ SimplifyLoopConditionsTraverser traverser(nullptr, symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+bool SimplifyLoopConditions(TCompiler *compiler,
+ TIntermNode *root,
+ unsigned int conditionsToSimplifyMask,
+ TSymbolTable *symbolTable)
+{
+ IntermNodePatternMatcher conditionsToSimplify(conditionsToSimplifyMask);
+ SimplifyLoopConditionsTraverser traverser(&conditionsToSimplify, symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h
new file mode 100644
index 0000000000..2a554c019d
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SimplifyLoopConditions.h
@@ -0,0 +1,32 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SimplifyLoopConditions is an AST traverser that converts loop conditions and loop expressions
+// to regular statements inside the loop. This way further transformations that generate statements
+// from loop conditions and loop expressions work correctly.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_SIMPLIFYLOOPCONDITIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_SIMPLIFYLOOPCONDITIONS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool SimplifyLoopConditions(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+[[nodiscard]] bool SimplifyLoopConditions(TCompiler *compiler,
+ TIntermNode *root,
+ unsigned int conditionsToSimplify,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_SIMPLIFYLOOPCONDITIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp
new file mode 100644
index 0000000000..45985954b0
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.cpp
@@ -0,0 +1,173 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SplitSequenceOperator is an AST traverser that detects sequence operator expressions that
+// go through further AST transformations that generate statements, and splits them so that
+// possible side effects of earlier parts of the sequence operator expression are guaranteed to be
+// evaluated before the latter parts of the sequence operator expression are evaluated.
+//
+
+#include "compiler/translator/tree_ops/SplitSequenceOperator.h"
+
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class SplitSequenceOperatorTraverser : public TLValueTrackingTraverser
+{
+ public:
+ SplitSequenceOperatorTraverser(unsigned int patternsToSplitMask, TSymbolTable *symbolTable);
+
+ bool visitUnary(Visit visit, TIntermUnary *node) override;
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ bool visitTernary(Visit visit, TIntermTernary *node) override;
+
+ void nextIteration();
+ bool foundExpressionToSplit() const { return mFoundExpressionToSplit; }
+
+ protected:
+ // Marked to true once an operation that needs to be hoisted out of the expression has been
+ // found. After that, no more AST updates are performed on that traversal.
+ bool mFoundExpressionToSplit;
+ int mInsideSequenceOperator;
+
+ IntermNodePatternMatcher mPatternToSplitMatcher;
+};
+
+SplitSequenceOperatorTraverser::SplitSequenceOperatorTraverser(unsigned int patternsToSplitMask,
+ TSymbolTable *symbolTable)
+ : TLValueTrackingTraverser(true, false, true, symbolTable),
+ mFoundExpressionToSplit(false),
+ mInsideSequenceOperator(0),
+ mPatternToSplitMatcher(patternsToSplitMask)
+{}
+
+void SplitSequenceOperatorTraverser::nextIteration()
+{
+ mFoundExpressionToSplit = false;
+ mInsideSequenceOperator = 0;
+}
+
+bool SplitSequenceOperatorTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ if (mFoundExpressionToSplit)
+ return false;
+
+ if (mInsideSequenceOperator > 0 && visit == PreVisit)
+ {
+ // Detect expressions that need to be simplified
+ mFoundExpressionToSplit = mPatternToSplitMatcher.match(node, getParentNode());
+ return !mFoundExpressionToSplit;
+ }
+
+ return true;
+}
+
+bool SplitSequenceOperatorTraverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+ if (mFoundExpressionToSplit)
+ return false;
+
+ if (mInsideSequenceOperator > 0 && visit == PreVisit)
+ {
+ // Detect expressions that need to be simplified
+ mFoundExpressionToSplit = mPatternToSplitMatcher.match(node);
+ return !mFoundExpressionToSplit;
+ }
+
+ return true;
+}
+
+bool SplitSequenceOperatorTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (node->getOp() == EOpComma)
+ {
+ if (visit == PreVisit)
+ {
+ if (mFoundExpressionToSplit)
+ {
+ return false;
+ }
+ mInsideSequenceOperator++;
+ }
+ else if (visit == PostVisit)
+ {
+ // Split sequence operators starting from the outermost one to preserve correct
+ // execution order.
+ if (mFoundExpressionToSplit && mInsideSequenceOperator == 1)
+ {
+ // Move the left side operand into a separate statement in the parent block.
+ TIntermSequence insertions;
+ insertions.push_back(node->getLeft());
+ insertStatementsInParentBlock(insertions);
+ // Replace the comma node with its right side operand.
+ queueReplacement(node->getRight(), OriginalNode::IS_DROPPED);
+ }
+ mInsideSequenceOperator--;
+ }
+ return true;
+ }
+
+ if (mFoundExpressionToSplit)
+ return false;
+
+ if (mInsideSequenceOperator > 0 && visit == PreVisit)
+ {
+ // Detect expressions that need to be simplified
+ mFoundExpressionToSplit =
+ mPatternToSplitMatcher.match(node, getParentNode(), isLValueRequiredHere());
+ return !mFoundExpressionToSplit;
+ }
+
+ return true;
+}
+
+bool SplitSequenceOperatorTraverser::visitTernary(Visit visit, TIntermTernary *node)
+{
+ if (mFoundExpressionToSplit)
+ return false;
+
+ if (mInsideSequenceOperator > 0 && visit == PreVisit)
+ {
+ // Detect expressions that need to be simplified
+ mFoundExpressionToSplit = mPatternToSplitMatcher.match(node);
+ return !mFoundExpressionToSplit;
+ }
+
+ return true;
+}
+
+} // namespace
+
+bool SplitSequenceOperator(TCompiler *compiler,
+ TIntermNode *root,
+ int patternsToSplitMask,
+ TSymbolTable *symbolTable)
+{
+ SplitSequenceOperatorTraverser traverser(patternsToSplitMask, symbolTable);
+ // Separate one expression at a time, and reset the traverser between iterations.
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.foundExpressionToSplit())
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.foundExpressionToSplit());
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h
new file mode 100644
index 0000000000..2a9f09a1f2
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/SplitSequenceOperator.h
@@ -0,0 +1,30 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SplitSequenceOperator is an AST traverser that detects sequence operator expressions that
+// go through further AST transformations that generate statements, and splits them so that
+// possible side effects of earlier parts of the sequence operator expression are guaranteed to be
+// evaluated before the latter parts of the sequence operator expression are evaluated.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_SPLITSEQUENCEOPERATOR_H_
+#define COMPILER_TRANSLATOR_TREEOPS_SPLITSEQUENCEOPERATOR_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool SplitSequenceOperator(TCompiler *compiler,
+ TIntermNode *root,
+ int patternsToSplitMask,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_SPLITSEQUENCEOPERATOR_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp
new file mode 100644
index 0000000000..36e426f8e1
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.cpp
@@ -0,0 +1,60 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// An AST traverser that rewrites for and while loops by replacing "condition" with
+// "condition && true" to work around condition bug on Intel Mac.
+class AddAndTrueToLoopConditionTraverser : public TIntermTraverser
+{
+ public:
+ AddAndTrueToLoopConditionTraverser() : TIntermTraverser(true, false, false) {}
+
+ bool visitLoop(Visit, TIntermLoop *loop) override
+ {
+ // do-while loop doesn't have this bug.
+ if (loop->getType() != ELoopFor && loop->getType() != ELoopWhile)
+ {
+ return true;
+ }
+
+ // For loop may not have a condition.
+ if (loop->getCondition() == nullptr)
+ {
+ return true;
+ }
+
+ // Constant true.
+ TIntermTyped *trueValue = CreateBoolNode(true);
+
+ // CONDITION && true.
+ TIntermBinary *andOp = new TIntermBinary(EOpLogicalAnd, loop->getCondition(), trueValue);
+ loop->setCondition(andOp);
+
+ return true;
+ }
+};
+
+} // anonymous namespace
+
+bool AddAndTrueToLoopCondition(TCompiler *compiler, TIntermNode *root)
+{
+ AddAndTrueToLoopConditionTraverser traverser;
+ root->traverse(&traverser);
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h
new file mode 100644
index 0000000000..d23158e81a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/AddAndTrueToLoopCondition.h
@@ -0,0 +1,31 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// Rewrite condition in for and while loops to work around driver bug on Intel Mac.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_ADDANDTRUETOLOOPCONDITION_H_
+#define COMPILER_TRANSLATOR_TREEOPS_APPLE_ADDANDTRUETOLOOPCONDITION_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+
+#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS)
+[[nodiscard]] bool AddAndTrueToLoopCondition(TCompiler *compiler, TIntermNode *root);
+#else
+[[nodiscard]] ANGLE_INLINE bool AddAndTrueToLoopCondition(TCompiler *compiler, TIntermNode *root)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_ADDANDTRUETOLOOPCONDITION_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp
new file mode 100644
index 0000000000..de322aadab
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.cpp
@@ -0,0 +1,147 @@
+//
+// Copyright 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// RewriteDoWhile.cpp: rewrites do-while loops using another equivalent
+// construct.
+
+#include "compiler/translator/tree_ops/apple/RewriteDoWhile.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// An AST traverser that rewrites loops of the form
+// do {
+// CODE;
+// } while (CONDITION)
+//
+// to loops of the form
+// bool temp = false;
+// while (true) {
+// if (temp) {
+// if (!CONDITION) {
+// break;
+// }
+// }
+// temp = true;
+// CODE;
+// }
+//
+// The reason we don't use a simpler form, with for example just (temp && !CONDITION) in the
+// while condition, is that short-circuit is often badly supported by driver shader compiler.
+// The double if has the same effect, but forces shader compilers to behave.
+//
+// TODO(cwallez) when UnfoldShortCircuitIntoIf handles loops correctly, revisit this as we might
+// be able to use while (temp || CONDITION) with temp initially set to true then run
+// UnfoldShortCircuitIntoIf
+class DoWhileRewriter : public TIntermTraverser
+{
+ public:
+ DoWhileRewriter(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable)
+ {}
+
+ bool visitBlock(Visit, TIntermBlock *node) override
+ {
+ // A well-formed AST can only have do-while inside TIntermBlock. By doing a prefix traversal
+ // we are able to replace the do-while in the sequence directly as the content of the
+ // do-while will be traversed later.
+
+ TIntermSequence *statements = node->getSequence();
+
+ // The statements vector will have new statements inserted when we encounter a do-while,
+ // which prevents us from using a range-based for loop. Using the usual i++ works, as
+ // the (two) new statements inserted replace the statement at the current position.
+ for (size_t i = 0; i < statements->size(); i++)
+ {
+ TIntermNode *statement = (*statements)[i];
+ TIntermLoop *loop = statement->getAsLoopNode();
+
+ if (loop == nullptr || loop->getType() != ELoopDoWhile)
+ {
+ continue;
+ }
+
+ // Found a loop to change.
+ const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>();
+ TVariable *conditionVariable = CreateTempVariable(mSymbolTable, boolType);
+
+ // bool temp = false;
+ TIntermDeclaration *tempDeclaration =
+ CreateTempInitDeclarationNode(conditionVariable, CreateBoolNode(false));
+
+ // temp = true;
+ TIntermBinary *assignTrue =
+ CreateTempAssignmentNode(conditionVariable, CreateBoolNode(true));
+
+ // if (temp) {
+ // if (!CONDITION) {
+ // break;
+ // }
+ // }
+ TIntermIfElse *breakIf = nullptr;
+ {
+ TIntermBranch *breakStatement = new TIntermBranch(EOpBreak, nullptr);
+
+ TIntermBlock *breakBlock = new TIntermBlock();
+ breakBlock->getSequence()->push_back(breakStatement);
+
+ TIntermUnary *negatedCondition =
+ new TIntermUnary(EOpLogicalNot, loop->getCondition(), nullptr);
+
+ TIntermIfElse *innerIf = new TIntermIfElse(negatedCondition, breakBlock, nullptr);
+
+ TIntermBlock *innerIfBlock = new TIntermBlock();
+ innerIfBlock->getSequence()->push_back(innerIf);
+
+ breakIf = new TIntermIfElse(CreateTempSymbolNode(conditionVariable), innerIfBlock,
+ nullptr);
+ }
+
+ // Assemble the replacement loops, reusing the do-while loop's body and inserting our
+ // statements at the front.
+ TIntermLoop *newLoop = nullptr;
+ {
+ TIntermBlock *body = loop->getBody();
+ if (body == nullptr)
+ {
+ body = new TIntermBlock();
+ }
+ auto sequence = body->getSequence();
+ sequence->insert(sequence->begin(), assignTrue);
+ sequence->insert(sequence->begin(), breakIf);
+
+ newLoop = new TIntermLoop(ELoopWhile, nullptr, CreateBoolNode(true), nullptr, body);
+ }
+
+ TIntermSequence replacement;
+ replacement.push_back(tempDeclaration);
+ replacement.push_back(newLoop);
+
+ node->replaceChildNodeWithMultiple(loop, replacement);
+ }
+ return true;
+ }
+};
+
+} // anonymous namespace
+
+bool RewriteDoWhile(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ DoWhileRewriter rewriter(symbolTable);
+
+ root->traverse(&rewriter);
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h
new file mode 100644
index 0000000000..71bdc8857b
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteDoWhile.h
@@ -0,0 +1,38 @@
+//
+// Copyright 2015 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// RewriteDoWhile.h: rewrite do-while loops as while loops to work around
+// driver bugs
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEDOWHILE_H_
+#define COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEDOWHILE_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS)
+[[nodiscard]] bool RewriteDoWhile(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+#else
+[[nodiscard]] ANGLE_INLINE bool RewriteDoWhile(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEDOWHILE_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp
new file mode 100644
index 0000000000..49dc11efe2
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.cpp
@@ -0,0 +1,1595 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteRowMajorMatrices: Rewrite row-major matrices as column-major.
+//
+
+#include "compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/tree_util/ReplaceVariable.h"
+
+namespace sh
+{
+namespace
+{
+// Only structs with matrices are tracked. If layout(row_major) is applied to a struct that doesn't
+// have matrices, it's silently dropped. This is also used to avoid creating duplicates for inner
+// structs that don't have matrices.
+struct StructConversionData
+{
+ // The converted struct with every matrix transposed.
+ TStructure *convertedStruct = nullptr;
+
+ // The copy-from and copy-to functions copying from a struct to its converted version and back.
+ TFunction *copyFromOriginal = nullptr;
+ TFunction *copyToOriginal = nullptr;
+};
+
+bool DoesFieldContainRowMajorMatrix(const TField *field, bool isBlockRowMajor)
+{
+ TLayoutMatrixPacking matrixPacking = field->type()->getLayoutQualifier().matrixPacking;
+
+ // The field is row major if either explicitly specified as such, or if it inherits it from the
+ // block layout qualifier.
+ if (matrixPacking == EmpColumnMajor || (matrixPacking == EmpUnspecified && !isBlockRowMajor))
+ {
+ return false;
+ }
+
+ // The field is qualified with row_major, but if it's not a matrix or a struct containing
+ // matrices, that's a useless qualifier.
+ const TType *type = field->type();
+ return type->isMatrix() || type->isStructureContainingMatrices();
+}
+
+TField *DuplicateField(const TField *field)
+{
+ return new TField(new TType(*field->type()), field->name(), field->line(), field->symbolType());
+}
+
+void SetColumnMajor(TType *type)
+{
+ TLayoutQualifier layoutQualifier = type->getLayoutQualifier();
+ layoutQualifier.matrixPacking = EmpColumnMajor;
+ type->setLayoutQualifier(layoutQualifier);
+}
+
+TType *TransposeMatrixType(const TType *type)
+{
+ TType *newType = new TType(*type);
+
+ SetColumnMajor(newType);
+
+ newType->setPrimarySize(type->getRows());
+ newType->setSecondarySize(type->getCols());
+
+ return newType;
+}
+
+void CopyArraySizes(const TType *from, TType *to)
+{
+ if (from->isArray())
+ {
+ to->makeArrays(from->getArraySizes());
+ }
+}
+
+// Determine if the node is an index node (array index or struct field selection). For the purposes
+// of this transformation, swizzle nodes are considered index nodes too.
+bool IsIndexNode(TIntermNode *node, TIntermNode *child)
+{
+ if (node->getAsSwizzleNode())
+ {
+ return true;
+ }
+
+ TIntermBinary *binaryNode = node->getAsBinaryNode();
+ if (binaryNode == nullptr || child != binaryNode->getLeft())
+ {
+ return false;
+ }
+
+ TOperator op = binaryNode->getOp();
+
+ return op == EOpIndexDirect || op == EOpIndexDirectInterfaceBlock ||
+ op == EOpIndexDirectStruct || op == EOpIndexIndirect;
+}
+
+TIntermSymbol *CopyToTempVariable(TSymbolTable *symbolTable,
+ TIntermTyped *node,
+ TIntermSequence *prependStatements)
+{
+ TVariable *temp = CreateTempVariable(symbolTable, &node->getType());
+ TIntermDeclaration *tempDecl = CreateTempInitDeclarationNode(temp, node);
+ prependStatements->push_back(tempDecl);
+
+ return new TIntermSymbol(temp);
+}
+
+TIntermAggregate *CreateStructCopyCall(const TFunction *copyFunc, TIntermTyped *expression)
+{
+ TIntermSequence args = {expression};
+ return TIntermAggregate::CreateFunctionCall(*copyFunc, &args);
+}
+
+TIntermTyped *CreateTransposeCall(TSymbolTable *symbolTable, TIntermTyped *expression)
+{
+ TIntermSequence args = {expression};
+ return CreateBuiltInFunctionCallNode("transpose", &args, *symbolTable, 300);
+}
+
+TOperator GetIndex(TSymbolTable *symbolTable,
+ TIntermNode *node,
+ TIntermSequence *indices,
+ TIntermSequence *prependStatements)
+{
+ // Swizzle nodes are converted EOpIndexDirect for simplicity, with one index per swizzle
+ // channel.
+ TIntermSwizzle *asSwizzle = node->getAsSwizzleNode();
+ if (asSwizzle)
+ {
+ for (int channel : asSwizzle->getSwizzleOffsets())
+ {
+ indices->push_back(CreateIndexNode(channel));
+ }
+ return EOpIndexDirect;
+ }
+
+ TIntermBinary *binaryNode = node->getAsBinaryNode();
+ ASSERT(binaryNode);
+
+ TOperator op = binaryNode->getOp();
+ ASSERT(op == EOpIndexDirect || op == EOpIndexDirectInterfaceBlock ||
+ op == EOpIndexDirectStruct || op == EOpIndexIndirect);
+
+ TIntermTyped *rhs = binaryNode->getRight()->deepCopy();
+ if (rhs->getAsConstantUnion() == nullptr)
+ {
+ rhs = CopyToTempVariable(symbolTable, rhs, prependStatements);
+ }
+
+ indices->push_back(rhs);
+ return op;
+}
+
+TIntermTyped *ReplicateIndexNode(TSymbolTable *symbolTable,
+ TIntermNode *node,
+ TIntermTyped *lhs,
+ TIntermSequence *indices)
+{
+ TIntermSwizzle *asSwizzle = node->getAsSwizzleNode();
+ if (asSwizzle)
+ {
+ return new TIntermSwizzle(lhs, asSwizzle->getSwizzleOffsets());
+ }
+
+ TIntermBinary *binaryNode = node->getAsBinaryNode();
+ ASSERT(binaryNode);
+
+ ASSERT(indices->size() == 1);
+ TIntermTyped *rhs = indices->front()->getAsTyped();
+
+ return new TIntermBinary(binaryNode->getOp(), lhs, rhs);
+}
+
+TOperator GetIndexOp(TIntermNode *node)
+{
+ return node->getAsConstantUnion() ? EOpIndexDirect : EOpIndexIndirect;
+}
+
+bool IsConvertedField(TIntermTyped *indexNode,
+ const angle::HashMap<const TField *, bool> &convertedFields)
+{
+ TIntermBinary *asBinary = indexNode->getAsBinaryNode();
+ if (asBinary == nullptr)
+ {
+ return false;
+ }
+
+ if (asBinary->getOp() != EOpIndexDirectInterfaceBlock)
+ {
+ return false;
+ }
+
+ const TInterfaceBlock *interfaceBlock = asBinary->getLeft()->getType().getInterfaceBlock();
+ ASSERT(interfaceBlock);
+
+ TIntermConstantUnion *fieldIndexNode = asBinary->getRight()->getAsConstantUnion();
+ ASSERT(fieldIndexNode);
+ ASSERT(fieldIndexNode->getConstantValue() != nullptr);
+
+ int fieldIndex = fieldIndexNode->getConstantValue()->getIConst();
+ const TField *field = interfaceBlock->fields()[fieldIndex];
+
+ return convertedFields.count(field) > 0 && convertedFields.at(field);
+}
+
+// A helper class to transform expressions of array type. Iterates over every element of the
+// array.
+class TransformArrayHelper
+{
+ public:
+ TransformArrayHelper(TIntermTyped *baseExpression)
+ : mBaseExpression(baseExpression),
+ mBaseExpressionType(baseExpression->getType()),
+ mArrayIndices(mBaseExpressionType.getArraySizes().size(), 0)
+ {}
+
+ TIntermTyped *getNextElement(TIntermTyped *valueExpression, TIntermTyped **valueElementOut)
+ {
+ const TSpan<const unsigned int> &arraySizes = mBaseExpressionType.getArraySizes();
+
+ // If the last index overflows, element enumeration is done.
+ if (mArrayIndices.back() >= arraySizes.back())
+ {
+ return nullptr;
+ }
+
+ TIntermTyped *element = getCurrentElement(mBaseExpression);
+ if (valueExpression)
+ {
+ *valueElementOut = getCurrentElement(valueExpression);
+ }
+
+ incrementIndices(arraySizes);
+ return element;
+ }
+
+ void accumulateForRead(TSymbolTable *symbolTable,
+ TIntermTyped *transformedElement,
+ TIntermSequence *prependStatements)
+ {
+ TIntermTyped *temp = CopyToTempVariable(symbolTable, transformedElement, prependStatements);
+ mReadTransformConstructorArgs.push_back(temp);
+ }
+
+ TIntermTyped *constructReadTransformExpression()
+ {
+ const TSpan<const unsigned int> &baseTypeArraySizes = mBaseExpressionType.getArraySizes();
+ TVector<unsigned int> arraySizes(baseTypeArraySizes.begin(), baseTypeArraySizes.end());
+ TIntermTyped *firstElement = mReadTransformConstructorArgs.front()->getAsTyped();
+ const TType &baseType = firstElement->getType();
+
+ // If N dimensions, acc[0] == size[0] and acc[i] == size[i] * acc[i-1].
+ // The last value is unused, and is not present.
+ TVector<unsigned int> accumulatedArraySizes(arraySizes.size() - 1);
+
+ if (accumulatedArraySizes.size() > 0)
+ {
+ accumulatedArraySizes[0] = arraySizes[0];
+ }
+ for (size_t index = 1; index + 1 < arraySizes.size(); ++index)
+ {
+ accumulatedArraySizes[index] = accumulatedArraySizes[index - 1] * arraySizes[index];
+ }
+
+ return constructReadTransformExpressionHelper(arraySizes, accumulatedArraySizes, baseType,
+ 0);
+ }
+
+ private:
+ TIntermTyped *getCurrentElement(TIntermTyped *expression)
+ {
+ TIntermTyped *element = expression->deepCopy();
+ for (auto it = mArrayIndices.rbegin(); it != mArrayIndices.rend(); ++it)
+ {
+ unsigned int index = *it;
+ element = new TIntermBinary(EOpIndexDirect, element, CreateIndexNode(index));
+ }
+ return element;
+ }
+
+ void incrementIndices(const TSpan<const unsigned int> &arraySizes)
+ {
+ // Assume mArrayIndices is an N digit number, where digit i is in the range
+ // [0, arraySizes[i]). This function increments this number. Last digit is the most
+ // significant digit.
+ for (size_t digitIndex = 0; digitIndex < arraySizes.size(); ++digitIndex)
+ {
+ ++mArrayIndices[digitIndex];
+ if (mArrayIndices[digitIndex] < arraySizes[digitIndex])
+ {
+ break;
+ }
+ if (digitIndex + 1 != arraySizes.size())
+ {
+ // This digit has now overflown and is reset to 0, carry will be added to the next
+ // digit. The most significant digit will keep the overflow though, to make it
+ // clear we have exhausted the range.
+ mArrayIndices[digitIndex] = 0;
+ }
+ }
+ }
+
+ TIntermTyped *constructReadTransformExpressionHelper(
+ const TVector<unsigned int> &arraySizes,
+ const TVector<unsigned int> &accumulatedArraySizes,
+ const TType &baseType,
+ size_t elementsOffset)
+ {
+ ASSERT(!arraySizes.empty());
+
+ TType *transformedType = new TType(baseType);
+ transformedType->makeArrays(arraySizes);
+
+ // If one dimensional, create the constructor with the given elements.
+ if (arraySizes.size() == 1)
+ {
+ ASSERT(accumulatedArraySizes.size() == 0);
+
+ auto sliceStart = mReadTransformConstructorArgs.begin() + elementsOffset;
+ TIntermSequence slice(sliceStart, sliceStart + arraySizes[0]);
+
+ return TIntermAggregate::CreateConstructor(*transformedType, &slice);
+ }
+
+ // If not, create constructors for every column recursively.
+ TVector<unsigned int> subArraySizes(arraySizes.begin(), arraySizes.end() - 1);
+ TVector<unsigned int> subArrayAccumulatedSizes(accumulatedArraySizes.begin(),
+ accumulatedArraySizes.end() - 1);
+
+ TIntermSequence constructorArgs;
+ unsigned int colStride = accumulatedArraySizes.back();
+ for (size_t col = 0; col < arraySizes.back(); ++col)
+ {
+ size_t colElementsOffset = elementsOffset + col * colStride;
+
+ constructorArgs.push_back(constructReadTransformExpressionHelper(
+ subArraySizes, subArrayAccumulatedSizes, baseType, colElementsOffset));
+ }
+
+ return TIntermAggregate::CreateConstructor(*transformedType, &constructorArgs);
+ }
+
+ TIntermTyped *mBaseExpression;
+ const TType &mBaseExpressionType;
+ TVector<unsigned int> mArrayIndices;
+
+ TIntermSequence mReadTransformConstructorArgs;
+};
+
+// Traverser that:
+//
+// 1. Converts |layout(row_major) matCxR M| to |layout(column_major) matRxC Mt|.
+// 2. Converts |layout(row_major) S s| to |layout(column_major) St st|, where S is a struct that
+// contains matrices, and St is a new struct with the transformation in 1 applied to matrix
+// members (recursively).
+// 3. When read from, the following transformations are applied:
+//
+// M -> transpose(Mt)
+// M[c] -> gvecN(Mt[0][c], Mt[1][c], ..., Mt[N-1][c])
+// M[c][r] -> Mt[r][c]
+// M[c].yz -> gvec2(Mt[1][c], Mt[2][c])
+// MArr -> MType[D1]..[DN](transpose(MtArr[0]...[0]), ...)
+// s -> copy_St_to_S(st)
+// sArr -> SType[D1]...[DN](copy_St_to_S(stArr[0]..[0]), ...)
+// (matrix reads through struct are transformed similarly to M)
+//
+// 4. When written to, the following transformations are applied:
+//
+// M = exp -> Mt = transpose(exp)
+// M[c] = exp -> temp = exp
+// Mt[0][c] = temp[0]
+// Mt[1][c] = temp[1]
+// ...
+// Mt[N-1][c] = temp[N-1]
+// M[c][r] = exp -> Mt[r][c] = exp
+// M[c].yz = exp -> temp = exp
+// Mt[1][c] = temp[0]
+// Mt[2][c] = temp[1]
+// MArr = exp -> temp = exp
+// Mt = MtType[D1]..[DN](temp([0]...[0]), ...)
+// s = exp -> st = copy_S_to_St(exp)
+// sArr = exp -> temp = exp
+// St = StType[D1]...[DN](copy_S_to_St(temp[0]..[0]), ...)
+// (matrix writes through struct are transformed similarly to M)
+//
+// 5. If any of the above is passed to an `inout` parameter, both transformations are applied:
+//
+// f(M[c]) -> temp = gvecN(Mt[0][c], Mt[1][c], ..., Mt[N-1][c])
+// f(temp)
+// Mt[0][c] = temp[0]
+// Mt[1][c] = temp[1]
+// ...
+// Mt[N-1][c] = temp[N-1]
+//
+// f(s) -> temp = copy_St_to_S(st)
+// f(temp)
+// st = copy_S_to_St(temp)
+//
+// If passed to an `out` parameter, the `temp` parameter is simply not initialized.
+//
+// 6. If the expression leading to the matrix or struct has array subscripts, temp values are
+// created for them to avoid duplicating side effects.
+//
+class RewriteRowMajorMatricesTraverser : public TIntermTraverser
+{
+ public:
+ RewriteRowMajorMatricesTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
+ : TIntermTraverser(true, true, true, symbolTable),
+ mCompiler(compiler),
+ mStructMapOut(&mOuterPass.structMap),
+ mInterfaceBlockMap(&mOuterPass.interfaceBlockMap),
+ mInterfaceBlockFieldConvertedIn(mOuterPass.interfaceBlockFieldConverted),
+ mCopyFunctionDefinitionsOut(&mOuterPass.copyFunctionDefinitions),
+ mOuterTraverser(nullptr),
+ mInnerPassRoot(nullptr),
+ mIsProcessingInnerPassSubtree(false)
+ {}
+
+ bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
+ {
+ // No need to process declarations in inner passes.
+ if (mInnerPassRoot != nullptr)
+ {
+ return true;
+ }
+
+ if (visit != PreVisit)
+ {
+ return true;
+ }
+
+ const TIntermSequence &sequence = *(node->getSequence());
+
+ TIntermTyped *variable = sequence.front()->getAsTyped();
+ const TType &type = variable->getType();
+
+ // If it's a struct declaration that has matrices, remember it. If a row-major instance
+ // of it is created, it will have to be converted.
+ if (type.isStructSpecifier() && type.isStructureContainingMatrices())
+ {
+ const TStructure *structure = type.getStruct();
+ ASSERT(structure);
+
+ ASSERT(mOuterPass.structMap.count(structure) == 0);
+
+ StructConversionData structData;
+ mOuterPass.structMap[structure] = structData;
+
+ return false;
+ }
+
+ // If it's an interface block, it may have to be converted if it contains any row-major
+ // fields.
+ if (type.isInterfaceBlock() && type.getInterfaceBlock()->containsMatrices())
+ {
+ const TInterfaceBlock *block = type.getInterfaceBlock();
+ ASSERT(block);
+ bool isBlockRowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor;
+
+ const TFieldList &fields = block->fields();
+ bool anyRowMajor = isBlockRowMajor;
+
+ for (const TField *field : fields)
+ {
+ if (DoesFieldContainRowMajorMatrix(field, isBlockRowMajor))
+ {
+ anyRowMajor = true;
+ break;
+ }
+ }
+
+ if (anyRowMajor)
+ {
+ convertInterfaceBlock(node);
+ }
+
+ return false;
+ }
+
+ return true;
+ }
+
+ void visitSymbol(TIntermSymbol *symbol) override
+ {
+ // If in inner pass, only process if the symbol is under that root.
+ if (mInnerPassRoot != nullptr && !mIsProcessingInnerPassSubtree)
+ {
+ return;
+ }
+
+ const TVariable *variable = &symbol->variable();
+ bool needsRewrite = mInterfaceBlockMap->count(variable) != 0;
+
+ // If it's a field of a nameless interface block, it may still need conversion.
+ if (!needsRewrite)
+ {
+ // Nameless interface block field symbols have the interface block pointer set, but are
+ // not interface blocks.
+ if (symbol->getType().getInterfaceBlock() && !variable->getType().isInterfaceBlock())
+ {
+ needsRewrite = convertNamelessInterfaceBlockField(symbol);
+ }
+ }
+
+ if (needsRewrite)
+ {
+ transformExpression(symbol);
+ }
+ }
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ if (node == mInnerPassRoot)
+ {
+ // We only want to process the right-hand side of an assignment in inner passes. When
+ // visit is InVisit, the left-hand side is already processed, and the right-hand side is
+ // next. Set a flag to mark this duration.
+ mIsProcessingInnerPassSubtree = visit == InVisit;
+ }
+
+ return true;
+ }
+
+ TIntermSequence *getStructCopyFunctions() { return &mOuterPass.copyFunctionDefinitions; }
+
+ private:
+ typedef angle::HashMap<const TStructure *, StructConversionData> StructMap;
+ typedef angle::HashMap<const TVariable *, TVariable *> InterfaceBlockMap;
+ typedef angle::HashMap<const TField *, bool> InterfaceBlockFieldConverted;
+
+ RewriteRowMajorMatricesTraverser(
+ TSymbolTable *symbolTable,
+ RewriteRowMajorMatricesTraverser *outerTraverser,
+ InterfaceBlockMap *interfaceBlockMap,
+ const InterfaceBlockFieldConverted &interfaceBlockFieldConverted,
+ StructMap *structMap,
+ TIntermSequence *copyFunctionDefinitions,
+ TIntermBinary *innerPassRoot)
+ : TIntermTraverser(true, true, true, symbolTable),
+ mStructMapOut(structMap),
+ mInterfaceBlockMap(interfaceBlockMap),
+ mInterfaceBlockFieldConvertedIn(interfaceBlockFieldConverted),
+ mCopyFunctionDefinitionsOut(copyFunctionDefinitions),
+ mOuterTraverser(outerTraverser),
+ mInnerPassRoot(innerPassRoot),
+ mIsProcessingInnerPassSubtree(false)
+ {}
+
+ void convertInterfaceBlock(TIntermDeclaration *node)
+ {
+ ASSERT(mInnerPassRoot == nullptr);
+
+ const TIntermSequence &sequence = *(node->getSequence());
+
+ TIntermTyped *variableNode = sequence.front()->getAsTyped();
+ const TType &type = variableNode->getType();
+ const TInterfaceBlock *block = type.getInterfaceBlock();
+ ASSERT(block);
+
+ bool isBlockRowMajor = type.getLayoutQualifier().matrixPacking == EmpRowMajor;
+
+ // Recreate the struct with its row-major fields converted to column-major equivalents.
+ TIntermSequence newDeclarations;
+
+ TFieldList *newFields = new TFieldList;
+ for (const TField *field : block->fields())
+ {
+ TField *newField = nullptr;
+
+ if (DoesFieldContainRowMajorMatrix(field, isBlockRowMajor))
+ {
+ newField = convertField(field, &newDeclarations);
+
+ // Remember that this field was converted.
+ mOuterPass.interfaceBlockFieldConverted[field] = true;
+ }
+ else
+ {
+ newField = DuplicateField(field);
+ }
+
+ newFields->push_back(newField);
+ }
+
+ // Create a new interface block with these fields.
+ TLayoutQualifier blockLayoutQualifier = type.getLayoutQualifier();
+ blockLayoutQualifier.matrixPacking = EmpColumnMajor;
+
+ TInterfaceBlock *newInterfaceBlock =
+ new TInterfaceBlock(mSymbolTable, block->name(), newFields, blockLayoutQualifier,
+ block->symbolType(), block->extensions());
+
+ // Create a new declaration with the new type. Declarations are separated at this point,
+ // so there should be only one variable here.
+ ASSERT(sequence.size() == 1);
+
+ TType *newInterfaceBlockType =
+ new TType(newInterfaceBlock, type.getQualifier(), blockLayoutQualifier);
+
+ TIntermDeclaration *newDeclaration = new TIntermDeclaration;
+ const TVariable *variable = &variableNode->getAsSymbolNode()->variable();
+
+ const TType *newType = newInterfaceBlockType;
+ if (type.isArray())
+ {
+ TType *newArrayType = new TType(*newType);
+ CopyArraySizes(&type, newArrayType);
+ newType = newArrayType;
+ }
+
+ // If the interface block variable itself is temp, use an empty name.
+ bool variableIsTemp = variable->symbolType() == SymbolType::Empty;
+ const ImmutableString &variableName =
+ variableIsTemp ? kEmptyImmutableString : variable->name();
+
+ TVariable *newVariable = new TVariable(mSymbolTable, variableName, newType,
+ variable->symbolType(), variable->extensions());
+
+ newDeclaration->appendDeclarator(new TIntermSymbol(newVariable));
+
+ mOuterPass.interfaceBlockMap[variable] = newVariable;
+
+ newDeclarations.push_back(newDeclaration);
+
+ // Replace the interface block definition with the new one, prepending any new struct
+ // definitions.
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(newDeclarations));
+ }
+
+ bool convertNamelessInterfaceBlockField(TIntermSymbol *symbol)
+ {
+ const TVariable *variable = &symbol->variable();
+ const TInterfaceBlock *interfaceBlock = symbol->getType().getInterfaceBlock();
+
+ // Find the variable corresponding to this interface block. If the interface block
+ // is not rewritten, or this refers to a field that is not rewritten, there's
+ // nothing to do.
+ for (auto iter : *mInterfaceBlockMap)
+ {
+ // Skip other rewritten nameless interface block fields.
+ if (!iter.first->getType().isInterfaceBlock())
+ {
+ continue;
+ }
+
+ // Skip if this is not a field of this rewritten interface block.
+ if (iter.first->getType().getInterfaceBlock() != interfaceBlock)
+ {
+ continue;
+ }
+
+ const ImmutableString symbolName = symbol->getName();
+
+ // Find which field it is
+ const TVector<TField *> fields = interfaceBlock->fields();
+ const size_t fieldIndex = variable->getType().getInterfaceBlockFieldIndex();
+ ASSERT(fieldIndex < fields.size());
+
+ const TField *field = fields[fieldIndex];
+ ASSERT(field->name() == symbolName);
+
+ // If this field doesn't need a rewrite, there's nothing to do.
+ if (mInterfaceBlockFieldConvertedIn.count(field) == 0 ||
+ !mInterfaceBlockFieldConvertedIn.at(field))
+ {
+ break;
+ }
+
+ // Create a new variable that references the replaced interface block.
+ TType *newType = new TType(variable->getType());
+ newType->setInterfaceBlockField(iter.second->getType().getInterfaceBlock(), fieldIndex);
+
+ TVariable *newVariable = new TVariable(mSymbolTable, variable->name(), newType,
+ variable->symbolType(), variable->extensions());
+
+ (*mInterfaceBlockMap)[variable] = newVariable;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ void convertStruct(const TStructure *structure, TIntermSequence *newDeclarations)
+ {
+ ASSERT(mInnerPassRoot == nullptr);
+
+ ASSERT(mOuterPass.structMap.count(structure) != 0);
+ StructConversionData *structData = &mOuterPass.structMap[structure];
+
+ if (structData->convertedStruct)
+ {
+ return;
+ }
+
+ TFieldList *newFields = new TFieldList;
+ for (const TField *field : structure->fields())
+ {
+ newFields->push_back(convertField(field, newDeclarations));
+ }
+
+ // Create unique names for the converted structs. We can't leave them nameless and have
+ // a name autogenerated similar to temp variables, as nameless structs exist. A fake
+ // variable is created for the sole purpose of generating a temp name.
+ TVariable *newStructTypeName =
+ new TVariable(mSymbolTable, kEmptyImmutableString,
+ StaticType::GetBasic<EbtUInt, EbpUndefined>(), SymbolType::Empty);
+
+ TStructure *newStruct = new TStructure(mSymbolTable, newStructTypeName->name(), newFields,
+ SymbolType::AngleInternal);
+ TType *newType = new TType(newStruct, true);
+ TVariable *newStructVar =
+ new TVariable(mSymbolTable, kEmptyImmutableString, newType, SymbolType::Empty);
+
+ TIntermDeclaration *structDecl = new TIntermDeclaration;
+ structDecl->appendDeclarator(new TIntermSymbol(newStructVar));
+
+ newDeclarations->push_back(structDecl);
+
+ structData->convertedStruct = newStruct;
+ }
+
+ TField *convertField(const TField *field, TIntermSequence *newDeclarations)
+ {
+ ASSERT(mInnerPassRoot == nullptr);
+
+ TField *newField = nullptr;
+
+ const TType *fieldType = field->type();
+ TType *newType = nullptr;
+
+ if (fieldType->isStructureContainingMatrices())
+ {
+ // If the field is a struct instance, convert the struct and replace the field
+ // with an instance of the new struct.
+ const TStructure *fieldTypeStruct = fieldType->getStruct();
+ convertStruct(fieldTypeStruct, newDeclarations);
+
+ StructConversionData &structData = mOuterPass.structMap[fieldTypeStruct];
+ newType = new TType(structData.convertedStruct, false);
+ SetColumnMajor(newType);
+ CopyArraySizes(fieldType, newType);
+ }
+ else if (fieldType->isMatrix())
+ {
+ // If the field is a matrix, transpose the matrix and replace the field with
+ // that, removing the matrix packing qualifier.
+ newType = TransposeMatrixType(fieldType);
+ }
+
+ if (newType)
+ {
+ newField = new TField(newType, field->name(), field->line(), field->symbolType());
+ }
+ else
+ {
+ newField = DuplicateField(field);
+ }
+
+ return newField;
+ }
+
+ void determineAccess(TIntermNode *expression,
+ TIntermNode *accessor,
+ bool *isReadOut,
+ bool *isWriteOut)
+ {
+ // If passing to a function, look at whether the parameter is in, out or inout.
+ TIntermAggregate *functionCall = accessor->getAsAggregate();
+
+ if (functionCall)
+ {
+ TIntermSequence *arguments = functionCall->getSequence();
+ for (size_t argIndex = 0; argIndex < arguments->size(); ++argIndex)
+ {
+ if ((*arguments)[argIndex] == expression)
+ {
+ TQualifier qualifier = EvqParamIn;
+
+ // If the aggregate is not a function call, it's a constructor, and so every
+ // argument is an input.
+ const TFunction *function = functionCall->getFunction();
+ if (function)
+ {
+ const TVariable *param = function->getParam(argIndex);
+ qualifier = param->getType().getQualifier();
+ }
+
+ *isReadOut = qualifier != EvqParamOut;
+ *isWriteOut = qualifier == EvqParamOut || qualifier == EvqParamInOut;
+ break;
+ }
+ }
+ return;
+ }
+
+ TIntermBinary *assignment = accessor->getAsBinaryNode();
+ if (assignment && IsAssignment(assignment->getOp()))
+ {
+ // If expression is on the right of assignment, it's being read from.
+ *isReadOut = assignment->getRight() == expression;
+ // If it's on the left of assignment, it's being written to.
+ *isWriteOut = assignment->getLeft() == expression;
+ return;
+ }
+
+ // Any other usage is a read.
+ *isReadOut = true;
+ *isWriteOut = false;
+ }
+
+ void transformExpression(TIntermSymbol *symbol)
+ {
+ // Walk up the parent chain while the nodes are EOpIndex* (whether array indexing or struct
+ // field selection) or swizzle and construct the replacement expression. This traversal can
+ // lead to one of the following possibilities:
+ //
+ // - a.b[N].etc.s (struct, or struct array): copy function should be declared and used,
+ // - a.b[N].etc.M (matrix or matrix array): transpose() should be used,
+ // - a.b[N].etc.M[c] (a column): each element in column needs to be handled separately,
+ // - a.b[N].etc.M[c].yz (multiple elements): similar to whole column, but a subset of
+ // elements,
+ // - a.b[N].etc.M[c][r] (an element): single element to handle.
+ // - a.b[N].etc.x (not struct or matrix): not modified
+ //
+ // primaryIndex will contain c, if any. secondaryIndices will contain {0, ..., R-1}
+ // (if no [r] or swizzle), {r} (if [r]), or {1, 2} (corresponding to .yz) if any.
+ //
+ // In all cases, the base symbol is replaced. |baseExpression| will contain everything up
+ // to (and not including) the last index/swizzle operations, i.e. a.b[N].etc.s/M/x. Any
+ // non constant array subscript is assigned to a temp variable to avoid duplicating side
+ // effects.
+ //
+ // ---
+ //
+ // NOTE that due to the use of insertStatementsInParentBlock, cases like this will be
+ // mistranslated, and this bug is likely present in most transformations that use this
+ // feature:
+ //
+ // if (x == 1 && a.b[x = 2].etc.M = value)
+ //
+ // which will translate to:
+ //
+ // temp = (x = 2)
+ // if (x == 1 && a.b[temp].etc.M = transpose(value))
+ //
+ // See http://anglebug.com/3829.
+ //
+ TIntermTyped *baseExpression =
+ new TIntermSymbol(mInterfaceBlockMap->at(&symbol->variable()));
+ const TStructure *structure = nullptr;
+
+ TIntermNode *primaryIndex = nullptr;
+ TIntermSequence secondaryIndices;
+
+ // In some cases, it is necessary to prepend or append statements. Those are captured in
+ // |prependStatements| and |appendStatements|.
+ TIntermSequence prependStatements;
+ TIntermSequence appendStatements;
+
+ // If the expression is neither a struct or matrix, no modification is necessary.
+ // If it's a struct that doesn't have matrices, again there's no transformation necessary.
+ // If it's an interface block matrix field that didn't need to be transposed, no
+ // transpformation is necessary.
+ //
+ // In all these cases, |baseExpression| contains all of the original expression.
+ //
+ // If the starting symbol itself is a field of a nameless interface block, it needs
+ // conversion if we reach here.
+ bool requiresTransformation = !symbol->getType().isInterfaceBlock();
+
+ uint32_t accessorIndex = 0;
+ TIntermTyped *previousAncestor = symbol;
+ while (IsIndexNode(getAncestorNode(accessorIndex), previousAncestor))
+ {
+ TIntermTyped *ancestor = getAncestorNode(accessorIndex)->getAsTyped();
+ ASSERT(ancestor);
+
+ const TType &previousAncestorType = previousAncestor->getType();
+
+ TIntermSequence indices;
+ TOperator op = GetIndex(mSymbolTable, ancestor, &indices, &prependStatements);
+
+ bool opIsIndex = op == EOpIndexDirect || op == EOpIndexIndirect;
+ bool isArrayIndex = opIsIndex && previousAncestorType.isArray();
+ bool isMatrixIndex = opIsIndex && previousAncestorType.isMatrix();
+
+ // If it's a direct index in a matrix, it's the primary index.
+ bool isMatrixPrimarySubscript = isMatrixIndex && !isArrayIndex;
+ ASSERT(!isMatrixPrimarySubscript ||
+ (primaryIndex == nullptr && secondaryIndices.empty()));
+ // If primary index is seen and the ancestor is still an index, it must be a direct
+ // index as the secondary one. Note that if primaryIndex is set, there can only ever be
+ // one more parent of interest, and that's subscripting the second dimension.
+ bool isMatrixSecondarySubscript = primaryIndex != nullptr;
+ ASSERT(!isMatrixSecondarySubscript || (opIsIndex && !isArrayIndex));
+
+ if (requiresTransformation && isMatrixPrimarySubscript)
+ {
+ ASSERT(indices.size() == 1);
+ primaryIndex = indices.front();
+
+ // Default the secondary indices to include every row. If there's a secondary
+ // subscript provided, it will override this.
+ const uint8_t rows = previousAncestorType.getRows();
+ for (uint8_t r = 0; r < rows; ++r)
+ {
+ secondaryIndices.push_back(CreateIndexNode(r));
+ }
+ }
+ else if (isMatrixSecondarySubscript)
+ {
+ ASSERT(requiresTransformation);
+
+ secondaryIndices = indices;
+
+ // Indices after this point are not interesting. There can't actually be any other
+ // index nodes other than desktop GLSL's swizzles on scalars, like M[1][2].yyy.
+ ++accessorIndex;
+ break;
+ }
+ else
+ {
+ // Replicate the expression otherwise.
+ baseExpression =
+ ReplicateIndexNode(mSymbolTable, ancestor, baseExpression, &indices);
+
+ const TType &ancestorType = ancestor->getType();
+ structure = ancestorType.getStruct();
+
+ requiresTransformation =
+ requiresTransformation ||
+ IsConvertedField(ancestor, mInterfaceBlockFieldConvertedIn);
+
+ // If we reach a point where the expression is neither a matrix-containing struct
+ // nor a matrix, there's no transformation required. This can happen if we decend
+ // through a struct marked with row-major but arrive at a member that doesn't
+ // include a matrix.
+ if (!ancestorType.isMatrix() && !ancestorType.isStructureContainingMatrices())
+ {
+ requiresTransformation = false;
+ }
+ }
+
+ previousAncestor = ancestor;
+ ++accessorIndex;
+ }
+
+ TIntermNode *originalExpression =
+ accessorIndex == 0 ? symbol : getAncestorNode(accessorIndex - 1);
+ TIntermNode *accessor = getAncestorNode(accessorIndex);
+
+ // if accessor is EOpArrayLength, we don't need to perform any transformations either.
+ // Note that this only applies to unsized arrays, as the RemoveArrayLengthMethod()
+ // transformation would have removed this operation otherwise.
+ TIntermUnary *accessorAsUnary = accessor->getAsUnaryNode();
+ if (requiresTransformation && accessorAsUnary && accessorAsUnary->getOp() == EOpArrayLength)
+ {
+ ASSERT(accessorAsUnary->getOperand() == originalExpression);
+ ASSERT(accessorAsUnary->getOperand()->getType().isUnsizedArray());
+
+ requiresTransformation = false;
+
+ // We need to replace the whole expression including the EOpArrayLength, to avoid
+ // confusing the replacement code as the original and new expressions don't have the
+ // same type (one is the transpose of the other). This doesn't affect the .length()
+ // operation, so this replacement is ok, though it's not worth special-casing this in
+ // the node replacement algorithm.
+ //
+ // Note: the |if (!requiresTransformation)| immediately below will be entered after
+ // this.
+ originalExpression = accessor;
+ accessor = getAncestorNode(accessorIndex + 1);
+ baseExpression = new TIntermUnary(EOpArrayLength, baseExpression, nullptr);
+ }
+
+ if (!requiresTransformation)
+ {
+ ASSERT(primaryIndex == nullptr);
+ queueReplacementWithParent(accessor, originalExpression, baseExpression,
+ OriginalNode::IS_DROPPED);
+
+ RewriteRowMajorMatricesTraverser *traverser = mOuterTraverser ? mOuterTraverser : this;
+ traverser->insertStatementsInParentBlock(prependStatements, appendStatements);
+ return;
+ }
+
+ ASSERT(structure == nullptr || primaryIndex == nullptr);
+ ASSERT(structure != nullptr || baseExpression->getType().isMatrix());
+
+ // At the end, we can determine if the expression is being read from or written to (or both,
+ // if sent as an inout parameter to a function). For the sake of the transformation, the
+ // left-hand side of operations like += can be treated as "written to", without necessarily
+ // "read from".
+ bool isRead = false;
+ bool isWrite = false;
+
+ determineAccess(originalExpression, accessor, &isRead, &isWrite);
+
+ ASSERT(isRead || isWrite);
+
+ TIntermTyped *readExpression = nullptr;
+ if (isRead)
+ {
+ readExpression = transformReadExpression(
+ baseExpression, primaryIndex, &secondaryIndices, structure, &prependStatements);
+
+ // If both read from and written to (i.e. passed to inout parameter), store the
+ // expression in a temp variable and pass that to the function.
+ if (isWrite)
+ {
+ readExpression =
+ CopyToTempVariable(mSymbolTable, readExpression, &prependStatements);
+ }
+
+ // Replace the original expression with the transformed one. Read transformations
+ // always generate a single expression that can be used in place of the original (as
+ // oppposed to write transformations that can generate multiple statements).
+ queueReplacementWithParent(accessor, originalExpression, readExpression,
+ OriginalNode::IS_DROPPED);
+ }
+
+ TIntermSequence postTransformPrependStatements;
+ TIntermSequence *writeStatements = &appendStatements;
+ TOperator assignmentOperator = EOpAssign;
+
+ if (isWrite)
+ {
+ TIntermTyped *valueExpression = readExpression;
+
+ if (!valueExpression)
+ {
+ // If there's already a read expression, this was an inout parameter and
+ // |valueExpression| will contain the temp variable that was passed to the function
+ // instead.
+ //
+ // If not, then the modification is either through being passed as an out parameter
+ // to a function, or an assignment. In the former case, create a temp variable to
+ // be passed to the function. In the latter case, create a temp variable that holds
+ // the right hand side expression.
+ //
+ // In either case, use that temp value as the value to assign to |baseExpression|.
+
+ TVariable *temp =
+ CreateTempVariable(mSymbolTable, &originalExpression->getAsTyped()->getType());
+ TIntermDeclaration *tempDecl = nullptr;
+
+ valueExpression = new TIntermSymbol(temp);
+
+ TIntermBinary *assignment = accessor->getAsBinaryNode();
+ if (assignment)
+ {
+ assignmentOperator = assignment->getOp();
+ ASSERT(IsAssignment(assignmentOperator));
+
+ // We are converting the assignment to the left-hand side of an expression in
+ // the form M=exp. A subexpression of exp itself could require a
+ // transformation. This complicates things as there would be two replacements:
+ //
+ // - Replace M=exp with temp (because the return value of the assignment could
+ // be used)
+ // - Replace exp with exp2, where parent is M=exp
+ //
+ // The second replacement however is ineffective as the whole of M=exp is
+ // already transformed. What's worse, M=exp is transformed without taking exp's
+ // transformations into account. To address this issue, this same traverser is
+ // called on the right-hand side expression, with a special flag such that it
+ // only processes that expression.
+ //
+ RewriteRowMajorMatricesTraverser *outerTraverser =
+ mOuterTraverser ? mOuterTraverser : this;
+ RewriteRowMajorMatricesTraverser rhsTraverser(
+ mSymbolTable, outerTraverser, mInterfaceBlockMap,
+ mInterfaceBlockFieldConvertedIn, mStructMapOut, mCopyFunctionDefinitionsOut,
+ assignment);
+ getRootNode()->traverse(&rhsTraverser);
+ bool valid = rhsTraverser.updateTree(mCompiler, getRootNode());
+ ASSERT(valid);
+
+ tempDecl = CreateTempInitDeclarationNode(temp, assignment->getRight());
+
+ // Replace the whole assignment expression with the right-hand side as a read
+ // expression, in case the result of the assignment is used. For example, this
+ // transforms:
+ //
+ // if ((M += exp) == X)
+ // {
+ // // use M
+ // }
+ //
+ // to:
+ //
+ // temp = exp;
+ // M += transform(temp);
+ // if (transform(M) == X)
+ // {
+ // // use M
+ // }
+ //
+ // Note that in this case the assignment to M must be prepended in the parent
+ // block. In contrast, when sent to a function, the assignment to M should be
+ // done after the current function call is done.
+ //
+ // If the read from M itself (to replace assigmnet) needs to generate extra
+ // statements, they should be appended after the statements that write to M.
+ // These statements are stored in postTransformPrependStatements and appended to
+ // prependStatements in the end.
+ //
+ writeStatements = &prependStatements;
+
+ TIntermTyped *assignmentResultExpression = transformReadExpression(
+ baseExpression->deepCopy(), primaryIndex, &secondaryIndices, structure,
+ &postTransformPrependStatements);
+
+ // Replace the whole assignment, instead of just the right hand side.
+ TIntermNode *accessorParent = getAncestorNode(accessorIndex + 1);
+ queueReplacementWithParent(accessorParent, accessor, assignmentResultExpression,
+ OriginalNode::IS_DROPPED);
+ }
+ else
+ {
+ tempDecl = CreateTempDeclarationNode(temp);
+
+ // Replace the write expression (a function call argument) with the temp
+ // variable.
+ queueReplacementWithParent(accessor, originalExpression, valueExpression,
+ OriginalNode::IS_DROPPED);
+ }
+ prependStatements.push_back(tempDecl);
+ }
+
+ if (isRead)
+ {
+ baseExpression = baseExpression->deepCopy();
+ }
+ transformWriteExpression(baseExpression, primaryIndex, &secondaryIndices, structure,
+ valueExpression, assignmentOperator, writeStatements);
+ }
+
+ prependStatements.insert(prependStatements.end(), postTransformPrependStatements.begin(),
+ postTransformPrependStatements.end());
+
+ RewriteRowMajorMatricesTraverser *traverser = mOuterTraverser ? mOuterTraverser : this;
+ traverser->insertStatementsInParentBlock(prependStatements, appendStatements);
+ }
+
+ TIntermTyped *transformReadExpression(TIntermTyped *baseExpression,
+ TIntermNode *primaryIndex,
+ TIntermSequence *secondaryIndices,
+ const TStructure *structure,
+ TIntermSequence *prependStatements)
+ {
+ const TType &baseExpressionType = baseExpression->getType();
+
+ if (structure)
+ {
+ ASSERT(primaryIndex == nullptr && secondaryIndices->empty());
+ ASSERT(mStructMapOut->count(structure) != 0);
+ ASSERT((*mStructMapOut)[structure].convertedStruct != nullptr);
+
+ // Declare copy-from-converted-to-original-struct function (if not already).
+ declareStructCopyToOriginal(structure);
+
+ const TFunction *copyToOriginal = (*mStructMapOut)[structure].copyToOriginal;
+
+ if (baseExpressionType.isArray())
+ {
+ // If base expression is an array, transform every element.
+ TransformArrayHelper transformHelper(baseExpression);
+
+ TIntermTyped *element = nullptr;
+ while ((element = transformHelper.getNextElement(nullptr, nullptr)) != nullptr)
+ {
+ TIntermTyped *transformedElement =
+ CreateStructCopyCall(copyToOriginal, element);
+ transformHelper.accumulateForRead(mSymbolTable, transformedElement,
+ prependStatements);
+ }
+ return transformHelper.constructReadTransformExpression();
+ }
+ else
+ {
+ // If not reading an array, the result is simply a call to this function with the
+ // base expression.
+ return CreateStructCopyCall(copyToOriginal, baseExpression);
+ }
+ }
+
+ // If not indexed, the result is transpose(exp)
+ if (primaryIndex == nullptr)
+ {
+ ASSERT(secondaryIndices->empty());
+
+ if (baseExpressionType.isArray())
+ {
+ // If array, transpose every element.
+ TransformArrayHelper transformHelper(baseExpression);
+
+ TIntermTyped *element = nullptr;
+ while ((element = transformHelper.getNextElement(nullptr, nullptr)) != nullptr)
+ {
+ TIntermTyped *transformedElement = CreateTransposeCall(mSymbolTable, element);
+ transformHelper.accumulateForRead(mSymbolTable, transformedElement,
+ prependStatements);
+ }
+ return transformHelper.constructReadTransformExpression();
+ }
+ else
+ {
+ return CreateTransposeCall(mSymbolTable, baseExpression);
+ }
+ }
+
+ // If indexed the result is a vector (or just one element) where the primary and secondary
+ // indices are swapped.
+ ASSERT(!secondaryIndices->empty());
+
+ TOperator primaryIndexOp = GetIndexOp(primaryIndex);
+ TIntermTyped *primaryIndexAsTyped = primaryIndex->getAsTyped();
+
+ TIntermSequence transposedColumn;
+ for (TIntermNode *secondaryIndex : *secondaryIndices)
+ {
+ TOperator secondaryIndexOp = GetIndexOp(secondaryIndex);
+ TIntermTyped *secondaryIndexAsTyped = secondaryIndex->getAsTyped();
+
+ TIntermBinary *colIndexed = new TIntermBinary(
+ secondaryIndexOp, baseExpression->deepCopy(), secondaryIndexAsTyped->deepCopy());
+ TIntermBinary *colRowIndexed =
+ new TIntermBinary(primaryIndexOp, colIndexed, primaryIndexAsTyped->deepCopy());
+
+ transposedColumn.push_back(colRowIndexed);
+ }
+
+ if (secondaryIndices->size() == 1)
+ {
+ // If only one element, return that directly.
+ return transposedColumn.front()->getAsTyped();
+ }
+
+ // Otherwise create a constructor with the appropriate dimension.
+ TType *vecType = new TType(baseExpressionType.getBasicType(), secondaryIndices->size());
+ return TIntermAggregate::CreateConstructor(*vecType, &transposedColumn);
+ }
+
+ void transformWriteExpression(TIntermTyped *baseExpression,
+ TIntermNode *primaryIndex,
+ TIntermSequence *secondaryIndices,
+ const TStructure *structure,
+ TIntermTyped *valueExpression,
+ TOperator assignmentOperator,
+ TIntermSequence *writeStatements)
+ {
+ const TType &baseExpressionType = baseExpression->getType();
+
+ if (structure)
+ {
+ ASSERT(primaryIndex == nullptr && secondaryIndices->empty());
+ ASSERT(mStructMapOut->count(structure) != 0);
+ ASSERT((*mStructMapOut)[structure].convertedStruct != nullptr);
+
+ // Declare copy-to-converted-from-original-struct function (if not already).
+ declareStructCopyFromOriginal(structure);
+
+ // The result is call to this function with the value expression assigned to base
+ // expression.
+ const TFunction *copyFromOriginal = (*mStructMapOut)[structure].copyFromOriginal;
+
+ if (baseExpressionType.isArray())
+ {
+ // If array, assign every element.
+ TransformArrayHelper transformHelper(baseExpression);
+
+ TIntermTyped *element = nullptr;
+ TIntermTyped *valueElement = nullptr;
+ while ((element = transformHelper.getNextElement(valueExpression, &valueElement)) !=
+ nullptr)
+ {
+ TIntermTyped *functionCall =
+ CreateStructCopyCall(copyFromOriginal, valueElement);
+ writeStatements->push_back(new TIntermBinary(EOpAssign, element, functionCall));
+ }
+ }
+ else
+ {
+ TIntermTyped *functionCall =
+ CreateStructCopyCall(copyFromOriginal, valueExpression->deepCopy());
+ writeStatements->push_back(
+ new TIntermBinary(EOpAssign, baseExpression, functionCall));
+ }
+
+ return;
+ }
+
+ // If not indexed, the result is transpose(exp)
+ if (primaryIndex == nullptr)
+ {
+ ASSERT(secondaryIndices->empty());
+
+ if (baseExpressionType.isArray())
+ {
+ // If array, assign every element.
+ TransformArrayHelper transformHelper(baseExpression);
+
+ TIntermTyped *element = nullptr;
+ TIntermTyped *valueElement = nullptr;
+ while ((element = transformHelper.getNextElement(valueExpression, &valueElement)) !=
+ nullptr)
+ {
+ TIntermTyped *valueTransposed = CreateTransposeCall(mSymbolTable, valueElement);
+ writeStatements->push_back(
+ new TIntermBinary(EOpAssign, element, valueTransposed));
+ }
+ }
+ else
+ {
+ TIntermTyped *valueTransposed =
+ CreateTransposeCall(mSymbolTable, valueExpression->deepCopy());
+ writeStatements->push_back(
+ new TIntermBinary(assignmentOperator, baseExpression, valueTransposed));
+ }
+
+ return;
+ }
+
+ // If indexed, create one assignment per secondary index. If the right-hand side is a
+ // scalar, it's used with every assignment. If it's a vector, the assignment is
+ // per-component. The right-hand side cannot be a matrix as that would imply left-hand
+ // side being a matrix too, which is covered above where |primaryIndex == nullptr|.
+ ASSERT(!secondaryIndices->empty());
+
+ bool isValueExpressionScalar = valueExpression->getType().getNominalSize() == 1;
+ ASSERT(isValueExpressionScalar || valueExpression->getType().getNominalSize() ==
+ static_cast<int>(secondaryIndices->size()));
+
+ TOperator primaryIndexOp = GetIndexOp(primaryIndex);
+ TIntermTyped *primaryIndexAsTyped = primaryIndex->getAsTyped();
+
+ for (TIntermNode *secondaryIndex : *secondaryIndices)
+ {
+ TOperator secondaryIndexOp = GetIndexOp(secondaryIndex);
+ TIntermTyped *secondaryIndexAsTyped = secondaryIndex->getAsTyped();
+
+ TIntermBinary *colIndexed = new TIntermBinary(
+ secondaryIndexOp, baseExpression->deepCopy(), secondaryIndexAsTyped->deepCopy());
+ TIntermBinary *colRowIndexed =
+ new TIntermBinary(primaryIndexOp, colIndexed, primaryIndexAsTyped->deepCopy());
+
+ TIntermTyped *valueExpressionIndexed = valueExpression->deepCopy();
+ if (!isValueExpressionScalar)
+ {
+ valueExpressionIndexed = new TIntermBinary(secondaryIndexOp, valueExpressionIndexed,
+ secondaryIndexAsTyped->deepCopy());
+ }
+
+ writeStatements->push_back(
+ new TIntermBinary(assignmentOperator, colRowIndexed, valueExpressionIndexed));
+ }
+ }
+
+ const TFunction *getCopyStructFieldFunction(const TType *fromFieldType,
+ const TType *toFieldType,
+ bool isCopyToOriginal)
+ {
+ ASSERT(fromFieldType->getStruct());
+ ASSERT(toFieldType->getStruct());
+
+ // If copying from or to the original struct, the "to" field struct could require
+ // conversion to or from the "from" field struct. |isCopyToOriginal| tells us if we
+ // should expect to find toField or fromField in mStructMapOut, if true or false
+ // respectively.
+ const TFunction *fieldCopyFunction = nullptr;
+ if (isCopyToOriginal)
+ {
+ const TStructure *toFieldStruct = toFieldType->getStruct();
+
+ auto iter = mStructMapOut->find(toFieldStruct);
+ if (iter != mStructMapOut->end())
+ {
+ declareStructCopyToOriginal(toFieldStruct);
+ fieldCopyFunction = iter->second.copyToOriginal;
+ }
+ }
+ else
+ {
+ const TStructure *fromFieldStruct = fromFieldType->getStruct();
+
+ auto iter = mStructMapOut->find(fromFieldStruct);
+ if (iter != mStructMapOut->end())
+ {
+ declareStructCopyFromOriginal(fromFieldStruct);
+ fieldCopyFunction = iter->second.copyFromOriginal;
+ }
+ }
+
+ return fieldCopyFunction;
+ }
+
+ void addFieldCopy(TIntermBlock *body,
+ TIntermTyped *to,
+ TIntermTyped *from,
+ bool isCopyToOriginal)
+ {
+ const TType &fromType = from->getType();
+ const TType &toType = to->getType();
+
+ TIntermTyped *rhs = from;
+
+ if (fromType.getStruct())
+ {
+ const TFunction *fieldCopyFunction =
+ getCopyStructFieldFunction(&fromType, &toType, isCopyToOriginal);
+
+ if (fieldCopyFunction)
+ {
+ rhs = CreateStructCopyCall(fieldCopyFunction, from);
+ }
+ }
+ else if (fromType.isMatrix())
+ {
+ rhs = CreateTransposeCall(mSymbolTable, from);
+ }
+
+ body->appendStatement(new TIntermBinary(EOpAssign, to, rhs));
+ }
+
+ TFunction *declareStructCopy(const TStructure *from,
+ const TStructure *to,
+ bool isCopyToOriginal)
+ {
+ TType *fromType = new TType(from, true);
+ TType *toType = new TType(to, true);
+
+ // Create the parameter and return value variables.
+ TVariable *fromVar = new TVariable(mSymbolTable, ImmutableString("from"), fromType,
+ SymbolType::AngleInternal);
+ TVariable *toVar =
+ new TVariable(mSymbolTable, ImmutableString("to"), toType, SymbolType::AngleInternal);
+
+ TIntermSymbol *fromSymbol = new TIntermSymbol(fromVar);
+ TIntermSymbol *toSymbol = new TIntermSymbol(toVar);
+
+ // Create the function body as statements are generated.
+ TIntermBlock *body = new TIntermBlock;
+
+ // Declare the result variable.
+ TIntermDeclaration *toDecl = new TIntermDeclaration();
+ toDecl->appendDeclarator(toSymbol);
+ body->appendStatement(toDecl);
+
+ // Iterate over fields of the struct and copy one by one, transposing the matrices. If a
+ // struct is encountered that requires a transformation, this function is recursively
+ // called. As a result, it is important that the copy functions are placed in the code in
+ // order.
+ const TFieldList &fromFields = from->fields();
+ const TFieldList &toFields = to->fields();
+ ASSERT(fromFields.size() == toFields.size());
+
+ for (size_t fieldIndex = 0; fieldIndex < fromFields.size(); ++fieldIndex)
+ {
+ TIntermTyped *fieldIndexNode = CreateIndexNode(static_cast<int>(fieldIndex));
+
+ TIntermTyped *fromField =
+ new TIntermBinary(EOpIndexDirectStruct, fromSymbol->deepCopy(), fieldIndexNode);
+ TIntermTyped *toField = new TIntermBinary(EOpIndexDirectStruct, toSymbol->deepCopy(),
+ fieldIndexNode->deepCopy());
+
+ const TType *fromFieldType = fromFields[fieldIndex]->type();
+ bool isStructOrMatrix = fromFieldType->getStruct() || fromFieldType->isMatrix();
+
+ if (fromFieldType->isArray() && isStructOrMatrix)
+ {
+ // If struct or matrix array, we need to copy element by element.
+ TransformArrayHelper transformHelper(toField);
+
+ TIntermTyped *toElement = nullptr;
+ TIntermTyped *fromElement = nullptr;
+ while ((toElement = transformHelper.getNextElement(fromField, &fromElement)) !=
+ nullptr)
+ {
+ addFieldCopy(body, toElement, fromElement, isCopyToOriginal);
+ }
+ }
+ else
+ {
+ addFieldCopy(body, toField, fromField, isCopyToOriginal);
+ }
+ }
+
+ // Add return statement.
+ body->appendStatement(new TIntermBranch(EOpReturn, toSymbol->deepCopy()));
+
+ // Declare the function
+ TFunction *copyFunction = new TFunction(mSymbolTable, kEmptyImmutableString,
+ SymbolType::AngleInternal, toType, true);
+ copyFunction->addParameter(fromVar);
+
+ TIntermFunctionDefinition *functionDef =
+ CreateInternalFunctionDefinitionNode(*copyFunction, body);
+ mCopyFunctionDefinitionsOut->push_back(functionDef);
+
+ return copyFunction;
+ }
+
+ void declareStructCopyFromOriginal(const TStructure *structure)
+ {
+ StructConversionData *structData = &(*mStructMapOut)[structure];
+ if (structData->copyFromOriginal)
+ {
+ return;
+ }
+
+ structData->copyFromOriginal =
+ declareStructCopy(structure, structData->convertedStruct, false);
+ }
+
+ void declareStructCopyToOriginal(const TStructure *structure)
+ {
+ StructConversionData *structData = &(*mStructMapOut)[structure];
+ if (structData->copyToOriginal)
+ {
+ return;
+ }
+
+ structData->copyToOriginal =
+ declareStructCopy(structData->convertedStruct, structure, true);
+ }
+
+ TCompiler *mCompiler;
+
+ // This traverser can call itself to transform a subexpression before moving on. However, it
+ // needs to accumulate conversion functions in inner passes. The fields below marked with Out
+ // or In are inherited from the outer pass (for inner passes), or point to storage fields in
+ // mOuterPass (for the outer pass). The latter should not be used by the inner passes as they
+ // would be empty, so they are placed inside a struct to make them explicit.
+ struct
+ {
+ StructMap structMap;
+ InterfaceBlockMap interfaceBlockMap;
+ InterfaceBlockFieldConverted interfaceBlockFieldConverted;
+ TIntermSequence copyFunctionDefinitions;
+ } mOuterPass;
+
+ // A map from structures with matrices to their converted version.
+ StructMap *mStructMapOut;
+ // A map from interface block instances with row-major matrices to their converted variable. If
+ // an interface block is nameless, its fields are placed in this map instead. When a variable
+ // in this map is encountered, it signals the start of an expression that my need conversion,
+ // which is either "interfaceBlock.field..." or "field..." if nameless.
+ InterfaceBlockMap *mInterfaceBlockMap;
+ // A map from interface block fields to whether they need to be converted. If a field was
+ // already column-major, it shouldn't be transposed.
+ const InterfaceBlockFieldConverted &mInterfaceBlockFieldConvertedIn;
+
+ TIntermSequence *mCopyFunctionDefinitionsOut;
+
+ // If set, it's an inner pass and this will point to the outer pass traverser. All statement
+ // insertions are stored in the outer traverser and applied at once in the end. This prevents
+ // the inner passes from adding statements which invalidates the outer traverser's statement
+ // position tracking.
+ RewriteRowMajorMatricesTraverser *mOuterTraverser;
+
+ // If set, it's an inner pass that should only process the right-hand side of this particular
+ // node.
+ TIntermBinary *mInnerPassRoot;
+ bool mIsProcessingInnerPassSubtree;
+};
+
+} // anonymous namespace
+
+bool RewriteRowMajorMatrices(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ RewriteRowMajorMatricesTraverser traverser(compiler, symbolTable);
+ root->traverse(&traverser);
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+
+ size_t firstFunctionIndex = FindFirstFunctionDefinitionIndex(root);
+ root->insertChildNodes(firstFunctionIndex, *traverser.getStructCopyFunctions());
+
+ return compiler->validateAST(root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h
new file mode 100644
index 0000000000..2f3577248e
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteRowMajorMatrices.h
@@ -0,0 +1,37 @@
+//
+// Copyright 2019 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteRowMajorMatrices: Change row-major matrices to column-major in uniform and storage
+// buffers.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEROWMAJORMATRICES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEROWMAJORMATRICES_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+#if (defined(ANGLE_ENABLE_GLSL) || defined(ANGLE_ENABLE_METAL)) && \
+ defined(ANGLE_ENABLE_APPLE_WORKAROUNDS)
+[[nodiscard]] bool RewriteRowMajorMatrices(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+#else
+[[nodiscard]] ANGLE_INLINE bool RewriteRowMajorMatrices(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEROWMAJORMATRICES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp
new file mode 100644
index 0000000000..4aa8a28203
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.cpp
@@ -0,0 +1,97 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h"
+
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class Traverser : public TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool Apply(TCompiler *compiler, TIntermNode *root);
+
+ private:
+ Traverser();
+ bool visitUnary(Visit visit, TIntermUnary *node) override;
+ void nextIteration();
+
+ bool mFound = false;
+};
+
+// static
+bool Traverser::Apply(TCompiler *compiler, TIntermNode *root)
+{
+ Traverser traverser;
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.mFound)
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.mFound);
+
+ return true;
+}
+
+Traverser::Traverser() : TIntermTraverser(true, false, false) {}
+
+void Traverser::nextIteration()
+{
+ mFound = false;
+}
+
+bool Traverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+ if (mFound)
+ {
+ return false;
+ }
+
+ // Detect if the current operator is unary minus operator.
+ if (node->getOp() != EOpNegative)
+ {
+ return true;
+ }
+
+ // Detect if the current operand is a float variable.
+ TIntermTyped *fValue = node->getOperand();
+ if (!fValue->getType().isScalarFloat())
+ {
+ return true;
+ }
+
+ // 0.0 - float
+ TIntermTyped *zero = CreateZeroNode(fValue->getType());
+ zero->setLine(fValue->getLine());
+ TIntermBinary *sub = new TIntermBinary(EOpSub, zero, fValue);
+ sub->setLine(fValue->getLine());
+
+ queueReplacement(sub, OriginalNode::IS_DROPPED);
+
+ mFound = true;
+ return false;
+}
+
+} // anonymous namespace
+
+bool RewriteUnaryMinusOperatorFloat(TCompiler *compiler, TIntermNode *root)
+{
+ return Traverser::Apply(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h
new file mode 100644
index 0000000000..30c4d25d01
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/RewriteUnaryMinusOperatorFloat.h
@@ -0,0 +1,31 @@
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Rewrite "-float" to "0.0 - float" to work around unary minus operator on float issue on Intel Mac
+// OSX 10.11.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEUNARYMINUSOPERATORFLOAT_H_
+#define COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEUNARYMINUSOPERATORFLOAT_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+
+#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS)
+[[nodiscard]] bool RewriteUnaryMinusOperatorFloat(TCompiler *compiler, TIntermNode *root);
+#else
+[[nodiscard]] ANGLE_INLINE bool RewriteUnaryMinusOperatorFloat(TCompiler *compiler,
+ TIntermNode *root)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_REWRITEUNARYMINUSOPERATORFLOAT_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp
new file mode 100644
index 0000000000..55e2f8b971
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.cpp
@@ -0,0 +1,74 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h"
+
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// "x || y" is equivalent to "x ? true : y".
+TIntermTernary *UnfoldOR(TIntermTyped *x, TIntermTyped *y)
+{
+ return new TIntermTernary(x, CreateBoolNode(true), y);
+}
+
+// "x && y" is equivalent to "x ? y : false".
+TIntermTernary *UnfoldAND(TIntermTyped *x, TIntermTyped *y)
+{
+ return new TIntermTernary(x, y, CreateBoolNode(false));
+}
+
+// This traverser identifies all the short circuit binary nodes that need to
+// be replaced, and creates the corresponding replacement nodes. However,
+// the actual replacements happen after the traverse through updateTree().
+
+class UnfoldShortCircuitASTTraverser : public TIntermTraverser
+{
+ public:
+ UnfoldShortCircuitASTTraverser() : TIntermTraverser(true, false, false) {}
+
+ bool visitBinary(Visit visit, TIntermBinary *) override;
+};
+
+bool UnfoldShortCircuitASTTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ TIntermTernary *replacement = nullptr;
+
+ switch (node->getOp())
+ {
+ case EOpLogicalOr:
+ replacement = UnfoldOR(node->getLeft(), node->getRight());
+ break;
+ case EOpLogicalAnd:
+ replacement = UnfoldAND(node->getLeft(), node->getRight());
+ break;
+ default:
+ break;
+ }
+ if (replacement)
+ {
+ queueReplacement(replacement, OriginalNode::IS_DROPPED);
+ }
+ return true;
+}
+
+} // anonymous namespace
+
+bool UnfoldShortCircuitAST(TCompiler *compiler, TIntermBlock *root)
+{
+ UnfoldShortCircuitASTTraverser traverser;
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h
new file mode 100644
index 0000000000..648c7190a9
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/apple/UnfoldShortCircuitAST.h
@@ -0,0 +1,33 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// UnfoldShortCircuitAST is an AST traverser to replace short-circuiting
+// operations with ternary operations.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_APPLE_UNFOLDSHORTCIRCUITAST_H_
+#define COMPILER_TRANSLATOR_TREEOPS_APPLE_UNFOLDSHORTCIRCUITAST_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+
+#if defined(ANGLE_ENABLE_GLSL) && defined(ANGLE_ENABLE_APPLE_WORKAROUNDS)
+[[nodiscard]] bool UnfoldShortCircuitAST(TCompiler *compiler, TIntermBlock *root);
+#else
+[[nodiscard]] ANGLE_INLINE bool UnfoldShortCircuitAST(TCompiler *compiler, TIntermBlock *root)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_APPLE_UNFOLDSHORTCIRCUITAST_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp
new file mode 100644
index 0000000000..8790c4f5f2
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.cpp
@@ -0,0 +1,61 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// AddDefaultReturnStatements.cpp: Add default return statements to functions that do not end in a
+// return.
+//
+
+#include "compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+bool NeedsReturnStatement(TIntermFunctionDefinition *node, TType *returnType)
+{
+ *returnType = node->getFunctionPrototype()->getType();
+ if (returnType->getBasicType() == EbtVoid)
+ {
+ return false;
+ }
+
+ TIntermBlock *bodyNode = node->getBody();
+ TIntermBranch *returnNode = bodyNode->getSequence()->back()->getAsBranchNode();
+ if (returnNode != nullptr && returnNode->getFlowOp() == EOpReturn)
+ {
+ return false;
+ }
+
+ return true;
+}
+
+} // anonymous namespace
+
+bool AddDefaultReturnStatements(TCompiler *compiler, TIntermBlock *root)
+{
+ TType returnType;
+ for (TIntermNode *node : *root->getSequence())
+ {
+ TIntermFunctionDefinition *definition = node->getAsFunctionDefinition();
+ if (definition != nullptr && NeedsReturnStatement(definition, &returnType))
+ {
+ TIntermBranch *branch = new TIntermBranch(EOpReturn, CreateZeroNode(returnType));
+
+ TIntermBlock *bodyNode = definition->getBody();
+ bodyNode->getSequence()->push_back(branch);
+ }
+ }
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h
new file mode 100644
index 0000000000..52b601514c
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AddDefaultReturnStatements.h
@@ -0,0 +1,24 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// AddDefaultReturnStatements.h: Add default return statements to functions that do not end in a
+// return.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+
+[[nodiscard]] bool AddDefaultReturnStatements(TCompiler *compiler, TIntermBlock *root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_ADDDEFAULTRETURNSTATEMENTS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp
new file mode 100644
index 0000000000..d9a0c9bebc
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.cpp
@@ -0,0 +1,82 @@
+//
+// Copyright 2022 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h"
+
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class AggregateAssignArraysInSSBOsTraverser : public TIntermTraverser
+{
+ public:
+ AggregateAssignArraysInSSBOsTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable)
+ {}
+
+ protected:
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ // Replace all aggregate assignments to arrays in SSBOs with element-by-element assignments.
+ // TODO(anglebug.com/7363): this implementation only works for the simple case (assignment
+ // statement), not more complex cases such as assignment-as-expression or functions with
+ // side effects in the RHS.
+
+ if (node->getOp() != EOpAssign)
+ {
+ return true;
+ }
+ else if (!node->getLeft()->getType().isArray())
+ {
+ return true;
+ }
+ else if (!IsInShaderStorageBlock(node->getLeft()))
+ {
+ return true;
+ }
+ const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>();
+ auto *indexVariable = CreateTempVariable(mSymbolTable, mediumpIndexType);
+ auto *indexInit =
+ CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType()));
+ auto *arraySizeNode = CreateIndexNode(node->getOutermostArraySize());
+ auto *indexSymbolNode = CreateTempSymbolNode(indexVariable);
+ auto *cond = new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode);
+ auto *indexIncrement =
+ new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr);
+ auto *forLoopBody = new TIntermBlock();
+ auto *indexedLeft =
+ new TIntermBinary(EOpIndexDirect, node->getLeft(), indexSymbolNode->deepCopy());
+ auto *indexedRight =
+ new TIntermBinary(EOpIndexDirect, node->getRight(), indexSymbolNode->deepCopy());
+ auto *assign = new TIntermBinary(TOperator::EOpAssign, indexedLeft, indexedRight);
+ forLoopBody->appendStatement(assign);
+ auto *forLoop =
+ new TIntermLoop(ELoopFor, indexInit, cond, indexIncrement, EnsureBlock(forLoopBody));
+ queueReplacement(forLoop, OriginalNode::IS_DROPPED);
+ return false;
+ }
+};
+
+} // namespace
+
+bool AggregateAssignArraysInSSBOs(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ AggregateAssignArraysInSSBOsTraverser traverser(symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h
new file mode 100644
index 0000000000..ce965bb002
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignArraysInSSBOs.h
@@ -0,0 +1,23 @@
+//
+// Copyright 2022 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool AggregateAssignArraysInSSBOs(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNARRAYSINSSBOS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp
new file mode 100644
index 0000000000..4293f4c4da
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.cpp
@@ -0,0 +1,76 @@
+//
+// Copyright 2022 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h"
+
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class AggregateAssignStructsInSSBOsTraverser : public TIntermTraverser
+{
+ public:
+ AggregateAssignStructsInSSBOsTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable)
+ {}
+
+ protected:
+ bool visitBinary(Visit visit, TIntermBinary *node) override
+ {
+ // Replace all assignments to structs in SSBOs with field-by-field asignments.
+ // TODO(anglebug.com/7362): this implementation only works for the simple case (assignment
+ // statement), not more complex cases such as assignment-as-expression or functions with
+ // side effects in the RHS.
+ const TStructure *s;
+ if (node->getOp() != EOpAssign)
+ {
+ return true;
+ }
+ else if (!IsInShaderStorageBlock(node->getLeft()))
+ {
+ return true;
+ }
+ else if (!(s = node->getLeft()->getType().getStruct()))
+ {
+ return true;
+ }
+ ASSERT(node->getRight()->getType().getStruct() == s);
+ auto *block = new TIntermBlock();
+ for (int i = 0; i < static_cast<int>(s->fields().size()); ++i)
+ {
+ auto *left = new TIntermBinary(EOpIndexDirectStruct, node->getLeft()->deepCopy(),
+ CreateIndexNode(i));
+ auto *right = new TIntermBinary(EOpIndexDirectStruct, node->getRight()->deepCopy(),
+ CreateIndexNode(i));
+ auto *assign = new TIntermBinary(TOperator::EOpAssign, left, right);
+ block->appendStatement(assign);
+ }
+
+ queueReplacement(block, OriginalNode::IS_DROPPED);
+ return false;
+ }
+};
+
+} // namespace
+
+bool AggregateAssignStructsInSSBOs(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ AggregateAssignStructsInSSBOsTraverser traverser(symbolTable);
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h
new file mode 100644
index 0000000000..910345902b
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/AggregateAssignStructsInSSBOs.h
@@ -0,0 +1,23 @@
+//
+// Copyright 2022 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+[[nodiscard]] bool AggregateAssignStructsInSSBOs(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_AGGREGATEASSIGNSTRUCTSINSSBOS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp
new file mode 100644
index 0000000000..54d8fc0808
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.cpp
@@ -0,0 +1,233 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The ArrayReturnValueToOutParameter function changes return values of an array type to out
+// parameters in function definitions, prototypes, and call sites.
+
+#include "compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h"
+
+#include <map>
+
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+constexpr const ImmutableString kReturnValueVariableName("angle_return");
+
+class ArrayReturnValueToOutParameterTraverser : private TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool apply(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+ private:
+ ArrayReturnValueToOutParameterTraverser(TSymbolTable *symbolTable);
+
+ void visitFunctionPrototype(TIntermFunctionPrototype *node) override;
+ bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ bool visitBranch(Visit visit, TIntermBranch *node) override;
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+
+ TIntermAggregate *createReplacementCall(TIntermAggregate *originalCall,
+ TIntermTyped *returnValueTarget);
+
+ // Set when traversal is inside a function with array return value.
+ TIntermFunctionDefinition *mFunctionWithArrayReturnValue;
+
+ struct ChangedFunction
+ {
+ const TVariable *returnValueVariable;
+ const TFunction *func;
+ };
+
+ // Map from function symbol ids to the changed function.
+ std::map<int, ChangedFunction> mChangedFunctions;
+};
+
+TIntermAggregate *ArrayReturnValueToOutParameterTraverser::createReplacementCall(
+ TIntermAggregate *originalCall,
+ TIntermTyped *returnValueTarget)
+{
+ TIntermSequence replacementArguments;
+ TIntermSequence *originalArguments = originalCall->getSequence();
+ for (auto &arg : *originalArguments)
+ {
+ replacementArguments.push_back(arg);
+ }
+ replacementArguments.push_back(returnValueTarget);
+ ASSERT(originalCall->getFunction());
+ const TSymbolUniqueId &originalId = originalCall->getFunction()->uniqueId();
+ TIntermAggregate *replacementCall = TIntermAggregate::CreateFunctionCall(
+ *mChangedFunctions[originalId.get()].func, &replacementArguments);
+ replacementCall->setLine(originalCall->getLine());
+ return replacementCall;
+}
+
+bool ArrayReturnValueToOutParameterTraverser::apply(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable)
+{
+ ArrayReturnValueToOutParameterTraverser arrayReturnValueToOutParam(symbolTable);
+ root->traverse(&arrayReturnValueToOutParam);
+ return arrayReturnValueToOutParam.updateTree(compiler, root);
+}
+
+ArrayReturnValueToOutParameterTraverser::ArrayReturnValueToOutParameterTraverser(
+ TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, true, symbolTable), mFunctionWithArrayReturnValue(nullptr)
+{}
+
+bool ArrayReturnValueToOutParameterTraverser::visitFunctionDefinition(
+ Visit visit,
+ TIntermFunctionDefinition *node)
+{
+ if (node->getFunctionPrototype()->isArray() && visit == PreVisit)
+ {
+ // Replacing the function header is done on visitFunctionPrototype().
+ mFunctionWithArrayReturnValue = node;
+ }
+ if (visit == PostVisit)
+ {
+ mFunctionWithArrayReturnValue = nullptr;
+ }
+ return true;
+}
+
+void ArrayReturnValueToOutParameterTraverser::visitFunctionPrototype(TIntermFunctionPrototype *node)
+{
+ if (node->isArray())
+ {
+ // Replace the whole prototype node with another node that has the out parameter
+ // added. Also set the function to return void.
+ const TSymbolUniqueId &functionId = node->getFunction()->uniqueId();
+ if (mChangedFunctions.find(functionId.get()) == mChangedFunctions.end())
+ {
+ TType *returnValueVariableType = new TType(node->getType());
+ returnValueVariableType->setQualifier(EvqParamOut);
+ ChangedFunction changedFunction;
+ changedFunction.returnValueVariable =
+ new TVariable(mSymbolTable, kReturnValueVariableName, returnValueVariableType,
+ SymbolType::AngleInternal);
+ TFunction *func = new TFunction(mSymbolTable, node->getFunction()->name(),
+ node->getFunction()->symbolType(),
+ StaticType::GetBasic<EbtVoid, EbpUndefined>(), false);
+ for (size_t i = 0; i < node->getFunction()->getParamCount(); ++i)
+ {
+ func->addParameter(node->getFunction()->getParam(i));
+ }
+ func->addParameter(changedFunction.returnValueVariable);
+ changedFunction.func = func;
+ mChangedFunctions[functionId.get()] = changedFunction;
+ }
+ TIntermFunctionPrototype *replacement =
+ new TIntermFunctionPrototype(mChangedFunctions[functionId.get()].func);
+ replacement->setLine(node->getLine());
+
+ queueReplacement(replacement, OriginalNode::IS_DROPPED);
+ }
+}
+
+bool ArrayReturnValueToOutParameterTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ ASSERT(!node->isArray() || node->getOp() != EOpCallInternalRawFunction);
+ if (visit == PreVisit && node->isArray() && node->getOp() == EOpCallFunctionInAST)
+ {
+ // Handle call sites where the returned array is not assigned.
+ // Examples where f() is a function returning an array:
+ // 1. f();
+ // 2. another_array == f();
+ // 3. another_function(f());
+ // 4. return f();
+ // Cases 2 to 4 are already converted to simpler cases by
+ // SeparateExpressionsReturningArrays, so we only need to worry about the case where a
+ // function call returning an array forms an expression by itself.
+ TIntermBlock *parentBlock = getParentNode()->getAsBlock();
+ if (parentBlock)
+ {
+ // replace
+ // f();
+ // with
+ // type s0[size]; f(s0);
+ TIntermSequence replacements;
+
+ // type s0[size];
+ TIntermDeclaration *returnValueDeclaration = nullptr;
+ TVariable *returnValue = DeclareTempVariable(mSymbolTable, new TType(node->getType()),
+ EvqTemporary, &returnValueDeclaration);
+ replacements.push_back(returnValueDeclaration);
+
+ // f(s0);
+ TIntermSymbol *returnValueSymbol = CreateTempSymbolNode(returnValue);
+ replacements.push_back(createReplacementCall(node, returnValueSymbol));
+ mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements));
+ }
+ return false;
+ }
+ return true;
+}
+
+bool ArrayReturnValueToOutParameterTraverser::visitBranch(Visit visit, TIntermBranch *node)
+{
+ if (mFunctionWithArrayReturnValue && node->getFlowOp() == EOpReturn)
+ {
+ // Instead of returning a value, assign to the out parameter and then return.
+ TIntermSequence replacements;
+
+ TIntermTyped *expression = node->getExpression();
+ ASSERT(expression != nullptr);
+ const TSymbolUniqueId &functionId =
+ mFunctionWithArrayReturnValue->getFunction()->uniqueId();
+ ASSERT(mChangedFunctions.find(functionId.get()) != mChangedFunctions.end());
+ TIntermSymbol *returnValueSymbol =
+ new TIntermSymbol(mChangedFunctions[functionId.get()].returnValueVariable);
+ TIntermBinary *replacementAssignment =
+ new TIntermBinary(EOpAssign, returnValueSymbol, expression);
+ replacementAssignment->setLine(expression->getLine());
+ replacements.push_back(replacementAssignment);
+
+ TIntermBranch *replacementBranch = new TIntermBranch(EOpReturn, nullptr);
+ replacementBranch->setLine(node->getLine());
+ replacements.push_back(replacementBranch);
+
+ mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), node,
+ std::move(replacements));
+ }
+ return false;
+}
+
+bool ArrayReturnValueToOutParameterTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (node->getOp() == EOpAssign && node->getLeft()->isArray())
+ {
+ TIntermAggregate *rightAgg = node->getRight()->getAsAggregate();
+ ASSERT(rightAgg == nullptr || rightAgg->getOp() != EOpCallInternalRawFunction);
+ if (rightAgg != nullptr && rightAgg->getOp() == EOpCallFunctionInAST)
+ {
+ TIntermAggregate *replacementCall = createReplacementCall(rightAgg, node->getLeft());
+ queueReplacement(replacementCall, OriginalNode::IS_DROPPED);
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+bool ArrayReturnValueToOutParameter(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable)
+{
+ return ArrayReturnValueToOutParameterTraverser::apply(compiler, root, symbolTable);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h
new file mode 100644
index 0000000000..ae8d04ae9a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ArrayReturnValueToOutParameter.h
@@ -0,0 +1,27 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The ArrayReturnValueToOutParameter function changes return values of an array type to out
+// parameters in function definitions, prototypes and call sites.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool ArrayReturnValueToOutParameter(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_ARRAYRETURNVALUETOOUTPARAMETER_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp
new file mode 100644
index 0000000000..908da7a8c2
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.cpp
@@ -0,0 +1,110 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend
+// may record a variable as aliasing another. Sometimes the alias information gets garbled
+// so we work around this issue by breaking the aliasing chain in inner loops.
+
+#include "compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+// A HLSL compiler developer gave us more details on the root cause and the workaround needed:
+// The root problem is that if the HLSL compiler is applying aliasing information even on
+// incomplete simulations (in this case, a single pass). The bug is triggered by an assignment
+// that comes from a series of assignments, possibly with swizzled or ternary operators with
+// known conditionals, where the source is before the loop.
+// So, a workaround is to add a +0 term to variables the first time they are assigned to in
+// an inner loop (if they are declared in an outside scope, otherwise there is no need).
+// This will break the aliasing chain.
+
+// For simplicity here we add a +0 to any assignment that is in at least two nested loops. Because
+// the bug only shows up with swizzles, and ternary assignment, whole array or whole structure
+// assignment don't need a workaround.
+
+namespace sh
+{
+
+namespace
+{
+
+class AliasingBreaker : public TIntermTraverser
+{
+ public:
+ AliasingBreaker() : TIntermTraverser(true, false, true) {}
+
+ protected:
+ bool visitBinary(Visit visit, TIntermBinary *binary) override
+ {
+ if (visit != PreVisit)
+ {
+ return false;
+ }
+
+ if (mLoopLevel < 2 || !binary->isAssignment())
+ {
+ return true;
+ }
+
+ TIntermTyped *B = binary->getRight();
+ TType type = B->getType();
+
+ if (!type.isScalar() && !type.isVector() && !type.isMatrix())
+ {
+ return true;
+ }
+
+ if (type.isArray() || IsSampler(type.getBasicType()))
+ {
+ return true;
+ }
+
+ // We have a scalar / vector / matrix assignment with loop depth 2.
+ // Transform it from
+ // A = B
+ // to
+ // A = (B + typeof<B>(0));
+
+ TIntermBinary *bPlusZero = new TIntermBinary(EOpAdd, B, CreateZeroNode(type));
+ bPlusZero->setLine(B->getLine());
+
+ binary->replaceChildNode(B, bPlusZero);
+
+ return true;
+ }
+
+ bool visitLoop(Visit visit, TIntermLoop *loop) override
+ {
+ if (visit == PreVisit)
+ {
+ mLoopLevel++;
+ }
+ else
+ {
+ ASSERT(mLoopLevel > 0);
+ mLoopLevel--;
+ }
+
+ return true;
+ }
+
+ private:
+ int mLoopLevel = 0;
+};
+
+} // anonymous namespace
+
+bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root)
+{
+ AliasingBreaker breaker;
+ root->traverse(&breaker);
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h
new file mode 100644
index 0000000000..455c7c322d
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend
+// may record a variable as aliasing another. Sometimes the alias information gets garbled
+// so we work around this issue by breaking the aliasing chain in inner loops.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+
+[[nodiscard]] bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_BREAKVARIABLEALIASINGININNERLOOPS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp
new file mode 100644
index 0000000000..e873db56cd
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.cpp
@@ -0,0 +1,152 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Implementation of the integer pow expressions HLSL bug workaround.
+// See header for more info.
+
+#include "compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h"
+
+#include <cmath>
+#include <cstdlib>
+
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class Traverser : public TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool Apply(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+ private:
+ Traverser(TSymbolTable *symbolTable);
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ void nextIteration();
+
+ bool mFound = false;
+};
+
+// static
+bool Traverser::Apply(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ Traverser traverser(symbolTable);
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.mFound)
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.mFound);
+
+ return true;
+}
+
+Traverser::Traverser(TSymbolTable *symbolTable) : TIntermTraverser(true, false, false, symbolTable)
+{}
+
+void Traverser::nextIteration()
+{
+ mFound = false;
+}
+
+bool Traverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ if (mFound)
+ {
+ return false;
+ }
+
+ // Test 0: skip non-pow operators.
+ if (node->getOp() != EOpPow)
+ {
+ return true;
+ }
+
+ const TIntermSequence *sequence = node->getSequence();
+ ASSERT(sequence->size() == 2u);
+ const TIntermConstantUnion *constantExponent = sequence->at(1)->getAsConstantUnion();
+
+ // Test 1: check for a single constant.
+ if (!constantExponent || constantExponent->getNominalSize() != 1)
+ {
+ return true;
+ }
+
+ float exponentValue = constantExponent->getConstantValue()->getFConst();
+
+ // Test 2: exponentValue is in the problematic range.
+ if (exponentValue < -5.0f || exponentValue > 9.0f)
+ {
+ return true;
+ }
+
+ // Test 3: exponentValue is integer or pretty close to an integer.
+ if (std::abs(exponentValue - std::round(exponentValue)) > 0.0001f)
+ {
+ return true;
+ }
+
+ // Test 4: skip -1, 0, and 1
+ int exponent = static_cast<int>(std::round(exponentValue));
+ int n = std::abs(exponent);
+ if (n < 2)
+ {
+ return true;
+ }
+
+ // Potential problem case detected, apply workaround.
+
+ TIntermTyped *lhs = sequence->at(0)->getAsTyped();
+ ASSERT(lhs);
+
+ TIntermDeclaration *lhsVariableDeclaration = nullptr;
+ TVariable *lhsVariable =
+ DeclareTempVariable(mSymbolTable, lhs, EvqTemporary, &lhsVariableDeclaration);
+ insertStatementInParentBlock(lhsVariableDeclaration);
+
+ // Create a chain of n-1 multiples.
+ TIntermTyped *current = CreateTempSymbolNode(lhsVariable);
+ for (int i = 1; i < n; ++i)
+ {
+ TIntermBinary *mul = new TIntermBinary(EOpMul, current, CreateTempSymbolNode(lhsVariable));
+ mul->setLine(node->getLine());
+ current = mul;
+ }
+
+ // For negative pow, compute the reciprocal of the positive pow.
+ if (exponent < 0)
+ {
+ TConstantUnion *oneVal = new TConstantUnion();
+ oneVal->setFConst(1.0f);
+ TIntermConstantUnion *oneNode = new TIntermConstantUnion(oneVal, node->getType());
+ TIntermBinary *div = new TIntermBinary(EOpDiv, oneNode, current);
+ current = div;
+ }
+
+ queueReplacement(current, OriginalNode::IS_DROPPED);
+ mFound = true;
+ return false;
+}
+
+} // anonymous namespace
+
+bool ExpandIntegerPowExpressions(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ return Traverser::Apply(compiler, root, symbolTable);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h
new file mode 100644
index 0000000000..2404fb08ac
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/ExpandIntegerPowExpressions.h
@@ -0,0 +1,34 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This mutating tree traversal works around a bug in the HLSL compiler optimizer with "pow" that
+// manifests under the following conditions:
+//
+// - If pow() has a literal exponent value
+// - ... and this value is integer or within 10e-6 of an integer
+// - ... and it is in {-4, -3, -2, 2, 3, 4, 5, 6, 7, 8}
+//
+// The workaround is to replace the pow with a series of multiplies.
+// See http://anglebug.com/851
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool ExpandIntegerPowExpressions(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_EXPANDINTEGERPOWEXPRESSIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp
new file mode 100644
index 0000000000..18d49814ac
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.cpp
@@ -0,0 +1,385 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RecordUniformBlocksWithLargeArrayMember.h:
+// Collect all uniform blocks which have one or more large array members,
+// and the array sizes are greater than or equal to 50. If some of them
+// satify some conditions, we will translate them to StructuredBuffers
+// on Direct3D backend.
+//
+
+#include "compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+// Only when a uniform block member's array size is greater than or equal to
+// kMinArraySizeUseStructuredBuffer, then we may translate the uniform block
+// to a StructuredBuffer on Direct3D backend.
+const unsigned int kMinArraySizeUseStructuredBuffer = 50u;
+
+// There is a maximum of D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT(128) slots that are
+// available for shader resources on Direct3D 11. When shader version is 300, we only use
+// D3D11_COMMONSHADER_SAMPLER_SLOT_COUNT(16) slots for texture units. We allow StructuredBuffer
+// to use the maximum of 60 slots, that is enough here.
+const unsigned int kMaxAllowToUseRegisterCount = 60u;
+
+// Traverser that all uniform blocks which have one or more large array members, and the array
+// sizes are greater than or equal to 50.
+class UniformBlocksWithLargeArrayMemberTraverser : public TIntermTraverser
+{
+ public:
+ UniformBlocksWithLargeArrayMemberTraverser();
+
+ void visitSymbol(TIntermSymbol *node) override;
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+ std::map<int, const TInterfaceBlock *> &getUniformBlockMayTranslation()
+ {
+ return mUniformBlockMayTranslation;
+ }
+ std::map<int, const TInterfaceBlock *> &getUniformBlockNotAllowTranslation()
+ {
+ return mUniformBlockNotAllowTranslation;
+ }
+ std::map<int, unsigned int> &getUniformBlockUsedRegisterCount()
+ {
+ return mUniformBlockUsedRegisterCount;
+ }
+ std::map<int, const TInterfaceBlock *> &getUniformBlockWithLargeArrayMember()
+ {
+ return mUniformBlockWithLargeArrayMember;
+ }
+
+ private:
+ std::map<int, const TInterfaceBlock *> mUniformBlockMayTranslation;
+ std::map<int, const TInterfaceBlock *> mUniformBlockNotAllowTranslation;
+ std::map<int, unsigned int> mUniformBlockUsedRegisterCount;
+ std::map<int, const TInterfaceBlock *> mUniformBlockWithLargeArrayMember;
+};
+
+UniformBlocksWithLargeArrayMemberTraverser::UniformBlocksWithLargeArrayMemberTraverser()
+ : TIntermTraverser(true, true, false)
+{}
+
+static bool IsSupportedTypeForStructuredBuffer(const TType &type)
+{
+ const TStructure *structure = type.getStruct();
+ const TLayoutMatrixPacking matrixPacking = type.getLayoutQualifier().matrixPacking;
+ if (structure)
+ {
+ const TFieldList &fields = structure->fields();
+ for (size_t i = 0; i < fields.size(); i++)
+ {
+ const TType &fieldType = *fields[i]->type();
+ // Do not allow the structure's member is array or structure.
+ if (!fieldType.isArray() && !fieldType.getStruct() &&
+ (fieldType.isScalar() || fieldType.isVector() ||
+ (fieldType.isMatrix() &&
+ ((matrixPacking != EmpRowMajor && fieldType.getRows() == 4) ||
+ (matrixPacking == EmpRowMajor && fieldType.getCols() == 4)))))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ else if (type.isMatrix())
+ {
+ // Only supports the matrix types that we do not need to pad in a structure or an array
+ // explicitly.
+ return (matrixPacking != EmpRowMajor && type.getRows() == 4) ||
+ (matrixPacking == EmpRowMajor && type.getCols() == 4);
+ }
+ else
+ {
+ // Supports vector and scalar types in a structure or an array.
+ return true;
+ }
+}
+
+static bool CanTranslateUniformBlockToStructuredBuffer(const TInterfaceBlock &interfaceBlock)
+{
+ const TLayoutBlockStorage blockStorage = interfaceBlock.blockStorage();
+
+ if (blockStorage == EbsStd140 && interfaceBlock.fields().size() == 1u)
+ {
+ const TType &fieldType = *interfaceBlock.fields()[0]->type();
+ if (fieldType.getNumArraySizes() == 1u &&
+ fieldType.getOutermostArraySize() >= kMinArraySizeUseStructuredBuffer)
+ {
+ return IsSupportedTypeForStructuredBuffer(fieldType);
+ }
+ }
+
+ return false;
+}
+
+static bool FieldIsOrHasLargeArrayField(const TField &field)
+{
+ const TType *type = field.type();
+ if (type->getArraySizeProduct() >= kMinArraySizeUseStructuredBuffer)
+ {
+ return true;
+ }
+
+ const TStructure *structure = type->getStruct();
+ if (structure)
+ {
+ const TFieldList &fields = structure->fields();
+ bool hasLargeArrayField = false;
+ for (size_t i = 0; i < fields.size(); i++)
+ {
+ hasLargeArrayField = FieldIsOrHasLargeArrayField(*fields[i]);
+ if (hasLargeArrayField)
+ {
+ break;
+ }
+ }
+ return hasLargeArrayField;
+ }
+
+ return false;
+}
+
+static bool IsInterfaceBlockWithLargeArrayField(const TInterfaceBlock &interfaceBlock)
+{
+ const TFieldList &fields = interfaceBlock.fields();
+ bool isLargeArrayField = false;
+ for (size_t i = 0; i < fields.size(); i++)
+ {
+ isLargeArrayField = FieldIsOrHasLargeArrayField(*fields[i]);
+ if (isLargeArrayField)
+ {
+ break;
+ }
+ }
+
+ return isLargeArrayField;
+}
+
+void UniformBlocksWithLargeArrayMemberTraverser::visitSymbol(TIntermSymbol *node)
+{
+ const TVariable &variable = node->variable();
+ const TType &variableType = variable.getType();
+ TQualifier qualifier = variable.getType().getQualifier();
+
+ if (qualifier == EvqUniform)
+ {
+ const TInterfaceBlock *interfaceBlock = variableType.getInterfaceBlock();
+ if (interfaceBlock)
+ {
+ if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock))
+ {
+ if (mUniformBlockMayTranslation.count(interfaceBlock->uniqueId().get()) == 0)
+ {
+ mUniformBlockMayTranslation[interfaceBlock->uniqueId().get()] = interfaceBlock;
+ }
+
+ if (!variableType.isInterfaceBlock())
+ {
+ TIntermNode *accessor = getAncestorNode(0);
+ TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode();
+ // The uniform block variable is array type, only indexing operator is allowed
+ // to operate on the variable, otherwise do not translate the uniform block to
+ // HLSL StructuredBuffer.
+ if (!accessorAsBinary ||
+ !(accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect ||
+ accessorAsBinary->getOp() == EOpIndexIndirect)))
+ {
+ if (mUniformBlockNotAllowTranslation.count(
+ interfaceBlock->uniqueId().get()) == 0)
+ {
+ mUniformBlockNotAllowTranslation[interfaceBlock->uniqueId().get()] =
+ interfaceBlock;
+ }
+ }
+ else
+ {
+ if (mUniformBlockUsedRegisterCount.count(
+ interfaceBlock->uniqueId().get()) == 0)
+ {
+ // The uniform block is not an instanced one, so it only uses one
+ // register.
+ mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] = 1;
+ }
+ }
+ }
+ else
+ {
+ if (mUniformBlockUsedRegisterCount.count(interfaceBlock->uniqueId().get()) == 0)
+ {
+ // The uniform block is an instanced one, the count of used registers
+ // depends on the array size of variable.
+ mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] =
+ variableType.isArray() ? variableType.getOutermostArraySize() : 1;
+ }
+ }
+ }
+
+ if (interfaceBlock->blockStorage() == EbsStd140 &&
+ IsInterfaceBlockWithLargeArrayField(*interfaceBlock))
+ {
+ if (!variableType.isInterfaceBlock())
+ {
+ TIntermNode *accessor = getAncestorNode(0);
+ TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode();
+ if (accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect ||
+ accessorAsBinary->getOp() == EOpIndexIndirect))
+ {
+ if (mUniformBlockWithLargeArrayMember.count(
+ interfaceBlock->uniqueId().get()) == 0)
+ {
+ mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] =
+ interfaceBlock;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+bool UniformBlocksWithLargeArrayMemberTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ switch (node->getOp())
+ {
+ case EOpIndexDirect:
+ {
+ if (visit == PreVisit)
+ {
+ const TType &leftType = node->getLeft()->getType();
+ if (leftType.isInterfaceBlock())
+ {
+ const TInterfaceBlock *interfaceBlock = leftType.getInterfaceBlock();
+ if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock) &&
+ mUniformBlockMayTranslation.count(interfaceBlock->uniqueId().get()) == 0)
+ {
+ mUniformBlockMayTranslation[interfaceBlock->uniqueId().get()] =
+ interfaceBlock;
+ if (mUniformBlockUsedRegisterCount.count(
+ interfaceBlock->uniqueId().get()) == 0)
+ {
+ // The uniform block is an instanced one, the count of used registers
+ // depends on the array size of variable.
+ mUniformBlockUsedRegisterCount[interfaceBlock->uniqueId().get()] =
+ leftType.isArray() ? leftType.getOutermostArraySize() : 1;
+ }
+ return false;
+ }
+
+ if (interfaceBlock->blockStorage() == EbsStd140 &&
+ IsInterfaceBlockWithLargeArrayField(*interfaceBlock))
+ {
+ if (mUniformBlockWithLargeArrayMember.count(
+ interfaceBlock->uniqueId().get()) == 0)
+ {
+ mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] =
+ interfaceBlock;
+ }
+ }
+ }
+ }
+ break;
+ }
+ case EOpIndexDirectInterfaceBlock:
+ {
+ if (visit == InVisit)
+ {
+ const TInterfaceBlock *interfaceBlock =
+ node->getLeft()->getType().getInterfaceBlock();
+ if (CanTranslateUniformBlockToStructuredBuffer(*interfaceBlock))
+ {
+ TIntermNode *accessor = getAncestorNode(0);
+ TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode();
+ // The uniform block variable is array type, only indexing operator is allowed
+ // to operate on the variable, otherwise do not translate the uniform block to
+ // HLSL StructuredBuffer.
+ if ((!accessorAsBinary ||
+ !(accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect ||
+ accessorAsBinary->getOp() == EOpIndexIndirect))) &&
+ mUniformBlockNotAllowTranslation.count(interfaceBlock->uniqueId().get()) ==
+ 0)
+ {
+ mUniformBlockNotAllowTranslation[interfaceBlock->uniqueId().get()] =
+ interfaceBlock;
+ return false;
+ }
+ }
+
+ if (interfaceBlock->blockStorage() == EbsStd140 &&
+ IsInterfaceBlockWithLargeArrayField(*interfaceBlock))
+ {
+ TIntermNode *accessor = getAncestorNode(0);
+ TIntermBinary *accessorAsBinary = accessor->getAsBinaryNode();
+ if (accessorAsBinary && (accessorAsBinary->getOp() == EOpIndexDirect ||
+ accessorAsBinary->getOp() == EOpIndexIndirect))
+ {
+ if (mUniformBlockWithLargeArrayMember.count(
+ interfaceBlock->uniqueId().get()) == 0)
+ {
+ mUniformBlockWithLargeArrayMember[interfaceBlock->uniqueId().get()] =
+ interfaceBlock;
+ }
+ }
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ return true;
+}
+} // namespace
+
+bool RecordUniformBlocksWithLargeArrayMember(
+ TIntermNode *root,
+ std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap,
+ std::set<std::string> &slowCompilingUniformBlockSet)
+{
+ UniformBlocksWithLargeArrayMemberTraverser traverser;
+ root->traverse(&traverser);
+ std::map<int, const TInterfaceBlock *> &uniformBlockMayTranslation =
+ traverser.getUniformBlockMayTranslation();
+ std::map<int, const TInterfaceBlock *> &uniformBlockNotAllowTranslation =
+ traverser.getUniformBlockNotAllowTranslation();
+ std::map<int, unsigned int> &uniformBlockUsedRegisterCount =
+ traverser.getUniformBlockUsedRegisterCount();
+ std::map<int, const TInterfaceBlock *> &uniformBlockWithLargeArrayMember =
+ traverser.getUniformBlockWithLargeArrayMember();
+
+ unsigned int usedRegisterCount = 0;
+ for (auto &uniformBlock : uniformBlockMayTranslation)
+ {
+ if (uniformBlockNotAllowTranslation.count(uniformBlock.first) == 0)
+ {
+ usedRegisterCount += uniformBlockUsedRegisterCount[uniformBlock.first];
+ if (usedRegisterCount > kMaxAllowToUseRegisterCount)
+ {
+ break;
+ }
+ uniformBlockOptimizedMap[uniformBlock.first] = uniformBlock.second;
+ }
+ }
+
+ for (auto &uniformBlock : uniformBlockWithLargeArrayMember)
+ {
+ if (uniformBlockOptimizedMap.count(uniformBlock.first) == 0)
+ {
+ slowCompilingUniformBlockSet.insert(uniformBlock.second->name().data());
+ }
+ }
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h
new file mode 100644
index 0000000000..d7825c28cb
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RecordUniformBlocksWithLargeArrayMember.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2020 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RecordUniformBlocksWithLargeArrayMember.h:
+// Collect all uniform blocks which have one or more large array members,
+// and the array sizes are greater than or equal to 50. If some of them
+// satify some conditions, we will translate them to StructuredBuffers
+// on Direct3D backend.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_
+
+#include "compiler/translator/IntermNode.h"
+
+namespace sh
+{
+class TIntermNode;
+
+[[nodiscard]] bool RecordUniformBlocksWithLargeArrayMember(
+ TIntermNode *root,
+ std::map<int, const TInterfaceBlock *> &uniformBlockOptimizedMap,
+ std::set<std::string> &slowCompilingUniformBlockSet);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_RECORDUNIFORMBLOCKSWITHLARGEARRAYMEMBER_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp
new file mode 100644
index 0000000000..6360f1083c
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.cpp
@@ -0,0 +1,270 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveSwitchFallThrough.cpp: Remove fall-through from switch statements.
+// Note that it is unsafe to do further AST transformations on the AST generated
+// by this function. It leaves duplicate nodes in the AST making replacements
+// unreliable.
+
+#include "compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h"
+
+#include "compiler/translator/Diagnostics.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class RemoveSwitchFallThroughTraverser : public TIntermTraverser
+{
+ public:
+ static TIntermBlock *removeFallThrough(TIntermBlock *statementList,
+ PerformanceDiagnostics *perfDiagnostics);
+
+ private:
+ RemoveSwitchFallThroughTraverser(TIntermBlock *statementList,
+ PerformanceDiagnostics *perfDiagnostics);
+
+ void visitSymbol(TIntermSymbol *node) override;
+ void visitConstantUnion(TIntermConstantUnion *node) override;
+ bool visitDeclaration(Visit, TIntermDeclaration *node) override;
+ bool visitBinary(Visit, TIntermBinary *node) override;
+ bool visitUnary(Visit, TIntermUnary *node) override;
+ bool visitTernary(Visit visit, TIntermTernary *node) override;
+ bool visitSwizzle(Visit, TIntermSwizzle *node) override;
+ bool visitIfElse(Visit visit, TIntermIfElse *node) override;
+ bool visitSwitch(Visit, TIntermSwitch *node) override;
+ bool visitCase(Visit, TIntermCase *node) override;
+ bool visitAggregate(Visit, TIntermAggregate *node) override;
+ bool visitBlock(Visit, TIntermBlock *node) override;
+ bool visitLoop(Visit, TIntermLoop *node) override;
+ bool visitBranch(Visit, TIntermBranch *node) override;
+
+ void outputSequence(TIntermSequence *sequence, size_t startIndex);
+ void handlePreviousCase();
+
+ TIntermBlock *mStatementList;
+ TIntermBlock *mStatementListOut;
+ bool mLastStatementWasBreak;
+ TIntermBlock *mPreviousCase;
+ std::vector<TIntermBlock *> mCasesSharingBreak;
+ PerformanceDiagnostics *mPerfDiagnostics;
+};
+
+TIntermBlock *RemoveSwitchFallThroughTraverser::removeFallThrough(
+ TIntermBlock *statementList,
+ PerformanceDiagnostics *perfDiagnostics)
+{
+ RemoveSwitchFallThroughTraverser rm(statementList, perfDiagnostics);
+ ASSERT(statementList);
+ statementList->traverse(&rm);
+ ASSERT(rm.mPreviousCase || statementList->getSequence()->empty());
+ if (!rm.mLastStatementWasBreak && rm.mPreviousCase)
+ {
+ // Make sure that there's a branch at the end of the final case inside the switch statement.
+ // This also ensures that any cases that fall through to the final case will get the break.
+ TIntermBranch *finalBreak = new TIntermBranch(EOpBreak, nullptr);
+ rm.mPreviousCase->getSequence()->push_back(finalBreak);
+ rm.mLastStatementWasBreak = true;
+ }
+ rm.handlePreviousCase();
+ return rm.mStatementListOut;
+}
+
+RemoveSwitchFallThroughTraverser::RemoveSwitchFallThroughTraverser(
+ TIntermBlock *statementList,
+ PerformanceDiagnostics *perfDiagnostics)
+ : TIntermTraverser(true, false, false),
+ mStatementList(statementList),
+ mLastStatementWasBreak(false),
+ mPreviousCase(nullptr),
+ mPerfDiagnostics(perfDiagnostics)
+{
+ mStatementListOut = new TIntermBlock();
+}
+
+void RemoveSwitchFallThroughTraverser::visitSymbol(TIntermSymbol *node)
+{
+ // Note that this assumes that switch statements which don't begin by a case statement
+ // have already been weeded out in validation.
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+}
+
+void RemoveSwitchFallThroughTraverser::visitConstantUnion(TIntermConstantUnion *node)
+{
+ // Conditions of case labels are not traversed, so this is a constant statement like "0;".
+ // These are no-ops so there's no need to add them back to the statement list. Should have
+ // already been pruned out of the AST, in fact.
+ UNREACHABLE();
+}
+
+bool RemoveSwitchFallThroughTraverser::visitDeclaration(Visit, TIntermDeclaration *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitBinary(Visit, TIntermBinary *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitUnary(Visit, TIntermUnary *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitTernary(Visit, TIntermTernary *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitSwizzle(Visit, TIntermSwizzle *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitIfElse(Visit, TIntermIfElse *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitSwitch(Visit, TIntermSwitch *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ // Don't go into nested switch statements
+ return false;
+}
+
+void RemoveSwitchFallThroughTraverser::outputSequence(TIntermSequence *sequence, size_t startIndex)
+{
+ for (size_t i = startIndex; i < sequence->size(); ++i)
+ {
+ mStatementListOut->getSequence()->push_back(sequence->at(i));
+ }
+}
+
+void RemoveSwitchFallThroughTraverser::handlePreviousCase()
+{
+ if (mPreviousCase)
+ mCasesSharingBreak.push_back(mPreviousCase);
+ if (mLastStatementWasBreak)
+ {
+ for (size_t i = 0; i < mCasesSharingBreak.size(); ++i)
+ {
+ ASSERT(!mCasesSharingBreak.at(i)->getSequence()->empty());
+ if (mCasesSharingBreak.at(i)->getSequence()->size() == 1)
+ {
+ // Fall-through is allowed in case the label has no statements.
+ outputSequence(mCasesSharingBreak.at(i)->getSequence(), 0);
+ }
+ else
+ {
+ // Include all the statements that this case can fall through under the same label.
+ if (mCasesSharingBreak.size() > i + 1u)
+ {
+ mPerfDiagnostics->warning(mCasesSharingBreak.at(i)->getLine(),
+ "Performance: non-empty fall-through cases in "
+ "switch statements generate extra code.",
+ "switch");
+ }
+ for (size_t j = i; j < mCasesSharingBreak.size(); ++j)
+ {
+ size_t startIndex =
+ j > i ? 1 : 0; // Add the label only from the first sequence.
+ outputSequence(mCasesSharingBreak.at(j)->getSequence(), startIndex);
+ }
+ }
+ }
+ mCasesSharingBreak.clear();
+ }
+ mLastStatementWasBreak = false;
+ mPreviousCase = nullptr;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitCase(Visit, TIntermCase *node)
+{
+ handlePreviousCase();
+ mPreviousCase = new TIntermBlock();
+ mPreviousCase->getSequence()->push_back(node);
+ mPreviousCase->setLine(node->getLine());
+ // Don't traverse the condition of the case statement
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitAggregate(Visit, TIntermAggregate *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool DoesBlockAlwaysBreak(TIntermBlock *node)
+{
+ if (node->getSequence()->empty())
+ {
+ return false;
+ }
+
+ TIntermBlock *lastStatementAsBlock = node->getSequence()->back()->getAsBlock();
+ if (lastStatementAsBlock)
+ {
+ return DoesBlockAlwaysBreak(lastStatementAsBlock);
+ }
+
+ TIntermBranch *lastStatementAsBranch = node->getSequence()->back()->getAsBranchNode();
+ return lastStatementAsBranch != nullptr;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitBlock(Visit, TIntermBlock *node)
+{
+ if (node != mStatementList)
+ {
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = DoesBlockAlwaysBreak(node);
+ return false;
+ }
+ return true;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitLoop(Visit, TIntermLoop *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ mLastStatementWasBreak = false;
+ return false;
+}
+
+bool RemoveSwitchFallThroughTraverser::visitBranch(Visit, TIntermBranch *node)
+{
+ mPreviousCase->getSequence()->push_back(node);
+ // TODO: Verify that accepting return or continue statements here doesn't cause problems.
+ mLastStatementWasBreak = true;
+ return false;
+}
+
+} // anonymous namespace
+
+TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList,
+ PerformanceDiagnostics *perfDiagnostics)
+{
+ return RemoveSwitchFallThroughTraverser::removeFallThrough(statementList, perfDiagnostics);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h
new file mode 100644
index 0000000000..b92e7e5f6d
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RemoveSwitchFallThrough.h
@@ -0,0 +1,27 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RemoveSwitchFallThrough.h: Remove fall-through from switch statements.
+// Note that it is unsafe to do further AST transformations on the AST generated
+// by this function. It leaves duplicate nodes in the AST making replacements
+// unreliable.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_
+
+namespace sh
+{
+
+class TIntermBlock;
+class PerformanceDiagnostics;
+
+// When given a statementList from a switch AST node, return an updated
+// statementList that has fall-through removed.
+TIntermBlock *RemoveSwitchFallThrough(TIntermBlock *statementList,
+ PerformanceDiagnostics *perfDiagnostics);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REMOVESWITCHFALLTHROUGH_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp
new file mode 100644
index 0000000000..b2de6079b7
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.cpp
@@ -0,0 +1,183 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Implementation of the function RewriteAtomicFunctionExpressions.
+// See the header for more details.
+
+#include "compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h"
+
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+namespace
+{
+// Traverser that simplifies all the atomic function expressions into the ones that can be directly
+// translated into HLSL.
+//
+// case 1 (only for atomicExchange and atomicCompSwap):
+// original:
+// atomicExchange(counter, newValue);
+// new:
+// tempValue = atomicExchange(counter, newValue);
+//
+// case 2 (atomic function, temporary variable required):
+// original:
+// value = atomicAdd(counter, 1) * otherValue;
+// someArray[atomicAdd(counter, 1)] = someOtherValue;
+// new:
+// value = ((tempValue = atomicAdd(counter, 1)), tempValue) * otherValue;
+// someArray[((tempValue = atomicAdd(counter, 1)), tempValue)] = someOtherValue;
+//
+// case 3 (atomic function used directly initialize a variable):
+// original:
+// int value = atomicAdd(counter, 1);
+// new:
+// tempValue = atomicAdd(counter, 1);
+// int value = tempValue;
+//
+class RewriteAtomicFunctionExpressionsTraverser : public TIntermTraverser
+{
+ public:
+ RewriteAtomicFunctionExpressionsTraverser(TSymbolTable *symbolTable, int shaderVersion);
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ bool visitBlock(Visit visit, TIntermBlock *node) override;
+
+ private:
+ static bool IsAtomicExchangeOrCompSwapNoReturnValue(TIntermAggregate *node,
+ TIntermNode *parentNode);
+ static bool IsAtomicFunctionInsideExpression(TIntermAggregate *node, TIntermNode *parentNode);
+
+ void rewriteAtomicFunctionCallNode(TIntermAggregate *oldAtomicFunctionNode);
+
+ const TVariable *getTempVariable(const TType *type);
+
+ int mShaderVersion;
+ TIntermSequence mTempVariables;
+};
+
+RewriteAtomicFunctionExpressionsTraverser::RewriteAtomicFunctionExpressionsTraverser(
+ TSymbolTable *symbolTable,
+ int shaderVersion)
+ : TIntermTraverser(false, false, true, symbolTable), mShaderVersion(shaderVersion)
+{}
+
+void RewriteAtomicFunctionExpressionsTraverser::rewriteAtomicFunctionCallNode(
+ TIntermAggregate *oldAtomicFunctionNode)
+{
+ ASSERT(oldAtomicFunctionNode);
+
+ const TVariable *returnVariable = getTempVariable(&oldAtomicFunctionNode->getType());
+
+ TIntermBinary *rewrittenNode = new TIntermBinary(
+ TOperator::EOpAssign, CreateTempSymbolNode(returnVariable), oldAtomicFunctionNode);
+
+ auto *parentNode = getParentNode();
+
+ auto *parentBinary = parentNode->getAsBinaryNode();
+ if (parentBinary && parentBinary->getOp() == EOpInitialize)
+ {
+ insertStatementInParentBlock(rewrittenNode);
+ queueReplacement(CreateTempSymbolNode(returnVariable), OriginalNode::IS_DROPPED);
+ }
+ else
+ {
+ // As all atomic function assignment will be converted to the last argument of an
+ // interlocked function, if we need the return value, assignment needs to be wrapped with
+ // the comma operator and the temporary variables.
+ if (!parentNode->getAsBlock())
+ {
+ rewrittenNode = TIntermBinary::CreateComma(
+ rewrittenNode, new TIntermSymbol(returnVariable), mShaderVersion);
+ }
+
+ queueReplacement(rewrittenNode, OriginalNode::IS_DROPPED);
+ }
+}
+
+const TVariable *RewriteAtomicFunctionExpressionsTraverser::getTempVariable(const TType *type)
+{
+ TIntermDeclaration *variableDeclaration;
+ TVariable *returnVariable =
+ DeclareTempVariable(mSymbolTable, type, EvqTemporary, &variableDeclaration);
+ mTempVariables.push_back(variableDeclaration);
+ return returnVariable;
+}
+
+bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicExchangeOrCompSwapNoReturnValue(
+ TIntermAggregate *node,
+ TIntermNode *parentNode)
+{
+ ASSERT(node);
+ return (node->getOp() == EOpAtomicExchange || node->getOp() == EOpAtomicCompSwap) &&
+ parentNode && parentNode->getAsBlock();
+}
+
+bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicFunctionInsideExpression(
+ TIntermAggregate *node,
+ TIntermNode *parentNode)
+{
+ ASSERT(node);
+ // We only need to handle atomic functions with a parent that it is not block nodes. If the
+ // parent node is block, it means that the atomic function is not inside an expression.
+ if (!BuiltInGroup::IsAtomicMemory(node->getOp()) || parentNode->getAsBlock())
+ {
+ return false;
+ }
+
+ auto *parentAsBinary = parentNode->getAsBinaryNode();
+ // Assignments are handled in OutputHLSL
+ return !parentAsBinary || parentAsBinary->getOp() != EOpAssign;
+}
+
+bool RewriteAtomicFunctionExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ ASSERT(visit == PostVisit);
+ // Skip atomic memory functions for SSBO. They will be processed in the OutputHLSL traverser.
+ if (BuiltInGroup::IsAtomicMemory(node->getOp()) &&
+ IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped()))
+ {
+ return false;
+ }
+
+ TIntermNode *parentNode = getParentNode();
+ if (IsAtomicExchangeOrCompSwapNoReturnValue(node, parentNode) ||
+ IsAtomicFunctionInsideExpression(node, parentNode))
+ {
+ rewriteAtomicFunctionCallNode(node);
+ }
+
+ return true;
+}
+
+bool RewriteAtomicFunctionExpressionsTraverser::visitBlock(Visit visit, TIntermBlock *node)
+{
+ ASSERT(visit == PostVisit);
+
+ if (!mTempVariables.empty() && getParentNode()->getAsFunctionDefinition())
+ {
+ insertStatementsInBlockAtPosition(node, 0, mTempVariables, TIntermSequence());
+ mTempVariables.clear();
+ }
+
+ return true;
+}
+
+} // anonymous namespace
+
+bool RewriteAtomicFunctionExpressions(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ int shaderVersion)
+{
+ RewriteAtomicFunctionExpressionsTraverser traverser(symbolTable, shaderVersion);
+ traverser.traverse(root);
+ return traverser.updateTree(compiler, root);
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h
new file mode 100644
index 0000000000..60e6ffa2d1
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteAtomicFunctionExpressions.h
@@ -0,0 +1,42 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteAtomicFunctionExpressions rewrites the expressions that contain
+// atomic function calls and cannot be directly translated into HLSL into
+// several simple ones that can be easily handled in the HLSL translator.
+//
+// We need to rewite these expressions because:
+// 1. All GLSL atomic functions have return values, which all represent the
+// original value of the shared or ssbo variable; while all HLSL atomic
+// functions don't, and the original value can be stored in the last
+// parameter of the function call.
+// 2. For HLSL atomic functions, the last parameter that stores the original
+// value is optional except for InterlockedExchange and
+// InterlockedCompareExchange. Missing original_value in the call of
+// InterlockedExchange or InterlockedCompareExchange results in a compile
+// error from HLSL compiler.
+//
+// RewriteAtomicFunctionExpressions is a function that can modify the AST
+// to ensure all the expressions that contain atomic function calls can be
+// directly translated into HLSL expressions.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteAtomicFunctionExpressions(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable,
+ int shaderVersion);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_ATOMIC_FUNCTION_EXPRESSIONS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp
new file mode 100644
index 0000000000..10647d1bf1
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.cpp
@@ -0,0 +1,123 @@
+//
+// Copyright 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteElseBlocks.cpp: Implementation for tree transform to change
+// all if-else blocks to if-if blocks.
+//
+
+#include "compiler/translator/tree_ops/d3d/RewriteElseBlocks.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/NodeSearch.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class ElseBlockRewriter : public TIntermTraverser
+{
+ public:
+ ElseBlockRewriter(TSymbolTable *symbolTable);
+
+ protected:
+ bool visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *aggregate) override;
+ bool visitBlock(Visit visit, TIntermBlock *block) override;
+
+ private:
+ TIntermNode *rewriteIfElse(TIntermIfElse *ifElse);
+
+ const TType *mFunctionType;
+};
+
+ElseBlockRewriter::ElseBlockRewriter(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, true, symbolTable), mFunctionType(nullptr)
+{}
+
+bool ElseBlockRewriter::visitFunctionDefinition(Visit visit, TIntermFunctionDefinition *node)
+{
+ // Store the current function context (see comment below)
+ mFunctionType = ((visit == PreVisit) ? &node->getFunctionPrototype()->getType() : nullptr);
+ return true;
+}
+
+bool ElseBlockRewriter::visitBlock(Visit visit, TIntermBlock *node)
+{
+ if (visit == PostVisit)
+ {
+ for (size_t statementIndex = 0; statementIndex != node->getSequence()->size();
+ statementIndex++)
+ {
+ TIntermNode *statement = (*node->getSequence())[statementIndex];
+ TIntermIfElse *ifElse = statement->getAsIfElseNode();
+ if (ifElse && ifElse->getFalseBlock() != nullptr)
+ {
+ (*node->getSequence())[statementIndex] = rewriteIfElse(ifElse);
+ }
+ }
+ }
+ return true;
+}
+
+TIntermNode *ElseBlockRewriter::rewriteIfElse(TIntermIfElse *ifElse)
+{
+ ASSERT(ifElse != nullptr);
+
+ TIntermDeclaration *storeCondition = nullptr;
+ TVariable *conditionVariable =
+ DeclareTempVariable(mSymbolTable, ifElse->getCondition(), EvqTemporary, &storeCondition);
+
+ TIntermBlock *falseBlock = nullptr;
+
+ TType boolType(EbtBool, EbpUndefined, EvqTemporary);
+
+ if (ifElse->getFalseBlock())
+ {
+ TIntermBlock *negatedElse = nullptr;
+ // crbug.com/346463
+ // D3D generates error messages claiming a function has no return value, when rewriting
+ // an if-else clause that returns something non-void in a function. By appending mock
+ // returns (that are unreachable) we can silence this compile error.
+ if (mFunctionType && mFunctionType->getBasicType() != EbtVoid)
+ {
+ TIntermNode *returnNode = new TIntermBranch(EOpReturn, CreateZeroNode(*mFunctionType));
+ negatedElse = new TIntermBlock();
+ negatedElse->appendStatement(returnNode);
+ }
+
+ TIntermSymbol *conditionSymbolElse = CreateTempSymbolNode(conditionVariable);
+ TIntermUnary *negatedCondition =
+ new TIntermUnary(EOpLogicalNot, conditionSymbolElse, nullptr);
+ TIntermIfElse *falseIfElse =
+ new TIntermIfElse(negatedCondition, ifElse->getFalseBlock(), negatedElse);
+ falseBlock = EnsureBlock(falseIfElse);
+ }
+
+ TIntermSymbol *conditionSymbolSel = CreateTempSymbolNode(conditionVariable);
+ TIntermIfElse *newIfElse =
+ new TIntermIfElse(conditionSymbolSel, ifElse->getTrueBlock(), falseBlock);
+
+ TIntermBlock *block = new TIntermBlock();
+ block->getSequence()->push_back(storeCondition);
+ block->getSequence()->push_back(newIfElse);
+
+ return block;
+}
+
+} // anonymous namespace
+
+bool RewriteElseBlocks(TCompiler *compiler, TIntermNode *node, TSymbolTable *symbolTable)
+{
+ ElseBlockRewriter rewriter(symbolTable);
+ node->traverse(&rewriter);
+
+ return compiler->validateAST(node);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h
new file mode 100644
index 0000000000..cab0f7090c
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteElseBlocks.h
@@ -0,0 +1,27 @@
+//
+// Copyright 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteElseBlocks.h: Prototype for tree transform to change
+// all if-else blocks to if-if blocks.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteElseBlocks(TCompiler *compiler,
+ TIntermNode *node,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEELSEBLOCKS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp
new file mode 100644
index 0000000000..709f394878
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.cpp
@@ -0,0 +1,420 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage
+// block calls into several simple ones that can be easily handled in the HLSL translator. After the
+// AST pass, all ssbo related blocks will be like below:
+// ssbo_access_chain = ssbo_access_chain;
+// ssbo_access_chain = expr_no_ssbo;
+// lvalue_no_ssbo = ssbo_access_chain;
+//
+
+#include "compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h"
+
+#include "compiler/translator/Symbol.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+namespace
+{
+
+bool IsIncrementOrDecrementOperator(TOperator op)
+{
+ switch (op)
+ {
+ case EOpPostIncrement:
+ case EOpPostDecrement:
+ case EOpPreIncrement:
+ case EOpPreDecrement:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool IsCompoundAssignment(TOperator op)
+{
+ switch (op)
+ {
+ case EOpAddAssign:
+ case EOpSubAssign:
+ case EOpMulAssign:
+ case EOpVectorTimesMatrixAssign:
+ case EOpVectorTimesScalarAssign:
+ case EOpMatrixTimesScalarAssign:
+ case EOpMatrixTimesMatrixAssign:
+ case EOpDivAssign:
+ case EOpIModAssign:
+ case EOpBitShiftLeftAssign:
+ case EOpBitShiftRightAssign:
+ case EOpBitwiseAndAssign:
+ case EOpBitwiseXorAssign:
+ case EOpBitwiseOrAssign:
+ return true;
+ default:
+ return false;
+ }
+}
+
+// EOpIndexDirect, EOpIndexIndirect, EOpIndexDirectStruct, EOpIndexDirectInterfaceBlock belong to
+// operators in SSBO access chain.
+bool IsReadonlyBinaryOperatorNotInSSBOAccessChain(TOperator op)
+{
+ switch (op)
+ {
+ case EOpComma:
+ case EOpAdd:
+ case EOpSub:
+ case EOpMul:
+ case EOpDiv:
+ case EOpIMod:
+ case EOpBitShiftLeft:
+ case EOpBitShiftRight:
+ case EOpBitwiseAnd:
+ case EOpBitwiseXor:
+ case EOpBitwiseOr:
+ case EOpEqual:
+ case EOpNotEqual:
+ case EOpLessThan:
+ case EOpGreaterThan:
+ case EOpLessThanEqual:
+ case EOpGreaterThanEqual:
+ case EOpVectorTimesScalar:
+ case EOpMatrixTimesScalar:
+ case EOpVectorTimesMatrix:
+ case EOpMatrixTimesVector:
+ case EOpMatrixTimesMatrix:
+ case EOpLogicalOr:
+ case EOpLogicalXor:
+ case EOpLogicalAnd:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool HasSSBOAsFunctionArgument(TIntermSequence *arguments)
+{
+ for (TIntermNode *arg : *arguments)
+ {
+ TIntermTyped *typedArg = arg->getAsTyped();
+ if (IsInShaderStorageBlock(typedArg))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+class RewriteExpressionsWithShaderStorageBlockTraverser : public TIntermTraverser
+{
+ public:
+ RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable);
+ void nextIteration();
+ bool foundSSBO() const { return mFoundSSBO; }
+
+ private:
+ bool visitBinary(Visit, TIntermBinary *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+ bool visitUnary(Visit visit, TIntermUnary *node) override;
+
+ TIntermSymbol *insertInitStatementAndReturnTempSymbol(TIntermTyped *node,
+ TIntermSequence *insertions);
+
+ bool mFoundSSBO;
+};
+
+RewriteExpressionsWithShaderStorageBlockTraverser::
+ RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, true, false, symbolTable), mFoundSSBO(false)
+{}
+
+TIntermSymbol *
+RewriteExpressionsWithShaderStorageBlockTraverser::insertInitStatementAndReturnTempSymbol(
+ TIntermTyped *node,
+ TIntermSequence *insertions)
+{
+ TIntermDeclaration *variableDeclaration;
+ TVariable *tempVariable =
+ DeclareTempVariable(mSymbolTable, node, EvqTemporary, &variableDeclaration);
+
+ insertions->push_back(variableDeclaration);
+ return CreateTempSymbolNode(tempVariable);
+}
+
+bool RewriteExpressionsWithShaderStorageBlockTraverser::visitBinary(Visit visit,
+ TIntermBinary *node)
+{
+ // Make sure that the expression is caculated from left to right.
+ if (visit != InVisit)
+ {
+ return true;
+ }
+
+ if (mFoundSSBO)
+ {
+ return false;
+ }
+
+ bool rightSSBO = IsInShaderStorageBlock(node->getRight());
+ bool leftSSBO = IsInShaderStorageBlock(node->getLeft());
+ if (!leftSSBO && !rightSSBO)
+ {
+ return true;
+ }
+
+ // case 1: Compound assigment operator
+ // original:
+ // lssbo += expr;
+ // new:
+ // var rvalue = expr;
+ // var temp = lssbo;
+ // temp += rvalue;
+ // lssbo = temp;
+ //
+ // original:
+ // lvalue_no_ssbo += rssbo;
+ // new:
+ // var rvalue = rssbo;
+ // lvalue_no_ssbo += rvalue;
+ if (IsCompoundAssignment(node->getOp()))
+ {
+ mFoundSSBO = true;
+ TIntermSequence insertions;
+ TIntermTyped *rightNode =
+ insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions);
+ if (leftSSBO)
+ {
+ TIntermSymbol *tempSymbol =
+ insertInitStatementAndReturnTempSymbol(node->getLeft()->deepCopy(), &insertions);
+ TIntermBinary *tempCompoundOperate =
+ new TIntermBinary(node->getOp(), tempSymbol->deepCopy(), rightNode->deepCopy());
+ insertions.push_back(tempCompoundOperate);
+ insertStatementsInParentBlock(insertions);
+
+ TIntermBinary *assignTempValueToSSBO =
+ new TIntermBinary(EOpAssign, node->getLeft(), tempSymbol->deepCopy());
+ queueReplacement(assignTempValueToSSBO, OriginalNode::IS_DROPPED);
+ }
+ else
+ {
+ insertStatementsInParentBlock(insertions);
+ TIntermBinary *compoundAssignRValueToLValue =
+ new TIntermBinary(node->getOp(), node->getLeft(), rightNode->deepCopy());
+ queueReplacement(compoundAssignRValueToLValue, OriginalNode::IS_DROPPED);
+ }
+ }
+ // case 2: Readonly binary operator
+ // original:
+ // ssbo0 + ssbo1 + ssbo2;
+ // new:
+ // var temp0 = ssbo0;
+ // var temp1 = ssbo1;
+ // var temp2 = ssbo2;
+ // temp0 + temp1 + temp2;
+ else if (IsReadonlyBinaryOperatorNotInSSBOAccessChain(node->getOp()) && (leftSSBO || rightSSBO))
+ {
+ mFoundSSBO = true;
+ TIntermTyped *rightNode = node->getRight();
+ TIntermTyped *leftNode = node->getLeft();
+ TIntermSequence insertions;
+ if (rightSSBO)
+ {
+ rightNode = insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions);
+ }
+ if (leftSSBO)
+ {
+ leftNode = insertInitStatementAndReturnTempSymbol(node->getLeft(), &insertions);
+ }
+
+ insertStatementsInParentBlock(insertions);
+ TIntermBinary *newExpr =
+ new TIntermBinary(node->getOp(), leftNode->deepCopy(), rightNode->deepCopy());
+ queueReplacement(newExpr, OriginalNode::IS_DROPPED);
+ }
+ return !mFoundSSBO;
+}
+
+// case 3: ssbo as the argument of aggregate type
+// original:
+// foo(ssbo);
+// new:
+// var tempArg = ssbo;
+// foo(tempArg);
+// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument)
+//
+// original:
+// foo(ssbo) * expr;
+// new:
+// var tempArg = ssbo;
+// var tempReturn = foo(tempArg);
+// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument)
+// tempReturn * expr;
+bool RewriteExpressionsWithShaderStorageBlockTraverser::visitAggregate(Visit visit,
+ TIntermAggregate *node)
+{
+ // Make sure that visitAggregate is only executed once for same node.
+ if (visit != PreVisit)
+ {
+ return true;
+ }
+
+ if (mFoundSSBO)
+ {
+ return false;
+ }
+
+ // We still need to process the ssbo as the non-first argument of atomic memory functions.
+ if (BuiltInGroup::IsAtomicMemory(node->getOp()) &&
+ IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped()))
+ {
+ return true;
+ }
+
+ if (!HasSSBOAsFunctionArgument(node->getSequence()))
+ {
+ return true;
+ }
+
+ mFoundSSBO = true;
+ TIntermSequence insertions;
+ TIntermSequence readBackToSSBOs;
+ TIntermSequence *originalArguments = node->getSequence();
+ for (size_t i = 0; i < node->getChildCount(); ++i)
+ {
+ TIntermTyped *ssboArgument = (*originalArguments)[i]->getAsTyped();
+ if (IsInShaderStorageBlock(ssboArgument))
+ {
+ TIntermSymbol *argumentCopy =
+ insertInitStatementAndReturnTempSymbol(ssboArgument, &insertions);
+ if (node->getFunction() != nullptr)
+ {
+ TQualifier qual = node->getFunction()->getParam(i)->getType().getQualifier();
+ if (qual == EvqParamInOut || qual == EvqParamOut)
+ {
+ TIntermBinary *readBackToSSBO = new TIntermBinary(
+ EOpAssign, ssboArgument->deepCopy(), argumentCopy->deepCopy());
+ readBackToSSBOs.push_back(readBackToSSBO);
+ }
+ }
+ node->replaceChildNode(ssboArgument, argumentCopy);
+ }
+ }
+
+ TIntermBlock *parentBlock = getParentNode()->getAsBlock();
+ if (parentBlock)
+ {
+ // Aggregate node is as a single sentence.
+ insertions.push_back(node);
+ if (!readBackToSSBOs.empty())
+ {
+ insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end());
+ }
+ mMultiReplacements.emplace_back(parentBlock, node, std::move(insertions));
+ }
+ else
+ {
+ // Aggregate node is inside an expression.
+ TIntermSymbol *tempSymbol = insertInitStatementAndReturnTempSymbol(node, &insertions);
+ if (!readBackToSSBOs.empty())
+ {
+ insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end());
+ }
+ insertStatementsInParentBlock(insertions);
+ queueReplacement(tempSymbol->deepCopy(), OriginalNode::IS_DROPPED);
+ }
+
+ return false;
+}
+
+bool RewriteExpressionsWithShaderStorageBlockTraverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+ if (mFoundSSBO)
+ {
+ return false;
+ }
+
+ if (!IsInShaderStorageBlock(node->getOperand()))
+ {
+ return true;
+ }
+
+ // .length() is processed in OutputHLSL.
+ if (node->getOp() == EOpArrayLength)
+ {
+ return true;
+ }
+
+ mFoundSSBO = true;
+
+ // case 4: ssbo as the operand of ++/--
+ // original:
+ // ++ssbo * expr;
+ // new:
+ // var temp1 = ssbo;
+ // var temp2 = ++temp1;
+ // ssbo = temp1;
+ // temp2 * expr;
+ if (IsIncrementOrDecrementOperator(node->getOp()))
+ {
+ TIntermSequence insertions;
+ TIntermSymbol *temp1 =
+ insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions);
+ TIntermUnary *newUnary = new TIntermUnary(node->getOp(), temp1->deepCopy(), nullptr);
+ TIntermSymbol *temp2 = insertInitStatementAndReturnTempSymbol(newUnary, &insertions);
+ TIntermBinary *readBackToSSBO =
+ new TIntermBinary(EOpAssign, node->getOperand()->deepCopy(), temp1->deepCopy());
+ insertions.push_back(readBackToSSBO);
+ insertStatementsInParentBlock(insertions);
+ queueReplacement(temp2->deepCopy(), OriginalNode::IS_DROPPED);
+ }
+ // case 5: ssbo as the operand of readonly unary operator
+ // original:
+ // ~ssbo * expr;
+ // new:
+ // var temp = ssbo;
+ // ~temp * expr;
+ else
+ {
+ TIntermSequence insertions;
+ TIntermSymbol *temp =
+ insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions);
+ insertStatementsInParentBlock(insertions);
+ node->replaceChildNode(node->getOperand(), temp->deepCopy());
+ }
+ return false;
+}
+
+void RewriteExpressionsWithShaderStorageBlockTraverser::nextIteration()
+{
+ mFoundSSBO = false;
+}
+
+} // anonymous namespace
+
+bool RewriteExpressionsWithShaderStorageBlock(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable)
+{
+ RewriteExpressionsWithShaderStorageBlockTraverser traverser(symbolTable);
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.foundSSBO())
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.foundSSBO());
+
+ return true;
+}
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h
new file mode 100644
index 0000000000..bf824c4a23
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteExpressionsWithShaderStorageBlock.h
@@ -0,0 +1,37 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage
+// block calls into several simple ones that can be easily handled in the HLSL translator. After the
+// AST pass, all ssbo related blocks will be like below:
+// ssbo_access_chain = ssbo_access_chain;
+// ssbo_access_chain = expr_no_ssbo;
+// lvalue_no_ssbo = ssbo_access_chain;
+//
+// Below situations are needed to be rewritten (Details can be found in .cpp file).
+// SSBO as the operand of compound assignment operators.
+// SSBO as the operand of ++/--.
+// SSBO as the operand of repeated assignment.
+// SSBO as the operand of readonly unary/binary/ternary operators.
+// SSBO as the argument of aggregate type.
+// SSBO as the condition of if/switch/while/do-while/for
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool RewriteExpressionsWithShaderStorageBlock(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITE_EXPRESSIONS_WITH_SHADER_STORAGE_BLOCK_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp
new file mode 100644
index 0000000000..5d8cdb657d
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.cpp
@@ -0,0 +1,117 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// Implementation of evaluating unary integer variable bug workaround.
+// See header for more info.
+
+#include "compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h"
+
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class Traverser : public TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool Apply(TCompiler *compiler, TIntermNode *root);
+
+ private:
+ Traverser();
+ bool visitUnary(Visit visit, TIntermUnary *node) override;
+ void nextIteration();
+
+ bool mFound = false;
+};
+
+// static
+bool Traverser::Apply(TCompiler *compiler, TIntermNode *root)
+{
+ Traverser traverser;
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.mFound)
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.mFound);
+
+ return true;
+}
+
+Traverser::Traverser() : TIntermTraverser(true, false, false) {}
+
+void Traverser::nextIteration()
+{
+ mFound = false;
+}
+
+bool Traverser::visitUnary(Visit visit, TIntermUnary *node)
+{
+ if (mFound)
+ {
+ return false;
+ }
+
+ // Decide if the current unary operator is unary minus.
+ if (node->getOp() != EOpNegative)
+ {
+ return true;
+ }
+
+ // Decide if the current operand is an integer variable.
+ TIntermTyped *opr = node->getOperand();
+ if (!opr->getType().isScalarInt())
+ {
+ return true;
+ }
+
+ // Potential problem case detected, apply workaround: -(int) -> ~(int) + 1.
+ // ~(int)
+ TIntermUnary *bitwiseNot = new TIntermUnary(EOpBitwiseNot, opr, nullptr);
+ bitwiseNot->setLine(opr->getLine());
+
+ // Constant 1 (or 1u)
+ TConstantUnion *one = new TConstantUnion();
+ if (opr->getType().getBasicType() == EbtInt)
+ {
+ one->setIConst(1);
+ }
+ else
+ {
+ one->setUConst(1u);
+ }
+ TType *oneType = new TType(opr->getType());
+ oneType->setQualifier(EvqConst);
+
+ TIntermConstantUnion *oneNode = new TIntermConstantUnion(one, *oneType);
+ oneNode->setLine(opr->getLine());
+
+ // ~(int) + 1
+ TIntermBinary *add = new TIntermBinary(EOpAdd, bitwiseNot, oneNode);
+ add->setLine(opr->getLine());
+
+ queueReplacement(add, OriginalNode::IS_DROPPED);
+
+ mFound = true;
+ return false;
+}
+
+} // anonymous namespace
+
+bool RewriteUnaryMinusOperatorInt(TCompiler *compiler, TIntermNode *root)
+{
+ return Traverser::Apply(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h
new file mode 100644
index 0000000000..cc5ac86456
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/RewriteUnaryMinusOperatorInt.h
@@ -0,0 +1,23 @@
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// This mutating tree traversal works around a bug on evaluating unary
+// integer variable on Intel D3D driver. It works by rewriting -(int) to
+// ~(int) + 1 when evaluating unary integer variables.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+
+[[nodiscard]] bool RewriteUnaryMinusOperatorInt(TCompiler *compiler, TIntermNode *root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_REWRITEUNARYMINUSOPERATORINT_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp
new file mode 100644
index 0000000000..170b29acd7
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.cpp
@@ -0,0 +1,83 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SeparateArrayConstructorStatements splits statements that are array constructors and drops all of
+// their constant arguments. For example, a statement like:
+// int[2](0, i++);
+// Will be changed to:
+// i++;
+
+#include "compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h"
+
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+void SplitConstructorArgs(const TIntermSequence &originalArgs, TIntermSequence *argsOut)
+{
+ for (TIntermNode *arg : originalArgs)
+ {
+ TIntermTyped *argTyped = arg->getAsTyped();
+ if (argTyped->hasSideEffects())
+ {
+ TIntermAggregate *argAggregate = argTyped->getAsAggregate();
+ if (argTyped->isArray() && argAggregate && argAggregate->isConstructor())
+ {
+ SplitConstructorArgs(*argAggregate->getSequence(), argsOut);
+ }
+ else
+ {
+ argsOut->push_back(argTyped);
+ }
+ }
+ }
+}
+
+class SeparateArrayConstructorStatementsTraverser : public TIntermTraverser
+{
+ public:
+ SeparateArrayConstructorStatementsTraverser();
+
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+};
+
+SeparateArrayConstructorStatementsTraverser::SeparateArrayConstructorStatementsTraverser()
+ : TIntermTraverser(true, false, false)
+{}
+
+bool SeparateArrayConstructorStatementsTraverser::visitAggregate(Visit visit,
+ TIntermAggregate *node)
+{
+ TIntermBlock *parentAsBlock = getParentNode()->getAsBlock();
+ if (!parentAsBlock)
+ {
+ return false;
+ }
+ if (!node->isArray() || !node->isConstructor())
+ {
+ return false;
+ }
+
+ TIntermSequence constructorArgs;
+ SplitConstructorArgs(*node->getSequence(), &constructorArgs);
+ mMultiReplacements.emplace_back(parentAsBlock, node, std::move(constructorArgs));
+
+ return false;
+}
+
+} // namespace
+
+bool SeparateArrayConstructorStatements(TCompiler *compiler, TIntermBlock *root)
+{
+ SeparateArrayConstructorStatementsTraverser traverser;
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h
new file mode 100644
index 0000000000..225a4651da
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayConstructorStatements.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SeparateArrayConstructorStatements splits statements that are array constructors and drops all of
+// their constant arguments. For example, a statement like:
+// int[2](0, i++);
+// Will be changed to:
+// i++;
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+
+[[nodiscard]] bool SeparateArrayConstructorStatements(TCompiler *compiler, TIntermBlock *root);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYCONSTRUCTORSTATEMENTS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp
new file mode 100644
index 0000000000..de5c393cf6
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.cpp
@@ -0,0 +1,89 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The SeparateArrayInitialization function splits each array initialization into a declaration and
+// an assignment.
+// Example:
+// type[n] a = initializer;
+// will effectively become
+// type[n] a;
+// a = initializer;
+//
+// Note that if the array is declared as const, the initialization may still be split, making the
+// AST technically invalid. Because of that this transformation should only be used when subsequent
+// stages don't care about const qualifiers. However, the initialization will not be split if the
+// initializer can be written as a HLSL literal.
+
+#include "compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h"
+
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/OutputHLSL.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class SeparateArrayInitTraverser : private TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool apply(TCompiler *compiler, TIntermNode *root);
+
+ private:
+ SeparateArrayInitTraverser();
+ bool visitDeclaration(Visit, TIntermDeclaration *node) override;
+};
+
+bool SeparateArrayInitTraverser::apply(TCompiler *compiler, TIntermNode *root)
+{
+ SeparateArrayInitTraverser separateInit;
+ root->traverse(&separateInit);
+ return separateInit.updateTree(compiler, root);
+}
+
+SeparateArrayInitTraverser::SeparateArrayInitTraverser() : TIntermTraverser(true, false, false) {}
+
+bool SeparateArrayInitTraverser::visitDeclaration(Visit, TIntermDeclaration *node)
+{
+ TIntermSequence *sequence = node->getSequence();
+ TIntermBinary *initNode = sequence->back()->getAsBinaryNode();
+ if (initNode != nullptr && initNode->getOp() == EOpInitialize)
+ {
+ TIntermTyped *initializer = initNode->getRight();
+ if (initializer->isArray() && !initializer->hasConstantValue())
+ {
+ // We rely on that array declarations have been isolated to single declarations.
+ ASSERT(sequence->size() == 1);
+ TIntermTyped *symbol = initNode->getLeft();
+ TIntermBlock *parentBlock = getParentNode()->getAsBlock();
+ ASSERT(parentBlock != nullptr);
+
+ TIntermSequence replacements;
+
+ TIntermDeclaration *replacementDeclaration = new TIntermDeclaration();
+ replacementDeclaration->appendDeclarator(symbol);
+ replacementDeclaration->setLine(symbol->getLine());
+ replacements.push_back(replacementDeclaration);
+
+ TIntermBinary *replacementAssignment =
+ new TIntermBinary(EOpAssign, symbol, initializer);
+ replacementAssignment->setLine(symbol->getLine());
+ replacements.push_back(replacementAssignment);
+
+ mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements));
+ }
+ }
+ return false;
+}
+
+} // namespace
+
+bool SeparateArrayInitialization(TCompiler *compiler, TIntermNode *root)
+{
+ return SeparateArrayInitTraverser::apply(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h
new file mode 100644
index 0000000000..e83554186a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateArrayInitialization.h
@@ -0,0 +1,32 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// The SeparateArrayInitialization function splits each array initialization into a declaration and
+// an assignment.
+// Example:
+// type[n] a = initializer;
+// will effectively become
+// type[n] a;
+// a = initializer;
+//
+// Note that if the array is declared as const, the initialization may still be split, making the
+// AST technically invalid. Because of that this transformation should only be used when subsequent
+// stages don't care about const qualifiers. However, the initialization will not be split if the
+// initializer can be written as a HLSL literal.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+
+[[nodiscard]] bool SeparateArrayInitialization(TCompiler *compiler, TIntermNode *root);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEARRAYINITIALIZATION_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp
new file mode 100644
index 0000000000..58d8a0a9be
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.cpp
@@ -0,0 +1,138 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SeparateExpressionsReturningArrays splits array-returning expressions that are not array names
+// from more complex expressions, assigning them to a temporary variable a#.
+// Examples where a, b and c are all arrays:
+// (a = b) == (a = c) is split into a = b; type[n] a1 = a; a = c; type[n] a2 = a; a1 == a2;
+// type d = type[n](...)[i]; is split into type[n] a1 = type[n](...); type d = a1[i];
+
+#include "compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h"
+
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// Traverser that separates one array expression into a statement at a time.
+class SeparateExpressionsTraverser : public TIntermTraverser
+{
+ public:
+ SeparateExpressionsTraverser(TSymbolTable *symbolTable);
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+ bool visitAggregate(Visit visit, TIntermAggregate *node) override;
+
+ void nextIteration();
+ bool foundArrayExpression() const { return mFoundArrayExpression; }
+
+ protected:
+ // Marked to true once an operation that needs to be hoisted out of the expression has been
+ // found. After that, no more AST updates are performed on that traversal.
+ bool mFoundArrayExpression;
+
+ IntermNodePatternMatcher mPatternToSeparateMatcher;
+};
+
+SeparateExpressionsTraverser::SeparateExpressionsTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable),
+ mFoundArrayExpression(false),
+ mPatternToSeparateMatcher(IntermNodePatternMatcher::kExpressionReturningArray)
+{}
+
+// Performs a shallow copy of an assignment node.
+// These shallow copies are useful when a node gets inserted into an aggregate node
+// and also needs to be replaced in its original location by a different node.
+TIntermBinary *CopyAssignmentNode(TIntermBinary *node)
+{
+ return new TIntermBinary(node->getOp(), node->getLeft(), node->getRight());
+}
+
+bool SeparateExpressionsTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (mFoundArrayExpression)
+ return false;
+
+ // Return if the expression is not an array or if we're not inside a complex expression.
+ if (!mPatternToSeparateMatcher.match(node, getParentNode()))
+ return true;
+
+ ASSERT(node->getOp() == EOpAssign);
+
+ mFoundArrayExpression = true;
+
+ TIntermSequence insertions;
+ insertions.push_back(CopyAssignmentNode(node));
+ // TODO(oetuaho): In some cases it would be more optimal to not add the temporary node, but just
+ // use the original target of the assignment. Care must be taken so that this doesn't happen
+ // when the same array symbol is a target of assignment more than once in one expression.
+ TIntermDeclaration *arrayVariableDeclaration;
+ TVariable *arrayVariable =
+ DeclareTempVariable(mSymbolTable, node->getLeft(), EvqTemporary, &arrayVariableDeclaration);
+ insertions.push_back(arrayVariableDeclaration);
+ insertStatementsInParentBlock(insertions);
+
+ queueReplacement(CreateTempSymbolNode(arrayVariable), OriginalNode::IS_DROPPED);
+
+ return false;
+}
+
+bool SeparateExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
+{
+ if (mFoundArrayExpression)
+ return false; // No need to traverse further
+
+ if (!mPatternToSeparateMatcher.match(node, getParentNode()))
+ return true;
+
+ ASSERT(node->isConstructor() || node->getOp() == EOpCallFunctionInAST);
+
+ mFoundArrayExpression = true;
+
+ TIntermDeclaration *arrayVariableDeclaration;
+ TVariable *arrayVariable = DeclareTempVariable(mSymbolTable, node->shallowCopy(), EvqTemporary,
+ &arrayVariableDeclaration);
+ insertStatementInParentBlock(arrayVariableDeclaration);
+
+ queueReplacement(CreateTempSymbolNode(arrayVariable), OriginalNode::IS_DROPPED);
+
+ return false;
+}
+
+void SeparateExpressionsTraverser::nextIteration()
+{
+ mFoundArrayExpression = false;
+}
+
+} // namespace
+
+bool SeparateExpressionsReturningArrays(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable)
+{
+ SeparateExpressionsTraverser traverser(symbolTable);
+ // Separate one expression at a time, and reset the traverser between iterations.
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.foundArrayExpression())
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.foundArrayExpression());
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h
new file mode 100644
index 0000000000..6a9350d89a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/SeparateExpressionsReturningArrays.h
@@ -0,0 +1,28 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// SeparateExpressionsReturningArrays splits array-returning expressions that are not array names
+// from more complex expressions, assigning them to a temporary variable a#.
+// Examples where a, b and c are all arrays:
+// (a = b) == (a = c) is split into a = b; type[n] a1 = a; a = c; type[n] a2 = a; a1 == a2;
+// type d = type[n](...)[i]; is split into type[n] a1 = type[n](...); type d = a1[i];
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool SeparateExpressionsReturningArrays(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_SEPARATEEXPRESSIONSRETURNINGARRAYS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp
new file mode 100644
index 0000000000..5b104c4889
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.cpp
@@ -0,0 +1,200 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// UnfoldShortCircuitToIf is an AST traverser to convert short-circuiting operators to if-else
+// statements.
+// The results are assigned to s# temporaries, which are used by the main translator instead of
+// the original expression.
+//
+
+#include "compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h"
+
+#include "compiler/translator/StaticType.h"
+#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+// Traverser that unfolds one short-circuiting operation at a time.
+class UnfoldShortCircuitTraverser : public TIntermTraverser
+{
+ public:
+ UnfoldShortCircuitTraverser(TSymbolTable *symbolTable);
+
+ bool visitBinary(Visit visit, TIntermBinary *node) override;
+ bool visitTernary(Visit visit, TIntermTernary *node) override;
+
+ void nextIteration();
+ bool foundShortCircuit() const { return mFoundShortCircuit; }
+
+ protected:
+ // Marked to true once an operation that needs to be unfolded has been found.
+ // After that, no more unfolding is performed on that traversal.
+ bool mFoundShortCircuit;
+
+ IntermNodePatternMatcher mPatternToUnfoldMatcher;
+};
+
+UnfoldShortCircuitTraverser::UnfoldShortCircuitTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, true, symbolTable),
+ mFoundShortCircuit(false),
+ mPatternToUnfoldMatcher(IntermNodePatternMatcher::kUnfoldedShortCircuitExpression)
+{}
+
+bool UnfoldShortCircuitTraverser::visitBinary(Visit visit, TIntermBinary *node)
+{
+ if (mFoundShortCircuit)
+ return false;
+
+ if (visit != PreVisit)
+ return true;
+
+ if (!mPatternToUnfoldMatcher.match(node, getParentNode()))
+ return true;
+
+ // If our right node doesn't have side effects, we know we don't need to unfold this
+ // expression: there will be no short-circuiting side effects to avoid
+ // (note: unfolding doesn't depend on the left node -- it will always be evaluated)
+ ASSERT(node->getRight()->hasSideEffects());
+
+ mFoundShortCircuit = true;
+
+ switch (node->getOp())
+ {
+ case EOpLogicalOr:
+ {
+ // "x || y" is equivalent to "x ? true : y", which unfolds to "bool s; if(x) s = true;
+ // else s = y;",
+ // and then further simplifies down to "bool s = x; if(!s) s = y;".
+
+ TIntermSequence insertions;
+ const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>();
+ TVariable *resultVariable = CreateTempVariable(mSymbolTable, boolType);
+
+ ASSERT(node->getLeft()->getType() == *boolType);
+ insertions.push_back(CreateTempInitDeclarationNode(resultVariable, node->getLeft()));
+
+ TIntermBlock *assignRightBlock = new TIntermBlock();
+ ASSERT(node->getRight()->getType() == *boolType);
+ assignRightBlock->getSequence()->push_back(
+ CreateTempAssignmentNode(resultVariable, node->getRight()));
+
+ TIntermUnary *notTempSymbol =
+ new TIntermUnary(EOpLogicalNot, CreateTempSymbolNode(resultVariable), nullptr);
+ TIntermIfElse *ifNode = new TIntermIfElse(notTempSymbol, assignRightBlock, nullptr);
+ insertions.push_back(ifNode);
+
+ insertStatementsInParentBlock(insertions);
+
+ queueReplacement(CreateTempSymbolNode(resultVariable), OriginalNode::IS_DROPPED);
+ return false;
+ }
+ case EOpLogicalAnd:
+ {
+ // "x && y" is equivalent to "x ? y : false", which unfolds to "bool s; if(x) s = y;
+ // else s = false;",
+ // and then further simplifies down to "bool s = x; if(s) s = y;".
+ TIntermSequence insertions;
+ const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>();
+ TVariable *resultVariable = CreateTempVariable(mSymbolTable, boolType);
+
+ ASSERT(node->getLeft()->getType() == *boolType);
+ insertions.push_back(CreateTempInitDeclarationNode(resultVariable, node->getLeft()));
+
+ TIntermBlock *assignRightBlock = new TIntermBlock();
+ ASSERT(node->getRight()->getType() == *boolType);
+ assignRightBlock->getSequence()->push_back(
+ CreateTempAssignmentNode(resultVariable, node->getRight()));
+
+ TIntermIfElse *ifNode =
+ new TIntermIfElse(CreateTempSymbolNode(resultVariable), assignRightBlock, nullptr);
+ insertions.push_back(ifNode);
+
+ insertStatementsInParentBlock(insertions);
+
+ queueReplacement(CreateTempSymbolNode(resultVariable), OriginalNode::IS_DROPPED);
+ return false;
+ }
+ default:
+ UNREACHABLE();
+ return true;
+ }
+}
+
+bool UnfoldShortCircuitTraverser::visitTernary(Visit visit, TIntermTernary *node)
+{
+ if (mFoundShortCircuit)
+ return false;
+
+ if (visit != PreVisit)
+ return true;
+
+ if (!mPatternToUnfoldMatcher.match(node))
+ return true;
+
+ mFoundShortCircuit = true;
+
+ // Unfold "b ? x : y" into "type s; if(b) s = x; else s = y;"
+ TIntermSequence insertions;
+ TIntermDeclaration *tempDeclaration = nullptr;
+ TVariable *resultVariable = DeclareTempVariable(mSymbolTable, new TType(node->getType()),
+ EvqTemporary, &tempDeclaration);
+ insertions.push_back(tempDeclaration);
+
+ TIntermBlock *trueBlock = new TIntermBlock();
+ TIntermBinary *trueAssignment =
+ CreateTempAssignmentNode(resultVariable, node->getTrueExpression());
+ trueBlock->getSequence()->push_back(trueAssignment);
+
+ TIntermBlock *falseBlock = new TIntermBlock();
+ TIntermBinary *falseAssignment =
+ CreateTempAssignmentNode(resultVariable, node->getFalseExpression());
+ falseBlock->getSequence()->push_back(falseAssignment);
+
+ TIntermIfElse *ifNode =
+ new TIntermIfElse(node->getCondition()->getAsTyped(), trueBlock, falseBlock);
+ insertions.push_back(ifNode);
+
+ insertStatementsInParentBlock(insertions);
+
+ TIntermSymbol *ternaryResult = CreateTempSymbolNode(resultVariable);
+ queueReplacement(ternaryResult, OriginalNode::IS_DROPPED);
+
+ return false;
+}
+
+void UnfoldShortCircuitTraverser::nextIteration()
+{
+ mFoundShortCircuit = false;
+}
+
+} // namespace
+
+bool UnfoldShortCircuitToIf(TCompiler *compiler, TIntermNode *root, TSymbolTable *symbolTable)
+{
+ UnfoldShortCircuitTraverser traverser(symbolTable);
+ // Unfold one operator at a time, and reset the traverser between iterations.
+ do
+ {
+ traverser.nextIteration();
+ root->traverse(&traverser);
+ if (traverser.foundShortCircuit())
+ {
+ if (!traverser.updateTree(compiler, root))
+ {
+ return false;
+ }
+ }
+ } while (traverser.foundShortCircuit());
+
+ return true;
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h
new file mode 100644
index 0000000000..97587e4d38
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/UnfoldShortCircuitToIf.h
@@ -0,0 +1,30 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// UnfoldShortCircuitToIf is an AST traverser to convert short-circuiting operators to if-else
+// statements.
+// The results are assigned to s# temporaries, which are used by the main translator instead of
+// the original expression.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermNode;
+class TSymbolTable;
+
+[[nodiscard]] bool UnfoldShortCircuitToIf(TCompiler *compiler,
+ TIntermNode *root,
+ TSymbolTable *symbolTable);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_UNFOLDSHORTCIRCUIT_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp
new file mode 100644
index 0000000000..58a941da0e
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.cpp
@@ -0,0 +1,126 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// WrapSwitchStatementsInBlocks.cpp: Wrap switch statements in blocks and declare all switch-scoped
+// variables there to make the AST compatible with HLSL output.
+//
+// switch (init)
+// {
+// case 0:
+// float f;
+// default:
+// f = 1.0;
+// }
+//
+// becomes
+//
+// {
+// float f;
+// switch (init)
+// {
+// case 0:
+// default:
+// f = 1.0;
+// }
+// }
+
+#include "compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h"
+
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class WrapSwitchStatementsInBlocksTraverser : public TIntermTraverser
+{
+ public:
+ WrapSwitchStatementsInBlocksTraverser() : TIntermTraverser(true, false, false) {}
+
+ bool visitSwitch(Visit visit, TIntermSwitch *node) override;
+};
+
+bool WrapSwitchStatementsInBlocksTraverser::visitSwitch(Visit, TIntermSwitch *node)
+{
+ std::vector<TIntermDeclaration *> declarations;
+ TIntermSequence *statementList = node->getStatementList()->getSequence();
+ for (TIntermNode *statement : *statementList)
+ {
+ TIntermDeclaration *asDeclaration = statement->getAsDeclarationNode();
+ if (asDeclaration)
+ {
+ declarations.push_back(asDeclaration);
+ }
+ }
+ if (declarations.empty())
+ {
+ // We don't need to wrap the switch if it doesn't contain declarations as its direct
+ // descendants.
+ return true;
+ }
+
+ TIntermBlock *wrapperBlock = new TIntermBlock();
+ for (TIntermDeclaration *declaration : declarations)
+ {
+ // SeparateDeclarations should have already been run.
+ ASSERT(declaration->getSequence()->size() == 1);
+
+ TIntermDeclaration *declarationInBlock = new TIntermDeclaration();
+ TIntermSymbol *declaratorAsSymbol = declaration->getSequence()->at(0)->getAsSymbolNode();
+ if (declaratorAsSymbol)
+ {
+ // This is a simple declaration like: "float f;"
+ // Remove the declaration from inside the switch and put it in the wrapping block.
+ TIntermSequence emptyReplacement;
+ mMultiReplacements.emplace_back(node->getStatementList(), declaration,
+ std::move(emptyReplacement));
+
+ declarationInBlock->appendDeclarator(declaratorAsSymbol->deepCopy());
+ // The declaration can't be the last statement inside the switch since unused variables
+ // should already have been pruned.
+ ASSERT(declaration != statementList->back());
+ }
+ else
+ {
+ // This is an init declaration like: "float f = 0.0;"
+ // Change the init declaration inside the switch into an assignment and put a plain
+ // declaration in the wrapping block.
+ TIntermBinary *declaratorAsBinary =
+ declaration->getSequence()->at(0)->getAsBinaryNode();
+ ASSERT(declaratorAsBinary);
+
+ TIntermBinary *initAssignment = new TIntermBinary(
+ EOpAssign, declaratorAsBinary->getLeft(), declaratorAsBinary->getRight());
+
+ queueReplacementWithParent(node->getStatementList(), declaration, initAssignment,
+ OriginalNode::IS_DROPPED);
+
+ declarationInBlock->appendDeclarator(declaratorAsBinary->getLeft()->deepCopy());
+ }
+ wrapperBlock->appendStatement(declarationInBlock);
+ }
+
+ wrapperBlock->appendStatement(node);
+ queueReplacement(wrapperBlock, OriginalNode::BECOMES_CHILD);
+
+ // Should be fine to process multiple switch statements, even nesting ones in the same
+ // traversal.
+ return true;
+}
+
+} // anonymous namespace
+
+// Wrap switch statements in the AST into blocks when needed.
+bool WrapSwitchStatementsInBlocks(TCompiler *compiler, TIntermBlock *root)
+{
+ WrapSwitchStatementsInBlocksTraverser traverser;
+ root->traverse(&traverser);
+ return traverser.updateTree(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h
new file mode 100644
index 0000000000..0c765c306a
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/d3d/WrapSwitchStatementsInBlocks.h
@@ -0,0 +1,25 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// WrapSwitchStatementsInBlocks.h: Wrap switch statements in blocks and declare all switch-scoped
+// variables there to make the AST compatible with HLSL output.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+
+// Wrap switch statements in the AST into blocks when needed. Returns true if the AST was changed.
+[[nodiscard]] bool WrapSwitchStatementsInBlocks(TCompiler *compiler, TIntermBlock *root);
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_D3D_WRAPSWITCHSTATEMENTSINBLOCKS_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp
new file mode 100644
index 0000000000..309cb14752
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.cpp
@@ -0,0 +1,54 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ClampFragDepth.cpp: Limit the value that is written to gl_FragDepth to the range [0.0, 1.0].
+// The clamping is run at the very end of shader execution, and is only performed if the shader
+// statically accesses gl_FragDepth.
+//
+
+#include "compiler/translator/tree_ops/gl/ClampFragDepth.h"
+
+#include "compiler/translator/ImmutableString.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/BuiltIn.h"
+#include "compiler/translator/tree_util/FindSymbolNode.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/RunAtTheEndOfShader.h"
+
+namespace sh
+{
+
+bool ClampFragDepth(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ // Only clamp gl_FragDepth if it's used in the shader.
+ if (!FindSymbolNode(root, ImmutableString("gl_FragDepth")))
+ {
+ return true;
+ }
+
+ TIntermSymbol *fragDepthNode = new TIntermSymbol(BuiltInVariable::gl_FragDepth());
+
+ TIntermTyped *minFragDepthNode = CreateZeroNode(TType(EbtFloat, EbpHigh, EvqConst));
+
+ TConstantUnion *maxFragDepthConstant = new TConstantUnion();
+ maxFragDepthConstant->setFConst(1.0);
+ TIntermConstantUnion *maxFragDepthNode =
+ new TIntermConstantUnion(maxFragDepthConstant, TType(EbtFloat, EbpHigh, EvqConst));
+
+ // clamp(gl_FragDepth, 0.0, 1.0)
+ TIntermSequence clampArguments;
+ clampArguments.push_back(fragDepthNode->deepCopy());
+ clampArguments.push_back(minFragDepthNode);
+ clampArguments.push_back(maxFragDepthNode);
+ TIntermTyped *clampedFragDepth =
+ CreateBuiltInFunctionCallNode("clamp", &clampArguments, *symbolTable, 100);
+
+ // gl_FragDepth = clamp(gl_FragDepth, 0.0, 1.0)
+ TIntermBinary *assignFragDepth = new TIntermBinary(EOpAssign, fragDepthNode, clampedFragDepth);
+
+ return RunAtTheEndOfShader(compiler, root, assignFragDepth, symbolTable);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h
new file mode 100644
index 0000000000..70326c9a34
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/ClampFragDepth.h
@@ -0,0 +1,39 @@
+//
+// Copyright 2017 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// ClampFragDepth.h: Limit the value that is written to gl_FragDepth to the range [0.0, 1.0].
+// The clamping is run at the very end of shader execution, and is only performed if the shader
+// statically accesses gl_FragDepth.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_CLAMPFRAGDEPTH_H_
+#define COMPILER_TRANSLATOR_TREEOPS_GL_CLAMPFRAGDEPTH_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+#ifdef ANGLE_ENABLE_GLSL
+[[nodiscard]] bool ClampFragDepth(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+#else
+[[nodiscard]] ANGLE_INLINE bool ClampFragDepth(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_GL_CLAMPFRAGDEPTH_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp
new file mode 100644
index 0000000000..e15ef32166
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.cpp
@@ -0,0 +1,119 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#include "compiler/translator/tree_ops/gl/RegenerateStructNames.h"
+
+#include "common/debug.h"
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/ImmutableStringBuilder.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+#include <set>
+
+namespace sh
+{
+
+namespace
+{
+constexpr const ImmutableString kPrefix("_webgl_struct_");
+} // anonymous namespace
+
+class RegenerateStructNamesTraverser : public TIntermTraverser
+{
+ public:
+ RegenerateStructNamesTraverser(TSymbolTable *symbolTable)
+ : TIntermTraverser(true, false, false, symbolTable), mScopeDepth(0)
+ {}
+
+ protected:
+ void visitSymbol(TIntermSymbol *) override;
+ bool visitBlock(Visit, TIntermBlock *block) override;
+
+ private:
+ // Indicating the depth of the current scope.
+ // The global scope is 1.
+ int mScopeDepth;
+
+ // If a struct is declared globally, push its ID in this set.
+ std::set<int> mDeclaredGlobalStructs;
+};
+
+void RegenerateStructNamesTraverser::visitSymbol(TIntermSymbol *symbol)
+{
+ ASSERT(symbol);
+ const TType &type = symbol->getType();
+ const TStructure *userType = type.getStruct();
+ if (!userType)
+ return;
+
+ if (userType->symbolType() == SymbolType::BuiltIn ||
+ userType->symbolType() == SymbolType::Empty)
+ {
+ // Built-in struct or nameless struct, do not touch it.
+ return;
+ }
+
+ int uniqueId = userType->uniqueId().get();
+
+ ASSERT(mScopeDepth > 0);
+ if (mScopeDepth == 1)
+ {
+ // If a struct is defined at global scope, we don't map its name.
+ // This is because at global level, the struct might be used to
+ // declare a uniform, so the same name needs to stay the same for
+ // vertex/fragment shaders. However, our mapping uses internal ID,
+ // which will be different for the same struct in vertex/fragment
+ // shaders.
+ // This is OK because names for any structs defined in other scopes
+ // will begin with "_webgl", which is reserved. So there will be
+ // no conflicts among unmapped struct names from global scope and
+ // mapped struct names from other scopes.
+ // However, we need to keep track of these global structs, so if a
+ // variable is used in a local scope, we don't try to modify the
+ // struct name through that variable.
+ mDeclaredGlobalStructs.insert(uniqueId);
+ return;
+ }
+ if (mDeclaredGlobalStructs.count(uniqueId) > 0)
+ return;
+ // Map {name} to _webgl_struct_{uniqueId}_{name}.
+ if (userType->name().beginsWith(kPrefix))
+ {
+ // The name has already been regenerated.
+ return;
+ }
+ ImmutableStringBuilder tmp(kPrefix.length() + sizeof(uniqueId) * 2u + 1u +
+ userType->name().length());
+ tmp << kPrefix;
+ tmp.appendHex(uniqueId);
+ tmp << '_' << userType->name();
+
+ // TODO(oetuaho): Add another mechanism to change symbol names so that the const_cast is not
+ // needed.
+ const_cast<TStructure *>(userType)->setName(tmp);
+}
+
+bool RegenerateStructNamesTraverser::visitBlock(Visit, TIntermBlock *block)
+{
+ ++mScopeDepth;
+ TIntermSequence &sequence = *(block->getSequence());
+ for (TIntermNode *node : sequence)
+ {
+ node->traverse(this);
+ }
+ --mScopeDepth;
+ return false;
+}
+
+bool RegenerateStructNames(TCompiler *compiler, TIntermBlock *root, TSymbolTable *symbolTable)
+{
+ RegenerateStructNamesTraverser traverser(symbolTable);
+ root->traverse(&traverser);
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h
new file mode 100644
index 0000000000..04cfe6a476
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RegenerateStructNames.h
@@ -0,0 +1,34 @@
+//
+// Copyright 2002 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_REGENERATESTRUCTNAMES_H_
+#define COMPILER_TRANSLATOR_TREEOPS_GL_REGENERATESTRUCTNAMES_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+#if defined(ANGLE_ENABLE_GLSL)
+[[nodiscard]] bool RegenerateStructNames(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable);
+#else
+[[nodiscard]] ANGLE_INLINE bool RegenerateStructNames(TCompiler *compiler,
+ TIntermBlock *root,
+ TSymbolTable *symbolTable)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_GL_REGENERATESTRUCTNAMES_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp
new file mode 100644
index 0000000000..83a0f029b8
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.cpp
@@ -0,0 +1,97 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteRepeatedAssignToSwizzled.cpp: Rewrite expressions that assign an assignment to a swizzled
+// vector, like:
+// v.x = z = expression;
+// to:
+// z = expression;
+// v.x = z;
+//
+// Note that this doesn't handle some corner cases: expressions nested inside other expressions,
+// inside loop headers, or inside if conditions.
+
+#include "compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h"
+
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/tree_util/IntermTraverse.h"
+
+namespace sh
+{
+
+namespace
+{
+
+class RewriteAssignToSwizzledTraverser : public TIntermTraverser
+{
+ public:
+ [[nodiscard]] static bool rewrite(TCompiler *compiler, TIntermBlock *root);
+
+ private:
+ RewriteAssignToSwizzledTraverser();
+
+ bool visitBinary(Visit, TIntermBinary *node) override;
+
+ void nextIteration();
+
+ bool didRewrite() { return mDidRewrite; }
+
+ bool mDidRewrite;
+};
+
+// static
+bool RewriteAssignToSwizzledTraverser::rewrite(TCompiler *compiler, TIntermBlock *root)
+{
+ RewriteAssignToSwizzledTraverser rewrite;
+ do
+ {
+ rewrite.nextIteration();
+ root->traverse(&rewrite);
+ if (!rewrite.updateTree(compiler, root))
+ {
+ return false;
+ }
+ } while (rewrite.didRewrite());
+
+ return true;
+}
+
+RewriteAssignToSwizzledTraverser::RewriteAssignToSwizzledTraverser()
+ : TIntermTraverser(true, false, false), mDidRewrite(false)
+{}
+
+void RewriteAssignToSwizzledTraverser::nextIteration()
+{
+ mDidRewrite = false;
+}
+
+bool RewriteAssignToSwizzledTraverser::visitBinary(Visit, TIntermBinary *node)
+{
+ TIntermBinary *rightBinary = node->getRight()->getAsBinaryNode();
+ TIntermBlock *parentBlock = getParentNode()->getAsBlock();
+ if (parentBlock && node->isAssignment() && node->getLeft()->getAsSwizzleNode() && rightBinary &&
+ rightBinary->isAssignment())
+ {
+ TIntermSequence replacements;
+ replacements.push_back(rightBinary);
+ TIntermTyped *rightAssignmentTargetCopy = rightBinary->getLeft()->deepCopy();
+ TIntermBinary *lastAssign =
+ new TIntermBinary(EOpAssign, node->getLeft(), rightAssignmentTargetCopy);
+ replacements.push_back(lastAssign);
+ mMultiReplacements.emplace_back(parentBlock, node, std::move(replacements));
+ mDidRewrite = true;
+ return false;
+ }
+ return true;
+}
+
+} // anonymous namespace
+
+bool RewriteRepeatedAssignToSwizzled(TCompiler *compiler, TIntermBlock *root)
+{
+ return RewriteAssignToSwizzledTraverser::rewrite(compiler, root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h
new file mode 100644
index 0000000000..1ab9b7ebb8
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/RewriteRepeatedAssignToSwizzled.h
@@ -0,0 +1,40 @@
+//
+// Copyright 2018 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// RewriteRepeatedAssignToSwizzled.h: Rewrite expressions that assign an assignment to a swizzled
+// vector, like:
+// v.x = z = expression;
+// to:
+// z = expression;
+// v.x = z;
+//
+// Note that this doesn't handle some corner cases: expressions nested inside other expressions,
+// inside loop headers, or inside if conditions.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_REWRITEREPEATEDASSIGNTOSWIZZLED_H_
+#define COMPILER_TRANSLATOR_TREEOPS_GL_REWRITEREPEATEDASSIGNTOSWIZZLED_H_
+
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+
+#ifdef ANGLE_ENABLE_GLSL
+[[nodiscard]] bool RewriteRepeatedAssignToSwizzled(TCompiler *compiler, TIntermBlock *root);
+#else
+[[nodiscard]] ANGLE_INLINE bool RewriteRepeatedAssignToSwizzled(TCompiler *compiler,
+ TIntermBlock *root)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_GL_REWRITEREPEATEDASSIGNTOSWIZZLED_H_
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp
new file mode 100644
index 0000000000..e94ca2fd17
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.cpp
@@ -0,0 +1,108 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// UseInterfaceBlockFields.cpp: insert statements to reference all members in InterfaceBlock list at
+// the beginning of main. This is to work around a Mac driver that treats unused standard/shared
+// uniform blocks as inactive.
+
+#include "compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h"
+
+#include "compiler/translator/Compiler.h"
+#include "compiler/translator/IntermNode.h"
+#include "compiler/translator/SymbolTable.h"
+#include "compiler/translator/tree_util/FindMain.h"
+#include "compiler/translator/tree_util/IntermNode_util.h"
+#include "compiler/translator/util.h"
+
+namespace sh
+{
+
+namespace
+{
+
+void AddNodeUseStatements(TIntermTyped *node, TIntermSequence *sequence)
+{
+ if (node->isArray())
+ {
+ for (unsigned int i = 0u; i < node->getOutermostArraySize(); ++i)
+ {
+ TIntermBinary *element =
+ new TIntermBinary(EOpIndexDirect, node->deepCopy(), CreateIndexNode(i));
+ AddNodeUseStatements(element, sequence);
+ }
+ }
+ else
+ {
+ sequence->insert(sequence->begin(), node);
+ }
+}
+
+void AddFieldUseStatements(const ShaderVariable &var,
+ TIntermSequence *sequence,
+ const TSymbolTable &symbolTable)
+{
+ ASSERT(var.name.find_last_of('[') == std::string::npos);
+ TIntermSymbol *symbol = ReferenceGlobalVariable(ImmutableString(var.name), symbolTable);
+ AddNodeUseStatements(symbol, sequence);
+}
+
+void InsertUseCode(const InterfaceBlock &block, TIntermTyped *blockNode, TIntermSequence *sequence)
+{
+ for (unsigned int i = 0; i < block.fields.size(); ++i)
+ {
+ TIntermBinary *element = new TIntermBinary(EOpIndexDirectInterfaceBlock,
+ blockNode->deepCopy(), CreateIndexNode(i));
+ sequence->insert(sequence->begin(), element);
+ }
+}
+
+void InsertUseCode(TIntermSequence *sequence,
+ const InterfaceBlockList &blocks,
+ const TSymbolTable &symbolTable)
+{
+ for (const auto &block : blocks)
+ {
+ if (block.instanceName.empty())
+ {
+ for (const auto &var : block.fields)
+ {
+ AddFieldUseStatements(var, sequence, symbolTable);
+ }
+ }
+ else if (block.arraySize > 0u)
+ {
+ TIntermSymbol *arraySymbol =
+ ReferenceGlobalVariable(ImmutableString(block.instanceName), symbolTable);
+ for (unsigned int i = 0u; i < block.arraySize; ++i)
+ {
+ TIntermBinary *elementSymbol =
+ new TIntermBinary(EOpIndexDirect, arraySymbol->deepCopy(), CreateIndexNode(i));
+ InsertUseCode(block, elementSymbol, sequence);
+ }
+ }
+ else
+ {
+ TIntermSymbol *blockSymbol =
+ ReferenceGlobalVariable(ImmutableString(block.instanceName), symbolTable);
+ InsertUseCode(block, blockSymbol, sequence);
+ }
+ }
+}
+
+} // namespace
+
+bool UseInterfaceBlockFields(TCompiler *compiler,
+ TIntermBlock *root,
+ const InterfaceBlockList &blocks,
+ const TSymbolTable &symbolTable)
+{
+ TIntermBlock *mainBody = FindMainBody(root);
+ InsertUseCode(mainBody->getSequence(), blocks, symbolTable);
+
+ return compiler->validateAST(root);
+}
+
+} // namespace sh
diff --git a/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h
new file mode 100644
index 0000000000..a4f5f3e5e6
--- /dev/null
+++ b/gfx/angle/checkout/src/compiler/translator/tree_ops/gl/UseInterfaceBlockFields.h
@@ -0,0 +1,44 @@
+//
+// Copyright 2016 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+
+// UseInterfaceBlockFields.h: insert statements to reference all members in InterfaceBlock list at
+// the beginning of main. This is to work around a Mac driver that treats unused standard/shared
+// uniform blocks as inactive.
+
+#ifndef COMPILER_TRANSLATOR_TREEOPS_GL_USEINTERFACEBLOCKFIELDS_H_
+#define COMPILER_TRANSLATOR_TREEOPS_GL_USEINTERFACEBLOCKFIELDS_H_
+
+#include <GLSLANG/ShaderLang.h>
+#include "common/angleutils.h"
+
+namespace sh
+{
+
+class TCompiler;
+class TIntermBlock;
+class TSymbolTable;
+
+using InterfaceBlockList = std::vector<sh::InterfaceBlock>;
+
+#ifdef ANGLE_ENABLE_GLSL
+[[nodiscard]] bool UseInterfaceBlockFields(TCompiler *compiler,
+ TIntermBlock *root,
+ const InterfaceBlockList &blocks,
+ const TSymbolTable &symbolTable);
+#else
+[[nodiscard]] ANGLE_INLINE bool UseInterfaceBlockFields(TCompiler *compiler,
+ TIntermBlock *root,
+ const InterfaceBlockList &blocks,
+ const TSymbolTable &symbolTable)
+{
+ UNREACHABLE();
+ return false;
+}
+#endif
+
+} // namespace sh
+
+#endif // COMPILER_TRANSLATOR_TREEOPS_GL_USEINTERFACEBLOCKFIELDS_H_