blob: c4659d5145ceda6e68c8bc903d9ee2b9a7e42112 [file] [log] [blame]
Olli Etuaho853dc1a2014-11-06 17:25:48 +02001//
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/EmulatePrecision.h"
8
9namespace
10{
11
12static void writeVectorPrecisionEmulationHelpers(
13 TInfoSinkBase& sink, ShShaderOutput outputLanguage, unsigned int size)
14{
15 std::stringstream vecTypeStrStr;
16 if (outputLanguage == SH_ESSL_OUTPUT)
17 vecTypeStrStr << "highp ";
18 vecTypeStrStr << "vec" << size;
19 std::string vecType = vecTypeStrStr.str();
20
21 sink <<
22 vecType << " angle_frm(in " << vecType << " v) {\n"
23 " v = clamp(v, -65504.0, 65504.0);\n"
24 " " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n"
25 " bvec" << size << " isNonZero = greaterThanEqual(exponent, vec" << size << "(-25.0));\n"
26 " v = v * exp2(-exponent);\n"
27 " v = sign(v) * floor(abs(v));\n"
28 " return v * exp2(exponent) * vec" << size << "(isNonZero);\n"
29 "}\n";
30
31 sink <<
32 vecType << " angle_frl(in " << vecType << " v) {\n"
33 " v = clamp(v, -2.0, 2.0);\n"
34 " v = v * 256.0;\n"
35 " v = sign(v) * floor(abs(v));\n"
36 " return v * 0.00390625;\n"
37 "}\n";
38}
39
40static void writeMatrixPrecisionEmulationHelper(
41 TInfoSinkBase& sink, ShShaderOutput outputLanguage, unsigned int size, const char *functionName)
42{
43 std::stringstream matTypeStrStr;
44 if (outputLanguage == SH_ESSL_OUTPUT)
45 matTypeStrStr << "highp ";
46 matTypeStrStr << "mat" << size;
47 std::string matType = matTypeStrStr.str();
48
49 sink << matType << " " << functionName << "(in " << matType << " m) {\n"
50 " " << matType << " rounded;\n";
51
52 for (unsigned int i = 0; i < size; ++i)
53 {
54 sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n";
55 }
56
57 sink << " return rounded;\n"
58 "}\n";
59}
60
61static void writeCommonPrecisionEmulationHelpers(TInfoSinkBase& sink, ShShaderOutput outputLanguage)
62{
63 // Write the angle_frm functions that round floating point numbers to
64 // half precision, and angle_frl functions that round them to minimum lowp
65 // precision.
66
67 // Unoptimized version of angle_frm for single floats:
68 //
69 // int webgl_maxNormalExponent(in int exponentBits) {
70 // int possibleExponents = int(exp2(float(exponentBits)));
71 // int exponentBias = possibleExponents / 2 - 1;
72 // int allExponentBitsOne = possibleExponents - 1;
73 // return (allExponentBitsOne - 1) - exponentBias;
74 // }
75 //
76 // float angle_frm(in float x) {
77 // int mantissaBits = 10;
78 // int exponentBits = 5;
79 // float possibleMantissas = exp2(float(mantissaBits));
80 // float mantissaMax = 2.0 - 1.0 / possibleMantissas;
81 // int maxNE = webgl_maxNormalExponent(exponentBits);
82 // float max = exp2(float(maxNE)) * mantissaMax;
83 // if (x > max) {
84 // return max;
85 // }
86 // if (x < -max) {
87 // return -max;
88 // }
89 // float exponent = floor(log2(abs(x)));
90 // if (abs(x) == 0.0 || exponent < -float(maxNE)) {
91 // return 0.0 * sign(x)
92 // }
93 // x = x * exp2(-(exponent - float(mantissaBits)));
94 // x = sign(x) * floor(abs(x));
95 // return x * exp2(exponent - float(mantissaBits));
96 // }
97
98 // All numbers with a magnitude less than 2^-15 are subnormal, and are
99 // flushed to zero.
100
101 // Note the constant numbers below:
102 // a) 65504 is the maximum possible mantissa (1.1111111111 in binary) times
103 // 2^15, the maximum normal exponent.
104 // b) 10.0 is the number of mantissa bits.
105 // c) -25.0 is the minimum normal half-float exponent -15.0 minus the number
106 // of mantissa bits.
107 // d) + 1e-30 is to make sure the argument of log2() won't be zero. It can
108 // only affect the result of log2 on x where abs(x) < 1e-22. Since these
109 // numbers will be flushed to zero either way (2^-15 is the smallest
110 // normal positive number), this does not introduce any error.
111
112 std::string floatType = "float";
113 if (outputLanguage == SH_ESSL_OUTPUT)
114 floatType = "highp float";
115
116 sink <<
117 floatType << " angle_frm(in " << floatType << " x) {\n"
118 " x = clamp(x, -65504.0, 65504.0);\n"
119 " " << floatType << " exponent = floor(log2(abs(x) + 1e-30)) - 10.0;\n"
120 " bool isNonZero = (exponent >= -25.0);\n"
121 " x = x * exp2(-exponent);\n"
122 " x = sign(x) * floor(abs(x));\n"
123 " return x * exp2(exponent) * float(isNonZero);\n"
124 "}\n";
125
126 sink <<
127 floatType << " angle_frl(in " << floatType << " x) {\n"
128 " x = clamp(x, -2.0, 2.0);\n"
129 " x = x * 256.0;\n"
130 " x = sign(x) * floor(abs(x));\n"
131 " return x * 0.00390625;\n"
132 "}\n";
133
134 writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 2);
135 writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 3);
136 writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 4);
137 for (unsigned int size = 2; size <= 4; ++size)
138 {
139 writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, "angle_frm");
140 writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, "angle_frl");
141 }
142}
143
144static void writeCompoundAssignmentPrecisionEmulation(
145 TInfoSinkBase& sink, ShShaderOutput outputLanguage,
146 const char *lType, const char *rType, const char *opStr, const char *opNameStr)
147{
148 std::string lTypeStr = lType;
149 std::string rTypeStr = rType;
150 if (outputLanguage == SH_ESSL_OUTPUT)
151 {
152 std::stringstream lTypeStrStr;
153 lTypeStrStr << "highp " << lType;
154 lTypeStr = lTypeStrStr.str();
155 std::stringstream rTypeStrStr;
156 rTypeStrStr << "highp " << rType;
157 rTypeStr = rTypeStrStr.str();
158 }
159
160 // Note that y should be passed through angle_frm at the function call site,
161 // but x can't be passed through angle_frm there since it is an inout parameter.
162 // So only pass x and the result through angle_frm here.
163 sink <<
164 lTypeStr << " angle_compound_" << opNameStr << "_frm(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
165 " x = angle_frm(angle_frm(x) " << opStr << " y);\n"
166 " return x;\n"
167 "}\n";
168 sink <<
169 lTypeStr << " angle_compound_" << opNameStr << "_frl(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
170 " x = angle_frl(angle_frm(x) " << opStr << " y);\n"
171 " return x;\n"
172 "}\n";
173}
174
175const char *getFloatTypeStr(const TType& type)
176{
177 switch (type.getNominalSize())
178 {
179 case 1:
180 return "float";
181 case 2:
Alexis Hetu07e57df2015-06-16 16:55:52 -0400182 switch(type.getSecondarySize())
183 {
184 case 1:
185 return "vec2";
186 case 2:
187 return "mat2";
188 case 3:
189 return "mat2x3";
190 case 4:
191 return "mat2x4";
192 default:
193 UNREACHABLE();
194 return NULL;
195 }
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200196 case 3:
Alexis Hetu07e57df2015-06-16 16:55:52 -0400197 switch(type.getSecondarySize())
198 {
199 case 1:
200 return "vec3";
201 case 2:
202 return "mat3x2";
203 case 3:
204 return "mat3";
205 case 4:
206 return "mat3x4";
207 default:
208 UNREACHABLE();
209 return NULL;
210 }
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200211 case 4:
Alexis Hetu07e57df2015-06-16 16:55:52 -0400212 switch(type.getSecondarySize())
213 {
214 case 1:
215 return "vec4";
216 case 2:
217 return "mat4x2";
218 case 3:
219 return "mat4x3";
220 case 4:
221 return "mat4";
222 default:
223 UNREACHABLE();
224 return NULL;
225 }
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200226 default:
227 UNREACHABLE();
228 return NULL;
229 }
230}
231
232bool canRoundFloat(const TType &type)
233{
234 return type.getBasicType() == EbtFloat && !type.isNonSquareMatrix() && !type.isArray() &&
235 (type.getPrecision() == EbpLow || type.getPrecision() == EbpMedium);
236}
237
238TIntermAggregate *createInternalFunctionCallNode(TString name, TIntermNode *child)
239{
240 TIntermAggregate *callNode = new TIntermAggregate();
241 callNode->setOp(EOpInternalFunctionCall);
242 callNode->setName(name);
243 callNode->getSequence()->push_back(child);
244 return callNode;
245}
246
247TIntermAggregate *createRoundingFunctionCallNode(TIntermTyped *roundedChild)
248{
249 TString roundFunctionName;
250 if (roundedChild->getPrecision() == EbpMedium)
251 roundFunctionName = "angle_frm";
252 else
253 roundFunctionName = "angle_frl";
254 return createInternalFunctionCallNode(roundFunctionName, roundedChild);
255}
256
257TIntermAggregate *createCompoundAssignmentFunctionCallNode(TIntermTyped *left, TIntermTyped *right, const char *opNameStr)
258{
259 std::stringstream strstr;
260 if (left->getPrecision() == EbpMedium)
261 strstr << "angle_compound_" << opNameStr << "_frm";
262 else
263 strstr << "angle_compound_" << opNameStr << "_frl";
264 TString functionName = strstr.str().c_str();
265 TIntermAggregate *callNode = createInternalFunctionCallNode(functionName, left);
266 callNode->getSequence()->push_back(right);
267 return callNode;
268}
269
Olli Etuaho1be88702015-01-19 16:56:44 +0200270bool parentUsesResult(TIntermNode* parent, TIntermNode* node)
271{
272 if (!parent)
273 {
274 return false;
275 }
276
277 TIntermAggregate *aggParent = parent->getAsAggregate();
278 // If the parent's op is EOpSequence, the result is not assigned anywhere,
279 // so rounding it is not needed. In particular, this can avoid a lot of
280 // unnecessary rounding of unused return values of assignment.
281 if (aggParent && aggParent->getOp() == EOpSequence)
282 {
283 return false;
284 }
285 if (aggParent && aggParent->getOp() == EOpComma && (aggParent->getSequence()->back() != node))
286 {
287 return false;
288 }
289 return true;
290}
291
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200292} // namespace anonymous
293
294EmulatePrecision::EmulatePrecision()
295 : TIntermTraverser(true, true, true),
296 mDeclaringVariables(false),
297 mInLValue(false),
298 mInFunctionCallOutParameter(false)
299{}
300
301void EmulatePrecision::visitSymbol(TIntermSymbol *node)
302{
303 if (canRoundFloat(node->getType()) &&
304 !mDeclaringVariables && !mInLValue && !mInFunctionCallOutParameter)
305 {
306 TIntermNode *parent = getParentNode();
307 TIntermNode *replacement = createRoundingFunctionCallNode(node);
308 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
309 }
310}
311
312
313bool EmulatePrecision::visitBinary(Visit visit, TIntermBinary *node)
314{
315 bool visitChildren = true;
316
317 if (node->isAssignment())
318 {
319 if (visit == PreVisit)
320 mInLValue = true;
321 else if (visit == InVisit)
322 mInLValue = false;
323 }
324
325 TOperator op = node->getOp();
326
327 // RHS of initialize is not being declared.
328 if (op == EOpInitialize && visit == InVisit)
329 mDeclaringVariables = false;
330
331 if ((op == EOpIndexDirectStruct || op == EOpVectorSwizzle) && visit == InVisit)
332 visitChildren = false;
333
334 if (visit != PreVisit)
335 return visitChildren;
336
337 const TType& type = node->getType();
338 bool roundFloat = canRoundFloat(type);
339
340 if (roundFloat) {
341 switch (op) {
342 // Math operators that can result in a float may need to apply rounding to the return
343 // value. Note that in the case of assignment, the rounding is applied to its return
344 // value here, not the value being assigned.
345 case EOpAssign:
346 case EOpAdd:
347 case EOpSub:
348 case EOpMul:
349 case EOpDiv:
350 case EOpVectorTimesScalar:
351 case EOpVectorTimesMatrix:
352 case EOpMatrixTimesVector:
353 case EOpMatrixTimesScalar:
354 case EOpMatrixTimesMatrix:
355 {
356 TIntermNode *parent = getParentNode();
Olli Etuaho1be88702015-01-19 16:56:44 +0200357 if (!parentUsesResult(parent, node))
358 {
359 break;
360 }
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200361 TIntermNode *replacement = createRoundingFunctionCallNode(node);
362 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
363 break;
364 }
365
366 // Compound assignment cases need to replace the operator with a function call.
367 case EOpAddAssign:
368 {
369 mEmulateCompoundAdd.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
370 TIntermNode *parent = getParentNode();
371 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "add");
372 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
373 break;
374 }
375 case EOpSubAssign:
376 {
377 mEmulateCompoundSub.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
378 TIntermNode *parent = getParentNode();
379 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "sub");
380 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
381 break;
382 }
383 case EOpMulAssign:
384 case EOpVectorTimesMatrixAssign:
385 case EOpVectorTimesScalarAssign:
386 case EOpMatrixTimesScalarAssign:
387 case EOpMatrixTimesMatrixAssign:
388 {
389 mEmulateCompoundMul.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
390 TIntermNode *parent = getParentNode();
391 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "mul");
392 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
393 break;
394 }
395 case EOpDivAssign:
396 {
397 mEmulateCompoundDiv.insert(TypePair(getFloatTypeStr(type), getFloatTypeStr(node->getRight()->getType())));
398 TIntermNode *parent = getParentNode();
399 TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(node->getLeft(), node->getRight(), "div");
400 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, false));
401 break;
402 }
403 default:
404 // The rest of the binary operations should not need precision emulation.
405 break;
406 }
407 }
408 return visitChildren;
409}
410
411bool EmulatePrecision::visitAggregate(Visit visit, TIntermAggregate *node)
412{
413 bool visitChildren = true;
414 switch (node->getOp())
415 {
416 case EOpSequence:
417 case EOpConstructStruct:
418 // No special handling
419 break;
420 case EOpFunction:
421 if (visit == PreVisit)
422 {
423 const TIntermSequence &sequence = *(node->getSequence());
424 TIntermSequence::const_iterator seqIter = sequence.begin();
425 TIntermAggregate *params = (*seqIter)->getAsAggregate();
426 ASSERT(params != NULL);
427 ASSERT(params->getOp() == EOpParameters);
428 mFunctionMap[node->getName()] = params->getSequence();
429 }
430 break;
431 case EOpPrototype:
432 if (visit == PreVisit)
433 mFunctionMap[node->getName()] = node->getSequence();
434 visitChildren = false;
435 break;
436 case EOpParameters:
437 visitChildren = false;
438 break;
439 case EOpInvariantDeclaration:
440 visitChildren = false;
441 break;
442 case EOpDeclaration:
443 // Variable declaration.
444 if (visit == PreVisit)
445 {
446 mDeclaringVariables = true;
447 }
448 else if (visit == InVisit)
449 {
450 mDeclaringVariables = true;
451 }
452 else
453 {
454 mDeclaringVariables = false;
455 }
456 break;
457 case EOpFunctionCall:
458 {
459 // Function call.
460 bool inFunctionMap = (mFunctionMap.find(node->getName()) != mFunctionMap.end());
461 if (visit == PreVisit)
462 {
Olli Etuaho1be88702015-01-19 16:56:44 +0200463 // User-defined function return values are not rounded, this relies on that
464 // calculations producing the value were rounded.
465 TIntermNode *parent = getParentNode();
466 if (canRoundFloat(node->getType()) && !inFunctionMap && parentUsesResult(parent, node))
467 {
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200468 TIntermNode *replacement = createRoundingFunctionCallNode(node);
469 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
470 }
471
472 if (inFunctionMap)
473 {
474 mSeqIterStack.push_back(mFunctionMap[node->getName()]->begin());
475 if (mSeqIterStack.back() != mFunctionMap[node->getName()]->end())
476 {
477 TQualifier qualifier = (*mSeqIterStack.back())->getAsTyped()->getQualifier();
478 mInFunctionCallOutParameter = (qualifier == EvqOut || qualifier == EvqInOut);
479 }
480 }
481 else
482 {
483 // The function is not user-defined - it is likely built-in texture function.
484 // Assume that those do not have out parameters.
485 mInFunctionCallOutParameter = false;
486 }
487 }
488 else if (visit == InVisit)
489 {
490 if (inFunctionMap)
491 {
492 ++mSeqIterStack.back();
493 TQualifier qualifier = (*mSeqIterStack.back())->getAsTyped()->getQualifier();
494 mInFunctionCallOutParameter = (qualifier == EvqOut || qualifier == EvqInOut);
495 }
496 }
497 else
498 {
499 if (inFunctionMap)
500 {
501 mSeqIterStack.pop_back();
502 mInFunctionCallOutParameter = false;
503 }
504 }
505 break;
506 }
507 default:
Olli Etuaho1be88702015-01-19 16:56:44 +0200508 TIntermNode *parent = getParentNode();
509 if (canRoundFloat(node->getType()) && visit == PreVisit && parentUsesResult(parent, node))
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200510 {
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200511 TIntermNode *replacement = createRoundingFunctionCallNode(node);
512 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
513 }
514 break;
515 }
516 return visitChildren;
517}
518
519bool EmulatePrecision::visitUnary(Visit visit, TIntermUnary *node)
520{
521 switch (node->getOp())
522 {
523 case EOpNegative:
524 case EOpVectorLogicalNot:
525 case EOpLogicalNot:
526 break;
527 case EOpPostIncrement:
528 case EOpPostDecrement:
529 case EOpPreIncrement:
530 case EOpPreDecrement:
531 if (visit == PreVisit)
532 mInLValue = true;
533 else if (visit == PostVisit)
534 mInLValue = false;
535 break;
536 default:
537 if (canRoundFloat(node->getType()) && visit == PreVisit)
538 {
539 TIntermNode *parent = getParentNode();
540 TIntermNode *replacement = createRoundingFunctionCallNode(node);
541 mReplacements.push_back(NodeUpdateEntry(parent, node, replacement, true));
542 }
543 break;
544 }
545
546 return true;
547}
548
549void EmulatePrecision::writeEmulationHelpers(TInfoSinkBase& sink, ShShaderOutput outputLanguage)
550{
551 // Other languages not yet supported
Zhenyao Mo05b6b7f2015-03-02 17:08:09 -0800552 ASSERT(outputLanguage == SH_GLSL_COMPATIBILITY_OUTPUT ||
Qingqing Dengad0d0792015-04-08 14:25:06 -0700553 IsGLSL130OrNewer(outputLanguage) ||
Zhenyao Mo05b6b7f2015-03-02 17:08:09 -0800554 outputLanguage == SH_ESSL_OUTPUT);
Olli Etuaho853dc1a2014-11-06 17:25:48 +0200555 writeCommonPrecisionEmulationHelpers(sink, outputLanguage);
556
557 EmulationSet::const_iterator it;
558 for (it = mEmulateCompoundAdd.begin(); it != mEmulateCompoundAdd.end(); it++)
559 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "+", "add");
560 for (it = mEmulateCompoundSub.begin(); it != mEmulateCompoundSub.end(); it++)
561 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "-", "sub");
562 for (it = mEmulateCompoundDiv.begin(); it != mEmulateCompoundDiv.end(); it++)
563 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "/", "div");
564 for (it = mEmulateCompoundMul.begin(); it != mEmulateCompoundMul.end(); it++)
565 writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "*", "mul");
566}
567