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 | |
21 | namespace sh |
22 | { |
23 | namespace |
24 | { |
25 | |
26 | bool 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 | |
40 | bool 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. |
66 | bool 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 | |
101 | bool 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 | |
114 | class 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 | |
132 | RewriteExpressionsWithShaderStorageBlockTraverser:: |
133 | RewriteExpressionsWithShaderStorageBlockTraverser(TSymbolTable *symbolTable) |
134 | : TIntermTraverser(true, true, false, symbolTable), mFoundSSBO(false) |
135 | {} |
136 | |
137 | TIntermSymbol * |
138 | RewriteExpressionsWithShaderStorageBlockTraverser::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 | |
150 | bool 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; |
258 | bool 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 | |
335 | bool 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 | |
393 | void RewriteExpressionsWithShaderStorageBlockTraverser::nextIteration() |
394 | { |
395 | mFoundSSBO = false; |
396 | } |
397 | |
398 | } // anonymous namespace |
399 | |
400 | void 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 | |