From 80ce27d34e92ee8032f136ee83ddefe0428b63d6 Mon Sep 17 00:00:00 2001 From: Vincent Rabaud Date: Wed, 16 Dec 2015 11:06:42 +0100 Subject: [PATCH] Speed up 24-bit packing / unpacking in YUV / RGB conversions. This implementation brings: - an SSE implementation of packing / unpacking - bigger buffers processed at the same time The speedup is of 4% on lossy decoding (YUV to RGB), 0.5% on lossy encoding (RGB to YUV was already optimized). Change-Id: Iec677ee17f91c08614d1adab67c6df551925767f --- src/dsp/yuv_sse2.c | 339 +++++++++++++++++++++++++++------------------ 1 file changed, 202 insertions(+), 137 deletions(-) diff --git a/src/dsp/yuv_sse2.c b/src/dsp/yuv_sse2.c index cb6339e3..8ea7ba69 100644 --- a/src/dsp/yuv_sse2.c +++ b/src/dsp/yuv_sse2.c @@ -110,46 +110,55 @@ static WEBP_INLINE void PackAndStore4(const __m128i* const R, _mm_storeu_si128((__m128i*)(dst + 16), RGBA_hi); } -// Pack R/G/B results into 24b output. -static WEBP_INLINE void PackAndStore3(const __m128i* const R, - const __m128i* const G, - const __m128i* const B, - uint8_t* const dst) { - const __m128i tmp0 = _mm_packus_epi16(*R, *R); - const __m128i tmp1 = _mm_packus_epi16(*G, *G); - const __m128i tmp2 = _mm_packus_epi16(*B, *B); - _mm_storel_epi64((__m128i*)(dst + 0 * 32), tmp0); - _mm_storel_epi64((__m128i*)(dst + 1 * 32), tmp1); - _mm_storel_epi64((__m128i*)(dst + 2 * 32), tmp2); +// Function used several times in PlanarTo24b. +// It samples the in buffer as follows: one every two unsigned char is stored +// at the beginning of the buffer, while the other half is stored at the end. +static WEBP_INLINE void PlanarTo24bHelper(const __m128i in[6], __m128i out[6]) { + const __m128i v_mask = _mm_set1_epi16(0x00ff); + + // Take one every two upper 8b values. + out[0] = _mm_packus_epi16(_mm_and_si128(in[0], v_mask), + _mm_and_si128(in[1], v_mask)); + out[1] = _mm_packus_epi16(_mm_and_si128(in[2], v_mask), + _mm_and_si128(in[3], v_mask)); + out[2] = _mm_packus_epi16(_mm_and_si128(in[4], v_mask), + _mm_and_si128(in[5], v_mask)); + // Take one every two lower 8b values. + out[3] = _mm_packus_epi16(_mm_srli_epi16(in[0], 8), _mm_srli_epi16(in[1], 8)); + out[4] = _mm_packus_epi16(_mm_srli_epi16(in[2], 8), _mm_srli_epi16(in[3], 8)); + out[5] = _mm_packus_epi16(_mm_srli_epi16(in[4], 8), _mm_srli_epi16(in[5], 8)); } -// Converts 32 samples in src[3][32] to interleaved RGB24 in dst[] -#define MK_UINT32(A, B, C, D) \ - ((A) << 0) | ((B) << 8) | ((C) << 16) | ((uint32_t)(D) << 24) -static WEBP_INLINE void PlanarTo24b(const uint8_t* src, uint8_t* dst) { -#if 1 - // This code is faster than the version below (left there for reference). - // It's also endian-dependent but we're only targeting x86. - const uint8_t* const end = src + 32; - for (; src < end; src += 4, dst += 12) { - const uint32_t A = MK_UINT32(src[0 + 0 * 32], src[0 + 1 * 32], - src[0 + 2 * 32], src[1 + 0 * 32]); - const uint32_t B = MK_UINT32(src[1 + 1 * 32], src[1 + 2 * 32], - src[2 + 0 * 32], src[2 + 1 * 32]); - const uint32_t C = MK_UINT32(src[2 + 2 * 32], src[3 + 0 * 32], - src[3 + 1 * 32], src[3 + 2 * 32]); - *(uint32_t*)(dst + 0) = A; - *(uint32_t*)(dst + 4) = B; - *(uint32_t*)(dst + 8) = C; - } -#else - int n; - for (n = 0; n < 32; ++n) { - *dst++ = src[0 * 32 + n]; - *dst++ = src[1 * 32 + n]; - *dst++ = src[2 * 32 + n]; - } -#endif +// Pack the planar buffers +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// triplet by triplet in the output buffer rgb as rgbrgbrgbrgb ... +static WEBP_INLINE void PlanarTo24b(__m128i in[6], uint8_t* rgb) { + // The input is 6 registers of sixteen 8b but for the sake of explanation, + // let's take 6 registers of four 8b values. + // To pack, we will keep taking one every two 8b integer and move it + // around as follows: + // Input: + // r0r1r2r3 | r4r5r6r7 | g0g1g2g3 | g4g5g6g7 | b0b1b2b3 | b4b5b6b7 + // Split the 6 registers in two sets of 3 registers: the first set as the even + // 8b bytes, the second the odd ones: + // r0r2r4r6 | g0g2g4g6 | b0b2b4b6 | r1r3r5r7 | g1g3g5g7 | b1b3b5b7 + // Repeat the same permutations twice more: + // r0r4g0g4 | b0b4r1r5 | g1g5b1b5 | r2r6g2g6 | b2b6r3r7 | g3g7b3b7 + // r0g0b0r1 | g1b1r2g2 | b2r3g3b3 | r4g4b4r5 | g5b5r6g6 | b6r7g7b7 + __m128i tmp[6]; + PlanarTo24bHelper(in, tmp); + PlanarTo24bHelper(tmp, in); + PlanarTo24bHelper(in, tmp); + // We need to do it two more times than the example as we have sixteen bytes. + PlanarTo24bHelper(tmp, in); + PlanarTo24bHelper(in, tmp); + + _mm_storeu_si128((__m128i*)(rgb + 0), tmp[0]); + _mm_storeu_si128((__m128i*)(rgb + 16), tmp[1]); + _mm_storeu_si128((__m128i*)(rgb + 32), tmp[2]); + _mm_storeu_si128((__m128i*)(rgb + 48), tmp[3]); + _mm_storeu_si128((__m128i*)(rgb + 64), tmp[4]); + _mm_storeu_si128((__m128i*)(rgb + 80), tmp[5]); } #undef MK_UINT32 @@ -177,26 +186,46 @@ void VP8YuvToBgra32(const uint8_t* y, const uint8_t* u, const uint8_t* v, void VP8YuvToRgb32(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst) { - int n; - uint8_t tmp[32 * 3]; - for (n = 0; n < 32; n += 8) { - __m128i R, G, B; - YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); - PackAndStore3(&R, &G, &B, tmp + n); - } - PlanarTo24b(tmp, dst); + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb[6]; + + YUV444ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb[0] = _mm_packus_epi16(R0, R1); + rgb[1] = _mm_packus_epi16(R2, R3); + rgb[2] = _mm_packus_epi16(G0, G1); + rgb[3] = _mm_packus_epi16(G2, G3); + rgb[4] = _mm_packus_epi16(B0, B1); + rgb[5] = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b(rgb, dst); } void VP8YuvToBgr32(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst) { - int n; - uint8_t tmp[32 * 3]; - for (n = 0; n < 32; n += 8) { - __m128i R, G, B; - YUV444ToRGB(y + n, u + n, v + n, &R, &G, &B); - PackAndStore3(&B, &G, &R, tmp + n); - } - PlanarTo24b(tmp, dst); + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr[6]; + + YUV444ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV444ToRGB(y + 8, u + 8, v + 8, &R1, &G1, &B1); + YUV444ToRGB(y + 16, u + 16, v + 16, &R2, &G2, &B2); + YUV444ToRGB(y + 24, u + 24, v + 24, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr[0] = _mm_packus_epi16(B0, B1); + bgr[1] = _mm_packus_epi16(B2, B3); + bgr[2] = _mm_packus_epi16(G0, G1); + bgr[3] = _mm_packus_epi16(G2, G3); + bgr[4] = _mm_packus_epi16(R0, R1); + bgr[5] = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b(bgr, dst); } //----------------------------------------------------------------------------- @@ -269,17 +298,25 @@ static void YuvToRgbRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int len) { int n; for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { - uint8_t tmp[32 * 3]; - __m128i R, G, B; - YUV420ToRGB(y + 0, u + 0, v + 0, &R, &G, &B); - PackAndStore3(&R, &G, &B, tmp + 0); - YUV420ToRGB(y + 8, u + 4, v + 4, &R, &G, &B); - PackAndStore3(&R, &G, &B, tmp + 8); - YUV420ToRGB(y + 16, u + 8, v + 8, &R, &G, &B); - PackAndStore3(&R, &G, &B, tmp + 16); - YUV420ToRGB(y + 24, u + 12, v + 12, &R, &G, &B); - PackAndStore3(&R, &G, &B, tmp + 24); - PlanarTo24b(tmp, dst); + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i rgb[6]; + + YUV420ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as RRRRGGGGBBBB. + rgb[0] = _mm_packus_epi16(R0, R1); + rgb[1] = _mm_packus_epi16(R2, R3); + rgb[2] = _mm_packus_epi16(G0, G1); + rgb[3] = _mm_packus_epi16(G2, G3); + rgb[4] = _mm_packus_epi16(B0, B1); + rgb[5] = _mm_packus_epi16(B2, B3); + + // Pack as RGBRGBRGBRGB. + PlanarTo24b(rgb, dst); + y += 32; u += 16; v += 16; @@ -297,17 +334,25 @@ static void YuvToBgrRow(const uint8_t* y, const uint8_t* u, const uint8_t* v, uint8_t* dst, int len) { int n; for (n = 0; n + 32 <= len; n += 32, dst += 32 * 3) { - uint8_t tmp[32 * 3]; - __m128i R, G, B; - YUV420ToRGB(y + 0, u + 0, v + 0, &R, &G, &B); - PackAndStore3(&B, &G, &R, tmp + 0); - YUV420ToRGB(y + 8, u + 4, v + 4, &R, &G, &B); - PackAndStore3(&B, &G, &R, tmp + 8); - YUV420ToRGB(y + 16, u + 8, v + 8, &R, &G, &B); - PackAndStore3(&B, &G, &R, tmp + 16); - YUV420ToRGB(y + 24, u + 12, v + 12, &R, &G, &B); - PackAndStore3(&B, &G, &R, tmp + 24); - PlanarTo24b(tmp, dst); + __m128i R0, R1, R2, R3, G0, G1, G2, G3, B0, B1, B2, B3; + __m128i bgr[6]; + + YUV420ToRGB(y + 0, u + 0, v + 0, &R0, &G0, &B0); + YUV420ToRGB(y + 8, u + 4, v + 4, &R1, &G1, &B1); + YUV420ToRGB(y + 16, u + 8, v + 8, &R2, &G2, &B2); + YUV420ToRGB(y + 24, u + 12, v + 12, &R3, &G3, &B3); + + // Cast to 8b and store as BBBBGGGGRRRR. + bgr[0] = _mm_packus_epi16(B0, B1); + bgr[1] = _mm_packus_epi16(B2, B3); + bgr[2] = _mm_packus_epi16(G0, G1); + bgr[3] = _mm_packus_epi16(G2, G3); + bgr[4] = _mm_packus_epi16(R0, R1); + bgr[5] = _mm_packus_epi16(R2, R3); + + // Pack as BGRBGRBGRBGR. + PlanarTo24b(bgr, dst); + y += 32; u += 16; v += 16; @@ -342,52 +387,36 @@ WEBP_TSAN_IGNORE_FUNCTION void WebPInitSamplersSSE2(void) { // Store either 16b-words into *dst #define STORE_16(V, dst) _mm_storeu_si128((__m128i*)(dst), (V)) -// Convert 8 packed RGB or BGR samples to r[], g[], b[] +// Function that inserts a value of the second half of the in buffer in between +// every two char of the first half. +static WEBP_INLINE void RGB24PackedToPlanarHelper(const __m128i in[6], + __m128i out[6]) { + out[0] = _mm_unpacklo_epi8(in[0], in[3]); + out[1] = _mm_unpackhi_epi8(in[0], in[3]); + out[2] = _mm_unpacklo_epi8(in[1], in[4]); + out[3] = _mm_unpackhi_epi8(in[1], in[4]); + out[4] = _mm_unpacklo_epi8(in[2], in[5]); + out[5] = _mm_unpackhi_epi8(in[2], in[5]); +} + +// Unpack the 8b input rgbrgbrgbrgb ... as contiguous registers: +// rrrr... rrrr... gggg... gggg... bbbb... bbbb.... +// Similar to PlanarTo24bHelper(), but in reverse order. static WEBP_INLINE void RGB24PackedToPlanar(const uint8_t* const rgb, - __m128i* const r, - __m128i* const g, - __m128i* const b, - int input_is_bgr) { - const __m128i zero = _mm_setzero_si128(); - // in0: r0 g0 b0 r1 | g1 b1 r2 g2 | b2 r3 g3 b3 | r4 g4 b4 r5 - // in1: b2 r3 g3 b3 | r4 g4 b4 r5 | g5 b5 r6 g6 | b6 r7 g7 b7 - const __m128i in0 = LOAD_16(rgb + 0); - const __m128i in1 = LOAD_16(rgb + 8); - // A0: | r2 g2 b2 r3 | g3 b3 r4 g4 | b4 r5 ... - // A1: ... b2 r3 | g3 b3 r4 g4 | b4 r5 g5 b5 | - const __m128i A0 = _mm_srli_si128(in0, 6); - const __m128i A1 = _mm_slli_si128(in1, 6); - // B0: r0 r2 g0 g2 | b0 b2 r1 r3 | g1 g3 b1 b3 | r2 r4 b2 b4 - // B1: g3 g5 b3 b5 | r4 r6 g4 g6 | b4 b6 r5 r7 | g5 g7 b5 b7 - const __m128i B0 = _mm_unpacklo_epi8(in0, A0); - const __m128i B1 = _mm_unpackhi_epi8(A1, in1); - // C0: r1 r3 g1 g3 | b1 b3 r2 r4 | b2 b4 ... - // C1: ... g3 g5 | b3 b5 r4 r6 | g4 g6 b4 b6 - const __m128i C0 = _mm_srli_si128(B0, 6); - const __m128i C1 = _mm_slli_si128(B1, 6); - // D0: r0 r1 r2 r3 | g0 g1 g2 g3 | b0 b1 b2 b3 | r1 r2 r3 r4 - // D1: b3 b4 b5 b6 | r4 r5 r6 r7 | g4 g5 g6 g7 | b4 b5 b6 b7 | - const __m128i D0 = _mm_unpacklo_epi8(B0, C0); - const __m128i D1 = _mm_unpackhi_epi8(C1, B1); - // r4 r5 r6 r7 | g4 g5 g6 g7 | b4 b5 b6 b7 | 0 - const __m128i D2 = _mm_srli_si128(D1, 4); - // r0 r1 r2 r3 | r4 r5 r6 r7 | g0 g1 g2 g3 | g4 g5 g6 g7 - const __m128i E0 = _mm_unpacklo_epi32(D0, D2); - // b0 b1 b2 b3 | b4 b5 b6 b7 | r1 r2 r3 r4 | 0 - const __m128i E1 = _mm_unpackhi_epi32(D0, D2); - // g0 g1 g2 g3 | g4 g5 g6 g7 | 0 - const __m128i E2 = _mm_srli_si128(E0, 8); - const __m128i F0 = _mm_unpacklo_epi8(E0, zero); // -> R - const __m128i F1 = _mm_unpacklo_epi8(E1, zero); // -> B - const __m128i F2 = _mm_unpacklo_epi8(E2, zero); // -> G - *g = F2; - if (input_is_bgr) { - *r = F1; - *b = F0; - } else { - *r = F0; - *b = F1; - } + __m128i out[6]) { + __m128i tmp[6]; + tmp[0] = _mm_loadu_si128((const __m128i*)(rgb + 0)); + tmp[1] = _mm_loadu_si128((const __m128i*)(rgb + 16)); + tmp[2] = _mm_loadu_si128((const __m128i*)(rgb + 32)); + tmp[3] = _mm_loadu_si128((const __m128i*)(rgb + 48)); + tmp[4] = _mm_loadu_si128((const __m128i*)(rgb + 64)); + tmp[5] = _mm_loadu_si128((const __m128i*)(rgb + 80)); + + RGB24PackedToPlanarHelper(tmp, out); + RGB24PackedToPlanarHelper(out, tmp); + RGB24PackedToPlanarHelper(tmp, out); + RGB24PackedToPlanarHelper(out, tmp); + RGB24PackedToPlanarHelper(tmp, out); } // Convert 8 packed ARGB to r[], g[], b[] @@ -471,15 +500,33 @@ static WEBP_INLINE void ConvertRGBToUV(const __m128i* const R, #undef TRANSFORM static void ConvertRGB24ToY(const uint8_t* rgb, uint8_t* y, int width) { - const int max_width = width & ~15; + const int max_width = width & ~31; int i; - for (i = 0; i < max_width; i += 16, rgb += 3 * 16) { - __m128i r, g, b, Y0, Y1; - RGB24PackedToPlanar(rgb + 0 * 8, &r, &g, &b, 0); - ConvertRGBToY(&r, &g, &b, &Y0); - RGB24PackedToPlanar(rgb + 3 * 8, &r, &g, &b, 0); - ConvertRGBToY(&r, &g, &b, &Y1); - STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + for (i = 0; i < max_width; rgb += 3 * 16 * 2) { + __m128i rgb_plane[6]; + int j; + + RGB24PackedToPlanar(rgb, rgb_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + r = _mm_unpacklo_epi8(rgb_plane[0 + j], zero); + g = _mm_unpacklo_epi8(rgb_plane[2 + j], zero); + b = _mm_unpacklo_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + r = _mm_unpackhi_epi8(rgb_plane[0 + j], zero); + g = _mm_unpackhi_epi8(rgb_plane[2 + j], zero); + b = _mm_unpackhi_epi8(rgb_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } } for (; i < width; ++i, rgb += 3) { // left-over y[i] = VP8RGBToY(rgb[0], rgb[1], rgb[2], YUV_HALF); @@ -487,15 +534,33 @@ static void ConvertRGB24ToY(const uint8_t* rgb, uint8_t* y, int width) { } static void ConvertBGR24ToY(const uint8_t* bgr, uint8_t* y, int width) { + const int max_width = width & ~31; int i; - const int max_width = width & ~15; - for (i = 0; i < max_width; i += 16, bgr += 3 * 16) { - __m128i r, g, b, Y0, Y1; - RGB24PackedToPlanar(bgr + 0 * 8, &r, &g, &b, 1); - ConvertRGBToY(&r, &g, &b, &Y0); - RGB24PackedToPlanar(bgr + 3 * 8, &r, &g, &b, 1); - ConvertRGBToY(&r, &g, &b, &Y1); - STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + for (i = 0; i < max_width; bgr += 3 * 16 * 2) { + __m128i bgr_plane[6]; + int j; + + RGB24PackedToPlanar(bgr, bgr_plane); + + for (j = 0; j < 2; ++j, i += 16) { + const __m128i zero = _mm_setzero_si128(); + __m128i r, g, b, Y0, Y1; + + // Convert to 16-bit Y. + b = _mm_unpacklo_epi8(bgr_plane[0 + j], zero); + g = _mm_unpacklo_epi8(bgr_plane[2 + j], zero); + r = _mm_unpacklo_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y0); + + // Convert to 16-bit Y. + b = _mm_unpackhi_epi8(bgr_plane[0 + j], zero); + g = _mm_unpackhi_epi8(bgr_plane[2 + j], zero); + r = _mm_unpackhi_epi8(bgr_plane[4 + j], zero); + ConvertRGBToY(&r, &g, &b, &Y1); + + // Cast to 8-bit and store. + STORE_16(_mm_packus_epi16(Y0, Y1), y + i); + } } for (; i < width; ++i, bgr += 3) { // left-over y[i] = VP8RGBToY(bgr[2], bgr[1], bgr[0], YUV_HALF);