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