[libyuv] Add SwapUVRow_AVX2 and NV12ToNV21

Add SwapUVRow_AVX2 implementation in row_win.cc for MSVC and enable
HAS_SWAPUVROW_AVX2 under USE_ROW_WIN.
Share kShuffleUVToVU shuffle table between SSSE3 and AVX2 in row_gcc.cc.
Make NV12ToNV21 the primary implementation and alias NV21ToNV12 to it.
Add NV12ToNV21 test in convert_test.cc and benchmark dispatch for
SSSE3 and AVX2 in planar_test.cc.

CPU                                   Before     After   Speedup
----------------------------------------------------------------
Intel Granite Rapids                  282 ms    125 ms     2.26x
Intel Emerald Rapids                  264 ms    108 ms     2.44x
Intel Sapphire Rapids                 276 ms    122 ms     2.26x
Intel Ice Lake                        310 ms    122 ms     2.54x
Intel Skylake-X                       573 ms    157 ms     3.65x
Intel Haswell                         620 ms    162 ms     3.83x
AMD Zen 4 (Genoa)                     266 ms     52 ms     5.12x
AMD Zen 6 (Venice)                    229 ms     43 ms     5.33x
ARM Neoverse-N1                       140 ms     71 ms     1.97x
NVIDIA Grace (Neoverse-V2)             62 ms     69 ms     0.90x

R=wtc@google.com

Test: libyuv_unittest --gtest_filter=*SwapUV*:*NV12ToNV21*:*NV21ToNV12*
Bug: 406251841
Change-Id: I9bed5522aedadea13cdf69c27e3cbe633e31dd5b
TAG=agy
CONV=bb50ac62-0c55-4969-8fff-c71ab3d9a79f
Reviewed-on: https://chromium-review.googlesource.com/c/libyuv/libyuv/+/8356968
Reviewed-by: richard winterton <rrwinterton@gmail.com>
Commit-Queue: Frank Barchard <fbarchard@google.com>
This commit is contained in:
Frank Barchard 2026-09-04 11:23:02 -07:00 committed by libyuv-scoped@luci-project-accounts.iam.gserviceaccount.com
parent fc0dc6c339
commit e58eeea369
9 changed files with 60 additions and 20 deletions

View File

@ -1,6 +1,6 @@
Name: libyuv
URL: https://chromium.googlesource.com/libyuv/libyuv/
Version: 1970
Version: 1971
Revision: DEPS
License: BSD-3-Clause
License File: LICENSE

View File

