Commit 8f87187c0c for aom
commit 8f87187c0c12b87b72a148b95a6679ee243c41c4
Author: Lin Zheng <linzhen@google.com>
Date: Sun Nov 8 23:00:35 2026 +0000
Highway: Add AVX512 variance, mse, and var stats
Function / Block Size │ AVX2 (ns) │ AVX512 (ns) │ Speedup (x)
─────────────────────────┼───────────┼─────────────┼────────────
variance 16x4 │ 3.67 │ 3.52 │ 1.04x
variance 16x8 │ 5.47 │ 5.01 │ 1.09x
variance 16x16 │ 10.09 │ 7.99 │ 1.26x
variance 16x32 │ 18.39 │ 14.16 │ 1.30x
variance 16x64 │ 39.76 │ 27.25 │ 1.46x
variance 32x8 │ 8.32 │ 6.52 │ 1.28x
variance 32x16 │ 18.58 │ 10.68 │ 1.74x
variance 32x32 │ 30.17 │ 20.90 │ 1.44x
variance 32x64 │ 59.39 │ 39.85 │ 1.49x
variance 64x16 │ 27.51 │ 22.80 │ 1.21x
variance 64x32 │ 52.99 │ 30.63 │ 1.73x
variance 64x64 │ 105.18 │ 59.44 │ 1.77x
variance 64x128 │ 207.84 │ 118.49 │ 1.75x
variance 128x64 │ 220.94 │ 106.01 │ 2.08x
variance 128x128 │ 439.94 │ 210.40 │ 2.09x
mse16x16 │ 10.22 │ 7.61 │ 1.34x
get_blk_sse_sum 64x64 │ 140.32 │ 105.47 │ 1.33x
calc_variance_stat 64x64│ 409.00 │ 169.71 │ 2.41x
Change-Id: I4318dfa229d6d162aad69b25eec2ee524e4ae9ed
diff --git a/aom_dsp/aom_dsp.cmake b/aom_dsp/aom_dsp.cmake
index 869152568a..2c6ee2537a 100644
--- a/aom_dsp/aom_dsp.cmake
+++ b/aom_dsp/aom_dsp.cmake
@@ -188,7 +188,8 @@ if(CONFIG_AV1_ENCODER)
if(CONFIG_HIGHWAY)
list(APPEND AOM_DSP_ENCODER_SOURCES "${AOM_ROOT}/aom_dsp/reduce_sum_hwy.h"
"${AOM_ROOT}/aom_dsp/sad_hwy.h"
- "${AOM_ROOT}/aom_dsp/subtract_hwy.h")
+ "${AOM_ROOT}/aom_dsp/subtract_hwy.h"
+ "${AOM_ROOT}/aom_dsp/variance_hwy.h")
endif()
# Flow estimation library and grain/noise table/model.
@@ -274,7 +275,8 @@ if(CONFIG_AV1_ENCODER)
"${AOM_ROOT}/aom_dsp/x86/sad_hwy_avx2.cc")
list(APPEND AOM_DSP_ENCODER_INTRIN_AVX512
"${AOM_ROOT}/aom_dsp/x86/sad_hwy_avx512.cc"
- "${AOM_ROOT}/aom_dsp/x86/subtract_hwy_avx512.cc")
+ "${AOM_ROOT}/aom_dsp/x86/subtract_hwy_avx512.cc"
+ "${AOM_ROOT}/aom_dsp/x86/variance_hwy_avx512.cc")
list(REMOVE_ITEM AOM_DSP_ENCODER_INTRIN_AVX2
"${AOM_ROOT}/aom_dsp/x86/sad_impl_avx2.c")
endif()
diff --git a/aom_dsp/aom_dsp_rtcd_defs.pl b/aom_dsp/aom_dsp_rtcd_defs.pl
index ed27e7d0fe..c8b28deb93 100755
--- a/aom_dsp/aom_dsp_rtcd_defs.pl
+++ b/aom_dsp/aom_dsp_rtcd_defs.pl
@@ -827,7 +827,11 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_sse sse4_1 avx2 neon neon_dotprod/;
add_proto qw/void/, "aom_get_blk_sse_sum", "const int16_t *data, int stride, int bw, int bh, int *x_sum, int64_t *x2_sum";
- specialize qw/aom_get_blk_sse_sum sse2 avx2 neon sve/;
+ if (aom_config("CONFIG_HIGHWAY") eq "yes") {
+ specialize qw/aom_get_blk_sse_sum sse2 avx2 avx512 neon sve/;
+ } else {
+ specialize qw/aom_get_blk_sse_sum sse2 avx2 neon sve/;
+ }
if (aom_config("CONFIG_AV1_HIGHBITDEPTH") eq "yes") {
add_proto qw/void aom_highbd_subtract_block/, "int rows, int cols, int16_t *diff_ptr, ptrdiff_t diff_stride, const uint8_t *src_ptr, ptrdiff_t src_stride, const uint8_t *pred_ptr, ptrdiff_t pred_stride";
@@ -1413,7 +1417,11 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
add_proto qw/unsigned int aom_mse8x16/, "const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int recon_stride, unsigned int *sse";
add_proto qw/unsigned int aom_mse8x8/, "const uint8_t *src_ptr, int source_stride, const uint8_t *ref_ptr, int recon_stride, unsigned int *sse";
- specialize qw/aom_mse16x16 sse2 avx2 neon neon_dotprod/;
+ if (aom_config("CONFIG_HIGHWAY") eq "yes") {
+ specialize qw/aom_mse16x16 sse2 avx2 avx512 neon neon_dotprod/;
+ } else {
+ specialize qw/aom_mse16x16 sse2 avx2 neon neon_dotprod/;
+ }
specialize qw/aom_mse16x8 sse2 neon neon_dotprod/;
specialize qw/aom_mse8x16 sse2 neon neon_dotprod/;
specialize qw/aom_mse8x8 sse2 neon neon_dotprod/;
@@ -1457,11 +1465,19 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_mse_wxh_16bit sse2 avx2 neon/;
add_proto qw/int64_t/, "aom_calc_variance_stat", "const uint8_t *src, int stride, int bw, int bh";
- specialize qw/aom_calc_variance_stat avx2/;
+ if (aom_config("CONFIG_HIGHWAY") eq "yes") {
+ specialize qw/aom_calc_variance_stat avx2 avx512/;
+ } else {
+ specialize qw/aom_calc_variance_stat avx2/;
+ }
if (aom_config("CONFIG_AV1_HIGHBITDEPTH") eq "yes") {
add_proto qw/int64_t/, "aom_highbd_calc_variance_stat", "const uint16_t *src, int stride, int bw, int bh";
- specialize qw/aom_highbd_calc_variance_stat avx2/;
+ if (aom_config("CONFIG_HIGHWAY") eq "yes") {
+ specialize qw/aom_highbd_calc_variance_stat avx2 avx512/;
+ } else {
+ specialize qw/aom_highbd_calc_variance_stat avx2/;
+ }
}
add_proto qw/uint64_t/, "aom_mse_16xh_16bit", "uint8_t *dst, int dstride,uint16_t *src, int w, int h";
@@ -1490,6 +1506,20 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_variance4x8 sse2 neon neon_dotprod/;
specialize qw/aom_variance4x4 sse2 neon/;
+ if (aom_config("CONFIG_HIGHWAY") eq "yes") {
+ specialize qw/aom_variance128x128 avx512/;
+ specialize qw/aom_variance128x64 avx512/;
+ specialize qw/aom_variance64x128 avx512/;
+ specialize qw/aom_variance64x64 avx512/;
+ specialize qw/aom_variance64x32 avx512/;
+ specialize qw/aom_variance32x64 avx512/;
+ specialize qw/aom_variance32x32 avx512/;
+ specialize qw/aom_variance32x16 avx512/;
+ specialize qw/aom_variance16x32 avx512/;
+ specialize qw/aom_variance16x16 avx512/;
+ specialize qw/aom_variance16x8 avx512/;
+ }
+
specialize qw/aom_sub_pixel_variance128x128 avx2 neon neon_dotprod/, "$ssse3_asm";
specialize qw/aom_sub_pixel_variance128x64 avx2 neon neon_dotprod/, "$ssse3_asm";
specialize qw/aom_sub_pixel_variance64x128 avx2 neon neon_dotprod/, "$ssse3_asm";
@@ -1532,6 +1562,13 @@ if (aom_config("CONFIG_AV1_ENCODER") eq "yes") {
specialize qw/aom_variance16x64 neon neon_dotprod sse2 avx2/;
specialize qw/aom_variance64x16 neon neon_dotprod sse2 avx2/;
+ if (aom_config("CONFIG_HIGHWAY") eq "yes") {
+ specialize qw/aom_variance16x4 avx512/;
+ specialize qw/aom_variance32x8 avx512/;
+ specialize qw/aom_variance16x64 avx512/;
+ specialize qw/aom_variance64x16 avx512/;
+ }
+
specialize qw/aom_sub_pixel_variance4x16 neon neon_dotprod/, "$ssse3_asm";
specialize qw/aom_sub_pixel_variance16x4 neon neon_dotprod avx2/, "$ssse3_asm";
specialize qw/aom_sub_pixel_variance8x32 neon neon_dotprod/, "$ssse3_asm";
diff --git a/aom_dsp/variance_hwy.h b/aom_dsp/variance_hwy.h
new file mode 100644
index 0000000000..73d2cd09d3
--- /dev/null
+++ b/aom_dsp/variance_hwy.h
@@ -0,0 +1,517 @@
+/*
+ * Copyright (c) 2026, Alliance for Open Media. All rights reserved.
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#ifndef AOM_AOM_DSP_VARIANCE_HWY_H_
+#define AOM_AOM_DSP_VARIANCE_HWY_H_
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include "config/aom_config.h"
+#include "config/aom_dsp_rtcd.h"
+
+#include "aom_dsp/reduce_sum_hwy.h"
+#include "third_party/highway/hwy/highway.h"
+
+HWY_BEFORE_NAMESPACE();
+
+namespace {
+namespace HWY_NAMESPACE {
+
+namespace hn = hwy::HWY_NAMESPACE;
+
+template <class D, class VHalf>
+HWY_ATTR HWY_INLINE hn::VFromD<D> Combine2Rows(D d, VHalf r0, VHalf r1) {
+ HWY_IF_CONSTEXPR(hn::MaxLanes(d) * sizeof(hn::TFromD<D>) >= 32) {
+ return hn::ConcatLowerLower(d, hn::ResizeBitCast(d, r1),
+ hn::ResizeBitCast(d, r0));
+ }
+ return hn::Combine(d, r1, r0);
+}
+
+template <class D, class VQuarter>
+HWY_ATTR HWY_INLINE hn::VFromD<D> Combine4Rows(D d, VQuarter r0, VQuarter r1,
+ VQuarter r2, VQuarter r3) {
+ HWY_IF_CONSTEXPR(hn::MaxLanes(d) * sizeof(hn::TFromD<D>) == 64) {
+ auto v = hn::ResizeBitCast(d, r0);
+ v = hn::InsertBlock<1>(v, r1);
+ v = hn::InsertBlock<2>(v, r2);
+ return hn::InsertBlock<3>(v, r3);
+ }
+ constexpr hn::Half<D> d_half;
+ auto r01 = Combine2Rows(d_half, r0, r1);
+ auto r23 = Combine2Rows(d_half, r2, r3);
+ return Combine2Rows(d, r01, r23);
+}
+
+template <int RowsPerVec, class D>
+HWY_ATTR HWY_INLINE hn::VFromD<D> LoadRowsU(D full_tag,
+ const hn::TFromD<D> *ptr,
+ ptrdiff_t stride) {
+ HWY_IF_CONSTEXPR(RowsPerVec == 4) {
+ constexpr hn::Half<hn::Half<D>> row_tag;
+ const hn::TFromD<D> *ptr2 = ptr + stride * 2;
+ return Combine4Rows(
+ full_tag, hn::LoadU(row_tag, ptr), hn::LoadU(row_tag, ptr + stride),
+ hn::LoadU(row_tag, ptr2), hn::LoadU(row_tag, ptr2 + stride));
+ }
+ HWY_IF_CONSTEXPR(RowsPerVec == 2) {
+ constexpr hn::Half<D> row_tag;
+ return Combine2Rows(full_tag, hn::LoadU(row_tag, ptr),
+ hn::LoadU(row_tag, ptr + stride));
+ }
+ return hn::LoadU(full_tag, ptr);
+}
+
+template <class DU8, class DI32, class DI16>
+HWY_ATTR HWY_INLINE void VarianceKernel(DU8 du8, DI32 di32, DI16 di16,
+ hn::VFromD<DU8> src,
+ hn::VFromD<DU8> ref,
+ hn::VFromD<DI32> *sse,
+ hn::VFromD<DI16> *sum) {
+ constexpr hn::Repartition<int8_t, DU8> di8;
+ const auto adj_sub =
+ hn::BitCast(di8, hn::Set(di16, static_cast<int16_t>(0xff01)));
+ const auto s_lo = hn::InterleaveLower(du8, src, ref);
+ const auto s_hi = hn::InterleaveUpper(du8, src, ref);
+ const auto diff_lo = hn::SatWidenMulPairwiseAdd(di16, s_lo, adj_sub);
+ const auto diff_hi = hn::SatWidenMulPairwiseAdd(di16, s_hi, adj_sub);
+
+ *sum = hn::Add(*sum, hn::Add(diff_lo, diff_hi));
+ const auto madd_lo = hn::WidenMulPairwiseAdd(di32, diff_lo, diff_lo);
+ const auto madd_hi = hn::WidenMulPairwiseAdd(di32, diff_hi, diff_hi);
+ *sse = hn::Add(*sse, hn::Add(madd_lo, madd_hi));
+}
+
+template <class DI32>
+HWY_ATTR HWY_INLINE int VarianceFinalFrom32BitSum(DI32 di32,
+ hn::VFromD<DI32> vsse,
+ hn::VFromD<DI32> vsum32,
+ unsigned int *const sse) {
+ constexpr hn::BlockDFromD<DI32> d128;
+ const auto sse128 = BlockReduceSum(di32, vsse);
+ const auto sum128 = BlockReduceSum(di32, vsum32);
+ const auto pair0 = hn::InterleaveLower(d128, sse128, sum128);
+ const auto pair1 = hn::InterleaveUpper(d128, sse128, sum128);
+ const auto sum_pair = hn::Add(pair0, pair1);
+ constexpr hn::FixedTag<int32_t, 2> d64;
+ const auto res =
+ hn::Add(hn::LowerHalf(d64, sum_pair), hn::UpperHalf(d64, sum_pair));
+ *sse = static_cast<unsigned int>(hn::GetLane(res));
+ return hn::ExtractLane(res, 1);
+}
+
+template <class DI32, class DI16>
+HWY_ATTR HWY_INLINE int VarianceFinalFrom16BitSum(DI32 di32, DI16 di16,
+ hn::VFromD<DI32> vsse,
+ hn::VFromD<DI16> vsum,
+ unsigned int *const sse) {
+ const auto one = hn::Set(di16, int16_t{ 1 });
+ const auto vsum32 = hn::WidenMulPairwiseAdd(di32, vsum, one);
+ return VarianceFinalFrom32BitSum(di32, vsse, vsum32, sse);
+}
+
+template <int BlockWidth>
+HWY_ATTR HWY_INLINE void VarianceBlock(
+ const uint8_t *src, int src_stride, const uint8_t *ref, int ref_stride,
+ int h, hn::Vec<hn::ScalableTag<int32_t>> *vsse,
+ hn::Vec<hn::ScalableTag<int16_t>> *vsum) {
+ constexpr hn::ScalableTag<uint8_t> du8;
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr hn::ScalableTag<int32_t> di32;
+ constexpr int vw = hn::MaxLanes(du8);
+ constexpr int kRowsPerVec = (BlockWidth < vw) ? (vw / BlockWidth) : 1;
+ constexpr int kRowSpan = (BlockWidth < vw) ? vw : BlockWidth;
+ assert(h % kRowsPerVec == 0);
+
+ *vsum = hn::Zero(di16);
+ for (int i = 0; i < h; i += kRowsPerVec) {
+ for (int j = 0; j < kRowSpan; j += vw) {
+ const auto s = LoadRowsU<kRowsPerVec>(du8, src + j, src_stride);
+ const auto r = LoadRowsU<kRowsPerVec>(du8, ref + j, ref_stride);
+ VarianceKernel(du8, di32, di16, s, r, vsse, vsum);
+ }
+ src += kRowsPerVec * src_stride;
+ ref += kRowsPerVec * ref_stride;
+ }
+}
+
+template <int BlockWidth, int BlockHeight>
+HWY_ATTR HWY_INLINE void GetBlockVar(const uint8_t *src, int src_stride,
+ const uint8_t *ref, int ref_stride,
+ unsigned int *sse, int *sum) {
+ constexpr hn::ScalableTag<uint8_t> du8;
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr hn::ScalableTag<int32_t> di32;
+ constexpr int vw = hn::MaxLanes(du8);
+ constexpr int kRowsPerVec = (BlockWidth < vw) ? (vw / BlockWidth) : 1;
+ // Each int16_t lane in vsum accumulates 2 pixel diffs per iteration of vw
+ // bytes. With 128 diffs per lane (128 * 255 = 32640 <= 32767), we can safely
+ // accumulate up to 64 * vw pixels before widening vsum to 32-bit.
+ constexpr int kMaxPixelsPerChunk = 64 * vw;
+ constexpr int kChunkHeight = (BlockWidth * BlockHeight <= kMaxPixelsPerChunk)
+ ? BlockHeight
+ : (kMaxPixelsPerChunk / BlockWidth);
+ static_assert(BlockHeight % kRowsPerVec == 0,
+ "BlockHeight must be a multiple of kRowsPerVec.");
+ static_assert(BlockHeight % kChunkHeight == 0,
+ "BlockHeight must be a multiple of kChunkHeight.");
+ static_assert(kChunkHeight % kRowsPerVec == 0,
+ "kChunkHeight must be a multiple of kRowsPerVec.");
+
+ auto vsse = hn::Zero(di32);
+ HWY_IF_CONSTEXPR(kChunkHeight == BlockHeight) {
+ hn::Vec<decltype(di16)> vsum;
+ VarianceBlock<BlockWidth>(src, src_stride, ref, ref_stride, BlockHeight,
+ &vsse, &vsum);
+ *sum = VarianceFinalFrom16BitSum(di32, di16, vsse, vsum, sse);
+ }
+ HWY_IF_CONSTEXPR(kChunkHeight != BlockHeight) {
+ auto vsum32 = hn::Zero(di32);
+ const auto one = hn::Set(di16, int16_t{ 1 });
+ for (int i = 0; i < BlockHeight / kChunkHeight; ++i) {
+ hn::Vec<decltype(di16)> vsum16;
+ VarianceBlock<BlockWidth>(src, src_stride, ref, ref_stride, kChunkHeight,
+ &vsse, &vsum16);
+ vsum32 = hn::Add(vsum32, hn::WidenMulPairwiseAdd(di32, vsum16, one));
+ src += kChunkHeight * src_stride;
+ ref += kChunkHeight * ref_stride;
+ }
+ *sum = VarianceFinalFrom32BitSum(di32, vsse, vsum32, sse);
+ }
+}
+
+template <int BlockWidth, int BlockHeight, int Bits>
+HWY_ATTR HWY_INLINE unsigned int VarianceWxH(const uint8_t *src, int src_stride,
+ const uint8_t *ref, int ref_stride,
+ unsigned int *sse) {
+ int sum = 0;
+ GetBlockVar<BlockWidth, BlockHeight>(src, src_stride, ref, ref_stride, sse,
+ &sum);
+ return *sse -
+ static_cast<unsigned int>((static_cast<int64_t>(sum) * sum) >> Bits);
+}
+
+HWY_ATTR HWY_INLINE void GetBlkSseSum(const int16_t *data, int stride, int bw,
+ int bh, int *x_sum, int64_t *x2_sum) {
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr hn::ScalableTag<int32_t> di32;
+ constexpr hn::ScalableTag<uint32_t> du32;
+ constexpr hn::ScalableTag<uint64_t> du64;
+ constexpr int kLanes = hn::MaxLanes(di16);
+
+ if (bw <= 0 || bh <= 0) {
+ *x_sum = 0;
+ *x2_sum = 0;
+ return;
+ }
+ if (bw < kLanes || bw % kLanes != 0) {
+ aom_get_blk_sse_sum_avx2(data, stride, bw, bh, x_sum, x2_sum);
+ return;
+ }
+
+ const auto one = hn::Set(di16, int16_t{ 1 });
+ auto sum_32 = hn::Zero(di32);
+ auto sse_64 = hn::Zero(du64);
+
+ for (int i = 0; i < bh; ++i) {
+ for (int j = 0; j < bw; j += kLanes) {
+ const auto d = hn::LoadU(di16, data + j);
+ sum_32 = hn::Add(sum_32, hn::WidenMulPairwiseAdd(di32, d, one));
+ const auto sq = hn::BitCast(du32, hn::WidenMulPairwiseAdd(di32, d, d));
+ const auto sq_lo = hn::PromoteLowerTo(du64, sq);
+ const auto sq_hi = hn::PromoteUpperTo(du64, sq);
+ sse_64 = hn::Add(sse_64, hn::Add(sq_lo, sq_hi));
+ }
+ data += stride;
+ }
+ *x_sum = static_cast<int>(hn::ReduceSum(di32, sum_32));
+ *x2_sum = static_cast<int64_t>(hn::ReduceSum(du64, sse_64));
+}
+
+HWY_ATTR HWY_INLINE void LoadRowHorizFilterU8(
+ const uint8_t *src_row, int x, int bw,
+ hn::Vec<hn::ScalableTag<int16_t>> *u16_curr_out,
+ hn::Vec<hn::ScalableTag<int16_t>> *u16_H_out) {
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr hn::Rebind<uint8_t, decltype(di16)> du8_half;
+ constexpr int kCols = hn::MaxLanes(du8_half);
+ static_assert(kCols == 16 || kCols == 32, "kCols must be 16 or 32.");
+ alignas(64) static constexpr int16_t kLeftIdx[32] = {
+ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
+ };
+ alignas(64) static constexpr int16_t kRightIdx32[32] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 31
+ };
+ alignas(32) static constexpr int16_t kRightIdx16[16] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15
+ };
+
+ const auto u16_curr = hn::PromoteTo(di16, hn::LoadU(du8_half, src_row + x));
+ hn::Vec<decltype(di16)> u16_left;
+ hn::Vec<decltype(di16)> u16_right;
+
+ if (x == 0) {
+ u16_left =
+ hn::TableLookupLanes(u16_curr, hn::SetTableIndices(di16, kLeftIdx));
+ } else {
+ u16_left = hn::PromoteTo(di16, hn::LoadU(du8_half, src_row + x - 1));
+ }
+
+ if (x + kCols < bw) {
+ u16_right = hn::PromoteTo(di16, hn::LoadU(du8_half, src_row + x + 1));
+ } else {
+ const int16_t *right_idx = (kCols == 32) ? kRightIdx32 : kRightIdx16;
+ u16_right =
+ hn::TableLookupLanes(u16_curr, hn::SetTableIndices(di16, right_idx));
+ }
+
+ *u16_curr_out = u16_curr;
+ *u16_H_out =
+ hn::Add(hn::Add(u16_left, u16_right), hn::ShiftLeft<1>(u16_curr));
+}
+
+HWY_ATTR HWY_INLINE int64_t CalcVarianceStat(const uint8_t *src, int stride,
+ int bw, int bh) {
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr hn::ScalableTag<int32_t> di32;
+ constexpr int kCols = hn::MaxLanes(di16);
+
+ if (bw <= 0 || bh <= 0) return 0;
+ if (bw == 4 || bw == 8 || bw == 16) {
+ return aom_calc_variance_stat_avx2(src, stride, bw, bh);
+ }
+ if (bw % kCols != 0) {
+ return aom_calc_variance_stat_c(src, stride, bw, bh);
+ }
+
+ auto acc_var = hn::Zero(di32);
+
+ for (int x = 0; x < bw; x += kCols) {
+ hn::Vec<decltype(di16)> u16_curr, H_curr;
+ LoadRowHorizFilterU8(src, x, bw, &u16_curr, &H_curr);
+ auto H_top = H_curr;
+
+ for (int y = 0; y < bh - 1; ++y) {
+ hn::Vec<decltype(di16)> u16_next, H_bot;
+ LoadRowHorizFilterU8(src + (y + 1) * stride, x, bw, &u16_next, &H_bot);
+ const auto u16_V =
+ hn::Add(hn::Add(H_top, H_bot), hn::ShiftLeft<1>(H_curr));
+ const auto u16_sum = hn::ShiftRight<4>(u16_V);
+ const auto diff = hn::Sub(u16_curr, u16_sum);
+ acc_var = hn::Add(acc_var, hn::WidenMulPairwiseAdd(di32, diff, diff));
+ H_top = H_curr;
+ H_curr = H_bot;
+ u16_curr = u16_next;
+ }
+ {
+ const auto u16_V =
+ hn::Add(hn::Add(H_top, H_curr), hn::ShiftLeft<1>(H_curr));
+ const auto u16_sum = hn::ShiftRight<4>(u16_V);
+ const auto diff = hn::Sub(u16_curr, u16_sum);
+ acc_var = hn::Add(acc_var, hn::WidenMulPairwiseAdd(di32, diff, diff));
+ }
+ }
+ return static_cast<int64_t>(hn::ReduceSum(di32, acc_var)) << 4;
+}
+
+#if CONFIG_AV1_HIGHBITDEPTH
+HWY_ATTR HWY_INLINE void HighbdLoadRowHorizFilterU16(
+ const uint16_t *src_row, int x, int bw,
+ hn::Vec<hn::ScalableTag<int16_t>> *v_curr_out,
+ hn::Vec<hn::ScalableTag<uint16_t>> *u16_H_out) {
+ constexpr hn::ScalableTag<uint16_t> du16;
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr int kCols = hn::MaxLanes(du16);
+ static_assert(kCols == 16 || kCols == 32, "kCols must be 16 or 32.");
+ alignas(64) static constexpr uint16_t kLeftIdx[32] = {
+ 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30
+ };
+ alignas(64) static constexpr uint16_t kRightIdx32[32] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 31
+ };
+ alignas(32) static constexpr uint16_t kRightIdx16[16] = {
+ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15
+ };
+
+ const auto v_curr = hn::LoadU(du16, src_row + x);
+ // Input pixels must be <= 4095 (12-bit) so that the 3x3 Gaussian filter sum
+ // u16_V = H_top + 2 * H_curr + H_bot <= 16 * 4095 = 65520 fits in uint16_t.
+ assert(hn::AllTrue(du16, hn::Le(v_curr, hn::Set(du16, uint16_t{ 4095 }))));
+ hn::Vec<decltype(du16)> v_left;
+ hn::Vec<decltype(du16)> v_right;
+
+ if (x == 0) {
+ v_left = hn::TableLookupLanes(v_curr, hn::SetTableIndices(du16, kLeftIdx));
+ } else {
+ v_left = hn::LoadU(du16, src_row + x - 1);
+ }
+
+ if (x + kCols < bw) {
+ v_right = hn::LoadU(du16, src_row + x + 1);
+ } else {
+ const uint16_t *right_idx = (kCols == 32) ? kRightIdx32 : kRightIdx16;
+ v_right =
+ hn::TableLookupLanes(v_curr, hn::SetTableIndices(du16, right_idx));
+ }
+
+ *v_curr_out = hn::BitCast(di16, v_curr);
+ *u16_H_out = hn::Add(hn::Add(v_left, v_right), hn::ShiftLeft<1>(v_curr));
+}
+
+// Assumes 8/10/12-bit input pixels (src[i] <= 4095) so that the 16-weight 3x3
+// Gaussian filter sum (<= 65520) does not overflow uint16_t.
+HWY_ATTR HWY_INLINE int64_t HighbdCalcVarianceStat(const uint16_t *src,
+ int stride, int bw, int bh) {
+ constexpr hn::ScalableTag<uint16_t> du16;
+ constexpr hn::ScalableTag<int16_t> di16;
+ constexpr hn::ScalableTag<int32_t> di32;
+ constexpr hn::ScalableTag<int64_t> di64;
+ constexpr int kCols = hn::MaxLanes(du16);
+
+ if (bw <= 0 || bh <= 0) return 0;
+ if (bw == 4 || bw == 8 || bw == 16) {
+ return aom_highbd_calc_variance_stat_avx2(src, stride, bw, bh);
+ }
+ if (bw % kCols != 0) {
+ return aom_highbd_calc_variance_stat_c(src, stride, bw, bh);
+ }
+
+ auto acc_var_64 = hn::Zero(di64);
+
+ for (int x = 0; x < bw; x += kCols) {
+ hn::Vec<decltype(di16)> v_curr;
+ hn::Vec<decltype(du16)> H_curr;
+ HighbdLoadRowHorizFilterU16(src, x, bw, &v_curr, &H_curr);
+ auto H_top = H_curr;
+
+ for (int y = 0; y < bh - 1; ++y) {
+ hn::Vec<decltype(di16)> v_next;
+ hn::Vec<decltype(du16)> H_bot;
+ HighbdLoadRowHorizFilterU16(src + (y + 1) * stride, x, bw, &v_next,
+ &H_bot);
+ const auto u16_V =
+ hn::Add(hn::Add(H_top, H_bot), hn::ShiftLeft<1>(H_curr));
+ const auto u16_sum = hn::BitCast(di16, hn::ShiftRight<4>(u16_V));
+ const auto diff = hn::Sub(v_curr, u16_sum);
+ const auto diff_sq = hn::WidenMulPairwiseAdd(di32, diff, diff);
+ acc_var_64 =
+ hn::Add(acc_var_64, hn::Add(hn::PromoteLowerTo(di64, diff_sq),
+ hn::PromoteUpperTo(di64, diff_sq)));
+ H_top = H_curr;
+ H_curr = H_bot;
+ v_curr = v_next;
+ }
+ {
+ const auto u16_V =
+ hn::Add(hn::Add(H_top, H_curr), hn::ShiftLeft<1>(H_curr));
+ const auto u16_sum = hn::BitCast(di16, hn::ShiftRight<4>(u16_V));
+ const auto diff = hn::Sub(v_curr, u16_sum);
+ const auto diff_sq = hn::WidenMulPairwiseAdd(di32, diff, diff);
+ acc_var_64 =
+ hn::Add(acc_var_64, hn::Add(hn::PromoteLowerTo(di64, diff_sq),
+ hn::PromoteUpperTo(di64, diff_sq)));
+ }
+ }
+ return static_cast<int64_t>(hn::ReduceSum(di64, acc_var_64)) << 4;
+}
+#endif // CONFIG_AV1_HIGHBITDEPTH
+
+} // namespace HWY_NAMESPACE
+} // namespace
+
+#define FVAR(w, h, bits, suffix) \
+ extern "C" unsigned int aom_variance##w##x##h##_##suffix( \
+ const uint8_t *src, int src_stride, const uint8_t *ref, int ref_stride, \
+ unsigned int *sse); \
+ HWY_ATTR unsigned int aom_variance##w##x##h##_##suffix( \
+ const uint8_t *src, int src_stride, const uint8_t *ref, int ref_stride, \
+ unsigned int *sse) { \
+ return HWY_NAMESPACE::VarianceWxH<w, h, bits>(src, src_stride, ref, \
+ ref_stride, sse); \
+ }
+
+#define FMSE16X16(suffix) \
+ extern "C" unsigned int aom_mse16x16_##suffix( \
+ const uint8_t *src, int src_stride, const uint8_t *ref, int ref_stride, \
+ unsigned int *sse); \
+ HWY_ATTR unsigned int aom_mse16x16_##suffix( \
+ const uint8_t *src, int src_stride, const uint8_t *ref, int ref_stride, \
+ unsigned int *sse) { \
+ int sum = 0; \
+ HWY_NAMESPACE::GetBlockVar<16, 16>(src, src_stride, ref, ref_stride, sse, \
+ &sum); \
+ return *sse; \
+ }
+
+#define FGET_BLK_SSE_SUM(suffix) \
+ extern "C" void aom_get_blk_sse_sum_##suffix(const int16_t *data, \
+ int stride, int bw, int bh, \
+ int *x_sum, int64_t *x2_sum); \
+ HWY_ATTR void aom_get_blk_sse_sum_##suffix(const int16_t *data, int stride, \
+ int bw, int bh, int *x_sum, \
+ int64_t *x2_sum) { \
+ HWY_NAMESPACE::GetBlkSseSum(data, stride, bw, bh, x_sum, x2_sum); \
+ }
+
+#if CONFIG_AV1_HIGHBITDEPTH
+#define FHIGHBD_CALC_VAR_STAT(suffix) \
+ extern "C" int64_t aom_highbd_calc_variance_stat_##suffix( \
+ const uint16_t *src, int stride, int bw, int bh); \
+ HWY_ATTR int64_t aom_highbd_calc_variance_stat_##suffix( \
+ const uint16_t *src, int stride, int bw, int bh) { \
+ return HWY_NAMESPACE::HighbdCalcVarianceStat(src, stride, bw, bh); \
+ }
+#else
+#define FHIGHBD_CALC_VAR_STAT(suffix)
+#endif
+
+#define FCALC_VAR_STAT(suffix) \
+ extern "C" int64_t aom_calc_variance_stat_##suffix( \
+ const uint8_t *src, int stride, int bw, int bh); \
+ HWY_ATTR int64_t aom_calc_variance_stat_##suffix( \
+ const uint8_t *src, int stride, int bw, int bh) { \
+ return HWY_NAMESPACE::CalcVarianceStat(src, stride, bw, bh); \
+ } \
+ FHIGHBD_CALC_VAR_STAT(suffix)
+
+#if !CONFIG_REALTIME_ONLY
+#define FOR_EACH_VAR_RECT_BLOCK_SIZE(X, suffix) \
+ X(64, 16, 10, suffix) \
+ X(32, 8, 8, suffix) \
+ X(16, 64, 10, suffix) \
+ X(16, 4, 6, suffix)
+#else
+#define FOR_EACH_VAR_RECT_BLOCK_SIZE(X, suffix)
+#endif
+
+#define FOR_EACH_VAR_BLOCK_SIZE(X, suffix) \
+ X(128, 128, 14, suffix) \
+ X(128, 64, 13, suffix) \
+ X(64, 128, 13, suffix) \
+ X(64, 64, 12, suffix) \
+ X(64, 32, 11, suffix) \
+ X(32, 64, 11, suffix) \
+ X(32, 32, 10, suffix) \
+ X(32, 16, 9, suffix) \
+ X(16, 32, 9, suffix) \
+ X(16, 16, 8, suffix) \
+ X(16, 8, 7, suffix) \
+ FOR_EACH_VAR_RECT_BLOCK_SIZE(X, suffix)
+
+HWY_AFTER_NAMESPACE();
+
+#endif // AOM_AOM_DSP_VARIANCE_HWY_H_
diff --git a/aom_dsp/x86/variance_hwy_avx512.cc b/aom_dsp/x86/variance_hwy_avx512.cc
new file mode 100644
index 0000000000..8532c333bb
--- /dev/null
+++ b/aom_dsp/x86/variance_hwy_avx512.cc
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2026, Alliance for Open Media. All rights reserved.
+ *
+ * This source code is subject to the terms of the BSD 2 Clause License and
+ * the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
+ * was not distributed with this source code in the LICENSE file, you can
+ * obtain it at www.aomedia.org/license/software. If the Alliance for Open
+ * Media Patent License 1.0 was not distributed with this source code in the
+ * PATENTS file, you can obtain it at www.aomedia.org/license/patent.
+ */
+
+#define HWY_BASELINE_TARGETS HWY_AVX3_DL
+#define HWY_BROKEN_32BIT 0
+
+#include "aom_dsp/variance_hwy.h"
+
+FOR_EACH_VAR_BLOCK_SIZE(FVAR, avx512)
+FMSE16X16(avx512)
+FGET_BLK_SSE_SUM(avx512)
+FCALC_VAR_STAT(avx512)
diff --git a/test/sum_squares_test.cc b/test/sum_squares_test.cc
index 309912f3c1..60959a266f 100644
--- a/test/sum_squares_test.cc
+++ b/test/sum_squares_test.cc
@@ -594,7 +594,7 @@ TEST_P(SSE_Sum_Test, ExtremeValues) { RunTest(false, GET_PARAM(1), 1); }
TEST_P(SSE_Sum_Test, DISABLED_Speed) { RunTest(true, GET_PARAM(1), 10000); }
-#if HAVE_SSE2 || HAVE_AVX2 || HAVE_NEON
+#if HAVE_SSE2 || HAVE_AVX2 || (CONFIG_HIGHWAY && HAVE_AVX512) || HAVE_NEON
const TX_SIZE kValidBlockSize[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32,
TX_64X64, TX_4X8, TX_8X4, TX_8X16,
TX_16X8, TX_16X32, TX_32X16, TX_64X32,
@@ -618,6 +618,14 @@ INSTANTIATE_TEST_SUITE_P(AVX2, SSE_Sum_Test,
ValuesIn(kValidBlockSize)));
#endif // HAVE_AVX2
+#if CONFIG_HIGHWAY && HAVE_AVX512
+TestSSE_SumFuncs sse_sum_avx512[] = { TestSSE_SumFuncs(
+ &aom_get_blk_sse_sum_c, &aom_get_blk_sse_sum_avx512) };
+INSTANTIATE_TEST_SUITE_P(AVX512, SSE_Sum_Test,
+ Combine(ValuesIn(sse_sum_avx512),
+ ValuesIn(kValidBlockSize)));
+#endif // CONFIG_HIGHWAY && HAVE_AVX512
+
#if HAVE_NEON
TestSSE_SumFuncs sse_sum_neon[] = { TestSSE_SumFuncs(
&aom_get_blk_sse_sum_c, &aom_get_blk_sse_sum_neon) };
diff --git a/test/variance_test.cc b/test/variance_test.cc
index 781f57446e..abf109491e 100644
--- a/test/variance_test.cc
+++ b/test/variance_test.cc
@@ -3868,7 +3868,7 @@ INSTANTIATE_TEST_SUITE_P(SVE, AvxHBDVarianceTest,
#endif // CONFIG_AV1_HIGHBITDEPTH
#endif // HAVE_SVE
-#if HAVE_AVX2
+#if HAVE_AVX2 || (CONFIG_HIGHWAY && HAVE_AVX512)
using CalcVarianceStatFunc = int64_t (*)(const uint8_t *src, int stride, int bw,
int bh);
@@ -3906,8 +3906,10 @@ TEST_P(CalcVarianceStatTest, CompareWithC) {
}
}
+#if HAVE_AVX2
INSTANTIATE_TEST_SUITE_P(AVX2, CalcVarianceStatTest,
::testing::Values(&aom_calc_variance_stat_avx2));
+#endif // HAVE_AVX2
#if CONFIG_AV1_HIGHBITDEPTH
using CalcVarianceStatHbdFunc = int64_t (*)(const uint16_t *src, int stride,
@@ -3947,10 +3949,49 @@ TEST_P(CalcVarianceStatHbdTest, CompareWithC) {
}
}
+#if HAVE_AVX2
INSTANTIATE_TEST_SUITE_P(
AVX2, CalcVarianceStatHbdTest,
::testing::Values(&aom_highbd_calc_variance_stat_avx2));
-#endif // CONFIG_AV1_HIGHBITDEPTH
#endif // HAVE_AVX2
+#endif // CONFIG_AV1_HIGHBITDEPTH
+#endif // HAVE_AVX2 || (CONFIG_HIGHWAY && HAVE_AVX512)
+
+#if CONFIG_HIGHWAY && HAVE_AVX512
+INSTANTIATE_TEST_SUITE_P(AVX512, AvxMseTest,
+ ::testing::Values(MseParams(4, 4,
+ &aom_mse16x16_avx512)));
+
+const VarianceParams kArrayVariance_avx512[] = {
+ VarianceParams(7, 7, &aom_variance128x128_avx512),
+ VarianceParams(7, 6, &aom_variance128x64_avx512),
+ VarianceParams(6, 7, &aom_variance64x128_avx512),
+ VarianceParams(6, 6, &aom_variance64x64_avx512),
+ VarianceParams(6, 5, &aom_variance64x32_avx512),
+ VarianceParams(5, 6, &aom_variance32x64_avx512),
+ VarianceParams(5, 5, &aom_variance32x32_avx512),
+ VarianceParams(5, 4, &aom_variance32x16_avx512),
+ VarianceParams(4, 5, &aom_variance16x32_avx512),
+ VarianceParams(4, 4, &aom_variance16x16_avx512),
+ VarianceParams(4, 3, &aom_variance16x8_avx512),
+#if !CONFIG_REALTIME_ONLY
+ VarianceParams(6, 4, &aom_variance64x16_avx512),
+ VarianceParams(4, 6, &aom_variance16x64_avx512),
+ VarianceParams(5, 3, &aom_variance32x8_avx512),
+ VarianceParams(4, 2, &aom_variance16x4_avx512),
+#endif
+};
+INSTANTIATE_TEST_SUITE_P(AVX512, AvxVarianceTest,
+ ::testing::ValuesIn(kArrayVariance_avx512));
+
+INSTANTIATE_TEST_SUITE_P(AVX512, CalcVarianceStatTest,
+ ::testing::Values(&aom_calc_variance_stat_avx512));
+
+#if CONFIG_AV1_HIGHBITDEPTH
+INSTANTIATE_TEST_SUITE_P(
+ AVX512, CalcVarianceStatHbdTest,
+ ::testing::Values(&aom_highbd_calc_variance_stat_avx512));
+#endif // CONFIG_AV1_HIGHBITDEPTH
+#endif // CONFIG_HIGHWAY && HAVE_AVX512
} // namespace