[libyuv] Add WASM SIMD128 and Relaxed SIMD for RAWToRGB24Row

Implement RAWToRGB24Row_WASMSIMD and RAWToRGB24Row_Any_WASMSIMD in
source/row_wasm.cc for RGB24ToRAW and RAWToRGB24 conversions:
- Process 16 pixels (48 bytes) per loop iteration using three 128-bit
  loads, three byte-wise shuffles, and four 2-way blends.
- Use wasm_i8x16_relaxed_swizzle when __wasm_relaxed_simd__ is enabled
  (compiling to a single vpshufb on x86 without bounds-checking
  overhead) and fall back to wasm_i8x16_swizzle for baseline SIMD128.
- Add kCpuHasWASM (0x100) and kCpuHasWASMSIMD (0x101) CPU feature
  flags and enable -msimd128 in BUILD.bazel, BUILD.gn, and
  CMakeLists.txt for WebAssembly targets.

Performance (RAWToRGB24_Opt, 1280x720, V8 d8):
CPU                     WASM C  SIMD128  Relaxed  SIMD Spd  Rlx Spd
-------------------------------------------------------------------
AMD Zen 4             595.0 us 137.0 us  84.0 us     4.34x    7.08x
AMD Zen 3             708.0 us 148.0 us 107.0 us     4.78x    6.62x
AMD Zen 5             375.0 us 107.0 us  67.0 us     3.50x    5.60x
ARM Neoverse-V2       638.0 us 118.0 us 116.0 us     5.41x    5.50x
ARM Neoverse-N1      1046.0 us 294.0 us 294.0 us     3.56x    3.56x
Intel Ice Lake        707.0 us 209.0 us 220.0 us     3.38x    3.21x
Intel Haswell        1110.0 us 344.0 us 360.0 us     3.23x    3.08x
Intel Emerald Rapids  519.0 us 207.0 us 208.0 us     2.51x    2.50x
Intel Sapphire Rapids 551.0 us 220.0 us 255.0 us     2.50x    2.16x
ARM Cortex-A53      14440.0 us 8660.0us 6920.0us     1.67x    2.09x
Intel Granite Rapids  453.0 us 225.0 us 221.0 us     2.01x    2.05x

Bug: 42271072
Test: libyuv_test --gunit_filter=*RAWToRGB24*:*RGB24ToRAW*:*Cpu*
Change-Id: Id902668263a2ab70aa300eeeea18851490c917b5
Reviewed-on: https://chromium-review.googlesource.com/c/libyuv/libyuv/+/8515581
Reviewed-by: richard winterton <rrwinterton@gmail.com>
Commit-Queue: Frank Barchard <fbarchard@google.com>
This commit is contained in:
Frank Barchard 2026-10-05 12:54:31 -07:00 committed by libyuv-scoped@luci-project-accounts.iam.gserviceaccount.com
parent 2b7ecc0aad
commit eb8eda9c79
12 changed files with 192 additions and 0 deletions

View File

@ -69,6 +69,8 @@ ARCH_SUPPORT_COPTS = select({
"@platforms//cpu:aarch64": SUPPORTS_NEON_COPTS + SUPPORTS_SVE_COPTS,
"@platforms//cpu:riscv32": SUPPORTS_RVV_COPTS,
"@platforms//cpu:riscv64": SUPPORTS_RVV_COPTS,
"@platforms//cpu:wasm32": ["-msimd128"],
"@platforms//cpu:wasm64": ["-msimd128"],
"//conditions:default": [],
})

View File

