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);