Coverage Report

Created: 2026-08-13 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/skcms/src/Transform_inl.h
Line
Count
Source
1
/*
2
 * Copyright 2018 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
// Intentionally NO #pragma once... included multiple times.
9
10
// This file is included from skcms.cc in a namespace with some pre-defines:
11
//    - N:    SIMD width of all vectors; 1, 4, 8 or 16 (preprocessor define)
12
//    - V<T>: a template to create a vector of N T's.
13
14
using F   = V<float>;
15
using I32 = V<int32_t>;
16
using U64 = V<uint64_t>;
17
using U32 = V<uint32_t>;
18
using U16 = V<uint16_t>;
19
using U8  = V<uint8_t>;
20
21
#if defined(__GNUC__) && !defined(__clang__)
22
    // GCC is kind of weird, not allowing vector = scalar directly.
23
    static constexpr F F0 = F() + 0.0f,
24
                       F1 = F() + 1.0f,
25
                       FInfBits = F() + 0x7f800000; // equals 2139095040, the bit pattern of +Inf
26
#else
27
    static constexpr F F0 = 0.0f,
28
                       F1 = 1.0f,
29
                       FInfBits = 0x7f800000; // equals 2139095040, the bit pattern of +Inf
30
#endif
31
32
// Instead of checking __AVX__ below, we'll check USING_AVX.
33
// This lets skcms.cc set USING_AVX to force us in even if the compiler's not set that way.
34
// Same deal for __F16C__ and __AVX2__ ~~~> USING_AVX_F16C, USING_AVX2.
35
36
#if !defined(USING_AVX)      && N == 8 && defined(__AVX__)
37
    #define  USING_AVX
38
#endif
39
#if !defined(USING_AVX_F16C) && defined(USING_AVX) && defined(__F16C__)
40
    #define  USING_AVX_F16C
41
#endif
42
#if !defined(USING_AVX2)     && defined(USING_AVX) && defined(__AVX2__)
43
    #define  USING_AVX2
44
#endif
45
#if !defined(USING_AVX512F)  && N == 16 && defined(__AVX512F__) && defined(__AVX512DQ__)
46
    #define  USING_AVX512F
47
#endif
48
49
// Similar to the AVX+ features, we define USING_NEON and USING_NEON_F16C.
50
// This is more for organizational clarity... skcms.cc doesn't force these.
51
#if N > 1 && defined(__ARM_NEON)
52
    #define USING_NEON
53
54
    // We have to use two different mechanisms to enable the f16 conversion intrinsics:
55
    #if defined(__clang__)
56
        // Clang's arm_neon.h guards them with the FP hardware bit:
57
        #if __ARM_FP & 2
58
            #define USING_NEON_F16C
59
        #endif
60
    #elif defined(__GNUC__)
61
        // GCC's arm_neon.h guards them with the FP16 format macros (IEEE and ALTERNATIVE).
62
        // We don't actually want the alternative format - we're reading/writing IEEE f16 values.
63
        #if defined(__ARM_FP16_FORMAT_IEEE)
64
            #define USING_NEON_F16C
65
        #endif
66
    #endif
67
#endif
68
69
// These -Wvector-conversion warnings seem to trigger in very bogus situations,
70
// like vst3q_f32() expecting a 16x char rather than a 4x float vector.  :/
71
#if defined(USING_NEON) && defined(__clang__)
72
    #pragma clang diagnostic ignored "-Wvector-conversion"
73
#endif
74
75
// GCC & Clang (but not clang-cl) warn returning U64 on x86 is larger than a register.
76
// You'd see warnings like, "using AVX even though AVX is not enabled".
77
// We stifle these warnings; our helpers that return U64 are always inlined.
78
#if defined(__SSE__) && defined(__GNUC__)
79
    #if !defined(__has_warning)
80
        #pragma GCC diagnostic ignored "-Wpsabi"
81
    #elif __has_warning("-Wpsabi")
82
        #pragma GCC diagnostic ignored "-Wpsabi"
83
    #endif
84
#endif
85
86
// We tag most helper functions as SI, to enforce good code generation
87
// but also work around what we think is a bug in GCC: when targeting 32-bit
88
// x86, GCC tends to pass U16 (4x uint16_t vector) function arguments in the
89
// MMX mm0 register, which seems to mess with unrelated code that later uses
90
// x87 FP instructions (MMX's mm0 is an alias for x87's st0 register).
91
#if defined(__clang__) || defined(__GNUC__)
92
    #define SI static inline __attribute__((always_inline))
93
#else
94
    #define SI static inline
95
#endif
96
97
template <typename T, typename P>
98
0
SI T load(const P* ptr) {
99
0
    T val;
100
0
    memcpy(&val, ptr, sizeof(val));
101
0
    return val;
102
0
}
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned char __vector(4) skcms_private::baseline::load<unsigned char __vector(4), char>(char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned short __vector(4) skcms_private::baseline::load<unsigned short __vector(4), char>(char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::load<unsigned int __vector(4), char>(char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned long __vector(4) skcms_private::baseline::load<unsigned long __vector(4), char>(char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned long __vector(4) skcms_private::baseline::load<unsigned long __vector(4), unsigned short>(unsigned short const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::load<float __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::load<float __vector(4), int __vector(4)>(int __vector(4) const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:int __vector(4) skcms_private::baseline::load<int __vector(4), float __vector(4)>(float __vector(4) const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::load<unsigned int __vector(4), float __vector(4)>(float __vector(4) const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned short skcms_private::baseline::load<unsigned short, unsigned char>(unsigned char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int skcms_private::baseline::load<unsigned int, unsigned char>(unsigned char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned long skcms_private::baseline::load<unsigned long, unsigned char>(unsigned char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::load<unsigned int __vector(4), int __vector(4)>(int __vector(4) const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:int __vector(4) skcms_private::baseline::load<int __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const*)
103
template <typename T, typename P>
104
0
SI void store(P* ptr, const T& val) {
105
0
    memcpy(ptr, &val, sizeof(val));
106
0
}
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store<unsigned char __vector(4), char>(char*, unsigned char __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store<unsigned short __vector(4), char>(char*, unsigned short __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store<unsigned int __vector(4), char>(char*, unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store<unsigned long __vector(4), unsigned short>(unsigned short*, unsigned long __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store<unsigned long __vector(4), char>(char*, unsigned long __vector(4) const&)
107
108
// (T)v is a cast when N == 1 and a bit-pun when N>1,
109
// so we use cast<T>(v) to actually cast or bit_pun<T>(v) to bit-pun.
110
template <typename D, typename S>
111
0
SI D cast(const S& v) {
112
#if N == 1
113
    return (D)v;
114
#else
115
0
    return __builtin_convertvector(v, D);
116
117
0
#endif
118
0
}
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::cast<float __vector(4), unsigned char __vector(4)>(unsigned char __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::cast<float __vector(4), unsigned short __vector(4)>(unsigned short __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::cast<float __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::cast<float __vector(4), unsigned long __vector(4)>(unsigned long __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::cast<unsigned int __vector(4), unsigned short __vector(4)>(unsigned short __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned short __vector(4) skcms_private::baseline::cast<unsigned short __vector(4), unsigned long __vector(4)>(unsigned long __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::cast<float __vector(4), int __vector(4)>(int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:int __vector(4) skcms_private::baseline::cast<int __vector(4), float __vector(4)>(float __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned char __vector(4) skcms_private::baseline::cast<unsigned char __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned short __vector(4) skcms_private::baseline::cast<unsigned short __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::cast<unsigned int __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned short __vector(4) skcms_private::baseline::cast<unsigned short __vector(4), float __vector(4)>(float __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::cast<float __vector(4), float __vector(4)>(float __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned long __vector(4) skcms_private::baseline::cast<unsigned long __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned long __vector(4) skcms_private::baseline::cast<unsigned long __vector(4), unsigned short __vector(4)>(unsigned short __vector(4) const&)
119
120
template <typename D, typename S>
121
0
SI D bit_pun(const S& v) {
122
0
    static_assert(sizeof(D) == sizeof(v), "");
123
0
    return load<D>(&v);
124
0
}
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::bit_pun<float __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::bit_pun<float __vector(4), int __vector(4)>(int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:int __vector(4) skcms_private::baseline::bit_pun<int __vector(4), float __vector(4)>(float __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::bit_pun<unsigned int __vector(4), float __vector(4)>(float __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::bit_pun<unsigned int __vector(4), int __vector(4)>(int __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:int __vector(4) skcms_private::baseline::bit_pun<int __vector(4), unsigned int __vector(4)>(unsigned int __vector(4) const&)
125
126
// When we convert from float to fixed point, it's very common to want to round,
127
// and for some reason compilers generate better code when converting to int32_t.
128
// To serve both those ends, we use this function to_fixed() instead of direct cast().
129
0
SI U32 to_fixed(F f) {  return (U32)cast<I32>(f + 0.5f); }
130
131
// Sometimes we do something crazy on one branch of a conditonal,
132
// like divide by zero or convert a huge float to an integer,
133
// but then harmlessly select the other side.  That trips up N==1
134
// sanitizer builds, so we make if_then_else() a macro to avoid
135
// evaluating the unused side.
136
137
#if N == 1
138
    #define if_then_else(cond, t, e) ((cond) ? (t) : (e))
139
#else
140
    template <typename C, typename T>
141
0
    SI T if_then_else(C cond, T t, T e) {
142
0
        return bit_pun<T>( ( cond & bit_pun<C>(t)) |
143
0
                           (~cond & bit_pun<C>(e)) );
144
0
    }
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::if_then_else<int __vector(4), float __vector(4)>(int __vector(4), float __vector(4), float __vector(4))
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::if_then_else<int __vector(4), unsigned int __vector(4)>(int __vector(4), unsigned int __vector(4), unsigned int __vector(4))
145
#endif
146
147
148
0
SI F F_from_Half(U16 half) {
149
#if defined(USING_NEON_F16C)
150
    return vcvt_f32_f16((float16x4_t)half);
151
#elif defined(USING_AVX512F)
152
    return (F)_mm512_cvtph_ps((__m256i)half);
153
#elif defined(USING_AVX_F16C)
154
#if defined(__clang__) && __clang_major__ >= 15 // for _Float16 support
155
    typedef _Float16 __attribute__((vector_size(16))) F16;
156
    return __builtin_convertvector((F16)half, F);
157
#else
158
    typedef int16_t __attribute__((vector_size(16))) I16;
159
    return __builtin_ia32_vcvtph2ps256((I16)half);
160
#endif // defined(__clang))
161
#else
162
0
    U32 wide = cast<U32>(half);
163
    // A half is 1-5-10 sign-exponent-mantissa, with 15 exponent bias.
164
0
    U32 s  = wide & 0x8000,
165
0
        em = wide ^ s;
166
167
    // Constructing the float is easy if the half is not denormalized.
168
0
    F norm = bit_pun<F>( (s<<16) + (em<<13) + ((127-15)<<23) );
169
170
    // Simply flush all denorm half floats to zero.
171
0
    return if_then_else(em < 0x0400, F0, norm);
172
0
#endif
173
0
}
174
175
#if defined(__clang__)
176
    // The -((127-15)<<10) underflows that side of the math when
177
    // we pass a denorm half float.  It's harmless... we'll take the 0 side anyway.
178
    __attribute__((no_sanitize("unsigned-integer-overflow")))
179
#endif
180
0
SI U16 Half_from_F(F f) {
181
#if defined(USING_NEON_F16C)
182
    return (U16)vcvt_f16_f32(f);
183
#elif defined(USING_AVX512F)
184
    return (U16)_mm512_cvtps_ph((__m512 )f, _MM_FROUND_CUR_DIRECTION );
185
#elif defined(USING_AVX_F16C)
186
    return (U16)__builtin_ia32_vcvtps2ph256(f, 0x04/*_MM_FROUND_CUR_DIRECTION*/);
