• Home
  • History
  • Annotate
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2015 Google Inc.
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef SkNx_DEFINED
9 #define SkNx_DEFINED
10 
11 #include "SkSafe_math.h"
12 #include "SkScalar.h"
13 #include "SkTypes.h"
14 
15 #include <algorithm>
16 #include <limits>
17 #include <type_traits>
18 
19 // Every single SkNx method wants to be fully inlined.  (We know better than MSVC).
20 #define AI SK_ALWAYS_INLINE
21 
22 namespace {  // NOLINT(google-build-namespaces)
23 
24 // The default SkNx<N,T> just proxies down to a pair of SkNx<N/2, T>.
25 template <int N, typename T>
26 struct SkNx {
27     typedef SkNx<N/2, T> Half;
28 
29     Half fLo, fHi;
30 
31     AI SkNx() = default;
SkNxSkNx32     AI SkNx(const Half& lo, const Half& hi) : fLo(lo), fHi(hi) {}
33 
SkNxSkNx34     AI SkNx(T v) : fLo(v), fHi(v) {}
35 
SkNxSkNx36     AI SkNx(T a, T b)           : fLo(a)  , fHi(b)   { static_assert(N==2, ""); }
SkNxSkNx37     AI SkNx(T a, T b, T c, T d) : fLo(a,b), fHi(c,d) { static_assert(N==4, ""); }
SkNxSkNx38     AI 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) {
39         static_assert(N==8, "");
40     }
SkNxSkNx41     AI SkNx(T a, T b, T c, T d,  T e, T f, T g, T h,
42             T i, T j, T k, T l,  T m, T n, T o, T p)
43         : fLo(a,b,c,d, e,f,g,h), fHi(i,j,k,l, m,n,o,p) {
44         static_assert(N==16, "");
45     }
46 
47     AI T operator[](int k) const {
48         SkASSERT(0 <= k && k < N);
49         return k < N/2 ? fLo[k] : fHi[k-N/2];
50     }
51 
LoadSkNx52     AI static SkNx Load(const void* vptr) {
53         auto ptr = (const char*)vptr;
54         return { Half::Load(ptr), Half::Load(ptr + N/2*sizeof(T)) };
55     }
storeSkNx56     AI void store(void* vptr) const {
57         auto ptr = (char*)vptr;
58         fLo.store(ptr);
59         fHi.store(ptr + N/2*sizeof(T));
60     }
61 
Load4SkNx62     AI static void Load4(const void* vptr, SkNx* a, SkNx* b, SkNx* c, SkNx* d) {
63         auto ptr = (const char*)vptr;
64         Half al, bl, cl, dl,
65              ah, bh, ch, dh;
66         Half::Load4(ptr                  , &al, &bl, &cl, &dl);
67         Half::Load4(ptr + 4*N/2*sizeof(T), &ah, &bh, &ch, &dh);
68         *a = SkNx{al, ah};
69         *b = SkNx{bl, bh};
70         *c = SkNx{cl, ch};
71         *d = SkNx{dl, dh};
72     }
Load3SkNx73     AI static void Load3(const void* vptr, SkNx* a, SkNx* b, SkNx* c) {
74         auto ptr = (const char*)vptr;
75         Half al, bl, cl,
76              ah, bh, ch;
77         Half::Load3(ptr                  , &al, &bl, &cl);
78         Half::Load3(ptr + 3*N/2*sizeof(T), &ah, &bh, &ch);
79         *a = SkNx{al, ah};
80         *b = SkNx{bl, bh};
81         *c = SkNx{cl, ch};
82     }
Load2SkNx83     AI static void Load2(const void* vptr, SkNx* a, SkNx* b) {
84         auto ptr = (const char*)vptr;
85         Half al, bl,
86              ah, bh;
87         Half::Load2(ptr                  , &al, &bl);
88         Half::Load2(ptr + 2*N/2*sizeof(T), &ah, &bh);
89         *a = SkNx{al, ah};
90         *b = SkNx{bl, bh};
91     }
Store4SkNx92     AI static void Store4(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c, const SkNx& d) {
93         auto ptr = (char*)vptr;
94         Half::Store4(ptr,                   a.fLo, b.fLo, c.fLo, d.fLo);
95         Half::Store4(ptr + 4*N/2*sizeof(T), a.fHi, b.fHi, c.fHi, d.fHi);
96     }
Store3SkNx97     AI static void Store3(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c) {
98         auto ptr = (char*)vptr;
99         Half::Store3(ptr,                   a.fLo, b.fLo, c.fLo);
100         Half::Store3(ptr + 3*N/2*sizeof(T), a.fHi, b.fHi, c.fHi);
101     }
Store2SkNx102     AI static void Store2(void* vptr, const SkNx& a, const SkNx& b) {
103         auto ptr = (char*)vptr;
104         Half::Store2(ptr,                   a.fLo, b.fLo);
105         Half::Store2(ptr + 2*N/2*sizeof(T), a.fHi, b.fHi);
106     }
107 
minSkNx108     AI T min() const { return SkTMin(fLo.min(), fHi.min()); }
maxSkNx109     AI T max() const { return SkTMax(fLo.max(), fHi.max()); }
anyTrueSkNx110     AI bool anyTrue() const { return fLo.anyTrue() || fHi.anyTrue(); }
allTrueSkNx111     AI bool allTrue() const { return fLo.allTrue() && fHi.allTrue(); }
112 
absSkNx113     AI SkNx    abs() const { return { fLo.   abs(), fHi.   abs() }; }
sqrtSkNx114     AI SkNx   sqrt() const { return { fLo.  sqrt(), fHi.  sqrt() }; }
rsqrtSkNx115     AI SkNx  rsqrt() const { return { fLo. rsqrt(), fHi. rsqrt() }; }
floorSkNx116     AI SkNx  floor() const { return { fLo. floor(), fHi. floor() }; }
invertSkNx117     AI SkNx invert() const { return { fLo.invert(), fHi.invert() }; }
118 
119     AI SkNx operator!() const { return { !fLo, !fHi }; }
120     AI SkNx operator-() const { return { -fLo, -fHi }; }
121     AI SkNx operator~() const { return { ~fLo, ~fHi }; }
122 
123     AI SkNx operator<<(int bits) const { return { fLo << bits, fHi << bits }; }
124     AI SkNx operator>>(int bits) const { return { fLo >> bits, fHi >> bits }; }
125 
126     AI SkNx operator+(const SkNx& y) const { return { fLo + y.fLo, fHi + y.fHi }; }
127     AI SkNx operator-(const SkNx& y) const { return { fLo - y.fLo, fHi - y.fHi }; }
128     AI SkNx operator*(const SkNx& y) const { return { fLo * y.fLo, fHi * y.fHi }; }
129     AI SkNx operator/(const SkNx& y) const { return { fLo / y.fLo, fHi / y.fHi }; }
130 
131     AI SkNx operator&(const SkNx& y) const { return { fLo & y.fLo, fHi & y.fHi }; }
132     AI SkNx operator|(const SkNx& y) const { return { fLo | y.fLo, fHi | y.fHi }; }
133     AI SkNx operator^(const SkNx& y) const { return { fLo ^ y.fLo, fHi ^ y.fHi }; }
134 
135     AI SkNx operator==(const SkNx& y) const { return { fLo == y.fLo, fHi == y.fHi }; }
136     AI SkNx operator!=(const SkNx& y) const { return { fLo != y.fLo, fHi != y.fHi }; }
137     AI SkNx operator<=(const SkNx& y) const { return { fLo <= y.fLo, fHi <= y.fHi }; }
138     AI SkNx operator>=(const SkNx& y) const { return { fLo >= y.fLo, fHi >= y.fHi }; }
139     AI SkNx operator< (const SkNx& y) const { return { fLo <  y.fLo, fHi <  y.fHi }; }
140     AI SkNx operator> (const SkNx& y) const { return { fLo >  y.fLo, fHi >  y.fHi }; }
141 
saturatedAddSkNx142     AI SkNx saturatedAdd(const SkNx& y) const {
143         return { fLo.saturatedAdd(y.fLo), fHi.saturatedAdd(y.fHi) };
144     }
145 
mulHiSkNx146     AI SkNx mulHi(const SkNx& m) const {
147         return { fLo.mulHi(m.fLo), fHi.mulHi(m.fHi) };
148     }
thenElseSkNx149     AI SkNx thenElse(const SkNx& t, const SkNx& e) const {
150         return { fLo.thenElse(t.fLo, e.fLo), fHi.thenElse(t.fHi, e.fHi) };
151     }
MinSkNx152     AI static SkNx Min(const SkNx& x, const SkNx& y) {
153         return { Half::Min(x.fLo, y.fLo), Half::Min(x.fHi, y.fHi) };
154     }
MaxSkNx155     AI static SkNx Max(const SkNx& x, const SkNx& y) {
156         return { Half::Max(x.fLo, y.fLo), Half::Max(x.fHi, y.fHi) };
157     }
158 };
159 
160 // The N -> N/2 recursion bottoms out at N == 1, a scalar value.
161 template <typename T>
162 struct SkNx<1,T> {
163     T fVal;
164 
165     AI SkNx() = default;
166     AI SkNx(T v) : fVal(v) {}
167 
168     // Android complains against unused parameters, so we guard it
169     AI T operator[](int SkDEBUGCODE(k)) const {
170         SkASSERT(k == 0);
171         return fVal;
172     }
173 
174     AI static SkNx Load(const void* ptr) {
175         SkNx v;
176         memcpy(&v, ptr, sizeof(T));
177         return v;
178     }
179     AI void store(void* ptr) const { memcpy(ptr, &fVal, sizeof(T)); }
180 
181     AI static void Load4(const void* vptr, SkNx* a, SkNx* b, SkNx* c, SkNx* d) {
182         auto ptr = (const char*)vptr;
183         *a = Load(ptr + 0*sizeof(T));
184         *b = Load(ptr + 1*sizeof(T));
185         *c = Load(ptr + 2*sizeof(T));
186         *d = Load(ptr + 3*sizeof(T));
187     }
188     AI static void Load3(const void* vptr, SkNx* a, SkNx* b, SkNx* c) {
189         auto ptr = (const char*)vptr;
190         *a = Load(ptr + 0*sizeof(T));
191         *b = Load(ptr + 1*sizeof(T));
192         *c = Load(ptr + 2*sizeof(T));
193     }
194     AI static void Load2(const void* vptr, SkNx* a, SkNx* b) {
195         auto ptr = (const char*)vptr;
196         *a = Load(ptr + 0*sizeof(T));
197         *b = Load(ptr + 1*sizeof(T));
198     }
199     AI static void Store4(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c, const SkNx& d) {
200         auto ptr = (char*)vptr;
201         a.store(ptr + 0*sizeof(T));
202         b.store(ptr + 1*sizeof(T));
203         c.store(ptr + 2*sizeof(T));
204         d.store(ptr + 3*sizeof(T));
205     }
206     AI static void Store3(void* vptr, const SkNx& a, const SkNx& b, const SkNx& c) {
207         auto ptr = (char*)vptr;
208         a.store(ptr + 0*sizeof(T));
209         b.store(ptr + 1*sizeof(T));
210         c.store(ptr + 2*sizeof(T));
211     }
212     AI static void Store2(void* vptr, const SkNx& a, const SkNx& b) {
213         auto ptr = (char*)vptr;
214         a.store(ptr + 0*sizeof(T));
215         b.store(ptr + 1*sizeof(T));
216     }
217 
218     AI T min() const { return fVal; }
219     AI T max() const { return fVal; }
220     AI bool anyTrue() const { return fVal != 0; }
221     AI bool allTrue() const { return fVal != 0; }
222 
223     AI SkNx    abs() const { return Abs(fVal); }
224     AI SkNx   sqrt() const { return Sqrt(fVal); }
225     AI SkNx  rsqrt() const { return T(1) / this->sqrt(); }
226     AI SkNx  floor() const { return Floor(fVal); }
227     AI SkNx invert() const { return T(1) / *this; }
228 
229     AI SkNx operator!() const { return !fVal; }
230     AI SkNx operator-() const { return -fVal; }
231     AI SkNx operator~() const { return FromBits(~ToBits(fVal)); }
232 
233     AI SkNx operator<<(int bits) const { return fVal << bits; }
234     AI SkNx operator>>(int bits) const { return fVal >> bits; }
235 
236     AI SkNx operator+(const SkNx& y) const { return fVal + y.fVal; }
237     AI SkNx operator-(const SkNx& y) const { return fVal - y.fVal; }
238     AI SkNx operator*(const SkNx& y) const { return fVal * y.fVal; }
239     AI SkNx operator/(const SkNx& y) const { return fVal / y.fVal; }
240 
241     AI SkNx operator&(const SkNx& y) const { return FromBits(ToBits(fVal) & ToBits(y.fVal)); }
242     AI SkNx operator|(const SkNx& y) const { return FromBits(ToBits(fVal) | ToBits(y.fVal)); }
243     AI SkNx operator^(const SkNx& y) const { return FromBits(ToBits(fVal) ^ ToBits(y.fVal)); }
244 
245     AI SkNx operator==(const SkNx& y) const { return FromBits(fVal == y.fVal ? ~0 : 0); }
246     AI SkNx operator!=(const SkNx& y) const { return FromBits(fVal != y.fVal ? ~0 : 0); }
247     AI SkNx operator<=(const SkNx& y) const { return FromBits(fVal <= y.fVal ? ~0 : 0); }
248     AI SkNx operator>=(const SkNx& y) const { return FromBits(fVal >= y.fVal ? ~0 : 0); }
249     AI SkNx operator< (const SkNx& y) const { return FromBits(fVal <  y.fVal ? ~0 : 0); }
250     AI SkNx operator> (const SkNx& y) const { return FromBits(fVal >  y.fVal ? ~0 : 0); }
251 
252     AI static SkNx Min(const SkNx& x, const SkNx& y) { return x.fVal < y.fVal ? x : y; }
253     AI static SkNx Max(const SkNx& x, const SkNx& y) { return x.fVal > y.fVal ? x : y; }
254 
255     AI SkNx saturatedAdd(const SkNx& y) const {
256         static_assert(std::is_unsigned<T>::value, "");
257         T sum = fVal + y.fVal;
258         return sum < fVal ? std::numeric_limits<T>::max() : sum;
259     }
260 
261     AI SkNx mulHi(const SkNx& m) const {
262         static_assert(std::is_unsigned<T>::value, "");
263         static_assert(sizeof(T) <= 4, "");
264         return static_cast<T>((static_cast<uint64_t>(fVal) * m.fVal) >> (sizeof(T)*8));
265     }
266 
267     AI SkNx thenElse(const SkNx& t, const SkNx& e) const { return fVal != 0 ? t : e; }
268 
269 private:
270     // Helper functions to choose the right float/double methods.  (In <cmath> madness lies...)
271     AI static int     Abs(int val) { return  val < 0 ? -val : val; }
272 
273     AI static float   Abs(float val) { return  ::fabsf(val); }
274     AI static float  Sqrt(float val) { return  ::sqrtf(val); }
275     AI static float Floor(float val) { return ::floorf(val); }
276 
277     AI static double   Abs(double val) { return  ::fabs(val); }
278     AI static double  Sqrt(double val) { return  ::sqrt(val); }
279     AI static double Floor(double val) { return ::floor(val); }
280 
281     // Helper functions for working with floats/doubles as bit patterns.
282     template <typename U>
283     AI static U ToBits(U v) { return v; }
284     AI static int32_t ToBits(float  v) { int32_t bits; memcpy(&bits, &v, sizeof(v)); return bits; }
285     AI static int64_t ToBits(double v) { int64_t bits; memcpy(&bits, &v, sizeof(v)); return bits; }
286 
287     template <typename Bits>
288     AI static T FromBits(Bits bits) {
289         static_assert(std::is_pod<T   >::value &&
290                       std::is_pod<Bits>::value &&
291                       sizeof(T) <= sizeof(Bits), "");
292         T val;
293         memcpy(&val, &bits, sizeof(T));
294         return val;
295     }
296 };
297 
298 // Allow scalars on the left or right of binary operators, and things like +=, &=, etc.
299 #define V template <int N, typename T> AI static SkNx<N,T>
300     V operator+ (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) +  y; }
301     V operator- (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) -  y; }
302     V operator* (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) *  y; }
303     V operator/ (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) /  y; }
304     V operator& (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) &  y; }
305     V operator| (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) |  y; }
306     V operator^ (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) ^  y; }
307     V operator==(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) == y; }
308     V operator!=(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) != y; }
309     V operator<=(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) <= y; }
310     V operator>=(T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) >= y; }
311     V operator< (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) <  y; }
312     V operator> (T x, const SkNx<N,T>& y) { return SkNx<N,T>(x) >  y; }
313 
314     V operator+ (const SkNx<N,T>& x, T y) { return x +  SkNx<N,T>(y); }
315     V operator- (const SkNx<N,T>& x, T y) { return x -  SkNx<N,T>(y); }
316     V operator* (const SkNx<N,T>& x, T y) { return x *  SkNx<N,T>(y); }
317     V operator/ (const SkNx<N,T>& x, T y) { return x /  SkNx<N,T>(y); }
318     V operator& (const SkNx<N,T>& x, T y) { return x &  SkNx<N,T>(y); }
319     V operator| (const SkNx<N,T>& x, T y) { return x |  SkNx<N,T>(y); }
320     V operator^ (const SkNx<N,T>& x, T y) { return x ^  SkNx<N,T>(y); }
321     V operator==(const SkNx<N,T>& x, T y) { return x == SkNx<N,T>(y); }
322     V operator!=(const SkNx<N,T>& x, T y) { return x != SkNx<N,T>(y); }
323     V operator<=(const SkNx<N,T>& x, T y) { return x <= SkNx<N,T>(y); }
324     V operator>=(const SkNx<N,T>& x, T y) { return x >= SkNx<N,T>(y); }
325     V operator< (const SkNx<N,T>& x, T y) { return x <  SkNx<N,T>(y); }
326     V operator> (const SkNx<N,T>& x, T y) { return x >  SkNx<N,T>(y); }
327 
328     V& operator<<=(SkNx<N,T>& x, int bits) { return (x = x << bits); }
329     V& operator>>=(SkNx<N,T>& x, int bits) { return (x = x >> bits); }
330 
331     V& operator +=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x + y); }
332     V& operator -=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x - y); }
333     V& operator *=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x * y); }
334     V& operator /=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x / y); }
335     V& operator &=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x & y); }
336     V& operator |=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x | y); }
337     V& operator ^=(SkNx<N,T>& x, const SkNx<N,T>& y) { return (x = x ^ y); }
338 
339     V& operator +=(SkNx<N,T>& x, T y) { return (x = x + SkNx<N,T>(y)); }
340     V& operator -=(SkNx<N,T>& x, T y) { return (x = x - SkNx<N,T>(y)); }
341     V& operator *=(SkNx<N,T>& x, T y) { return (x = x * SkNx<N,T>(y)); }
342     V& operator /=(SkNx<N,T>& x, T y) { return (x = x / SkNx<N,T>(y)); }
343     V& operator &=(SkNx<N,T>& x, T y) { return (x = x & SkNx<N,T>(y)); }
344     V& operator |=(SkNx<N,T>& x, T y) { return (x = x | SkNx<N,T>(y)); }
345     V& operator ^=(SkNx<N,T>& x, T y) { return (x = x ^ SkNx<N,T>(y)); }
346 #undef V
347 
348 // SkNx<N,T> ~~> SkNx<N/2,T> + SkNx<N/2,T>
349 template <int N, typename T>
350 AI static void SkNx_split(const SkNx<N,T>& v, SkNx<N/2,T>* lo, SkNx<N/2,T>* hi) {
351     *lo = v.fLo;
352     *hi = v.fHi;
353 }
354 
355 // SkNx<N/2,T> + SkNx<N/2,T> ~~> SkNx<N,T>
356 template <int N, typename T>
357 AI static SkNx<N*2,T> SkNx_join(const SkNx<N,T>& lo, const SkNx<N,T>& hi) {
358     return { lo, hi };
359 }
360 
361 // A very generic shuffle.  Can reorder, duplicate, contract, expand...
362 //    Sk4f v = { R,G,B,A };
363 //    SkNx_shuffle<2,1,0,3>(v)         ~~> {B,G,R,A}
364 //    SkNx_shuffle<2,1>(v)             ~~> {B,G}
365 //    SkNx_shuffle<2,1,2,1,2,1,2,1>(v) ~~> {B,G,B,G,B,G,B,G}
366 //    SkNx_shuffle<3,3,3,3>(v)         ~~> {A,A,A,A}
367 template <int... Ix, int N, typename T>
368 AI static SkNx<sizeof...(Ix),T> SkNx_shuffle(const SkNx<N,T>& v) {
369     return { v[Ix]... };
370 }
371 
372 // Cast from SkNx<N, Src> to SkNx<N, Dst>, as if you called static_cast<Dst>(Src).
373 template <typename Dst, typename Src, int N>
374 AI static SkNx<N,Dst> SkNx_cast(const SkNx<N,Src>& v) {
375     return { SkNx_cast<Dst>(v.fLo), SkNx_cast<Dst>(v.fHi) };
376 }
377 template <typename Dst, typename Src>
378 AI static SkNx<1,Dst> SkNx_cast(const SkNx<1,Src>& v) {
379     return static_cast<Dst>(v.fVal);
380 }
381 
382 template <int N, typename T>
383 AI static SkNx<N,T> SkNx_fma(const SkNx<N,T>& f, const SkNx<N,T>& m, const SkNx<N,T>& a) {
384     return f*m+a;
385 }
386 
387 }  // namespace
388 
389 typedef SkNx<2,     float> Sk2f;
390 typedef SkNx<4,     float> Sk4f;
391 typedef SkNx<8,     float> Sk8f;
392 typedef SkNx<16,    float> Sk16f;
393 
394 typedef SkNx<2,  SkScalar> Sk2s;
395 typedef SkNx<4,  SkScalar> Sk4s;
396 typedef SkNx<8,  SkScalar> Sk8s;
397 typedef SkNx<16, SkScalar> Sk16s;
398 
399 typedef SkNx<4,   uint8_t> Sk4b;
400 typedef SkNx<8,   uint8_t> Sk8b;
401 typedef SkNx<16,  uint8_t> Sk16b;
402 
403 typedef SkNx<4,  uint16_t> Sk4h;
404 typedef SkNx<8,  uint16_t> Sk8h;
405 typedef SkNx<16, uint16_t> Sk16h;
406 
407 typedef SkNx<4,  int32_t> Sk4i;
408 typedef SkNx<8,  int32_t> Sk8i;
409 typedef SkNx<4, uint32_t> Sk4u;
410 
411 // Include platform specific specializations if available.
412 #if !defined(SKNX_NO_SIMD) && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE2
413     #include "SkNx_sse.h"
414 #elif !defined(SKNX_NO_SIMD) && defined(SK_ARM_HAS_NEON)
415     #include "SkNx_neon.h"
416 #else
417 
418 AI static Sk4i Sk4f_round(const Sk4f& x) {
419     return { (int) lrintf (x[0]),
420              (int) lrintf (x[1]),
421              (int) lrintf (x[2]),
422              (int) lrintf (x[3]), };
423 }
424 
425 #endif
426 
427 AI static void Sk4f_ToBytes(uint8_t p[16],
428                             const Sk4f& a, const Sk4f& b, const Sk4f& c, const Sk4f& d) {
429     SkNx_cast<uint8_t>(SkNx_join(SkNx_join(a,b), SkNx_join(c,d))).store(p);
430 }
431 
432 #undef AI
433 
434 #endif//SkNx_DEFINED
435