1 //
2 // Copyright 2002 The ANGLE Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style license that can be
4 // found in the LICENSE file.
5 //
6 // RemoveDynamicIndexing is an AST traverser to remove dynamic indexing of non-SSBO vectors and
7 // matrices, replacing them with calls to functions that choose which component to return or write.
8 // We don't need to consider dynamic indexing in SSBO since it can be directly as part of the offset
9 // of RWByteAddressBuffer.
10 //
11
12 #include "compiler/translator/tree_ops/RemoveDynamicIndexing.h"
13
14 #include "compiler/translator/Compiler.h"
15 #include "compiler/translator/Diagnostics.h"
16 #include "compiler/translator/InfoSink.h"
17 #include "compiler/translator/StaticType.h"
18 #include "compiler/translator/SymbolTable.h"
19 #include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
20 #include "compiler/translator/tree_util/IntermNode_util.h"
21 #include "compiler/translator/tree_util/IntermTraverse.h"
22
23 namespace sh
24 {
25
26 namespace
27 {
28
29 using DynamicIndexingNodeMatcher = std::function<bool(TIntermBinary *)>;
30
31 const TType *kIndexType = StaticType::Get<EbtInt, EbpHigh, EvqParamIn, 1, 1>();
32
33 constexpr const ImmutableString kBaseName("base");
34 constexpr const ImmutableString kIndexName("index");
35 constexpr const ImmutableString kValueName("value");
36
GetIndexFunctionName(const TType & type,bool write)37 std::string GetIndexFunctionName(const TType &type, bool write)
38 {
39 TInfoSinkBase nameSink;
40 nameSink << "dyn_index_";
41 if (write)
42 {
43 nameSink << "write_";
44 }
45 if (type.isMatrix())
46 {
47 nameSink << "mat" << type.getCols() << "x" << type.getRows();
48 }
49 else
50 {
51 switch (type.getBasicType())
52 {
53 case EbtInt:
54 nameSink << "ivec";
55 break;
56 case EbtBool:
57 nameSink << "bvec";
58 break;
59 case EbtUInt:
60 nameSink << "uvec";
61 break;
62 case EbtFloat:
63 nameSink << "vec";
64 break;
65 default:
66 UNREACHABLE();
67 }
68 nameSink << type.getNominalSize();
69 }
70 return nameSink.str();
71 }
72
CreateIntConstantNode(int i)73 TIntermConstantUnion *CreateIntConstantNode(int i)
74 {
75 TConstantUnion *constant = new TConstantUnion();
76 constant->setIConst(i);
77 return new TIntermConstantUnion(constant, TType(EbtInt, EbpHigh));
78 }
79
EnsureSignedInt(TIntermTyped * node)80 TIntermTyped *EnsureSignedInt(TIntermTyped *node)
81 {
82 if (node->getBasicType() == EbtInt)
83 return node;
84
85 TIntermSequence arguments;
86 arguments.push_back(node);
87 return TIntermAggregate::CreateConstructor(TType(EbtInt), &arguments);
88 }
89
GetFieldType(const TType & indexedType)90 TType *GetFieldType(const TType &indexedType)
91 {
92 TType *fieldType = new TType(indexedType);
93 if (indexedType.isMatrix())
94 {
95 fieldType->toMatrixColumnType();
96 }
97 else
98 {
99 ASSERT(indexedType.isVector());
100 fieldType->toComponentType();
101 }
102 // Default precision to highp if not specified. For example in |vec3(0)[i], i < 0|, there is no
103 // precision assigned to vec3(0).
104 if (fieldType->getPrecision() == EbpUndefined)
105 {
106 fieldType->setPrecision(EbpHigh);
107 }
108 return fieldType;
109 }
110
GetBaseType(const TType & type,bool write)111 const TType *GetBaseType(const TType &type, bool write)
112 {
113 TType *baseType = new TType(type);
114 // Conservatively use highp here, even if the indexed type is not highp. That way the code can't
115 // end up using mediump version of an indexing function for a highp value, if both mediump and
116 // highp values are being indexed in the shader. For HLSL precision doesn't matter, but in
117 // principle this code could be used with multiple backends.
118 baseType->setPrecision(EbpHigh);
119 baseType->setQualifier(EvqParamInOut);
120 if (!write)
121 baseType->setQualifier(EvqParamIn);
122 return baseType;
123 }
124
125 // Generate a read or write function for one field in a vector/matrix.
126 // Out-of-range indices are clamped. This is consistent with how ANGLE handles out-of-range
127 // indices in other places.
128 // Note that indices can be either int or uint. We create only int versions of the functions,
129 // and convert uint indices to int at the call site.
130 // read function example:
131 // float dyn_index_vec2(in vec2 base, in int index)
132 // {
133 // switch(index)
134 // {
135 // case (0):
136 // return base[0];
137 // case (1):
138 // return base[1];
139 // default:
140 // break;
141 // }
142 // if (index < 0)
143 // return base[0];
144 // return base[1];
145 // }
146 // write function example:
147 // void dyn_index_write_vec2(inout vec2 base, in int index, in float value)
148 // {
149 // switch(index)
150 // {
151 // case (0):
152 // base[0] = value;
153 // return;
154 // case (1):
155 // base[1] = value;
156 // return;
157 // default:
158 // break;
159 // }
160 // if (index < 0)
161 // {
162 // base[0] = value;
163 // return;
164 // }
165 // base[1] = value;
166 // }
167 // Note that else is not used in above functions to avoid the RewriteElseBlocks transformation.
GetIndexFunctionDefinition(const TType & type,bool write,const TFunction & func,TSymbolTable * symbolTable)168 TIntermFunctionDefinition *GetIndexFunctionDefinition(const TType &type,
169 bool write,
170 const TFunction &func,
171 TSymbolTable *symbolTable)
172 {
173 ASSERT(!type.isArray());
174
175 int numCases = 0;
176 if (type.isMatrix())
177 {
178 numCases = type.getCols();
179 }
180 else
181 {
182 numCases = type.getNominalSize();
183 }
184
185 std::string functionName = GetIndexFunctionName(type, write);
186 TIntermFunctionPrototype *prototypeNode = CreateInternalFunctionPrototypeNode(func);
187
188 TIntermSymbol *baseParam = new TIntermSymbol(func.getParam(0));
189 TIntermSymbol *indexParam = new TIntermSymbol(func.getParam(1));
190 TIntermSymbol *valueParam = nullptr;
191 if (write)
192 {
193 valueParam = new TIntermSymbol(func.getParam(2));
194 }
195
196 TIntermBlock *statementList = new TIntermBlock();
197 for (int i = 0; i < numCases; ++i)
198 {
199 TIntermCase *caseNode = new TIntermCase(CreateIntConstantNode(i));
200 statementList->getSequence()->push_back(caseNode);
201
202 TIntermBinary *indexNode =
203 new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(i));
204 if (write)
205 {
206 TIntermBinary *assignNode =
207 new TIntermBinary(EOpAssign, indexNode, valueParam->deepCopy());
208 statementList->getSequence()->push_back(assignNode);
209 TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr);
210 statementList->getSequence()->push_back(returnNode);
211 }
212 else
213 {
214 TIntermBranch *returnNode = new TIntermBranch(EOpReturn, indexNode);
215 statementList->getSequence()->push_back(returnNode);
216 }
217 }
218
219 // Default case
220 TIntermCase *defaultNode = new TIntermCase(nullptr);
221 statementList->getSequence()->push_back(defaultNode);
222 TIntermBranch *breakNode = new TIntermBranch(EOpBreak, nullptr);
223 statementList->getSequence()->push_back(breakNode);
224
225 TIntermSwitch *switchNode = new TIntermSwitch(indexParam->deepCopy(), statementList);
226
227 TIntermBlock *bodyNode = new TIntermBlock();
228 bodyNode->getSequence()->push_back(switchNode);
229
230 TIntermBinary *cond =
231 new TIntermBinary(EOpLessThan, indexParam->deepCopy(), CreateIntConstantNode(0));
232
233 // Two blocks: one accesses (either reads or writes) the first element and returns,
234 // the other accesses the last element.
235 TIntermBlock *useFirstBlock = new TIntermBlock();
236 TIntermBlock *useLastBlock = new TIntermBlock();
237 TIntermBinary *indexFirstNode =
238 new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(0));
239 TIntermBinary *indexLastNode =
240 new TIntermBinary(EOpIndexDirect, baseParam->deepCopy(), CreateIndexNode(numCases - 1));
241 if (write)
242 {
243 TIntermBinary *assignFirstNode =
244 new TIntermBinary(EOpAssign, indexFirstNode, valueParam->deepCopy());
245 useFirstBlock->getSequence()->push_back(assignFirstNode);
246 TIntermBranch *returnNode = new TIntermBranch(EOpReturn, nullptr);
247 useFirstBlock->getSequence()->push_back(returnNode);
248
249 TIntermBinary *assignLastNode =
250 new TIntermBinary(EOpAssign, indexLastNode, valueParam->deepCopy());
251 useLastBlock->getSequence()->push_back(assignLastNode);
252 }
253 else
254 {
255 TIntermBranch *returnFirstNode = new TIntermBranch(EOpReturn, indexFirstNode);
256 useFirstBlock->getSequence()->push_back(returnFirstNode);
257
258 TIntermBranch *returnLastNode = new TIntermBranch(EOpReturn, indexLastNode);
259 useLastBlock->getSequence()->push_back(returnLastNode);
260 }
261 TIntermIfElse *ifNode = new TIntermIfElse(cond, useFirstBlock, nullptr);
262 bodyNode->getSequence()->push_back(ifNode);
263 bodyNode->getSequence()->push_back(useLastBlock);
264
265 TIntermFunctionDefinition *indexingFunction =
266 new TIntermFunctionDefinition(prototypeNode, bodyNode);
267 return indexingFunction;
268 }
269
270 class RemoveDynamicIndexingTraverser : public TLValueTrackingTraverser
271 {
272 public:
273 RemoveDynamicIndexingTraverser(DynamicIndexingNodeMatcher &&matcher,
274 TSymbolTable *symbolTable,
275 PerformanceDiagnostics *perfDiagnostics);
276
277 bool visitBinary(Visit visit, TIntermBinary *node) override;
278
279 void insertHelperDefinitions(TIntermNode *root);
280
281 void nextIteration();
282
usedTreeInsertion() const283 bool usedTreeInsertion() const { return mUsedTreeInsertion; }
284
285 protected:
286 // Maps of types that are indexed to the indexing function ids used for them. Note that these
287 // can not store multiple variants of the same type with different precisions - only one
288 // precision gets stored.
289 std::map<TType, TFunction *> mIndexedVecAndMatrixTypes;
290 std::map<TType, TFunction *> mWrittenVecAndMatrixTypes;
291
292 bool mUsedTreeInsertion;
293
294 // When true, the traverser will remove side effects from any indexing expression.
295 // This is done so that in code like
296 // V[j++][i]++.
297 // where V is an array of vectors, j++ will only be evaluated once.
298 bool mRemoveIndexSideEffectsInSubtree;
299
300 DynamicIndexingNodeMatcher mMatcher;
301 PerformanceDiagnostics *mPerfDiagnostics;
302 };
303
RemoveDynamicIndexingTraverser(DynamicIndexingNodeMatcher && matcher,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)304 RemoveDynamicIndexingTraverser::RemoveDynamicIndexingTraverser(
305 DynamicIndexingNodeMatcher &&matcher,
306 TSymbolTable *symbolTable,
307 PerformanceDiagnostics *perfDiagnostics)
308 : TLValueTrackingTraverser(true, false, false, symbolTable),
309 mUsedTreeInsertion(false),
310 mRemoveIndexSideEffectsInSubtree(false),
311 mMatcher(matcher),
312 mPerfDiagnostics(perfDiagnostics)
313 {}
314
insertHelperDefinitions(TIntermNode * root)315 void RemoveDynamicIndexingTraverser::insertHelperDefinitions(TIntermNode *root)
316 {
317 TIntermBlock *rootBlock = root->getAsBlock();
318 ASSERT(rootBlock != nullptr);
319 TIntermSequence insertions;
320 for (auto &type : mIndexedVecAndMatrixTypes)
321 {
322 insertions.push_back(
323 GetIndexFunctionDefinition(type.first, false, *type.second, mSymbolTable));
324 }
325 for (auto &type : mWrittenVecAndMatrixTypes)
326 {
327 insertions.push_back(
328 GetIndexFunctionDefinition(type.first, true, *type.second, mSymbolTable));
329 }
330 rootBlock->insertChildNodes(0, insertions);
331 }
332
333 // Create a call to dyn_index_*() based on an indirect indexing op node
CreateIndexFunctionCall(TIntermBinary * node,TIntermTyped * index,TFunction * indexingFunction)334 TIntermAggregate *CreateIndexFunctionCall(TIntermBinary *node,
335 TIntermTyped *index,
336 TFunction *indexingFunction)
337 {
338 ASSERT(node->getOp() == EOpIndexIndirect);
339 TIntermSequence arguments;
340 arguments.push_back(node->getLeft());
341 arguments.push_back(index);
342
343 TIntermAggregate *indexingCall =
344 TIntermAggregate::CreateFunctionCall(*indexingFunction, &arguments);
345 indexingCall->setLine(node->getLine());
346 return indexingCall;
347 }
348
CreateIndexedWriteFunctionCall(TIntermBinary * node,TVariable * index,TVariable * writtenValue,TFunction * indexedWriteFunction)349 TIntermAggregate *CreateIndexedWriteFunctionCall(TIntermBinary *node,
350 TVariable *index,
351 TVariable *writtenValue,
352 TFunction *indexedWriteFunction)
353 {
354 ASSERT(node->getOp() == EOpIndexIndirect);
355 TIntermSequence arguments;
356 // Deep copy the child nodes so that two pointers to the same node don't end up in the tree.
357 arguments.push_back(node->getLeft()->deepCopy());
358 arguments.push_back(CreateTempSymbolNode(index));
359 arguments.push_back(CreateTempSymbolNode(writtenValue));
360
361 TIntermAggregate *indexedWriteCall =
362 TIntermAggregate::CreateFunctionCall(*indexedWriteFunction, &arguments);
363 indexedWriteCall->setLine(node->getLine());
364 return indexedWriteCall;
365 }
366
visitBinary(Visit visit,TIntermBinary * node)367 bool RemoveDynamicIndexingTraverser::visitBinary(Visit visit, TIntermBinary *node)
368 {
369 if (mUsedTreeInsertion)
370 return false;
371
372 if (node->getOp() == EOpIndexIndirect)
373 {
374 if (mRemoveIndexSideEffectsInSubtree)
375 {
376 ASSERT(node->getRight()->hasSideEffects());
377 // In case we're just removing index side effects, convert
378 // v_expr[index_expr]
379 // to this:
380 // int s0 = index_expr; v_expr[s0];
381 // Now v_expr[s0] can be safely executed several times without unintended side effects.
382 TIntermDeclaration *indexVariableDeclaration = nullptr;
383 TVariable *indexVariable = DeclareTempVariable(mSymbolTable, node->getRight(),
384 EvqTemporary, &indexVariableDeclaration);
385 insertStatementInParentBlock(indexVariableDeclaration);
386 mUsedTreeInsertion = true;
387
388 // Replace the index with the temp variable
389 TIntermSymbol *tempIndex = CreateTempSymbolNode(indexVariable);
390 queueReplacementWithParent(node, node->getRight(), tempIndex, OriginalNode::IS_DROPPED);
391 }
392 else if (mMatcher(node))
393 {
394 if (mPerfDiagnostics)
395 {
396 mPerfDiagnostics->warning(node->getLine(),
397 "Performance: dynamic indexing of vectors and "
398 "matrices is emulated and can be slow.",
399 "[]");
400 }
401 bool write = isLValueRequiredHere();
402
403 #if defined(ANGLE_ENABLE_ASSERTS)
404 // Make sure that IntermNodePatternMatcher is consistent with the slightly differently
405 // implemented checks in this traverser.
406 IntermNodePatternMatcher matcher(
407 IntermNodePatternMatcher::kDynamicIndexingOfVectorOrMatrixInLValue);
408 ASSERT(matcher.match(node, getParentNode(), isLValueRequiredHere()) == write);
409 #endif
410
411 const TType &type = node->getLeft()->getType();
412 ImmutableString indexingFunctionName(GetIndexFunctionName(type, false));
413 TFunction *indexingFunction = nullptr;
414 if (mIndexedVecAndMatrixTypes.find(type) == mIndexedVecAndMatrixTypes.end())
415 {
416 indexingFunction =
417 new TFunction(mSymbolTable, indexingFunctionName, SymbolType::AngleInternal,
418 GetFieldType(type), true);
419 indexingFunction->addParameter(new TVariable(
420 mSymbolTable, kBaseName, GetBaseType(type, false), SymbolType::AngleInternal));
421 indexingFunction->addParameter(
422 new TVariable(mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal));
423 mIndexedVecAndMatrixTypes[type] = indexingFunction;
424 }
425 else
426 {
427 indexingFunction = mIndexedVecAndMatrixTypes[type];
428 }
429
430 if (write)
431 {
432 // Convert:
433 // v_expr[index_expr]++;
434 // to this:
435 // int s0 = index_expr; float s1 = dyn_index(v_expr, s0); s1++;
436 // dyn_index_write(v_expr, s0, s1);
437 // This works even if index_expr has some side effects.
438 if (node->getLeft()->hasSideEffects())
439 {
440 // If v_expr has side effects, those need to be removed before proceeding.
441 // Otherwise the side effects of v_expr would be evaluated twice.
442 // The only case where an l-value can have side effects is when it is
443 // indexing. For example, it can be V[j++] where V is an array of vectors.
444 mRemoveIndexSideEffectsInSubtree = true;
445 return true;
446 }
447
448 TIntermBinary *leftBinary = node->getLeft()->getAsBinaryNode();
449 if (leftBinary != nullptr && mMatcher(leftBinary))
450 {
451 // This is a case like:
452 // mat2 m;
453 // m[a][b]++;
454 // Process the child node m[a] first.
455 return true;
456 }
457
458 // TODO(oetuaho@nvidia.com): This is not optimal if the expression using the value
459 // only writes it and doesn't need the previous value. http://anglebug.com/1116
460
461 TFunction *indexedWriteFunction = nullptr;
462 if (mWrittenVecAndMatrixTypes.find(type) == mWrittenVecAndMatrixTypes.end())
463 {
464 ImmutableString functionName(
465 GetIndexFunctionName(node->getLeft()->getType(), true));
466 indexedWriteFunction =
467 new TFunction(mSymbolTable, functionName, SymbolType::AngleInternal,
468 StaticType::GetBasic<EbtVoid, EbpUndefined>(), false);
469 indexedWriteFunction->addParameter(new TVariable(mSymbolTable, kBaseName,
470 GetBaseType(type, true),
471 SymbolType::AngleInternal));
472 indexedWriteFunction->addParameter(new TVariable(
473 mSymbolTable, kIndexName, kIndexType, SymbolType::AngleInternal));
474 TType *valueType = GetFieldType(type);
475 valueType->setQualifier(EvqParamIn);
476 indexedWriteFunction->addParameter(new TVariable(
477 mSymbolTable, kValueName, static_cast<const TType *>(valueType),
478 SymbolType::AngleInternal));
479 mWrittenVecAndMatrixTypes[type] = indexedWriteFunction;
480 }
481 else
482 {
483 indexedWriteFunction = mWrittenVecAndMatrixTypes[type];
484 }
485
486 TIntermSequence insertionsBefore;
487 TIntermSequence insertionsAfter;
488
489 // Store the index in a temporary signed int variable.
490 // s0 = index_expr;
491 TIntermTyped *indexInitializer = EnsureSignedInt(node->getRight());
492 TIntermDeclaration *indexVariableDeclaration = nullptr;
493 TVariable *indexVariable = DeclareTempVariable(
494 mSymbolTable, indexInitializer, EvqTemporary, &indexVariableDeclaration);
495 insertionsBefore.push_back(indexVariableDeclaration);
496
497 // s1 = dyn_index(v_expr, s0);
498 TIntermAggregate *indexingCall = CreateIndexFunctionCall(
499 node, CreateTempSymbolNode(indexVariable), indexingFunction);
500 TIntermDeclaration *fieldVariableDeclaration = nullptr;
501 TVariable *fieldVariable = DeclareTempVariable(
502 mSymbolTable, indexingCall, EvqTemporary, &fieldVariableDeclaration);
503 insertionsBefore.push_back(fieldVariableDeclaration);
504
505 // dyn_index_write(v_expr, s0, s1);
506 TIntermAggregate *indexedWriteCall = CreateIndexedWriteFunctionCall(
507 node, indexVariable, fieldVariable, indexedWriteFunction);
508 insertionsAfter.push_back(indexedWriteCall);
509 insertStatementsInParentBlock(insertionsBefore, insertionsAfter);
510
511 // replace the node with s1
512 queueReplacement(CreateTempSymbolNode(fieldVariable), OriginalNode::IS_DROPPED);
513 mUsedTreeInsertion = true;
514 }
515 else
516 {
517 // The indexed value is not being written, so we can simply convert
518 // v_expr[index_expr]
519 // into
520 // dyn_index(v_expr, index_expr)
521 // If the index_expr is unsigned, we'll convert it to signed.
522 ASSERT(!mRemoveIndexSideEffectsInSubtree);
523 TIntermAggregate *indexingCall = CreateIndexFunctionCall(
524 node, EnsureSignedInt(node->getRight()), indexingFunction);
525 queueReplacement(indexingCall, OriginalNode::IS_DROPPED);
526 }
527 }
528 }
529 return !mUsedTreeInsertion;
530 }
531
nextIteration()532 void RemoveDynamicIndexingTraverser::nextIteration()
533 {
534 mUsedTreeInsertion = false;
535 mRemoveIndexSideEffectsInSubtree = false;
536 }
537
RemoveDynamicIndexingIf(DynamicIndexingNodeMatcher && matcher,TCompiler * compiler,TIntermNode * root,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)538 bool RemoveDynamicIndexingIf(DynamicIndexingNodeMatcher &&matcher,
539 TCompiler *compiler,
540 TIntermNode *root,
541 TSymbolTable *symbolTable,
542 PerformanceDiagnostics *perfDiagnostics)
543 {
544 // This transformation adds function declarations after the fact and so some validation is
545 // momentarily disabled.
546 bool enableValidateFunctionCall = compiler->disableValidateFunctionCall();
547
548 RemoveDynamicIndexingTraverser traverser(std::move(matcher), symbolTable, perfDiagnostics);
549 do
550 {
551 traverser.nextIteration();
552 root->traverse(&traverser);
553 if (!traverser.updateTree(compiler, root))
554 {
555 return false;
556 }
557 } while (traverser.usedTreeInsertion());
558 // TODO(oetuaho@nvidia.com): It might be nicer to add the helper definitions also in the middle
559 // of traversal. Now the tree ends up in an inconsistent state in the middle, since there are
560 // function call nodes with no corresponding definition nodes. This needs special handling in
561 // TIntermLValueTrackingTraverser, and creates intricacies that are not easily apparent from a
562 // superficial reading of the code.
563 traverser.insertHelperDefinitions(root);
564
565 compiler->restoreValidateFunctionCall(enableValidateFunctionCall);
566 return compiler->validateAST(root);
567 }
568
569 } // namespace
570
RemoveDynamicIndexingOfNonSSBOVectorOrMatrix(TCompiler * compiler,TIntermNode * root,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)571 ANGLE_NO_DISCARD bool RemoveDynamicIndexingOfNonSSBOVectorOrMatrix(
572 TCompiler *compiler,
573 TIntermNode *root,
574 TSymbolTable *symbolTable,
575 PerformanceDiagnostics *perfDiagnostics)
576 {
577 DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) {
578 return IntermNodePatternMatcher::IsDynamicIndexingOfNonSSBOVectorOrMatrix(node);
579 };
580 return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable,
581 perfDiagnostics);
582 }
583
RemoveDynamicIndexingOfSwizzledVector(TCompiler * compiler,TIntermNode * root,TSymbolTable * symbolTable,PerformanceDiagnostics * perfDiagnostics)584 ANGLE_NO_DISCARD bool RemoveDynamicIndexingOfSwizzledVector(TCompiler *compiler,
585 TIntermNode *root,
586 TSymbolTable *symbolTable,
587 PerformanceDiagnostics *perfDiagnostics)
588 {
589 DynamicIndexingNodeMatcher matcher = [](TIntermBinary *node) {
590 return IntermNodePatternMatcher::IsDynamicIndexingOfSwizzledVector(node);
591 };
592 return RemoveDynamicIndexingIf(std::move(matcher), compiler, root, symbolTable,
593 perfDiagnostics);
594 }
595
596 } // namespace sh
597