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