@ -48,6 +48,9 @@ config("libyuv_config") {
if (!libyuv_use_lasx) {
defines += [ "LIBYUV_DISABLE_LASX" ]
}
if (current_cpu == "wasm") {
cflags_cc = [ "-msimd128" ]
}
}
# This target is built when no specific target is specified on the command line.
@ -166,6 +169,7 @@ static_library("libyuv_internal") {
"source/row_any.cc",
"source/row_common.cc",
"source/row_gcc.cc",
"source/row_wasm.cc",
"source/row_win.cc",
"source/scale.cc",
"source/scale_any.cc",

View File

@ -46,6 +46,7 @@ set ( ly_common_source_files
${ly_src_dir}/row_common.cc
${ly_src_dir}/row_gcc.cc
${ly_src_dir}/row_rvv.cc
${ly_src_dir}/row_wasm.cc
${ly_src_dir}/row_win.cc
${ly_src_dir}/scale.cc
${ly_src_dir}/scale_any.cc
@ -174,6 +175,11 @@ int main(void) { return 0; }
add_definitions(-DLIBYUV_DISABLE_RVV)
endif()
endif()
if(arch_lowercase MATCHES "^wasm" OR EMSCRIPTEN)
set(CMAKE_C_FLAGS "-msimd128 ${CMAKE_C_FLAGS}")
set(CMAKE_CXX_FLAGS "-msimd128 ${CMAKE_CXX_FLAGS}")
endif()
endif()
if(LOONGARCH64)

View File

@ -69,6 +69,10 @@ static const int kCpuHasLOONGARCH = 0x20;
static const int kCpuHasLSX = 0x100;
static const int kCpuHasLASX = 0x200;
// These flags are only valid on WASM processors.
static const int kCpuHasWASM = 0x40;
static const int kCpuHasWASMSIMD = 0x100;
// Optional init function. TestCpuFlag does an auto-init.
// Returns cpu_info flags.
LIBYUV_API

View File

@ -929,6 +929,10 @@ extern "C" {
#define HAS_RGBATOARGBROW_RVV
#endif
#if !defined(LIBYUV_DISABLE_WASM) && defined(__wasm_simd128__)
#define HAS_RAWTORGB24ROW_WASMSIMD
#endif
#if defined(_MSC_VER) && !defined(__CLR_VER) && !defined(__clang__)
#if defined(VISUALC_HAS_AVX2)
#define SIMD_ALIGNED(var) __declspec(align(32)) var
@ -4031,6 +4035,9 @@ void RAWToRGB24Row_SVE2(const uint8_t* src_raw, uint8_t* dst_rgb24, int width);
void RAWToRGB24Row_LSX(const uint8_t* src_raw, uint8_t* dst_rgb24, int width);
void RAWToRGB24Row_LASX(const uint8_t* src_raw, uint8_t* dst_rgb24, int width);
void RAWToRGB24Row_RVV(const uint8_t* src_raw, uint8_t* dst_rgb24, int width);
void RAWToRGB24Row_WASMSIMD(const uint8_t* src_raw,
uint8_t* dst_rgb24,
int width);
void RGB565ToARGBRow_NEON(const uint8_t* src_rgb565,
uint8_t* dst_argb,
int width);
@ -4142,6 +4149,9 @@ void RAWToRGB24Row_Any_LSX(const uint8_t* src_ptr, uint8_t* dst_ptr, int width);
void RAWToRGB24Row_Any_LASX(const uint8_t* src_ptr,
uint8_t* dst_ptr,
int width);
void RAWToRGB24Row_Any_WASMSIMD(const uint8_t* src_ptr,
uint8_t* dst_ptr,
int width);
void RGB565ToARGBRow_Any_NEON(const uint8_t* src_ptr,
uint8_t* dst_ptr,
int width);

View File

@ -492,6 +492,12 @@ static SAFEBUFFERS int GetCpuFlags(void) {
cpu_info = RiscvCpuCaps("/proc/cpuinfo");
cpu_info |= kCpuHasRISCV;
#endif // __riscv
#if defined(__wasm__)
cpu_info = kCpuHasWASM;
#if !defined(LIBYUV_DISABLE_WASM) && defined(__wasm_simd128__)
cpu_info |= kCpuHasWASMSIMD;
#endif
#endif // __wasm__
cpu_info |= kCpuInitialized;
return cpu_info;
}

View File

@ -3656,6 +3656,14 @@ int RAWToRGB24(const uint8_t* src_raw,
RAWToRGB24Row = RAWToRGB24Row_RVV;
}
#endif
#if defined(HAS_RAWTORGB24ROW_WASMSIMD)
if (TestCpuFlag(kCpuHasWASMSIMD)) {
RAWToRGB24Row = RAWToRGB24Row_Any_WASMSIMD;
if (IS_ALIGNED(width, 16)) {
RAWToRGB24Row = RAWToRGB24Row_WASMSIMD;
}
}
#endif
for (y = 0; y < height; ++y) {
RAWToRGB24Row(src_raw, dst_rgb24, width);

View File

@ -1133,6 +1133,9 @@ ANY11(RAWToRGB24Row_Any_LSX, RAWToRGB24Row_LSX, 0, 3, 3, 15)
#if defined(HAS_RAWTORGB24ROW_LASX)
ANY11(RAWToRGB24Row_Any_LASX, RAWToRGB24Row_LASX, 0, 3, 3, 31)
#endif
#if defined(HAS_RAWTORGB24ROW_WASMSIMD)
ANY11(RAWToRGB24Row_Any_WASMSIMD, RAWToRGB24Row_WASMSIMD, 0, 3, 3, 15)
#endif
#ifdef HAS_UYVYTOYROW_AVX2
ANY11(UYVYToYRow_Any_AVX2, UYVYToYRow_AVX2, 0, 2, 1, 31)

120
source/row_wasm.cc Normal file
View File

@ -0,0 +1,120 @@
/*
* Copyright 2026 The LibYuv Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "libyuv/row.h"
#if !defined(LIBYUV_DISABLE_WASM) && defined(__wasm_simd128__)
#include <wasm_simd128.h>
#ifdef __cplusplus
namespace libyuv {
extern "C" {
#endif
#ifdef HAS_RAWTORGB24ROW_WASMSIMD
#ifdef __wasm_relaxed_simd__
#define LIBYUV_WASM_SWIZZLE wasm_i8x16_relaxed_swizzle
#else
#define LIBYUV_WASM_SWIZZLE wasm_i8x16_swizzle
#endif
static const uint8_t kRawToRGB24SwizzleTable[96] = {
2, 1, 0, 5, 4, 3, 8, 7, 6, 11, 10, 9, 14, 13, 12,
128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
128, 1, 0, 128, 4, 3, 2, 7, 6, 5, 10, 9, 8, 13, 12,
11, 128, 15, 128, 1, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 2, 128, 128, 3, 2, 1, 6, 5, 4, 9, 8, 7, 12,
11, 10, 15, 14, 13, 14, 128, 128, 128, 128, 128, 128, 128, 128, 128,
128, 128, 128, 128, 128, 128};
void RAWToRGB24Row_WASMSIMD(const uint8_t* src_raw,
uint8_t* dst_rgb24,
int width) {
const uint8_t* shuf = kRawToRGB24SwizzleTable;
// Load shuffle masks into Wasm locals outside the loop so Binaryen's
// precompute-propagate pass does not sink v128.const into the loop.
asm("" : "+r"(shuf));
v128_t kShuf0_0 = wasm_v128_load(shuf);
v128_t kShuf0_1 = wasm_v128_load(shuf + 16);
v128_t kShuf1_1 = wasm_v128_load(shuf + 32);
v128_t kShuf1_02 = wasm_v128_load(shuf + 48);
v128_t kShuf2_2 = wasm_v128_load(shuf + 64);
v128_t kShuf2_1 = wasm_v128_load(shuf + 80);
while (width >= 32) {
// Prevent LLVM LSR from splitting constant load/store offsets into separate
// pointer induction variables.
asm("" : "+r"(src_raw), "+r"(dst_rgb24));
v128_t s0 = wasm_v128_load(src_raw);
v128_t s1 = wasm_v128_load(src_raw + 16);
v128_t s2 = wasm_v128_load(src_raw + 32);
v128_t s3 = wasm_v128_load(src_raw + 48);
v128_t s4 = wasm_v128_load(src_raw + 64);
v128_t s5 = wasm_v128_load(src_raw + 80);
v128_t s02 = wasm_i8x16_shuffle(s0, s2, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29);
v128_t s35 = wasm_i8x16_shuffle(s3, s5, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29);
v128_t d0 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s0, kShuf0_0),
LIBYUV_WASM_SWIZZLE(s1, kShuf0_1));
v128_t d1 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s1, kShuf1_1),
LIBYUV_WASM_SWIZZLE(s02, kShuf1_02));
v128_t d2 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s2, kShuf2_2),
LIBYUV_WASM_SWIZZLE(s1, kShuf2_1));
v128_t d3 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s3, kShuf0_0),
LIBYUV_WASM_SWIZZLE(s4, kShuf0_1));
v128_t d4 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s4, kShuf1_1),
LIBYUV_WASM_SWIZZLE(s35, kShuf1_02));
v128_t d5 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s5, kShuf2_2),
LIBYUV_WASM_SWIZZLE(s4, kShuf2_1));
wasm_v128_store(dst_rgb24, d0);
wasm_v128_store(dst_rgb24 + 16, d1);
wasm_v128_store(dst_rgb24 + 32, d2);
wasm_v128_store(dst_rgb24 + 48, d3);
wasm_v128_store(dst_rgb24 + 64, d4);
wasm_v128_store(dst_rgb24 + 80, d5);
src_raw += 96;
dst_rgb24 += 96;
width -= 32;
}
if (width > 0) {
asm("" : "+r"(src_raw), "+r"(dst_rgb24));
v128_t s0 = wasm_v128_load(src_raw);
v128_t s1 = wasm_v128_load(src_raw + 16);
v128_t s2 = wasm_v128_load(src_raw + 32);
v128_t s02 = wasm_i8x16_shuffle(s0, s2, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29);
v128_t d0 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s0, kShuf0_0),
LIBYUV_WASM_SWIZZLE(s1, kShuf0_1));
v128_t d1 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s1, kShuf1_1),
LIBYUV_WASM_SWIZZLE(s02, kShuf1_02));
v128_t d2 = wasm_v128_or(LIBYUV_WASM_SWIZZLE(s2, kShuf2_2),
LIBYUV_WASM_SWIZZLE(s1, kShuf2_1));
wasm_v128_store(dst_rgb24, d0);
wasm_v128_store(dst_rgb24 + 16, d1);
wasm_v128_store(dst_rgb24 + 32, d2);
}
}
#undef LIBYUV_WASM_SWIZZLE
#endif // HAS_RAWTORGB24ROW_WASMSIMD
#ifdef __cplusplus
} // extern "C"
} // namespace libyuv
#endif
#endif // !defined(LIBYUV_DISABLE_WASM) && defined(__wasm_simd128__)

View File

@ -163,6 +163,15 @@ TEST_F(LibYUVBaseTest, TestCpuHas) {
}
#endif // defined(__loongarch__)
#if defined(__wasm__)
int has_wasm = TestCpuFlag(kCpuHasWASM);
if (has_wasm) {
int has_wasm_simd = TestCpuFlag(kCpuHasWASMSIMD);
printf("Has WASM 0x%x\n", has_wasm);
printf("Has WASM SIMD 0x%x\n", has_wasm_simd);
}
#endif // defined(__wasm__)
#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || \
defined(_M_X64)
int has_x86 = TestCpuFlag(kCpuHasX86);
@ -288,6 +297,12 @@ TEST_F(LibYUVBaseTest, TestCompilerMacros) {
#ifdef __loongarch__
printf("__loongarch__ %d\n", __loongarch__);
#endif
#ifdef __wasm__
printf("__wasm__ %d\n", __wasm__);
#endif
#ifdef __wasm_simd128__
printf("__wasm_simd128__ %d\n", __wasm_simd128__);
#endif
#ifdef _WIN32
printf("_WIN32 %d\n", _WIN32);
#endif

View File

@ -106,6 +106,11 @@ static int TestCpuEnv(int cpu_info) {
cpu_info &= ~libyuv::kCpuHasRVV;
}
#endif
#if defined(__wasm__)
if (TestEnv("LIBYUV_DISABLE_WASM")) {
cpu_info &= ~libyuv::kCpuHasWASMSIMD;
}
#endif
#if !defined(__pnacl__) && !defined(__CLR_VER) && \
(defined(__x86_64__) || defined(_M_X64) || defined(__i386__) || \
defined(_M_IX86))

View File

@ -136,6 +136,15 @@ int main(int argc, const char* argv[]) {
}
#endif // defined(__loongarch__)
#if defined(__wasm__)
int has_wasm = TestCpuFlag(kCpuHasWASM);
if (has_wasm) {
int has_wasm_simd = TestCpuFlag(kCpuHasWASMSIMD);
printf("Has WASM 0x%x\n", has_wasm);
printf("Has WASM SIMD 0x%x\n", has_wasm_simd);
}
#endif // defined(__wasm__)
#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || \
defined(_M_X64)
int has_x86 = TestCpuFlag(kCpuHasX86);