Skip to content

Commit

Permalink
mix: reimplement based on std::lerp reference implementation.
Browse files Browse the repository at this point in the history
The referenced paper explains the problems with various other
implementations of lerp/mix, including ours.
  • Loading branch information
hvdijk committed Nov 17, 2023
1 parent 0f34945 commit 3840346
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,35 @@ template<typename T> T degrees(const T& t) {
return t * T(57.295779513082320876798154814105170332405472466564321);
}

template<typename T, typename U> T mix(const T& x, const T& y, const U& a) {
return x + ((y - x) * T(a));
template <typename T>
T mix(const T &a, const T &b, const T &t) {
// Unoptimized vector version of the implementation of std::lerp given in
// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0811r3.html
// adjusted to support non-finite arguments.
//
// Unlike std::lerp, OpenCL mix returns an undefined result if
// !(t >= 0 && t <= 1), so we could simplify slightly, but we aim to behave
// sensibly for this.

using SignedType = typename TypeTraits<T>::SignedType;

// The direct translation of the expression mix/lerp is defined as. This
// version is used in most cases, but has issues around t=1 and does not
// account for the possibility b-a overflows.
const T x = a + t * (b - a);

// Correct for the issues around t=1 by returning b in edge cases.
const SignedType useb =
__abacus_select(b <= x, x <= b, (t > T(1)) == (b > a)) |
((t == T(1)) & (x == x));

// Alternative definition that avoids overflow of b-a. This version is used
// when a and b are finite numbers with different signs.
const T y = t * b + (T(1) - t) * a;
const SignedType usey = isfinite(a) & isfinite(b) &
(((a <= 0) & (b >= 0)) | ((a >= 0) & (b <= 0)));

return __abacus_select(__abacus_select(x, b, useb), y, usey);
}

template<typename T> T radians(const T& t) {
Expand Down
6 changes: 3 additions & 3 deletions modules/compiler/builtins/abacus/source/abacus_common/mix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@
TYPE __abacus_mix(TYPE x, TYPE y, TYPE a) { \
return abacus::detail::common::mix(x, y, a); \
}
#define DEF2(TYPE, TYPE2) \
TYPE __abacus_mix(TYPE x, TYPE y, TYPE2 a) { \
return abacus::detail::common::mix(x, y, a); \
#define DEF2(TYPE, TYPE2) \
TYPE __abacus_mix(TYPE x, TYPE y, TYPE2 a) { \
return abacus::detail::common::mix<TYPE>(x, y, a); \
}

#ifdef __CA_BUILTINS_HALF_SUPPORT
Expand Down
37 changes: 6 additions & 31 deletions source/cl/test/UnitCL/source/ktst_precision.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1633,24 +1633,12 @@ TEST_P(HalfMathBuiltins, Precision_53_Half_mix) {
GTEST_SKIP();
}

// TODO: CA-2882: Vector widths 2 and 4 don't work
#ifdef __arm__
if ((getParam() == 2) || (getParam() == 4)) {
GTEST_SKIP();
}
#endif

auto mix_ref = [](cl_float x, cl_float y, cl_float a) -> cl_float {
// Our reference for mix is inaccurate due to intermediate rounding, even if
// we use float rather than half, but because we test with MAX_ULP_ERROR,
// this does not matter for the overall test.
cl_float sub = y - x;

// Check for overflow and underflow of intermediate
const cl_half sub_as_half = ConvertFloatToHalf(sub);
if (IsInf(sub_as_half)) {
sub = std::copysign(INFINITY, sub);
} else if (0 == (sub_as_half & ~TypeInfo<cl_half>::sign_bit)) {
sub = std::copysign(0.0f, sub);
}

return x + sub * a;
};

Expand All @@ -1666,25 +1654,12 @@ TEST_P(HalfMathBuiltins, Precision_53_Half_mix_scalar) {
GTEST_SKIP();
}

// TODO: CA-2731: Vector widths 2 and 8 don't work
// TODO: CA-2882: Vector width 4 doesn't work
#ifdef __arm__
if ((getParam() == 2) || (getParam() == 4) || (getParam() == 8)) {
GTEST_SKIP();
}
#endif

auto mix_ref = [](cl_float x, cl_float y, cl_float a) -> cl_float {
// Our reference for mix is inaccurate due to intermediate rounding, even if
// we use float rather than half, but because we test with MAX_ULP_ERROR,
// this does not matter for the overall test.
cl_float sub = y - x;

// Check for overflow and underflow of intermediate
const cl_half sub_as_half = ConvertFloatToHalf(sub);
if (IsInf(sub_as_half)) {
sub = std::copysign(INFINITY, sub);
} else if (0 == (sub_as_half & ~TypeInfo<cl_half>::sign_bit)) {
sub = std::copysign(0.0f, sub);
}

return x + sub * a;
};

Expand Down

0 comments on commit 3840346

Please sign in to comment.