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