187
#else
188
    // A float is 1-8-23 sign-exponent-mantissa, with 127 exponent bias.
189
0
    U32 sem = bit_pun<U32>(f),
190
0
        s   = sem & 0x80000000,
191
0
         em = sem ^ s;
192
193
    // For simplicity we flush denorm half floats (including all denorm floats) to zero.
194
0
    return cast<U16>(if_then_else(em < 0x38800000, (U32)F0
195
0
                                                 , (s>>16) + (em>>13) - ((127-15)<<10)));
196
0
#endif
197
0
}
198
199
// Swap high and low bytes of 16-bit lanes, converting between big-endian and little-endian.
200
#if defined(USING_NEON)
201
    SI U16 swap_endian_16(U16 v) {
202
        return (U16)vrev16_u8((uint8x8_t) v);
203
    }
204
#endif
205
206
0
SI U64 swap_endian_16x4(const U64& rgba) {
207
0
    return (rgba & 0x00ff00ff00ff00ff) << 8
208
0
         | (rgba & 0xff00ff00ff00ff00) >> 8;
209
0
}
210
211
#if defined(USING_NEON)
212
    SI F min_(F x, F y) { return (F)vminq_f32((float32x4_t)x, (float32x4_t)y); }
213
    SI F max_(F x, F y) { return (F)vmaxq_f32((float32x4_t)x, (float32x4_t)y); }
214
#elif defined(__loongarch_sx)
215
    SI F min_(F x, F y) { return (F)__lsx_vfmin_s(x, y); }
216
    SI F max_(F x, F y) { return (F)__lsx_vfmax_s(x, y); }
217
#else
218
0
    SI F min_(F x, F y) { return if_then_else(x > y, y, x); }
219
0
    SI F max_(F x, F y) { return if_then_else(x < y, y, x); }
