From eb8eda9c7973704d1103a535059224df7a6063b5 Mon Sep 17 00:00:00 2001 From: Frank Barchard Date: Mon, 5 Oct 2026 12:54:31 -0700 Subject: [PATCH] [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 Commit-Queue: Frank Barchard --- BUILD.bazel | 2 + BUILD.gn | 4 ++ CMakeLists.txt | 6 ++ include/libyuv/cpu_id.h | 4 ++ include/libyuv/row.h | 10 ++++ source/cpu_id.cc | 6 ++ source/planar_functions.cc | 8 +++ source/row_any.cc | 3 + source/row_wasm.cc | 120 +++++++++++++++++++++++++++++++++++++ unit_test/cpu_test.cc | 15 +++++ unit_test/unit_test.cc | 5 ++ util/cpuid.c | 9 +++ 12 files changed, 192 insertions(+) create mode 100644 source/row_wasm.cc diff --git a/BUILD.bazel b/BUILD.bazel index 1e93dc6eb..62bc4cd0e 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -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": [], }) diff --git a/BUILD.gn b/BUILD.gn index 99cc006fc..389a08940 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -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", diff --git a/CMakeLists.txt b/CMakeLists.txt index 65dfd72a1..29357a89f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) diff --git a/include/libyuv/cpu_id.h b/include/libyuv/cpu_id.h index cda193d98..61e15dfb8 100644 --- a/include/libyuv/cpu_id.h +++ b/include/libyuv/cpu_id.h @@ -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 diff --git a/include/libyuv/row.h b/include/libyuv/row.h index c05059207..fd2e80430 100644 --- a/include/libyuv/row.h +++ b/include/libyuv/row.h @@ -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); diff --git a/source/cpu_id.cc b/source/cpu_id.cc index 0b6f047bb..8d607b47e 100644 --- a/source/cpu_id.cc +++ b/source/cpu_id.cc @@ -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; } diff --git a/source/planar_functions.cc b/source/planar_functions.cc index edc72d70e..cfc65ecfe 100644 --- a/source/planar_functions.cc +++ b/source/planar_functions.cc @@ -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); diff --git a/source/row_any.cc b/source/row_any.cc index c7704776f..74c9d7134 100644 --- a/source/row_any.cc +++ b/source/row_any.cc @@ -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) diff --git a/source/row_wasm.cc b/source/row_wasm.cc new file mode 100644 index 000000000..5814eb1cb --- /dev/null +++ b/source/row_wasm.cc @@ -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 + +#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__) diff --git a/unit_test/cpu_test.cc b/unit_test/cpu_test.cc index 77c7ffee8..ff0999df4 100644 --- a/unit_test/cpu_test.cc +++ b/unit_test/cpu_test.cc @@ -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 diff --git a/unit_test/unit_test.cc b/unit_test/unit_test.cc index 1dcb216f0..0aaba5571 100644 --- a/unit_test/unit_test.cc +++ b/unit_test/unit_test.cc @@ -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)) diff --git a/util/cpuid.c b/util/cpuid.c index c9862dc12..c20f712bd 100644 --- a/util/cpuid.c +++ b/util/cpuid.c @@ -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);