Proposal: Add Google Highway portable-SIMD tier for non-x86/aarch64 platforms

Summary

SVT-AV1 ships hand-written SIMD for x86 and aarch64. On every other architecture the encoder falls back to plain C.

I propose an additional, strictly additive SIMD tier written against Google Highway: kernels are written once against Highway's portable ops, compiled for every Highway target of the build architecture, and dispatched at runtime to the best target the CPU supports.

I have almost completely implemented and tested the SVT-AV1 Highway backend. See my repository:

This is not a new idea inside AOMedia, libaom has been merging Highway kernels since 2025. The dependency form and the C++-in-a-C-library mix therefore both have an in-organization precedent. My proposal extends the same model to the platforms that have no hand-written SIMD at all.

Motivation

Outside x86 and aarch64, SVT-AV1 has no hand-written SIMD at all. On RISC-V, LoongArch and similar platforms the encoder runs on the C reference plus whatever the compiler's auto-vectorizer manages to make of it, which is slow in practice (measured below, distro interest in these targets already came up in #2239 (closed)).

Writing and maintaining per-architecture kernels for each of these platforms by hand is not realistic, but a single Highway version of each kernel covers all of them and still lets each CPU run its widest vector ISA. RVV is the most natural fit: Highway's scalable vectors are RVV's own programming model.

Design

The tier never replaces a native SIMD kernel. After the native rtcd setup, each covered slot is filled only if it still points at the C reference:

x86/aarch64 behavior is unchanged. The tier defaults to OFF there and is not even compiled unless -DENABLE_HIGHWAY=ON is passed.

  • On other platforms it defaults to ON. It is currently the only SIMD tier there.
  • Highway is an optional dependency: if not found, the build continues with a warning and pure-C behavior.
  • Dispatch uses Highway's per-target mechanism with the function pointer resolved once at init. The hot path is a plain indirect call.
  • --asm hwy selects the Highway tier alone (useful for exercising it on x86/aarch64 hosts). --asm c still forces pure C.

Performance

I've tested my implementation on the devices I have access to. I don't have a device that supports RVV, so I haven't been able to test the performance of RVV.

Loongson 3C5000L (LoongArch64's LA464, dual socket), clang build

Command: ffmpeg -i input.webm -c:a copy -c:v libsvtav1 -f null - -benchmark (ffmpeg 8.1.2, default libsvtav1 settings; input: 3840x2160, yuv420p, bt709, 23.98 fps). With the SVT-AV1 branch hwy-upstream-v4.2.0 on my repo.

build speed vs upstream
upstream master (auto-vectorized by compiler for LSX) 0.151x realtime (3.6 fps) 1.0
Highway tier, LSX (128-bit) 0.404x (9.7 fps) 2.7x
Highway tier, LASX (256-bit) 0.44x (11 fps) 2.9x

The LSX to LASX scaling on the same source also demonstrates that the runtime dispatch actually uses the widest ISA the CPU offers rather than a fixed 128-bit baseline; the same sources scale to RVV VLEN and AVX-512 widths.

These are end-to-end figures, the input is software-decoded in the same run and that fixed decode cost is included in all three builds, so the speedup of the encoder itself is strictly larger than the ratios above.

Apple M4 Pro (aarch64), 1080p 300 frames, preset 8

End-to-end (median of 3 runs, run-to-run variance on this host is ±15%):

Level FPS Relative
--asm c 45.5 fps 1.0x
--asm hwy 124.1 fps 2.7x
--asm max 135.0 fps 3.0x

Bitstreams are byte-identical across all three. Per-kernel, measured via direct rtcd function-pointer calls (ns/call, min of 3, note the "C" column is clang-auto-vectorized NEON, not scalar):

kernel C Highway hand-written NEON
sad64x64 484 45 45
nxm_sad64x64 488 45 46
variance64x64 338 66 65
subpel_variance64x64 733 280 260
convolve_y_sr 64x64 9813 350 276
convolve_2d_sr 64x64 14605 1300 803
fwd_txfm2d_32x32 2298 548 360

SAD/variance/reduction kernels reach parity with the hand-written NEON. Convolve and sub-pixel variance are within 1.3-1.6x. The remaining gaps come from NEON-only instruction fusions that portable code cannot express they shrink as Highway backends improve, without any change on our side.

Correctness

Every Highway kernel is checked against the C reference in the gtest suites, with random data over all block sizes, and must match bit exactly. 5740 such tests pass. Encoder output is byte-identical between --asm c, --asm hwy and --asm max aarch64, and also byte-identical between --asm c, --asm hwy on LoongArch64.

Kernels that do not beat the auto-vectorized C reference on the validation host are kept unregistered on purpose. They are still covered by the tests and listed in the design doc.

Dependency

Highway >= 1.4.0 is currently found via find_package/pkg-config as an optional system dependency.

  • The tier is C++ (required by Highway) compiled as an object library inside the otherwise-C encoder. That's the arrangement libaom already uses.
  • COMPILE_C_ONLY=ON disables the tier entirely.

Scope

~24 kernel families, ~27.5k lines: SAD, variance/MSE, sub-pixel variance, 8-bit and highbd convolve, joint compound, OBMC, warped motion, intra prediction, CDEF, deblocking, loop restoration, temporal filtering, picture operators, forward/inverse transforms, quantization/encodetxb, compound blend/masks, and misc DSP. Full list: https://gitlab.com/wszqkzqk/SVT-AV1/-/blob/master/Docs/Appendix-Highway-Portable-SIMD.md?ref_type=heads.

Edited by Zhou Qiankang