220
#endif
221
222
0
SI F floor_(F x) {
223
#if N == 1
224
    return floorf_(x);
225
#elif defined(__aarch64__)
226
    return vrndmq_f32(x);
227
#elif defined(USING_AVX512F)
228
    // Clang's _mm512_floor_ps() passes its mask as -1, not (__mmask16)-1,
229
    // and integer santizer catches that this implicit cast changes the
230
    // value from -1 to 65535.  We'll cast manually to work around it.
231
    // Read this as `return _mm512_floor_ps(x)`.
232
    return _mm512_mask_floor_ps(x, (__mmask16)-1, x);
233
#elif defined(USING_AVX)
234
    return __builtin_ia32_roundps256(x, 0x01/*_MM_FROUND_FLOOR*/);
235
#elif defined(__SSE4_1__)
236
    return _mm_floor_ps(x);
237
#elif defined(__loongarch_sx)
238
    return __lsx_vfrintrm_s((__m128)x);
239
#else
240
    // Round trip through integers with a truncating cast.
241
0
    F roundtrip = cast<F>(cast<I32>(x));
242
    // If x is negative, truncating gives the ceiling instead of the floor.
243
0
    return roundtrip - if_then_else(roundtrip > x, F1, F0);
244
245
    // This implementation fails for values of x that are outside
246
    // the range an integer can represent.  We expect most x to be small.
247
0
#endif
248
0
}
249
250
0
SI F approx_log2(F x) {
251
    // The first approximation of log2(x) is its exponent 'e', minus 127.
252
0
    I32 bits = bit_pun<I32>(x);
253
254
0
    F e = cast<F>(bits) * (1.0f / (1<<23));
255
256
    // If we use the mantissa too we can refine the error signficantly.
257
0
    F m = bit_pun<F>( (bits & 0x007fffff) | 0x3f000000 );
258
259
0
    return e - 124.225514990f
260
0
             -   1.498030302f*m
261
0
             -   1.725879990f/(0.3520887068f + m);
262
0
}
263
264
0
SI F approx_log(F x) {
265
0
    const float ln2 = 0.69314718f;
266
0
    return ln2 * approx_log2(x);
267
0
}
268
269
0
SI F approx_exp2(F x) {
270
0
    F fract = x - floor_(x);
271
272
0
    F fbits = (1.0f * (1<<23)) * (x + 121.274057500f
273
0
                                    -   1.490129070f*fract
274
0
                                    +  27.728023300f/(4.84252568f - fract));
275
0
    I32 bits = cast<I32>(min_(max_(fbits, F0), FInfBits));
276
277
0
    return bit_pun<F>(bits);
278
0
}
279
280
0
SI F approx_pow(F x, float y) {
281
0
    return if_then_else((x == F0) | (x == F1), x
282
0
                                             , approx_exp2(approx_log2(x) * y));
283
0
}
284
285
0
SI F approx_exp(F x) {
286
0
    const float log2_e = 1.4426950408889634074f;
287
0
    return approx_exp2(log2_e * x);
288
0
}
289
290
0
SI F strip_sign(F x, U32* sign) {
291
0
    U32 bits = bit_pun<U32>(x);
292
0
    *sign = bits & 0x80000000;
293
0
    return bit_pun<F>(bits ^ *sign);
294
0
}
295
296
0
SI F apply_sign(F x, U32 sign) {
297
0
    return bit_pun<F>(sign | bit_pun<U32>(x));
298
0
}
299
300
// Return tf(x).
301
0
SI F apply_tf(const skcms_TransferFunction* tf, F x) {
302
    // Peel off the sign bit and set x = |x|.
303
0
    U32 sign;
304
0
    x = strip_sign(x, &sign);
305
306
    // The transfer function has a linear part up to d, exponential at d and after.
307
0
    F v = if_then_else(x < tf->d,            tf->c*x + tf->f
308
0
                                , approx_pow(tf->a*x + tf->b, tf->g) + tf->e);
309
310
    // Tack the sign bit back on.
311
0
    return apply_sign(v, sign);
312
0
}
313
314
// Return the gamma function (|x|^G with the original sign re-applied to x).
315
0
SI F apply_gamma(const skcms_TransferFunction* tf, F x) {
316
0
    U32 sign;
317
0
    x = strip_sign(x, &sign);
318
0
    return apply_sign(approx_pow(x, tf->g), sign);
319
0
}
320
321
0
SI F apply_pq(const skcms_TransferFunction* tf, F x) {
322
0
    U32 bits = bit_pun<U32>(x),
323
0
        sign = bits & 0x80000000;
324
0
    x = bit_pun<F>(bits ^ sign);
325
326
0
    F v = approx_pow(max_(tf->a + tf->b * approx_pow(x, tf->c), F0)
327
0
                       / (tf->d + tf->e * approx_pow(x, tf->c)),
328
0
                     tf->f);
329
330
0
    return bit_pun<F>(sign | bit_pun<U32>(v));
331
0
}
332
333
0
SI F apply_hlg(const skcms_TransferFunction* tf, F x) {
334
0
    const float R = tf->a, G = tf->b,
335
0
                a = tf->c, b = tf->d, c = tf->e,
336
0
                K = tf->f + 1;
337
0
    U32 bits = bit_pun<U32>(x),
338
0
        sign = bits & 0x80000000;
339
0
    x = bit_pun<F>(bits ^ sign);
340
341
0
    F v = if_then_else(x*R <= 1, approx_pow(x*R, G)
342
0
                               , approx_exp((x-c)*a) + b);
343
344
0
    return K*bit_pun<F>(sign | bit_pun<U32>(v));
345
0
}
346
347
0
SI F apply_hlginv(const skcms_TransferFunction* tf, F x) {
348
0
    const float R = tf->a, G = tf->b,
349
0
                a = tf->c, b = tf->d, c = tf->e,
350
0
                K = tf->f + 1;
351
0
    U32 bits = bit_pun<U32>(x),
352
0
        sign = bits & 0x80000000;
353
0
    x = bit_pun<F>(bits ^ sign);
354
0
    x /= K;
355
356
0
    F v = if_then_else(x <= 1, R * approx_pow(x, G)
357
0
                             , a * approx_log(x - b) + c);
358
359
0
    return bit_pun<F>(sign | bit_pun<U32>(v));
360
0
}
361
362
// Compute the luminance Y used in the HLG OOTF. This is equivalent to computing the dot product
363
// with the vector [0.2627 0.678  0.0593] in Rec2020 primaries, but is performed in the XYZD50
364
// space to simplify the pipeline.
365
0
SI F compute_Y_in_xyzd50(F x, F y, F z) {
366
0
  return -0.02831655f * x +
367
0
          1.00995452f * y +
368
0
          0.02102382f * z;
369
0
}
370
371
// Strided loads and stores of N values, starting from p.
372
template <typename T, typename P>
373
0
SI T load_3(const P* p) {
374
#if N == 1
375
    return (T)p[0];
376
#elif N == 4
377
    return T{p[ 0],p[ 3],p[ 6],p[ 9]};
378
#elif N == 8
379
    return T{p[ 0],p[ 3],p[ 6],p[ 9], p[12],p[15],p[18],p[21]};
380
#elif N == 16
381
    return T{p[ 0],p[ 3],p[ 6],p[ 9], p[12],p[15],p[18],p[21],
382
             p[24],p[27],p[30],p[33], p[36],p[39],p[42],p[45]};
383
#endif
384
0
}
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::load_3<unsigned int __vector(4), unsigned char>(unsigned char const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned int __vector(4) skcms_private::baseline::load_3<unsigned int __vector(4), unsigned short>(unsigned short const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:unsigned short __vector(4) skcms_private::baseline::load_3<unsigned short __vector(4), unsigned short>(unsigned short const*)
Unexecuted instantiation: skcms_TransformBaseline.cc:float __vector(4) skcms_private::baseline::load_3<float __vector(4), float>(float const*)
385
386
template <typename T, typename P>
387
0
SI T load_4(const P* p) {
388
#if N == 1
389
    return (T)p[0];
390
#elif N == 4
391
    return T{p[ 0],p[ 4],p[ 8],p[12]};
392
#elif N == 8
393
    return T{p[ 0],p[ 4],p[ 8],p[12], p[16],p[20],p[24],p[28]};
394
#elif N == 16
395
    return T{p[ 0],p[ 4],p[ 8],p[12], p[16],p[20],p[24],p[28],
396
             p[32],p[36],p[40],p[44], p[48],p[52],p[56],p[60]};
397
#endif
398
0
}
399
400
template <typename T, typename P>
401
0
SI void store_3(P* p, const T& v) {
402
#if N == 1
403
    p[0] = v;
404
#elif N == 4
405
    p[ 0] = v[ 0]; p[ 3] = v[ 1]; p[ 6] = v[ 2]; p[ 9] = v[ 3];
406
#elif N == 8
407
    p[ 0] = v[ 0]; p[ 3] = v[ 1]; p[ 6] = v[ 2]; p[ 9] = v[ 3];
408
    p[12] = v[ 4]; p[15] = v[ 5]; p[18] = v[ 6]; p[21] = v[ 7];
409
#elif N == 16
410
    p[ 0] = v[ 0]; p[ 3] = v[ 1]; p[ 6] = v[ 2]; p[ 9] = v[ 3];
411
    p[12] = v[ 4]; p[15] = v[ 5]; p[18] = v[ 6]; p[21] = v[ 7];
412
    p[24] = v[ 8]; p[27] = v[ 9]; p[30] = v[10]; p[33] = v[11];
413
    p[36] = v[12]; p[39] = v[13]; p[42] = v[14]; p[45] = v[15];
414
#endif
415
0
}
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store_3<unsigned char __vector(4), unsigned char>(unsigned char*, unsigned char __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store_3<unsigned short __vector(4), unsigned short>(unsigned short*, unsigned short __vector(4) const&)
Unexecuted instantiation: skcms_TransformBaseline.cc:void skcms_private::baseline::store_3<float __vector(4), float>(float*, float __vector(4) const&)
416
417
template <typename T, typename P>
418
0
SI void store_4(P* p, const T& v) {
419
#if N == 1
420
    p[0] = v;
421
#elif N == 4
422
    p[ 0] = v[ 0]; p[ 4] = v[ 1]; p[ 8] = v[ 2]; p[12] = v[ 3];
423
#elif N == 8
424
    p[ 0] = v[ 0]; p[ 4] = v[ 1]; p[ 8] = v[ 2]; p[12] = v[ 3];
425
    p[16] = v[ 4]; p[20] = v[ 5]; p[24] = v[ 6]; p[28] = v[ 7];
426
#elif N == 16
427
    p[ 0] = v[ 0]; p[ 4] = v[ 1]; p[ 8] = v[ 2]; p[12] = v[ 3];
428
    p[16] = v[ 4]; p[20] = v[ 5]; p[24] = v[ 6]; p[28] = v[ 7];
429
    p[32] = v[ 8]; p[36] = v[ 9]; p[40] = v[10]; p[44] = v[11];
430
    p[48] = v[12]; p[52] = v[13]; p[56] = v[14]; p[60] = v[15];
431
#endif
432
0
}
433
434
435
0
SI U8 gather_8(const uint8_t* p, I32 ix) {
436
#if N == 1
437
    U8 v = p[ix];
438
#elif N == 4
439
    U8 v = { p[ix[0]], p[ix[1]], p[ix[2]], p[ix[3]] };
440
#elif N == 8
441
    U8 v = { p[ix[0]], p[ix[1]], p[ix[2]], p[ix[3]],
442
             p[ix[4]], p[ix[5]], p[ix[6]], p[ix[7]] };
443
#elif N == 16
444
    U8 v = { p[ix[ 0]], p[ix[ 1]], p[ix[ 2]], p[ix[ 3]],
445
             p[ix[ 4]], p[ix[ 5]], p[ix[ 6]], p[ix[ 7]],
446
             p[ix[ 8]], p[ix[ 9]], p[ix[10]], p[ix[11]],
447
             p[ix[12]], p[ix[13]], p[ix[14]], p[ix[15]] };
448
#endif
449
0
    return v;
450
0
}
451
452
0
SI U16 gather_16(const uint8_t* p, I32 ix) {
453
    // Load the i'th 16-bit value from p.
454
0
    auto load_16 = [p](int i) {
455
0
        return load<uint16_t>(p + 2*i);
456
0
    };
457
#if N == 1
458
    U16 v = load_16(ix);
459
#elif N == 4
460
    U16 v = { load_16(ix[0]), load_16(ix[1]), load_16(ix[2]), load_16(ix[3]) };
461
#elif N == 8
462
    U16 v = { load_16(ix[0]), load_16(ix[1]), load_16(ix[2]), load_16(ix[3]),
463
              load_16(ix[4]), load_16(ix[5]), load_16(ix[6]), load_16(ix[7]) };
464
#elif N == 16
465
    U16 v = { load_16(ix[ 0]), load_16(ix[ 1]), load_16(ix[ 2]), load_16(ix[ 3]),
466
              load_16(ix[ 4]), load_16(ix[ 5]), load_16(ix[ 6]), load_16(ix[ 7]),
467
              load_16(ix[ 8]), load_16(ix[ 9]), load_16(ix[10]), load_16(ix[11]),
468
              load_16(ix[12]), load_16(ix[13]), load_16(ix[14]), load_16(ix[15]) };
469
#endif
470
0
    return v;
471
0
}
472
473
0
SI U32 gather_32(const uint8_t* p, I32 ix) {
474
    // Load the i'th 32-bit value from p.
475
0
    auto load_32 = [p](int i) {
476
0
        return load<uint32_t>(p + 4*i);
477
0
    };
478
#if N == 1
479
    U32 v = load_32(ix);
480
#elif N == 4
481
    U32 v = { load_32(ix[0]), load_32(ix[1]), load_32(ix[2]), load_32(ix[3]) };
482
#elif N == 8
483
    U32 v = { load_32(ix[0]), load_32(ix[1]), load_32(ix[2]), load_32(ix[3]),
484
              load_32(ix[4]), load_32(ix[5]), load_32(ix[6]), load_32(ix[7]) };
485
#elif N == 16
486
    U32 v = { load_32(ix[ 0]), load_32(ix[ 1]), load_32(ix[ 2]), load_32(ix[ 3]),
487
              load_32(ix[ 4]), load_32(ix[ 5]), load_32(ix[ 6]), load_32(ix[ 7]),
488
              load_32(ix[ 8]), load_32(ix[ 9]), load_32(ix[10]), load_32(ix[11]),
489
              load_32(ix[12]), load_32(ix[13]), load_32(ix[14]), load_32(ix[15]) };
490
#endif
491
    // TODO: AVX2 and AVX-512 gathers (c.f. gather_24).
492
0
    return v;
493
0
}
494
495
0
SI U32 gather_24(const uint8_t* p, I32 ix) {
496
    // Load the i'th 24-bit value from p, and 1 extra byte.
497
0
    auto load_24_32 = [p](int i) {
498
0
        return load<uint32_t>(p + 3*i);
499
0
    };
500
501
    // Now load multiples of 4 bytes (r,g,b, then a junk byte).
502
#if N == 1
503
    U32 v = load_24_32(ix);
504
#elif N == 4
505
    U32 v = { load_24_32(ix[0]), load_24_32(ix[1]), load_24_32(ix[2]), load_24_32(ix[3]) };
506
#elif N == 8 && !defined(USING_AVX2)
507
    U32 v = { load_24_32(ix[0]), load_24_32(ix[1]), load_24_32(ix[2]), load_24_32(ix[3]),
508
              load_24_32(ix[4]), load_24_32(ix[5]), load_24_32(ix[6]), load_24_32(ix[7]) };
509
#elif N == 8
510
    (void)load_24_32;
511
    // The gather instruction here doesn't need any particular alignment,
512
    // but the intrinsic takes a const int*.
513
    const int* p4 = bit_pun<const int*>(p);
514
    I32 zero = { 0, 0, 0, 0,  0, 0, 0, 0},
515
        mask = {-1,-1,-1,-1, -1,-1,-1,-1};
516
    #if defined(__clang__)
517
        U32 v = (U32)__builtin_ia32_gatherd_d256(zero, p4, 3*ix, mask, 1);
518
    #elif defined(__GNUC__)
519
        U32 v = (U32)__builtin_ia32_gathersiv8si(zero, p4, 3*ix, mask, 1);
520
    #endif
521
#elif N == 16
522
    (void)load_24_32;
523
    // The intrinsic is supposed to take const void* now, but it takes const int*, just like AVX2.
524
    // And AVX-512 swapped the order of arguments.  :/
525
    const int* p4 = bit_pun<const int*>(p);
526
    U32 v = (U32)_mm512_i32gather_epi32((__m512i)(3*ix), p4, 1);
527
#endif
528
529
    // Mask off the junk byte, leaving r,g,b in low 24 bits.
530
0
    return v & 0x00FFFFFF;
531
0
}
532
533
#if !defined(__arm__)
534
0
    SI void gather_48(const uint8_t* p, I32 ix, U64* v) {
535
        // Load the i'th 48-bit value from p, and 2 extra bytes.
536
0
        auto load_48_64 = [p](int i) {
537
0
            return load<uint64_t>(p + 6*i);
538
0
        };
539
540
    #if N == 1
541
        *v = load_48_64(ix);
542
    #elif N == 4
543
        *v = U64{
544
0
            load_48_64(ix[0]), load_48_64(ix[1]), load_48_64(ix[2]), load_48_64(ix[3]),
545
0
        };
546
    #elif N == 8 && !defined(USING_AVX2)
547
        *v = U64{
548
            load_48_64(ix[0]), load_48_64(ix[1]), load_48_64(ix[2]), load_48_64(ix[3]),
549
            load_48_64(ix[4]), load_48_64(ix[5]), load_48_64(ix[6]), load_48_64(ix[7]),
550
        };
551
    #elif N == 8
552
        (void)load_48_64;
553
        typedef int32_t   __attribute__((vector_size(16))) Half_I32;
554
        typedef long long __attribute__((vector_size(32))) Half_I64;
555
556
        // The gather instruction here doesn't need any particular alignment,
557
        // but the intrinsic takes a const long long*.
558
        const long long int* p8 = bit_pun<const long long int*>(p);
559
560
        Half_I64 zero = { 0, 0, 0, 0},
561
                 mask = {-1,-1,-1,-1};
562
563
        ix *= 6;
564
        Half_I32 ix_lo = { ix[0], ix[1], ix[2], ix[3] },
565
                 ix_hi = { ix[4], ix[5], ix[6], ix[7] };
566
567
        #if defined(__clang__)
568
            Half_I64 lo = (Half_I64)__builtin_ia32_gatherd_q256(zero, p8, ix_lo, mask, 1),
569
                     hi = (Half_I64)__builtin_ia32_gatherd_q256(zero, p8, ix_hi, mask, 1);
570
        #elif defined(__GNUC__)
571
            Half_I64 lo = (Half_I64)__builtin_ia32_gathersiv4di(zero, p8, ix_lo, mask, 1),
572
                     hi = (Half_I64)__builtin_ia32_gathersiv4di(zero, p8, ix_hi, mask, 1);
573
        #endif
574
        store((char*)v +  0, lo);
575
        store((char*)v + 32, hi);
576
    #elif N == 16
577
        (void)load_48_64;
578
        const long long int* p8 = bit_pun<const long long int*>(p);
579
        __m512i lo = _mm512_i32gather_epi64(_mm512_extracti32x8_epi32((__m512i)(6*ix), 0), p8, 1),
580
                hi = _mm512_i32gather_epi64(_mm512_extracti32x8_epi32((__m512i)(6*ix), 1), p8, 1);
581
        store((char*)v +  0, lo);
582
        store((char*)v + 64, hi);
583
    #endif
584
585
0
        *v &= 0x0000FFFFFFFFFFFFULL;
586
0
    }
587
#endif
588
589
0
SI F F_from_U8(U8 v) {
590
0
    return cast<F>(v) * (1/255.0f);
591
0
}
592
593
0
SI F F_from_U16_BE(U16 v) {
594
    // All 16-bit ICC values are big-endian, so we byte swap before converting to float.
595
    // MSVC catches the "loss" of data here in the portable path, so we also make sure to mask.
596
0
    U16 lo = (v >> 8),
597
0
        hi = (v << 8) & 0xffff;
598
0
    return cast<F>(lo|hi) * (1/65535.0f);
599
0
}
600
601
0
SI U16 U16_from_F(F v) {
602
    // 65535 == inf in FP16, so promote to FP32 before converting.
603
0
    return cast<U16>(cast<V<float>>(v) * 65535 + 0.5f);
604
0
}
605
606
0
SI F minus_1_ulp(F v) {
607
0
    return bit_pun<F>( bit_pun<U32>(v) - 1 );
608
0
}
609
610
0
SI F table(const skcms_Curve* curve, F v) {
611
    // Clamp the input to [0,1], then scale to a table index.
612
0
    F ix = max_(F0, min_(v, F1)) * (float)(curve->table_entries - 1);
613
614
    // We'll look up (equal or adjacent) entries at lo and hi, then lerp by t between the two.
615
0
    I32 lo = cast<I32>(            ix      ),
616
0
        hi = cast<I32>(minus_1_ulp(ix+1.0f));
617
0
    F t = ix - cast<F>(lo);  // i.e. the fractional part of ix.
618
619
    // TODO: can we load l and h simultaneously?  Each entry in 'h' is either
620
    // the same as in 'l' or adjacent.  We have a rough idea that's it'd always be safe
621
    // to read adjacent entries and perhaps underflow the table by a byte or two
622
    // (it'd be junk, but always safe to read).  Not sure how to lerp yet.
623
0
    F l,h;
624
0
    if (curve->table_8) {
625
0
        l = F_from_U8(gather_8(curve->table_8, lo));
626
0
        h = F_from_U8(gather_8(curve->table_8, hi));
627
0
    } else {
628
0
        l = F_from_U16_BE(gather_16(curve->table_16, lo));
629
0
        h = F_from_U16_BE(gather_16(curve->table_16, hi));
630
0
    }
631
0
    return l + (h-l)*t;
632
0
}
633
634
0
SI void sample_clut_8(const uint8_t* grid_8, I32 ix, F* r, F* g, F* b) {
635
0
    U32 rgb = gather_24(grid_8, ix);
636
637
0
    *r = cast<F>((rgb >>  0) & 0xff) * (1/255.0f);
638
0
    *g = cast<F>((rgb >>  8) & 0xff) * (1/255.0f);
639
0
    *b = cast<F>((rgb >> 16) & 0xff) * (1/255.0f);
640
0
}
641
642
0
SI void sample_clut_8(const uint8_t* grid_8, I32 ix, F* r, F* g, F* b, F* a) {
643
    // TODO: don't forget to optimize gather_32().
644
0
    U32 rgba = gather_32(grid_8, ix);
645
646
0
    *r = cast<F>((rgba >>  0) & 0xff) * (1/255.0f);
647
0
    *g = cast<F>((rgba >>  8) & 0xff) * (1/255.0f);
648
0
    *b = cast<F>((rgba >> 16) & 0xff) * (1/255.0f);
649
0
    *a = cast<F>((rgba >> 24) & 0xff) * (1/255.0f);
650
0
}
651
652
0
SI void sample_clut_16(const uint8_t* grid_16, I32 ix, F* r, F* g, F* b) {
653
#if defined(__arm__) || defined(__loongarch_sx)
654
    // This is up to 2x faster on 32-bit ARM than the #else-case fast path.
655
    *r = F_from_U16_BE(gather_16(grid_16, 3*ix+0));
656
    *g = F_from_U16_BE(gather_16(grid_16, 3*ix+1));
657
    *b = F_from_U16_BE(gather_16(grid_16, 3*ix+2));
658
#else
659
    // This strategy is much faster for 64-bit builds, and fine for 32-bit x86 too.
660
0
    U64 rgb;
661
0
    gather_48(grid_16, ix, &rgb);
662
0
    rgb = swap_endian_16x4(rgb);
663
664
0
    *r = cast<F>((rgb >>  0) & 0xffff) * (1/65535.0f);
665
0
    *g = cast<F>((rgb >> 16) & 0xffff) * (1/65535.0f);
666
0
    *b = cast<F>((rgb >> 32) & 0xffff) * (1/65535.0f);
667
0
#endif
668
0
}
669
670
0
SI void sample_clut_16(const uint8_t* grid_16, I32 ix, F* r, F* g, F* b, F* a) {
671
    // TODO: gather_64()-based fast path?
672
0
    *r = F_from_U16_BE(gather_16(grid_16, 4*ix+0));
673
0
    *g = F_from_U16_BE(gather_16(grid_16, 4*ix+1));
674
0
    *b = F_from_U16_BE(gather_16(grid_16, 4*ix+2));
675
0
    *a = F_from_U16_BE(gather_16(grid_16, 4*ix+3));
676
0
}
677
678
static void clut(uint32_t input_channels, uint32_t output_channels,
679
                 const uint8_t grid_points[4], const uint8_t* grid_8, const uint8_t* grid_16,
680
0
                 F* r, F* g, F* b, F* a) {
681
682
0
    const int dim = (int)input_channels;
683
0
    if (dim <= 0 || dim > 4) {
684
0
        return;
685
0
    }
686
0
    assert (output_channels == 3 ||
687
0
            output_channels == 4);
688
689
    // For each of these arrays, think foo[2*dim], but we use foo[8] since we know dim <= 4.
690
0
    I32 index [8] = {0,0,0,0, 0,0,0,0};  // Index contribution by dimension, first low from 0, then high from 4.
691
0
    F   weight[8] = {F0,F0,F0,F0, F0,F0,F0,F0};  // Weight for each contribution, again first low, then high.
692
693
    // O(dim) work first: calculate index,weight from r,g,b,a.
694
0
    const F inputs[] = { *r,*g,*b,*a };
695
0
    for (int i = dim-1, stride = 1; i >= 0; i--) {
696
        // x is where we logically want to sample the grid in the i-th dimension.
697
        // We MUST clamp to [0,1] here to avoid negative indices.
698
0
        F x = max_(F0, min_(inputs[i], F1)) * (float)(grid_points[i] - 1);
699
700
        // But we can't index at floats.  lo and hi are the two integer grid points surrounding x.
701
0
        I32 lo = cast<I32>(            x      ),   // i.e. trunc(x) == floor(x) here.
702
0
            hi = cast<I32>(minus_1_ulp(x+1.0f));
703
        // Notice how we fold in the accumulated stride across previous dimensions here.
704
0
        index[i+0] = lo * stride;
705
0
        index[i+4] = hi * stride;
706
0
        stride *= grid_points[i];
707
708
        // We'll interpolate between those two integer grid points by t.
709
0
        F t = x - cast<F>(lo);  // i.e. fract(x)
710
0
        weight[i+0] = 1-t;
711
0
        weight[i+4] = t;
712
0
    }
713
714
0
    *r = *g = *b = F0;
715
0
    if (output_channels == 4) {
716
0
        *a = F0;
717
0
    }
718
719
    // We'll sample 2^dim == 1<<dim table entries per pixel,
720
    // in all combinations of low and high in each dimension.
721
0
    for (int combo = 0; combo < (1<<dim); combo++) {  // This loop can be done in any order.
722
723
        // Each of these upcoming (combo&N)*K expressions here evaluates to 0 or 4,
724
        // where 0 selects the low index contribution and its weight 1-t,
725
        // or 4 the high index contribution and its weight t.
726
727
        // Since 0<dim≤4, we can always just start off with the 0-th channel,
728
        // then handle the others conditionally.
729
0
        I32 ix = index [0 + (combo&1)*4];
730
0
        F    w = weight[0 + (combo&1)*4];
731
732
0
        switch ((dim-1)&3) {  // This lets the compiler know there are no other cases to handle.
733
0
            case 3: ix += index [3 + (combo&8)/2];
734
0
                    w  *= weight[3 + (combo&8)/2];
735
0
                    SKCMS_FALLTHROUGH;
736
                    // fall through
737
738
0
            case 2: ix += index [2 + (combo&4)*1];
739
0
                    w  *= weight[2 + (combo&4)*1];
740
0
                    SKCMS_FALLTHROUGH;
741
                    // fall through
742
743
0
            case 1: ix += index [1 + (combo&2)*2];
744
0
                    w  *= weight[1 + (combo&2)*2];
745
0
        }
746
747
0
        F R,G,B,A=F0;
748
0
        if (output_channels == 3) {
749
0
            if (grid_8) { sample_clut_8 (grid_8 ,ix, &R,&G,&B); }
750
0
            else        { sample_clut_16(grid_16,ix, &R,&G,&B); }
751
0
        } else {
752
0
            if (grid_8) { sample_clut_8 (grid_8 ,ix, &R,&G,&B,&A); }
753
0
            else        { sample_clut_16(grid_16,ix, &R,&G,&B,&A); }
754
0
        }
755
0
        *r += w*R;
756
0
        *g += w*G;
757
0
        *b += w*B;
758
0
        *a += w*A;
759
0
    }
760
0
}
761
762
0
static void clut(const skcms_A2B* a2b, F* r, F* g, F* b, F a) {
763
0
    clut(a2b->input_channels, a2b->output_channels,
764
0
         a2b->grid_points, a2b->grid_8, a2b->grid_16,
765
0
         r,g,b,&a);
766
0
}
767
0
static void clut(const skcms_B2A* b2a, F* r, F* g, F* b, F* a) {
768
0
    clut(b2a->input_channels, b2a->output_channels,
769
0
         b2a->grid_points, b2a->grid_8, b2a->grid_16,
770
0
         r,g,b,a);
771
0
}
772
773
struct NoCtx {};
774
775
struct Ctx {
776
    const void* fArg;
777
0
    operator NoCtx()                    { return NoCtx{}; }
778
0
    template <typename T> operator T*() { return (const T*)fArg; }
Unexecuted instantiation: skcms_private::baseline::Ctx::operator skcms_Matrix3x3 const*<skcms_Matrix3x3 const>()
Unexecuted instantiation: skcms_private::baseline::Ctx::operator skcms_Matrix3x4 const*<skcms_Matrix3x4 const>()
Unexecuted instantiation: skcms_private::baseline::Ctx::operator skcms_TransferFunction const*<skcms_TransferFunction const>()
Unexecuted instantiation: skcms_private::baseline::Ctx::operator void const*<void const>()
Unexecuted instantiation: skcms_private::baseline::Ctx::operator skcms_Curve const*<skcms_Curve const>()
Unexecuted instantiation: skcms_private::baseline::Ctx::operator skcms_A2B const*<skcms_A2B const>()
Unexecuted instantiation: skcms_private::baseline::Ctx::operator skcms_B2A const*<skcms_B2A const>()
779
};
780
781
#define STAGE_PARAMS(MAYBE_REF) SKCMS_MAYBE_UNUSED const char* src, \
782
                                SKCMS_MAYBE_UNUSED char* dst,       \
783
                                SKCMS_MAYBE_UNUSED F MAYBE_REF r,   \
784
                                SKCMS_MAYBE_UNUSED F MAYBE_REF g,   \
785
                                SKCMS_MAYBE_UNUSED F MAYBE_REF b,   \
786
                                SKCMS_MAYBE_UNUSED F MAYBE_REF a,   \
787
                                SKCMS_MAYBE_UNUSED int i
788
789
#if SKCMS_HAS_MUSTTAIL
790
791
    // Stages take a stage list, and each stage is responsible for tail-calling the next one.
792
    //
793
    // Unfortunately, we can't declare a StageFn as a function pointer which takes a pointer to
794
    // another StageFn; declaring this leads to a circular dependency. To avoid this, StageFn is
795
    // wrapped in a single-element `struct StageList` which we are able to forward-declare.
796
    struct StageList;
797
    using StageFn = void (*)(StageList stages, const void** ctx, STAGE_PARAMS());
798
    struct StageList {
799
        const StageFn* fn;
800
    };
801
802
    #define DECLARE_STAGE(name, arg, CALL_NEXT)                                 \
803
        SI void Exec_##name##_k(arg, STAGE_PARAMS(&));                          \
804
                                                                                \
805
0
        SI void Exec_##name(StageList list, const void** ctx, STAGE_PARAMS()) { \
806
0
            Exec_##name##_k(Ctx{*ctx}, src, dst, r, g, b, a, i);                \
807
0
            ++list.fn; ++ctx;                                                   \
808
0
            CALL_NEXT;                                                          \
809
0
        }                                                                       \
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_a8(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_g8(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_ga88(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_4444(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_565(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_888(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_8888(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_1010102(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_101010x_XR(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_10101010_XR(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_161616LE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_16161616LE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_161616BE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_16161616BE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_hhh(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_hhhh(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_fff(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_load_ffff(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_swap_rb(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_clamp(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_invert(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_force_opaque(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_premul(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_unpremul(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_matrix_3x3(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_matrix_3x4(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_lab_to_xyz(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_xyz_to_lab(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_gamma_r(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_gamma_g(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_gamma_b(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_gamma_a(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_gamma_rgb(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_tf_r(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_tf_g(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_tf_b(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_tf_a(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_tf_rgb(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_pq_r(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_pq_g(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_pq_b(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_pq_a(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_pq_rgb(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlg_r(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlg_g(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlg_b(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlg_a(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlg_rgb(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlg_ootf_scale(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlginv_r(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlginv_g(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlginv_b(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlginv_a(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlginv_rgb(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_hlginv_ootf_scale(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_table_r(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_table_g(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_table_b(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_table_a(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_clut_A2B(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_clut_B2A(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_a8(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_g8(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_ga88(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_4444(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_565(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_888(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_8888(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_1010102(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_161616LE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_16161616LE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_161616BE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_16161616BE(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_101010x_XR(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_10101010_XR(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_hhh(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_hhhh(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_fff(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
Unexecuted instantiation: skcms_TransformBaseline.cc:skcms_private::baseline::Exec_store_ffff(skcms_private::baseline::StageList, void const**, char const*, char*, float __vector(4), float __vector(4), float __vector(4), float __vector(4), int)
810
                                                                                \
811
        SI void Exec_##name##_k(arg, STAGE_PARAMS(&))
812
813
    #define STAGE(name, arg)                                                                \
814
        DECLARE_STAGE(name, arg, [[clang::musttail]] return (*list.fn)(list, ctx, src, dst, \
815
                                                                       r, g, b, a, i))
816
817
    #define FINAL_STAGE(name, arg) \
818
        DECLARE_STAGE(name, arg, /* Stop executing stages and return to the caller. */)
819
820
#else
821
822
    #define DECLARE_STAGE(name, arg)                            \
823
        SI void Exec_##name##_k(arg, STAGE_PARAMS(&));          \
824
                                                                \
825
        SI void Exec_##name(const void* ctx, STAGE_PARAMS(&)) { \
826
            Exec_##name##_k(Ctx{ctx}, src, dst, r, g, b, a, i); \
827
        }                                                       \
828
                                                                \
829
        SI void Exec_##name##_k(arg, STAGE_PARAMS(&))
830
831
    #define STAGE(name, arg)       DECLARE_STAGE(name, arg)
832
    #define FINAL_STAGE(name, arg) DECLARE_STAGE(name, arg)
833
834
#endif
835
836
0
STAGE(load_a8, NoCtx) {
837
0
    a = F_from_U8(load<U8>(src + 1*i));
838
0
}
839
840
0
STAGE(load_g8, NoCtx) {
841
0
    r = g = b = F_from_U8(load<U8>(src + 1*i));
842
0
}
843
844
0
STAGE(load_ga88, NoCtx) {
845
0
    U16 u16 = load<U16>(src + 2 * i);
846
0
    r = g = b = cast<F>((u16 >> 0) & 0xff) * (1 / 255.0f);
847
0
            a = cast<F>((u16 >> 8) & 0xff) * (1 / 255.0f);
848
0
}
849
850
0
STAGE(load_4444, NoCtx) {
851
0
    U16 abgr = load<U16>(src + 2*i);
852
853
0
    r = cast<F>((abgr >> 12) & 0xf) * (1/15.0f);
854
0
    g = cast<F>((abgr >>  8) & 0xf) * (1/15.0f);
855
0
    b = cast<F>((abgr >>  4) & 0xf) * (1/15.0f);
856
0
    a = cast<F>((abgr >>  0) & 0xf) * (1/15.0f);
857
0
}
858
859
0
STAGE(load_565, NoCtx) {
860
0
    U16 rgb = load<U16>(src + 2*i);
861
862
0
    r = cast<F>(rgb & (uint16_t)(31<< 0)) * (1.0f / (31<< 0));
863
0
    g = cast<F>(rgb & (uint16_t)(63<< 5)) * (1.0f / (63<< 5));
864
0
    b = cast<F>(rgb & (uint16_t)(31<<11)) * (1.0f / (31<<11));
865
0
}
866
867
0
STAGE(load_888, NoCtx) {
868
0
    const uint8_t* rgb = (const uint8_t*)(src + 3*i);
869
#if defined(USING_NEON)
870
    // There's no uint8x4x3_t or vld3 load for it, so we'll load each rgb pixel one at
871
    // a time.  Since we're doing that, we might as well load them into 16-bit lanes.
872
    // (We'd even load into 32-bit lanes, but that's not possible on ARMv7.)
873
    uint8x8x3_t v = {{ vdup_n_u8(0), vdup_n_u8(0), vdup_n_u8(0) }};
874
    v = vld3_lane_u8(rgb+0, v, 0);
875
    v = vld3_lane_u8(rgb+3, v, 2);
876
    v = vld3_lane_u8(rgb+6, v, 4);
877
    v = vld3_lane_u8(rgb+9, v, 6);
878
879
    // Now if we squint, those 3 uint8x8_t we constructed are really U16s, easy to
880
    // convert to F.  (Again, U32 would be even better here if drop ARMv7 or split
881
    // ARMv7 and ARMv8 impls.)
882
    r = cast<F>((U16)v.val[0]) * (1/255.0f);
883
    g = cast<F>((U16)v.val[1]) * (1/255.0f);
884
    b = cast<F>((U16)v.val[2]) * (1/255.0f);
885
#else
886
0
    r = cast<F>(load_3<U32>(rgb+0) ) * (1/255.0f);
887
0
    g = cast<F>(load_3<U32>(rgb+1) ) * (1/255.0f);
888
0
    b = cast<F>(load_3<U32>(rgb+2) ) * (1/255.0f);
889
0
#endif
890
0
}
891
892
0
STAGE(load_8888, NoCtx) {
893
0
    U32 rgba = load<U32>(src + 4*i);
894
895
0
    r = cast<F>((rgba >>  0) & 0xff) * (1/255.0f);
896
0
    g = cast<F>((rgba >>  8) & 0xff) * (1/255.0f);
897
0
    b = cast<F>((rgba >> 16) & 0xff) * (1/255.0f);
898
0
    a = cast<F>((rgba >> 24) & 0xff) * (1/255.0f);
899
0
}
900
901
0
STAGE(load_1010102, NoCtx) {
902
0
    U32 rgba = load<U32>(src + 4*i);
903
904
0
    r = cast<F>((rgba >>  0) & 0x3ff) * (1/1023.0f);
905
0
    g = cast<F>((rgba >> 10) & 0x3ff) * (1/1023.0f);
906
0
    b = cast<F>((rgba >> 20) & 0x3ff) * (1/1023.0f);
907
0
    a = cast<F>((rgba >> 30) & 0x3  ) * (1/   3.0f);
908
0
}
909
910
0
STAGE(load_101010x_XR, NoCtx) {
911
0
    U32 rgba = load<U32>(src + 4*i);
912
0
    r = cast<F>(((rgba >>  0) & 0x3ff) - 384) / 510.0f;
913
0
    g = cast<F>(((rgba >> 10) & 0x3ff) - 384) / 510.0f;
914
0
    b = cast<F>(((rgba >> 20) & 0x3ff) - 384) / 510.0f;
915
0
}
916
917
0
STAGE(load_10101010_XR, NoCtx) {
918
0
    U64 rgba = load<U64>(src + 8 * i);
919
    // Each channel is 16 bits, where the 6 low bits are padding.
920
0
    r = cast<F>(((rgba >> ( 0+6)) & 0x3ff) - 384) / 510.0f;
921
0
    g = cast<F>(((rgba >> (16+6)) & 0x3ff) - 384) / 510.0f;
922
0
    b = cast<F>(((rgba >> (32+6)) & 0x3ff) - 384) / 510.0f;
923
0
    a = cast<F>(((rgba >> (48+6)) & 0x3ff) - 384) / 510.0f;
924
0
}
925
926
0
STAGE(load_161616LE, NoCtx) {
927
0
    uintptr_t ptr = (uintptr_t)(src + 6*i);
928
0
    assert( (ptr & 1) == 0 );                   // src must be 2-byte aligned for this
929
0
    const uint16_t* rgb = (const uint16_t*)ptr; // cast to const uint16_t* to be safe.
930
#if defined(USING_NEON)
931
    uint16x4x3_t v = vld3_u16(rgb);
932
    r = cast<F>((U16)v.val[0]) * (1/65535.0f);
933
    g = cast<F>((U16)v.val[1]) * (1/65535.0f);
934
    b = cast<F>((U16)v.val[2]) * (1/65535.0f);
935
#else
936
0
    r = cast<F>(load_3<U32>(rgb+0)) * (1/65535.0f);
937
0
    g = cast<F>(load_3<U32>(rgb+1)) * (1/65535.0f);
938
0
    b = cast<F>(load_3<U32>(rgb+2)) * (1/65535.0f);
939
0
#endif
940
0
}
941
942
0
STAGE(load_16161616LE, NoCtx) {
943
0
    uintptr_t ptr = (uintptr_t)(src + 8*i);
944
0
    assert( (ptr & 1) == 0 );                    // src must be 2-byte aligned for this
945
0
    const uint16_t* rgba = (const uint16_t*)ptr; // cast to const uint16_t* to be safe.
946
#if defined(USING_NEON)
947
    uint16x4x4_t v = vld4_u16(rgba);
948
    r = cast<F>((U16)v.val[0]) * (1/65535.0f);
949
    g = cast<F>((U16)v.val[1]) * (1/65535.0f);
950
    b = cast<F>((U16)v.val[2]) * (1/65535.0f);
951
    a = cast<F>((U16)v.val[3]) * (1/65535.0f);
952
#else
953
0
    U64 px = load<U64>(rgba);
954
955
0
    r = cast<F>((px >>  0) & 0xffff) * (1/65535.0f);
956
0
    g = cast<F>((px >> 16) & 0xffff) * (1/65535.0f);
957
0
    b = cast<F>((px >> 32) & 0xffff) * (1/65535.0f);
958
0
    a = cast<F>((px >> 48) & 0xffff) * (1/65535.0f);
959
0
#endif
960
0
}
961
962
0
STAGE(load_161616BE, NoCtx) {
963
0
    uintptr_t ptr = (uintptr_t)(src + 6*i);
964
0
    assert( (ptr & 1) == 0 );                   // src must be 2-byte aligned for this
965
0
    const uint16_t* rgb = (const uint16_t*)ptr; // cast to const uint16_t* to be safe.
966
#if defined(USING_NEON)
967
    uint16x4x3_t v = vld3_u16(rgb);
968
    r = cast<F>(swap_endian_16((U16)v.val[0])) * (1/65535.0f);
969
    g = cast<F>(swap_endian_16((U16)v.val[1])) * (1/65535.0f);
970
    b = cast<F>(swap_endian_16((U16)v.val[2])) * (1/65535.0f);
971
#else
972
0
    U32 R = load_3<U32>(rgb+0),
973
0
        G = load_3<U32>(rgb+1),
974
0
        B = load_3<U32>(rgb+2);
975
    // R,G,B are big-endian 16-bit, so byte swap them before converting to float.
976
0
    r = cast<F>((R & 0x00ff)<<8 | (R & 0xff00)>>8) * (1/65535.0f);
977
0
    g = cast<F>((G & 0x00ff)<<8 | (G & 0xff00)>>8) * (1/65535.0f);
978
0
    b = cast<F>((B & 0x00ff)<<8 | (B & 0xff00)>>8) * (1/65535.0f);
979
0
#endif
980
0
}
981
982
0
STAGE(load_16161616BE, NoCtx) {
983
0
    uintptr_t ptr = (uintptr_t)(src + 8*i);
984
0
    assert( (ptr & 1) == 0 );                    // src must be 2-byte aligned for this
985
0
    const uint16_t* rgba = (const uint16_t*)ptr; // cast to const uint16_t* to be safe.
986
#if defined(USING_NEON)
987
    uint16x4x4_t v = vld4_u16(rgba);
988
    r = cast<F>(swap_endian_16((U16)v.val[0])) * (1/65535.0f);
989
    g = cast<F>(swap_endian_16((U16)v.val[1])) * (1/65535.0f);
990
    b = cast<F>(swap_endian_16((U16)v.val[2])) * (1/65535.0f);
991
    a = cast<F>(swap_endian_16((U16)v.val[3])) * (1/65535.0f);
992
#else
993
0
    U64 px = swap_endian_16x4(load<U64>(rgba));
994
995
0
    r = cast<F>((px >>  0) & 0xffff) * (1/65535.0f);
996
0
    g = cast<F>((px >> 16) & 0xffff) * (1/65535.0f);
997
0
    b = cast<F>((px >> 32) & 0xffff) * (1/65535.0f);
998
0
    a = cast<F>((px >> 48) & 0xffff) * (1/65535.0f);
999
0
#endif
1000
0
}
1001
1002
0
STAGE(load_hhh, NoCtx) {
1003
0
    uintptr_t ptr = (uintptr_t)(src + 6*i);
1004
0
    assert( (ptr & 1) == 0 );                   // src must be 2-byte aligned for this
1005
0
    const uint16_t* rgb = (const uint16_t*)ptr; // cast to const uint16_t* to be safe.
1006
#if defined(USING_NEON)
1007
    uint16x4x3_t v = vld3_u16(rgb);
1008
    U16 R = (U16)v.val[0],
1009
        G = (U16)v.val[1],
1010
        B = (U16)v.val[2];
1011
#else
1012
0
    U16 R = load_3<U16>(rgb+0),
1013
0
        G = load_3<U16>(rgb+1),
1014
0
        B = load_3<U16>(rgb+2);
1015
0
#endif
1016
0
    r = F_from_Half(R);
1017
0
    g = F_from_Half(G);
1018
0
    b = F_from_Half(B);
1019
0
}
1020
1021
0
STAGE(load_hhhh, NoCtx) {
1022
0
    uintptr_t ptr = (uintptr_t)(src + 8*i);
1023
0
    assert( (ptr & 1) == 0 );                    // src must be 2-byte aligned for this
1024
0
    const uint16_t* rgba = (const uint16_t*)ptr; // cast to const uint16_t* to be safe.
1025
#if defined(USING_NEON)
1026
    uint16x4x4_t v = vld4_u16(rgba);
1027
    U16 R = (U16)v.val[0],
1028
        G = (U16)v.val[1],
1029
        B = (U16)v.val[2],
1030
        A = (U16)v.val[3];
1031
#else
1032
0
    U64 px = load<U64>(rgba);
1033
0
    U16 R = cast<U16>((px >>  0) & 0xffff),
1034
0
        G = cast<U16>((px >> 16) & 0xffff),
1035
0
        B = cast<U16>((px >> 32) & 0xffff),
1036
0
        A = cast<U16>((px >> 48) & 0xffff);
1037
0
#endif
1038
0
    r = F_from_Half(R);
1039
0
    g = F_from_Half(G);
1040
0
    b = F_from_Half(B);
1041
0
    a = F_from_Half(A);
1042
0
}
1043
1044
0
STAGE(load_fff, NoCtx) {
1045
0
    uintptr_t ptr = (uintptr_t)(src + 12*i);
1046
0
    assert( (ptr & 3) == 0 );                   // src must be 4-byte aligned for this
1047
0
    const float* rgb = (const float*)ptr;       // cast to const float* to be safe.
1048
#if defined(USING_NEON)
1049
    float32x4x3_t v = vld3q_f32(rgb);
1050
    r = (F)v.val[0];
1051
    g = (F)v.val[1];
1052
    b = (F)v.val[2];
1053
#else
1054
0
    r = load_3<F>(rgb+0);
1055
0
    g = load_3<F>(rgb+1);
1056
0
    b = load_3<F>(rgb+2);
1057
0
#endif
1058
0
}
1059
1060
0
STAGE(load_ffff, NoCtx) {
1061
0
    uintptr_t ptr = (uintptr_t)(src + 16*i);
1062
0
    assert( (ptr & 3) == 0 );                   // src must be 4-byte aligned for this
1063
0
    const float* rgba = (const float*)ptr;      // cast to const float* to be safe.
1064
#if defined(USING_NEON)
1065
    float32x4x4_t v = vld4q_f32(rgba);
1066
    r = (F)v.val[0];
1067
    g = (F)v.val[1];
1068
    b = (F)v.val[2];
1069
    a = (F)v.val[3];
1070
#else
1071
0
    r = load_4<F>(rgba+0);
1072
0
    g = load_4<F>(rgba+1);
1073
0
    b = load_4<F>(rgba+2);
1074
0
    a = load_4<F>(rgba+3);
1075
0
#endif
1076
0
}
1077
1078
0
STAGE(swap_rb, NoCtx) {
1079
0
    F t = r;
1080
0
    r = b;
1081
0
    b = t;
1082
0
}
1083
1084
0
STAGE(clamp, NoCtx) {
1085
0
    r = max_(F0, min_(r, F1));
1086
0
    g = max_(F0, min_(g, F1));
1087
0
    b = max_(F0, min_(b, F1));
1088
0
    a = max_(F0, min_(a, F1));
1089
0
}
1090
1091
0
STAGE(invert, NoCtx) {
1092
0
    r = F1 - r;
1093
0
    g = F1 - g;
1094
0
    b = F1 - b;
1095
0
    a = F1 - a;
1096
0
}
1097
1098
0
STAGE(force_opaque, NoCtx) {
1099
0
    a = F1;
1100
0
}
1101
1102
0
STAGE(premul, NoCtx) {
1103
0
    r *= a;
1104
0
    g *= a;
1105
0
    b *= a;
1106
0
}
1107
1108
0
STAGE(unpremul, NoCtx) {
1109
0
    F scale = if_then_else(F1 / a < INFINITY_, F1 / a, F0);
1110
0
    r *= scale;
1111
0
    g *= scale;
1112
0
    b *= scale;
1113
0
}
1114
1115
0
STAGE(matrix_3x3, const skcms_Matrix3x3* matrix) {
1116
0
    const float* m = &matrix->vals[0][0];
1117
1118
0
    F R = m[0]*r + m[1]*g + m[2]*b,
1119
0
      G = m[3]*r + m[4]*g + m[5]*b,
1120
0
      B = m[6]*r + m[7]*g + m[8]*b;
1121
1122
0
    r = R;
1123
0
    g = G;
1124
0
    b = B;
1125
0
}
1126
1127
0
STAGE(matrix_3x4, const skcms_Matrix3x4* matrix) {
1128
0
    const float* m = &matrix->vals[0][0];
1129
1130
0
    F R = m[0]*r + m[1]*g + m[ 2]*b + m[ 3],
1131
0
      G = m[4]*r + m[5]*g + m[ 6]*b + m[ 7],
1132
0
      B = m[8]*r + m[9]*g + m[10]*b + m[11];
1133
1134
0
    r = R;
1135
0
    g = G;
1136
0
    b = B;
1137
0
}
1138
1139
0
STAGE(lab_to_xyz, NoCtx) {
1140
    // The L*a*b values are in r,g,b, but normalized to [0,1].  Reconstruct them:
1141
0
    F L = r * 100.0f,
1142
0
      A = g * 255.0f - 128.0f,
1143
0
      B = b * 255.0f - 128.0f;
1144
1145
    // Convert to CIE XYZ.
1146
0
    F Y = (L + 16.0f) * (1/116.0f),
1147
0
      X = Y + A*(1/500.0f),
1148
0
      Z = Y - B*(1/200.0f);
1149
1150
0
    X = if_then_else(X*X*X > 0.008856f, X*X*X, (X - (16/116.0f)) * (1/7.787f));
1151
0
    Y = if_then_else(Y*Y*Y > 0.008856f, Y*Y*Y, (Y - (16/116.0f)) * (1/7.787f));
1152
0
    Z = if_then_else(Z*Z*Z > 0.008856f, Z*Z*Z, (Z - (16/116.0f)) * (1/7.787f));
1153
1154
    // Adjust to XYZD50 illuminant, and stuff back into r,g,b for the next op.
1155
0
    r = X * 0.9642f;
1156
0
    g = Y          ;
1157
0
    b = Z * 0.8249f;
1158
0
}
1159
1160
// As above, in reverse.
1161
0
STAGE(xyz_to_lab, NoCtx) {
1162
0
    F X = r * (1/0.9642f),
1163
0
      Y = g,
1164
0
      Z = b * (1/0.8249f);
1165
1166
0
    X = if_then_else(X > 0.008856f, approx_pow(X, 1/3.0f), X*7.787f + (16/116.0f));
1167
0
    Y = if_then_else(Y > 0.008856f, approx_pow(Y, 1/3.0f), Y*7.787f + (16/116.0f));
1168
0
    Z = if_then_else(Z > 0.008856f, approx_pow(Z, 1/3.0f), Z*7.787f + (16/116.0f));
1169
1170
0
    F L = Y*116.0f - 16.0f,
1171
0
      A = (X-Y)*500.0f,
1172
0
      B = (Y-Z)*200.0f;
1173
1174
0
    r = L * (1/100.f);
1175
0
    g = (A + 128.0f) * (1/255.0f);
1176
0
    b = (B + 128.0f) * (1/255.0f);
1177
0
}
1178
1179
0
STAGE(gamma_r, const skcms_TransferFunction* tf) { r = apply_gamma(tf, r); }
1180
0
STAGE(gamma_g, const skcms_TransferFunction* tf) { g = apply_gamma(tf, g); }
1181
0
STAGE(gamma_b, const skcms_TransferFunction* tf) { b = apply_gamma(tf, b); }
1182
0
STAGE(gamma_a, const skcms_TransferFunction* tf) { a = apply_gamma(tf, a); }
1183
1184
0
STAGE(gamma_rgb, const skcms_TransferFunction* tf) {
1185
0
    r = apply_gamma(tf, r);
1186
0
    g = apply_gamma(tf, g);
1187
0
    b = apply_gamma(tf, b);
1188
0
}
1189
1190
0
STAGE(tf_r, const skcms_TransferFunction* tf) { r = apply_tf(tf, r); }
1191
0
STAGE(tf_g, const skcms_TransferFunction* tf) { g = apply_tf(tf, g); }
1192
0
STAGE(tf_b, const skcms_TransferFunction* tf) { b = apply_tf(tf, b); }
1193
0
STAGE(tf_a, const skcms_TransferFunction* tf) { a = apply_tf(tf, a); }
1194
1195
0
STAGE(tf_rgb, const skcms_TransferFunction* tf) {
1196
0
    r = apply_tf(tf, r);
1197
0
    g = apply_tf(tf, g);
1198
0
    b = apply_tf(tf, b);
1199
0
}
1200
1201
0
STAGE(pq_r, const skcms_TransferFunction* tf) { r = apply_pq(tf, r); }
1202
0
STAGE(pq_g, const skcms_TransferFunction* tf) { g = apply_pq(tf, g); }
1203
0
STAGE(pq_b, const skcms_TransferFunction* tf) { b = apply_pq(tf, b); }
1204
0
STAGE(pq_a, const skcms_TransferFunction* tf) { a = apply_pq(tf, a); }
1205
1206
0
STAGE(pq_rgb, const skcms_TransferFunction* tf) {
1207
0
    r = apply_pq(tf, r);
1208
0
    g = apply_pq(tf, g);
1209
0
    b = apply_pq(tf, b);
1210
0
}
1211
1212
0
STAGE(hlg_r, const skcms_TransferFunction* tf) { r = apply_hlg(tf, r); }
1213
0
STAGE(hlg_g, const skcms_TransferFunction* tf) { g = apply_hlg(tf, g); }
1214
0
STAGE(hlg_b, const skcms_TransferFunction* tf) { b = apply_hlg(tf, b); }
1215
0
STAGE(hlg_a, const skcms_TransferFunction* tf) { a = apply_hlg(tf, a); }
1216
1217
0
STAGE(hlg_rgb, const skcms_TransferFunction* tf) {
1218
0
    r = apply_hlg(tf, r);
1219
0
    g = apply_hlg(tf, g);
1220
0
    b = apply_hlg(tf, b);
1221
0
}
1222
1223
// Apply the HLG Reference OOTF, as described in ITU-R BT.2100-3 Table 5.
1224
0
STAGE(hlg_ootf_scale, const void*) {
1225
    // Compute Y in the XYZD50 primaries.
1226
0
    F Y = compute_Y_in_xyzd50(r, g, b);
1227
1228
    // Apply the gamma of 1.2.
1229
0
    const float gamma_minus_1 = 0.2f;
1230
0
    U32 sign;
1231
0
    Y = strip_sign(Y, &sign);
1232
0
    F Y_to_gamma_minus1 = apply_sign(approx_pow(Y, gamma_minus_1), sign);
1233
0
    r = r * Y_to_gamma_minus1;
1234
0
    g = g * Y_to_gamma_minus1;
1235
0
    b = b * Y_to_gamma_minus1;
1236
1237
    // Scale to the reference peak white (1000 nits) to get display luminance. Then divide by the
1238
    // HDR reference white (203 nits), to get a value in relative linear color space.
1239
0
    r *= 1000.0f / 203.0f;
1240
0
    g *= 1000.0f / 203.0f;
1241
0
    b *= 1000.0f / 203.0f;
1242
0
}
1243
1244
0
STAGE(hlginv_r, const skcms_TransferFunction* tf) { r = apply_hlginv(tf, r); }
1245
0
STAGE(hlginv_g, const skcms_TransferFunction* tf) { g = apply_hlginv(tf, g); }
1246
0
STAGE(hlginv_b, const skcms_TransferFunction* tf) { b = apply_hlginv(tf, b); }
1247
0
STAGE(hlginv_a, const skcms_TransferFunction* tf) { a = apply_hlginv(tf, a); }
1248
1249
0
STAGE(hlginv_rgb, const skcms_TransferFunction* tf) {
1250
0
    r = apply_hlginv(tf, r);
1251
0
    g = apply_hlginv(tf, g);
1252
0
    b = apply_hlginv(tf, b);
1253
0
}
1254
1255
// Perform the inverse of the operation in hlg_ootf_scale.
1256
0
STAGE(hlginv_ootf_scale, const void*) {
1257
0
    r *= (203.f / 1000.0f);
1258
0
    g *= (203.f / 1000.0f);
1259
0
    b *= (203.f / 1000.0f);
1260
1261
0
    const float gamma_inv_minus_1 = 1.0f / 1.2f - 1.0f;
1262
0
    F Y = compute_Y_in_xyzd50(r, g, b);
1263
0
    U32 sign;
1264
0
    Y = strip_sign(Y, &sign);
1265
0
    F Y_to_gamma_minus1 = apply_sign(approx_pow(Y, gamma_inv_minus_1), sign);
1266
1267
0
    r = r * Y_to_gamma_minus1;
1268
0
    g = g * Y_to_gamma_minus1;
1269
0
    b = b * Y_to_gamma_minus1;
1270
0
}
1271
1272
0
STAGE(table_r, const skcms_Curve* curve) { r = table(curve, r); }
1273
0
STAGE(table_g, const skcms_Curve* curve) { g = table(curve, g); }
1274
0
STAGE(table_b, const skcms_Curve* curve) { b = table(curve, b); }
1275
0
STAGE(table_a, const skcms_Curve* curve) { a = table(curve, a); }
1276
1277
0
STAGE(clut_A2B, const skcms_A2B* a2b) {
1278
0
    clut(a2b, &r,&g,&b,a);
1279
1280
0
    if (a2b->input_channels == 4) {
1281
        // CMYK is opaque.
1282
0
        a = F1;
1283
0
    }
1284
0
}
1285
1286
0
STAGE(clut_B2A, const skcms_B2A* b2a) {
1287
0
    clut(b2a, &r,&g,&b,&a);
1288
0
}
1289
1290
// From here on down, the store_ ops are all "final stages," terminating processing of this group.
1291
1292
0
FINAL_STAGE(store_a8, NoCtx) {
1293
0
    store(dst + 1*i, cast<U8>(to_fixed(a * 255)));
1294
0
}
1295
1296
0
FINAL_STAGE(store_g8, NoCtx) {
1297
    // g should be holding luminance (Y) (r,g,b ~~~> X,Y,Z)
1298
0
    store(dst + 1*i, cast<U8>(to_fixed(g * 255)));
1299
0
}
1300
1301
0
FINAL_STAGE(store_ga88, NoCtx) {
1302
    // g should be holding luminance (Y) (r,g,b ~~~> X,Y,Z)
1303
0
    store<U16>(dst + 2*i, cast<U16>(to_fixed(g * 255) << 0 )
1304
0
                        | cast<U16>(to_fixed(a * 255) << 8 ));
1305
0
}
1306
1307
0
FINAL_STAGE(store_4444, NoCtx) {
1308
0
    store<U16>(dst + 2*i, cast<U16>(to_fixed(r * 15) << 12)
1309
0
                        | cast<U16>(to_fixed(g * 15) <<  8)
1310
0
                        | cast<U16>(to_fixed(b * 15) <<  4)
1311
0
                        | cast<U16>(to_fixed(a * 15) <<  0));
1312
0
}
1313
1314
0
FINAL_STAGE(store_565, NoCtx) {
1315
0
    store<U16>(dst + 2*i, cast<U16>(to_fixed(r * 31) <<  0 )
1316
0
                        | cast<U16>(to_fixed(g * 63) <<  5 )
1317
0
                        | cast<U16>(to_fixed(b * 31) << 11 ));
1318
0
}
1319
1320
0
FINAL_STAGE(store_888, NoCtx) {
1321
0
    uint8_t* rgb = (uint8_t*)dst + 3*i;
1322
#if defined(USING_NEON)
1323
    // Same deal as load_888 but in reverse... we'll store using uint8x8x3_t, but
1324
    // get there via U16 to save some instructions converting to float.  And just
1325
    // like load_888, we'd prefer to go via U32 but for ARMv7 support.
1326
    U16 R = cast<U16>(to_fixed(r * 255)),
1327
        G = cast<U16>(to_fixed(g * 255)),
1328
        B = cast<U16>(to_fixed(b * 255));
1329
1330
    uint8x8x3_t v = {{ (uint8x8_t)R, (uint8x8_t)G, (uint8x8_t)B }};
1331
    vst3_lane_u8(rgb+0, v, 0);
1332
    vst3_lane_u8(rgb+3, v, 2);
1333
    vst3_lane_u8(rgb+6, v, 4);
1334
    vst3_lane_u8(rgb+9, v, 6);
1335
#else
1336
0
    store_3(rgb+0, cast<U8>(to_fixed(r * 255)) );
1337
0
    store_3(rgb+1, cast<U8>(to_fixed(g * 255)) );
1338
0
    store_3(rgb+2, cast<U8>(to_fixed(b * 255)) );
1339
0
#endif
1340
0
}
1341
1342
0
FINAL_STAGE(store_8888, NoCtx) {
1343
0
    store(dst + 4*i, cast<U32>(to_fixed(r * 255)) <<  0
1344
0
                   | cast<U32>(to_fixed(g * 255)) <<  8
1345
0
                   | cast<U32>(to_fixed(b * 255)) << 16
1346
0
                   | cast<U32>(to_fixed(a * 255)) << 24);
1347
0
}
1348
1349
0
FINAL_STAGE(store_101010x_XR, NoCtx) {
1350
0
    store(dst + 4*i, cast<U32>(to_fixed((r * 510) + 384)) <<  0
1351
0
                   | cast<U32>(to_fixed((g * 510) + 384)) << 10
1352
0
                   | cast<U32>(to_fixed((b * 510) + 384)) << 20);
1353
0
}
1354
1355
0
FINAL_STAGE(store_10101010_XR, NoCtx) {
1356
    // Each channel is 16 bits, where the 6 low bits are padding.
1357
0
    store(dst + 8*i, cast<U64>(to_fixed((r * 510) + 384)) << ( 0+6)
1358
0
                   | cast<U64>(to_fixed((g * 510) + 384)) << (16+6)
1359
0
                   | cast<U64>(to_fixed((b * 510) + 384)) << (32+6)
1360
0
                   | cast<U64>(to_fixed((a * 510) + 384)) << (48+6));
1361
0
}
1362
1363
0
FINAL_STAGE(store_1010102, NoCtx) {
1364
0
    store(dst + 4*i, cast<U32>(to_fixed(r * 1023)) <<  0
1365
0
                   | cast<U32>(to_fixed(g * 1023)) << 10
1366
0
                   | cast<U32>(to_fixed(b * 1023)) << 20
1367
0
                   | cast<U32>(to_fixed(a *    3)) << 30);
1368
0
}
1369
1370
0
FINAL_STAGE(store_161616LE, NoCtx) {
1371
0
    uintptr_t ptr = (uintptr_t)(dst + 6*i);
1372
0
    assert( (ptr & 1) == 0 );                // The dst pointer must be 2-byte aligned
1373
0
    uint16_t* rgb = (uint16_t*)ptr;          // for this cast to uint16_t* to be safe.
1374
#if defined(USING_NEON)
1375
    uint16x4x3_t v = {{
1376
        (uint16x4_t)U16_from_F(r),
1377
        (uint16x4_t)U16_from_F(g),
1378
        (uint16x4_t)U16_from_F(b),
1379
    }};
1380
    vst3_u16(rgb, v);
1381
#else
1382
0
    store_3(rgb+0, U16_from_F(r));
1383
0
    store_3(rgb+1, U16_from_F(g));
1384
0
    store_3(rgb+2, U16_from_F(b));
1385
0
#endif
1386
1387
0
}
1388
1389
0
FINAL_STAGE(store_16161616LE, NoCtx) {
1390
0
    uintptr_t ptr = (uintptr_t)(dst + 8*i);
1391
0
    assert( (ptr & 1) == 0 );               // The dst pointer must be 2-byte aligned
1392
0
    uint16_t* rgba = (uint16_t*)ptr;        // for this cast to uint16_t* to be safe.
1393
#if defined(USING_NEON)
1394
    uint16x4x4_t v = {{
1395
        (uint16x4_t)U16_from_F(r),
1396
        (uint16x4_t)U16_from_F(g),
1397
        (uint16x4_t)U16_from_F(b),
1398
        (uint16x4_t)U16_from_F(a),
1399
    }};
1400
    vst4_u16(rgba, v);
1401
#else
1402
0
    U64 px = cast<U64>(to_fixed(r * 65535)) <<  0
1403
0
           | cast<U64>(to_fixed(g * 65535)) << 16
1404
0
           | cast<U64>(to_fixed(b * 65535)) << 32
1405
0
           | cast<U64>(to_fixed(a * 65535)) << 48;
1406
0
    store(rgba, px);
1407
0
#endif
1408
0
}
1409
1410
0
FINAL_STAGE(store_161616BE, NoCtx) {
1411
0
    uintptr_t ptr = (uintptr_t)(dst + 6*i);
1412
0
    assert( (ptr & 1) == 0 );                // The dst pointer must be 2-byte aligned
1413
0
    uint16_t* rgb = (uint16_t*)ptr;          // for this cast to uint16_t* to be safe.
1414
#if defined(USING_NEON)
1415
    uint16x4x3_t v = {{
1416
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(r))),
1417
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(g))),
1418
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(b))),
1419
    }};
1420
    vst3_u16(rgb, v);
1421
#else
1422
0
    U32 R = to_fixed(r * 65535),
1423
0
        G = to_fixed(g * 65535),
1424
0
        B = to_fixed(b * 65535);
1425
0
    store_3(rgb+0, cast<U16>((R & 0x00ff) << 8 | (R & 0xff00) >> 8) );
1426
0
    store_3(rgb+1, cast<U16>((G & 0x00ff) << 8 | (G & 0xff00) >> 8) );
1427
0
    store_3(rgb+2, cast<U16>((B & 0x00ff) << 8 | (B & 0xff00) >> 8) );
1428
0
#endif
1429
1430
0
}
1431
1432
0
FINAL_STAGE(store_16161616BE, NoCtx) {
1433
0
    uintptr_t ptr = (uintptr_t)(dst + 8*i);
1434
0
    assert( (ptr & 1) == 0 );               // The dst pointer must be 2-byte aligned
1435
0
    uint16_t* rgba = (uint16_t*)ptr;        // for this cast to uint16_t* to be safe.
1436
#if defined(USING_NEON)
1437
    uint16x4x4_t v = {{
1438
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(r))),
1439
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(g))),
1440
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(b))),
1441
        (uint16x4_t)swap_endian_16(cast<U16>(U16_from_F(a))),
1442
    }};
1443
    vst4_u16(rgba, v);
1444
#else
1445
0
    U64 px = cast<U64>(to_fixed(r * 65535)) <<  0
1446
0
           | cast<U64>(to_fixed(g * 65535)) << 16
1447
0
           | cast<U64>(to_fixed(b * 65535)) << 32
1448
0
           | cast<U64>(to_fixed(a * 65535)) << 48;
1449
0
    store(rgba, swap_endian_16x4(px));
1450
0
#endif
1451
0
}
1452
1453
0
FINAL_STAGE(store_hhh, NoCtx) {
1454
0
    uintptr_t ptr = (uintptr_t)(dst + 6*i);
1455
0
    assert( (ptr & 1) == 0 );                // The dst pointer must be 2-byte aligned
1456
0
    uint16_t* rgb = (uint16_t*)ptr;          // for this cast to uint16_t* to be safe.
1457
1458
0
    U16 R = Half_from_F(r),
1459
0
        G = Half_from_F(g),
1460
0
        B = Half_from_F(b);
1461
#if defined(USING_NEON)
1462
    uint16x4x3_t v = {{
1463
        (uint16x4_t)R,
1464
        (uint16x4_t)G,
1465
        (uint16x4_t)B,
1466
    }};
1467
    vst3_u16(rgb, v);
1468
#else
1469
0
    store_3(rgb+0, R);
1470
0
    store_3(rgb+1, G);
1471
0
    store_3(rgb+2, B);
1472
0
#endif
1473
0
}
1474
1475
0
FINAL_STAGE(store_hhhh, NoCtx) {
1476
0
    uintptr_t ptr = (uintptr_t)(dst + 8*i);
1477
0
    assert( (ptr & 1) == 0 );                // The dst pointer must be 2-byte aligned
1478
0
    uint16_t* rgba = (uint16_t*)ptr;         // for this cast to uint16_t* to be safe.
1479
1480
0
    U16 R = Half_from_F(r),
1481
0
        G = Half_from_F(g),
1482
0
        B = Half_from_F(b),
1483
0
        A = Half_from_F(a);
1484
#if defined(USING_NEON)
1485
    uint16x4x4_t v = {{
1486
        (uint16x4_t)R,
1487
        (uint16x4_t)G,
1488
        (uint16x4_t)B,
1489
        (uint16x4_t)A,
1490
    }};
1491
    vst4_u16(rgba, v);
1492
#else
1493
0
    store(rgba, cast<U64>(R) <<  0
1494
0
              | cast<U64>(G) << 16
1495
0
              | cast<U64>(B) << 32
1496
0
              | cast<U64>(A) << 48);
1497
0
#endif
1498
0
}
1499
1500
0
FINAL_STAGE(store_fff, NoCtx) {
1501
0
    uintptr_t ptr = (uintptr_t)(dst + 12*i);
1502
0
    assert( (ptr & 3) == 0 );                // The dst pointer must be 4-byte aligned
1503
0
    float* rgb = (float*)ptr;                // for this cast to float* to be safe.
1504
#if defined(USING_NEON)
1505
    float32x4x3_t v = {{
1506
        (float32x4_t)r,
1507
        (float32x4_t)g,
1508
        (float32x4_t)b,
1509
    }};
1510
    vst3q_f32(rgb, v);
1511
#else
1512
0
    store_3(rgb+0, r);
1513
0
    store_3(rgb+1, g);
1514
0
    store_3(rgb+2, b);
1515
0
#endif
1516
0
}
1517
1518
0
FINAL_STAGE(store_ffff, NoCtx) {
1519
0
    uintptr_t ptr = (uintptr_t)(dst + 16*i);
1520
0
    assert( (ptr & 3) == 0 );                // The dst pointer must be 4-byte aligned
1521
0
    float* rgba = (float*)ptr;               // for this cast to float* to be safe.
1522
#if defined(USING_NEON)
1523
    float32x4x4_t v = {{
1524
        (float32x4_t)r,
1525
        (float32x4_t)g,
1526
        (float32x4_t)b,
1527
        (float32x4_t)a,
1528
    }};
1529
    vst4q_f32(rgba, v);
1530
#else
1531
0
    store_4(rgba+0, r);
1532
0
    store_4(rgba+1, g);
1533
0
    store_4(rgba+2, b);
1534
0
    store_4(rgba+3, a);
1535
0
#endif
1536
0
}
1537
1538
#if SKCMS_HAS_MUSTTAIL
1539
1540
0
    SI void exec_stages(StageFn* stages, const void** contexts, const char* src, char* dst, int i) {
1541
0
        (*stages)({stages}, contexts, src, dst, F0, F0, F0, F1, i);
1542
0
    }
1543
1544
#else
1545
1546
    static void exec_stages(const Op* ops, const void** contexts,
1547
                            const char* src, char* dst, int i) {
1548
        F r = F0, g = F0, b = F0, a = F1;
1549
        while (true) {
1550
            switch (*ops++) {
1551
#define M(name) case Op::name: Exec_##name(*contexts++, src, dst, r, g, b, a, i); break;
1552
                SKCMS_WORK_OPS(M)
1553
#undef M
1554
#define M(name) case Op::name: Exec_##name(*contexts++, src, dst, r, g, b, a, i); return;
1555
                SKCMS_STORE_OPS(M)
1556
#undef M
1557
            }
1558
        }
1559
    }
1560
1561
#endif
1562
1563
// NOLINTNEXTLINE(misc-definitions-in-headers)
1564
void run_program(const Op* program, const void** contexts, SKCMS_MAYBE_UNUSED ptrdiff_t programSize,
1565
                 const char* src, char* dst, int n,
1566
0
                size_t src_bpp, size_t dst_bpp) {
1567
0
#if SKCMS_HAS_MUSTTAIL
1568
    // Convert the program into an array of tailcall stages.
1569
0
    StageFn stages[32];
1570
0
    assert(programSize <= ARRAY_COUNT(stages));
1571
1572
0
    static constexpr StageFn kStageFns[] = {
1573
0
#define M(name) &Exec_##name,
1574
0
        SKCMS_WORK_OPS(M)
1575
0
        SKCMS_STORE_OPS(M)
1576
0
#undef M
1577
0
    };
1578
1579
0
    for (ptrdiff_t index = 0; index < programSize; ++index) {
1580
0
        stages[index] = kStageFns[(int)program[index]];
1581
0
    }
1582
#else
1583
    // Use the op array as-is.
1584
    const Op* stages = program;
1585
#endif
1586
1587
0
    int i = 0;
1588
0
    while (n >= N) {
1589
0
        exec_stages(stages, contexts, src, dst, i);
1590
0
        i += N;
1591
0
        n -= N;
1592
0
    }
1593
0
    if (n > 0) {
1594
0
        char tmp[4*4*N] = {0};
1595
1596
0
        memcpy(tmp, (const char*)src + (size_t)i*src_bpp, (size_t)n*src_bpp);
1597
0
        exec_stages(stages, contexts, tmp, tmp, 0);
1598
0
        memcpy((char*)dst + (size_t)i*dst_bpp, tmp, (size_t)n*dst_bpp);
1599
0
    }
1600
0
}