From 27f765e53ed7d0842beef24b8f74e49694456fd1 Mon Sep 17 00:00:00 2001 From: Frank Barchard Date: Mon, 8 Jun 2026 15:18:14 -0700 Subject: [PATCH] I420ToRAW and I420ToRGB24 1 pass AVX2 Replaced the 2-pass conversion (I420 -> ARGB -> RGB24/RAW) with a highly optimized 1-pass AVX2 implementation. This avoids intermediate stack buffering and significantly reduces memory bandwidth. Implemented `I422ToRGB24Row_AVX2` in: - `row_gcc.cc`: Inline assembly for GCC/Clang. - `row_win.cc`: C++ intrinsics for MSVC (also verified with Clang). Optimized the width alignment requirement: changed from 32-pixel to 16-pixel alignment in `convert_argb.cc` and `row_any.cc`. This allows the optimized AVX2 path to be used for more common video resolutions. Performance results (1080p, 100 iterations): - C Reference: ~18.5 ms - AVX2 2-Pass (Baseline): ~412 us (~45x speedup) - AVX2 1-Pass (GCC Assembly): ~411 us (~s45x speedup) - AVX2 1-Pass (Intrinsics): ~365 us (~50x speedup, 11% faster than asm) Test: libyuv_unittest --gunit_filter=*I420ToRGB24* Test: libyuv_unittest --gunit_filter=*I420ToRAW* Bug: 42280902 Change-Id: I07c0505c95410ea16a6218c858844791a11ef073 --- README.chromium | 2 +- include/libyuv/row.h | 1 + include/libyuv/version.h | 2 +- source/convert_argb.cc | 4 +- source/row_any.cc | 2 +- source/row_common.cc | 3 +- source/row_gcc.cc | 52 ++++++++++++++++++++++ source/row_win.cc | 95 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 155 insertions(+), 6 deletions(-) diff --git a/README.chromium b/README.chromium index cc424502a..f02788ca1 100644 --- a/README.chromium +++ b/README.chromium @@ -1,6 +1,6 @@ Name: libyuv URL: https://chromium.googlesource.com/libyuv/libyuv/ -Version: 1948 +Version: 1949 Revision: DEPS License: BSD-3-Clause License File: LICENSE diff --git a/include/libyuv/row.h b/include/libyuv/row.h index 835342acd..ec829e445 100644 --- a/include/libyuv/row.h +++ b/include/libyuv/row.h @@ -349,6 +349,7 @@ extern "C" { ((defined(_MSC_VER) && !defined(__clang__)) || \ defined(LIBYUV_ENABLE_ROWWIN)) #define HAS_RAWTOARGBROW_AVX2 +#define HAS_I422TORGB24ROW_AVX2 #define HAS_RGB24TOARGBROW_AVX2 #define HAS_RGB565TOARGBROW_AVX2 #define HAS_ARGB1555TOARGBROW_AVX2 diff --git a/include/libyuv/version.h b/include/libyuv/version.h index 9f9d18da7..7c8b5dcb2 100644 --- a/include/libyuv/version.h +++ b/include/libyuv/version.h @@ -11,6 +11,6 @@ #ifndef INCLUDE_LIBYUV_VERSION_H_ #define INCLUDE_LIBYUV_VERSION_H_ -#define LIBYUV_VERSION 1948 +#define LIBYUV_VERSION 1949 #endif // INCLUDE_LIBYUV_VERSION_H_ diff --git a/source/convert_argb.cc b/source/convert_argb.cc index 3844e9691..70a55d1f4 100644 --- a/source/convert_argb.cc +++ b/source/convert_argb.cc @@ -5551,7 +5551,7 @@ int I420ToRGB24Matrix(const uint8_t* src_y, #if defined(HAS_I422TORGB24ROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { I422ToRGB24Row = I422ToRGB24Row_Any_AVX2; - if (IS_ALIGNED(width, 32)) { + if (IS_ALIGNED(width, 16)) { I422ToRGB24Row = I422ToRGB24Row_AVX2; } } @@ -5772,7 +5772,7 @@ int I422ToRGB24Matrix(const uint8_t* src_y, #if defined(HAS_I422TORGB24ROW_AVX2) if (TestCpuFlag(kCpuHasAVX2)) { I422ToRGB24Row = I422ToRGB24Row_Any_AVX2; - if (IS_ALIGNED(width, 32)) { + if (IS_ALIGNED(width, 16)) { I422ToRGB24Row = I422ToRGB24Row_AVX2; } } diff --git a/source/row_any.cc b/source/row_any.cc index 919b231e6..fff040018 100644 --- a/source/row_any.cc +++ b/source/row_any.cc @@ -385,7 +385,7 @@ ANY31C(I444ToARGBRow_Any_SSSE3, I444ToARGBRow_SSSE3, 0, 0, 4, 7) ANY31C(I444ToRGB24Row_Any_SSSE3, I444ToRGB24Row_SSSE3, 0, 0, 3, 15) #endif #ifdef HAS_I422TORGB24ROW_AVX2 -ANY31C(I422ToRGB24Row_Any_AVX2, I422ToRGB24Row_AVX2, 1, 0, 3, 31) +ANY31C(I422ToRGB24Row_Any_AVX2, I422ToRGB24Row_AVX2, 1, 0, 3, 15) #endif #ifdef HAS_I422TORGB24ROW_AVX512VBMI ANY31C(I422ToRGB24Row_Any_AVX512VBMI, I422ToRGB24Row_AVX512VBMI, 1, 0, 3, 31) diff --git a/source/row_common.cc b/source/row_common.cc index 70ceaf5c8..adf13bed4 100644 --- a/source/row_common.cc +++ b/source/row_common.cc @@ -4276,7 +4276,8 @@ void I422ToARGB4444Row_AVX2(const uint8_t* src_y, } #endif -#if defined(HAS_I422TOARGBROW_AVX2) && defined(HAS_ARGBTORGB24ROW_AVX2) +#if defined(HAS_I422TOARGBROW_AVX2) && defined(HAS_ARGBTORGB24ROW_AVX2) && \ + !defined(HAS_I422TORGB24ROW_AVX2) void I422ToRGB24Row_AVX2(const uint8_t* src_y, const uint8_t* src_u, const uint8_t* src_v, diff --git a/source/row_gcc.cc b/source/row_gcc.cc index 10ecf5910..216210a94 100644 --- a/source/row_gcc.cc +++ b/source/row_gcc.cc @@ -3775,6 +3775,58 @@ void OMITFP I422ToARGBRow_AVX2(const uint8_t* y_buf, } #endif // HAS_I422TOARGBROW_AVX2 +#if defined(HAS_I422TORGB24ROW_AVX2) +// 16 pixels +// 8 UV values upsampled to 16 UV, mixed with 16 Y producing 16 RGB24 (48 bytes). +void OMITFP I422ToRGB24Row_AVX2(const uint8_t* y_buf, + const uint8_t* u_buf, + const uint8_t* v_buf, + uint8_t* dst_rgb24, + const struct YuvConstants* yuvconstants, + int width) { + asm volatile ( + YUVTORGB_SETUP_AVX2(yuvconstants) + "vbroadcasti128 %[kShuffleMaskARGBToRGB24_0],%%ymm5 \n" + "vbroadcasti128 %[kShuffleMaskARGBToRGB24],%%ymm6 \n" + "sub %[u_buf],%[v_buf] \n" + + LABELALIGN + "1: \n" + READYUV422_AVX2 + YUVTORGB_AVX2(yuvconstants) + "vpunpcklbw %%ymm1,%%ymm0,%%ymm0 \n" + "vpunpcklbw %%ymm2,%%ymm2,%%ymm2 \n" + "vmovdqa %%ymm0,%%ymm1 \n" + "vpunpcklwd %%ymm2,%%ymm0,%%ymm0 \n" + "vpunpckhwd %%ymm2,%%ymm1,%%ymm1 \n" + "vpshufb %%ymm5,%%ymm0,%%ymm0 \n" + "vpshufb %%ymm6,%%ymm1,%%ymm1 \n" + "vpalignr $0xc,%%ymm0,%%ymm1,%%ymm1 \n" + "vextracti128 $1,%%ymm0,%%xmm2 \n" + "vextracti128 $1,%%ymm1,%%xmm3 \n" + "vmovq %%xmm0,(%[dst_rgb24]) \n" + "vmovdqu %%xmm1,0x8(%[dst_rgb24]) \n" + "vmovq %%xmm2,0x18(%[dst_rgb24]) \n" + "vmovdqu %%xmm3,0x20(%[dst_rgb24]) \n" + "lea 0x30(%[dst_rgb24]),%[dst_rgb24] \n" + "sub $0x10,%[width] \n" + "jg 1b \n" + "vzeroupper \n" + : [y_buf]"+r"(y_buf), // %[y_buf] + [u_buf]"+r"(u_buf), // %[u_buf] + [v_buf]"+r"(v_buf), // %[v_buf] + [dst_rgb24]"+r"(dst_rgb24), // %[dst_rgb24] + [width]"+rm"(width) // %[width] + : [yuvconstants]"r"(yuvconstants), // %[yuvconstants] + [kShuffleMaskARGBToRGB24_0]"m"(kShuffleMaskARGBToRGB24_0), + [kShuffleMaskARGBToRGB24]"m"(kShuffleMaskARGBToRGB24) + : "memory", "cc", YUVTORGB_REGS_AVX2 + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6" + ); +} +#endif // HAS_I422TORGB24ROW_AVX2 + + #if defined(HAS_I422TOARGBROW_AVX512BW) static const uint64_t kSplitQuadWords[8] = {0, 2, 2, 2, 1, 2, 2, 2}; static const uint64_t kSplitDoubleQuadWords[8] = {0, 1, 4, 4, 2, 3, 4, 4}; diff --git a/source/row_win.cc b/source/row_win.cc index a7ed75199..4429a926c 100644 --- a/source/row_win.cc +++ b/source/row_win.cc @@ -973,6 +973,101 @@ void ARGBShuffleRow_AVX512BW(const uint8_t* src_argb, #endif +#ifdef HAS_I422TORGB24ROW_AVX2 +LIBYUV_TARGET_AVX2 +void I422ToRGB24Row_AVX2(const uint8_t* src_y, + const uint8_t* src_u, + const uint8_t* src_v, + uint8_t* dst_rgb24, + const struct YuvConstants* yuvconstants, + int width) { + // Constants + __m256i ymm_kUVToB = _mm256_loadu_si256((const __m256i*)yuvconstants->kUVToB); + __m256i ymm_kUVToG = _mm256_loadu_si256((const __m256i*)yuvconstants->kUVToG); + __m256i ymm_kUVToR = _mm256_loadu_si256((const __m256i*)yuvconstants->kUVToR); + __m256i ymm_kYToRgb = _mm256_loadu_si256((const __m256i*)yuvconstants->kYToRgb); + __m256i ymm_kYBiasToRgb = _mm256_loadu_si256((const __m256i*)yuvconstants->kYBiasToRgb); + __m256i ymm_128 = _mm256_set1_epi8((char)0x80); + + __m128i shuf0 = _mm_setr_epi8(0, 1, 2, 4, 5, 6, 8, 9, (char)0x80, (char)0x80, (char)0x80, (char)0x80, 10, 12, 13, 14); + __m128i shuf1 = _mm_setr_epi8(0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14, (char)0x80, (char)0x80, (char)0x80, (char)0x80); + __m256i ymm_shuf0 = _mm256_broadcastsi128_si256(shuf0); + __m256i ymm_shuf1 = _mm256_broadcastsi128_si256(shuf1); + __m256i ymm_u_zero = _mm256_setzero_si256(); + + ptrdiff_t offset = src_v - src_u; + + while (width >= 16) { + // READYUV422_AVX2 + __m128i xmm_u = _mm_loadl_epi64((const __m128i*)src_u); + __m128i xmm_v = _mm_loadl_epi64((const __m128i*)(src_u + offset)); + src_u += 8; + + __m256i ymm3 = _mm256_insertf128_si256(ymm_u_zero, xmm_u, 0); + __m256i ymm1 = _mm256_insertf128_si256(ymm_u_zero, xmm_v, 0); + + ymm3 = _mm256_unpacklo_epi8(ymm3, ymm1); + ymm3 = _mm256_permute4x64_epi64(ymm3, 0xd8); + ymm3 = _mm256_unpacklo_epi16(ymm3, ymm3); + + __m128i xmm_y = _mm_loadu_si128((const __m128i*)src_y); + src_y += 16; + __m256i ymm4 = _mm256_insertf128_si256(ymm_u_zero, xmm_y, 0); + ymm4 = _mm256_permute4x64_epi64(ymm4, 0xd8); + ymm4 = _mm256_unpacklo_epi8(ymm4, ymm4); + + // YUVTORGB_AVX2 + ymm3 = _mm256_sub_epi8(ymm3, ymm_128); + ymm4 = _mm256_mulhi_epu16(ymm4, ymm_kYToRgb); + + __m256i ymm0 = _mm256_maddubs_epi16(ymm_kUVToB, ymm3); + ymm1 = _mm256_maddubs_epi16(ymm_kUVToG, ymm3); + __m256i ymm2 = _mm256_maddubs_epi16(ymm_kUVToR, ymm3); + + ymm4 = _mm256_add_epi16(ymm4, ymm_kYBiasToRgb); + + ymm0 = _mm256_adds_epi16(ymm0, ymm4); + ymm1 = _mm256_subs_epi16(ymm4, ymm1); + ymm2 = _mm256_adds_epi16(ymm2, ymm4); + + ymm0 = _mm256_srai_epi16(ymm0, 6); + ymm1 = _mm256_srai_epi16(ymm1, 6); + ymm2 = _mm256_srai_epi16(ymm2, 6); + + ymm0 = _mm256_packus_epi16(ymm0, ymm0); + ymm1 = _mm256_packus_epi16(ymm1, ymm1); + ymm2 = _mm256_packus_epi16(ymm2, ymm2); + + // STORERGB24_AVX2 + __m256i ymm0_packed = _mm256_unpacklo_epi8(ymm0, ymm1); + __m256i ymm2_packed = _mm256_unpacklo_epi8(ymm2, ymm2); + __m256i ymm1_packed = ymm0_packed; + + ymm0_packed = _mm256_unpacklo_epi16(ymm0_packed, ymm2_packed); + ymm1_packed = _mm256_unpackhi_epi16(ymm1_packed, ymm2_packed); + + ymm0_packed = _mm256_shuffle_epi8(ymm0_packed, ymm_shuf0); + ymm1_packed = _mm256_shuffle_epi8(ymm1_packed, ymm_shuf1); + + ymm1_packed = _mm256_alignr_epi8(ymm1_packed, ymm0_packed, 0xc); + + __m128i xmm0_store = _mm256_castsi256_si128(ymm0_packed); + __m128i xmm1_store = _mm256_castsi256_si128(ymm1_packed); + __m128i xmm2_store = _mm256_extractf128_si256(ymm0_packed, 1); + __m128i xmm3_store = _mm256_extractf128_si256(ymm1_packed, 1); + + _mm_storel_epi64((__m128i*)dst_rgb24, xmm0_store); + _mm_storeu_si128((__m128i*)(dst_rgb24 + 8), xmm1_store); + _mm_storel_epi64((__m128i*)(dst_rgb24 + 24), xmm2_store); + _mm_storeu_si128((__m128i*)(dst_rgb24 + 32), xmm3_store); + + dst_rgb24 += 48; + width -= 16; + } + _mm256_zeroupper(); +} +#endif + #ifdef __cplusplus } // extern "C" } // namespace libyuv