Fix #110169: nearly flat normalized fcurves jump around in editor #110796

Merged
Nathan Vegdahl merged 3 commits from nathanvegdahl/blender:fix_110169_broken_fcurve_normalization into main 2023-08-07 10:53:10 +02:00
4 changed files with 106 additions and 34 deletions

View File

@ -170,6 +170,26 @@ MINLINE size_t clamp_z(size_t value, size_t min, size_t max);
* \param max_diff: the maximum absolute difference.
*/
MINLINE int compare_ff(float a, float b, float max_diff);
/**
* Computes the distance between two floats in ulps.
*
* In other words, returns zero if the floats are exactly equal, and
* otherwise returns 1 plus the number of (unique) representable floats
* between `a` and `b` on the number line.
*
* Notes:
* - The order of `a` and `b` doesn't matter. The returned value is the
* absolute difference.
* - Unlike many ulp difference functions, this function handles the
* difference between positive and negative floats in a meaningful way.
* It returns the number (plus 1) of representable floats between those
* two values as they would be arranged on a number line.
* - Zero and negative zero are *not* considered unique from each other.
* They are counted together as a single float in the difference.
* - NaNs are not handled meaningfully. If either number is NaN, this
* function returns uint max (0xffffffff).
*/
MINLINE uint ulp_diff_ff(float a, float b);
/**
* Almost-equal for IEEE floats, using their integer representation
* (mixing ULP and absolute difference methods).

View File

@ -644,26 +644,48 @@ MINLINE int compare_ff(float a, float b, const float max_diff)
return fabsf(a - b) <= max_diff;
}
MINLINE int compare_ff_relative(float a, float b, const float max_diff, const int max_ulps)
MINLINE uint ulp_diff_ff(float a, float b)
{
BLI_assert(sizeof(float) == sizeof(uint));
const uint sign_bit = 0x80000000;
const uint infinity = 0x7f800000;
union {
float f;
int i;
uint i;
} ua, ub;
ua.f = a;
ub.f = b;
BLI_assert(sizeof(float) == sizeof(int));
BLI_assert(max_ulps < (1 << 22));
const uint a_sign = ua.i & sign_bit;
const uint b_sign = ub.i & sign_bit;
const uint a_abs = ua.i & ~sign_bit;
const uint b_abs = ub.i & ~sign_bit;
if (a_abs > infinity || b_abs > infinity) {
/* NaNs always return maximum ulps apart. */
return 0xffffffff;
}
else if (a_sign == b_sign) {
const uint min_abs = a_abs < b_abs ? a_abs : b_abs;
const uint max_abs = a_abs > b_abs ? a_abs : b_abs;
return max_abs - min_abs;
}
else {
return a_abs + b_abs;
}
}
MINLINE int compare_ff_relative(float a, float b, const float max_diff, const int max_ulps)
{
BLI_assert(max_ulps >= 0 && max_ulps < (1 << 22));
if (fabsf(a - b) <= max_diff) {
return 1;
}
ua.f = a;
ub.f = b;
/* Important to compare sign from integers, since (-0.0f < 0) is false
* (though this shall not be an issue in common cases)... */
return ((ua.i < 0) != (ub.i < 0)) ? 0 : (abs(ua.i - ub.i) <= max_ulps) ? 1 : 0;
return (ulp_diff_ff(a, b) <= (uint)max_ulps) ? 1 : 0;
}
MINLINE bool compare_threshold_relative(const float value1, const float value2, const float thresh)

View File

