Coverage Report

Created: 2026-11-12 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libwebp/sharpyuv/sharpyuv_avx2.c
Line
Count
Source
1
// Copyright 2026 Google Inc. All Rights Reserved.
2
//
3
// Use of this source code is governed by a BSD-style license
4
// that can be found in the COPYING file in the root of the source
5
// tree. An additional intellectual property rights grant can be found
6
// in the file PATENTS. All contributing project authors may
7
// be found in the AUTHORS file in the root of the source tree.
8
// -----------------------------------------------------------------------------
9
//
10
// AVX2 speed-critical functions for Sharp YUV.
11
12
#include "./sharpyuv.h"
13
#include "./sharpyuv_dsp.h"
14
#include "./sharpyuv_gamma.h"
15
16
#if defined(WEBP_USE_AVX2)
17
#include <assert.h>
18
#include <immintrin.h>
19
#include <stdint.h>
20
#include <stdlib.h>
21
22
#include "src/dsp/cpu.h"
23
#include "webp/types.h"
24
25
0
#define LOAD_256(P) _mm256_loadu_si256((const __m256i*)(P))
26
0
#define STORE_256(P, V) _mm256_storeu_si256((__m256i*)(P), (V))
27
0
#define LOAD_128(P) _mm_loadu_si128((const __m128i*)(P))
28
0
#define STORE_128(P, V) _mm_storeu_si128((__m128i*)(P), (V))
29
0
#define LOAD_64(P) _mm_loadl_epi64((const __m128i*)(P))
30
0
#define STORE_64(P, V) _mm_storel_epi64((__m128i*)(P), (V))
31
32
// Clamps 16x (resp. 8x) int16 lanes to [0, max].
33
0
static WEBP_INLINE __m256i Clip16_AVX2(__m256i v, __m256i max) {
34
0
  return _mm256_max_epi16(_mm256_min_epi16(v, max), _mm256_setzero_si256());
35
0
}
36
0
static WEBP_INLINE __m128i Clip16_128_AVX2(__m128i v, __m128i max) {
37
0
  return _mm_max_epi16(_mm_min_epi16(v, max), _mm_setzero_si128());
38
0
}
39
40
// Widens 8x int16 lanes into low/high 4x32 halves (sign-extending).
41
static WEBP_INLINE void WidenLoHi16To32_AVX2(__m128i v, __m128i* lo,
42
0
                                             __m128i* hi) {
43
0
  *lo = _mm_cvtepi16_epi32(v);
44
0
  *hi = _mm_cvtepi16_epi32(_mm_srli_si128(v, 8));
45
0
}
46
47
// e0 = (9*a0+3*a1+3*b0+b1+8)>>4, e1 symmetric. SUFFIX is epi16 or epi32.
48
#define SHARP_FILTER933_AVX2(SUFFIX, a0, a1, b0, b1, kCst8, e0_out, e1_out) \
49
0
  do {                                                                      \
50
0
    const __m256i a0b1 = _mm256_add_##SUFFIX(a0, b1);                       \
51
0
    const __m256i a1b0 = _mm256_add_##SUFFIX(a1, b0);                       \
52
0
    const __m256i a0a1b0b1 = _mm256_add_##SUFFIX(a0b1, a1b0);               \
53
0
    const __m256i a0a1b0b1_8 = _mm256_add_##SUFFIX(a0a1b0b1, kCst8);        \
54
0
    const __m256i a0b1_2 = _mm256_add_##SUFFIX(a0b1, a0b1);                 \
55
0
    const __m256i a1b0_2 = _mm256_add_##SUFFIX(a1b0, a1b0);                 \
56
0
    const __m256i c0_ =                                                     \
57
0
        _mm256_srai_##SUFFIX(_mm256_add_##SUFFIX(a0b1_2, a0a1b0b1_8), 3);   \
58
0
    const __m256i c1_ =                                                     \
59
0
        _mm256_srai_##SUFFIX(_mm256_add_##SUFFIX(a1b0_2, a0a1b0b1_8), 3);   \
60
0
    const __m256i d0_ = _mm256_add_##SUFFIX(c1_, a0);                       \
61
0
    const __m256i d1_ = _mm256_add_##SUFFIX(c0_, a1);                       \
62
0
    e0_out = _mm256_srai_##SUFFIX(d0_, 1);                                  \
63
0
    e1_out = _mm256_srai_##SUFFIX(d1_, 1);                                  \
64
0
  } while (0)
