Support precision emulation on HLSL
Re-submit with missing virtual destructor and angle::NonCopyable
added.
Add precision emulation support to HLSL 4.1 output. This makes it
possible for developers to test their shaders for precision issues
easily on Chrome on Windows without having to use the GL backend. The
patch has been verified with Chrome on Windows to reproduce some
precision bugs in real-world WebGL content, including old versions of
the babylon.js library.
The EmulatePrecision AST transformation still relies on writing out
raw shader code for the rounding functions, with raw HLSL code added
alongside pre-existing GLSL and ESSL code. In some ways it would be
nicer to do the EmulatePrecision step as a pure AST transformation,
but on the other hand the raw code is much more readable and easier
to optimize.
To better support multiple output languages in EmulatePrecision, add a
RoundingHelperWriter class that has different subclasses for writing
the rounding functions in different languages.
The unit tests are expanded to cover the HLSL output of precision
emulation. The parts of the tests that require the HLSL output are
only active on Windows where ANGLE_ENABLE_HLSL define is added to the
unit tests. Putting the HLSL tests in an entirely separate file is a
worse alternative, since it would require either a lot of code
duplication or add a lot of boilerplate to the individual tests.
BUG=angleproject:1437
TEST=angle_unittests
Change-Id: I47d501037c206f4bd8b976d3acab9b21c717084c
Reviewed-on: https://chromium-review.googlesource.com/360152
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/EmulatePrecision.cpp b/src/compiler/translator/EmulatePrecision.cpp
index 7b22686..11fa35e 100644
--- a/src/compiler/translator/EmulatePrecision.cpp
+++ b/src/compiler/translator/EmulatePrecision.cpp
@@ -6,99 +6,205 @@
#include "compiler/translator/EmulatePrecision.h"
+#include <memory>
+
namespace
{
-static void writeVectorPrecisionEmulationHelpers(TInfoSinkBase &sink,
- const ShShaderOutput outputLanguage,
- const unsigned int size)
+class RoundingHelperWriter : angle::NonCopyable
{
- std::stringstream vecTypeStrStr;
- if (outputLanguage == SH_ESSL_OUTPUT)
- vecTypeStrStr << "highp ";
- vecTypeStrStr << "vec" << size;
- std::string vecType = vecTypeStrStr.str();
+ public:
+ static RoundingHelperWriter *createHelperWriter(const ShShaderOutput outputLanguage);
- 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";
+ void writeCommonRoundingHelpers(TInfoSinkBase &sink, const int shaderVersion);
+ void writeCompoundAssignmentHelper(TInfoSinkBase &sink,
+ const char *lType,
+ const char *rType,
+ const char *opStr,
+ const char *opNameStr);
- 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";
-}
+ virtual ~RoundingHelperWriter() {}
-static void writeMatrixPrecisionEmulationHelper(TInfoSinkBase &sink,
- const ShShaderOutput outputLanguage,
- const unsigned int columns,
- const unsigned int rows,
- const char *functionName)
+ 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
{
- std::stringstream matTypeStrStr;
- if (outputLanguage == SH_ESSL_OUTPUT)
- matTypeStrStr << "highp ";
- matTypeStrStr << "mat" << columns;
- if (rows != columns)
+ public:
+ RoundingHelperWriterGLSL(const ShShaderOutput outputLanguage)
+ : RoundingHelperWriter(outputLanguage)
{
- matTypeStrStr << "x" << rows;
}
- std::string matType = matTypeStrStr.str();
+ 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;
+};
- sink << matType << " " << functionName << "(in " << matType << " m) {\n"
- " " << matType << " rounded;\n";
-
- for (unsigned int i = 0; i < columns; ++i)
+class RoundingHelperWriterESSL : public RoundingHelperWriterGLSL
+{
+ public:
+ RoundingHelperWriterESSL(const ShShaderOutput outputLanguage)
+ : RoundingHelperWriterGLSL(outputLanguage)
{
- sink << " rounded[" << i << "] = " << functionName << "(m[" << i << "]);\n";
}
- sink << " return rounded;\n"
- "}\n";
+ 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)
+{
+ switch (outputLanguage)
+ {
+ case SH_HLSL_4_1_OUTPUT:
+ return new RoundingHelperWriterHLSL(outputLanguage);
+ case SH_ESSL_OUTPUT:
+ return new RoundingHelperWriterESSL(outputLanguage);
+ default:
+ // Other languages not yet supported
+ ASSERT(outputLanguage == SH_GLSL_COMPATIBILITY_OUTPUT ||
+ IsGLSL130OrNewer(outputLanguage));
+ return new RoundingHelperWriterGLSL(outputLanguage);
+ }
}
-static void writeCommonPrecisionEmulationHelpers(TInfoSinkBase &sink,
- const int shaderVersion,
- const ShShaderOutput 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 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) {
+ // 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) {
+ // if (x > max)
+ // {
// return max;
// }
- // if (x < -max) {
+ // if (x < -max)
+ // {
// return -max;
// }
// float exponent = floor(log2(abs(x)));
- // if (abs(x) == 0.0 || exponent < -float(maxNE)) {
+ // if (abs(x) == 0.0 || exponent < -float(maxNE))
+ // {
// return 0.0 * sign(x)
// }
// x = x * exp2(-(exponent - float(mantissaBits)));
@@ -120,83 +226,198 @@
// 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 = "float";
- if (outputLanguage == SH_ESSL_OUTPUT)
- floatType = "highp float";
+ 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_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";
-
- writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 2);
- writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 3);
- writeVectorPrecisionEmulationHelpers(sink, outputLanguage, 4);
- if (shaderVersion > 100)
- {
- for (unsigned int columns = 2; columns <= 4; ++columns)
- {
- for (unsigned int rows = 2; rows <= 4; ++rows)
- {
- writeMatrixPrecisionEmulationHelper(sink, outputLanguage, columns, rows,
- "angle_frm");
- writeMatrixPrecisionEmulationHelper(sink, outputLanguage, columns, rows,
- "angle_frl");
- }
- }
- }
- else
- {
- for (unsigned int size = 2; size <= 4; ++size)
- {
- writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, size, "angle_frm");
- writeMatrixPrecisionEmulationHelper(sink, outputLanguage, size, size, "angle_frl");
- }
- }
+ 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
}
-static void writeCompoundAssignmentPrecisionEmulation(
- TInfoSinkBase& sink, ShShaderOutput outputLanguage,
- const char *lType, const char *rType, const char *opStr, const char *opNameStr)
+void RoundingHelperWriterGLSL::writeVectorRoundingHelpers(TInfoSinkBase &sink,
+ const unsigned int size)
{
- std::string lTypeStr = lType;
- std::string rTypeStr = rType;
- if (outputLanguage == SH_ESSL_OUTPUT)
+ 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)
{
- std::stringstream lTypeStrStr;
- lTypeStrStr << "highp " << lType;
- lTypeStr = lTypeStrStr.str();
- std::stringstream rTypeStrStr;
- rTypeStrStr << "highp " << rType;
- rTypeStr = rTypeStrStr.str();
+ 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";
}
- // 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.
+ 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 <<
- 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";
+ 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 <<
- 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";
+ 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)
@@ -474,20 +695,19 @@
const int shaderVersion,
const ShShaderOutput outputLanguage)
{
- // Other languages not yet supported
- ASSERT(outputLanguage == SH_GLSL_COMPATIBILITY_OUTPUT ||
- IsGLSL130OrNewer(outputLanguage) ||
- outputLanguage == SH_ESSL_OUTPUT);
- writeCommonPrecisionEmulationHelpers(sink, shaderVersion, 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++)
- writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "+", "add");
+ roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "+", "add");
for (it = mEmulateCompoundSub.begin(); it != mEmulateCompoundSub.end(); it++)
- writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "-", "sub");
+ roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "-", "sub");
for (it = mEmulateCompoundDiv.begin(); it != mEmulateCompoundDiv.end(); it++)
- writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "/", "div");
+ roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "/", "div");
for (it = mEmulateCompoundMul.begin(); it != mEmulateCompoundMul.end(); it++)
- writeCompoundAssignmentPrecisionEmulation(sink, outputLanguage, it->lType, it->rType, "*", "mul");
+ roundingHelperWriter->writeCompoundAssignmentHelper(sink, it->lType, it->rType, "*", "mul");
}