• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2018 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 // RewriteStructSamplers: Extract samplers from structs.
7 //
8 
9 #include "compiler/translator/tree_ops/RewriteStructSamplers.h"
10 
11 #include "GLSLANG/ShaderVars.h"
12 #include "common/hash_containers.h"
13 #include "common/span.h"
14 #include "compiler/translator/Compiler.h"
15 #include "compiler/translator/ImmutableString.h"
16 #include "compiler/translator/ImmutableStringBuilder.h"
17 #include "compiler/translator/SymbolTable.h"
18 #include "compiler/translator/tree_util/IntermNode_util.h"
19 #include "compiler/translator/tree_util/IntermTraverse.h"
20 
21 namespace sh
22 {
23 namespace
24 {
25 
26 // Used to map one structure type to another (one where the samplers are removed).
27 struct StructureData
28 {
29     // The structure this was replaced with.  If nullptr, it means the structure is removed (because
30     // it had all samplers).
31     //
32     // ParseContext reorders the samplers to the end of the struct, so the EOpIndexDirectStruct
33     // expressions that select non-sampler members don't have to change when they are moved out of
34     // the struct.
35     const TStructure *modified;
36 };
37 
38 using StructureMap        = angle::HashMap<const TStructure *, StructureData>;
39 using StructureUniformMap = angle::HashMap<const TVariable *, const TVariable *>;
40 using ExtractedSamplerMap = angle::HashMap<std::string, const TVariable *>;
41 
42 TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
43     TCompiler *compiler,
44     TIntermBinary *node,
45     const StructureMap &structureMap,
46     const StructureUniformMap &structureUniformMap,
47     const ExtractedSamplerMap &extractedSamplers);
48 
RewriteExpressionVisitBinaryHelper(TCompiler * compiler,TIntermBinary * node,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)49 TIntermTyped *RewriteExpressionVisitBinaryHelper(TCompiler *compiler,
50                                                  TIntermBinary *node,
51                                                  const StructureMap &structureMap,
52                                                  const StructureUniformMap &structureUniformMap,
53                                                  const ExtractedSamplerMap &extractedSamplers)
54 {
55     // Only interested in EOpIndex* binary nodes.
56     switch (node->getOp())
57     {
58         case EOpIndexDirectInterfaceBlock:
59         case EOpIndexIndirect:
60         case EOpIndexDirect:
61         case EOpIndexDirectStruct:
62             break;
63         default:
64             return nullptr;
65     }
66 
67     const TStructure *structure = node->getLeft()->getType().getStruct();
68     if (structure == nullptr)
69     {
70         return nullptr;
71     }
72 
73     // If the result of the index is not a sampler and the struct is not replaced, there's nothing
74     // to do.
75     if (!node->getType().isSampler() && structureMap.find(structure) == structureMap.end())
76     {
77         return nullptr;
78     }
79 
80     // Otherwise, replace the whole expression such that:
81     //
82     // - if sampler, it's indexed with whatever indices the parent structs were indexed with,
83     // - otherwise, the chain of field selections is rewritten by modifying the base uniform so all
84     //   the intermediate nodes would have the correct type (and therefore fields).
85     ASSERT(structureMap.find(structure) != structureMap.end());
86 
87     return RewriteModifiedStructFieldSelectionExpression(compiler, node, structureMap,
88                                                          structureUniformMap, extractedSamplers);
89 }
90 
91 // Given an expression, this traverser calculates a new expression where sampler-in-structs are
92 // replaced with their extracted ones, and field indices are adjusted for the rest of the fields.
93 // In particular, this is run on the right node of EOpIndexIndirect binary nodes, so that the
94 // expression in the index gets a chance to go through this transformation.
95 class RewriteExpressionTraverser final : public TIntermTraverser
96 {
97   public:
RewriteExpressionTraverser(TCompiler * compiler,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)98     explicit RewriteExpressionTraverser(TCompiler *compiler,
99                                         const StructureMap &structureMap,
100                                         const StructureUniformMap &structureUniformMap,
101                                         const ExtractedSamplerMap &extractedSamplers)
102         : TIntermTraverser(true, false, false),
103           mCompiler(compiler),
104           mStructureMap(structureMap),
105           mStructureUniformMap(structureUniformMap),
106           mExtractedSamplers(extractedSamplers)
107     {}
108 
visitBinary(Visit visit,TIntermBinary * node)109     bool visitBinary(Visit visit, TIntermBinary *node) override
110     {
111         TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
112             mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
113 
114         if (rewritten == nullptr)
115         {
116             return true;
117         }
118 
119         queueReplacement(rewritten, OriginalNode::IS_DROPPED);
120 
121         // Don't iterate as the expression is rewritten.
122         return false;
123     }
124 
visitSymbol(TIntermSymbol * node)125     void visitSymbol(TIntermSymbol *node) override
126     {
127         // It's impossible to reach here with a symbol that needs replacement.
128         // MonomorphizeUnsupportedFunctions makes sure that whole structs containing
129         // samplers are not passed to functions, so any instance of the struct uniform is
130         // necessarily indexed right away.  visitBinary should have already taken care of it.
131         ASSERT(mStructureUniformMap.find(&node->variable()) == mStructureUniformMap.end());
132     }
133 
134   private:
135     TCompiler *mCompiler;
136 
137     // See RewriteStructSamplersTraverser.
138     const StructureMap &mStructureMap;
139     const StructureUniformMap &mStructureUniformMap;
140     const ExtractedSamplerMap &mExtractedSamplers;
141 };
142 
143 // Rewrite the index of an EOpIndexIndirect expression.  The root can never need replacing, because
144 // it cannot be a sampler itself or of a struct type.
RewriteIndexExpression(TCompiler * compiler,TIntermTyped * expression,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)145 void RewriteIndexExpression(TCompiler *compiler,
146                             TIntermTyped *expression,
147                             const StructureMap &structureMap,
148                             const StructureUniformMap &structureUniformMap,
149                             const ExtractedSamplerMap &extractedSamplers)
150 {
151     RewriteExpressionTraverser traverser(compiler, structureMap, structureUniformMap,
152                                          extractedSamplers);
153     expression->traverse(&traverser);
154     bool valid = traverser.updateTree(compiler, expression);
155     ASSERT(valid);
156 }
157 
158 // Given an expression such as the following:
159 //
160 //                                                    EOpIndexDirectStruct (sampler)
161 //                                                    /                  \
162 //                                               EOpIndex*           field index
163 //                                              /        \
164 //                                EOpIndexDirectStruct   index 2
165 //                                /                  \
166 //                           EOpIndex*           field index
167 //                          /        \
168 //            EOpIndexDirectStruct   index 1
169 //            /                  \
170 //     Uniform Struct           field index
171 //
172 // produces:
173 //
174 //                                EOpIndex*
175 //                                /      \
176 //                           EOpIndex*  index 2
177 //                          /        \
178 //                      sampler    index 1
179 //
180 // If the expression is not a sampler, it only replaces the struct with the modified one, while
181 // still processing the EOpIndexIndirect expressions (which may contain more structs to map).
RewriteModifiedStructFieldSelectionExpression(TCompiler * compiler,TIntermBinary * node,const StructureMap & structureMap,const StructureUniformMap & structureUniformMap,const ExtractedSamplerMap & extractedSamplers)182 TIntermTyped *RewriteModifiedStructFieldSelectionExpression(
183     TCompiler *compiler,
184     TIntermBinary *node,
185     const StructureMap &structureMap,
186     const StructureUniformMap &structureUniformMap,
187     const ExtractedSamplerMap &extractedSamplers)
188 {
189     const bool isSampler = node->getType().isSampler();
190 
191     TIntermSymbol *baseUniform = nullptr;
192     std::string samplerName;
193 
194     TVector<TIntermBinary *> indexNodeStack;
195 
196     // Iterate once and build the name of the sampler.
197     TIntermBinary *iter = node;
198     while (baseUniform == nullptr)
199     {
200         indexNodeStack.push_back(iter);
201         baseUniform = iter->getLeft()->getAsSymbolNode();
202 
203         if (isSampler)
204         {
205             if (iter->getOp() == EOpIndexDirectStruct)
206             {
207                 // When indexed into a struct, get the field name instead and construct the sampler
208                 // name.
209                 samplerName.insert(0, iter->getIndexStructFieldName().data());
210                 samplerName.insert(0, "_");
211             }
212 
213             if (baseUniform)
214             {
215                 // If left is a symbol, we have reached the end of the chain.  Use the struct name
216                 // to finish building the name of the sampler.
217                 samplerName.insert(0, baseUniform->variable().name().data());
218             }
219         }
220 
221         iter = iter->getLeft()->getAsBinaryNode();
222     }
223 
224     TIntermTyped *rewritten = nullptr;
225 
226     if (isSampler)
227     {
228         ASSERT(extractedSamplers.find(samplerName) != extractedSamplers.end());
229         rewritten = new TIntermSymbol(extractedSamplers.at(samplerName));
230     }
231     else
232     {
233         const TVariable *baseUniformVar = &baseUniform->variable();
234         ASSERT(structureUniformMap.find(baseUniformVar) != structureUniformMap.end());
235         rewritten = new TIntermSymbol(structureUniformMap.at(baseUniformVar));
236     }
237 
238     // Iterate again and build the expression from bottom up.
239     for (auto it = indexNodeStack.rbegin(); it != indexNodeStack.rend(); ++it)
240     {
241         TIntermBinary *indexNode = *it;
242 
243         switch (indexNode->getOp())
244         {
245             case EOpIndexDirectStruct:
246                 if (!isSampler)
247                 {
248                     rewritten =
249                         new TIntermBinary(EOpIndexDirectStruct, rewritten, indexNode->getRight());
250                 }
251                 break;
252 
253             case EOpIndexDirect:
254                 rewritten = new TIntermBinary(EOpIndexDirect, rewritten, indexNode->getRight());
255                 break;
256 
257             case EOpIndexIndirect:
258             {
259                 // Run RewriteExpressionTraverser on the right node.  It may itself be an expression
260                 // with a sampler inside that needs to be rewritten, or simply use a field of a
261                 // struct that's remapped.
262                 TIntermTyped *indexExpression = indexNode->getRight();
263                 RewriteIndexExpression(compiler, indexExpression, structureMap, structureUniformMap,
264                                        extractedSamplers);
265                 rewritten = new TIntermBinary(EOpIndexIndirect, rewritten, indexExpression);
266                 break;
267             }
268 
269             default:
270                 UNREACHABLE();
271                 break;
272         }
273     }
274 
275     return rewritten;
276 }
277 
278 class RewriteStructSamplersTraverser final : public TIntermTraverser
279 {
280   public:
RewriteStructSamplersTraverser(TCompiler * compiler,TSymbolTable * symbolTable)281     explicit RewriteStructSamplersTraverser(TCompiler *compiler, TSymbolTable *symbolTable)
282         : TIntermTraverser(true, false, false, symbolTable),
283           mCompiler(compiler),
284           mRemovedUniformsCount(0)
285     {}
286 
removedUniformsCount() const287     int removedUniformsCount() const { return mRemovedUniformsCount; }
288 
289     // Each struct sampler declaration is stripped of its samplers. New uniforms are added for each
290     // stripped struct sampler.
visitDeclaration(Visit visit,TIntermDeclaration * decl)291     bool visitDeclaration(Visit visit, TIntermDeclaration *decl) override
292     {
293         if (!mInGlobalScope)
294         {
295             return true;
296         }
297 
298         const TIntermSequence &sequence = *(decl->getSequence());
299         TIntermTyped *declarator        = sequence.front()->getAsTyped();
300         const TType &type               = declarator->getType();
301 
302         if (!type.isStructureContainingSamplers())
303         {
304             return false;
305         }
306 
307         TIntermSequence newSequence;
308 
309         if (type.isStructSpecifier())
310         {
311             // If this is just a struct definition (not a uniform variable declaration of a
312             // struct type), just remove the samplers.  They are not instantiated yet.
313             const TStructure *structure = type.getStruct();
314             ASSERT(structure && mStructureMap.find(structure) == mStructureMap.end());
315 
316             stripStructSpecifierSamplers(structure, &newSequence);
317         }
318         else
319         {
320             const TStructure *structure = type.getStruct();
321 
322             // If the structure is defined at the same time, create the mapping to the stripped
323             // version first.
324             if (mStructureMap.find(structure) == mStructureMap.end())
325             {
326                 stripStructSpecifierSamplers(structure, &newSequence);
327             }
328 
329             // Then, extract the samplers from the struct and create global-scope variables instead.
330             TIntermSymbol *asSymbol = declarator->getAsSymbolNode();
331             ASSERT(asSymbol);
332             const TVariable &variable = asSymbol->variable();
333             ASSERT(variable.symbolType() != SymbolType::Empty);
334 
335             extractStructSamplerUniforms(variable, structure, &newSequence);
336         }
337 
338         mMultiReplacements.emplace_back(getParentNode()->getAsBlock(), decl,
339                                         std::move(newSequence));
340 
341         return false;
342     }
343 
344     // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
visitBinary(Visit visit,TIntermBinary * node)345     bool visitBinary(Visit visit, TIntermBinary *node) override
346     {
347         TIntermTyped *rewritten = RewriteExpressionVisitBinaryHelper(
348             mCompiler, node, mStructureMap, mStructureUniformMap, mExtractedSamplers);
349 
350         if (rewritten == nullptr)
351         {
352             return true;
353         }
354 
355         queueReplacement(rewritten, OriginalNode::IS_DROPPED);
356 
357         // Don't iterate as the expression is rewritten.
358         return false;
359     }
360 
361     // Same implementation as in RewriteExpressionTraverser.  That traverser cannot replace root.
visitSymbol(TIntermSymbol * node)362     void visitSymbol(TIntermSymbol *node) override
363     {
364         auto replacement = mStructureUniformMap.find(&node->variable());
365         if (replacement != mStructureUniformMap.end())
366         {
367             // This is a reference to the whole struct, just replace it with its replacement.
368             queueReplacement(new TIntermSymbol(replacement->second), OriginalNode::IS_DROPPED);
369         }
370     }
371 
372   private:
isActiveUniform(const ImmutableString & rootStructureName)373     bool isActiveUniform(const ImmutableString &rootStructureName)
374     {
375         if (!mActiveUniforms)
376         {
377             mActiveUniforms = new TSet<ImmutableString>();
378             for (const ShaderVariable &uniform : mCompiler->getUniforms())
379             {
380                 if (uniform.active)
381                 {
382                     mActiveUniforms->insert(uniform.name);
383                 }
384             }
385         }
386 
387         return mActiveUniforms->count(rootStructureName) > 0;
388     }
389 
390     // Removes all samplers from a struct specifier.
stripStructSpecifierSamplers(const TStructure * structure,TIntermSequence * newSequence)391     void stripStructSpecifierSamplers(const TStructure *structure, TIntermSequence *newSequence)
392     {
393         TFieldList *newFieldList = new TFieldList;
394         ASSERT(structure->containsSamplers());
395 
396         // Add this struct to the struct map
397         ASSERT(mStructureMap.find(structure) == mStructureMap.end());
398         StructureData *modifiedData = &mStructureMap[structure];
399 
400         modifiedData->modified = nullptr;
401 
402         for (size_t fieldIndex = 0; fieldIndex < structure->fields().size(); ++fieldIndex)
403         {
404             const TField *field    = structure->fields()[fieldIndex];
405             const TType &fieldType = *field->type();
406 
407             // If the field is a sampler, or a struct that's entirely removed, skip it.
408             if (!fieldType.isSampler() && !isRemovedStructType(fieldType))
409             {
410                 TType *newType = nullptr;
411 
412                 // Otherwise, if it's a struct that's replaced, create a new field of the replaced
413                 // type.
414                 if (fieldType.isStructureContainingSamplers())
415                 {
416                     const TStructure *fieldStruct = fieldType.getStruct();
417                     ASSERT(mStructureMap.find(fieldStruct) != mStructureMap.end());
418 
419                     const TStructure *modifiedStruct = mStructureMap[fieldStruct].modified;
420                     ASSERT(modifiedStruct);
421 
422                     newType = new TType(modifiedStruct, true);
423                     if (fieldType.isArray())
424                     {
425                         newType->makeArrays(fieldType.getArraySizes());
426                     }
427                 }
428                 else
429                 {
430                     // If not, duplicate the field as is.
431                     newType = new TType(fieldType);
432                 }
433 
434                 TField *newField =
435                     new TField(newType, field->name(), field->line(), field->symbolType());
436                 newFieldList->push_back(newField);
437             }
438         }
439 
440         // Prune empty structs.
441         if (newFieldList->empty())
442         {
443             return;
444         }
445 
446         // Declare a new struct with the same name and the new fields.
447         modifiedData->modified =
448             new TStructure(mSymbolTable,
449                            structure->symbolType() == SymbolType::Empty ? kEmptyImmutableString
450                                                                         : structure->name(),
451                            newFieldList, structure->symbolType());
452         TType *newStructType = new TType(modifiedData->modified, true);
453         TVariable *newStructVar =
454             new TVariable(mSymbolTable, kEmptyImmutableString, newStructType, SymbolType::Empty);
455         TIntermSymbol *newStructRef = new TIntermSymbol(newStructVar);
456 
457         TIntermDeclaration *structDecl = new TIntermDeclaration;
458         structDecl->appendDeclarator(newStructRef);
459 
460         newSequence->push_back(structDecl);
461     }
462 
463     // Returns true if the type is a struct that was removed because we extracted all the members.
isRemovedStructType(const TType & type) const464     bool isRemovedStructType(const TType &type) const
465     {
466         const TStructure *structure = type.getStruct();
467         if (structure == nullptr)
468         {
469             // Not a struct
470             return false;
471         }
472 
473         // A struct is removed if it is in the map, but doesn't have a replacement struct.
474         auto iter = mStructureMap.find(structure);
475         return iter != mStructureMap.end() && iter->second.modified == nullptr;
476     }
477 
478     // Removes samplers from struct uniforms. For each sampler removed also adds a new globally
479     // defined sampler uniform.
extractStructSamplerUniforms(const TVariable & variable,const TStructure * structure,TIntermSequence * newSequence)480     void extractStructSamplerUniforms(const TVariable &variable,
481                                       const TStructure *structure,
482                                       TIntermSequence *newSequence)
483     {
484         ASSERT(structure->containsSamplers());
485         ASSERT(mStructureMap.find(structure) != mStructureMap.end());
486 
487         const TType &type = variable.getType();
488         enterArray(type);
489 
490         for (const TField *field : structure->fields())
491         {
492             extractFieldSamplers(isActiveUniform(variable.name()), variable.name().data(), field,
493                                  newSequence);
494         }
495 
496         // If there's a replacement structure (because there are non-sampler fields in the struct),
497         // add a declaration with that type.
498         const TStructure *modified = mStructureMap[structure].modified;
499         if (modified != nullptr)
500         {
501             TType *newType = new TType(modified, false);
502             if (type.isArray())
503             {
504                 newType->makeArrays(type.getArraySizes());
505             }
506             newType->setQualifier(EvqUniform);
507             const TVariable *newVariable =
508                 new TVariable(mSymbolTable, variable.name(), newType, variable.symbolType());
509 
510             TIntermDeclaration *newDecl = new TIntermDeclaration();
511             newDecl->appendDeclarator(new TIntermSymbol(newVariable));
512 
513             newSequence->push_back(newDecl);
514 
515             ASSERT(mStructureUniformMap.find(&variable) == mStructureUniformMap.end());
516             mStructureUniformMap[&variable] = newVariable;
517         }
518         else
519         {
520             mRemovedUniformsCount++;
521         }
522 
523         exitArray(type);
524     }
525 
526     // Extracts samplers from a field of a struct. Works with nested structs and arrays.
extractFieldSamplers(bool inActiveUniform,const std::string & prefix,const TField * field,TIntermSequence * newSequence)527     void extractFieldSamplers(bool inActiveUniform,
528                               const std::string &prefix,
529                               const TField *field,
530                               TIntermSequence *newSequence)
531     {
532         const TType &fieldType = *field->type();
533         if (fieldType.isSampler() || fieldType.isStructureContainingSamplers())
534         {
535             std::string newPrefix = prefix + "_" + field->name().data();
536 
537             if (fieldType.isSampler())
538             {
539                 if (inActiveUniform)
540                 {
541                     extractSampler(newPrefix, fieldType, newSequence);
542                 }
543             }
544             else
545             {
546                 enterArray(fieldType);
547                 const TStructure *structure = fieldType.getStruct();
548                 for (const TField *nestedField : structure->fields())
549                 {
550                     extractFieldSamplers(inActiveUniform, newPrefix, nestedField, newSequence);
551                 }
552                 exitArray(fieldType);
553             }
554         }
555     }
556 
GenerateArraySizesFromStack(TVector<unsigned int> * sizesOut)557     void GenerateArraySizesFromStack(TVector<unsigned int> *sizesOut)
558     {
559         sizesOut->reserve(mArraySizeStack.size());
560 
561         for (auto it = mArraySizeStack.rbegin(); it != mArraySizeStack.rend(); ++it)
562         {
563             sizesOut->push_back(*it);
564         }
565     }
566 
567     // Extracts a sampler from a struct. Declares the new extracted sampler.
extractSampler(const std::string & newName,const TType & fieldType,TIntermSequence * newSequence)568     void extractSampler(const std::string &newName,
569                         const TType &fieldType,
570                         TIntermSequence *newSequence)
571     {
572         ASSERT(fieldType.isSampler());
573 
574         TType *newType = new TType(fieldType);
575 
576         // Add array dimensions accumulated so far due to struct arrays.  Note that to support
577         // nested arrays, mArraySizeStack has the outermost size in the front.  |makeArrays| thus
578         // expects this in reverse order.
579         TVector<unsigned int> parentArraySizes;
580         GenerateArraySizesFromStack(&parentArraySizes);
581         newType->makeArrays(parentArraySizes);
582 
583         ImmutableStringBuilder nameBuilder(newName.size() + 1);
584         nameBuilder << newName;
585 
586         newType->setQualifier(EvqUniform);
587         TVariable *newVariable =
588             new TVariable(mSymbolTable, nameBuilder, newType, SymbolType::AngleInternal);
589         TIntermSymbol *newSymbol = new TIntermSymbol(newVariable);
590 
591         TIntermDeclaration *samplerDecl = new TIntermDeclaration;
592         samplerDecl->appendDeclarator(newSymbol);
593 
594         newSequence->push_back(samplerDecl);
595 
596         // TODO: Use a temp name instead of generating a name as currently done.  There is no
597         // guarantee that these generated names cannot clash.  Create a mapping from the previous
598         // name to the name assigned to the temp variable so ShaderVariable::mappedName can be
599         // updated post-transformation.  http://anglebug.com/42262930
600         ASSERT(mExtractedSamplers.find(newName) == mExtractedSamplers.end());
601         mExtractedSamplers[newName] = newVariable;
602     }
603 
enterArray(const TType & arrayType)604     void enterArray(const TType &arrayType)
605     {
606         const angle::Span<const unsigned int> &arraySizes = arrayType.getArraySizes();
607         for (auto it = arraySizes.rbegin(); it != arraySizes.rend(); ++it)
608         {
609             unsigned int arraySize = *it;
610             mArraySizeStack.push_back(arraySize);
611         }
612     }
613 
exitArray(const TType & arrayType)614     void exitArray(const TType &arrayType)
615     {
616         mArraySizeStack.resize(mArraySizeStack.size() - arrayType.getNumArraySizes());
617     }
618 
619     TCompiler *mCompiler;
620     int mRemovedUniformsCount;
621 
622     // Map structures with samplers to ones that have their samplers removed.
623     StructureMap mStructureMap;
624 
625     // Map uniform variables of structure type that are replaced with another variable.
626     StructureUniformMap mStructureUniformMap;
627 
628     // Map a constructed sampler name to its variable.  Used to replace an expression that uses this
629     // sampler with the extracted one.
630     ExtractedSamplerMap mExtractedSamplers;
631 
632     // A stack of array sizes.  Used to figure out the array dimensions of the extracted sampler,
633     // for example when it's nested in an array of structs in an array of structs.
634     TVector<unsigned int> mArraySizeStack;
635 
636     // Caches the names of all inactive uniforms.
637     TSet<ImmutableString> *mActiveUniforms = nullptr;
638 };
639 }  // anonymous namespace
640 
RewriteStructSamplers(TCompiler * compiler,TIntermBlock * root,TSymbolTable * symbolTable,int * removedUniformsCountOut)641 bool RewriteStructSamplers(TCompiler *compiler,
642                            TIntermBlock *root,
643                            TSymbolTable *symbolTable,
644                            int *removedUniformsCountOut)
645 {
646     RewriteStructSamplersTraverser traverser(compiler, symbolTable);
647     root->traverse(&traverser);
648     *removedUniformsCountOut = traverser.removedUniformsCount();
649     return traverser.updateTree(compiler, root);
650 }
651 }  // namespace sh
652