• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright 2023 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 // EmulateFramebufferFetch.h: Replace input, gl_LastFragData and gl_LastFragColorARM with usages of
7 // input attachments.
8 //
9 
10 #include "compiler/translator/tree_ops/spirv/EmulateFramebufferFetch.h"
11 
12 #include "common/bitset_utils.h"
13 #include "compiler/translator/Compiler.h"
14 #include "compiler/translator/ImmutableStringBuilder.h"
15 #include "compiler/translator/SymbolTable.h"
16 #include "compiler/translator/tree_util/BuiltIn.h"
17 #include "compiler/translator/tree_util/IntermNode_util.h"
18 #include "compiler/translator/tree_util/IntermTraverse.h"
19 #include "compiler/translator/tree_util/ReplaceVariable.h"
20 #include "compiler/translator/tree_util/RunAtTheBeginningOfShader.h"
21 #include "compiler/translator/util.h"
22 
23 namespace sh
24 {
25 namespace
26 {
27 using InputAttachmentIndexUsage = angle::BitSet<32>;
28 
29 // A traverser that looks at which inout variables exist, which gl_LastFragData indices have been
30 // used and whether gl_LastFragColorARM is referenced.  It builds a set of indices correspondingly;
31 // these are input attachment indices the shader may read from.
32 class InputAttachmentUsageTraverser : public TIntermTraverser
33 {
34   public:
InputAttachmentUsageTraverser(uint32_t maxDrawBuffers,TVector<const TType * > * attachmentTypes)35     InputAttachmentUsageTraverser(uint32_t maxDrawBuffers, TVector<const TType *> *attachmentTypes)
36         : TIntermTraverser(true, false, false),
37           mMaxDrawBuffers(maxDrawBuffers),
38           mAttachmentTypes(attachmentTypes),
39           mUsesLastFragColorARM(false)
40     {
41         mAttachmentTypes->resize(maxDrawBuffers, nullptr);
42     }
43 
44     bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
45     bool visitBinary(Visit visit, TIntermBinary *node) override;
46     void visitSymbol(TIntermSymbol *node) override;
47 
getIndexUsage() const48     InputAttachmentIndexUsage getIndexUsage() const { return mIndexUsage; }
usesLastFragColorARM() const49     bool usesLastFragColorARM() const { return mUsesLastFragColorARM; }
50 
51   private:
52     void setInputAttachmentIndex(uint32_t index, const TType *type);
53 
54     uint32_t mMaxDrawBuffers;
55     InputAttachmentIndexUsage mIndexUsage;
56     TVector<const TType *> *mAttachmentTypes;
57     bool mUsesLastFragColorARM;
58 };
59 
setInputAttachmentIndex(uint32_t index,const TType * type)60 void InputAttachmentUsageTraverser::setInputAttachmentIndex(uint32_t index, const TType *type)
61 {
62     ASSERT(index < mMaxDrawBuffers);
63     mIndexUsage.set(index);
64     (*mAttachmentTypes)[index] = type;
65 }
66 
visitDeclaration(Visit visit,TIntermDeclaration * node)67 bool InputAttachmentUsageTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
68 {
69     const TIntermSequence &sequence = *node->getSequence();
70     ASSERT(sequence.size() == 1);
71 
72     TIntermSymbol *symbol = sequence.front()->getAsSymbolNode();
73     if (symbol == nullptr)
74     {
75         return true;
76     }
77 
78     if (symbol->getQualifier() == EvqFragmentInOut)
79     {
80         ASSERT(symbol->getType().getLayoutQualifier().index <= 0);
81 
82         // The input attachment index is identical to the location qualifier.  If there's only one
83         // output, GLSL is allowed to not specify the location qualifier, in which case it would
84         // implicitly be at location 0.
85         const TType &type = symbol->getType();
86         const unsigned int baseInputAttachmentIndex =
87             std::max(0, type.getLayoutQualifier().location);
88 
89         uint32_t arraySize = type.isArray() ? type.getOutermostArraySize() : 1;
90         for (unsigned int index = 0; index < arraySize; index++)
91         {
92             setInputAttachmentIndex(baseInputAttachmentIndex + index, &type);
93         }
94     }
95 
96     return false;
97 }
98 
visitBinary(Visit visit,TIntermBinary * node)99 bool InputAttachmentUsageTraverser::visitBinary(Visit visit, TIntermBinary *node)
100 {
101     TOperator op = node->getOp();
102     if (op != EOpIndexDirect && op != EOpIndexIndirect)
103     {
104         return true;
105     }
106 
107     TIntermSymbol *left = node->getLeft()->getAsSymbolNode();
108     if (left == nullptr || left->getQualifier() != EvqLastFragData)
109     {
110         return true;
111     }
112 
113     ASSERT(left->getName() == "gl_LastFragData");
114     const TType &type = left->getType();
115 
116     const TConstantUnion *constIndex = node->getRight()->getConstantValue();
117     // Non-const indices on gl_LastFragData are not allowed.
118     ASSERT(constIndex != nullptr);
119 
120     uint32_t index = 0;
121     switch (constIndex->getType())
122     {
123         case EbtInt:
124             index = constIndex->getIConst();
125             break;
126         case EbtUInt:
127             index = constIndex->getUConst();
128             break;
129         case EbtFloat:
130             index = static_cast<uint32_t>(constIndex->getFConst());
131             break;
132         case EbtBool:
133             index = constIndex->getBConst() ? 1 : 0;
134             break;
135         default:
136             UNREACHABLE();
137             break;
138     }
139     setInputAttachmentIndex(index, &type);
140 
141     return true;
142 }
143 
visitSymbol(TIntermSymbol * symbol)144 void InputAttachmentUsageTraverser::visitSymbol(TIntermSymbol *symbol)
145 {
146     if (symbol->getQualifier() != EvqLastFragColor)
147     {
148         return;
149     }
150     ASSERT(symbol->getName() == "gl_LastFragColorARM");
151 
152     // gl_LastFragColorARM always reads back from location 0.
153     setInputAttachmentIndex(0, &symbol->getType());
154     mUsesLastFragColorARM = true;
155 }
156 
GetInputAttachmentName(size_t index)157 ImmutableString GetInputAttachmentName(size_t index)
158 {
159     std::stringstream nameStream = sh::InitializeStream<std::stringstream>();
160     nameStream << "ANGLEInputAttachment" << index;
161     return ImmutableString(nameStream.str());
162 }
163 
GetBasicTypeForSubpassInput(TBasicType inputType)164 TBasicType GetBasicTypeForSubpassInput(TBasicType inputType)
165 {
166     switch (inputType)
167     {
168         case EbtFloat:
169             return EbtSubpassInput;
170         case EbtInt:
171             return EbtISubpassInput;
172         case EbtUInt:
173             return EbtUSubpassInput;
174         default:
175             UNREACHABLE();
176             return EbtVoid;
177     }
178 }
179 
180 // Declare an input attachment variable at a given index.
DeclareInputAttachmentVariable(TSymbolTable * symbolTable,const TType & outputType,size_t index,InputAttachmentMap * inputAttachmentMapOut,TIntermSequence * declarationsOut)181 void DeclareInputAttachmentVariable(TSymbolTable *symbolTable,
182                                     const TType &outputType,
183                                     size_t index,
184                                     InputAttachmentMap *inputAttachmentMapOut,
185                                     TIntermSequence *declarationsOut)
186 {
187     const TBasicType subpassInputType = GetBasicTypeForSubpassInput(outputType.getBasicType());
188 
189     TType *inputAttachmentType =
190         new TType(subpassInputType, outputType.getPrecision(), EvqUniform, 1);
191     TLayoutQualifier inputAttachmentQualifier     = inputAttachmentType->getLayoutQualifier();
192     inputAttachmentQualifier.inputAttachmentIndex = static_cast<int>(index);
193     inputAttachmentType->setLayoutQualifier(inputAttachmentQualifier);
194 
195     const TVariable *inputAttachmentVar = new TVariable(
196         symbolTable, GetInputAttachmentName(index), inputAttachmentType, SymbolType::AngleInternal);
197     (*inputAttachmentMapOut)[static_cast<uint32_t>(index)] = inputAttachmentVar;
198 
199     TIntermDeclaration *decl = new TIntermDeclaration;
200     decl->appendDeclarator(new TIntermSymbol(inputAttachmentVar));
201     declarationsOut->push_back(decl);
202 }
203 
204 // Declare a global variable to hold gl_LastFragData/gl_LastFragColorARM
DeclareLastFragDataGlobalVariable(TCompiler * compiler,TIntermBlock * root,const TVector<const TType * > & attachmentTypes,TIntermSequence * declarationsOut)205 const TVariable *DeclareLastFragDataGlobalVariable(TCompiler *compiler,
206                                                    TIntermBlock *root,
207                                                    const TVector<const TType *> &attachmentTypes,
208                                                    TIntermSequence *declarationsOut)
209 {
210     // Find the first input attachment that is used.  If gl_LastFragColorARM was used, this will be
211     // index 0.  Otherwise if this is ES100, any index of gl_LastFragData may be used.  Either way,
212     // the global variable is declared the same as gl_LastFragData would have been if used.
213     const TType *attachmentType = nullptr;
214     for (const TType *type : attachmentTypes)
215     {
216         if (type != nullptr)
217         {
218             attachmentType = type;
219             break;
220         }
221     }
222     ASSERT(attachmentType != nullptr);
223     TType *globalType = new TType(*attachmentType);
224     globalType->setQualifier(EvqGlobal);
225 
226     // If the type of gl_LastFragColorARM is found, convert it to an array to match gl_LastFragData.
227     // This is necessary if the shader uses both gl_LastFragData and gl_LastFragColorARM
228     // simultaneously.
229     if (!globalType->isArray())
230     {
231         globalType->makeArray(compiler->getBuiltInResources().MaxDrawBuffers);
232     }
233 
234     // Declare the global
235     const TVariable *global =
236         new TVariable(&compiler->getSymbolTable(), ImmutableString("ANGLELastFragData"), globalType,
237                       SymbolType::AngleInternal);
238 
239     TIntermDeclaration *decl = new TIntermDeclaration;
240     decl->appendDeclarator(new TIntermSymbol(global));
241     declarationsOut->push_back(decl);
242 
243     return global;
244 }
245 
246 // Declare an input attachment for each used index.  Additionally, create a global variable for
247 // gl_LastFragData and gl_LastFragColorARM if needed.
DeclareVariables(TCompiler * compiler,TIntermBlock * root,InputAttachmentIndexUsage indexUsage,bool usesLastFragData,const TVector<const TType * > & attachmentTypes,InputAttachmentMap * inputAttachmentMapOut,const TVariable ** lastFragDataOut)248 [[nodiscard]] bool DeclareVariables(TCompiler *compiler,
249                                     TIntermBlock *root,
250                                     InputAttachmentIndexUsage indexUsage,
251                                     bool usesLastFragData,
252                                     const TVector<const TType *> &attachmentTypes,
253                                     InputAttachmentMap *inputAttachmentMapOut,
254                                     const TVariable **lastFragDataOut)
255 {
256     TSymbolTable *symbolTable = &compiler->getSymbolTable();
257 
258     TIntermSequence declarations;
259 
260     // For every detected index, declare an input attachment variable.
261     for (size_t index : indexUsage)
262     {
263         ASSERT(attachmentTypes[index] != nullptr);
264         DeclareInputAttachmentVariable(symbolTable, *attachmentTypes[index], index,
265                                        inputAttachmentMapOut, &declarations);
266     }
267 
268     // If gl_LastFragData or gl_LastFragColorARM is used, declare a global variable to retain that.
269     // The difference between ES300+ inout variables and gl_LastFrag* is that if the inout variable
270     // is read back after being written to, it should contain the latest value written to it, while
271     // gl_LastFrag* should contain the value before the fragment shader's invocation.
272     //
273     // As such, it is enough to initialize inout variables with the values from input attachments,
274     // but gl_LastFrag* needs to be stored in a global variable to retain its value even after
275     // gl_Frag* has been overwritten.
276     *lastFragDataOut = nullptr;
277     if (usesLastFragData)
278     {
279         *lastFragDataOut =
280             DeclareLastFragDataGlobalVariable(compiler, root, attachmentTypes, &declarations);
281     }
282 
283     // Add the declarations to the beginning of the shader.
284     TIntermSequence &topLevel = *root->getSequence();
285     declarations.insert(declarations.end(), topLevel.begin(), topLevel.end());
286     topLevel = std::move(declarations);
287 
288     return compiler->validateAST(root);
289 }
290 
CreateSubpassLoadFuncCall(TSymbolTable * symbolTable,const TVariable * inputVariable)291 TIntermTyped *CreateSubpassLoadFuncCall(TSymbolTable *symbolTable, const TVariable *inputVariable)
292 {
293     TIntermSequence args = {new TIntermSymbol(inputVariable)};
294     return CreateBuiltInFunctionCallNode("subpassLoad", &args, *symbolTable,
295                                          kESSLInternalBackendBuiltIns);
296 }
297 
GatherInoutVariables(TIntermBlock * root,TVector<const TVariable * > * inoutVariablesOut)298 void GatherInoutVariables(TIntermBlock *root, TVector<const TVariable *> *inoutVariablesOut)
299 {
300     TIntermSequence &topLevel = *root->getSequence();
301 
302     for (TIntermNode *node : topLevel)
303     {
304         TIntermDeclaration *decl = node->getAsDeclarationNode();
305         if (decl != nullptr)
306         {
307             ASSERT(decl->getSequence()->size() == 1);
308 
309             TIntermSymbol *symbol = decl->getSequence()->front()->getAsSymbolNode();
310             if (symbol != nullptr && symbol->getQualifier() == EvqFragmentInOut)
311             {
312                 ASSERT(symbol->getType().getLayoutQualifier().index <= 0);
313                 inoutVariablesOut->push_back(&symbol->variable());
314             }
315         }
316     }
317 }
318 
InitializeFromInputAttachment(TSymbolTable * symbolTable,TIntermBlock * block,const TVariable * inputVariable,const TVariable * assignVariable,uint32_t assignVariableArrayIndex)319 void InitializeFromInputAttachment(TSymbolTable *symbolTable,
320                                    TIntermBlock *block,
321                                    const TVariable *inputVariable,
322                                    const TVariable *assignVariable,
323                                    uint32_t assignVariableArrayIndex)
324 {
325     ASSERT(inputVariable != nullptr);
326 
327     TIntermTyped *var = new TIntermSymbol(assignVariable);
328     if (var->getType().isArray())
329     {
330         var = new TIntermBinary(EOpIndexDirect, var, CreateIndexNode(assignVariableArrayIndex));
331     }
332 
333     TIntermTyped *input = CreateSubpassLoadFuncCall(symbolTable, inputVariable);
334 
335     const int vecSize = assignVariable->getType().getNominalSize();
336     if (vecSize < 4)
337     {
338         TVector<int> swizzle = {0, 1, 2, 3};
339         swizzle.resize(vecSize);
340         input = new TIntermSwizzle(input, swizzle);
341     }
342 
343     TIntermTyped *assignment = new TIntermBinary(EOpAssign, var, input);
344 
345     block->appendStatement(assignment);
346 }
347 
InitializeFromInputAttachments(TCompiler * compiler,TIntermBlock * root,const InputAttachmentMap & inputAttachmentMap,const TVector<const TVariable * > & inoutVariables,const TVariable * lastFragData)348 [[nodiscard]] bool InitializeFromInputAttachments(TCompiler *compiler,
349                                                   TIntermBlock *root,
350                                                   const InputAttachmentMap &inputAttachmentMap,
351                                                   const TVector<const TVariable *> &inoutVariables,
352                                                   const TVariable *lastFragData)
353 {
354     TSymbolTable *symbolTable = &compiler->getSymbolTable();
355     TIntermBlock *init        = new TIntermBlock;
356 
357     // Initialize inout variables
358     for (const TVariable *inoutVar : inoutVariables)
359     {
360         const TType &type = inoutVar->getType();
361         const unsigned int baseInputAttachmentIndex =
362             std::max(0, type.getLayoutQualifier().location);
363 
364         uint32_t arraySize = type.isArray() ? type.getOutermostArraySize() : 1;
365         for (unsigned int index = 0; index < arraySize; index++)
366         {
367             ASSERT(inputAttachmentMap.find(baseInputAttachmentIndex + index) !=
368                    inputAttachmentMap.end());
369 
370             InitializeFromInputAttachment(symbolTable, init,
371                                           inputAttachmentMap.at(baseInputAttachmentIndex + index),
372                                           inoutVar, index);
373         }
374     }
375 
376     // Initialize lastFragData, if present
377     if (lastFragData != nullptr)
378     {
379         for (auto &iter : inputAttachmentMap)
380         {
381             const uint32_t index                = iter.first;
382             const TVariable *inputAttachmentVar = iter.second;
383 
384             InitializeFromInputAttachment(symbolTable, init, inputAttachmentVar, lastFragData,
385                                           index);
386         }
387     }
388 
389     return RunAtTheBeginningOfShader(compiler, root, init);
390 }
391 
ReplaceVariables(TCompiler * compiler,TIntermBlock * root,const InputAttachmentMap & inputAttachmentMap,const TVariable * lastFragData)392 [[nodiscard]] bool ReplaceVariables(TCompiler *compiler,
393                                     TIntermBlock *root,
394                                     const InputAttachmentMap &inputAttachmentMap,
395                                     const TVariable *lastFragData)
396 {
397     TSymbolTable *symbolTable = &compiler->getSymbolTable();
398 
399     TVector<const TVariable *> inoutVariables;
400     GatherInoutVariables(root, &inoutVariables);
401 
402     // Generate code that initializes the global variable and the inout variables with corresponding
403     // input attachments.
404     if (!InitializeFromInputAttachments(compiler, root, inputAttachmentMap, inoutVariables,
405                                         lastFragData))
406     {
407         return false;
408     }
409 
410     // Build a map from:
411     //
412     // - inout to out variables
413     // - gl_LastFragData to lastFragData
414     // - gl_LastFragColorARM to lastFragData[0]
415 
416     VariableReplacementMap replacementMap;
417     for (const TVariable *var : inoutVariables)
418     {
419         TType *outType = new TType(var->getType());
420         outType->setQualifier(EvqFragmentOut);
421         const TVariable *replacement =
422             new TVariable(symbolTable, var->name(), outType, var->symbolType());
423         replacementMap[var] = new TIntermSymbol(replacement);
424     }
425 
426     if (lastFragData != nullptr)
427     {
428         // Use the user-defined variables if found (and remove their declaration), or the built-in
429         // otherwise.
430         TIntermSequence &topLevel = *root->getSequence();
431         TIntermSequence newTopLevel;
432 
433         const TVariable *glLastFragData  = nullptr;
434         const TVariable *glLastFragColor = nullptr;
435 
436         for (TIntermNode *node : topLevel)
437         {
438             TIntermDeclaration *decl = node->getAsDeclarationNode();
439             if (decl != nullptr)
440             {
441                 ASSERT(decl->getSequence()->size() == 1);
442 
443                 TIntermSymbol *symbol = decl->getSequence()->front()->getAsSymbolNode();
444                 if (symbol != nullptr)
445                 {
446                     if (symbol->getQualifier() == EvqLastFragData)
447                     {
448                         glLastFragData = &symbol->variable();
449                         continue;
450                     }
451                     if (symbol->getQualifier() == EvqLastFragColor)
452                     {
453                         glLastFragColor = &symbol->variable();
454                         continue;
455                     }
456                 }
457             }
458             newTopLevel.push_back(node);
459         }
460 
461         topLevel = std::move(newTopLevel);
462 
463         if (glLastFragData == nullptr)
464         {
465             glLastFragData = static_cast<const TVariable *>(
466                 symbolTable->findBuiltIn(ImmutableString("gl_LastFragData"), 100));
467         }
468         if (glLastFragColor == nullptr)
469         {
470             glLastFragColor = static_cast<const TVariable *>(symbolTable->findBuiltIn(
471                 ImmutableString("gl_LastFragColorARM"), compiler->getShaderVersion()));
472         }
473 
474         replacementMap[glLastFragData] = new TIntermSymbol(lastFragData);
475         replacementMap[glLastFragColor] =
476             new TIntermBinary(EOpIndexDirect, new TIntermSymbol(lastFragData), CreateIndexNode(0));
477     }
478 
479     // Replace the variables accordingly.
480     return ReplaceVariables(compiler, root, replacementMap);
481 }
482 }  // anonymous namespace
483 
EmulateFramebufferFetch(TCompiler * compiler,TIntermBlock * root,InputAttachmentMap * inputAttachmentMapOut)484 [[nodiscard]] bool EmulateFramebufferFetch(TCompiler *compiler,
485                                            TIntermBlock *root,
486                                            InputAttachmentMap *inputAttachmentMapOut)
487 {
488     // First, check if input attachments are necessary at all.
489     TVector<const TType *> attachmentTypes;
490     InputAttachmentUsageTraverser usageTraverser(compiler->getBuiltInResources().MaxDrawBuffers,
491                                                  &attachmentTypes);
492     root->traverse(&usageTraverser);
493 
494     InputAttachmentIndexUsage indexUsage = usageTraverser.getIndexUsage();
495     if (!indexUsage.any())
496     {
497         return true;
498     }
499 
500     const bool usesLastFragData =
501         compiler->getShaderVersion() == 100 || usageTraverser.usesLastFragColorARM();
502 
503     // Declare the necessary variables for emulation; input attachments to read from and global
504     // variables to hold last frag data.
505     const TVariable *lastFragData = nullptr;
506     if (!DeclareVariables(compiler, root, indexUsage, usesLastFragData, attachmentTypes,
507                           inputAttachmentMapOut, &lastFragData))
508     {
509         return false;
510     }
511 
512     // Then replace references to gl_LastFragData with the global, gl_LastFragColorARM with
513     // global[0], replace inout variables with out equivalents and make sure input attachments
514     // initialize the appropriate variables at the beginning of the shader.
515     if (!ReplaceVariables(compiler, root, *inputAttachmentMapOut, lastFragData))
516     {
517         return false;
518     }
519 
520     return true;
521 }
522 
523 }  // namespace sh
524