Use our own isinf/isnan over compiler provided ones that are unreliable

* Due to standards nonsense the availability of isinf/isnan in C++ is quite
  complex and varies a lot between compilers. Trying to access them reliably is
  quite brittle and they're easy to implement with bit-inspection of float
  patterns, so we do that instead.
This commit is contained in:
baldurk
2020-07-14 13:24:36 +01:00
parent eabfb9b074
commit ec6627af03
11 changed files with 154 additions and 62 deletions
+92
View File
@@ -165,6 +165,98 @@ T RDCLERP(const T &a, const T &b, const T &step)
return (1.0f - step) * a + step * b;
}
inline bool RDCISNAN(float input)
{
union
{
uint32_t u;
float f;
} x;
x.f = input;
// ignore sign bit (0x80000000)
// check that exponent (0x7f800000) is fully set
// AND that mantissa (0x007fffff) is greater than 0 (if it's 0 then this is an inf)
return (x.u & 0x7fffffffU) > 0x7f800000U;
}
inline bool RDCISINF(float input)
{
union
{
uint32_t u;
float f;
} x;
x.f = input;
// ignore sign bit (0x80000000)
// check that exponent (0x7f800000) is fully set
// AND that mantissa (0x007fffff) is exactly than 0 (if it's non-0 then this is an nan)
return (x.u & 0x7fffffffU) == 0x7f800000U;
}
inline bool RDCISFINITE(float input)
{
union
{
uint32_t u;
float f;
} x;
x.f = input;
// ignore sign bit (0x80000000)
// check that exponent (0x7f800000) is not fully set (if it's fully set then this is a
// nan/inf)
return (x.u & 0x7f800000U) != 0x7f800000U;
}
// double variants
inline bool RDCISNAN(double input)
{
union
{
uint64_t u;
double f;
} x;
x.f = input;
// ignore sign bit (0x80000000)
// check that exponent (0x7f800000) is fully set
// AND that mantissa (0x007fffff) is greater than 0 (if it's 0 then this is an inf)
return (x.u & 0x7fffffffffffffffULL) > 0x7ff0000000000000ULL;
}
inline bool RDCISINF(double input)
{
union
{
uint64_t u;
double f;
} x;
x.f = input;
return (x.u & 0x7fffffffffffffffULL) == 0x7ff0000000000000ULL;
}
inline bool RDCISFINITE(double input)
{
union
{
uint64_t u;
double f;
} x;
x.f = input;
return (x.u & 0x7ff0000000000000ULL) != 0x7ff0000000000000ULL;
}
template <typename T>
inline T AlignUp4(T x)
{
@@ -494,7 +494,7 @@ bool D3D11Replay::RenderTextureInternal(TextureDisplay cfg, TexDisplayFlags flag
pixelData.RangeMinimum = cfg.rangeMin;
pixelData.InverseRangeSize = 1.0f / (cfg.rangeMax - cfg.rangeMin);
if(_isnan(pixelData.InverseRangeSize) || !_finite(pixelData.InverseRangeSize))
if(!RDCISFINITE(pixelData.InverseRangeSize))
{
pixelData.InverseRangeSize = FLT_MAX;
}
+3 -3
View File
@@ -1149,7 +1149,7 @@ bool D3D11DebugAPIWrapper::CalculateSampleGather(
for(uint32_t i = 0; i < ddxCalc.columns; i++)
{
if(_isnan(ddxCalc.value.fv[i]) || !_finite(ddxCalc.value.fv[i]))
if(!RDCISFINITE(ddxCalc.value.fv[i]))
{
RDCWARN("NaN or Inf in texlookup");
ddxCalc.value.fv[i] = 0.0f;
@@ -1160,7 +1160,7 @@ bool D3D11DebugAPIWrapper::CalculateSampleGather(
"texture lookup ddx - using 0.0 instead",
m_instruction, opString));
}
if(_isnan(ddyCalc.value.fv[i]) || !_finite(ddyCalc.value.fv[i]))
if(!RDCISFINITE(ddyCalc.value.fv[i]))
{
RDCWARN("NaN or Inf in texlookup");
ddyCalc.value.fv[i] = 0.0f;
@@ -1175,7 +1175,7 @@ bool D3D11DebugAPIWrapper::CalculateSampleGather(
for(uint32_t i = 0; i < uv.columns; i++)
{
if(texcoordType == 0 && (_isnan(uv.value.fv[i]) || !_finite(uv.value.fv[i])))
if(texcoordType == 0 && (RDCISFINITE(uv.value.fv[i])))
{
RDCWARN("NaN or Inf in texlookup");
uv.value.fv[i] = 0.0f;
@@ -380,7 +380,7 @@ bool D3D12Replay::RenderTextureInternal(D3D12_CPU_DESCRIPTOR_HANDLE rtv, Texture
pixelData.RangeMinimum = cfg.rangeMin;
pixelData.InverseRangeSize = 1.0f / (cfg.rangeMax - cfg.rangeMin);
if(_isnan(pixelData.InverseRangeSize) || !_finite(pixelData.InverseRangeSize))
if(!RDCISFINITE(pixelData.InverseRangeSize))
{
pixelData.InverseRangeSize = FLT_MAX;
}
+3 -3
View File
@@ -1209,7 +1209,7 @@ bool D3D12DebugAPIWrapper::CalculateSampleGather(
for(uint32_t i = 0; i < ddxCalc.columns; i++)
{
if(_isnan(ddxCalc.value.fv[i]) || !_finite(ddxCalc.value.fv[i]))
if(!RDCISFINITE(ddxCalc.value.fv[i]))
{
RDCWARN("NaN or Inf in texlookup");
ddxCalc.value.fv[i] = 0.0f;
@@ -1220,7 +1220,7 @@ bool D3D12DebugAPIWrapper::CalculateSampleGather(
"texture lookup ddx - using 0.0 instead",
m_instruction, opString));
}
if(_isnan(ddyCalc.value.fv[i]) || !_finite(ddyCalc.value.fv[i]))
if(!RDCISFINITE(ddyCalc.value.fv[i]))
{
RDCWARN("NaN or Inf in texlookup");
ddyCalc.value.fv[i] = 0.0f;
@@ -1235,7 +1235,7 @@ bool D3D12DebugAPIWrapper::CalculateSampleGather(
for(uint32_t i = 0; i < uv.columns; i++)
{
if(texcoordType == 0 && (_isnan(uv.value.fv[i]) || !_finite(uv.value.fv[i])))
if(texcoordType == 0 && (!RDCISFINITE(uv.value.fv[i])))
{
RDCWARN("NaN or Inf in texlookup");
uv.value.fv[i] = 0.0f;
+16 -16
View File
@@ -38,7 +38,7 @@ namespace DXBCDebug
{
static float round_ne(float x)
{
if(!_finite(x) || _isnan(x))
if(!RDCISFINITE(x))
return x;
float rem = remainderf(x, 1.0f);
@@ -770,10 +770,10 @@ ShaderVariable TypedUAVLoad(GlobalState::ViewFmt &fmt, const byte *d)
float dxbc_min(float a, float b)
{
if(_isnan(a))
if(RDCISNAN(a))
return b;
if(_isnan(b))
if(RDCISNAN(b))
return a;
return a < b ? a : b;
@@ -781,10 +781,10 @@ float dxbc_min(float a, float b)
double dxbc_min(double a, double b)
{
if(_isnan(a))
if(RDCISNAN(a))
return b;
if(_isnan(b))
if(RDCISNAN(b))
return a;
return a < b ? a : b;
@@ -792,10 +792,10 @@ double dxbc_min(double a, double b)
float dxbc_max(float a, float b)
{
if(_isnan(a))
if(RDCISNAN(a))
return b;
if(_isnan(b))
if(RDCISNAN(b))
return a;
return a >= b ? a : b;
@@ -803,10 +803,10 @@ float dxbc_max(float a, float b)
double dxbc_max(double a, double b)
{
if(_isnan(a))
if(RDCISNAN(a))
return b;
if(_isnan(b))
if(RDCISNAN(b))
return a;
return a >= b ? a : b;
@@ -1143,13 +1143,13 @@ ShaderEvents ThreadState::AssignValue(ShaderVariable &dst, uint32_t dstIndex,
if(src.type == VarType::Float)
{
float ft = src.value.fv[srcIndex];
if(!_finite(ft) || _isnan(ft))
if(!RDCISFINITE(ft))
flags |= ShaderEvents::GeneratedNanOrInf;
}
else if(src.type == VarType::Double)
{
double dt = src.value.dv[srcIndex];
if(!_finite(dt) || _isnan(dt))
if(!RDCISFINITE(dt))
flags |= ShaderEvents::GeneratedNanOrInf;
}
@@ -5548,7 +5548,7 @@ TEST_CASE("DXBC debugging helpers", "[program]")
CHECK(dxbc_min(nan, neginf) == neginf);
CHECK(dxbc_min(nan, a) == a);
CHECK(dxbc_min(nan, posinf) == posinf);
CHECK(_isnan(dxbc_min(nan, nan)));
CHECK(RDCISNAN(dxbc_min(nan, nan)));
};
SECTION("dxbc_max")
@@ -5568,7 +5568,7 @@ TEST_CASE("DXBC debugging helpers", "[program]")
CHECK(dxbc_max(nan, neginf) == neginf);
CHECK(dxbc_max(nan, a) == a);
CHECK(dxbc_max(nan, posinf) == posinf);
CHECK(_isnan(dxbc_max(nan, nan)));
CHECK(RDCISNAN(dxbc_max(nan, nan)));
};
SECTION("sat/abs/neg on NaNs")
@@ -5585,14 +5585,14 @@ TEST_CASE("DXBC debugging helpers", "[program]")
v2 = neg(v, VarType::Float);
CHECK(v2.value.f.x == -b);
CHECK(_isnan(v2.value.f.y));
CHECK(RDCISNAN(v2.value.f.y));
CHECK(v2.value.f.z == posinf);
CHECK(v2.value.f.w == neginf);
v2 = abs(v, VarType::Float);
CHECK(v2.value.f.x == b);
CHECK(_isnan(v2.value.f.y));
CHECK(RDCISNAN(v2.value.f.y));
CHECK(v2.value.f.z == posinf);
CHECK(v2.value.f.w == posinf);
};
@@ -5607,7 +5607,7 @@ TEST_CASE("DXBC debugging helpers", "[program]")
CHECK(flush_denorm(-foo) == -foo);
// check NaN/inf values
CHECK(_isnan(flush_denorm(nan)));
CHECK(RDCISNAN(flush_denorm(nan)));
CHECK(flush_denorm(neginf) == neginf);
CHECK(flush_denorm(posinf) == posinf);
@@ -1726,7 +1726,7 @@ rdcstr Constant::toString(bool withType) const
orig = val.fv[0];
// NaNs/infs are printed as hex to ensure we don't lose bits
if(!isnan(orig) && !isinf(orig))
if(RDCISFINITE(orig))
{
// check we can reparse precisely a float-formatted string. Otherwise we print as hex
rdcstr flt = StringFormat::Fmt("%.6le", orig);
@@ -43,16 +43,16 @@ static bool ContainsNaNInf(const ShaderVariable &val)
{
for(int i = 0; i < count; i++)
{
ret |= isinf(val.value.fv[i]);
ret |= isnan(val.value.fv[i]) != 0;
ret |= RDCISINF(val.value.fv[i]);
ret |= RDCISNAN(val.value.fv[i]) != 0;
}
}
else if(val.type == VarType::Double)
{
for(int i = 0; i < count; i++)
{
ret |= isinf(val.value.dv[i]);
ret |= isnan(val.value.dv[i]) != 0;
ret |= RDCISINF(val.value.dv[i]);
ret |= RDCISNAN(val.value.dv[i]) != 0;
}
}
@@ -1440,7 +1440,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
ShaderVariable var = GetSrc(is.x);
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = isnan(var.value.fv[c]) ? 1 : 0;
var.value.uv[c] = RDCISNAN(var.value.fv[c]) ? 1 : 0;
var.type = VarType::Bool;
@@ -1454,7 +1454,7 @@ void ThreadState::StepNext(ShaderDebugState *state, const rdcarray<ThreadState>
ShaderVariable var = GetSrc(is.x);
for(uint8_t c = 0; c < var.columns; c++)
var.value.uv[c] = isinf(var.value.fv[c]) ? 1 : 0;
var.value.uv[c] = RDCISINF(var.value.fv[c]) ? 1 : 0;
var.type = VarType::Bool;
@@ -49,7 +49,7 @@ ShaderVariable RoundEven(ThreadState &state, uint32_t, const rdcarray<Id> &param
for(uint32_t c = 0; c < var.columns; c++)
{
float x = var.value.fv[c];
if(!isinf(x) && !isnan(x))
if(RDCISFINITE(x))
var.value.fv[c] = x - remainderf(x, 1.0f);
}
@@ -854,8 +854,8 @@ ShaderVariable FindUMsb(ThreadState &state, uint32_t, const rdcarray<Id> &params
static float GLSLNMax(float x, float y)
{
const bool xnan = isnan(x);
const bool ynan = isnan(y);
const bool xnan = RDCISNAN(x);
const bool ynan = RDCISNAN(y);
if(xnan && !ynan)
return y;
else if(!xnan && ynan)
@@ -866,8 +866,8 @@ static float GLSLNMax(float x, float y)
static float GLSLNMin(float x, float y)
{
const bool xnan = isnan(x);
const bool ynan = isnan(y);
const bool xnan = RDCISNAN(x);
const bool ynan = RDCISNAN(y);
if(xnan && !ynan)
return y;
else if(!xnan && ynan)
+25 -25
View File
@@ -1244,11 +1244,11 @@ TEST_CASE("Check format conversion", "[format]")
{
SECTION("Check half conversion is reflexive")
{
CHECK(std::isnan(ConvertFromHalf(ConvertToHalf(NAN))));
CHECK(!std::isnan(ConvertFromHalf(ConvertToHalf(INFINITY))));
CHECK(!std::isnan(ConvertFromHalf(ConvertToHalf(-INFINITY))));
CHECK(!std::isfinite(ConvertFromHalf(ConvertToHalf(INFINITY))));
CHECK(!std::isfinite(ConvertFromHalf(ConvertToHalf(-INFINITY))));
CHECK(RDCISNAN(ConvertFromHalf(ConvertToHalf(NAN))));
CHECK(!RDCISNAN(ConvertFromHalf(ConvertToHalf(INFINITY))));
CHECK(!RDCISNAN(ConvertFromHalf(ConvertToHalf(-INFINITY))));
CHECK(!RDCISFINITE(ConvertFromHalf(ConvertToHalf(INFINITY))));
CHECK(!RDCISFINITE(ConvertFromHalf(ConvertToHalf(-INFINITY))));
for(uint16_t i = 0;; i++)
{
@@ -1257,15 +1257,15 @@ TEST_CASE("Check format conversion", "[format]")
// NaNs and infinites get mapped together. Just check that the value we get out is still a
// nan/inf
if(std::isnan(f))
if(RDCISNAN(f))
{
float f2 = ConvertFromHalf(i2);
CHECK(std::isnan(f2));
CHECK(RDCISNAN(f2));
}
else if(!std::isfinite(f))
else if(!RDCISFINITE(f))
{
float f2 = ConvertFromHalf(i2);
CHECK(!std::isfinite(f2));
CHECK(!RDCISFINITE(f2));
}
else if(i == 0x8000)
{
@@ -1328,15 +1328,15 @@ TEST_CASE("Check format conversion", "[format]")
Vec3f vec = ConvertFromR11G11B10(input);
uint32_t output = ConvertToR11G11B10(vec);
if(std::isnan(vec.x))
if(RDCISNAN(vec.x))
{
Vec3f vec2 = ConvertFromR11G11B10(output);
CHECK(std::isnan(vec2.x));
CHECK(RDCISNAN(vec2.x));
}
else if(!std::isfinite(vec.x))
else if(!RDCISFINITE(vec.x))
{
Vec3f vec2 = ConvertFromR11G11B10(output);
CHECK(!std::isfinite(vec2.x));
CHECK(!RDCISFINITE(vec2.x));
}
else
{
@@ -1351,15 +1351,15 @@ TEST_CASE("Check format conversion", "[format]")
Vec3f vec = ConvertFromR11G11B10(input);
uint32_t output = ConvertToR11G11B10(vec);
if(std::isnan(vec.z))
if(RDCISNAN(vec.z))
{
Vec3f vec2 = ConvertFromR11G11B10(output);
CHECK(std::isnan(vec2.z));
CHECK(RDCISNAN(vec2.z));
}
else if(!std::isfinite(vec.z))
else if(!RDCISFINITE(vec.z))
{
Vec3f vec2 = ConvertFromR11G11B10(output);
CHECK(!std::isfinite(vec2.z));
CHECK(!RDCISFINITE(vec2.z));
}
else
{
@@ -1456,21 +1456,21 @@ TEST_CASE("Check format conversion", "[format]")
Vec3f conv = ConvertFromR11G11B10(test.first);
if(std::isnan(conv.x))
if(RDCISNAN(conv.x))
{
CHECK(std::isnan(test.second.x));
CHECK(RDCISNAN(test.second.x));
}
else if(!std::isfinite(conv.x))
else if(!RDCISFINITE(conv.x))
{
CHECK(!std::isfinite(test.second.x));
CHECK(!RDCISFINITE(test.second.x));
}
else if(std::isnan(conv.z))
else if(RDCISNAN(conv.z))
{
CHECK(std::isnan(test.second.z));
CHECK(RDCISNAN(test.second.z));
}
else if(!std::isfinite(conv.z))
else if(!RDCISFINITE(conv.z))
{
CHECK(!std::isfinite(test.second.z));
CHECK(!RDCISFINITE(test.second.z));
}
else
{
@@ -75,7 +75,7 @@ inline rdcstr ValueStr(SDObject *ptr)
}
else if(ptr->IsFloat())
{
if(isnan(ptr->data.basic.d))
if(RDCISNAN(ptr->data.basic.d))
ptr->data.basic.d = 1.0f;
result = std::to_string(ptr->AsDouble()) + "f";
}