/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 | } |