65
66
static uint64_t SharpYuvUpdateY_AVX2(const uint16_t* ref, const uint16_t* src,
67
0
                                     uint16_t* dst, int len, int bit_depth) {
68
0
  const int max_y = (1 << bit_depth) - 1;
69
0
  int i;
70
0
  const __m256i max = _mm256_set1_epi16(max_y);
71
0
  const __m256i one = _mm256_set1_epi16(1);
72
0
  __m256i sum = _mm256_setzero_si256();
73
0
  uint64_t diff;
74
75
0
  for (i = 0; i + 16 <= len; i += 16) {
76
0
    const __m256i A = LOAD_256(ref + i);
77
0
    const __m256i B = LOAD_256(src + i);
78
0
    const __m256i C = LOAD_256(dst + i);
79
0
    const __m256i D = _mm256_sub_epi16(A, B);        // diff_y
80
0
    const __m256i F = _mm256_add_epi16(C, D);        // new_y
81
0
    const __m256i AbsD = _mm256_abs_epi16(D);        // abs(diff_y)
82
0
    const __m256i I = _mm256_madd_epi16(AbsD, one);  // 32-bit pairs
83
0
    STORE_256(dst + i, Clip16_AVX2(F, max));
84
0
    sum = _mm256_add_epi32(sum, I);
85
0
  }
86
0
  {
87
0
    uint32_t tmp[8];
88
0
    STORE_256(tmp, sum);
89
0
    diff = (uint64_t)tmp[0] + tmp[1] + tmp[2] + tmp[3] + tmp[4] + tmp[5] +
90
0
           tmp[6] + tmp[7];
91
0
  }
92
0
  for (; i < len; ++i) {
93
0
    const int diff_y = ref[i] - src[i];
94
0
    const int new_y = (int)dst[i] + diff_y;
95
0
    dst[i] = SharpYuvClip16(new_y, max_y);
96
0
    diff += (uint64_t)abs(diff_y);
97
0
  }
98
0
  return diff;
99
0
}
100
101
static void SharpYuvUpdateRGB_AVX2(const int16_t* ref, const int16_t* src,
102
0
                                   int16_t* dst, int len) {
103
0
  int i;
104
0
  for (i = 0; i + 16 <= len; i += 16) {
105
0
    const __m256i A = LOAD_256(ref + i);
106
0
    const __m256i B = LOAD_256(src + i);
107
0
    const __m256i C = LOAD_256(dst + i);
108
0
    const __m256i D = _mm256_sub_epi16(A, B);  // diff_uv
109
0
    const __m256i E = _mm256_add_epi16(C, D);  // new_uv
110
0
    STORE_256(dst + i, E);
111
0
  }
112
0
  for (; i < len; ++i) {
113
0
    const int diff_uv = ref[i] - src[i];
114
0
    dst[i] += diff_uv;
115
0
  }
116
0
}
117
118
static void SharpYuvFilterRow16_AVX2(const int16_t* A, const int16_t* B,
119
                                     int len, const uint16_t* best_y,
120
0
                                     uint16_t* out, int bit_depth) {
121
0
  const int max_y = (1 << bit_depth) - 1;
122
0
  int i;
123
0
  const __m256i kCst8 = _mm256_set1_epi16(8);
124
0
  const __m256i max = _mm256_set1_epi16(max_y);
125
0
  for (i = 0; i + 16 <= len; i += 16) {
126
0
    const __m256i a0 = LOAD_256(A + i + 0);
127
0
    const __m256i a1 = LOAD_256(A + i + 1);
128
0
    const __m256i b0 = LOAD_256(B + i + 0);
129
0
    const __m256i b1 = LOAD_256(B + i + 1);
130
0
    __m256i e0, e1;
131
0
    SHARP_FILTER933_AVX2(epi16, a0, a1, b0, b1, kCst8, e0, e1);
132
0
    {
133
0
      const __m256i lo = _mm256_unpacklo_epi16(e0, e1);
134
0
      const __m256i hi = _mm256_unpackhi_epi16(e0, e1);
135
      // unpacklo/hi interleave within 128-bit lanes; permute to restore the
136
      // sequential (i, i+8) ordering matching best_y/out layout.
137
0
      const __m256i f0 = _mm256_permute2x128_si256(lo, hi, 0x20);
138
0
      const __m256i f1 = _mm256_permute2x128_si256(lo, hi, 0x31);
139
0
      const __m256i g0 = LOAD_256(best_y + 2 * i + 0);
140
0
      const __m256i g1 = LOAD_256(best_y + 2 * i + 16);
141
0
      STORE_256(out + 2 * i + 0, Clip16_AVX2(_mm256_add_epi16(g0, f0), max));
142
0
      STORE_256(out + 2 * i + 16, Clip16_AVX2(_mm256_add_epi16(g1, f1), max));
143
0
    }
144
0
  }
145
0
  for (; i < len; ++i) {
146
0
    const int v0 =
147
0
        (A[i + 0] * 9 + A[i + 1] * 3 + B[i + 0] * 3 + B[i + 1] + 8) >> 4;
148
0
    const int v1 =
149
0
        (A[i + 1] * 9 + A[i + 0] * 3 + B[i + 1] * 3 + B[i + 0] + 8) >> 4;
150
0
    out[2 * i + 0] = SharpYuvClip16(best_y[2 * i + 0] + v0, max_y);
151
0
    out[2 * i + 1] = SharpYuvClip16(best_y[2 * i + 1] + v1, max_y);
152
0
  }
153
0
}
154
155
// SharpYuvFilterRow16_AVX2's intermediate sums (e.g. a0a1b0b1_8) can exceed
156
// int16 range once bit_depth > 10 (chroma deltas get large enough that
157
// 2*(A+B) overflows signed 16 bits and wraps). This variant does the same
158
// computation in 32-bit lanes to stay safe, matching
159
// SharpYuvFilterRow32_SSE2/NEON.
160
static void SharpYuvFilterRow32_AVX2(const int16_t* A, const int16_t* B,
161
                                     int len, const uint16_t* best_y,
162
0
                                     uint16_t* out, int bit_depth) {
163
0
  const int max_y = (1 << bit_depth) - 1;
164
0
  int i;
165
0
  const __m256i kCst8 = _mm256_set1_epi32(8);
166
0
  const __m128i max = _mm_set1_epi16(max_y);
167
0
  for (i = 0; i + 8 <= len; i += 8) {
168
0
    const __m256i a0 = _mm256_cvtepi16_epi32(LOAD_128(A + i + 0));
169
0
    const __m256i a1 = _mm256_cvtepi16_epi32(LOAD_128(A + i + 1));
170
0
    const __m256i b0 = _mm256_cvtepi16_epi32(LOAD_128(B + i + 0));
171
0
    const __m256i b1 = _mm256_cvtepi16_epi32(LOAD_128(B + i + 1));
172
0
    __m256i e0, e1;
173
0
    SHARP_FILTER933_AVX2(epi32, a0, a1, b0, b1, kCst8, e0, e1);
174
    // Split into 128-bit halves and interleave/pack each independently (pure
175
    // SSE-style ops): avoids AVX2's cross-128-lane pack/unpack pitfalls.
176
0
    {
177
0
      const __m128i e0_lo = _mm256_castsi256_si128(e0);
178
0
      const __m128i e0_hi = _mm256_extracti128_si256(e0, 1);
179
0
      const __m128i e1_lo = _mm256_castsi256_si128(e1);
180
0
      const __m128i e1_hi = _mm256_extracti128_si256(e1, 1);
181
0
      const __m128i out_lo = _mm_packs_epi32(_mm_unpacklo_epi32(e0_lo, e1_lo),
182
0
                                             _mm_unpackhi_epi32(e0_lo, e1_lo));
183
0
      const __m128i out_hi = _mm_packs_epi32(_mm_unpacklo_epi32(e0_hi, e1_hi),
184
0
                                             _mm_unpackhi_epi32(e0_hi, e1_hi));
185
0
      const __m128i g_lo = LOAD_128(best_y + 2 * i + 0);
186
0
      const __m128i g_hi = LOAD_128(best_y + 2 * i + 8);
187
0
      STORE_128(out + 2 * i + 0,
188
0
                Clip16_128_AVX2(_mm_add_epi16(g_lo, out_lo), max));
189
0
      STORE_128(out + 2 * i + 8,
190
0
                Clip16_128_AVX2(_mm_add_epi16(g_hi, out_hi), max));
191
0
    }
192
0
  }
193
0
  for (; i < len; ++i) {
194
0
    const int a0b1 = A[i + 0] + B[i + 1];
195
0
    const int a1b0 = A[i + 1] + B[i + 0];
196
0
    const int a0a1b0b1 = a0b1 + a1b0 + 8;
197
0
    const int v0 = (8 * A[i + 0] + 2 * a1b0 + a0a1b0b1) >> 4;
198
0
    const int v1 = (8 * A[i + 1] + 2 * a0b1 + a0a1b0b1) >> 4;
199
0
    out[2 * i + 0] = SharpYuvClip16(best_y[2 * i + 0] + v0, max_y);
200
0
    out[2 * i + 1] = SharpYuvClip16(best_y[2 * i + 1] + v1, max_y);
201
0
  }
202
0
}
203
204
static void SharpYuvFilterRow_AVX2(const int16_t* A, const int16_t* B, int len,
205
                                   const uint16_t* best_y, uint16_t* out,
206
0
                                   int bit_depth) {
207
0
  if (bit_depth <= 10) {
208
0
    SharpYuvFilterRow16_AVX2(A, B, len, best_y, out, bit_depth);
209
0
  } else {
210
0
    SharpYuvFilterRow32_AVX2(A, B, len, best_y, out, bit_depth);
211
0
  }
212
0
}
213
214
//------------------------------------------------------------------------------
215
// Final W/RGB -> YUV reconstruction. Must stay numerically equivalent to
216
// RGBToYUVComponent() in sharpyuv.c, including its int64_t precision.
217
218
// Arithmetic right shift by a runtime amount n (1 <= n <= 63): AVX2 has no
219
// psraq, so build it from the logical shift plus a sign-extended top.
220
0
static WEBP_INLINE __m256i Sra64_AVX2(__m256i a, int n) {
221
0
  const __m128i cnt = _mm_cvtsi32_si128(n);
222
0
  const __m128i cnt2 = _mm_cvtsi32_si128(64 - n);
223
0
  const __m256i sign = _mm256_cmpgt_epi64(_mm256_setzero_si256(), a);
224
0
  const __m256i shifted = _mm256_srl_epi64(a, cnt);
225
0
  const __m256i topbits = _mm256_sll_epi64(sign, cnt2);
226
0
  return _mm256_or_si256(shifted, topbits);
227
0
}
228
229
// Truncating (non-saturating) narrow of 8x int32 lanes to 8x int16: matches
230
// a plain C (int16_t)/(uint16_t) cast, unlike _mm256_packs/packus_epi32.
231
0
static WEBP_INLINE __m128i TruncateNarrow32To16_AVX2(__m256i v) {
232
0
  const __m256i masked = _mm256_and_si256(v, _mm256_set1_epi32(0xffff));
233
0
  const __m256i packed = _mm256_packus_epi32(masked, masked);
234
0
  const __m128i lo = _mm256_castsi256_si128(packed);
235
0
  const __m128i hi = _mm256_extracti128_si256(packed, 1);
236
0
  return _mm_unpacklo_epi64(lo, hi);
237
0
}
238
239
// Narrow 4x64-bit lanes (2 groups of 2, from the mul_epi32 lane layout) to
240
// 4x32-bit, keeping only the low 32 bits of each 64-bit lane.
241
0
static WEBP_INLINE __m128i Narrow64To32_AVX2(__m256i v) {
242
0
  const __m256i shuffled = _mm256_shuffle_epi32(v, _MM_SHUFFLE(2, 0, 2, 0));
243
0
  const __m128i lo = _mm256_castsi256_si128(shuffled);
244
0
  const __m128i hi = _mm256_extracti128_si256(shuffled, 1);
245
0
  return _mm_unpacklo_epi64(lo, hi);
246
0
}
247
248
// round((R*coeffs[0] + G*coeffs[1] + B*coeffs[2] + bias) >> shift) for 4
249
// lanes, in exact 64-bit precision (matches the int64_t path in
250
// RGBToYUVComponent / RGBToGray).
251
static WEBP_INLINE __m128i Convert4_AVX2(__m128i R, __m128i G, __m128i B,
252
                                         const int coeffs[4], __m256i bias,
253
0
                                         int shift) {
254
0
  const __m256i R64 = _mm256_cvtepi32_epi64(R);
255
0
  const __m256i G64 = _mm256_cvtepi32_epi64(G);
256
0
  const __m256i B64 = _mm256_cvtepi32_epi64(B);
257
0
  __m256i acc = _mm256_add_epi64(
258
0
      bias, _mm256_mul_epi32(R64, _mm256_set1_epi64x(coeffs[0])));
259
0
  acc = _mm256_add_epi64(acc,
260
0
                         _mm256_mul_epi32(G64, _mm256_set1_epi64x(coeffs[1])));
261
0
  acc = _mm256_add_epi64(acc,
262
0
                         _mm256_mul_epi32(B64, _mm256_set1_epi64x(coeffs[2])));
263
0
  acc = Sra64_AVX2(acc, shift);
264
0
  return Narrow64To32_AVX2(acc);
265
0
}
266
267
static void SharpYuvConvertRowY_AVX2(const uint16_t* best_y,
268
                                     const int16_t* best_uv, int width,
269
                                     int uv_w, const int coeffs[4], int sfix,
270
0
                                     int yuv_bit_depth, void* y_out) {
271
0
  const int shift = SHARPYUV_YUV_FIX + sfix;
272
0
  const int64_t rounder = 1LL << (shift - 1);
273
0
  const __m256i bias = _mm256_set1_epi64x(coeffs[3] + rounder);
274
0
  const int yuv_max = (1 << yuv_bit_depth) - 1;
275
0
  int i = 0;
276
0
  for (; i + 8 <= width; i += 8) {
277
0
    const int off = i >> 1;
278
0
    const __m128i W = LOAD_128(best_y + i);
279
0
    const __m128i Ro = LOAD_64(best_uv + off + 0 * uv_w);
280
0
    const __m128i Go = LOAD_64(best_uv + off + 1 * uv_w);
281
0
    const __m128i Bo = LOAD_64(best_uv + off + 2 * uv_w);
282
0
    const __m128i Rv = _mm_add_epi16(_mm_unpacklo_epi16(Ro, Ro), W);
283
0
    const __m128i Gv = _mm_add_epi16(_mm_unpacklo_epi16(Go, Go), W);
284
0
    const __m128i Bv = _mm_add_epi16(_mm_unpacklo_epi16(Bo, Bo), W);
285
0
    __m128i R_lo, R_hi, G_lo, G_hi, B_lo, B_hi;
286
0
    WidenLoHi16To32_AVX2(Rv, &R_lo, &R_hi);
287
0
    WidenLoHi16To32_AVX2(Gv, &G_lo, &G_hi);
288
0
    WidenLoHi16To32_AVX2(Bv, &B_lo, &B_hi);
289
0
    {
290
0
      const __m128i y_lo = Convert4_AVX2(R_lo, G_lo, B_lo, coeffs, bias, shift);
291
0
      const __m128i y_hi = Convert4_AVX2(R_hi, G_hi, B_hi, coeffs, bias, shift);
292
0
      const __m128i y16 = _mm_packs_epi32(y_lo, y_hi);  // saturating:
293
                                                        // matches
294
                                                        // clip()/clip_8b().
295
0
      if (yuv_bit_depth <= 8) {
296
0
        STORE_64((uint8_t*)y_out + i, _mm_packus_epi16(y16, y16));
297
0
      } else {
298
0
        const __m128i maxv = _mm_set1_epi16((int16_t)yuv_max);
299
0
        STORE_128((uint16_t*)y_out + i, Clip16_128_AVX2(y16, maxv));
300
0
      }
301
0
    }
302
0
  }
303
0
  for (; i < width; ++i) {
304
0
    const int off = i >> 1;
305
0
    const int W = best_y[i];
306
0
    const int r = best_uv[off + 0 * uv_w] + W;
307
0
    const int g = best_uv[off + 1 * uv_w] + W;
308
0
    const int b = best_uv[off + 2 * uv_w] + W;
309
0
    const int y = SharpYuvConvertComponent(r, g, b, coeffs, sfix);
310
0
    if (yuv_bit_depth <= 8) {
311
0
      ((uint8_t*)y_out)[i] = (uint8_t)SharpYuvClip16(y, 255);
312
0
    } else {
313
0
      ((uint16_t*)y_out)[i] = SharpYuvClip16(y, yuv_max);
314
0
    }
315
0
  }
316
0
}
317
318
static void SharpYuvConvertRowUV_AVX2(const int16_t* best_uv, int uv_w,
319
                                      const int coeffs_u[4],
320
                                      const int coeffs_v[4], int sfix,
321
                                      int yuv_bit_depth, void* u_out,
322
0
                                      void* v_out) {
323
0
  const int shift = SHARPYUV_YUV_FIX + sfix;
324
0
  const int64_t rounder = 1LL << (shift - 1);
325
0
  const __m256i bias_u = _mm256_set1_epi64x(coeffs_u[3] + rounder);
326
0
  const __m256i bias_v = _mm256_set1_epi64x(coeffs_v[3] + rounder);
327
0
  const int yuv_max = (1 << yuv_bit_depth) - 1;
328
0
  int i = 0;
329
0
  for (; i + 8 <= uv_w; i += 8) {
330
0
    const __m128i R16 = LOAD_128(best_uv + i + 0 * uv_w);
331
0
    const __m128i G16 = LOAD_128(best_uv + i + 1 * uv_w);
332
0
    const __m128i B16 = LOAD_128(best_uv + i + 2 * uv_w);
333
0
    __m128i R_lo, R_hi, G_lo, G_hi, B_lo, B_hi;
334
0
    WidenLoHi16To32_AVX2(R16, &R_lo, &R_hi);
335
0
    WidenLoHi16To32_AVX2(G16, &G_lo, &G_hi);
336
0
    WidenLoHi16To32_AVX2(B16, &B_lo, &B_hi);
337
0
    {
338
0
      const __m128i u_lo =
339
0
          Convert4_AVX2(R_lo, G_lo, B_lo, coeffs_u, bias_u, shift);
340
0
      const __m128i u_hi =
341
0
          Convert4_AVX2(R_hi, G_hi, B_hi, coeffs_u, bias_u, shift);
342
0
      const __m128i v_lo =
343
0
          Convert4_AVX2(R_lo, G_lo, B_lo, coeffs_v, bias_v, shift);
344
0
      const __m128i v_hi =
345
0
          Convert4_AVX2(R_hi, G_hi, B_hi, coeffs_v, bias_v, shift);
346
0
      const __m128i u16 = _mm_packs_epi32(u_lo, u_hi);
347
0
      const __m128i v16 = _mm_packs_epi32(v_lo, v_hi);
348
0
      if (yuv_bit_depth <= 8) {
349
0
        STORE_64((uint8_t*)u_out + i, _mm_packus_epi16(u16, u16));
350
0
        STORE_64((uint8_t*)v_out + i, _mm_packus_epi16(v16, v16));
351
0
      } else {
352
0
        const __m128i maxv = _mm_set1_epi16((int16_t)yuv_max);
353
0
        STORE_128((uint16_t*)u_out + i, Clip16_128_AVX2(u16, maxv));
354
0
        STORE_128((uint16_t*)v_out + i, Clip16_128_AVX2(v16, maxv));
355
0
      }
356
0
    }
357
0
  }
358
0
  for (; i < uv_w; ++i) {
359
0
    const int r = best_uv[i + 0 * uv_w];
360
0
    const int g = best_uv[i + 1 * uv_w];
361
0
    const int b = best_uv[i + 2 * uv_w];
362
0
    const int u = SharpYuvConvertComponent(r, g, b, coeffs_u, sfix);
363
0
    const int v = SharpYuvConvertComponent(r, g, b, coeffs_v, sfix);
364
0
    if (yuv_bit_depth <= 8) {
365
0
      ((uint8_t*)u_out)[i] = (uint8_t)SharpYuvClip16(u, 255);
366
0
      ((uint8_t*)v_out)[i] = (uint8_t)SharpYuvClip16(v, 255);
367
0
    } else {
368
0
      ((uint16_t*)u_out)[i] = SharpYuvClip16(u, yuv_max);
369
0
      ((uint16_t*)v_out)[i] = SharpYuvClip16(v, yuv_max);
370
0
    }
371
0
  }
372
0
}
373
374
//------------------------------------------------------------------------------
375
// BT.709 and sRGB gamma<->linear fast paths for UpdateW/UpdateChroma.
376
// Leverage vpgatherdd gather instruction!
377
378
// Widens 8 samples to 16.16 fixed-point linear values using the
379
// gamma->linear table (shift_right = bit_depth - table_bits, >= 0).
380
static WEBP_INLINE __m256i GammaToLinear8_AVX2(__m128i v16, int shift_right,
381
0
                                               const uint32_t* g2l_tab) {
382
0
  const __m256i v32 = _mm256_cvtepu16_epi32(v16);
383
0
  const __m128i cnt = _mm_cvtsi32_si128(shift_right);
384
0
  const __m256i pos = _mm256_srl_epi32(v32, cnt);
385
0
  const __m256i x = _mm256_sub_epi32(v32, _mm256_sll_epi32(pos, cnt));
386
0
  const __m256i v0 = _mm256_i32gather_epi32((const int*)g2l_tab, pos, 4);
387
0
  const __m256i v1 = _mm256_i32gather_epi32(
388
0
      (const int*)g2l_tab, _mm256_add_epi32(pos, _mm256_set1_epi32(1)), 4);
389
  // d * x <= 65536 * 2^shift_right: fits in int32
390
0
  const __m256i prod = _mm256_mullo_epi32(_mm256_sub_epi32(v1, v0), x);
391
0
  const int half = (shift_right > 0) ? (1 << (shift_right - 1)) : 0;
392
0
  const __m256i sum = _mm256_add_epi32(prod, _mm256_set1_epi32(half));
393
0
  const __m256i shifted = _mm256_srl_epi32(sum, cnt);
394
0
  return _mm256_add_epi32(v0, shifted);
395
0
}
396
397
// Reverse direction: 8 16.16 fixed-point values -> 8 gamma samples.
398
static WEBP_INLINE __m256i LinearToGamma8_AVX2(__m256i v, int out_shift,
399
0
                                               const uint32_t* l2g_tab) {
400
0
  const __m256i pos = _mm256_srli_epi32(v, 7);  // table bits are fixed.
401
0
  const __m256i x = _mm256_sub_epi32(v, _mm256_slli_epi32(pos, 7));
402
0
  const __m256i v0_raw = _mm256_i32gather_epi32((const int*)l2g_tab, pos, 4);
403
0
  const __m256i v1_raw = _mm256_i32gather_epi32(
404
0
      (const int*)l2g_tab, _mm256_add_epi32(pos, _mm256_set1_epi32(1)), 4);
405
0
  const __m128i cnt = _mm_cvtsi32_si128(out_shift);
406
0
  const __m256i v0 = _mm256_srl_epi32(v0_raw, cnt);
407
0
  const __m256i v1 = _mm256_srl_epi32(v1_raw, cnt);
408
  // d * x <= 65536 * 128: fits in int32
409
0
  const __m256i prod = _mm256_mullo_epi32(_mm256_sub_epi32(v1, v0), x);
410
0
  const __m256i sum = _mm256_add_epi32(prod, _mm256_set1_epi32(64));
411
0
  const __m256i shifted = _mm256_srai_epi32(sum, 7);
412
0
  return _mm256_add_epi32(v0, shifted);
413
0
}
414
415
// (13933*R + 46871*G + 4732*B + 32768) >> 16, fits tightly in 32bits!
416
static WEBP_INLINE __m256i SharpYuvRGBToGray8_AVX2(__m256i R, __m256i G,
417
0
                                                   __m256i B) {
418
0
  static const int kGrayCoeffs[4] = {13933, 46871, 4732, 0};
419
0
  const __m256i bias = _mm256_set1_epi64x(1LL << (SHARPYUV_YUV_FIX - 1));
420
0
  const __m128i y_lo = Convert4_AVX2(
421
0
      _mm256_castsi256_si128(R), _mm256_castsi256_si128(G),
422
0
      _mm256_castsi256_si128(B), kGrayCoeffs, bias, SHARPYUV_YUV_FIX);
423
0
  const __m128i y_hi = Convert4_AVX2(
424
0
      _mm256_extracti128_si256(R, 1), _mm256_extracti128_si256(G, 1),
425
0
      _mm256_extracti128_si256(B, 1), kGrayCoeffs, bias, SHARPYUV_YUV_FIX);
426
0
  return _mm256_insertf128_si256(_mm256_castsi128_si256(y_lo), y_hi, 1);
427
0
}
428
429
static void UpdateW_AVX2(const uint16_t* src, uint16_t* dst, int w,
430
                         int bit_depth,
431
                         SharpYuvTransferFunctionType transfer_type,
432
0
                         const uint32_t* g2l_tab, const uint32_t* l2g_tab) {
433
0
  const int shift_right = bit_depth - SHARPYUV_GAMMA_TO_LINEAR_TAB_BITS;
434
0
  const int out_shift = SHARPYUV_GAMMA_TO_LINEAR_BITS - bit_depth;
435
0
  int i = 0;
436
0
  assert(shift_right >= 0);
437
0
  for (; i + 8 <= w; i += 8) {
438
0
    const __m256i R =
439
0
        GammaToLinear8_AVX2(LOAD_128(src + 0 * w + i), shift_right, g2l_tab);
440
0
    const __m256i G =
441
0
        GammaToLinear8_AVX2(LOAD_128(src + 1 * w + i), shift_right, g2l_tab);
442
0
    const __m256i B =
443
0
        GammaToLinear8_AVX2(LOAD_128(src + 2 * w + i), shift_right, g2l_tab);
444
0
    const __m256i Y = SharpYuvRGBToGray8_AVX2(R, G, B);
445
0
    STORE_128(dst + i, TruncateNarrow32To16_AVX2(
446
0
                           LinearToGamma8_AVX2(Y, out_shift, l2g_tab)));
447
0
  }
448
0
  for (; i < w; ++i) {
449
0
    const uint32_t R =
450
0
        SharpYuvGammaToLinear(src[0 * w + i], bit_depth, transfer_type);
451
0
    const uint32_t G =
452
0
        SharpYuvGammaToLinear(src[1 * w + i], bit_depth, transfer_type);
453
0
    const uint32_t B =
454
0
        SharpYuvGammaToLinear(src[2 * w + i], bit_depth, transfer_type);
455
0
    const int Y = SharpYuvRGBToGray(R, G, B);
456
0
    dst[i] = SharpYuvLinearToGamma((uint32_t)Y, bit_depth, transfer_type);
457
0
  }
458
0
}
459
460
static void SharpYuvUpdateW709_AVX2(const uint16_t* src, uint16_t* dst, int w,
461
0
                                    int bit_depth) {
462
0
  UpdateW_AVX2(src, dst, w, bit_depth, kSharpYuvTransferFunctionBt709,
463
0
               kSharpYuvGammaToLinearTabS, kSharpYuvLinearToGammaTabS);
464
0
}
465
466
static void SharpYuvUpdateWSrgb_AVX2(const uint16_t* src, uint16_t* dst, int w,
467
0
                                     int bit_depth) {
468
0
  UpdateW_AVX2(src, dst, w, bit_depth, kSharpYuvTransferFunctionSrgb,
469
0
               kSharpYuvGammaToLinearTabSrgb, kSharpYuvLinearToGammaTabSrgb);
470
0
}
471
472
// Deinterleaves 8 (a,b) pairs into a=[a0..a7], b=[b0..b7].
473
static WEBP_INLINE void Deinterleave8_AVX2(const uint16_t* p, __m128i* a,
474
0
                                           __m128i* b) {
475
0
  static const uint8_t kM[16] = {0, 1, 4, 5, 8,  9,  12, 13,
476
0
                                 2, 3, 6, 7, 10, 11, 14, 15};
477
0
  const __m256i mask = _mm256_broadcastsi128_si256(LOAD_128(kM));
478
0
  const __m256i v = LOAD_256(p);
479
0
  const __m256i shuffled = _mm256_shuffle_epi8(v, mask);
480
0
  const __m128i lane0 = _mm256_castsi256_si128(shuffled);
481
0
  const __m128i lane1 = _mm256_extracti128_si256(shuffled, 1);
482
0
  *a = _mm_unpacklo_epi64(lane0, lane1);
483
0
  *b = _mm_unpackhi_epi64(lane0, lane1);
484
0
}
485
486
// Box-downsamples 8 consecutive 2x2 uv-groups, matching ScaleDown().
487
static WEBP_INLINE __m128i ScaleDown8_AVX2(const uint16_t* p1,
488
                                           const uint16_t* p2, int shift_right,
489
                                           int out_shift,
490
                                           const uint32_t* g2l_tab,
491
0
                                           const uint32_t* l2g_tab) {
492
0
  __m128i a, b, c, d;
493
0
  Deinterleave8_AVX2(p1, &a, &b);
494
0
  Deinterleave8_AVX2(p2, &c, &d);
495
0
  {
496
0
    const __m256i A = GammaToLinear8_AVX2(a, shift_right, g2l_tab);
497
0
    const __m256i B = GammaToLinear8_AVX2(b, shift_right, g2l_tab);
498
0
    const __m256i C = GammaToLinear8_AVX2(c, shift_right, g2l_tab);
499
0
    const __m256i D = GammaToLinear8_AVX2(d, shift_right, g2l_tab);
500
0
    const __m256i sum =
501
0
        _mm256_add_epi32(_mm256_add_epi32(A, B), _mm256_add_epi32(C, D));
502
0
    const __m256i avg =
503
0
        _mm256_srli_epi32(_mm256_add_epi32(sum, _mm256_set1_epi32(2)), 2);
504
0
    return TruncateNarrow32To16_AVX2(
505
0
        LinearToGamma8_AVX2(avg, out_shift, l2g_tab));
506
0
  }
507
0
}
508
509
static void UpdateChroma_AVX2(const uint16_t* src1, const uint16_t* src2,
510
                              int16_t* dst, int uv_w, int bit_depth,
511
                              SharpYuvTransferFunctionType transfer_type,
512
                              const uint32_t* g2l_tab,
513
0
                              const uint32_t* l2g_tab) {
514
0
  const int shift_right = bit_depth - SHARPYUV_GAMMA_TO_LINEAR_TAB_BITS;
515
0
  const int out_shift = SHARPYUV_GAMMA_TO_LINEAR_BITS - bit_depth;
516
0
  int i = 0;
517
0
  assert(shift_right >= 0);
518
0
  for (; i + 8 <= uv_w; i += 8) {
519
0
    const int off = 2 * i;
520
0
    const __m128i r16 =
521
0
        ScaleDown8_AVX2(src1 + off + 0 * uv_w, src2 + off + 0 * uv_w,
522
0
                        shift_right, out_shift, g2l_tab, l2g_tab);
523
0
    const __m128i g16 =
524
0
        ScaleDown8_AVX2(src1 + off + 2 * uv_w, src2 + off + 2 * uv_w,
525
0
                        shift_right, out_shift, g2l_tab, l2g_tab);
526
0
    const __m128i b16 =
527
0
        ScaleDown8_AVX2(src1 + off + 4 * uv_w, src2 + off + 4 * uv_w,
528
0
                        shift_right, out_shift, g2l_tab, l2g_tab);
529
0
    const __m256i r32 = _mm256_cvtepu16_epi32(r16);
530
0
    const __m256i g32 = _mm256_cvtepu16_epi32(g16);
531
0
    const __m256i b32 = _mm256_cvtepu16_epi32(b16);
532
0
    const __m256i w32 = SharpYuvRGBToGray8_AVX2(r32, g32, b32);
533
0
    STORE_128(dst + i + 0 * uv_w,
534
0
              TruncateNarrow32To16_AVX2(_mm256_sub_epi32(r32, w32)));
535
0
    STORE_128(dst + i + 1 * uv_w,
536
0
              TruncateNarrow32To16_AVX2(_mm256_sub_epi32(g32, w32)));
537
0
    STORE_128(dst + i + 2 * uv_w,
538
0
              TruncateNarrow32To16_AVX2(_mm256_sub_epi32(b32, w32)));
539
0
  }
540
0
  for (; i < uv_w; ++i) {
541
0
    const int off = 2 * i;
542
0
    uint32_t rgb[3];
543
0
    int c;
544
0
    for (c = 0; c < 3; ++c) {
545
0
      const uint16_t* s1 = src1 + off + 2 * c * uv_w;
546
0
      const uint16_t* s2 = src2 + off + 2 * c * uv_w;
547
0
      const uint32_t A = SharpYuvGammaToLinear(s1[0], bit_depth, transfer_type);
548
0
      const uint32_t B = SharpYuvGammaToLinear(s1[1], bit_depth, transfer_type);
549
0
      const uint32_t C = SharpYuvGammaToLinear(s2[0], bit_depth, transfer_type);
550
0
      const uint32_t D = SharpYuvGammaToLinear(s2[1], bit_depth, transfer_type);
551
0
      rgb[c] = SharpYuvLinearToGamma((A + B + C + D + 2) >> 2, bit_depth,
552
0
                                     transfer_type);
553
0
    }
554
0
    {
555
0
      const int W = SharpYuvRGBToGray(rgb[0], rgb[1], rgb[2]);
556
0
      dst[i + 0 * uv_w] = (int16_t)((int)rgb[0] - W);
557
0
      dst[i + 1 * uv_w] = (int16_t)((int)rgb[1] - W);
558
0
      dst[i + 2 * uv_w] = (int16_t)((int)rgb[2] - W);
559
0
    }
560
0
  }
561
0
}
562
563
static void SharpYuvUpdateChroma709_AVX2(const uint16_t* src1,
564
                                         const uint16_t* src2, int16_t* dst,
565
0
                                         int uv_w, int bit_depth) {
566
0
  UpdateChroma_AVX2(src1, src2, dst, uv_w, bit_depth,
567
0
                    kSharpYuvTransferFunctionBt709, kSharpYuvGammaToLinearTabS,
568
0
                    kSharpYuvLinearToGammaTabS);
569
0
}
570
571
static void SharpYuvUpdateChromaSrgb_AVX2(const uint16_t* src1,
572
                                          const uint16_t* src2, int16_t* dst,
573
0
                                          int uv_w, int bit_depth) {
574
0
  UpdateChroma_AVX2(
575
0
      src1, src2, dst, uv_w, bit_depth, kSharpYuvTransferFunctionSrgb,
576
0
      kSharpYuvGammaToLinearTabSrgb, kSharpYuvLinearToGammaTabSrgb);
577
0
}
578
579
//------------------------------------------------------------------------------
580
581
extern void InitSharpYuvAVX2(void);
582
583
0
WEBP_TSAN_IGNORE_FUNCTION void InitSharpYuvAVX2(void) {
584
0
  SharpYuvUpdateY = SharpYuvUpdateY_AVX2;
585
0
  SharpYuvUpdateRGB = SharpYuvUpdateRGB_AVX2;
586
0
  SharpYuvFilterRow = SharpYuvFilterRow_AVX2;
587
0
  SharpYuvConvertRowY = SharpYuvConvertRowY_AVX2;
588
0
  SharpYuvConvertRowUV = SharpYuvConvertRowUV_AVX2;
589
0
  SharpYuvUpdateW709 = SharpYuvUpdateW709_AVX2;
590
0
  SharpYuvUpdateChroma709 = SharpYuvUpdateChroma709_AVX2;
591
0
  SharpYuvUpdateWSrgb = SharpYuvUpdateWSrgb_AVX2;
592
0
  SharpYuvUpdateChromaSrgb = SharpYuvUpdateChromaSrgb_AVX2;
593
0
}
594
595
#else  // !WEBP_USE_AVX2
596
597
extern void InitSharpYuvAVX2(void);
598
599
void InitSharpYuvAVX2(void) {}
600
601
#endif  // WEBP_USE_AVX2