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