• 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,TVector<const TVariable * > * inputAttachmentVarsOut,TIntermSequence * declarationsOut)181 void DeclareInputAttachmentVariable(TSymbolTable *symbolTable,
182                                     const TType &outputType,
183                                     size_t index,
184                                     TVector<const TVariable *> *inputAttachmentVarsOut,
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     (*inputAttachmentVarsOut)[index] = new TVariable(
196         symbolTable, GetInputAttachmentName(index), inputAttachmentType, SymbolType::AngleInternal);
197 
198     TIntermDeclaration *decl = new TIntermDeclaration;
199     decl->appendDeclarator(new TIntermSymbol((*inputAttachmentVarsOut)[index]));
200     declarationsOut->push_back(decl);
201 }
202 
203 // Declare a global variable to hold gl_LastFragData/gl_LastFragColorARM
DeclareLastFragDataGlobalVariable(TCompiler * compiler,TIntermBlock * root,const TVector<const TType * > & attachmentTypes,TIntermSequence * declarationsOut)204 const TVariable *DeclareLastFragDataGlobalVariable(TCompiler *compiler,
205                                                    TIntermBlock *root,
206                                                    const TVector<const TType *> &attachmentTypes,
207                                                    TIntermSequence *declarationsOut)
208 {
209     // Find the first input attachment that is used.  If gl_LastFragColorARM was used, this will be
210     // index 0.  Otherwise if this is ES100, any index of gl_LastFragData may be used.  Either way,
211     // the global variable is declared the same as gl_LastFragData would have been if used.
212     const TType *attachmentType = nullptr;
213     for (const TType *type : attachmentTypes)
214     {
215         if (type != nullptr)
216         {
217             attachmentType = type;
218             break;
219         }
220     }
221     ASSERT(attachmentType != nullptr);
222     TType *globalType = new TType(*attachmentType);
223     globalType->setQualifier(EvqGlobal);
224 
225     // If the type of gl_LastFragColorARM is found, convert it to an array to match gl_LastFragData.
226     // This is necessary if the shader uses both gl_LastFragData and gl_LastFragColorARM
227     // simultaneously.
228     if (!globalType->isArray())
229     {
230         globalType->makeArray(compiler->getBuiltInResources().MaxDrawBuffers);
231     }
232 
233     // Declare the global
234     const TVariable *global =
235         new TVariable(&compiler->getSymbolTable(), ImmutableString("ANGLELastFragData"), globalType,
236                       SymbolType::AngleInternal);
237 
238     TIntermDeclaration *decl = new TIntermDeclaration;
239     decl->appendDeclarator(new TIntermSymbol(global));
240     declarationsOut->push_back(decl);
241 
242     return global;
243 }
244 
245 // Declare an input attachment for each used index.  Additionally, create a global variable for
246 // gl_LastFragData and gl_LastFragColorARM if needed.
DeclareVariables(TCompiler * compiler,TIntermBlock * root,InputAttachmentIndexUsage indexUsage,bool usesLastFragData,const TVector<const TType * > & attachmentTypes,TVector<const TVariable * > * inputAttachmentVarsOut,const TVariable ** lastFragDataOut)247 [[nodiscard]] bool DeclareVariables(TCompiler *compiler,
248                                     TIntermBlock *root,
249                                     InputAttachmentIndexUsage indexUsage,
250                                     bool usesLastFragData,
251                                     const TVector<const TType *> &attachmentTypes,
252                                     TVector<const TVariable *> *inputAttachmentVarsOut,
253                                     const TVariable **lastFragDataOut)
254 {
255     TSymbolTable *symbolTable = &compiler->getSymbolTable();
256 
257     TIntermSequence declarations;
258     inputAttachmentVarsOut->resize(indexUsage.last() + 1, nullptr);
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                                        inputAttachmentVarsOut, &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 
InitializeFromInputAttachmentIfPresent(TSymbolTable * symbolTable,TIntermBlock * block,const TVariable * inputVariable,const TVariable * assignVariable,uint32_t assignVariableArrayIndex)319 void InitializeFromInputAttachmentIfPresent(TSymbolTable *symbolTable,
320                                             TIntermBlock *block,
321                                             const TVariable *inputVariable,
322                                             const TVariable *assignVariable,
323                                             uint32_t assignVariableArrayIndex)
324 {
325     if (inputVariable == nullptr)
326     {
327         // No input variable usage for this index
328         return;
329     }
330 
331     TIntermTyped *var = new TIntermSymbol(assignVariable);
332     if (var->getType().isArray())
333     {
334         var = new TIntermBinary(EOpIndexDirect, var, CreateIndexNode(assignVariableArrayIndex));
335     }
336 
337     TIntermTyped *input = CreateSubpassLoadFuncCall(symbolTable, inputVariable);
338 
339     const int vecSize = assignVariable->getType().getNominalSize();
340     if (vecSize < 4)
341     {
342         TVector<int> swizzle = {0, 1, 2, 3};
343         swizzle.resize(vecSize);
344         input = new TIntermSwizzle(input, swizzle);
345     }
346 
347     TIntermTyped *assignment = new TIntermBinary(EOpAssign, var, input);
348 
349     block->appendStatement(assignment);
350 }
351 
InitializeFromInputAttachments(TCompiler * compiler,TIntermBlock * root,const TVector<const TVariable * > & inputAttachmentVars,const TVector<const TVariable * > & inoutVariables,const TVariable * lastFragData)352 [[nodiscard]] bool InitializeFromInputAttachments(
353     TCompiler *compiler,
354     TIntermBlock *root,
355     const TVector<const TVariable *> &inputAttachmentVars,
356     const TVector<const TVariable *> &inoutVariables,
357     const TVariable *lastFragData)
358 {
359     TSymbolTable *symbolTable = &compiler->getSymbolTable();
360     TIntermBlock *init        = new TIntermBlock;
361 
362     // Initialize inout variables
363     for (const TVariable *inoutVar : inoutVariables)
364     {
365         const TType &type = inoutVar->getType();
366         const unsigned int baseInputAttachmentIndex =
367             std::max(0, type.getLayoutQualifier().location);
368 
369         uint32_t arraySize = type.isArray() ? type.getOutermostArraySize() : 1;
370         for (unsigned int index = 0; index < arraySize; index++)
371         {
372             InitializeFromInputAttachmentIfPresent(
373                 symbolTable, init, inputAttachmentVars[baseInputAttachmentIndex + index], inoutVar,
374                 index);
375         }
376     }
377 
378     // Initialize lastFragData, if present
379     if (lastFragData != nullptr)
380     {
381         for (uint32_t index = 0; index < inputAttachmentVars.size(); ++index)
382         {
383             InitializeFromInputAttachmentIfPresent(symbolTable, init, inputAttachmentVars[index],
384                                                    lastFragData, index);
385         }
386     }
387 
388     return RunAtTheBeginningOfShader(compiler, root, init);
389 }
390 
ReplaceVariables(TCompiler * compiler,TIntermBlock * root,const TVector<const TVariable * > & inputAttachmentVars,const TVariable * lastFragData)391 [[nodiscard]] bool ReplaceVariables(TCompiler *compiler,
392                                     TIntermBlock *root,
393                                     const TVector<const TVariable *> &inputAttachmentVars,
394                                     const TVariable *lastFragData)
395 {
396     TSymbolTable *symbolTable = &compiler->getSymbolTable();
397 
398     TVector<const TVariable *> inoutVariables;
399     GatherInoutVariables(root, &inoutVariables);
400 
401     // Generate code that initializes the global variable and the inout variables with corresponding
402     // input attachments.
403     if (!InitializeFromInputAttachments(compiler, root, inputAttachmentVars, inoutVariables,
404                                         lastFragData))
405     {
406         return false;
407     }
408 
409     // Build a map from:
410     //
411     // - inout to out variables
412     // - gl_LastFragData to lastFragData
413     // - gl_LastFragColorARM to lastFragData[0]
414 
415     VariableReplacementMap replacementMap;
416     for (const TVariable *var : inoutVariables)
417     {
418         TType *outType = new TType(var->getType());
419         outType->setQualifier(EvqFragmentOut);
420         const TVariable *replacement =
421             new TVariable(symbolTable, var->name(), outType, var->symbolType());
422         replacementMap[var] = new TIntermSymbol(replacement);
423     }
424 
425     if (lastFragData != nullptr)
426     {
427         // Use the user-defined variables if found (and remove their declaration), or the built-in
428         // otherwise.
429         TIntermSequence &topLevel = *root->getSequence();
430         TIntermSequence newTopLevel;
431 
432         const TVariable *glLastFragData  = nullptr;
433         const TVariable *glLastFragColor = nullptr;
434 
435         for (TIntermNode *node : topLevel)
436         {
437             TIntermDeclaration *decl = node->getAsDeclarationNode();
438             if (decl != nullptr)
439             {
440                 ASSERT(decl->getSequence()->size() == 1);
441 
442                 TIntermSymbol *symbol = decl->getSequence()->front()->getAsSymbolNode();
443                 if (symbol != nullptr)
444                 {
445                     if (symbol->getQualifier() == EvqLastFragData)
446                     {
447                         glLastFragData = &symbol->variable();
448                         continue;
449                     }
450                     if (symbol->getQualifier() == EvqLastFragColor)
451                     {
452                         glLastFragColor = &symbol->variable();
453                         continue;
454                     }
455                 }
456             }
457             newTopLevel.push_back(node);
458         }
459 
460         topLevel = std::move(newTopLevel);
461 
462         if (glLastFragData == nullptr)
463         {
464             glLastFragData = static_cast<const TVariable *>(
465                 symbolTable->findBuiltIn(ImmutableString("gl_LastFragData"), 100));
466         }
467         if (glLastFragColor == nullptr)
468         {
469             glLastFragColor = static_cast<const TVariable *>(symbolTable->findBuiltIn(
470                 ImmutableString("gl_LastFragColorARM"), compiler->getShaderVersion()));
471         }
472 
473         replacementMap[glLastFragData] = new TIntermSymbol(lastFragData);
474         replacementMap[glLastFragColor] =
475             new TIntermBinary(EOpIndexDirect, new TIntermSymbol(lastFragData), CreateIndexNode(0));
476     }
477 
478     // Replace the variables accordingly.
479     return ReplaceVariables(compiler, root, replacementMap);
480 }
481 
AddInputAttachmentsToUniforms(const TVector<const TVariable * > & inputAttachmentVars,std::vector<ShaderVariable> * uniforms)482 void AddInputAttachmentsToUniforms(const TVector<const TVariable *> &inputAttachmentVars,
483                                    std::vector<ShaderVariable> *uniforms)
484 {
485     for (const TVariable *inputVar : inputAttachmentVars)
486     {
487         if (inputVar == nullptr)
488         {
489             continue;
490         }
491 
492         ShaderVariable uniform;
493         uniform.active    = true;
494         uniform.staticUse = true;
495         uniform.name.assign(inputVar->name().data(), inputVar->name().length());
496         uniform.mappedName.assign(uniform.name);
497         uniform.isFragmentInOut = true;
498         uniform.location        = inputVar->getType().getLayoutQualifier().inputAttachmentIndex;
499 
500         uniforms->push_back(uniform);
501     }
502 }
503 }  // anonymous namespace
504 
EmulateFramebufferFetch(TCompiler * compiler,TIntermBlock * root,std::vector<ShaderVariable> * uniforms)505 [[nodiscard]] bool EmulateFramebufferFetch(TCompiler *compiler,
506                                            TIntermBlock *root,
507                                            std::vector<ShaderVariable> *uniforms)
508 {
509     // First, check if input attachments are necessary at all.
510     TVector<const TType *> attachmentTypes;
511     InputAttachmentUsageTraverser usageTraverser(compiler->getBuiltInResources().MaxDrawBuffers,
512                                                  &attachmentTypes);
513     root->traverse(&usageTraverser);
514 
515     InputAttachmentIndexUsage indexUsage = usageTraverser.getIndexUsage();
516     if (!indexUsage.any())
517     {
518         return true;
519     }
520 
521     const bool usesLastFragData =
522         compiler->getShaderVersion() == 100 || usageTraverser.usesLastFragColorARM();
523 
524     // Declare the necessary variables for emulation; input attachments to read from and global
525     // variables to hold last frag data.
526     TVector<const TVariable *> inputAttachmentVars;
527     const TVariable *lastFragData = nullptr;
528     if (!DeclareVariables(compiler, root, indexUsage, usesLastFragData, attachmentTypes,
529                           &inputAttachmentVars, &lastFragData))
530     {
531         return false;
532     }
533 
534     // Then replace references to gl_LastFragData with the global, gl_LastFragColorARM with
535     // global[0], replace inout variables with out equivalents and make sure input attachments
536     // initialize the appropriate variables at the beginning of the shader.
537     if (!ReplaceVariables(compiler, root, inputAttachmentVars, lastFragData))
538     {
539         return false;
540     }
541 
542     // Add the newly declared variables to the list of uniforms.
543     AddInputAttachmentsToUniforms(inputAttachmentVars, uniforms);
544     return true;
545 }
546 
547 }  // namespace sh
548