@ -479,21 +479,21 @@ int UYVYToNV12(const uint8_t* src_uyvy,
int width,
int height);
// Convert NV21 to NV12.
// Convert NV12 to NV21.
LIBYUV_API
int NV21ToNV12(const uint8_t* src_y,
int NV12ToNV21(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_vu,
int src_stride_vu,
const uint8_t* src_uv,
int src_stride_uv,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_uv,
int dst_stride_uv,
uint8_t* dst_vu,
int dst_stride_vu,
int width,
int height);
// Alias
#define NV12ToNV21 NV21ToNV12
#define NV21ToNV12 NV12ToNV21
LIBYUV_API
int YUY2ToY(const uint8_t* src_yuy2,

View File

@ -405,6 +405,7 @@ extern "C" {
#define HAS_INTERPOLATEROW_AVX2
#define HAS_INTERPOLATEROW_16_AVX2
#define HAS_MULTIPLYROW_16_AVX2
#define HAS_SWAPUVROW_AVX2
#endif
// The following are available for AVX512 clang x64 platforms:

View File

@ -11,6 +11,6 @@
#ifndef INCLUDE_LIBYUV_VERSION_H_
#define INCLUDE_LIBYUV_VERSION_H_
#define LIBYUV_VERSION 1970
#define LIBYUV_VERSION 1971
#endif // INCLUDE_LIBYUV_VERSION_H_

View File

@ -1108,22 +1108,22 @@ void SwapUVPlane(const uint8_t* src_uv,
}
}
// Convert NV21 to NV12.
// Convert NV12 to NV21.
LIBYUV_API
int NV21ToNV12(const uint8_t* src_y,
int NV12ToNV21(const uint8_t* src_y,
int src_stride_y,
const uint8_t* src_vu,
int src_stride_vu,
const uint8_t* src_uv,
int src_stride_uv,
uint8_t* dst_y,
int dst_stride_y,
uint8_t* dst_uv,
int dst_stride_uv,
uint8_t* dst_vu,
int dst_stride_vu,
int width,
int height) {
int halfwidth = (width + 1) >> 1;
int halfheight = (height + 1) >> 1;
if (!src_vu || !dst_uv || width <= 0 || height == 0 || height == INT_MIN) {
if (!src_uv || !dst_vu || width <= 0 || height == 0 || height == INT_MIN) {
return -1;
}
@ -1135,11 +1135,11 @@ int NV21ToNV12(const uint8_t* src_y,
if (height < 0) {
height = -height;
halfheight = (height + 1) >> 1;
src_vu = src_vu + (ptrdiff_t)(halfheight - 1) * src_stride_vu;
src_stride_vu = -src_stride_vu;
src_uv = src_uv + (ptrdiff_t)(halfheight - 1) * src_stride_uv;
src_stride_uv = -src_stride_uv;
}
SwapUVPlane(src_vu, src_stride_vu, dst_uv, dst_stride_uv, halfwidth,
SwapUVPlane(src_uv, src_stride_uv, dst_vu, dst_stride_vu, halfwidth,
halfheight);
return 0;
}

View File

@ -10498,12 +10498,14 @@ void NV21ToYUV24Row_AVX512(const uint8_t* src_y,
#endif // HAS_NV21ToYUV24ROW_AVX512
#ifdef HAS_SWAPUVROW_SSSE3
#if defined(HAS_SWAPUVROW_SSSE3) || defined(HAS_SWAPUVROW_AVX2)
// Shuffle table for reversing the bytes.
static const uvec8 kShuffleUVToVU = {1u, 0u, 3u, 2u, 5u, 4u, 7u, 6u,
9u, 8u, 11u, 10u, 13u, 12u, 15u, 14u};
#endif
#ifdef HAS_SWAPUVROW_SSSE3
// Convert UV plane of NV12 to VU of NV21.
void SwapUVRow_SSSE3(const uint8_t* src_uv, uint8_t* dst_vu, int width) {
asm volatile("movdqu %3,%%xmm5 \n"

View File

@ -874,6 +874,26 @@ void MirrorUVRow_AVX2(const uint8_t* src_uv, uint8_t* dst_uv, int width) {
}
#endif // HAS_MIRRORUVROW_AVX2
#ifdef HAS_SWAPUVROW_AVX2
LIBYUV_TARGET_AVX2
void SwapUVRow_AVX2(const uint8_t* src_uv, uint8_t* dst_vu, int width) {
__m256i ymm_shuf = _mm256_broadcastsi128_si256(
_mm_setr_epi8(1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14));
while (width > 0) {
__m256i ymm0 = _mm256_loadu_si256((const __m256i*)src_uv);
__m256i ymm1 = _mm256_loadu_si256((const __m256i*)(src_uv + 32));
ymm0 = _mm256_shuffle_epi8(ymm0, ymm_shuf);
ymm1 = _mm256_shuffle_epi8(ymm1, ymm_shuf);
_mm256_storeu_si256((__m256i*)dst_vu, ymm0);
_mm256_storeu_si256((__m256i*)(dst_vu + 32), ymm1);
src_uv += 64;
dst_vu += 64;
width -= 32;
}
_mm256_zeroupper();
}
#endif // HAS_SWAPUVROW_AVX2
#ifdef HAS_MIRRORSPLITUVROW_AVX2
LIBYUV_TARGET_AVX2
void MirrorSplitUVRow_AVX2(const uint8_t* src_uv,

View File

@ -588,6 +588,7 @@ TESTPLANARTOBP(I212, uint16_t, 2, 2, 1, P212, uint16_t, 2, 2, 1, 12)
TILE_HEIGHT)
#endif
TESTBPTOBP(NV12, uint8_t, 1, 2, 2, NV21, uint8_t, 1, 2, 2, 8, 1, 1)
TESTBPTOBP(NV21, uint8_t, 1, 2, 2, NV12, uint8_t, 1, 2, 2, 8, 1, 1)
TESTBPTOBP(NV12, uint8_t, 1, 2, 2, NV12Mirror, uint8_t, 1, 2, 2, 8, 1, 1)
TESTBPTOBP(NV12, uint8_t, 1, 2, 2, NV24, uint8_t, 1, 1, 1, 8, 1, 1)

View File

@ -4794,6 +4794,22 @@ TEST_F(LibYUVPlanarTest, SwapUVRow) {
MemRandomize(src_pixels_vu, kPixels * 2);
memset(dst_pixels_uv, 1, kPixels * 2);
#if defined(HAS_SWAPUVROW_SSSE3)
if (TestCpuFlag(kCpuHasSSSE3)) {
SwapUVRow = SwapUVRow_Any_SSSE3;
if (IS_ALIGNED(kPixels, 16)) {
SwapUVRow = SwapUVRow_SSSE3;
}
}
#endif
#if defined(HAS_SWAPUVROW_AVX2)
if (TestCpuFlag(kCpuHasAVX2)) {
SwapUVRow = SwapUVRow_Any_AVX2;
if (IS_ALIGNED(kPixels, 32)) {
SwapUVRow = SwapUVRow_AVX2;
}
}
#endif
#if defined(HAS_SWAPUVROW_NEON)
if (TestCpuFlag(kCpuHasNEON)) {
SwapUVRow = SwapUVRow_Any_NEON;