@ -62,32 +62,57 @@ TEST(math_base, CompareFFRelativeZero)
EXPECT_TRUE(compare_ff_relative(f0, f1, -1.0f, 3));
EXPECT_TRUE(compare_ff_relative(f1, f0, -1.0f, 3));
EXPECT_FALSE(compare_ff_relative(f0, f1, -1.0f, 2));
EXPECT_FALSE(compare_ff_relative(f1, f0, -1.0f, 2));
EXPECT_TRUE(compare_ff_relative(f0, f1, max_diff, 2));
EXPECT_TRUE(compare_ff_relative(f1, f0, max_diff, 2));
EXPECT_FALSE(compare_ff_relative(f0, f1, -1.0f, 1));
EXPECT_FALSE(compare_ff_relative(f1, f0, -1.0f, 1));
EXPECT_TRUE(compare_ff_relative(fn0, f1, -1.0f, 3));
EXPECT_TRUE(compare_ff_relative(f1, fn0, -1.0f, 3));
EXPECT_FALSE(compare_ff_relative(fn0, f1, -1.0f, 2));
EXPECT_FALSE(compare_ff_relative(f1, fn0, -1.0f, 2));
EXPECT_TRUE(compare_ff_relative(fn0, f1, max_diff, 2));
EXPECT_TRUE(compare_ff_relative(f1, fn0, max_diff, 2));
EXPECT_TRUE(compare_ff_relative(fn0, fn1, -1.0f, 8));
EXPECT_TRUE(compare_ff_relative(fn1, fn0, -1.0f, 8));
EXPECT_TRUE(compare_ff_relative(f0, f1, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(f1, f0, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(fn0, f0, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(f0, fn0, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(fn0, fn1, -1.0f, 2));
EXPECT_TRUE(compare_ff_relative(fn1, fn0, -1.0f, 2));
EXPECT_FALSE(compare_ff_relative(fn0, fn1, -1.0f, 1));
EXPECT_FALSE(compare_ff_relative(fn1, fn0, -1.0f, 1));
EXPECT_TRUE(compare_ff_relative(fn0, fn1, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(fn1, fn0, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(f0, fn1, -1.0f, 2));
EXPECT_TRUE(compare_ff_relative(fn1, f0, -1.0f, 2));
EXPECT_FALSE(compare_ff_relative(f0, fn1, -1.0f, 1));
EXPECT_FALSE(compare_ff_relative(fn1, f0, -1.0f, 1));
EXPECT_TRUE(compare_ff_relative(f0, fn1, max_diff, 1));
EXPECT_TRUE(compare_ff_relative(fn1, f0, max_diff, 1));
/* NOTE: in theory, this should return false, since 0.0f and -0.0f have 0x80000000 diff,
* but overflow in subtraction seems to break something here
* (abs(*(int *)&fn0 - *(int *)&f0) == 0x80000000 == fn0), probably because int32 cannot
* hold this abs value. this is yet another illustration of why one shall never use (near-)zero
* floats in pure-ULP comparison. */
// EXPECT_FALSE(compare_ff_relative(fn0, f0, -1.0f, 1024));
// EXPECT_FALSE(compare_ff_relative(f0, fn0, -1.0f, 1024));
EXPECT_TRUE(compare_ff_relative(fn0, f0, -1.0f, 0));
EXPECT_TRUE(compare_ff_relative(f0, fn0, -1.0f, 0));
}
EXPECT_FALSE(compare_ff_relative(fn0, f1, -1.0f, 1024));
EXPECT_FALSE(compare_ff_relative(f1, fn0, -1.0f, 1024));
TEST(math_base, UlpDiffFF)
{
EXPECT_EQ(ulp_diff_ff(0.0, 0.0), 0);
EXPECT_EQ(ulp_diff_ff(0.0, -0.0), 0);
EXPECT_EQ(ulp_diff_ff(-0.0, -0.0), 0);
EXPECT_EQ(ulp_diff_ff(1.0, 1.0), 0);
EXPECT_EQ(ulp_diff_ff(1.0, 2.0), 1 << 23);
EXPECT_EQ(ulp_diff_ff(2.0, 4.0), 1 << 23);
EXPECT_EQ(ulp_diff_ff(-1.0, -2.0), 1 << 23);
EXPECT_EQ(ulp_diff_ff(-2.0, -4.0), 1 << 23);
EXPECT_EQ(ulp_diff_ff(-1.0, 1.0), 0x7f000000);
EXPECT_EQ(ulp_diff_ff(0.0, 1.0), 0x3f800000);
EXPECT_EQ(ulp_diff_ff(-0.0, 1.0), 0x3f800000);
EXPECT_EQ(ulp_diff_ff(0.0, -1.0), 0x3f800000);
EXPECT_EQ(ulp_diff_ff(-0.0, -1.0), 0x3f800000);
EXPECT_EQ(ulp_diff_ff(INFINITY, -INFINITY), 0xff000000);
EXPECT_EQ(ulp_diff_ff(NAN, NAN), 0xffffffff);
EXPECT_EQ(ulp_diff_ff(NAN, 1.0), 0xffffffff);
EXPECT_EQ(ulp_diff_ff(1.0, NAN), 0xffffffff);
EXPECT_EQ(ulp_diff_ff(-NAN, 1.0), 0xffffffff);
EXPECT_EQ(ulp_diff_ff(1.0, -NAN), 0xffffffff);
}
TEST(math_base, Log2FloorU)

View File

@ -504,14 +504,19 @@ static float normalization_factor_get(Scene *scene, FCurve *fcu, short flag, flo
float min_coord = FLT_MAX;
fcurve_scene_coord_range_get(scene, fcu, &min_coord, &max_coord);
if (max_coord > min_coord) {
/* We use an ulps-based floating point comparison here, with the
* rationale that if there are too few possible values between
* `min_coord` and `max_coord`, then after display normalization it
* will certainly be a weird quantized experience for the user anyway.
*/
if (min_coord < max_coord && ulp_diff_ff(min_coord, max_coord) > 256) {
/* Normalize. */
const float range = max_coord - min_coord;
if (range > FLT_EPSILON) {
factor = 2.0f / range;
}
factor = 2.0f / range;
offset = -min_coord - range / 2.0f;
}
else if (max_coord == min_coord) {
else {
/* Skip normalization. */
factor = 1.0f;
offset = -min_coord;
}