Add simd::if_then_else Elementwise ternary ?: operations on vectors aren't supported until clang 13. This change adds a "simd::if_then_else" method that we will have to use instead, which has a fallback implementation for compilers that don't support the vector ternary. Diffs= 440512dca Add simd::if_then_else (#4403)
diff --git a/.rive_head b/.rive_head index b72dae7..4f308ae 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -ec9fb5bfcc6442c4e674ddac9a7bfad08ef5151a +440512dcaee3a07b7643ce9c00d1ebd477556ef2
diff --git a/include/rive/math/simd.hpp b/include/rive/math/simd.hpp index dfc38fb..3391c11 100644 --- a/include/rive/math/simd.hpp +++ b/include/rive/math/simd.hpp
@@ -24,6 +24,11 @@ static_assert(std::numeric_limits<float>::is_iec559, "Conformant IEEE 754 behavior for NaN and Inf is required."); +// Recommended in https://clang.llvm.org/docs/LanguageExtensions.html#feature-checking-macros +#ifndef __has_builtin +#define __has_builtin(x) 0 +#endif + namespace rive { namespace simd @@ -75,6 +80,24 @@ ////// Math ////// +// Elementwise ternary expression: "_if ? _then : _else" for each component. +template <typename T, int N> +SIMD_ALWAYS_INLINE gvec<T, N> if_then_else(gvec<int32_t, N> _if, gvec<T, N> _then, gvec<T, N> _else) +{ +#if __clang_major__ >= 13 + // The '?:' operator supports a vector condition beginning in clang 13. + return _if ? _then : _else; +#else +#pragma message("performance: vectorized '?:' operator not supported. Consider updating clang.") + gvec<T, N> ret{}; + for (int i = 0; i < N; ++i) + { + ret[i] = _if[i] ? _then[i] : _else[i]; + } + return ret; +#endif +} + // Similar to std::min(), with a noteworthy difference: // If a[i] or b[i] is NaN and the other is not, returns whichever is _not_ NaN. template <typename T, int N> SIMD_ALWAYS_INLINE gvec<T, N> min(gvec<T, N> a, gvec<T, N> b) @@ -84,7 +107,7 @@ #else #pragma message("performance: __builtin_elementwise_min() not supported. Consider updating clang.") // Generate the same behavior for NaN as the SIMD builtins. (isnan() is a no-op for int types.) - return b < a || isnan(a) ? b : a; + return if_then_else(b < a || isnan(a), b, a); #endif } @@ -97,7 +120,7 @@ #else #pragma message("performance: __builtin_elementwise_max() not supported. Consider updating clang.") // Generate the same behavior for NaN as the SIMD builtins. (isnan() is a no-op for int types.) - return a < b || isnan(a) ? b : a; + return if_then_else(a < b || isnan(a), b, a); #endif } @@ -121,7 +144,7 @@ return __builtin_elementwise_abs(x); #else #pragma message("performance: __builtin_elementwise_abs() not supported. Consider updating clang.") - return x < 0 ? -x : x; // But the negation in the "true" side so we never negate NaN. + return if_then_else(x < 0, -x, x); // Do the negation on the "true" side so we never negate NaN. #endif }
diff --git a/test/simd_test.cpp b/test/simd_test.cpp index 20ed98d..7517267 100644 --- a/test/simd_test.cpp +++ b/test/simd_test.cpp
@@ -51,15 +51,15 @@ CHECK(!simd::any(test.zw == test.zw)); // NaN } -// Check that ?: works on vector and scalar conditions. +// Check simd::if_then_else. TEST_CASE("ternary-operator", "[simd]") { // Vector condition. - float4 f4 = int4{1, 2, 3, 4} < int4{4, 3, 2, 1} ? float4(-1) : 1.f; + float4 f4 = simd::if_then_else(int4{1, 2, 3, 4} < int4{4, 3, 2, 1}, float4(-1), float4(1)); CHECK(simd::all(f4 == float4{-1, -1, 1, 1})); // In vector, -1 is true, 0 is false. - uint2 u2 = int2{0, -1} ? uint2{1, 2} : uint2{3, 4}; + uint2 u2 = simd::if_then_else(int2{0, -1}, uint2{1, 2}, uint2{3, 4}); CHECK(simd::all(u2 == uint2{3, 2})); // Scalar condition. @@ -211,7 +211,7 @@ static float frand() { float kMaxBelow1 = math::bit_cast<float>(math::bit_cast<uint32_t>(1.f) - 1); - float f = static_cast<float>(rand()) / RAND_MAX; + float f = static_cast<float>(rand()) / static_cast<float>(RAND_MAX); return std::min(kMaxBelow1, f); } template <int N> vec<N> vrand()