// // Copyright 2002 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/tree_ops/EmulatePrecision.h" #include "compiler/translator/FunctionLookup.h" #include namespace sh { namespace { constexpr const ImmutableString kParamXName("x"); constexpr const ImmutableString kParamYName("y"); constexpr const ImmutableString kAngleFrmString("angle_frm"); constexpr const ImmutableString kAngleFrlString("angle_frl"); 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_frl(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 = sh::InitializeStream(); 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 = sh::InitializeStream(); 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 = sh::InitializeStream(); 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 = sh::InitializeStream(); 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 = sh::InitializeStream(); 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); } 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 EmulatePrecision::EmulatePrecision(TSymbolTable *symbolTable) : TLValueTrackingTraverser(true, true, true, symbolTable), 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(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(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(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(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(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(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::visitGlobalQualifierDeclaration(Visit visit, TIntermGlobalQualifierDeclaration *node) { return false; } bool EmulatePrecision::visitAggregate(Visit visit, TIntermAggregate *node) { if (visit != PreVisit) return true; // User-defined function return values are not rounded. The calculations that produced // the value inside the function definition should have been rounded. TOperator op = node->getOp(); if (op == EOpCallInternalRawFunction || op == EOpCallFunctionInAST || (op == EOpConstruct && node->getBasicType() == EbtStruct)) { return true; } TIntermNode *parent = getParentNode(); if (canRoundFloat(node->getType()) && ParentUsesResult(parent, node) && !ParentConstructorTakesCareOfRounding(parent, node)) { TIntermNode *replacement = createRoundingFunctionCallNode(node); queueReplacement(replacement, OriginalNode::BECOMES_CHILD); } 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 EOpNotComponentWise: break; default: if (canRoundFloat(node->getType()) && visit == PreVisit) { TIntermNode *replacement = createRoundingFunctionCallNode(node); queueReplacement(replacement, OriginalNode::BECOMES_CHILD); } break; } return true; } void EmulatePrecision::writeEmulationHelpers(TInfoSinkBase &sink, const int shaderVersion, const ShShaderOutput outputLanguage) { std::unique_ptr 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)); } } const TFunction *EmulatePrecision::getInternalFunction(const ImmutableString &functionName, const TType &returnType, TIntermSequence *arguments, const TVector ¶meters, bool knownToNotHaveSideEffects) { ImmutableString mangledName = TFunctionLookup::GetMangledName(functionName.data(), *arguments); if (mInternalFunctions.find(mangledName) == mInternalFunctions.end()) { TFunction *func = new TFunction(mSymbolTable, functionName, SymbolType::AngleInternal, new TType(returnType), knownToNotHaveSideEffects); ASSERT(parameters.size() == arguments->size()); for (size_t i = 0; i < parameters.size(); ++i) { func->addParameter(parameters[i]); } mInternalFunctions[mangledName] = func; } return mInternalFunctions[mangledName]; } TIntermAggregate *EmulatePrecision::createRoundingFunctionCallNode(TIntermTyped *roundedChild) { const ImmutableString *roundFunctionName = &kAngleFrmString; if (roundedChild->getPrecision() == EbpLow) roundFunctionName = &kAngleFrlString; TIntermSequence arguments; arguments.push_back(roundedChild); TVector parameters; TType *paramType = new TType(roundedChild->getType()); paramType->setPrecision(EbpHigh); paramType->setQualifier(EvqIn); parameters.push_back(new TVariable(mSymbolTable, kParamXName, static_cast(paramType), SymbolType::AngleInternal)); return TIntermAggregate::CreateRawFunctionCall( *getInternalFunction(*roundFunctionName, roundedChild->getType(), &arguments, parameters, true), &arguments); } TIntermAggregate *EmulatePrecision::createCompoundAssignmentFunctionCallNode(TIntermTyped *left, TIntermTyped *right, const char *opNameStr) { std::stringstream strstr = sh::InitializeStream(); if (left->getPrecision() == EbpMedium) strstr << "angle_compound_" << opNameStr << "_frm"; else strstr << "angle_compound_" << opNameStr << "_frl"; ImmutableString functionName = ImmutableString(strstr.str()); TIntermSequence arguments; arguments.push_back(left); arguments.push_back(right); TVector parameters; TType *leftParamType = new TType(left->getType()); leftParamType->setPrecision(EbpHigh); leftParamType->setQualifier(EvqOut); parameters.push_back(new TVariable(mSymbolTable, kParamXName, static_cast(leftParamType), SymbolType::AngleInternal)); TType *rightParamType = new TType(right->getType()); rightParamType->setPrecision(EbpHigh); rightParamType->setQualifier(EvqIn); parameters.push_back(new TVariable(mSymbolTable, kParamYName, static_cast(rightParamType), SymbolType::AngleInternal)); return TIntermAggregate::CreateRawFunctionCall( *getInternalFunction(functionName, left->getType(), &arguments, parameters, false), &arguments); } } // namespace sh