1 | // |
2 | // Copyright (c) 2002-2014 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 | |
7 | #include "compiler/translator/tree_ops/EmulatePrecision.h" |
8 | |
9 | #include "compiler/translator/FunctionLookup.h" |
10 | |
11 | #include <memory> |
12 | |
13 | namespace sh |
14 | { |
15 | |
16 | namespace |
17 | { |
18 | |
19 | constexpr const ImmutableString kParamXName("x" ); |
20 | constexpr const ImmutableString kParamYName("y" ); |
21 | constexpr const ImmutableString kAngleFrmString("angle_frm" ); |
22 | constexpr const ImmutableString kAngleFrlString("angle_frl" ); |
23 | |
24 | class RoundingHelperWriter : angle::NonCopyable |
25 | { |
26 | public: |
27 | static RoundingHelperWriter *createHelperWriter(const ShShaderOutput outputLanguage); |
28 | |
29 | void writeCommonRoundingHelpers(TInfoSinkBase &sink, const int shaderVersion); |
30 | void writeCompoundAssignmentHelper(TInfoSinkBase &sink, |
31 | const char *lType, |
32 | const char *rType, |
33 | const char *opStr, |
34 | const char *opNameStr); |
35 | |
36 | virtual ~RoundingHelperWriter() {} |
37 | |
38 | protected: |
39 | RoundingHelperWriter(const ShShaderOutput outputLanguage) : mOutputLanguage(outputLanguage) {} |
40 | RoundingHelperWriter() = delete; |
41 | |
42 | const ShShaderOutput mOutputLanguage; |
43 | |
44 | private: |
45 | virtual std::string getTypeString(const char *glslType) = 0; |
46 | virtual void writeFloatRoundingHelpers(TInfoSinkBase &sink) = 0; |
47 | virtual void writeVectorRoundingHelpers(TInfoSinkBase &sink, const unsigned int size) = 0; |
48 | virtual void writeMatrixRoundingHelper(TInfoSinkBase &sink, |
49 | const unsigned int columns, |
50 | const unsigned int rows, |
51 | const char *functionName) = 0; |
52 | }; |
53 | |
54 | class RoundingHelperWriterGLSL : public RoundingHelperWriter |
55 | { |
56 | public: |
57 | RoundingHelperWriterGLSL(const ShShaderOutput outputLanguage) |
58 | : RoundingHelperWriter(outputLanguage) |
59 | {} |
60 | |
61 | private: |
62 | std::string getTypeString(const char *glslType) override; |
63 | void writeFloatRoundingHelpers(TInfoSinkBase &sink) override; |
64 | void writeVectorRoundingHelpers(TInfoSinkBase &sink, const unsigned int size) override; |
65 | void writeMatrixRoundingHelper(TInfoSinkBase &sink, |
66 | const unsigned int columns, |
67 | const unsigned int rows, |
68 | const char *functionName) override; |
69 | }; |
70 | |
71 | class RoundingHelperWriterESSL : public RoundingHelperWriterGLSL |
72 | { |
73 | public: |
74 | RoundingHelperWriterESSL(const ShShaderOutput outputLanguage) |
75 | : RoundingHelperWriterGLSL(outputLanguage) |
76 | {} |
77 | |
78 | private: |
79 | std::string getTypeString(const char *glslType) override; |
80 | }; |
81 | |
82 | class RoundingHelperWriterHLSL : public RoundingHelperWriter |
83 | { |
84 | public: |
85 | RoundingHelperWriterHLSL(const ShShaderOutput outputLanguage) |
86 | : RoundingHelperWriter(outputLanguage) |
87 | {} |
88 | |
89 | private: |
90 | std::string getTypeString(const char *glslType) override; |
91 | void writeFloatRoundingHelpers(TInfoSinkBase &sink) override; |
92 | void writeVectorRoundingHelpers(TInfoSinkBase &sink, const unsigned int size) override; |
93 | void writeMatrixRoundingHelper(TInfoSinkBase &sink, |
94 | const unsigned int columns, |
95 | const unsigned int rows, |
96 | const char *functionName) override; |
97 | }; |
98 | |
99 | RoundingHelperWriter *RoundingHelperWriter::createHelperWriter(const ShShaderOutput outputLanguage) |
100 | { |
101 | ASSERT(EmulatePrecision::SupportedInLanguage(outputLanguage)); |
102 | switch (outputLanguage) |
103 | { |
104 | case SH_HLSL_4_1_OUTPUT: |
105 | return new RoundingHelperWriterHLSL(outputLanguage); |
106 | case SH_ESSL_OUTPUT: |
107 | return new RoundingHelperWriterESSL(outputLanguage); |
108 | default: |
109 | return new RoundingHelperWriterGLSL(outputLanguage); |
110 | } |
111 | } |
112 | |
113 | void RoundingHelperWriter::writeCommonRoundingHelpers(TInfoSinkBase &sink, const int shaderVersion) |
114 | { |
115 | // Write the angle_frm functions that round floating point numbers to |
116 | // half precision, and angle_frl functions that round them to minimum lowp |
117 | // precision. |
118 | |
119 | writeFloatRoundingHelpers(sink); |
120 | writeVectorRoundingHelpers(sink, 2); |
121 | writeVectorRoundingHelpers(sink, 3); |
122 | writeVectorRoundingHelpers(sink, 4); |
123 | if (shaderVersion > 100) |
124 | { |
125 | for (unsigned int columns = 2; columns <= 4; ++columns) |
126 | { |
127 | for (unsigned int rows = 2; rows <= 4; ++rows) |
128 | { |
129 | writeMatrixRoundingHelper(sink, columns, rows, "angle_frm" ); |
130 | writeMatrixRoundingHelper(sink, columns, rows, "angle_frl" ); |
131 | } |
132 | } |
133 | } |
134 | else |
135 | { |
136 | for (unsigned int size = 2; size <= 4; ++size) |
137 | { |
138 | writeMatrixRoundingHelper(sink, size, size, "angle_frm" ); |
139 | writeMatrixRoundingHelper(sink, size, size, "angle_frl" ); |
140 | } |
141 | } |
142 | } |
143 | |
144 | void RoundingHelperWriter::writeCompoundAssignmentHelper(TInfoSinkBase &sink, |
145 | const char *lType, |
146 | const char *rType, |
147 | const char *opStr, |
148 | const char *opNameStr) |
149 | { |
150 | std::string lTypeStr = getTypeString(lType); |
151 | std::string rTypeStr = getTypeString(rType); |
152 | |
153 | // Note that y should be passed through angle_frm at the function call site, |
154 | // but x can't be passed through angle_frm there since it is an inout parameter. |
155 | // So only pass x and the result through angle_frm here. |
156 | // clang-format off |
157 | sink << |
158 | lTypeStr << " angle_compound_" << opNameStr << "_frm(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n" |
159 | " x = angle_frm(angle_frm(x) " << opStr << " y);\n" |
160 | " return x;\n" |
161 | "}\n" ; |
162 | sink << |
163 | lTypeStr << " angle_compound_" << opNameStr << "_frl(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n" |
164 | " x = angle_frl(angle_frl(x) " << opStr << " y);\n" |
165 | " return x;\n" |
166 | "}\n" ; |
167 | // clang-format on |
168 | } |
169 | |
170 | std::string RoundingHelperWriterGLSL::getTypeString(const char *glslType) |
171 | { |
172 | return glslType; |
173 | } |
174 | |
175 | std::string RoundingHelperWriterESSL::getTypeString(const char *glslType) |
176 | { |
177 | std::stringstream typeStrStr = sh::InitializeStream<std::stringstream>(); |
178 | typeStrStr << "highp " << glslType; |
179 | return typeStrStr.str(); |
180 | } |
181 | |
182 | void RoundingHelperWriterGLSL::writeFloatRoundingHelpers(TInfoSinkBase &sink) |
183 | { |
184 | // Unoptimized version of angle_frm for single floats: |
185 | // |
186 | // int webgl_maxNormalExponent(in int exponentBits) |
187 | // { |
188 | // int possibleExponents = int(exp2(float(exponentBits))); |
189 | // int exponentBias = possibleExponents / 2 - 1; |
190 | // int allExponentBitsOne = possibleExponents - 1; |
191 | // return (allExponentBitsOne - 1) - exponentBias; |
192 | // } |
193 | // |
194 | // float angle_frm(in float x) |
195 | // { |
196 | // int mantissaBits = 10; |
197 | // int exponentBits = 5; |
198 | // float possibleMantissas = exp2(float(mantissaBits)); |
199 | // float mantissaMax = 2.0 - 1.0 / possibleMantissas; |
200 | // int maxNE = webgl_maxNormalExponent(exponentBits); |
201 | // float max = exp2(float(maxNE)) * mantissaMax; |
202 | // if (x > max) |
203 | // { |
204 | // return max; |
205 | // } |
206 | // if (x < -max) |
207 | // { |
208 | // return -max; |
209 | // } |
210 | // float exponent = floor(log2(abs(x))); |
211 | // if (abs(x) == 0.0 || exponent < -float(maxNE)) |
212 | // { |
213 | // return 0.0 * sign(x) |
214 | // } |
215 | // x = x * exp2(-(exponent - float(mantissaBits))); |
216 | // x = sign(x) * floor(abs(x)); |
217 | // return x * exp2(exponent - float(mantissaBits)); |
218 | // } |
219 | |
220 | // All numbers with a magnitude less than 2^-15 are subnormal, and are |
221 | // flushed to zero. |
222 | |
223 | // Note the constant numbers below: |
224 | // a) 65504 is the maximum possible mantissa (1.1111111111 in binary) times |
225 | // 2^15, the maximum normal exponent. |
226 | // b) 10.0 is the number of mantissa bits. |
227 | // c) -25.0 is the minimum normal half-float exponent -15.0 minus the number |
228 | // of mantissa bits. |
229 | // d) + 1e-30 is to make sure the argument of log2() won't be zero. It can |
230 | // only affect the result of log2 on x where abs(x) < 1e-22. Since these |
231 | // numbers will be flushed to zero either way (2^-15 is the smallest |
232 | // normal positive number), this does not introduce any error. |
233 | |
234 | std::string floatType = getTypeString("float" ); |
235 | |
236 | // clang-format off |
237 | sink << |
238 | floatType << " angle_frm(in " << floatType << " x) {\n" |
239 | " x = clamp(x, -65504.0, 65504.0);\n" |
240 | " " << floatType << " exponent = floor(log2(abs(x) + 1e-30)) - 10.0;\n" |
241 | " bool isNonZero = (exponent >= -25.0);\n" |
242 | " x = x * exp2(-exponent);\n" |
243 | " x = sign(x) * floor(abs(x));\n" |
244 | " return x * exp2(exponent) * float(isNonZero);\n" |
245 | "}\n" ; |
246 | |
247 | sink << |
248 | floatType << " angle_frl(in " << floatType << " x) {\n" |
249 | " x = clamp(x, -2.0, 2.0);\n" |
250 | " x = x * 256.0;\n" |
251 | " x = sign(x) * floor(abs(x));\n" |
252 | " return x * 0.00390625;\n" |
253 | "}\n" ; |
254 | // clang-format on |
255 | } |
256 | |
257 | void RoundingHelperWriterGLSL::writeVectorRoundingHelpers(TInfoSinkBase &sink, |
258 | const unsigned int size) |
259 | { |
260 | std::stringstream vecTypeStrStr = sh::InitializeStream<std::stringstream>(); |
261 | vecTypeStrStr << "vec" << size; |
262 | std::string vecType = getTypeString(vecTypeStrStr.str().c_str()); |
263 | |
264 | // clang-format off |
265 | sink << |
266 | vecType << " angle_frm(in " << vecType << " v) {\n" |
267 | " v = clamp(v, -65504.0, 65504.0);\n" |
268 | " " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n" |
269 | " bvec" << size << " isNonZero = greaterThanEqual(exponent, vec" << size << "(-25.0));\n" |
270 | " v = v * exp2(-exponent);\n" |
271 | " v = sign(v) * floor(abs(v));\n" |
272 | " return v * exp2(exponent) * vec" << size << "(isNonZero);\n" |
273 | "}\n" ; |
274 | |
275 | sink << |
276 | vecType << " angle_frl(in " << vecType << " v) {\n" |
277 | " v = clamp(v, -2.0, 2.0);\n" |
278 | " v = v * 256.0;\n" |
279 | " v = sign(v) * floor(abs(v));\n" |
280 | " return v * 0.00390625;\n" |
281 | "}\n" ; |
282 | // clang-format on |
283 | } |
284 | |
285 | void RoundingHelperWriterGLSL::writeMatrixRoundingHelper(TInfoSinkBase &sink, |
286 | const unsigned int columns, |
287 | const unsigned int rows, |
288 | const char *functionName) |
289 | { |
290 | std::stringstream matTypeStrStr = sh::InitializeStream<std::stringstream>(); |
291 | matTypeStrStr << "mat" << columns; |
292 | if (rows != columns) |
293 | { |
294 | matTypeStrStr << "x" << rows; |
295 | } |
296 | std::string matType = getTypeString(matTypeStrStr.str().c_str()); |
297 | |
298 | sink << matType << " " << functionName << "(in " << matType << " m) {\n" |
299 | << " " << matType << " rounded;\n" ; |
300 | |
301 | for (unsigned int i = 0; i < columns; ++i) |
302 | { |
303 | sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n" ; |
304 | } |
305 | |
306 | sink << " return rounded;\n" |
307 | "}\n" ; |
308 | } |
309 | |
310 | static const char *GetHLSLTypeStr(const char *floatTypeStr) |
311 | { |
312 | if (strcmp(floatTypeStr, "float" ) == 0) |
313 | { |
314 | return "float" ; |
315 | } |
316 | if (strcmp(floatTypeStr, "vec2" ) == 0) |
317 | { |
318 | return "float2" ; |
319 | } |
320 | if (strcmp(floatTypeStr, "vec3" ) == 0) |
321 | { |
322 | return "float3" ; |
323 | } |
324 | if (strcmp(floatTypeStr, "vec4" ) == 0) |
325 | { |
326 | return "float4" ; |
327 | } |
328 | if (strcmp(floatTypeStr, "mat2" ) == 0) |
329 | { |
330 | return "float2x2" ; |
331 | } |
332 | if (strcmp(floatTypeStr, "mat3" ) == 0) |
333 | { |
334 | return "float3x3" ; |
335 | } |
336 | if (strcmp(floatTypeStr, "mat4" ) == 0) |
337 | { |
338 | return "float4x4" ; |
339 | } |
340 | if (strcmp(floatTypeStr, "mat2x3" ) == 0) |
341 | { |
342 | return "float2x3" ; |
343 | } |
344 | if (strcmp(floatTypeStr, "mat2x4" ) == 0) |
345 | { |
346 | return "float2x4" ; |
347 | } |
348 | if (strcmp(floatTypeStr, "mat3x2" ) == 0) |
349 | { |
350 | return "float3x2" ; |
351 | } |
352 | if (strcmp(floatTypeStr, "mat3x4" ) == 0) |
353 | { |
354 | return "float3x4" ; |
355 | } |
356 | if (strcmp(floatTypeStr, "mat4x2" ) == 0) |
357 | { |
358 | return "float4x2" ; |
359 | } |
360 | if (strcmp(floatTypeStr, "mat4x3" ) == 0) |
361 | { |
362 | return "float4x3" ; |
363 | } |
364 | UNREACHABLE(); |
365 | return nullptr; |
366 | } |
367 | |
368 | std::string RoundingHelperWriterHLSL::getTypeString(const char *glslType) |
369 | { |
370 | return GetHLSLTypeStr(glslType); |
371 | } |
372 | |
373 | void RoundingHelperWriterHLSL::writeFloatRoundingHelpers(TInfoSinkBase &sink) |
374 | { |
375 | // In HLSL scalars are the same as 1-vectors. |
376 | writeVectorRoundingHelpers(sink, 1); |
377 | } |
378 | |
379 | void RoundingHelperWriterHLSL::writeVectorRoundingHelpers(TInfoSinkBase &sink, |
380 | const unsigned int size) |
381 | { |
382 | std::stringstream vecTypeStrStr = sh::InitializeStream<std::stringstream>(); |
383 | vecTypeStrStr << "float" << size; |
384 | std::string vecType = vecTypeStrStr.str(); |
385 | |
386 | // clang-format off |
387 | sink << |
388 | vecType << " angle_frm(" << vecType << " v) {\n" |
389 | " v = clamp(v, -65504.0, 65504.0);\n" |
390 | " " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n" |
391 | " bool" << size << " isNonZero = exponent < -25.0;\n" |
392 | " v = v * exp2(-exponent);\n" |
393 | " v = sign(v) * floor(abs(v));\n" |
394 | " return v * exp2(exponent) * (float" << size << ")(isNonZero);\n" |
395 | "}\n" ; |
396 | |
397 | sink << |
398 | vecType << " angle_frl(" << vecType << " v) {\n" |
399 | " v = clamp(v, -2.0, 2.0);\n" |
400 | " v = v * 256.0;\n" |
401 | " v = sign(v) * floor(abs(v));\n" |
402 | " return v * 0.00390625;\n" |
403 | "}\n" ; |
404 | // clang-format on |
405 | } |
406 | |
407 | void RoundingHelperWriterHLSL::writeMatrixRoundingHelper(TInfoSinkBase &sink, |
408 | const unsigned int columns, |
409 | const unsigned int rows, |
410 | const char *functionName) |
411 | { |
412 | std::stringstream matTypeStrStr = sh::InitializeStream<std::stringstream>(); |
413 | matTypeStrStr << "float" << columns << "x" << rows; |
414 | std::string matType = matTypeStrStr.str(); |
415 | |
416 | sink << matType << " " << functionName << "(" << matType << " m) {\n" |
417 | << " " << matType << " rounded;\n" ; |
418 | |
419 | for (unsigned int i = 0; i < columns; ++i) |
420 | { |
421 | sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n" ; |
422 | } |
423 | |
424 | sink << " return rounded;\n" |
425 | "}\n" ; |
426 | } |
427 | |
428 | bool canRoundFloat(const TType &type) |
429 | { |
430 | return type.getBasicType() == EbtFloat && !type.isArray() && |
431 | (type.getPrecision() == EbpLow || type.getPrecision() == EbpMedium); |
432 | } |
433 | |
434 | bool ParentUsesResult(TIntermNode *parent, TIntermTyped *node) |
435 | { |
436 | if (!parent) |
437 | { |
438 | return false; |
439 | } |
440 | |
441 | TIntermBlock *blockParent = parent->getAsBlock(); |
442 | // If the parent is a block, the result is not assigned anywhere, |
443 | // so rounding it is not needed. In particular, this can avoid a lot of |
444 | // unnecessary rounding of unused return values of assignment. |
445 | if (blockParent) |
446 | { |
447 | return false; |
448 | } |
449 | TIntermBinary *binaryParent = parent->getAsBinaryNode(); |
450 | if (binaryParent && binaryParent->getOp() == EOpComma && (binaryParent->getRight() != node)) |
451 | { |
452 | return false; |
453 | } |
454 | return true; |
455 | } |
456 | |
457 | bool ParentConstructorTakesCareOfRounding(TIntermNode *parent, TIntermTyped *node) |
458 | { |
459 | if (!parent) |
460 | { |
461 | return false; |
462 | } |
463 | TIntermAggregate *parentConstructor = parent->getAsAggregate(); |
464 | if (!parentConstructor || parentConstructor->getOp() != EOpConstruct) |
465 | { |
466 | return false; |
467 | } |
468 | if (parentConstructor->getPrecision() != node->getPrecision()) |
469 | { |
470 | return false; |
471 | } |
472 | return canRoundFloat(parentConstructor->getType()); |
473 | } |
474 | |
475 | } // namespace |
476 | |
477 | EmulatePrecision::EmulatePrecision(TSymbolTable *symbolTable) |
478 | : TLValueTrackingTraverser(true, true, true, symbolTable), mDeclaringVariables(false) |
479 | {} |
480 | |
481 | void EmulatePrecision::visitSymbol(TIntermSymbol *node) |
482 | { |
483 | TIntermNode *parent = getParentNode(); |
484 | if (canRoundFloat(node->getType()) && ParentUsesResult(parent, node) && |
485 | !ParentConstructorTakesCareOfRounding(parent, node) && !mDeclaringVariables && |
486 | !isLValueRequiredHere()) |
487 | { |
488 | TIntermNode *replacement = createRoundingFunctionCallNode(node); |
489 | queueReplacement(replacement, OriginalNode::BECOMES_CHILD); |
490 | } |
491 | } |
492 | |
493 | bool EmulatePrecision::visitBinary(Visit visit, TIntermBinary *node) |
494 | { |
495 | bool visitChildren = true; |
496 | |
497 | TOperator op = node->getOp(); |
498 | |
499 | // RHS of initialize is not being declared. |
500 | if (op == EOpInitialize && visit == InVisit) |
501 | mDeclaringVariables = false; |
502 | |
503 | if ((op == EOpIndexDirectStruct) && visit == InVisit) |
504 | visitChildren = false; |
505 | |
506 | if (visit != PreVisit) |
507 | return visitChildren; |
508 | |
509 | const TType &type = node->getType(); |
510 | bool roundFloat = canRoundFloat(type); |
511 | |
512 | if (roundFloat) |
513 | { |
514 | switch (op) |
515 | { |
516 | // Math operators that can result in a float may need to apply rounding to the return |
517 | // value. Note that in the case of assignment, the rounding is applied to its return |
518 | // value here, not the value being assigned. |
519 | case EOpAssign: |
520 | case EOpAdd: |
521 | case EOpSub: |
522 | case EOpMul: |
523 | case EOpDiv: |
524 | case EOpVectorTimesScalar: |
525 | case EOpVectorTimesMatrix: |
526 | case EOpMatrixTimesVector: |
527 | case EOpMatrixTimesScalar: |
528 | case EOpMatrixTimesMatrix: |
529 | { |
530 | TIntermNode *parent = getParentNode(); |
531 | if (!ParentUsesResult(parent, node) || |
532 | ParentConstructorTakesCareOfRounding(parent, node)) |
533 | { |
534 | break; |
535 | } |
536 | TIntermNode *replacement = createRoundingFunctionCallNode(node); |
537 | queueReplacement(replacement, OriginalNode::BECOMES_CHILD); |
538 | break; |
539 | } |
540 | |
541 | // Compound assignment cases need to replace the operator with a function call. |
542 | case EOpAddAssign: |
543 | { |
544 | mEmulateCompoundAdd.insert( |
545 | TypePair(type.getBuiltInTypeNameString(), |
546 | node->getRight()->getType().getBuiltInTypeNameString())); |
547 | TIntermNode *replacement = createCompoundAssignmentFunctionCallNode( |
548 | node->getLeft(), node->getRight(), "add" ); |
549 | queueReplacement(replacement, OriginalNode::IS_DROPPED); |
550 | break; |
551 | } |
552 | case EOpSubAssign: |
553 | { |
554 | mEmulateCompoundSub.insert( |
555 | TypePair(type.getBuiltInTypeNameString(), |
556 | node->getRight()->getType().getBuiltInTypeNameString())); |
557 | TIntermNode *replacement = createCompoundAssignmentFunctionCallNode( |
558 | node->getLeft(), node->getRight(), "sub" ); |
559 | queueReplacement(replacement, OriginalNode::IS_DROPPED); |
560 | break; |
561 | } |
562 | case EOpMulAssign: |
563 | case EOpVectorTimesMatrixAssign: |
564 | case EOpVectorTimesScalarAssign: |
565 | case EOpMatrixTimesScalarAssign: |
566 | case EOpMatrixTimesMatrixAssign: |
567 | { |
568 | mEmulateCompoundMul.insert( |
569 | TypePair(type.getBuiltInTypeNameString(), |
570 | node->getRight()->getType().getBuiltInTypeNameString())); |
571 | TIntermNode *replacement = createCompoundAssignmentFunctionCallNode( |
572 | node->getLeft(), node->getRight(), "mul" ); |
573 | queueReplacement(replacement, OriginalNode::IS_DROPPED); |
574 | break; |
575 | } |
576 | case EOpDivAssign: |
577 | { |
578 | mEmulateCompoundDiv.insert( |
579 | TypePair(type.getBuiltInTypeNameString(), |
580 | node->getRight()->getType().getBuiltInTypeNameString())); |
581 | TIntermNode *replacement = createCompoundAssignmentFunctionCallNode( |
582 | node->getLeft(), node->getRight(), "div" ); |
583 | queueReplacement(replacement, OriginalNode::IS_DROPPED); |
584 | break; |
585 | } |
586 | default: |
587 | // The rest of the binary operations should not need precision emulation. |
588 | break; |
589 | } |
590 | } |
591 | return visitChildren; |
592 | } |
593 | |
594 | bool EmulatePrecision::visitDeclaration(Visit visit, TIntermDeclaration *node) |
595 | { |
596 | // Variable or interface block declaration. |
597 | if (visit == PreVisit) |
598 | { |
599 | mDeclaringVariables = true; |
600 | } |
601 | else if (visit == InVisit) |
602 | { |
603 | mDeclaringVariables = true; |
604 | } |
605 | else |
606 | { |
607 | mDeclaringVariables = false; |
608 | } |
609 | return true; |
610 | } |
611 | |
612 | bool EmulatePrecision::visitInvariantDeclaration(Visit visit, TIntermInvariantDeclaration *node) |
613 | { |
614 | return false; |
615 | } |
616 | |
617 | bool EmulatePrecision::visitAggregate(Visit visit, TIntermAggregate *node) |
618 | { |
619 | if (visit != PreVisit) |
620 | return true; |
621 | |
622 | // User-defined function return values are not rounded. The calculations that produced |
623 | // the value inside the function definition should have been rounded. |
624 | TOperator op = node->getOp(); |
625 | if (op == EOpCallInternalRawFunction || op == EOpCallFunctionInAST || |
626 | (op == EOpConstruct && node->getBasicType() == EbtStruct)) |
627 | { |
628 | return true; |
629 | } |
630 | |
631 | TIntermNode *parent = getParentNode(); |
632 | if (canRoundFloat(node->getType()) && ParentUsesResult(parent, node) && |
633 | !ParentConstructorTakesCareOfRounding(parent, node)) |
634 | { |
635 | TIntermNode *replacement = createRoundingFunctionCallNode(node); |
636 | queueReplacement(replacement, OriginalNode::BECOMES_CHILD); |
637 | } |
638 | return true; |
639 | } |
640 | |
641 | bool EmulatePrecision::visitUnary(Visit visit, TIntermUnary *node) |
642 | { |
643 | switch (node->getOp()) |
644 | { |
645 | case EOpNegative: |
646 | case EOpLogicalNot: |
647 | case EOpPostIncrement: |
648 | case EOpPostDecrement: |
649 | case EOpPreIncrement: |
650 | case EOpPreDecrement: |
651 | case EOpLogicalNotComponentWise: |
652 | break; |
653 | default: |
654 | if (canRoundFloat(node->getType()) && visit == PreVisit) |
655 | { |
656 | TIntermNode *replacement = createRoundingFunctionCallNode(node); |
657 | queueReplacement(replacement, OriginalNode::BECOMES_CHILD); |
658 | } |
659 | break; |
660 | } |
661 | |
662 | return true; |
663 | } |
664 | |
665 | void EmulatePrecision::writeEmulationHelpers(TInfoSinkBase &sink, |
666 | const int shaderVersion, |
667 | const ShShaderOutput outputLanguage) |
668 | { |
669 | std::unique_ptr<RoundingHelperWriter> roundingHelperWriter( |
670 | RoundingHelperWriter::createHelperWriter(outputLanguage)); |
671 | |
672 | roundingHelperWriter->writeCommonRoundingHelpers(sink, shaderVersion); |
673 | |
674 | EmulationSet::const_iterator it; |
675 | for (it = mEmulateCompoundAdd.begin(); it != mEmulateCompoundAdd.end(); it++) |
676 | roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "+" , "add" ); |
677 | for (it = mEmulateCompoundSub.begin(); it != mEmulateCompoundSub.end(); it++) |
678 | roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "-" , "sub" ); |
679 | for (it = mEmulateCompoundDiv.begin(); it != mEmulateCompoundDiv.end(); it++) |
680 | roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "/" , "div" ); |
681 | for (it = mEmulateCompoundMul.begin(); it != mEmulateCompoundMul.end(); it++) |
682 | roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "*" , "mul" ); |
683 | } |
684 | |
685 | // static |
686 | bool EmulatePrecision::SupportedInLanguage(const ShShaderOutput outputLanguage) |
687 | { |
688 | switch (outputLanguage) |
689 | { |
690 | case SH_HLSL_4_1_OUTPUT: |
691 | case SH_ESSL_OUTPUT: |
692 | return true; |
693 | default: |
694 | // Other languages not yet supported |
695 | return (outputLanguage == SH_GLSL_COMPATIBILITY_OUTPUT || |
696 | sh::IsGLSL130OrNewer(outputLanguage)); |
697 | } |
698 | } |
699 | |
700 | const TFunction *EmulatePrecision::getInternalFunction(const ImmutableString &functionName, |
701 | const TType &returnType, |
702 | TIntermSequence *arguments, |
703 | const TVector<const TVariable *> ¶meters, |
704 | bool knownToNotHaveSideEffects) |
705 | { |
706 | ImmutableString mangledName = TFunctionLookup::GetMangledName(functionName.data(), *arguments); |
707 | if (mInternalFunctions.find(mangledName) == mInternalFunctions.end()) |
708 | { |
709 | TFunction *func = new TFunction(mSymbolTable, functionName, SymbolType::AngleInternal, |
710 | new TType(returnType), knownToNotHaveSideEffects); |
711 | ASSERT(parameters.size() == arguments->size()); |
712 | for (size_t i = 0; i < parameters.size(); ++i) |
713 | { |
714 | func->addParameter(parameters[i]); |
715 | } |
716 | mInternalFunctions[mangledName] = func; |
717 | } |
718 | return mInternalFunctions[mangledName]; |
719 | } |
720 | |
721 | TIntermAggregate *EmulatePrecision::createRoundingFunctionCallNode(TIntermTyped *roundedChild) |
722 | { |
723 | const ImmutableString *roundFunctionName = &kAngleFrmString; |
724 | if (roundedChild->getPrecision() == EbpLow) |
725 | roundFunctionName = &kAngleFrlString; |
726 | TIntermSequence *arguments = new TIntermSequence(); |
727 | arguments->push_back(roundedChild); |
728 | |
729 | TVector<const TVariable *> parameters; |
730 | TType *paramType = new TType(roundedChild->getType()); |
731 | paramType->setPrecision(EbpHigh); |
732 | paramType->setQualifier(EvqIn); |
733 | parameters.push_back(new TVariable(mSymbolTable, kParamXName, |
734 | static_cast<const TType *>(paramType), |
735 | SymbolType::AngleInternal)); |
736 | |
737 | return TIntermAggregate::CreateRawFunctionCall( |
738 | *getInternalFunction(*roundFunctionName, roundedChild->getType(), arguments, parameters, |
739 | true), |
740 | arguments); |
741 | } |
742 | |
743 | TIntermAggregate *EmulatePrecision::createCompoundAssignmentFunctionCallNode(TIntermTyped *left, |
744 | TIntermTyped *right, |
745 | const char *opNameStr) |
746 | { |
747 | std::stringstream strstr = sh::InitializeStream<std::stringstream>(); |
748 | if (left->getPrecision() == EbpMedium) |
749 | strstr << "angle_compound_" << opNameStr << "_frm" ; |
750 | else |
751 | strstr << "angle_compound_" << opNameStr << "_frl" ; |
752 | ImmutableString functionName = ImmutableString(strstr.str()); |
753 | TIntermSequence *arguments = new TIntermSequence(); |
754 | arguments->push_back(left); |
755 | arguments->push_back(right); |
756 | |
757 | TVector<const TVariable *> parameters; |
758 | TType *leftParamType = new TType(left->getType()); |
759 | leftParamType->setPrecision(EbpHigh); |
760 | leftParamType->setQualifier(EvqOut); |
761 | parameters.push_back(new TVariable(mSymbolTable, kParamXName, |
762 | static_cast<const TType *>(leftParamType), |
763 | SymbolType::AngleInternal)); |
764 | TType *rightParamType = new TType(right->getType()); |
765 | rightParamType->setPrecision(EbpHigh); |
766 | rightParamType->setQualifier(EvqIn); |
767 | parameters.push_back(new TVariable(mSymbolTable, kParamYName, |
768 | static_cast<const TType *>(rightParamType), |
769 | SymbolType::AngleInternal)); |
770 | |
771 | return TIntermAggregate::CreateRawFunctionCall( |
772 | *getInternalFunction(functionName, left->getType(), arguments, parameters, false), |
773 | arguments); |
774 | } |
775 | |
776 | } // namespace sh |
777 | |