blob: aebcce6ba19c43232fefa89b7e0f4cf01a7cb4bd [file] [log] [blame]
//
// Copyright (c) 2002-2014 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
#include "compiler/translator/EmulatePrecision.h"
#include <memory>
namespace sh
{
namespace
{
class RoundingHelperWriter : angle::NonCopyable
{
public:
static RoundingHelperWriter *createHelperWriter(const ShShaderOutput outputLanguage);
void writeCommonRoundingHelpers(TInfoSinkBase &sink, const int shaderVersion);
void writeCompoundAssignmentHelper(TInfoSinkBase &sink,
const char *lType,
const char *rType,
const char *opStr,
const char *opNameStr);
virtual ~RoundingHelperWriter() {}
protected:
RoundingHelperWriter(const ShShaderOutput outputLanguage) : mOutputLanguage(outputLanguage) {}
RoundingHelperWriter() = delete;
const ShShaderOutput mOutputLanguage;
private:
virtual std::string getTypeString(const char *glslType) = 0;
virtual void writeFloatRoundingHelpers(TInfoSinkBase &sink) = 0;
virtual void writeVectorRoundingHelpers(TInfoSinkBase &sink, const unsigned int size) = 0;
virtual void writeMatrixRoundingHelper(TInfoSinkBase &sink,
const unsigned int columns,
const unsigned int rows,
const char *functionName) = 0;
};
class RoundingHelperWriterGLSL : public RoundingHelperWriter
{
public:
RoundingHelperWriterGLSL(const ShShaderOutput outputLanguage)
: RoundingHelperWriter(outputLanguage)
{
}
private:
std::string getTypeString(const char *glslType) override;
void writeFloatRoundingHelpers(TInfoSinkBase &sink) override;
void writeVectorRoundingHelpers(TInfoSinkBase &sink, const unsigned int size) override;
void writeMatrixRoundingHelper(TInfoSinkBase &sink,
const unsigned int columns,
const unsigned int rows,
const char *functionName) override;
};
class RoundingHelperWriterESSL : public RoundingHelperWriterGLSL
{
public:
RoundingHelperWriterESSL(const ShShaderOutput outputLanguage)
: RoundingHelperWriterGLSL(outputLanguage)
{
}
private:
std::string getTypeString(const char *glslType) override;
};
class RoundingHelperWriterHLSL : public RoundingHelperWriter
{
public:
RoundingHelperWriterHLSL(const ShShaderOutput outputLanguage)
: RoundingHelperWriter(outputLanguage)
{
}
private:
std::string getTypeString(const char *glslType) override;
void writeFloatRoundingHelpers(TInfoSinkBase &sink) override;
void writeVectorRoundingHelpers(TInfoSinkBase &sink, const unsigned int size) override;
void writeMatrixRoundingHelper(TInfoSinkBase &sink,
const unsigned int columns,
const unsigned int rows,
const char *functionName) override;
};
RoundingHelperWriter *RoundingHelperWriter::createHelperWriter(const ShShaderOutput outputLanguage)
{
ASSERT(EmulatePrecision::SupportedInLanguage(outputLanguage));
switch (outputLanguage)
{
case SH_HLSL_4_1_OUTPUT:
return new RoundingHelperWriterHLSL(outputLanguage);
case SH_ESSL_OUTPUT:
return new RoundingHelperWriterESSL(outputLanguage);
default:
return new RoundingHelperWriterGLSL(outputLanguage);
}
}
void RoundingHelperWriter::writeCommonRoundingHelpers(TInfoSinkBase &sink, const int shaderVersion)
{
// Write the angle_frm functions that round floating point numbers to
// half precision, and angle_frl functions that round them to minimum lowp
// precision.
writeFloatRoundingHelpers(sink);
writeVectorRoundingHelpers(sink, 2);
writeVectorRoundingHelpers(sink, 3);
writeVectorRoundingHelpers(sink, 4);
if (shaderVersion > 100)
{
for (unsigned int columns = 2; columns <= 4; ++columns)
{
for (unsigned int rows = 2; rows <= 4; ++rows)
{
writeMatrixRoundingHelper(sink, columns, rows, "angle_frm");
writeMatrixRoundingHelper(sink, columns, rows, "angle_frl");
}
}
}
else
{
for (unsigned int size = 2; size <= 4; ++size)
{
writeMatrixRoundingHelper(sink, size, size, "angle_frm");
writeMatrixRoundingHelper(sink, size, size, "angle_frl");
}
}
}
void RoundingHelperWriter::writeCompoundAssignmentHelper(TInfoSinkBase &sink,
const char *lType,
const char *rType,
const char *opStr,
const char *opNameStr)
{
std::string lTypeStr = getTypeString(lType);
std::string rTypeStr = getTypeString(rType);
// Note that y should be passed through angle_frm at the function call site,
// but x can't be passed through angle_frm there since it is an inout parameter.
// So only pass x and the result through angle_frm here.
// clang-format off
sink <<
lTypeStr << " angle_compound_" << opNameStr << "_frm(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
" x = angle_frm(angle_frm(x) " << opStr << " y);\n"
" return x;\n"
"}\n";
sink <<
lTypeStr << " angle_compound_" << opNameStr << "_frl(inout " << lTypeStr << " x, in " << rTypeStr << " y) {\n"
" x = angle_frl(angle_frm(x) " << opStr << " y);\n"
" return x;\n"
"}\n";
// clang-format on
}
std::string RoundingHelperWriterGLSL::getTypeString(const char *glslType)
{
return glslType;
}
std::string RoundingHelperWriterESSL::getTypeString(const char *glslType)
{
std::stringstream typeStrStr;
typeStrStr << "highp " << glslType;
return typeStrStr.str();
}
void RoundingHelperWriterGLSL::writeFloatRoundingHelpers(TInfoSinkBase &sink)
{
// Unoptimized version of angle_frm for single floats:
//
// int webgl_maxNormalExponent(in int exponentBits)
// {
// int possibleExponents = int(exp2(float(exponentBits)));
// int exponentBias = possibleExponents / 2 - 1;
// int allExponentBitsOne = possibleExponents - 1;
// return (allExponentBitsOne - 1) - exponentBias;
// }
//
// float angle_frm(in float x)
// {
// int mantissaBits = 10;
// int exponentBits = 5;
// float possibleMantissas = exp2(float(mantissaBits));
// float mantissaMax = 2.0 - 1.0 / possibleMantissas;
// int maxNE = webgl_maxNormalExponent(exponentBits);
// float max = exp2(float(maxNE)) * mantissaMax;
// if (x > max)
// {
// return max;
// }
// if (x < -max)
// {
// return -max;
// }
// float exponent = floor(log2(abs(x)));
// if (abs(x) == 0.0 || exponent < -float(maxNE))
// {
// return 0.0 * sign(x)
// }
// x = x * exp2(-(exponent - float(mantissaBits)));
// x = sign(x) * floor(abs(x));
// return x * exp2(exponent - float(mantissaBits));
// }
// All numbers with a magnitude less than 2^-15 are subnormal, and are
// flushed to zero.
// Note the constant numbers below:
// a) 65504 is the maximum possible mantissa (1.1111111111 in binary) times
// 2^15, the maximum normal exponent.
// b) 10.0 is the number of mantissa bits.
// c) -25.0 is the minimum normal half-float exponent -15.0 minus the number
// of mantissa bits.
// d) + 1e-30 is to make sure the argument of log2() won't be zero. It can
// only affect the result of log2 on x where abs(x) < 1e-22. Since these
// numbers will be flushed to zero either way (2^-15 is the smallest
// normal positive number), this does not introduce any error.
std::string floatType = getTypeString("float");
// clang-format off
sink <<
floatType << " angle_frm(in " << floatType << " x) {\n"
" x = clamp(x, -65504.0, 65504.0);\n"
" " << floatType << " exponent = floor(log2(abs(x) + 1e-30)) - 10.0;\n"
" bool isNonZero = (exponent >= -25.0);\n"
" x = x * exp2(-exponent);\n"
" x = sign(x) * floor(abs(x));\n"
" return x * exp2(exponent) * float(isNonZero);\n"
"}\n";
sink <<
floatType << " angle_frl(in " << floatType << " x) {\n"
" x = clamp(x, -2.0, 2.0);\n"
" x = x * 256.0;\n"
" x = sign(x) * floor(abs(x));\n"
" return x * 0.00390625;\n"
"}\n";
// clang-format on
}
void RoundingHelperWriterGLSL::writeVectorRoundingHelpers(TInfoSinkBase &sink,
const unsigned int size)
{
std::stringstream vecTypeStrStr;
vecTypeStrStr << "vec" << size;
std::string vecType = getTypeString(vecTypeStrStr.str().c_str());
// clang-format off
sink <<
vecType << " angle_frm(in " << vecType << " v) {\n"
" v = clamp(v, -65504.0, 65504.0);\n"
" " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n"
" bvec" << size << " isNonZero = greaterThanEqual(exponent, vec" << size << "(-25.0));\n"
" v = v * exp2(-exponent);\n"
" v = sign(v) * floor(abs(v));\n"
" return v * exp2(exponent) * vec" << size << "(isNonZero);\n"
"}\n";
sink <<
vecType << " angle_frl(in " << vecType << " v) {\n"
" v = clamp(v, -2.0, 2.0);\n"
" v = v * 256.0;\n"
" v = sign(v) * floor(abs(v));\n"
" return v * 0.00390625;\n"
"}\n";
// clang-format on
}
void RoundingHelperWriterGLSL::writeMatrixRoundingHelper(TInfoSinkBase &sink,
const unsigned int columns,
const unsigned int rows,
const char *functionName)
{
std::stringstream matTypeStrStr;
matTypeStrStr << "mat" << columns;
if (rows != columns)
{
matTypeStrStr << "x" << rows;
}
std::string matType = getTypeString(matTypeStrStr.str().c_str());
sink << matType << " " << functionName << "(in " << matType << " m) {\n"
<< " " << matType << " rounded;\n";
for (unsigned int i = 0; i < columns; ++i)
{
sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n";
}
sink << " return rounded;\n"
"}\n";
}
static const char *GetHLSLTypeStr(const char *floatTypeStr)
{
if (strcmp(floatTypeStr, "float") == 0)
{
return "float";
}
if (strcmp(floatTypeStr, "vec2") == 0)
{
return "float2";
}
if (strcmp(floatTypeStr, "vec3") == 0)
{
return "float3";
}
if (strcmp(floatTypeStr, "vec4") == 0)
{
return "float4";
}
if (strcmp(floatTypeStr, "mat2") == 0)
{
return "float2x2";
}
if (strcmp(floatTypeStr, "mat3") == 0)
{
return "float3x3";
}
if (strcmp(floatTypeStr, "mat4") == 0)
{
return "float4x4";
}
if (strcmp(floatTypeStr, "mat2x3") == 0)
{
return "float2x3";
}
if (strcmp(floatTypeStr, "mat2x4") == 0)
{
return "float2x4";
}
if (strcmp(floatTypeStr, "mat3x2") == 0)
{
return "float3x2";
}
if (strcmp(floatTypeStr, "mat3x4") == 0)
{
return "float3x4";
}
if (strcmp(floatTypeStr, "mat4x2") == 0)
{
return "float4x2";
}
if (strcmp(floatTypeStr, "mat4x3") == 0)
{
return "float4x3";
}
UNREACHABLE();
return nullptr;
}
std::string RoundingHelperWriterHLSL::getTypeString(const char *glslType)
{
return GetHLSLTypeStr(glslType);
}
void RoundingHelperWriterHLSL::writeFloatRoundingHelpers(TInfoSinkBase &sink)
{
// In HLSL scalars are the same as 1-vectors.
writeVectorRoundingHelpers(sink, 1);
}
void RoundingHelperWriterHLSL::writeVectorRoundingHelpers(TInfoSinkBase &sink,
const unsigned int size)
{
std::stringstream vecTypeStrStr;
vecTypeStrStr << "float" << size;
std::string vecType = vecTypeStrStr.str();
// clang-format off
sink <<
vecType << " angle_frm(" << vecType << " v) {\n"
" v = clamp(v, -65504.0, 65504.0);\n"
" " << vecType << " exponent = floor(log2(abs(v) + 1e-30)) - 10.0;\n"
" bool" << size << " isNonZero = exponent < -25.0;\n"
" v = v * exp2(-exponent);\n"
" v = sign(v) * floor(abs(v));\n"
" return v * exp2(exponent) * (float" << size << ")(isNonZero);\n"
"}\n";
sink <<
vecType << " angle_frl(" << vecType << " v) {\n"
" v = clamp(v, -2.0, 2.0);\n"
" v = v * 256.0;\n"
" v = sign(v) * floor(abs(v));\n"
" return v * 0.00390625;\n"
"}\n";
// clang-format on
}
void RoundingHelperWriterHLSL::writeMatrixRoundingHelper(TInfoSinkBase &sink,
const unsigned int columns,
const unsigned int rows,
const char *functionName)
{
std::stringstream matTypeStrStr;
matTypeStrStr << "float" << columns << "x" << rows;
std::string matType = matTypeStrStr.str();
sink << matType << " " << functionName << "(" << matType << " m) {\n"
<< " " << matType << " rounded;\n";
for (unsigned int i = 0; i < columns; ++i)
{
sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n";
}
sink << " return rounded;\n"
"}\n";
}
bool canRoundFloat(const TType &type)
{
return type.getBasicType() == EbtFloat && !type.isArray() &&
(type.getPrecision() == EbpLow || type.getPrecision() == EbpMedium);
}
TIntermAggregate *createInternalFunctionCallNode(const TType &type,
TString name,
TIntermSequence *arguments)
{
TName nameObj(name);
nameObj.setInternal(true);
TIntermAggregate *callNode =
TIntermAggregate::Create(type, EOpCallInternalRawFunction, arguments);
callNode->getFunctionSymbolInfo()->setNameObj(nameObj);
return callNode;
}
TIntermAggregate *createRoundingFunctionCallNode(TIntermTyped *roundedChild)
{
TString roundFunctionName;
if (roundedChild->getPrecision() == EbpMedium)
roundFunctionName = "angle_frm";
else
roundFunctionName = "angle_frl";
TIntermSequence *arguments = new TIntermSequence();
arguments->push_back(roundedChild);
return createInternalFunctionCallNode(roundedChild->getType(), roundFunctionName, arguments);
}
TIntermAggregate *createCompoundAssignmentFunctionCallNode(TIntermTyped *left,
TIntermTyped *right,
const char *opNameStr)
{
std::stringstream strstr;
if (left->getPrecision() == EbpMedium)
strstr << "angle_compound_" << opNameStr << "_frm";
else
strstr << "angle_compound_" << opNameStr << "_frl";
TString functionName = strstr.str().c_str();
TIntermSequence *arguments = new TIntermSequence();
arguments->push_back(left);
arguments->push_back(right);
return createInternalFunctionCallNode(TType(EbtVoid), functionName, arguments);
}
bool ParentUsesResult(TIntermNode *parent, TIntermTyped *node)
{
if (!parent)
{
return false;
}
TIntermBlock *blockParent = parent->getAsBlock();
// If the parent is a block, the result is not assigned anywhere,
// so rounding it is not needed. In particular, this can avoid a lot of
// unnecessary rounding of unused return values of assignment.
if (blockParent)
{
return false;
}
TIntermBinary *binaryParent = parent->getAsBinaryNode();
if (binaryParent && binaryParent->getOp() == EOpComma && (binaryParent->getRight() != node))
{
return false;
}
return true;
}
bool ParentConstructorTakesCareOfRounding(TIntermNode *parent, TIntermTyped *node)
{
if (!parent)
{
return false;
}
TIntermAggregate *parentConstructor = parent->getAsAggregate();
if (!parentConstructor || parentConstructor->getOp() != EOpConstruct)
{
return false;
}
if (parentConstructor->getPrecision() != node->getPrecision())
{
return false;
}
return canRoundFloat(parentConstructor->getType());
}
} // namespace anonymous
EmulatePrecision::EmulatePrecision(const TSymbolTable &symbolTable, int shaderVersion)
: TLValueTrackingTraverser(true, true, true, symbolTable, shaderVersion),
mDeclaringVariables(false)
{
}
void EmulatePrecision::visitSymbol(TIntermSymbol *node)
{
TIntermNode *parent = getParentNode();
if (canRoundFloat(node->getType()) && ParentUsesResult(parent, node) &&
!ParentConstructorTakesCareOfRounding(parent, node) && !mDeclaringVariables &&
!isLValueRequiredHere())
{
TIntermNode *replacement = createRoundingFunctionCallNode(node);
queueReplacement(node, replacement, OriginalNode::BECOMES_CHILD);
}
}
bool EmulatePrecision::visitBinary(Visit visit, TIntermBinary *node)
{
bool visitChildren = true;
TOperator op = node->getOp();
// RHS of initialize is not being declared.
if (op == EOpInitialize && visit == InVisit)
mDeclaringVariables = false;
if ((op == EOpIndexDirectStruct) && visit == InVisit)
visitChildren = false;
if (visit != PreVisit)
return visitChildren;
const TType &type = node->getType();
bool roundFloat = canRoundFloat(type);
if (roundFloat)
{
switch (op)
{
// Math operators that can result in a float may need to apply rounding to the return
// value. Note that in the case of assignment, the rounding is applied to its return
// value here, not the value being assigned.
case EOpAssign:
case EOpAdd:
case EOpSub:
case EOpMul:
case EOpDiv:
case EOpVectorTimesScalar:
case EOpVectorTimesMatrix:
case EOpMatrixTimesVector:
case EOpMatrixTimesScalar:
case EOpMatrixTimesMatrix:
{
TIntermNode *parent = getParentNode();
if (!ParentUsesResult(parent, node) ||
ParentConstructorTakesCareOfRounding(parent, node))
{
break;
}
TIntermNode *replacement = createRoundingFunctionCallNode(node);
queueReplacement(node, replacement, OriginalNode::BECOMES_CHILD);
break;
}
// Compound assignment cases need to replace the operator with a function call.
case EOpAddAssign:
{
mEmulateCompoundAdd.insert(
TypePair(type.getBuiltInTypeNameString(),
node->getRight()->getType().getBuiltInTypeNameString()));
TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
node->getLeft(), node->getRight(), "add");
queueReplacement(node, replacement, OriginalNode::IS_DROPPED);
break;
}
case EOpSubAssign:
{
mEmulateCompoundSub.insert(
TypePair(type.getBuiltInTypeNameString(),
node->getRight()->getType().getBuiltInTypeNameString()));
TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
node->getLeft(), node->getRight(), "sub");
queueReplacement(node, replacement, OriginalNode::IS_DROPPED);
break;
}
case EOpMulAssign:
case EOpVectorTimesMatrixAssign:
case EOpVectorTimesScalarAssign:
case EOpMatrixTimesScalarAssign:
case EOpMatrixTimesMatrixAssign:
{
mEmulateCompoundMul.insert(
TypePair(type.getBuiltInTypeNameString(),
node->getRight()->getType().getBuiltInTypeNameString()));
TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
node->getLeft(), node->getRight(), "mul");
queueReplacement(node, replacement, OriginalNode::IS_DROPPED);
break;
}
case EOpDivAssign:
{
mEmulateCompoundDiv.insert(
TypePair(type.getBuiltInTypeNameString(),
node->getRight()->getType().getBuiltInTypeNameString()));
TIntermNode *replacement = createCompoundAssignmentFunctionCallNode(
node->getLeft(), node->getRight(), "div");
queueReplacement(node, replacement, OriginalNode::IS_DROPPED);
break;
}
default:
// The rest of the binary operations should not need precision emulation.
break;
}
}
return visitChildren;
}
bool EmulatePrecision::visitDeclaration(Visit visit, TIntermDeclaration *node)
{
// Variable or interface block declaration.
if (visit == PreVisit)
{
mDeclaringVariables = true;
}
else if (visit == InVisit)
{
mDeclaringVariables = true;
}
else
{
mDeclaringVariables = false;
}
return true;
}
bool EmulatePrecision::visitInvariantDeclaration(Visit visit, TIntermInvariantDeclaration *node)
{
return false;
}
bool EmulatePrecision::visitFunctionPrototype(Visit visit, TIntermFunctionPrototype *node)
{
return false;
}
bool EmulatePrecision::visitAggregate(Visit visit, TIntermAggregate *node)
{
if (visit != PreVisit)
return true;
switch (node->getOp())
{
case EOpCallInternalRawFunction:
case EOpCallFunctionInAST:
// User-defined function return values are not rounded. The calculations that produced
// the value inside the function definition should have been rounded.
break;
case EOpConstruct:
if (node->getBasicType() == EbtStruct)
{
break;
}
default:
TIntermNode *parent = getParentNode();
if (canRoundFloat(node->getType()) && ParentUsesResult(parent, node) &&
!ParentConstructorTakesCareOfRounding(parent, node))
{
TIntermNode *replacement = createRoundingFunctionCallNode(node);
queueReplacement(node, replacement, OriginalNode::BECOMES_CHILD);
}
break;
}
return true;
}
bool EmulatePrecision::visitUnary(Visit visit, TIntermUnary *node)
{
switch (node->getOp())
{
case EOpNegative:
case EOpLogicalNot:
case EOpPostIncrement:
case EOpPostDecrement:
case EOpPreIncrement:
case EOpPreDecrement:
case EOpLogicalNotComponentWise:
break;
default:
if (canRoundFloat(node->getType()) && visit == PreVisit)
{
TIntermNode *replacement = createRoundingFunctionCallNode(node);
queueReplacement(node, replacement, OriginalNode::BECOMES_CHILD);
}
break;
}
return true;
}
void EmulatePrecision::writeEmulationHelpers(TInfoSinkBase &sink,
const int shaderVersion,
const ShShaderOutput outputLanguage)
{
std::unique_ptr<RoundingHelperWriter> roundingHelperWriter(
RoundingHelperWriter::createHelperWriter(outputLanguage));
roundingHelperWriter->writeCommonRoundingHelpers(sink, shaderVersion);
EmulationSet::const_iterator it;
for (it = mEmulateCompoundAdd.begin(); it != mEmulateCompoundAdd.end(); it++)
roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "+", "add");
for (it = mEmulateCompoundSub.begin(); it != mEmulateCompoundSub.end(); it++)
roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "-", "sub");
for (it = mEmulateCompoundDiv.begin(); it != mEmulateCompoundDiv.end(); it++)
roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "/", "div");
for (it = mEmulateCompoundMul.begin(); it != mEmulateCompoundMul.end(); it++)
roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "*", "mul");
}
// static
bool EmulatePrecision::SupportedInLanguage(const ShShaderOutput outputLanguage)
{
switch (outputLanguage)
{
case SH_HLSL_4_1_OUTPUT:
case SH_ESSL_OUTPUT:
return true;
default:
// Other languages not yet supported
return (outputLanguage == SH_GLSL_COMPATIBILITY_OUTPUT ||
sh::IsGLSL130OrNewer(outputLanguage));
}
}
} // namespace sh