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// RewriteExpressionsWithShaderStorageBlock rewrites the expressions that contain shader storage
7// block calls into several simple ones that can be easily handled in the HLSL translator. After the
8// AST pass, all ssbo related blocks will be like below:
9// ssbo_access_chain = ssbo_access_chain;
10// ssbo_access_chain = expr_no_ssbo;
11// lvalue_no_ssbo = ssbo_access_chain;
12//
13
14#include "compiler/translator/tree_ops/RewriteExpressionsWithShaderStorageBlock.h"
15
16#include "compiler/translator/Symbol.h"
17#include "compiler/translator/tree_util/IntermNode_util.h"
18#include "compiler/translator/tree_util/IntermTraverse.h"
19#include "compiler/translator/util.h"
20
21namespace sh
22{
23namespace
24{
25
26bool IsIncrementOrDecrementOperator(TOperator op)
27{
28 switch (op)
29 {
30 case EOpPostIncrement:
31 case EOpPostDecrement:
32 case EOpPreIncrement:
33 case EOpPreDecrement:
34 return true;
35 default:
36 return false;
37 }
38}
39
40bool IsCompoundAssignment(TOperator op)
41{
42 switch (op)
43 {
44 case EOpAddAssign:
45 case EOpSubAssign:
46 case EOpMulAssign:
47 case EOpVectorTimesMatrixAssign:
48 case EOpVectorTimesScalarAssign:
49 case EOpMatrixTimesScalarAssign:
50 case EOpMatrixTimesMatrixAssign:
51 case EOpDivAssign:
52 case EOpIModAssign:
53 case EOpBitShiftLeftAssign:
54 case EOpBitShiftRightAssign:
55 case EOpBitwiseAndAssign:
56 case EOpBitwiseXorAssign:
57 case EOpBitwiseOrAssign:
58 return true;
59 default:
60 return false;
61 }
62}
63
64// EOpIndexDirect, EOpIndexIndirect, EOpIndexDirectStruct, EOpIndexDirectInterfaceBlock belong to
65// operators in SSBO access chain.
66bool IsReadonlyBinaryOperatorNotInSSBOAccessChain(TOperator op)
67{
68 switch (op)
69 {
70 case EOpComma:
71 case EOpAdd:
72 case EOpSub:
73 case EOpMul:
74 case EOpDiv:
75 case EOpIMod:
76 case EOpBitShiftLeft:
77 case EOpBitShiftRight:
78 case EOpBitwiseAnd:
79 case EOpBitwiseXor:
80 case EOpBitwiseOr:
81 case EOpEqual:
82 case EOpNotEqual:
83 case EOpLessThan:
84 case EOpGreaterThan:
85 case EOpLessThanEqual:
86 case EOpGreaterThanEqual:
87 case EOpVectorTimesScalar:
88 case EOpMatrixTimesScalar:
89 case EOpVectorTimesMatrix:
90 case EOpMatrixTimesVector:
91 case EOpMatrixTimesMatrix:
92 case EOpLogicalOr:
93 case EOpLogicalXor:
94 case EOpLogicalAnd:
95 return true;
96 default:
97 return false;
98 }
99}
100
101bool HasSSBOAsFunctionArgument(TIntermSequence *arguments)
102{
103 for (TIntermNode *arg : *arguments)
104 {
105 TIntermTyped *typedArg = arg->getAsTyped();
106 if (IsInShaderStorageBlock(typedArg))
107 {
108 return true;
109 }
110 }
111 return false;
112}
113
114class RewriteExpressionsWithShaderStorageBlockTraverser : public TIntermTraverser
115{
116 public:
117 RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable);
118 void nextIteration();
119 bool foundSSBO() const { return mFoundSSBO; }
120
121 private:
122 bool visitBinary(Visit, TIntermBinary *node) override;
123 bool visitAggregate(Visit visit, TIntermAggregate *node) override;
124 bool visitUnary(Visit visit, TIntermUnary *node) override;
125
126 TIntermSymbol *insertInitStatementAndReturnTempSymbol(TIntermTyped *node,
127 TIntermSequence *insertions);
128
129 bool mFoundSSBO;
130};
131
132RewriteExpressionsWithShaderStorageBlockTraverser::
133 RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable)
134 : TIntermTraverser(true, true, false, symbolTable), mFoundSSBO(false)
135{}
136
137TIntermSymbol *
138RewriteExpressionsWithShaderStorageBlockTraverser::insertInitStatementAndReturnTempSymbol(
139 TIntermTyped *node,
140 TIntermSequence *insertions)
141{
142 TIntermDeclaration *variableDeclaration;
143 TVariable *tempVariable =
144 DeclareTempVariable(mSymbolTable, node, EvqTemporary, &variableDeclaration);
145
146 insertions->push_back(variableDeclaration);
147 return CreateTempSymbolNode(tempVariable);
148}
149
150bool RewriteExpressionsWithShaderStorageBlockTraverser::visitBinary(Visit visit,
151 TIntermBinary *node)
152{
153 // Make sure that the expression is caculated from left to right.
154 if (visit != InVisit)
155 {
156 return true;
157 }
158
159 if (mFoundSSBO)
160 {
161 return false;
162 }
163
164 bool rightSSBO = IsInShaderStorageBlock(node->getRight());
165 bool leftSSBO = IsInShaderStorageBlock(node->getLeft());
166 if (!leftSSBO && !rightSSBO)
167 {
168 return true;
169 }
170
171 // case 1: Compound assigment operator
172 // original:
173 // lssbo += expr;
174 // new:
175 // var rvalue = expr;
176 // var temp = lssbo;
177 // temp += rvalue;
178 // lssbo = temp;
179 //
180 // original:
181 // lvalue_no_ssbo += rssbo;
182 // new:
183 // var rvalue = rssbo;
184 // lvalue_no_ssbo += rvalue;
185 if (IsCompoundAssignment(node->getOp()))
186 {
187 mFoundSSBO = true;
188 TIntermSequence insertions;
189 TIntermTyped *rightNode =
190 insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions);
191 if (leftSSBO)
192 {
193 TIntermSymbol *tempSymbol =
194 insertInitStatementAndReturnTempSymbol(node->getLeft()->deepCopy(), &insertions);
195 TIntermBinary *tempCompoundOperate =
196 new TIntermBinary(node->getOp(), tempSymbol->deepCopy(), rightNode->deepCopy());
197 insertions.push_back(tempCompoundOperate);
198 insertStatementsInParentBlock(insertions);
199
200 TIntermBinary *assignTempValueToSSBO =
201 new TIntermBinary(EOpAssign, node->getLeft(), tempSymbol->deepCopy());
202 queueReplacement(assignTempValueToSSBO, OriginalNode::IS_DROPPED);
203 }
204 else
205 {
206 insertStatementsInParentBlock(insertions);
207 TIntermBinary *compoundAssignRValueToLValue =
208 new TIntermBinary(node->getOp(), node->getLeft(), rightNode->deepCopy());
209 queueReplacement(compoundAssignRValueToLValue, OriginalNode::IS_DROPPED);
210 }
211 }
212 // case 2: Readonly binary operator
213 // original:
214 // ssbo0 + ssbo1 + ssbo2;
215 // new:
216 // var temp0 = ssbo0;
217 // var temp1 = ssbo1;
218 // var temp2 = ssbo2;
219 // temp0 + temp1 + temp2;
220 else if (IsReadonlyBinaryOperatorNotInSSBOAccessChain(node->getOp()) && (leftSSBO || rightSSBO))
221 {
222 mFoundSSBO = true;
223 TIntermTyped *rightNode = node->getRight();
224 TIntermTyped *leftNode = node->getLeft();
225 TIntermSequence insertions;
226 if (rightSSBO)
227 {
228 rightNode = insertInitStatementAndReturnTempSymbol(node->getRight(), &insertions);
229 }
230 if (leftSSBO)
231 {
232 leftNode = insertInitStatementAndReturnTempSymbol(node->getLeft(), &insertions);
233 }
234
235 insertStatementsInParentBlock(insertions);
236 TIntermBinary *newExpr =
237 new TIntermBinary(node->getOp(), leftNode->deepCopy(), rightNode->deepCopy());
238 queueReplacement(newExpr, OriginalNode::IS_DROPPED);
239 }
240 return !mFoundSSBO;
241}
242
243// case 3: ssbo as the argument of aggregate type
244// original:
245// foo(ssbo);
246// new:
247// var tempArg = ssbo;
248// foo(tempArg);
249// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument)
250//
251// original:
252// foo(ssbo) * expr;
253// new:
254// var tempArg = ssbo;
255// var tempReturn = foo(tempArg);
256// ssbo = tempArg; (Optional based on whether ssbo is an out|input argument)
257// tempReturn * expr;
258bool RewriteExpressionsWithShaderStorageBlockTraverser::visitAggregate(Visit visit,
259 TIntermAggregate *node)
260{
261 // Make sure that visitAggregate is only executed once for same node.
262 if (visit != PreVisit)
263 {
264 return true;
265 }
266
267 if (mFoundSSBO)
268 {
269 return false;
270 }
271
272 // We still need to process the ssbo as the non-first argument of atomic memory functions.
273 if (IsAtomicFunction(node->getOp()) &&
274 IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped()))
275 {
276 return true;
277 }
278
279 if (!HasSSBOAsFunctionArgument(node->getSequence()))
280 {
281 return true;
282 }
283
284 mFoundSSBO = true;
285 TIntermSequence insertions;
286 TIntermSequence readBackToSSBOs;
287 TIntermSequence *originalArguments = node->getSequence();
288 for (size_t i = 0; i < node->getChildCount(); ++i)
289 {
290 TIntermTyped *ssboArgument = (*originalArguments)[i]->getAsTyped();
291 if (IsInShaderStorageBlock(ssboArgument))
292 {
293 TIntermSymbol *argumentCopy =
294 insertInitStatementAndReturnTempSymbol(ssboArgument, &insertions);
295 if (node->getFunction() != nullptr)
296 {
297 TQualifier qual = node->getFunction()->getParam(i)->getType().getQualifier();
298 if (qual == EvqInOut || qual == EvqOut)
299 {
300 TIntermBinary *readBackToSSBO = new TIntermBinary(
301 EOpAssign, ssboArgument->deepCopy(), argumentCopy->deepCopy());
302 readBackToSSBOs.push_back(readBackToSSBO);
303 }
304 }
305 node->replaceChildNode(ssboArgument, argumentCopy);
306 }
307 }
308
309 TIntermBlock *parentBlock = getParentNode()->getAsBlock();
310 if (parentBlock)
311 {
312 // Aggregate node is as a single sentence.
313 insertions.push_back(node);
314 if (!readBackToSSBOs.empty())
315 {
316 insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end());
317 }
318 mMultiReplacements.push_back(NodeReplaceWithMultipleEntry(parentBlock, node, insertions));
319 }
320 else
321 {
322 // Aggregate node is inside an expression.
323 TIntermSymbol *tempSymbol = insertInitStatementAndReturnTempSymbol(node, &insertions);
324 if (!readBackToSSBOs.empty())
325 {
326 insertions.insert(insertions.end(), readBackToSSBOs.begin(), readBackToSSBOs.end());
327 }
328 insertStatementsInParentBlock(insertions);
329 queueReplacement(tempSymbol->deepCopy(), OriginalNode::IS_DROPPED);
330 }
331
332 return false;
333}
334
335bool RewriteExpressionsWithShaderStorageBlockTraverser::visitUnary(Visit visit, TIntermUnary *node)
336{
337 if (mFoundSSBO)
338 {
339 return false;
340 }
341
342 if (!IsInShaderStorageBlock(node->getOperand()))
343 {
344 return true;
345 }
346
347 // .length() is processed in OutputHLSL.
348 if (node->getOp() == EOpArrayLength)
349 {
350 return true;
351 }
352
353 mFoundSSBO = true;
354
355 // case 4: ssbo as the operand of ++/--
356 // original:
357 // ++ssbo * expr;
358 // new:
359 // var temp1 = ssbo;
360 // var temp2 = ++temp1;
361 // ssbo = temp1;
362 // temp2 * expr;
363 if (IsIncrementOrDecrementOperator(node->getOp()))
364 {
365 TIntermSequence insertions;
366 TIntermSymbol *temp1 =
367 insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions);
368 TIntermUnary *newUnary = new TIntermUnary(node->getOp(), temp1->deepCopy(), nullptr);
369 TIntermSymbol *temp2 = insertInitStatementAndReturnTempSymbol(newUnary, &insertions);
370 TIntermBinary *readBackToSSBO =
371 new TIntermBinary(EOpAssign, node->getOperand()->deepCopy(), temp1->deepCopy());
372 insertions.push_back(readBackToSSBO);
373 insertStatementsInParentBlock(insertions);
374 queueReplacement(temp2->deepCopy(), OriginalNode::IS_DROPPED);
375 }
376 // case 5: ssbo as the operand of readonly unary operator
377 // original:
378 // ~ssbo * expr;
379 // new:
380 // var temp = ssbo;
381 // ~temp * expr;
382 else
383 {
384 TIntermSequence insertions;
385 TIntermSymbol *temp =
386 insertInitStatementAndReturnTempSymbol(node->getOperand(), &insertions);
387 insertStatementsInParentBlock(insertions);
388 node->replaceChildNode(node->getOperand(), temp->deepCopy());
389 }
390 return false;
391}
392
393void RewriteExpressionsWithShaderStorageBlockTraverser::nextIteration()
394{
395 mFoundSSBO = false;
396}
397
398} // anonymous namespace
399
400void RewriteExpressionsWithShaderStorageBlock(TIntermNode *root, TSymbolTable *symbolTable)
401{
402 RewriteExpressionsWithShaderStorageBlockTraverser traverser(symbolTable);
403 do
404 {
405 traverser.nextIteration();
406 root->traverse(&traverser);
407 if (traverser.foundSSBO())
408 traverser.updateTree();
409 } while (traverser.foundSSBO());
410}
411} // namespace sh
412