aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/core/SkNx.h
diff options
context:
space:
mode:
authorGravatar mtklein <mtklein@chromium.org>2016-03-21 10:04:46 -0700
committerGravatar Commit bot <commit-bot@chromium.org>2016-03-21 10:04:46 -0700
commitf8f90e4a85638faa18e7b4133cfe4d1ff5b1b23e (patch)
treef9288bc101e5c87ac35a29d595ba09a750297c8e /src/core/SkNx.h
parent04cdc4b61879849df63e883e68eecafe6510f423 (diff)
SkNx refresh
- rearrange a bit - fewer macros - hooks for all operators - add left and right scalar operator overrides - add +=, &=, <<=, etc. - add SkNx_split() and SkNx_join() - simplify the many rsqrt() and invert() options to just what we actually use This refactoring pointed out that our float <-> int NEON conversions are not specialized, so I've implemented them. It seems nice that this is an error rather than silently falling back to serial code. It's unclear to me if split/join want to be external, static methods, or non-static methods (SkNx_join(), Sk4f::Join(), x.join()). Time will tell? BUG=skia: GOLD_TRYBOT_URL= https://gold.skia.org/search2?unt=true&query=source_type%3Dgm&master=false&issue=1812233003 CQ_EXTRA_TRYBOTS=client.skia.android:Test-Android-GCC-Nexus5-CPU-NEON-Arm7-Release-Trybot;client.skia:Test-Ubuntu-GCC-GCE-CPU-AVX2-x86_64-Release-SKNX_NO_SIMD-Trybot Review URL: https://codereview.chromium.org/1812233003
Diffstat (limited to 'src/core/SkNx.h')
-rw-r--r--src/core/SkNx.h372
1 files changed, 233 insertions, 139 deletions
diff --git a/src/core/SkNx.h b/src/core/SkNx.h
index 4ad119a5da..d0c7f41f1c 100644
--- a/src/core/SkNx.h
+++ b/src/core/SkNx.h
@@ -12,61 +12,32 @@
#include "SkScalar.h"
#include "SkTypes.h"
+#include <limits>
#include <math.h>
+#include <type_traits>
-// The default implementations just fall back on a pair of size N/2.
-// These support the union of operations we might do to ints and floats, but
-// platform specializations might support fewer (e.g. no float <<, no int /).
-template <int N, typename T>
-class SkNx {
-public:
- SkNx() {}
- SkNx(T val) : fLo(val), fHi(val) {}
+#define SI static inline
+// The default SkNx<N,T> just proxies down to a pair of SkNx<N/2, T>.
+template <int N, typename T>
+struct SkNx {
typedef SkNx<N/2, T> Half;
- SkNx(const Half& lo, const Half& hi) : fLo(lo), fHi(hi) {}
-
- SkNx(T a, T b) : fLo(a), fHi(b) {}
- SkNx(T a, T b, T c, T d) : fLo(a,b), fHi(c,d) {}
- SkNx(T a, T b, T c, T d, T e, T f, T g, T h) : fLo(a,b,c,d), fHi(e,f,g,h) {}
- SkNx(T a, T b, T c, T d, T e, T f, T g, T h,
- T i, T j, T k, T l, T m, T n, T o, T p) : fLo(a,b,c,d, e,f,g,h), fHi(i,j,k,l, m,n,o,p) {}
-
- static SkNx Load(const void* ptr) {
- auto vals = (const T*)ptr;
- return SkNx(Half::Load(vals), Half::Load(vals+N/2));
- }
- void store(void* ptr) const {
- auto vals = (T*)ptr;
- fLo.store(vals);
- fHi.store(vals+N/2);
- }
-
-#define OP(op) SkNx operator op(const SkNx& o) const { return {fLo op o.fLo, fHi op o.fHi}; }
- OP(+) OP(-) OP(*) OP(/)
- OP(&) OP(|) OP(^)
- OP(==) OP(!=) OP(<) OP(>) OP(<=) OP(>=)
-#undef OP
-
-#define OP(op) SkNx op() const { return {fLo.op(), fHi.op()}; }
- OP(abs) OP(floor)
- OP(sqrt) OP(rsqrt0) OP(rsqrt1) OP(rsqrt2)
- OP(invert) OP(approxInvert)
-#undef OP
+ Half fLo, fHi;
- SkNx operator << (int bits) const { return SkNx(fLo << bits, fHi << bits); }
- SkNx operator >> (int bits) const { return SkNx(fLo >> bits, fHi >> bits); }
+ SkNx() = default;
+ SkNx(const Half& lo, const Half& hi) : fLo(lo), fHi(hi) {}
- SkNx saturatedAdd(const SkNx& o) const {
- return {fLo.saturatedAdd(o.fLo), fHi.saturatedAdd(o.fHi)};
- }
+ SkNx(T v) : fLo(v), fHi(v) {}
- static SkNx Min(const SkNx& a, const SkNx& b) {
- return {Half::Min(a.fLo, b.fLo), Half::Min(a.fHi, b.fHi)};
+ SkNx(T a, T b) : fLo(a) , fHi(b) { static_assert(N==2, ""); }
+ SkNx(T a, T b, T c, T d) : fLo(a,b), fHi(c,d) { static_assert(N==4, ""); }
+ SkNx(T a, T b, T c, T d, T e, T f, T g, T h) : fLo(a,b,c,d), fHi(e,f,g,h) {
+ static_assert(N==8, "");
}
- static SkNx Max(const SkNx& a, const SkNx& b) {
- return {Half::Max(a.fLo, b.fLo), Half::Max(a.fHi, b.fHi)};
+ SkNx(T a, T b, T c, T d, T e, T f, T g, T h,
+ T i, T j, T k, T l, T m, T n, T o, T p) : fLo(a,b,c,d, e,f,g,h), fHi(i,j,k,l, m,n,o,p) {
+ static_assert(N==16, "");
}
T operator[](int k) const {
@@ -74,143 +45,266 @@ public:
return k < N/2 ? fLo[k] : fHi[k-N/2];
}
- bool allTrue() const { return fLo.allTrue() && fHi.allTrue(); }
+ static SkNx Load(const void* vptr) {
+ auto ptr = (const char*)vptr;
+ return { Half::Load(ptr), Half::Load(ptr + N/2*sizeof(T)) };
+ }
+ void store(void* vptr) const {
+ auto ptr = (char*)vptr;
+ fLo.store(ptr);
+ fHi.store(ptr + N/2*sizeof(T));
+ }
+
bool anyTrue() const { return fLo.anyTrue() || fHi.anyTrue(); }
+ bool allTrue() const { return fLo.allTrue() && fHi.allTrue(); }
+
+ SkNx abs() const { return { fLo. abs(), fHi. abs() }; }
+ SkNx sqrt() const { return { fLo. sqrt(), fHi. sqrt() }; }
+ SkNx rsqrt() const { return { fLo. rsqrt(), fHi. rsqrt() }; }
+ SkNx floor() const { return { fLo. floor(), fHi. floor() }; }
+ SkNx invert() const { return { fLo.invert(), fHi.invert() }; }
+
+ SkNx operator!() const { return { !fLo, !fHi }; }
+ SkNx operator-() const { return { -fLo, -fHi }; }
+ SkNx operator~() const { return { ~fLo, ~fHi }; }
+
+ SkNx operator<<(int bits) const { return { fLo << bits, fHi << bits }; }
+ SkNx operator>>(int bits) const { return { fLo >> bits, fHi >> bits }; }
+
+ SkNx operator+(const SkNx& y) const { return { fLo + y.fLo, fHi + y.fHi }; }
+ SkNx operator-(const SkNx& y) const { return { fLo - y.fLo, fHi - y.fHi }; }
+ SkNx operator*(const SkNx& y) const { return { fLo * y.fLo, fHi * y.fHi }; }
+ SkNx operator/(const SkNx& y) const { return { fLo / y.fLo, fHi / y.fHi }; }
+
+ SkNx operator&(const SkNx& y) const { return { fLo & y.fLo, fHi & y.fHi }; }
+ SkNx operator|(const SkNx& y) const { return { fLo | y.fLo, fHi | y.fHi }; }
+ SkNx operator^(const SkNx& y) const { return { fLo ^ y.fLo, fHi ^ y.fHi }; }
+
+ SkNx operator==(const SkNx& y) const { return { fLo == y.fLo, fHi == y.fHi }; }
+ SkNx operator!=(const SkNx& y) const { return { fLo != y.fLo, fHi != y.fHi }; }
+ SkNx operator<=(const SkNx& y) const { return { fLo <= y.fLo, fHi <= y.fHi }; }
+ SkNx operator>=(const SkNx& y) const { return { fLo >= y.fLo, fHi >= y.fHi }; }
+ SkNx operator< (const SkNx& y) const { return { fLo < y.fLo, fHi < y.fHi }; }
+ SkNx operator> (const SkNx& y) const { return { fLo > y.fLo, fHi > y.fHi }; }
+
+ SkNx saturatedAdd(const SkNx& y) const {
+ return { fLo.saturatedAdd(y.fLo), fHi.saturatedAdd(y.fHi) };
+ }
SkNx thenElse(const SkNx& t, const SkNx& e) const {
- return SkNx(fLo.thenElse(t.fLo, e.fLo), fHi.thenElse(t.fHi, e.fHi));
+ return { fLo.thenElse(t.fLo, e.fLo), fHi.thenElse(t.fHi, e.fHi) };
}
-protected:
- static_assert(0 == (N & (N-1)), "N must be a power of 2.");
-
- Half fLo, fHi;
+ static SkNx Min(const SkNx& x, const SkNx& y) {
+ return { Half::Min(x.fLo, y.fLo), Half::Min(x.fHi, y.fHi) };
+ }
+ static SkNx Max(const SkNx& x, const SkNx& y) {
+ return { Half::Max(x.fLo, y.fLo), Half::Max(x.fHi, y.fHi) };
+ }
};
-// Bottom out the default implementations with scalars when nothing's been specialized.
+// The N -> N/2 recursion bottoms out at N == 1, a scalar value.
template <typename T>
-class SkNx<1, T> {
-public:
- SkNx() {}
- SkNx(T val) : fVal(val) {}
+struct SkNx<1,T> {
+ T fVal;
- static SkNx Load(const void* ptr) {
- auto vals = (const T*)ptr;
- return SkNx(vals[0]);
- }
+ SkNx() = default;
+ SkNx(T v) : fVal(v) {}
- void store(void* ptr) const {
- auto vals = (T*) ptr;
- vals[0] = fVal;
+ T operator[](int k) const {
+ SkASSERT(k == 0);
+ return fVal;
}
-#define OP(op) SkNx operator op(const SkNx& o) const { return fVal op o.fVal; }
- OP(+) OP(-) OP(*) OP(/)
- OP(&) OP(|) OP(^)
- OP(==) OP(!=) OP(<) OP(>) OP(<=) OP(>=)
-#undef OP
-
- SkNx operator << (int bits) const { return fVal << bits; }
- SkNx operator >> (int bits) const { return fVal >> bits; }
-
- SkNx saturatedAdd(const SkNx& o) const {
- SkASSERT((T)(~0) > 0); // TODO: support signed T?
- T sum = fVal + o.fVal;
- return sum < fVal ? (T)(~0) : sum;
+ static SkNx Load(const void* ptr) {
+ SkNx v;
+ memcpy(&v, ptr, sizeof(T));
+ return v;
}
+ void store(void* ptr) const { memcpy(ptr, &fVal, sizeof(T)); }
- static SkNx Min(const SkNx& a, const SkNx& b) { return SkTMin(a.fVal, b.fVal); }
- static SkNx Max(const SkNx& a, const SkNx& b) { return SkTMax(a.fVal, b.fVal); }
-
- SkNx abs() const { return SkTAbs(fVal); }
- SkNx floor() const { return Floor(fVal); }
-
- SkNx sqrt () const { return Sqrt(fVal); }
- SkNx rsqrt0() const { return this->sqrt().invert(); }
- SkNx rsqrt1() const { return this->rsqrt0(); }
- SkNx rsqrt2() const { return this->rsqrt1(); }
-
- SkNx invert() const { return 1 / fVal; }
- SkNx approxInvert() const { return this->invert(); }
+ bool anyTrue() const { return fVal != 0; }
+ bool allTrue() const { return fVal != 0; }
- T operator[](int k) const {
- SkASSERT(0 == k);
- return fVal;
+ SkNx abs() const { return Abs(fVal); }
+ SkNx sqrt() const { return Sqrt(fVal); }
+ SkNx rsqrt() const { return T(1) / this->sqrt(); }
+ SkNx floor() const { return Floor(fVal); }
+ SkNx invert() const { return T(1) / *this; }
+
+ SkNx operator!() const { return !fVal; }
+ SkNx operator-() const { return -fVal; }
+ SkNx operator~() const { return FromBits(~ToBits(fVal)); }
+
+ SkNx operator<<(int bits) const { return fVal << bits; }
+ SkNx operator>>(int bits) const { return fVal >> bits; }
+
+ SkNx operator+(const SkNx& y) const { return fVal + y.fVal; }
+ SkNx operator-(const SkNx& y) const { return fVal - y.fVal; }
+ SkNx operator*(const SkNx& y) const { return fVal * y.fVal; }
+ SkNx operator/(const SkNx& y) const { return fVal / y.fVal; }
+
+ SkNx operator&(const SkNx& y) const { return FromBits(ToBits(fVal) & ToBits(y.fVal)); }
+ SkNx operator|(const SkNx& y) const { return FromBits(ToBits(fVal) | ToBits(y.fVal)); }
+ SkNx operator^(const SkNx& y) const { return FromBits(ToBits(fVal) ^ ToBits(y.fVal)); }
+
+ SkNx operator==(const SkNx& y) const { return FromBits(fVal == y.fVal ? ~0 : 0); }
+ SkNx operator!=(const SkNx& y) const { return FromBits(fVal != y.fVal ? ~0 : 0); }
+ SkNx operator<=(const SkNx& y) const { return FromBits(fVal <= y.fVal ? ~0 : 0); }
+ SkNx operator>=(const SkNx& y) const { return FromBits(fVal >= y.fVal ? ~0 : 0); }
+ SkNx operator< (const SkNx& y) const { return FromBits(fVal < y.fVal ? ~0 : 0); }
+ SkNx operator> (const SkNx& y) const { return FromBits(fVal > y.fVal ? ~0 : 0); }
+
+ static SkNx Min(const SkNx& x, const SkNx& y) { return x.fVal < y.fVal ? x : y; }
+ static SkNx Max(const SkNx& x, const SkNx& y) { return x.fVal > y.fVal ? x : y; }
+
+ SkNx saturatedAdd(const SkNx& y) const {
+ static_assert(std::is_unsigned<T>::value, "");
+ T sum = fVal + y.fVal;
+ return sum < fVal ? std::numeric_limits<T>::max() : sum;
}
- bool allTrue() const { return fVal != 0; }
- bool anyTrue() const { return fVal != 0; }
SkNx thenElse(const SkNx& t, const SkNx& e) const { return fVal != 0 ? t : e; }
-protected:
- static double Floor(double val) { return ::floor (val); }
- static float Floor(float val) { return ::floorf(val); }
- static double Sqrt(double val) { return ::sqrt (val); }
- static float Sqrt(float val) { return ::sqrtf(val); }
-
- T fVal;
+private:
+ // Helper functions to choose the right float/double methods. (In <cmath> madness lies...)
+ static float Abs(float val) { return ::fabsf(val); }
+ static float Sqrt(float val) { return ::sqrtf(val); }
+ static float Floor(float val) { return ::floorf(val); }
+
+ static double Abs(double val) { return ::fabs(val); }
+ static double Sqrt(double val) { return ::sqrt(val); }
+ static double Floor(double val) { return ::floor(val); }
+
+ // Helper functions for working with floats/doubles as bit patterns.
+ template <typename U> static U ToBits(U v) { return v; }
+ static int32_t ToBits(float v) { int32_t bits; memcpy(&bits, &v, sizeof(v)); return bits; }
+ static int64_t ToBits(double v) { int64_t bits; memcpy(&bits, &v, sizeof(v)); return bits; }
+
+ template <typename Bits> static T FromBits(Bits bits) {
+ static_assert(std::is_pod<T >::value &&
+ std::is_pod<Bits>::value &&
+ sizeof(T) <= sizeof(Bits), "");
+ T val;
+ memcpy(&val, &bits, sizeof(T));
+ return val;
+ }
};
-// This generic shuffle can be called to create any valid SkNx<N,T>.
-// Sk4f f(a,b,c,d);
-// Sk2f t = SkNx_shuffle<2,1>(f); // ~~~> Sk2f(c,b)
-// f = SkNx_shuffle<0,1,1,0>(t); // ~~~> Sk4f(c,b,b,c)
-template <int... Ix, int N, typename T>
-static inline SkNx<sizeof...(Ix), T> SkNx_shuffle(const SkNx<N,T>& src) { return { src[Ix]... }; }
-
-// This is a generic cast between two SkNx with the same number of elements N. E.g.
-// Sk4b bs = ...; // Load 4 bytes.
-// Sk4f fs = SkNx_cast<float>(bs); // Cast each byte to a float.
-// Sk4h hs = SkNx_cast<uint16_t>(fs); // Cast each float to uint16_t.
-template <typename D, typename S>
-static inline SkNx<2,D> SkNx_cast(const SkNx<2,S>& src) {
- return { (D)src[0], (D)src[1] };
+// Allow scalars on the left or right of binary operators, and things like +=, &=, etc.
+#define V template <int N, typename T> SI SkNx<N,T>
+ V operator+ (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) + y; }
+ V operator- (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) - y; }
+ V operator* (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) * y; }
+ V operator/ (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) / y; }
+ V operator& (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) & y; }
+ V operator| (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) | y; }
+ V operator^ (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) ^ y; }
+ V operator==(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) == y; }
+ V operator!=(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) != y; }
+ V operator<=(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) <= y; }
+ V operator>=(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) >= y; }
+ V operator< (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) < y; }
+ V operator> (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) > y; }
+
+ V operator+ (const SkNx<N,T>& x, T y) { return x + SkNx<N,T>(y); }
+ V operator- (const SkNx<N,T>& x, T y) { return x - SkNx<N,T>(y); }
+ V operator* (const SkNx<N,T>& x, T y) { return x * SkNx<N,T>(y); }
+ V operator/ (const SkNx<N,T>& x, T y) { return x / SkNx<N,T>(y); }
+ V operator& (const SkNx<N,T>& x, T y) { return x & SkNx<N,T>(y); }
+ V operator| (const SkNx<N,T>& x, T y) { return x | SkNx<N,T>(y); }
+ V operator^ (const SkNx<N,T>& x, T y) { return x ^ SkNx<N,T>(y); }
+ V operator==(const SkNx<N,T>& x, T y) { return x == SkNx<N,T>(y); }
+ V operator!=(const SkNx<N,T>& x, T y) { return x != SkNx<N,T>(y); }
+ V operator<=(const SkNx<N,T>& x, T y) { return x <= SkNx<N,T>(y); }
+ V operator>=(const SkNx<N,T>& x, T y) { return x >= SkNx<N,T>(y); }
+ V operator< (const SkNx<N,T>& x, T y) { return x < SkNx<N,T>(y); }
+ V operator> (const SkNx<N,T>& x, T y) { return x > SkNx<N,T>(y); }
+
+ V& operator<<=(SkNx<N,T>& x, int bits) { return (x = x << bits); }
+ V& operator>>=(SkNx<N,T>& x, int bits) { return (x = x >> bits); }
+
+ V& operator +=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x + y); }
+ V& operator -=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x - y); }
+ V& operator *=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x * y); }
+ V& operator /=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x / y); }
+ V& operator &=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x & y); }
+ V& operator |=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x | y); }
+ V& operator ^=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x ^ y); }
+
+ V& operator +=(SkNx<N,T>& x, T y) { return (x = x + SkNx<N,T>(y)); }
+ V& operator -=(SkNx<N,T>& x, T y) { return (x = x - SkNx<N,T>(y)); }
+ V& operator *=(SkNx<N,T>& x, T y) { return (x = x * SkNx<N,T>(y)); }
+ V& operator /=(SkNx<N,T>& x, T y) { return (x = x / SkNx<N,T>(y)); }
+ V& operator &=(SkNx<N,T>& x, T y) { return (x = x & SkNx<N,T>(y)); }
+ V& operator |=(SkNx<N,T>& x, T y) { return (x = x | SkNx<N,T>(y)); }
+ V& operator ^=(SkNx<N,T>& x, T y) { return (x = x ^ SkNx<N,T>(y)); }
+#undef V
+
+// SkNx<N,T> ~~> SkNx<N/2,T> + SkNx<N/2,T>
+template <int N, typename T>
+SI void SkNx_split(const SkNx<N,T>& v, SkNx<N/2,T>* lo, SkNx<N/2,T>* hi) {
+ *lo = v.fLo;
+ *hi = v.fHi;
}
-template <typename D, typename S>
-static inline SkNx<4,D> SkNx_cast(const SkNx<4,S>& src) {
- return { (D)src[0], (D)src[1], (D)src[2], (D)src[3] };
+// SkNx<N/2,T> + SkNx<N/2,T> ~~> SkNx<N,T>
+template <int N, typename T>
+SI SkNx<N*2,T> SkNx_join(const SkNx<N,T>& lo, const SkNx<N,T>& hi) {
+ return { lo, hi };
}
-template <typename D, typename S>
-static inline SkNx<8,D> SkNx_cast(const SkNx<8,S>& src) {
- return { (D)src[0], (D)src[1], (D)src[2], (D)src[3],
- (D)src[4], (D)src[5], (D)src[6], (D)src[7] };
+// A very generic shuffle. Can reorder, duplicate, contract, expand...
+// Sk4f v = { R,G,B,A };
+// SkNx_shuffle<2,1,0,3>(v) ~~> {B,G,R,A}
+// SkNx_shuffle<2,1>(v) ~~> {B,G}
+// SkNx_shuffle<2,1,2,1,2,1,2,1>(v) ~~> {B,G,B,G,B,G,B,G}
+// SkNx_shuffle<3,3,3,3>(v) ~~> {A,A,A,A}
+template <int... Ix, int N, typename T>
+SI SkNx<sizeof...(Ix),T> SkNx_shuffle(const SkNx<N,T>& v) {
+ return { v[Ix]... };
}
-template <typename D, typename S>
-static inline SkNx<16,D> SkNx_cast(const SkNx<16,S>& src) {
- return { (D)src[ 0], (D)src[ 1], (D)src[ 2], (D)src[ 3],
- (D)src[ 4], (D)src[ 5], (D)src[ 6], (D)src[ 7],
- (D)src[ 8], (D)src[ 9], (D)src[10], (D)src[11],
- (D)src[12], (D)src[13], (D)src[14], (D)src[15] };
+// Cast from SkNx<N, Src> to SkNx<N, Dst>, as if you called static_cast<Dst>(Src).
+template <typename Dst, typename Src, int N>
+SI SkNx<N,Dst> SkNx_cast(const SkNx<N,Src>& v) {
+ return { SkNx_cast<Dst>(v.fLo), SkNx_cast<Dst>(v.fHi) };
+}
+template <typename Dst, typename Src>
+SI SkNx<1,Dst> SkNx_cast(const SkNx<1,Src>& v) {
+ return static_cast<Dst>(v.fVal);
}
typedef SkNx<2, float> Sk2f;
typedef SkNx<4, float> Sk4f;
+typedef SkNx<8, float> Sk8f;
+typedef SkNx<16, float> Sk16f;
+
typedef SkNx<2, SkScalar> Sk2s;
typedef SkNx<4, SkScalar> Sk4s;
+typedef SkNx<8, SkScalar> Sk8s;
+typedef SkNx<16, SkScalar> Sk16s;
typedef SkNx<4, uint8_t> Sk4b;
+typedef SkNx<8, uint8_t> Sk8b;
typedef SkNx<16, uint8_t> Sk16b;
+
typedef SkNx<4, uint16_t> Sk4h;
+typedef SkNx<8, uint16_t> Sk8h;
typedef SkNx<16, uint16_t> Sk16h;
-typedef SkNx<4, int> Sk4i;
-typedef SkNx<4, int> Sk4i;
+typedef SkNx<4, int> Sk4i;
// Include platform specific specializations if available.
#if !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
#include "../opts/SkNx_sse.h"
#elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON)
#include "../opts/SkNx_neon.h"
-#else
- static inline
- void Sk4f_ToBytes(uint8_t p[16], const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& d) {
- SkNx_cast<uint8_t>(a).store(p+ 0);
- SkNx_cast<uint8_t>(b).store(p+ 4);
- SkNx_cast<uint8_t>(c).store(p+ 8);
- SkNx_cast<uint8_t>(d).store(p+12);
- }
#endif
+SI void Sk4f_ToBytes(uint8_t p[16], const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& d) {
+ SkNx_cast<uint8_t>(SkNx_join(SkNx_join(a,b), SkNx_join(c,d))).store(p);
+}
+
+#undef SI
+
#endif//SkNx_DEFINED