From b5d9d69bd6db4efaaae77fd65fd948801860f3f8 Mon Sep 17 00:00:00 2001 From: Joseph Justiss Date: Tue, 15 Sep 2026 12:09:28 -0700 Subject: [PATCH 1/5] Add NEON for fwd_txfm --- av2/av2.cmake | 1 + av2/common/av2_rtcd_defs.pl | 4 +- av2/encoder/arm/neon/highbd_fwd_txfm_neon.c | 757 ++++++++++++++++++++ av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c | 54 ++ av2/encoder/fwd_txfm_internal.h | 67 ++ avm_dsp/arm/fwd_txfm_neon.c | 265 +++++++ avm_dsp/avm_dsp_rtcd_defs.pl | 2 +- test/test.cmake | 3 +- test/txfm_misc_test.cc | 418 +++++++++++ 9 files changed, 1567 insertions(+), 4 deletions(-) create mode 100644 av2/encoder/arm/neon/highbd_fwd_txfm_neon.c create mode 100644 av2/encoder/fwd_txfm_internal.h create mode 100644 test/txfm_misc_test.cc diff --git a/av2/av2.cmake b/av2/av2.cmake index 0f571deec0..fa0ab039a0 100644 --- a/av2/av2.cmake +++ b/av2/av2.cmake @@ -439,6 +439,7 @@ list( "${AVM_ROOT}/av2/encoder/arm/neon/rdopt_neon.c" "${AVM_ROOT}/av2/encoder/arm/neon/encodetxb_neon.c" "${AVM_ROOT}/av2/encoder/arm/neon/fwd_stxfm_neon.c" + "${AVM_ROOT}/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c" "${AVM_ROOT}/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c") list(APPEND AVM_AV2_ENCODER_INTRIN_MSA diff --git a/av2/common/av2_rtcd_defs.pl b/av2/common/av2_rtcd_defs.pl index e518ceb35e..eea69d269b 100644 --- a/av2/common/av2_rtcd_defs.pl +++ b/av2/common/av2_rtcd_defs.pl @@ -247,14 +247,14 @@ () # fwd cctx add_proto qw/void av2_fwd_cross_chroma_tx_block/, "tran_low_t *coeff_c1, tran_low_t *coeff_c2, TX_SIZE tx_size, CctxType cctx_type, const int bd"; - specialize qw/av2_fwd_cross_chroma_tx_block avx2/; + specialize qw/av2_fwd_cross_chroma_tx_block avx2 neon/; #fwd txfm add_proto qw/void fwd_stxfm/ , "tran_low_t *src, tran_low_t *dst, const PREDICTION_MODE mode, const uint8_t stx_idx, const int size, const int bd"; specialize qw/fwd_stxfm sse4_1 avx2 neon/; add_proto qw/void fwd_txfm/, "const int16_t *resi, tran_low_t *coeff, int diff_stride, TxfmParam *txfm_param"; - specialize qw/fwd_txfm avx2/; + specialize qw/fwd_txfm avx2 neon/; # # Motion search diff --git a/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c b/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c new file mode 100644 index 0000000000..faccd880be --- /dev/null +++ b/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c @@ -0,0 +1,757 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved + * + * This source code is subject to the terms of the BSD 3-Clause Clear License + * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear + * License was not distributed with this source code in the LICENSE file, you + * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. 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 + * aomedia.org/license/patent-license/. + */ + +#include +#include +#include + +#include "config/av2_rtcd.h" + +#include "av2/common/av2_txfm.h" +#include "av2/common/common_data.h" +#include "av2/common/enums.h" +#include "av2/common/txb_common.h" +#include "av2/encoder/fwd_txfm_internal.h" +#include "avm_dsp/txfm_common.h" + +static INLINE void transpose_store_4x4_s32(int32x4_t r0, int32x4_t r1, + int32x4_t r2, int32x4_t r3, int *dst, + int dst_stride) { + int32x4x2_t t01 = vtrnq_s32(r0, r1); + int32x4x2_t t23 = vtrnq_s32(r2, r3); + vst1q_s32(dst, + vcombine_s32(vget_low_s32(t01.val[0]), vget_low_s32(t23.val[0]))); + vst1q_s32(dst + dst_stride, + vcombine_s32(vget_low_s32(t01.val[1]), vget_low_s32(t23.val[1]))); + vst1q_s32(dst + 2 * dst_stride, + vcombine_s32(vget_high_s32(t01.val[0]), vget_high_s32(t23.val[0]))); + vst1q_s32(dst + 3 * dst_stride, + vcombine_s32(vget_high_s32(t01.val[1]), vget_high_s32(t23.val[1]))); +} + +static void fwd_txfm_dct2_size4_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int *tx_mat = tx_kernel_dct2_size4[FWD_TXFM][0]; + const int32x4_t v_add = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int j = 0; j < nz_line; j += 4) { + int32x4_t s0 = vld1q_s32(src + 0 * line + j); + int32x4_t s1 = vld1q_s32(src + 1 * line + j); + int32x4_t s2 = vld1q_s32(src + 2 * line + j); + int32x4_t s3 = vld1q_s32(src + 3 * line + j); + + int32x4_t a0 = vaddq_s32(s0, s3); + int32x4_t b0 = vsubq_s32(s0, s3); + int32x4_t a1 = vaddq_s32(s1, s2); + int32x4_t b1 = vsubq_s32(s1, s2); + + int32x4_t r0 = vmlaq_n_s32(v_add, a0, tx_mat[0]); + r0 = vmlaq_n_s32(r0, a1, tx_mat[1]); + r0 = vshlq_s32(r0, v_shift); + + int32x4_t r2 = vmlaq_n_s32(v_add, a0, tx_mat[8]); + r2 = vmlaq_n_s32(r2, a1, tx_mat[9]); + r2 = vshlq_s32(r2, v_shift); + + int32x4_t r1 = vmlaq_n_s32(v_add, b0, tx_mat[4]); + r1 = vmlaq_n_s32(r1, b1, tx_mat[5]); + r1 = vshlq_s32(r1, v_shift); + + int32x4_t r3 = vmlaq_n_s32(v_add, b0, tx_mat[12]); + r3 = vmlaq_n_s32(r3, b1, tx_mat[13]); + r3 = vshlq_s32(r3, v_shift); + + transpose_store_4x4_s32(r0, r1, r2, r3, dst + j * 4, 4); + } + if (skip_line) { + memset(dst + nz_line * 4, 0, sizeof(int) * 4 * skip_line); + } +} + +static void fwd_txfm_dct2_size8_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int *tx_mat = tx_kernel_dct2_size8[FWD_TXFM][0]; + const int32x4_t v_add = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int j = 0; j < nz_line; j += 4) { + int32x4_t s[8]; + for (int k = 0; k < 8; k++) { + s[k] = vld1q_s32(src + k * line + j); + } + int32x4_t a[4], b[4]; + for (int k = 0; k < 4; k++) { + a[k] = vaddq_s32(s[k], s[7 - k]); + b[k] = vsubq_s32(s[k], s[7 - k]); + } + int32x4_t c0 = vaddq_s32(a[0], a[3]); + int32x4_t d0 = vsubq_s32(a[0], a[3]); + int32x4_t c1 = vaddq_s32(a[1], a[2]); + int32x4_t d1 = vsubq_s32(a[1], a[2]); + + int32x4_t out[8]; + out[0] = vshlq_s32( + vmlaq_n_s32(vmlaq_n_s32(v_add, c0, tx_mat[0]), c1, tx_mat[1]), v_shift); + out[4] = vshlq_s32( + vmlaq_n_s32(vmlaq_n_s32(v_add, c0, tx_mat[32]), c1, tx_mat[33]), + v_shift); + out[2] = vshlq_s32( + vmlaq_n_s32(vmlaq_n_s32(v_add, d0, tx_mat[16]), d1, tx_mat[17]), + v_shift); + out[6] = vshlq_s32( + vmlaq_n_s32(vmlaq_n_s32(v_add, d0, tx_mat[48]), d1, tx_mat[49]), + v_shift); + + for (int idx = 1; idx < 8; idx += 2) { + const int *row = tx_mat + idx * 8; + int32x4_t acc = vmlaq_n_s32(v_add, b[0], row[0]); + acc = vmlaq_n_s32(acc, b[1], row[1]); + acc = vmlaq_n_s32(acc, b[2], row[2]); + acc = vmlaq_n_s32(acc, b[3], row[3]); + out[idx] = vshlq_s32(acc, v_shift); + } + + for (int blk = 0; blk < 8; blk += 4) { + transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], + out[blk + 3], dst + j * 8 + blk, 8); + } + } + if (skip_line) { + memset(dst + nz_line * 8, 0, sizeof(int) * 8 * skip_line); + } +} + +static void fwd_txfm_dct2_size16_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int *tx_mat = tx_kernel_dct2_size16[FWD_TXFM][0]; + const int32x4_t v_add = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int j = 0; j < nz_line; j += 4) { + int32x4_t s[16]; + for (int k = 0; k < 16; k++) { + s[k] = vld1q_s32(src + k * line + j); + } + int32x4_t a[8], b[8]; + for (int k = 0; k < 8; k++) { + a[k] = vaddq_s32(s[k], s[15 - k]); + b[k] = vsubq_s32(s[k], s[15 - k]); + } + int32x4_t c[4], d[4]; + for (int k = 0; k < 4; k++) { + c[k] = vaddq_s32(a[k], a[7 - k]); + d[k] = vsubq_s32(a[k], a[7 - k]); + } + int32x4_t e0 = vaddq_s32(c[0], c[3]); + int32x4_t f0 = vsubq_s32(c[0], c[3]); + int32x4_t e1 = vaddq_s32(c[1], c[2]); + int32x4_t f1 = vsubq_s32(c[1], c[2]); + + int32x4_t out[16]; + out[0] = vshlq_s32( + vmlaq_n_s32(vmlaq_n_s32(v_add, e0, tx_mat[0]), e1, tx_mat[1]), v_shift); + out[8] = vshlq_s32(vmlaq_n_s32(vmlaq_n_s32(v_add, e0, tx_mat[8 * 16]), e1, + tx_mat[8 * 16 + 1]), + v_shift); + out[4] = vshlq_s32(vmlaq_n_s32(vmlaq_n_s32(v_add, f0, tx_mat[4 * 16]), f1, + tx_mat[4 * 16 + 1]), + v_shift); + out[12] = vshlq_s32(vmlaq_n_s32(vmlaq_n_s32(v_add, f0, tx_mat[12 * 16]), f1, + tx_mat[12 * 16 + 1]), + v_shift); + + for (int k = 2; k < 16; k += 4) { + const int *row = tx_mat + k * 16; + int32x4_t acc = vmlaq_n_s32(v_add, d[0], row[0]); + acc = vmlaq_n_s32(acc, d[1], row[1]); + acc = vmlaq_n_s32(acc, d[2], row[2]); + acc = vmlaq_n_s32(acc, d[3], row[3]); + out[k] = vshlq_s32(acc, v_shift); + } + + for (int k = 1; k < 16; k += 4) { + const int *row0 = tx_mat + k * 16; + const int *row1 = tx_mat + (k + 2) * 16; + int32x4_t acc0 = vmlaq_n_s32(v_add, b[0], row0[0]); + int32x4_t acc1 = vmlaq_n_s32(v_add, b[0], row1[0]); + acc0 = vmlaq_n_s32(acc0, b[1], row0[1]); + acc1 = vmlaq_n_s32(acc1, b[1], row1[1]); + acc0 = vmlaq_n_s32(acc0, b[2], row0[2]); + acc1 = vmlaq_n_s32(acc1, b[2], row1[2]); + acc0 = vmlaq_n_s32(acc0, b[3], row0[3]); + acc1 = vmlaq_n_s32(acc1, b[3], row1[3]); + acc0 = vmlaq_n_s32(acc0, b[4], row0[4]); + acc1 = vmlaq_n_s32(acc1, b[4], row1[4]); + acc0 = vmlaq_n_s32(acc0, b[5], row0[5]); + acc1 = vmlaq_n_s32(acc1, b[5], row1[5]); + acc0 = vmlaq_n_s32(acc0, b[6], row0[6]); + acc1 = vmlaq_n_s32(acc1, b[6], row1[6]); + acc0 = vmlaq_n_s32(acc0, b[7], row0[7]); + acc1 = vmlaq_n_s32(acc1, b[7], row1[7]); + out[k] = vshlq_s32(acc0, v_shift); + out[k + 2] = vshlq_s32(acc1, v_shift); + } + + // Transpose 4x16 and store: process in 4x4 blocks + for (int blk = 0; blk < 16; blk += 4) { + transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], + out[blk + 3], dst + j * 16 + blk, 16); + } + } + if (skip_line) { + memset(dst + nz_line * 16, 0, sizeof(int) * 16 * skip_line); + } +} + +static void fwd_txfm_dct2_size32_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int *tx_mat = tx_kernel_dct2_size32[FWD_TXFM][0]; + const int32x4_t v_add = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int j = 0; j < nz_line; j += 4) { + int32x4_t s[32]; + for (int k = 0; k < 32; k++) { + s[k] = vld1q_s32(src + k * line + j); + } + int32x4_t a[16], b[16]; + for (int k = 0; k < 16; k++) { + a[k] = vaddq_s32(s[k], s[31 - k]); + b[k] = vsubq_s32(s[k], s[31 - k]); + } + int32x4_t c[8], d[8]; + for (int k = 0; k < 8; k++) { + c[k] = vaddq_s32(a[k], a[15 - k]); + d[k] = vsubq_s32(a[k], a[15 - k]); + } + int32x4_t e[4], f[4]; + for (int k = 0; k < 4; k++) { + e[k] = vaddq_s32(c[k], c[7 - k]); + f[k] = vsubq_s32(c[k], c[7 - k]); + } + int32x4_t g0 = vaddq_s32(e[0], e[3]); + int32x4_t h0 = vsubq_s32(e[0], e[3]); + int32x4_t g1 = vaddq_s32(e[1], e[2]); + int32x4_t h1 = vsubq_s32(e[1], e[2]); + + int32x4_t out[32]; + out[0] = vshlq_s32( + vmlaq_n_s32(vmlaq_n_s32(v_add, g0, tx_mat[0]), g1, tx_mat[1]), v_shift); + out[16] = vshlq_s32(vmlaq_n_s32(vmlaq_n_s32(v_add, g0, tx_mat[16 * 32]), g1, + tx_mat[16 * 32 + 1]), + v_shift); + out[8] = vshlq_s32(vmlaq_n_s32(vmlaq_n_s32(v_add, h0, tx_mat[8 * 32]), h1, + tx_mat[8 * 32 + 1]), + v_shift); + out[24] = vshlq_s32(vmlaq_n_s32(vmlaq_n_s32(v_add, h0, tx_mat[24 * 32]), h1, + tx_mat[24 * 32 + 1]), + v_shift); + + for (int k = 4; k < 32; k += 8) { + const int *row = tx_mat + k * 32; + int32x4_t acc = vmlaq_n_s32(v_add, f[0], row[0]); + acc = vmlaq_n_s32(acc, f[1], row[1]); + acc = vmlaq_n_s32(acc, f[2], row[2]); + acc = vmlaq_n_s32(acc, f[3], row[3]); + out[k] = vshlq_s32(acc, v_shift); + } + + for (int k = 2; k < 32; k += 8) { + const int *row0 = tx_mat + k * 32; + const int *row1 = tx_mat + (k + 4) * 32; + int32x4_t acc0 = vmlaq_n_s32(v_add, d[0], row0[0]); + int32x4_t acc1 = vmlaq_n_s32(v_add, d[0], row1[0]); + for (int m = 1; m < 8; m++) { + acc0 = vmlaq_n_s32(acc0, d[m], row0[m]); + acc1 = vmlaq_n_s32(acc1, d[m], row1[m]); + } + out[k] = vshlq_s32(acc0, v_shift); + out[k + 4] = vshlq_s32(acc1, v_shift); + } + + for (int k = 1; k < 32; k += 4) { + const int *row0 = tx_mat + k * 32; + const int *row1 = tx_mat + (k + 2) * 32; + int32x4_t acc0 = vmlaq_n_s32(v_add, b[0], row0[0]); + int32x4_t acc1 = vmlaq_n_s32(v_add, b[0], row1[0]); + for (int m = 1; m < 16; m++) { + acc0 = vmlaq_n_s32(acc0, b[m], row0[m]); + acc1 = vmlaq_n_s32(acc1, b[m], row1[m]); + } + out[k] = vshlq_s32(acc0, v_shift); + out[k + 2] = vshlq_s32(acc1, v_shift); + } + + for (int blk = 0; blk < 32; blk += 4) { + transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], + out[blk + 3], dst + j * 32 + blk, 32); + } + } + if (skip_line) { + memset(dst + nz_line * 32, 0, sizeof(int) * 32 * skip_line); + } +} + +static void fwd_txfm_matmul_size4_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line, + const int *tx_mat, int reverse) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int32x4_t v_offset = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int i = 0; i < nz_line; i += 4) { + int32x4_t s0 = vld1q_s32(src + (reverse ? 3 : 0) * line + i); + int32x4_t s1 = vld1q_s32(src + (reverse ? 2 : 1) * line + i); + int32x4_t s2 = vld1q_s32(src + (reverse ? 1 : 2) * line + i); + int32x4_t s3 = vld1q_s32(src + (reverse ? 0 : 3) * line + i); + + int32x4_t r0 = vmlaq_n_s32(v_offset, s0, tx_mat[0]); + r0 = vmlaq_n_s32(r0, s1, tx_mat[1]); + r0 = vmlaq_n_s32(r0, s2, tx_mat[2]); + r0 = vmlaq_n_s32(r0, s3, tx_mat[3]); + r0 = vshlq_s32(r0, v_shift); + + int32x4_t r1 = vmlaq_n_s32(v_offset, s0, tx_mat[4]); + r1 = vmlaq_n_s32(r1, s1, tx_mat[5]); + r1 = vmlaq_n_s32(r1, s2, tx_mat[6]); + r1 = vmlaq_n_s32(r1, s3, tx_mat[7]); + r1 = vshlq_s32(r1, v_shift); + + int32x4_t r2 = vmlaq_n_s32(v_offset, s0, tx_mat[8]); + r2 = vmlaq_n_s32(r2, s1, tx_mat[9]); + r2 = vmlaq_n_s32(r2, s2, tx_mat[10]); + r2 = vmlaq_n_s32(r2, s3, tx_mat[11]); + r2 = vshlq_s32(r2, v_shift); + + int32x4_t r3 = vmlaq_n_s32(v_offset, s0, tx_mat[12]); + r3 = vmlaq_n_s32(r3, s1, tx_mat[13]); + r3 = vmlaq_n_s32(r3, s2, tx_mat[14]); + r3 = vmlaq_n_s32(r3, s3, tx_mat[15]); + r3 = vshlq_s32(r3, v_shift); + + transpose_store_4x4_s32(r0, r1, r2, r3, dst + i * 4, 4); + } + + if (skip_line) { + memset(dst + nz_line * 4, 0, sizeof(int) * 4 * skip_line); + } +} + +static void fwd_txfm_matmul_size8_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line, + const int *tx_mat, int reverse) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int32x4_t v_offset = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int i = 0; i < nz_line; i += 4) { + int32x4_t s[8]; + for (int k = 0; k < 8; k++) { + s[k] = vld1q_s32(src + (reverse ? 7 - k : k) * line + i); + } + + int32x4_t out[8]; + for (int j = 0; j < 8; j += 2) { + const int *row0 = tx_mat + j * 8; + const int *row1 = tx_mat + (j + 1) * 8; + int32x4_t acc0 = vmlaq_n_s32(v_offset, s[0], row0[0]); + int32x4_t acc1 = vmlaq_n_s32(v_offset, s[0], row1[0]); + acc0 = vmlaq_n_s32(acc0, s[1], row0[1]); + acc1 = vmlaq_n_s32(acc1, s[1], row1[1]); + acc0 = vmlaq_n_s32(acc0, s[2], row0[2]); + acc1 = vmlaq_n_s32(acc1, s[2], row1[2]); + acc0 = vmlaq_n_s32(acc0, s[3], row0[3]); + acc1 = vmlaq_n_s32(acc1, s[3], row1[3]); + acc0 = vmlaq_n_s32(acc0, s[4], row0[4]); + acc1 = vmlaq_n_s32(acc1, s[4], row1[4]); + acc0 = vmlaq_n_s32(acc0, s[5], row0[5]); + acc1 = vmlaq_n_s32(acc1, s[5], row1[5]); + acc0 = vmlaq_n_s32(acc0, s[6], row0[6]); + acc1 = vmlaq_n_s32(acc1, s[6], row1[6]); + acc0 = vmlaq_n_s32(acc0, s[7], row0[7]); + acc1 = vmlaq_n_s32(acc1, s[7], row1[7]); + out[j] = vshlq_s32(acc0, v_shift); + out[j + 1] = vshlq_s32(acc1, v_shift); + } + + for (int blk = 0; blk < 8; blk += 4) { + transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], + out[blk + 3], dst + i * 8 + blk, 8); + } + } + + if (skip_line) { + memset(dst + nz_line * 8, 0, sizeof(int) * 8 * skip_line); + } +} + +static void fwd_txfm_matmul_size16_neon(const int *src, int *dst, int shift, + int line, int skip_line, int zero_line, + const int *tx_mat, int reverse) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int32x4_t v_offset = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int i = 0; i < nz_line; i += 4) { + int32x4_t s[16]; + for (int k = 0; k < 16; k++) { + s[k] = vld1q_s32(src + (reverse ? 15 - k : k) * line + i); + } + + int32x4_t out[16]; + for (int j = 0; j < 16; j += 2) { + const int *row0 = tx_mat + j * 16; + const int *row1 = tx_mat + (j + 1) * 16; + int32x4_t acc0 = vmlaq_n_s32(v_offset, s[0], row0[0]); + int32x4_t acc1 = vmlaq_n_s32(v_offset, s[0], row1[0]); + for (int k = 1; k < 16; k++) { + acc0 = vmlaq_n_s32(acc0, s[k], row0[k]); + acc1 = vmlaq_n_s32(acc1, s[k], row1[k]); + } + out[j] = vshlq_s32(acc0, v_shift); + out[j + 1] = vshlq_s32(acc1, v_shift); + } + + for (int blk = 0; blk < 16; blk += 4) { + transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], + out[blk + 3], dst + i * 16 + blk, 16); + } + } + + if (skip_line) { + memset(dst + nz_line * 16, 0, sizeof(int) * 16 * skip_line); + } +} + +static void fwd_txfm_idtx_neon(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line, int tx1d_size, + int scale) { + (void)zero_line; + const int nz_line = line - skip_line; + assert((nz_line & 3) == 0); + const int32x4_t v_scale = vdupq_n_s32(scale); + const int32x4_t v_offset = + shift > 0 ? vdupq_n_s32(1 << (shift - 1)) : vdupq_n_s32(0); + const int32x4_t v_shift = vdupq_n_s32(-shift); + + for (int i = 0; i < nz_line; i += 4) { + for (int j = 0; j < tx1d_size; j += 4) { + int32x4_t r0 = vshlq_s32( + vmlaq_s32(v_offset, vld1q_s32(src + (j + 0) * line + i), v_scale), + v_shift); + int32x4_t r1 = vshlq_s32( + vmlaq_s32(v_offset, vld1q_s32(src + (j + 1) * line + i), v_scale), + v_shift); + int32x4_t r2 = vshlq_s32( + vmlaq_s32(v_offset, vld1q_s32(src + (j + 2) * line + i), v_scale), + v_shift); + int32x4_t r3 = vshlq_s32( + vmlaq_s32(v_offset, vld1q_s32(src + (j + 3) * line + i), v_scale), + v_shift); + transpose_store_4x4_s32(r0, r1, r2, r3, dst + i * tx1d_size + j, + tx1d_size); + } + } + + if (skip_line) { + memset(dst + nz_line * tx1d_size, 0, sizeof(int) * tx1d_size * skip_line); + } +} + +static void fwd_transform_1d_neon(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line, + const int tx_type_index, + const int size_index) { + switch (size_index) { + case 0: + switch (tx_type_index) { + case 0: + fwd_txfm_dct2_size4_neon(src, dst, shift, line, skip_line, zero_line); + break; + case 1: + fwd_txfm_idtx_neon(src, dst, shift, line, skip_line, zero_line, 4, + 128); + break; + case 2: + fwd_txfm_matmul_size4_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_adst_size4[FWD_TXFM][0], 0); + break; + case 3: + fwd_txfm_matmul_size4_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_fdst_size4[FWD_TXFM][0], 0); + break; + case 4: + fwd_txfm_matmul_size4_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_ddtx_size4[FWD_TXFM][0], 0); + break; + case 5: + fwd_txfm_matmul_size4_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_ddtx_size4[FWD_TXFM][0], 1); + break; + default: + assert(0); + __builtin_unreachable(); + break; + } + break; + case 1: + switch (tx_type_index) { + case 0: + fwd_txfm_dct2_size8_neon(src, dst, shift, line, skip_line, zero_line); + break; + case 1: + fwd_txfm_idtx_neon(src, dst, shift, line, skip_line, zero_line, 8, + 181); + break; + case 2: + fwd_txfm_matmul_size8_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_adst_size8[FWD_TXFM][0], 0); + break; + case 3: + fwd_txfm_matmul_size8_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_fdst_size8[FWD_TXFM][0], 0); + break; + case 4: + fwd_txfm_matmul_size8_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_ddtx_size8[FWD_TXFM][0], 0); + break; + case 5: + fwd_txfm_matmul_size8_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_ddtx_size8[FWD_TXFM][0], 1); + break; + default: + assert(0); + __builtin_unreachable(); + break; + } + break; + case 2: + switch (tx_type_index) { + case 0: + fwd_txfm_dct2_size16_neon(src, dst, shift, line, skip_line, + zero_line); + break; + case 1: + fwd_txfm_idtx_neon(src, dst, shift, line, skip_line, zero_line, 16, + 256); + break; + case 2: + fwd_txfm_matmul_size16_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_adst_size16[FWD_TXFM][0], 0); + break; + case 3: + fwd_txfm_matmul_size16_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_fdst_size16[FWD_TXFM][0], 0); + break; + case 4: + fwd_txfm_matmul_size16_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_ddtx_size16[FWD_TXFM][0], 0); + break; + case 5: + fwd_txfm_matmul_size16_neon(src, dst, shift, line, skip_line, + zero_line, + tx_kernel_ddtx_size16[FWD_TXFM][0], 1); + break; + default: + assert(0); + __builtin_unreachable(); + break; + } + break; + case 3: + switch (tx_type_index) { + case 0: + fwd_txfm_dct2_size32_neon(src, dst, shift, line, skip_line, + zero_line); + break; + case 1: + fwd_txfm_idtx_neon(src, dst, shift, line, skip_line, zero_line, 32, + 362); + break; + default: + assert(0); + __builtin_unreachable(); + break; + } + break; + case 4: + switch (tx_type_index) { + case 0: + fwd_txfm_dct2_size64_c(src, dst, shift, line, skip_line, zero_line); + break; + default: + assert(0); + __builtin_unreachable(); + break; + } + break; + default: + assert(0); + __builtin_unreachable(); + break; + } +} + +void fwd_txfm_neon(const int16_t *resi, tran_low_t *coeff, int diff_stride, + TxfmParam *txfm_param) { + const TX_SIZE tx_size = txfm_param->tx_size; + + const int width = tx_size_wide[tx_size]; + const int height = tx_size_high[tx_size]; + assert(width >= 4 && height >= 4); + + const uint32_t tx_wide_index = tx_size_wide_log2[tx_size] - 2; + const uint32_t tx_high_index = tx_size_high_log2[tx_size] - 2; + + TX_TYPE tx_type = txfm_param->tx_type; + + if (txfm_param->lossless) { + assert(tx_type == DCT_DCT); + av2_highbd_fwht4x4(resi, coeff, diff_stride); + return; + } + + int tx_type_row = g_hor_tx_type[tx_type]; + int tx_type_col = g_ver_tx_type[tx_type]; + + if (txfm_param->use_ddt) { + const int use_ddt_row = (width == 4 && REPLACE_ADST4) || + (width == 8 && REPLACE_ADST8) || + (width == 16 && REPLACE_ADST16); + if (use_ddt_row && (tx_type_row == DST7 || tx_type_row == DCT8)) { + tx_type_row = (tx_type_row == DST7) ? DDTX : FDDT; + } + const int use_ddt_col = (height == 4 && REPLACE_ADST4) || + (height == 8 && REPLACE_ADST8) || + (height == 16 && REPLACE_ADST16); + if (use_ddt_col && (tx_type_col == DST7 || tx_type_col == DCT8)) { + tx_type_col = (tx_type_col == DST7) ? DDTX : FDDT; + } + } + + int skip_width = width > 32 ? width - 32 : 0; + int skip_height = height > 32 ? height - 32 : 0; + + int buf[MAX_TX_SQUARE]; + + // Copy residuals (int16) to coeff buffer (int32) using NEON + if (diff_stride == width) { + const int total = width * height; + assert((total & 7) == 0); + int i = 0; + for (; i + 8 <= total; i += 8) { + int16x8_t r = vld1q_s16(resi + i); + vst1q_s32(coeff + i, vmovl_s16(vget_low_s16(r))); + vst1q_s32(coeff + i + 4, vmovl_s16(vget_high_s16(r))); + } + } else { + for (int y = 0; y < height; y++) { + int x = 0; + for (; x + 8 <= width; x += 8) { + int16x8_t r = vld1q_s16(resi + y * diff_stride + x); + vst1q_s32(coeff + y * width + x, vmovl_s16(vget_low_s16(r))); + vst1q_s32(coeff + y * width + x + 4, vmovl_s16(vget_high_s16(r))); + } + for (; x + 4 <= width; x += 4) { + vst1q_s32(coeff + y * width + x, + vmovl_s16(vld1_s16(resi + y * diff_stride + x))); + } + } + } + + const int shift_1st = fwd_tx_shift[tx_size][0]; + const int shift_2nd = fwd_tx_shift[tx_size][1]; + + fwd_transform_1d_neon(coeff, buf, shift_1st, width, 0, skip_height, + tx_type_col, tx_high_index); + fwd_transform_1d_neon(buf, coeff, shift_2nd, height, skip_height, skip_width, + tx_type_row, tx_wide_index); + + // Re-pack non-zero coeffs in the first 32x32 indices. + if (skip_width) { + for (int row = 1; row < height; ++row) { + int32x4_t d0 = vld1q_s32(coeff + row * width); + int32x4_t d1 = vld1q_s32(coeff + row * width + 4); + int32x4_t d2 = vld1q_s32(coeff + row * width + 8); + int32x4_t d3 = vld1q_s32(coeff + row * width + 12); + int32x4_t d4 = vld1q_s32(coeff + row * width + 16); + int32x4_t d5 = vld1q_s32(coeff + row * width + 20); + int32x4_t d6 = vld1q_s32(coeff + row * width + 24); + int32x4_t d7 = vld1q_s32(coeff + row * width + 28); + vst1q_s32(coeff + row * 32, d0); + vst1q_s32(coeff + row * 32 + 4, d1); + vst1q_s32(coeff + row * 32 + 8, d2); + vst1q_s32(coeff + row * 32 + 12, d3); + vst1q_s32(coeff + row * 32 + 16, d4); + vst1q_s32(coeff + row * 32 + 20, d5); + vst1q_s32(coeff + row * 32 + 24, d6); + vst1q_s32(coeff + row * 32 + 28, d7); + } + } + + const int log2width = tx_size_wide_log2[tx_size]; + const int log2height = tx_size_high_log2[tx_size]; + const int sqrt2 = ((log2width + log2height) & 1) ? 1 : 0; + if (sqrt2) { + const int count = AVMMIN(1024, width * height); + int i = 0; + for (; i + 4 <= count; i += 4) { + int32x4_t v = vld1q_s32(coeff + i); + int64x2_t lo = vmull_s32(vget_low_s32(v), vdup_n_s32(NewSqrt2)); + int64x2_t hi = vmull_s32(vget_high_s32(v), vdup_n_s32(NewSqrt2)); + int32x2_t r_lo = vrshrn_n_s64(lo, NewSqrt2Bits); + int32x2_t r_hi = vrshrn_n_s64(hi, NewSqrt2Bits); + vst1q_s32(coeff + i, vcombine_s32(r_lo, r_hi)); + } + for (; i < count; i++) { + coeff[i] = + (int32_t)round_shift((int64_t)coeff[i] * NewSqrt2, NewSqrt2Bits); + } + } +} diff --git a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c index 4bcc750fb2..f31d643919 100644 --- a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c +++ b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c @@ -12,6 +12,10 @@ #include +#include "config/av2_rtcd.h" + +#include "av2/common/av2_txfm.h" +#include "av2/common/enums.h" #include "avm_dsp/txfm_common.h" static void transpose4x4(int16x8_t in[2], int16x4_t out[4]) { @@ -82,3 +86,53 @@ void av2_highbd_fwht4x4_neon(const int16_t *input, tran_low_t *output, int stride) { av2_fwht4x4_neon(input, output, stride); } + +static INLINE int32x4_t round_power_of_two_signed_neon(int32x4_t v, int bits) { + const int32x4_t bias = vdupq_n_s32((1 << bits) >> 1); + const int32x4_t sign = vshrq_n_s32(v, 31); + return vshlq_s32(vaddq_s32(vaddq_s32(v, bias), sign), vdupq_n_s32(-bits)); +} + +void av2_fwd_cross_chroma_tx_block_neon(tran_low_t *coeff_c1, + tran_low_t *coeff_c2, TX_SIZE tx_size, + CctxType cctx_type, const int bd) { + if (cctx_type == CCTX_NONE) return; + assert(bd <= 14); + const int ncoeffs = av2_get_max_eob(tx_size); + int32_t *src_c1 = (int32_t *)coeff_c1; + int32_t *src_c2 = (int32_t *)coeff_c2; + + const int angle_idx = cctx_type - CCTX_START; + const int32x4_t cos_t = vdupq_n_s32(cctx_mtx[angle_idx][0]); + const int32x4_t sin_t = vdupq_n_s32(cctx_mtx[angle_idx][1]); + const int32x4_t max_val = vdupq_n_s32((1 << (7 + bd)) - 1); + const int32x4_t min_val = vdupq_n_s32(-(1 << (7 + bd))); + + int i = 0; + for (; i + 4 <= ncoeffs; i += 4) { + const int32x4_t c1 = vld1q_s32(&src_c1[i]); + const int32x4_t c2 = vld1q_s32(&src_c2[i]); + + const int32x4_t t0 = vaddq_s32(vmulq_s32(cos_t, c1), vmulq_s32(sin_t, c2)); + const int32x4_t t1 = vsubq_s32(vmulq_s32(cos_t, c2), vmulq_s32(sin_t, c1)); + + int32x4_t r0 = round_power_of_two_signed_neon(t0, CCTX_PREC_BITS); + int32x4_t r1 = round_power_of_two_signed_neon(t1, CCTX_PREC_BITS); + + r0 = vminq_s32(vmaxq_s32(r0, min_val), max_val); + r1 = vminq_s32(vmaxq_s32(r1, min_val), max_val); + + vst1q_s32(&src_c1[i], r0); + vst1q_s32(&src_c2[i], r1); + } + for (; i < ncoeffs; i++) { + int64_t tmp0 = (int64_t)cctx_mtx[angle_idx][0] * (int64_t)src_c1[i] + + (int64_t)cctx_mtx[angle_idx][1] * (int64_t)src_c2[i]; + int64_t tmp1 = (int64_t)-cctx_mtx[angle_idx][1] * (int64_t)src_c1[i] + + (int64_t)cctx_mtx[angle_idx][0] * (int64_t)src_c2[i]; + src_c1[i] = (int32_t)ROUND_POWER_OF_TWO_SIGNED_64(tmp0, CCTX_PREC_BITS); + src_c2[i] = (int32_t)ROUND_POWER_OF_TWO_SIGNED_64(tmp1, CCTX_PREC_BITS); + src_c1[i] = clamp_value(src_c1[i], 8 + bd); + src_c2[i] = clamp_value(src_c2[i], 8 + bd); + } +} diff --git a/av2/encoder/fwd_txfm_internal.h b/av2/encoder/fwd_txfm_internal.h new file mode 100644 index 0000000000..a089aa2363 --- /dev/null +++ b/av2/encoder/fwd_txfm_internal.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved + * + * This source code is subject to the terms of the BSD 3-Clause Clear License + * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear + * License was not distributed with this source code in the LICENSE file, you + * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. 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 + * aomedia.org/license/patent-license/. + */ + +#ifndef AVM_AV2_ENCODER_FWD_TXFM_INTERNAL_H_ +#define AVM_AV2_ENCODER_FWD_TXFM_INTERNAL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +void fwd_txfm_dct2_size4_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_dct2_size8_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_dct2_size16_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_dct2_size32_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_dct2_size64_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_idtx_size4_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_idtx_size8_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_idtx_size16_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_idtx_size32_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_adst_size4_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_adst_size8_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_adst_size16_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_fdst_size4_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_fdst_size8_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_fdst_size16_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_ddtx_size4_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_ddtx_size8_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_ddtx_size16_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_fddt_size4_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_fddt_size8_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); +void fwd_txfm_fddt_size16_c(const int *src, int *dst, int shift, int line, + int skip_line, int zero_line); + +#ifdef __cplusplus +} +#endif + +#endif // AVM_AV2_ENCODER_FWD_TXFM_INTERNAL_H_ diff --git a/avm_dsp/arm/fwd_txfm_neon.c b/avm_dsp/arm/fwd_txfm_neon.c index b5b03d15ba..fcfb7d7430 100644 --- a/avm_dsp/arm/fwd_txfm_neon.c +++ b/avm_dsp/arm/fwd_txfm_neon.c @@ -112,3 +112,268 @@ void avm_fdct4x4_lp_neon(const int16_t *input, int16_t *final_output, vst1q_s16(final_output + 0 * 8, out_01); vst1q_s16(final_output + 1 * 8, out_23); } + +// Perform one pass of 8-point DCT (Loeffler butterfly) on 8 columns +// stored as int32x4_t pairs (lo/hi for 8 elements). +// Input: 8 rows of 8 int32 values. Output: 8 DCT coefficients per column. +static void fdct8_pass_neon(const int32x4_t *in_lo, const int32x4_t *in_hi, + int32x4_t *out_lo, int32x4_t *out_hi) { + // Stage 1: butterfly + int32x4_t s0_lo = vaddq_s32(in_lo[0], in_lo[7]); + int32x4_t s0_hi = vaddq_s32(in_hi[0], in_hi[7]); + int32x4_t s1_lo = vaddq_s32(in_lo[1], in_lo[6]); + int32x4_t s1_hi = vaddq_s32(in_hi[1], in_hi[6]); + int32x4_t s2_lo = vaddq_s32(in_lo[2], in_lo[5]); + int32x4_t s2_hi = vaddq_s32(in_hi[2], in_hi[5]); + int32x4_t s3_lo = vaddq_s32(in_lo[3], in_lo[4]); + int32x4_t s3_hi = vaddq_s32(in_hi[3], in_hi[4]); + int32x4_t s4_lo = vsubq_s32(in_lo[3], in_lo[4]); + int32x4_t s4_hi = vsubq_s32(in_hi[3], in_hi[4]); + int32x4_t s5_lo = vsubq_s32(in_lo[2], in_lo[5]); + int32x4_t s5_hi = vsubq_s32(in_hi[2], in_hi[5]); + int32x4_t s6_lo = vsubq_s32(in_lo[1], in_lo[6]); + int32x4_t s6_hi = vsubq_s32(in_hi[1], in_hi[6]); + int32x4_t s7_lo = vsubq_s32(in_lo[0], in_lo[7]); + int32x4_t s7_hi = vsubq_s32(in_hi[0], in_hi[7]); + + // Even part: fdct4(s0..s3) + int32x4_t x0_lo = vaddq_s32(s0_lo, s3_lo); + int32x4_t x0_hi = vaddq_s32(s0_hi, s3_hi); + int32x4_t x1_lo = vaddq_s32(s1_lo, s2_lo); + int32x4_t x1_hi = vaddq_s32(s1_hi, s2_hi); + int32x4_t x2_lo = vsubq_s32(s1_lo, s2_lo); + int32x4_t x2_hi = vsubq_s32(s1_hi, s2_hi); + int32x4_t x3_lo = vsubq_s32(s0_lo, s3_lo); + int32x4_t x3_hi = vsubq_s32(s0_hi, s3_hi); + + // out[0] = (x0+x1)*cospi16 >> DCT_CONST_BITS + const int32_t c16 = (int32_t)cospi_16_64; + int64x2_t t0_0 = vmull_n_s32(vget_low_s32(vaddq_s32(x0_lo, x1_lo)), c16); + int64x2_t t0_1 = vmull_n_s32(vget_high_s32(vaddq_s32(x0_lo, x1_lo)), c16); + int64x2_t t0_2 = vmull_n_s32(vget_low_s32(vaddq_s32(x0_hi, x1_hi)), c16); + int64x2_t t0_3 = vmull_n_s32(vget_high_s32(vaddq_s32(x0_hi, x1_hi)), c16); + out_lo[0] = vcombine_s32(vrshrn_n_s64(t0_0, DCT_CONST_BITS), + vrshrn_n_s64(t0_1, DCT_CONST_BITS)); + out_hi[0] = vcombine_s32(vrshrn_n_s64(t0_2, DCT_CONST_BITS), + vrshrn_n_s64(t0_3, DCT_CONST_BITS)); + + // out[4] = (x0-x1)*cospi16 >> DCT_CONST_BITS + int64x2_t t4_0 = vmull_n_s32(vget_low_s32(vsubq_s32(x0_lo, x1_lo)), c16); + int64x2_t t4_1 = vmull_n_s32(vget_high_s32(vsubq_s32(x0_lo, x1_lo)), c16); + int64x2_t t4_2 = vmull_n_s32(vget_low_s32(vsubq_s32(x0_hi, x1_hi)), c16); + int64x2_t t4_3 = vmull_n_s32(vget_high_s32(vsubq_s32(x0_hi, x1_hi)), c16); + out_lo[4] = vcombine_s32(vrshrn_n_s64(t4_0, DCT_CONST_BITS), + vrshrn_n_s64(t4_1, DCT_CONST_BITS)); + out_hi[4] = vcombine_s32(vrshrn_n_s64(t4_2, DCT_CONST_BITS), + vrshrn_n_s64(t4_3, DCT_CONST_BITS)); + + // out[2] = x2*cospi24 + x3*cospi8 >> DCT_CONST_BITS + const int32_t c24 = (int32_t)cospi_24_64; + const int32_t c8 = (int32_t)cospi_8_64; + int64x2_t t2_0 = vmlal_n_s32(vmull_n_s32(vget_low_s32(x2_lo), c24), + vget_low_s32(x3_lo), c8); + int64x2_t t2_1 = vmlal_n_s32(vmull_n_s32(vget_high_s32(x2_lo), c24), + vget_high_s32(x3_lo), c8); + int64x2_t t2_2 = vmlal_n_s32(vmull_n_s32(vget_low_s32(x2_hi), c24), + vget_low_s32(x3_hi), c8); + int64x2_t t2_3 = vmlal_n_s32(vmull_n_s32(vget_high_s32(x2_hi), c24), + vget_high_s32(x3_hi), c8); + out_lo[2] = vcombine_s32(vrshrn_n_s64(t2_0, DCT_CONST_BITS), + vrshrn_n_s64(t2_1, DCT_CONST_BITS)); + out_hi[2] = vcombine_s32(vrshrn_n_s64(t2_2, DCT_CONST_BITS), + vrshrn_n_s64(t2_3, DCT_CONST_BITS)); + + // out[6] = -x2*cospi8 + x3*cospi24 >> DCT_CONST_BITS + int64x2_t t6_0 = vmlsl_n_s32(vmull_n_s32(vget_low_s32(x3_lo), c24), + vget_low_s32(x2_lo), c8); + int64x2_t t6_1 = vmlsl_n_s32(vmull_n_s32(vget_high_s32(x3_lo), c24), + vget_high_s32(x2_lo), c8); + int64x2_t t6_2 = vmlsl_n_s32(vmull_n_s32(vget_low_s32(x3_hi), c24), + vget_low_s32(x2_hi), c8); + int64x2_t t6_3 = vmlsl_n_s32(vmull_n_s32(vget_high_s32(x3_hi), c24), + vget_high_s32(x2_hi), c8); + out_lo[6] = vcombine_s32(vrshrn_n_s64(t6_0, DCT_CONST_BITS), + vrshrn_n_s64(t6_1, DCT_CONST_BITS)); + out_hi[6] = vcombine_s32(vrshrn_n_s64(t6_2, DCT_CONST_BITS), + vrshrn_n_s64(t6_3, DCT_CONST_BITS)); + + // Odd part: Stage 2 -- t2 = (s6-s5)*cospi16, t3 = (s6+s5)*cospi16 + int64x2_t u0_0 = vmull_n_s32(vget_low_s32(vsubq_s32(s6_lo, s5_lo)), c16); + int64x2_t u0_1 = vmull_n_s32(vget_high_s32(vsubq_s32(s6_lo, s5_lo)), c16); + int64x2_t u0_2 = vmull_n_s32(vget_low_s32(vsubq_s32(s6_hi, s5_hi)), c16); + int64x2_t u0_3 = vmull_n_s32(vget_high_s32(vsubq_s32(s6_hi, s5_hi)), c16); + int32x4_t t2r_lo = vcombine_s32(vrshrn_n_s64(u0_0, DCT_CONST_BITS), + vrshrn_n_s64(u0_1, DCT_CONST_BITS)); + int32x4_t t2r_hi = vcombine_s32(vrshrn_n_s64(u0_2, DCT_CONST_BITS), + vrshrn_n_s64(u0_3, DCT_CONST_BITS)); + + int64x2_t u1_0 = vmull_n_s32(vget_low_s32(vaddq_s32(s6_lo, s5_lo)), c16); + int64x2_t u1_1 = vmull_n_s32(vget_high_s32(vaddq_s32(s6_lo, s5_lo)), c16); + int64x2_t u1_2 = vmull_n_s32(vget_low_s32(vaddq_s32(s6_hi, s5_hi)), c16); + int64x2_t u1_3 = vmull_n_s32(vget_high_s32(vaddq_s32(s6_hi, s5_hi)), c16); + int32x4_t t3r_lo = vcombine_s32(vrshrn_n_s64(u1_0, DCT_CONST_BITS), + vrshrn_n_s64(u1_1, DCT_CONST_BITS)); + int32x4_t t3r_hi = vcombine_s32(vrshrn_n_s64(u1_2, DCT_CONST_BITS), + vrshrn_n_s64(u1_3, DCT_CONST_BITS)); + + // Stage 3 + int32x4_t y0_lo = vaddq_s32(s4_lo, t2r_lo); + int32x4_t y0_hi = vaddq_s32(s4_hi, t2r_hi); + int32x4_t y1_lo = vsubq_s32(s4_lo, t2r_lo); + int32x4_t y1_hi = vsubq_s32(s4_hi, t2r_hi); + int32x4_t y2_lo = vsubq_s32(s7_lo, t3r_lo); + int32x4_t y2_hi = vsubq_s32(s7_hi, t3r_hi); + int32x4_t y3_lo = vaddq_s32(s7_lo, t3r_lo); + int32x4_t y3_hi = vaddq_s32(s7_hi, t3r_hi); + + // Stage 4 + const int32_t c28 = (int32_t)cospi_28_64; + const int32_t c4 = (int32_t)cospi_4_64; + const int32_t c12 = (int32_t)cospi_12_64; + const int32_t c20 = (int32_t)cospi_20_64; + + // out[1] = y0*cospi28 + y3*cospi4 + int64x2_t w0 = vmlal_n_s32(vmull_n_s32(vget_low_s32(y0_lo), c28), + vget_low_s32(y3_lo), c4); + int64x2_t w1 = vmlal_n_s32(vmull_n_s32(vget_high_s32(y0_lo), c28), + vget_high_s32(y3_lo), c4); + int64x2_t w2 = vmlal_n_s32(vmull_n_s32(vget_low_s32(y0_hi), c28), + vget_low_s32(y3_hi), c4); + int64x2_t w3 = vmlal_n_s32(vmull_n_s32(vget_high_s32(y0_hi), c28), + vget_high_s32(y3_hi), c4); + out_lo[1] = vcombine_s32(vrshrn_n_s64(w0, DCT_CONST_BITS), + vrshrn_n_s64(w1, DCT_CONST_BITS)); + out_hi[1] = vcombine_s32(vrshrn_n_s64(w2, DCT_CONST_BITS), + vrshrn_n_s64(w3, DCT_CONST_BITS)); + + // out[3] = y2*cospi12 - y1*cospi20 + w0 = vmlsl_n_s32(vmull_n_s32(vget_low_s32(y2_lo), c12), vget_low_s32(y1_lo), + c20); + w1 = vmlsl_n_s32(vmull_n_s32(vget_high_s32(y2_lo), c12), vget_high_s32(y1_lo), + c20); + w2 = vmlsl_n_s32(vmull_n_s32(vget_low_s32(y2_hi), c12), vget_low_s32(y1_hi), + c20); + w3 = vmlsl_n_s32(vmull_n_s32(vget_high_s32(y2_hi), c12), vget_high_s32(y1_hi), + c20); + out_lo[3] = vcombine_s32(vrshrn_n_s64(w0, DCT_CONST_BITS), + vrshrn_n_s64(w1, DCT_CONST_BITS)); + out_hi[3] = vcombine_s32(vrshrn_n_s64(w2, DCT_CONST_BITS), + vrshrn_n_s64(w3, DCT_CONST_BITS)); + + // out[5] = y1*cospi12 + y2*cospi20 + w0 = vmlal_n_s32(vmull_n_s32(vget_low_s32(y1_lo), c12), vget_low_s32(y2_lo), + c20); + w1 = vmlal_n_s32(vmull_n_s32(vget_high_s32(y1_lo), c12), vget_high_s32(y2_lo), + c20); + w2 = vmlal_n_s32(vmull_n_s32(vget_low_s32(y1_hi), c12), vget_low_s32(y2_hi), + c20); + w3 = vmlal_n_s32(vmull_n_s32(vget_high_s32(y1_hi), c12), vget_high_s32(y2_hi), + c20); + out_lo[5] = vcombine_s32(vrshrn_n_s64(w0, DCT_CONST_BITS), + vrshrn_n_s64(w1, DCT_CONST_BITS)); + out_hi[5] = vcombine_s32(vrshrn_n_s64(w2, DCT_CONST_BITS), + vrshrn_n_s64(w3, DCT_CONST_BITS)); + + // out[7] = y3*cospi28 - y0*cospi4 + w0 = vmlsl_n_s32(vmull_n_s32(vget_low_s32(y3_lo), c28), vget_low_s32(y0_lo), + c4); + w1 = vmlsl_n_s32(vmull_n_s32(vget_high_s32(y3_lo), c28), vget_high_s32(y0_lo), + c4); + w2 = vmlsl_n_s32(vmull_n_s32(vget_low_s32(y3_hi), c28), vget_low_s32(y0_hi), + c4); + w3 = vmlsl_n_s32(vmull_n_s32(vget_high_s32(y3_hi), c28), vget_high_s32(y0_hi), + c4); + out_lo[7] = vcombine_s32(vrshrn_n_s64(w0, DCT_CONST_BITS), + vrshrn_n_s64(w1, DCT_CONST_BITS)); + out_hi[7] = vcombine_s32(vrshrn_n_s64(w2, DCT_CONST_BITS), + vrshrn_n_s64(w3, DCT_CONST_BITS)); +} + +// Transpose a 4x8 block of int32x4_t values. +// Input: in[0..7], each 4-wide. +// Output: out[0..3] = cols 0-3 from rows 0-3, +// out[4..7] = cols 0-3 from rows 4-7. +static void transpose_4x8_s32(const int32x4_t *in, int32x4_t *out) { + int32x4x2_t r01 = vtrnq_s32(in[0], in[1]); + int32x4x2_t r23 = vtrnq_s32(in[2], in[3]); + int32x4x2_t r45 = vtrnq_s32(in[4], in[5]); + int32x4x2_t r67 = vtrnq_s32(in[6], in[7]); + out[0] = vcombine_s32(vget_low_s32(r01.val[0]), vget_low_s32(r23.val[0])); + out[1] = vcombine_s32(vget_low_s32(r01.val[1]), vget_low_s32(r23.val[1])); + out[2] = vcombine_s32(vget_high_s32(r01.val[0]), vget_high_s32(r23.val[0])); + out[3] = vcombine_s32(vget_high_s32(r01.val[1]), vget_high_s32(r23.val[1])); + out[4] = vcombine_s32(vget_low_s32(r45.val[0]), vget_low_s32(r67.val[0])); + out[5] = vcombine_s32(vget_low_s32(r45.val[1]), vget_low_s32(r67.val[1])); + out[6] = vcombine_s32(vget_high_s32(r45.val[0]), vget_high_s32(r67.val[0])); + out[7] = vcombine_s32(vget_high_s32(r45.val[1]), vget_high_s32(r67.val[1])); +} + +void avm_highbd_fdct8x8_neon(const int16_t *input, tran_low_t *final_output, + int stride) { + // Pass 1: column DCT. Load 8x8 input, multiply by 4, transform columns. + // col_lo[row] = columns 0-3, col_hi[row] = columns 4-7. + int32x4_t col_lo[8], col_hi[8]; + for (int i = 0; i < 8; i++) { + const int16x8_t row = vld1q_s16(input + i * stride); + col_lo[i] = vshll_n_s16(vget_low_s16(row), 2); + col_hi[i] = vshll_n_s16(vget_high_s16(row), 2); + } + + int32x4_t tmp_lo[8], tmp_hi[8]; + fdct8_pass_neon(col_lo, col_hi, tmp_lo, tmp_hi); + + // After pass 1: tmp_lo[vfreq] has cols 0-3, tmp_hi[vfreq] has cols 4-7. + // For pass 2 (row DCT), we need in[col] indexed by column, with lanes + // holding different vfreqs. This requires transposing both halves. + int32x4_t tr_lo[8], tr_hi[8]; + transpose_4x8_s32(tmp_lo, tr_lo); + transpose_4x8_s32(tmp_hi, tr_hi); + + // Recombine: tr_lo[i] (i<4) = vfreqs 0-3 for col i, + // tr_lo[i+4] = vfreqs 4-7 for col i. + // For pass 2: in[col], lo half = vfreqs 0-3, hi half = vfreqs 4-7. + int32x4_t in2_lo[8], in2_hi[8]; + for (int i = 0; i < 4; i++) { + in2_lo[i] = tr_lo[i]; + in2_hi[i] = tr_lo[i + 4]; + } + for (int i = 0; i < 4; i++) { + in2_lo[i + 4] = tr_hi[i]; + in2_hi[i + 4] = tr_hi[i + 4]; + } + + int32x4_t res_lo[8], res_hi[8]; + fdct8_pass_neon(in2_lo, in2_hi, res_lo, res_hi); + + // After pass 2: res_lo[hfreq] has lanes = vfreqs 0-3, + // res_hi[hfreq] has lanes = vfreqs 4-7. + // Need to store as final_output[vfreq*8 + hfreq], so transpose output. + int32x4_t fin_lo[8], fin_hi[8]; + transpose_4x8_s32(res_lo, fin_lo); + transpose_4x8_s32(res_hi, fin_hi); + + // fin_lo[vf] (vf<4) = hfreqs 0-3 for vfreq vf + // fin_lo[vf+4] = hfreqs 4-7 for vfreq vf + // fin_hi[vf] (vf<4) = hfreqs 0-3 for vfreq vf+4 + // fin_hi[vf+4] = hfreqs 4-7 for vfreq vf+4 + for (int vf = 0; vf < 4; vf++) { + int32x4_t v0 = fin_lo[vf]; + int32x4_t v1 = fin_lo[vf + 4]; + uint32x4_t s0 = vshrq_n_u32(vreinterpretq_u32_s32(v0), 31); + uint32x4_t s1 = vshrq_n_u32(vreinterpretq_u32_s32(v1), 31); + vst1q_s32(final_output + vf * 8, + vshrq_n_s32(vaddq_s32(v0, vreinterpretq_s32_u32(s0)), 1)); + vst1q_s32(final_output + vf * 8 + 4, + vshrq_n_s32(vaddq_s32(v1, vreinterpretq_s32_u32(s1)), 1)); + } + for (int vf = 0; vf < 4; vf++) { + int32x4_t v0 = fin_hi[vf]; + int32x4_t v1 = fin_hi[vf + 4]; + uint32x4_t s0 = vshrq_n_u32(vreinterpretq_u32_s32(v0), 31); + uint32x4_t s1 = vshrq_n_u32(vreinterpretq_u32_s32(v1), 31); + vst1q_s32(final_output + (4 + vf) * 8, + vshrq_n_s32(vaddq_s32(v0, vreinterpretq_s32_u32(s0)), 1)); + vst1q_s32(final_output + (4 + vf) * 8 + 4, + vshrq_n_s32(vaddq_s32(v1, vreinterpretq_s32_u32(s1)), 1)); + } +} diff --git a/avm_dsp/avm_dsp_rtcd_defs.pl b/avm_dsp/avm_dsp_rtcd_defs.pl index 254cf8424e..faa23aa564 100644 --- a/avm_dsp/avm_dsp_rtcd_defs.pl +++ b/avm_dsp/avm_dsp_rtcd_defs.pl @@ -204,7 +204,7 @@ () specialize qw/avm_fdct4x4_lp neon sse2/; add_proto qw/void avm_highbd_fdct8x8/, "const int16_t *input, tran_low_t *output, int stride"; - specialize qw/avm_highbd_fdct8x8 sse2/; + specialize qw/avm_highbd_fdct8x8 sse2 neon/; # FFT/IFFT (float) only used for denoising (and noise power spectral density estimation) add_proto qw/void avm_fft2x2_float/, "const float *input, float *temp, float *output"; diff --git a/test/test.cmake b/test/test.cmake index 8358e0c78d..9df0ba7893 100644 --- a/test/test.cmake +++ b/test/test.cmake @@ -243,7 +243,8 @@ if(NOT BUILD_SHARED_LIBS) "${AVM_ROOT}/test/palette_test.cc") list(APPEND AVM_UNIT_TEST_ENCODER_SOURCES - "${AVM_ROOT}/test/lossless_idtx_test.cc") + "${AVM_ROOT}/test/lossless_idtx_test.cc" + "${AVM_ROOT}/test/txfm_misc_test.cc") list( APPEND AVM_UNIT_TEST_ENCODER_INTRIN_SSE4_1 diff --git a/test/txfm_misc_test.cc b/test/txfm_misc_test.cc new file mode 100644 index 0000000000..68d1d8c146 --- /dev/null +++ b/test/txfm_misc_test.cc @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2026, Alliance for Open Media. All rights reserved + * + * This source code is subject to the terms of the BSD 3-Clause Clear License + * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear + * License was not distributed with this source code in the LICENSE file, you + * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/. 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 + * aomedia.org/license/patent-license/. + */ + +#include +#include +#include +#include + +#include "third_party/googletest/src/googletest/include/gtest/gtest.h" + +#include "config/avm_config.h" +#include "config/av2_rtcd.h" +#include "config/avm_dsp_rtcd.h" +#include "test/acm_random.h" +#include "test/clear_system_state.h" +#include "test/register_state_check.h" +#include "av2/common/common_data.h" +#include "av2/common/enums.h" +#include "avm_ports/avm_timer.h" +#include "avm_ports/mem.h" + +using libavm_test::ACMRandom; + +namespace { + +// -- highbd fdct8x8 -- + +typedef void (*Fdct8x8Func)(const int16_t *, tran_low_t *, int); +typedef std::tuple Fdct8x8Param; + +class HighbdFdct8x8Test : public ::testing::TestWithParam { + protected: + void TearDown() override { libavm_test::ClearSystemState(); } + + void RunCheck(const int16_t *input, int tolerance, const char *label) { + DECLARE_ALIGNED(16, tran_low_t, output_ref[64]); + DECLARE_ALIGNED(16, tran_low_t, output_tst[64]); + memset(output_ref, 0, sizeof(output_ref)); + memset(output_tst, 0, sizeof(output_tst)); + + Fdct8x8Func ref = std::get<0>(GetParam()); + Fdct8x8Func tst = std::get<1>(GetParam()); + + ref(input, output_ref, 8); + ASM_REGISTER_STATE_CHECK(tst(input, output_tst, 8)); + + for (int i = 0; i < 64; i++) { + ASSERT_LE(abs(output_ref[i] - output_tst[i]), tolerance) + << label << " mismatch at " << i; + } + } + + void RunMatchTest(int num_iterations) { + ACMRandom rnd(ACMRandom::DeterministicSeed()); + DECLARE_ALIGNED(16, int16_t, input[64]); + + for (int iter = 0; iter < num_iterations; iter++) { + for (int i = 0; i < 64; i++) { + input[i] = (int16_t)(rnd.Rand16() & 0x3FF) - 512; + } + RunCheck(input, 0, "random"); + } + } +}; +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(HighbdFdct8x8Test); + +TEST_P(HighbdFdct8x8Test, RandomBitExact) { RunMatchTest(1000); } + +TEST_P(HighbdFdct8x8Test, ExtremesAllZero) { + DECLARE_ALIGNED(16, int16_t, input[64]); + memset(input, 0, sizeof(input)); + RunCheck(input, 0, "AllZero"); +} + +TEST_P(HighbdFdct8x8Test, ExtremesAllMax) { + DECLARE_ALIGNED(16, int16_t, input[64]); + for (int i = 0; i < 64; i++) input[i] = 511; + RunCheck(input, 0, "AllMax"); +} + +TEST_P(HighbdFdct8x8Test, ExtremesAllMin) { + DECLARE_ALIGNED(16, int16_t, input[64]); + for (int i = 0; i < 64; i++) input[i] = -512; + RunCheck(input, 0, "AllMin"); +} + +TEST_P(HighbdFdct8x8Test, ExtremesInt16Range) { + DECLARE_ALIGNED(16, int16_t, input[64]); + for (int i = 0; i < 64; i++) input[i] = (i & 1) ? INT16_MAX : INT16_MIN; + RunCheck(input, 0, "Int16Extremes"); +} + +TEST_P(HighbdFdct8x8Test, RandomWideRange) { + ACMRandom rnd(ACMRandom::DeterministicSeed()); + DECLARE_ALIGNED(16, int16_t, input[64]); + for (int iter = 0; iter < 1000; iter++) { + for (int i = 0; i < 64; i++) input[i] = (int16_t)rnd.Rand16(); + RunCheck(input, 0, "wide-range"); + } +} + +TEST_P(HighbdFdct8x8Test, DISABLED_Speed) { + DECLARE_ALIGNED(16, int16_t, input[64]); + DECLARE_ALIGNED(16, tran_low_t, output[64]); + ACMRandom rnd(ACMRandom::DeterministicSeed()); + for (int i = 0; i < 64; i++) input[i] = (int16_t)(rnd.Rand16() & 0x3FF) - 512; + + Fdct8x8Func ref = std::get<0>(GetParam()); + Fdct8x8Func tst = std::get<1>(GetParam()); + const int kNumIter = 10000000; + + avm_usec_timer timer_c; + avm_usec_timer_start(&timer_c); + for (int i = 0; i < kNumIter; i++) ref(input, output, 8); + avm_usec_timer_mark(&timer_c); + + avm_usec_timer timer_tst; + avm_usec_timer_start(&timer_tst); + for (int i = 0; i < kNumIter; i++) tst(input, output, 8); + avm_usec_timer_mark(&timer_tst); + + const double t_c = static_cast(avm_usec_timer_elapsed(&timer_c)); + const double t_tst = static_cast(avm_usec_timer_elapsed(&timer_tst)); + printf("highbd_fdct8x8: C=%7.1fms SIMD=%7.1fms gain=%.2fx\n", t_c / 1000.0, + t_tst / 1000.0, t_c / t_tst); +} + +INSTANTIATE_TEST_SUITE_P(C, HighbdFdct8x8Test, + ::testing::Values(std::make_tuple( + &avm_highbd_fdct8x8_c, &avm_highbd_fdct8x8_c))); + +#if HAVE_NEON +INSTANTIATE_TEST_SUITE_P(NEON, HighbdFdct8x8Test, + ::testing::Values(std::make_tuple( + &avm_highbd_fdct8x8_c, &avm_highbd_fdct8x8_neon))); +#endif // HAVE_NEON +// -- fwd cross chroma tx -- + +typedef void (*CctxFunc)(tran_low_t *, tran_low_t *, TX_SIZE, CctxType, + const int); +typedef std::tuple CctxParam; + +class FwdCctxTest : public ::testing::TestWithParam { + protected: + void TearDown() override { libavm_test::ClearSystemState(); } + + void RunMatchTest(int num_iterations) { + ACMRandom rnd(ACMRandom::DeterministicSeed()); + CctxFunc ref = std::get<0>(GetParam()); + CctxFunc tst = std::get<1>(GetParam()); + CctxType cctx_type = std::get<2>(GetParam()); + TX_SIZE tx_size = std::get<3>(GetParam()); + int bd = std::get<4>(GetParam()); + const int ncoeffs = av2_get_max_eob(tx_size); + DECLARE_ALIGNED(16, tran_low_t, c1_ref[1024]); + DECLARE_ALIGNED(16, tran_low_t, c2_ref[1024]); + DECLARE_ALIGNED(16, tran_low_t, c1_tst[1024]); + DECLARE_ALIGNED(16, tran_low_t, c2_tst[1024]); + + for (int iter = 0; iter < num_iterations; iter++) { + const int range = 1 << (7 + bd); + for (int i = 0; i < ncoeffs; i++) { + int32_t val1 = (int32_t)(rnd.Rand31() % (2 * range)) - range; + int32_t val2 = (int32_t)(rnd.Rand31() % (2 * range)) - range; + c1_ref[i] = c1_tst[i] = val1; + c2_ref[i] = c2_tst[i] = val2; + } + + ref(c1_ref, c2_ref, tx_size, cctx_type, bd); + ASM_REGISTER_STATE_CHECK(tst(c1_tst, c2_tst, tx_size, cctx_type, bd)); + + for (int i = 0; i < ncoeffs; i++) { + ASSERT_EQ(c1_ref[i], c1_tst[i]) + << "c1 mismatch at " << i << " cctx=" << cctx_type << " bd=" << bd + << " iter=" << iter; + ASSERT_EQ(c2_ref[i], c2_tst[i]) + << "c2 mismatch at " << i << " cctx=" << cctx_type << " bd=" << bd + << " iter=" << iter; + } + } + } +}; +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(FwdCctxTest); + +TEST_P(FwdCctxTest, RandomBitExact) { RunMatchTest(1000); } + +// jscpd:ignore-start +TEST_P(FwdCctxTest, DISABLED_Speed) { + ACMRandom rnd(ACMRandom::DeterministicSeed()); + CctxFunc ref = std::get<0>(GetParam()); + CctxFunc tst = std::get<1>(GetParam()); + CctxType cctx_type = std::get<2>(GetParam()); + TX_SIZE tx_size = std::get<3>(GetParam()); + int bd = std::get<4>(GetParam()); + const int ncoeffs = av2_get_max_eob(tx_size); + + DECLARE_ALIGNED(16, tran_low_t, c1[1024]); + DECLARE_ALIGNED(16, tran_low_t, c2[1024]); + const int range = 1 << (7 + bd); + for (int i = 0; i < ncoeffs; i++) { + c1[i] = (int32_t)(rnd.Rand31() % (2 * range)) - range; + c2[i] = (int32_t)(rnd.Rand31() % (2 * range)) - range; + } + + const int kNumIter = 1000000; + + avm_usec_timer timer_c; + avm_usec_timer_start(&timer_c); + for (int i = 0; i < kNumIter; i++) ref(c1, c2, tx_size, cctx_type, bd); + avm_usec_timer_mark(&timer_c); + + avm_usec_timer timer_tst; + avm_usec_timer_start(&timer_tst); + for (int i = 0; i < kNumIter; i++) tst(c1, c2, tx_size, cctx_type, bd); + avm_usec_timer_mark(&timer_tst); + + const double t_c = static_cast(avm_usec_timer_elapsed(&timer_c)); + const double t_tst = static_cast(avm_usec_timer_elapsed(&timer_tst)); + printf("fwd_cctx cctx=%d bd=%d: C=%7.1fms SIMD=%7.1fms gain=%.2fx\n", + cctx_type, bd, t_c / 1000.0, t_tst / 1000.0, t_c / t_tst); +} +// jscpd:ignore-end + +static const CctxType kCctxTypes[] = { + CCTX_45, CCTX_30, CCTX_60, CCTX_MINUS45, CCTX_MINUS30, CCTX_MINUS60 +}; +static const TX_SIZE kCctxTxSizes[] = { TX_4X4, TX_8X8, TX_16X16 }; + +INSTANTIATE_TEST_SUITE_P( + C, FwdCctxTest, + ::testing::Combine(::testing::Values(&av2_fwd_cross_chroma_tx_block_c), + ::testing::Values(&av2_fwd_cross_chroma_tx_block_c), + ::testing::ValuesIn(kCctxTypes), + ::testing::ValuesIn(kCctxTxSizes), + ::testing::Values(8, 10, 12))); + +#if HAVE_NEON +INSTANTIATE_TEST_SUITE_P( + NEON, FwdCctxTest, + ::testing::Combine(::testing::Values(&av2_fwd_cross_chroma_tx_block_c), + ::testing::Values(&av2_fwd_cross_chroma_tx_block_neon), + ::testing::ValuesIn(kCctxTypes), + ::testing::ValuesIn(kCctxTxSizes), + ::testing::Values(8, 10, 12))); +#endif // HAVE_NEON + +// -- fwd_txfm (full pipeline C vs RTCD) -- + +struct FwdTxfmParam { + TX_SIZE tx_size; + TX_TYPE tx_type; + int bd; + int seed; + int use_ddt; +}; + +class FwdTxfmVariantTest : public ::testing::TestWithParam {}; + +TEST_P(FwdTxfmVariantTest, BitExact) { + const FwdTxfmParam &p = GetParam(); + const int txw = tx_size_wide[p.tx_size]; + const int txh = tx_size_high[p.tx_size]; + const int num_coeffs = txw * txh; + + ACMRandom rng(p.seed); + + DECLARE_ALIGNED(32, int16_t, input[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, ref_coeff[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, opt_coeff[64 * 64]); + + const int max_resi = (1 << p.bd) - 1; + for (int k = 0; k < txw * txh; k++) { + input[k] = (int16_t)((rng.Rand31() % (2 * max_resi + 1)) - max_resi); + } + memset(ref_coeff, 0, sizeof(ref_coeff)); + memset(opt_coeff, 0, sizeof(opt_coeff)); + + TxfmParam txfm_param; + memset(&txfm_param, 0, sizeof(txfm_param)); + txfm_param.tx_size = p.tx_size; + txfm_param.tx_type = p.tx_type; + txfm_param.bd = p.bd; + txfm_param.lossless = 0; + txfm_param.use_ddt = p.use_ddt; + + fwd_txfm_c(input, ref_coeff, txw, &txfm_param); + fwd_txfm(input, opt_coeff, txw, &txfm_param); + + for (int k = 0; k < num_coeffs; k++) { + ASSERT_EQ(ref_coeff[k], opt_coeff[k]) + << "mismatch at " << k << " tx_size=" << p.tx_size + << " tx_type=" << p.tx_type << " bd=" << p.bd << " seed=" << p.seed + << " use_ddt=" << p.use_ddt; + } +} + +static bool is_valid_fwd_txfm_combo(TX_SIZE sz, TX_TYPE ty) { + if (ty == DCT_DCT || ty == IDTX) return true; + const int w = tx_size_wide[sz]; + const int h = tx_size_high[sz]; + const int row_1d = g_hor_tx_type[ty]; + const int col_1d = g_ver_tx_type[ty]; + if ((row_1d == DST7 || row_1d == DCT8) && w > 16) return false; + if ((col_1d == DST7 || col_1d == DCT8) && h > 16) return false; + if (row_1d == IDT && w > 32) return false; + if (col_1d == IDT && h > 32) return false; + return true; +} + +static std::vector GenerateFwdParams() { + std::vector params; + const int seeds[] = { 1, 42, 100, 255, 1000, 2023, 3141, 5678, 7777, 9999 }; + const int bds[] = { 8, 10, 12 }; + const TX_SIZE sizes[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32, TX_4X8, + TX_8X4, TX_8X16, TX_16X8, TX_16X32, TX_32X16 }; + const TX_TYPE types[] = { + DCT_DCT, ADST_DCT, DCT_ADST, ADST_ADST, FLIPADST_DCT, + DCT_FLIPADST, FLIPADST_FLIPADST, IDTX, V_DCT, H_DCT + }; + + for (int use_ddt = 0; use_ddt <= 1; use_ddt++) + for (auto bd : bds) + for (auto sz : sizes) + for (auto ty : types) + if (is_valid_fwd_txfm_combo(sz, ty)) + for (auto seed : seeds) + params.push_back({ sz, ty, bd, seed, use_ddt }); + + return params; +} + +INSTANTIATE_TEST_SUITE_P(Variants, FwdTxfmVariantTest, + ::testing::ValuesIn(GenerateFwdParams())); + +TEST(FwdTxfmVariantExtreme, AllZero) { + const TX_SIZE sizes[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32 }; + const int bds[] = { 8, 10, 12 }; + + for (auto bd : bds) { + for (auto sz : sizes) { + const int txw = tx_size_wide[sz]; + const int txh = tx_size_high[sz]; + + DECLARE_ALIGNED(32, int16_t, input[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, ref_coeff[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, opt_coeff[64 * 64]); + + memset(input, 0, sizeof(input)); + memset(ref_coeff, 0, sizeof(ref_coeff)); + memset(opt_coeff, 0, sizeof(opt_coeff)); + + TxfmParam txfm_param; + memset(&txfm_param, 0, sizeof(txfm_param)); + txfm_param.tx_size = sz; + txfm_param.tx_type = DCT_DCT; + txfm_param.bd = bd; + + fwd_txfm_c(input, ref_coeff, txw, &txfm_param); + fwd_txfm(input, opt_coeff, txw, &txfm_param); + + for (int k = 0; k < txw * txh; k++) { + ASSERT_EQ(ref_coeff[k], opt_coeff[k]) + << "all-zero mismatch at " << k << " sz=" << sz << " bd=" << bd; + } + } + } +} + +TEST(FwdTxfmVariantExtreme, StrideMismatch) { + const TX_SIZE sizes[] = { TX_8X8, TX_16X16 }; + const int bds[] = { 8, 10, 12 }; + + for (auto bd : bds) { + for (auto sz : sizes) { + const int txw = tx_size_wide[sz]; + const int txh = tx_size_high[sz]; + const int stride = txw + 8; + + DECLARE_ALIGNED(32, int16_t, input[72 * 64]); + DECLARE_ALIGNED(32, tran_low_t, ref_coeff[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, opt_coeff[64 * 64]); + + ACMRandom rng(42); + const int max_resi = (1 << bd) - 1; + for (int y = 0; y < txh; y++) + for (int x = 0; x < stride; x++) + input[y * stride + x] = + (int16_t)((rng.Rand31() % (2 * max_resi + 1)) - max_resi); + memset(ref_coeff, 0, sizeof(ref_coeff)); + memset(opt_coeff, 0, sizeof(opt_coeff)); + + TxfmParam txfm_param; + memset(&txfm_param, 0, sizeof(txfm_param)); + txfm_param.tx_size = sz; + txfm_param.tx_type = DCT_DCT; + txfm_param.bd = bd; + + fwd_txfm_c(input, ref_coeff, stride, &txfm_param); + fwd_txfm(input, opt_coeff, stride, &txfm_param); + + for (int k = 0; k < txw * txh; k++) { + ASSERT_EQ(ref_coeff[k], opt_coeff[k]) + << "stride mismatch at " << k << " sz=" << sz << " bd=" << bd; + } + } + } +} + +} // namespace From 9024dc2a41b6496c0807edc45034a2ce90976999 Mon Sep 17 00:00:00 2001 From: Joseph Justiss Date: Wed, 16 Sep 2026 08:25:41 -0700 Subject: [PATCH 2/5] fix integer sanitizer: cast to int32_t before unsigned subtraction in tests --- test/txfm_misc_test.cc | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/txfm_misc_test.cc b/test/txfm_misc_test.cc index 68d1d8c146..57572ce0e5 100644 --- a/test/txfm_misc_test.cc +++ b/test/txfm_misc_test.cc @@ -279,7 +279,8 @@ TEST_P(FwdTxfmVariantTest, BitExact) { const int max_resi = (1 << p.bd) - 1; for (int k = 0; k < txw * txh; k++) { - input[k] = (int16_t)((rng.Rand31() % (2 * max_resi + 1)) - max_resi); + input[k] = + (int16_t)((int32_t)(rng.Rand31() % (2 * max_resi + 1)) - max_resi); } memset(ref_coeff, 0, sizeof(ref_coeff)); memset(opt_coeff, 0, sizeof(opt_coeff)); @@ -394,7 +395,8 @@ TEST(FwdTxfmVariantExtreme, StrideMismatch) { for (int y = 0; y < txh; y++) for (int x = 0; x < stride; x++) input[y * stride + x] = - (int16_t)((rng.Rand31() % (2 * max_resi + 1)) - max_resi); + (int16_t)((int32_t)(rng.Rand31() % (2 * max_resi + 1)) - + max_resi); memset(ref_coeff, 0, sizeof(ref_coeff)); memset(opt_coeff, 0, sizeof(opt_coeff)); From 8bed4db8330348a3284c0d84d9dacf7675970771 Mon Sep 17 00:00:00 2001 From: Joseph Justiss Date: Thu, 17 Sep 2026 14:34:05 -0700 Subject: [PATCH 3/5] bench: thread2+3 MLA/MLS + constant shift for CCTX --- av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c | 32 ++++++++------------- 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c index f31d643919..f68ae48574 100644 --- a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c +++ b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c @@ -87,10 +87,10 @@ void av2_highbd_fwht4x4_neon(const int16_t *input, tran_low_t *output, av2_fwht4x4_neon(input, output, stride); } -static INLINE int32x4_t round_power_of_two_signed_neon(int32x4_t v, int bits) { - const int32x4_t bias = vdupq_n_s32((1 << bits) >> 1); - const int32x4_t sign = vshrq_n_s32(v, 31); - return vshlq_s32(vaddq_s32(vaddq_s32(v, bias), sign), vdupq_n_s32(-bits)); +static INLINE int32x4_t round_power_of_two_signed_cctx_neon(int32x4_t v, + int32x4_t bias) { + int32x4_t round = vsraq_n_s32(bias, v, 31); + return vshrq_n_s32(vaddq_s32(round, v), CCTX_PREC_BITS); } void av2_fwd_cross_chroma_tx_block_neon(tran_low_t *coeff_c1, @@ -107,17 +107,19 @@ void av2_fwd_cross_chroma_tx_block_neon(tran_low_t *coeff_c1, const int32x4_t sin_t = vdupq_n_s32(cctx_mtx[angle_idx][1]); const int32x4_t max_val = vdupq_n_s32((1 << (7 + bd)) - 1); const int32x4_t min_val = vdupq_n_s32(-(1 << (7 + bd))); + const int32x4_t bias = vdupq_n_s32((1 << CCTX_PREC_BITS) >> 1); - int i = 0; - for (; i + 4 <= ncoeffs; i += 4) { + for (int i = 0; i + 4 <= ncoeffs; i += 4) { const int32x4_t c1 = vld1q_s32(&src_c1[i]); const int32x4_t c2 = vld1q_s32(&src_c2[i]); - const int32x4_t t0 = vaddq_s32(vmulq_s32(cos_t, c1), vmulq_s32(sin_t, c2)); - const int32x4_t t1 = vsubq_s32(vmulq_s32(cos_t, c2), vmulq_s32(sin_t, c1)); + int32x4_t t0 = vmulq_s32(cos_t, c1); + t0 = vmlaq_s32(t0, sin_t, c2); + int32x4_t t1 = vmulq_s32(cos_t, c2); + t1 = vmlsq_s32(t1, sin_t, c1); - int32x4_t r0 = round_power_of_two_signed_neon(t0, CCTX_PREC_BITS); - int32x4_t r1 = round_power_of_two_signed_neon(t1, CCTX_PREC_BITS); + int32x4_t r0 = round_power_of_two_signed_cctx_neon(t0, bias); + int32x4_t r1 = round_power_of_two_signed_cctx_neon(t1, bias); r0 = vminq_s32(vmaxq_s32(r0, min_val), max_val); r1 = vminq_s32(vmaxq_s32(r1, min_val), max_val); @@ -125,14 +127,4 @@ void av2_fwd_cross_chroma_tx_block_neon(tran_low_t *coeff_c1, vst1q_s32(&src_c1[i], r0); vst1q_s32(&src_c2[i], r1); } - for (; i < ncoeffs; i++) { - int64_t tmp0 = (int64_t)cctx_mtx[angle_idx][0] * (int64_t)src_c1[i] + - (int64_t)cctx_mtx[angle_idx][1] * (int64_t)src_c2[i]; - int64_t tmp1 = (int64_t)-cctx_mtx[angle_idx][1] * (int64_t)src_c1[i] + - (int64_t)cctx_mtx[angle_idx][0] * (int64_t)src_c2[i]; - src_c1[i] = (int32_t)ROUND_POWER_OF_TWO_SIGNED_64(tmp0, CCTX_PREC_BITS); - src_c2[i] = (int32_t)ROUND_POWER_OF_TWO_SIGNED_64(tmp1, CCTX_PREC_BITS); - src_c1[i] = clamp_value(src_c1[i], 8 + bd); - src_c2[i] = clamp_value(src_c2[i], 8 + bd); - } } From 26febcd7899b6118b5de0456bd75edfdb893b925 Mon Sep 17 00:00:00 2001 From: Joseph Justiss Date: Fri, 18 Sep 2026 07:51:41 -0700 Subject: [PATCH 4/5] address review: shared transpose reuse, dead tail removal, test coverage --- av2/encoder/arm/neon/highbd_fwd_txfm_neon.c | 81 ++++++++++++--------- av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c | 26 +++---- avm_dsp/arm/fwd_txfm_neon.c | 44 ++--------- test/txfm_misc_test.cc | 43 ++++++++++- 4 files changed, 107 insertions(+), 87 deletions(-) diff --git a/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c b/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c index faccd880be..369ee41e59 100644 --- a/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c +++ b/av2/encoder/arm/neon/highbd_fwd_txfm_neon.c @@ -22,21 +22,7 @@ #include "av2/common/txb_common.h" #include "av2/encoder/fwd_txfm_internal.h" #include "avm_dsp/txfm_common.h" - -static INLINE void transpose_store_4x4_s32(int32x4_t r0, int32x4_t r1, - int32x4_t r2, int32x4_t r3, int *dst, - int dst_stride) { - int32x4x2_t t01 = vtrnq_s32(r0, r1); - int32x4x2_t t23 = vtrnq_s32(r2, r3); - vst1q_s32(dst, - vcombine_s32(vget_low_s32(t01.val[0]), vget_low_s32(t23.val[0]))); - vst1q_s32(dst + dst_stride, - vcombine_s32(vget_low_s32(t01.val[1]), vget_low_s32(t23.val[1]))); - vst1q_s32(dst + 2 * dst_stride, - vcombine_s32(vget_high_s32(t01.val[0]), vget_high_s32(t23.val[0]))); - vst1q_s32(dst + 3 * dst_stride, - vcombine_s32(vget_high_s32(t01.val[1]), vget_high_s32(t23.val[1]))); -} +#include "avm_dsp/arm/transpose_neon.h" static void fwd_txfm_dct2_size4_neon(const int *src, int *dst, int shift, int line, int skip_line, int zero_line) { @@ -75,7 +61,11 @@ static void fwd_txfm_dct2_size4_neon(const int *src, int *dst, int shift, r3 = vmlaq_n_s32(r3, b1, tx_mat[13]); r3 = vshlq_s32(r3, v_shift); - transpose_store_4x4_s32(r0, r1, r2, r3, dst + j * 4, 4); + transpose_elems_inplace_s32_4x4(&r0, &r1, &r2, &r3); + vst1q_s32(dst + j * 4, r0); + vst1q_s32(dst + j * 4 + 4, r1); + vst1q_s32(dst + j * 4 + 2 * 4, r2); + vst1q_s32(dst + j * 4 + 3 * 4, r3); } if (skip_line) { memset(dst + nz_line * 4, 0, sizeof(int) * 4 * skip_line); @@ -130,8 +120,12 @@ static void fwd_txfm_dct2_size8_neon(const int *src, int *dst, int shift, } for (int blk = 0; blk < 8; blk += 4) { - transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], - out[blk + 3], dst + j * 8 + blk, 8); + transpose_elems_inplace_s32_4x4(&out[blk + 0], &out[blk + 1], + &out[blk + 2], &out[blk + 3]); + vst1q_s32(dst + j * 8 + blk, out[blk + 0]); + vst1q_s32(dst + j * 8 + blk + 8, out[blk + 1]); + vst1q_s32(dst + j * 8 + blk + 2 * 8, out[blk + 2]); + vst1q_s32(dst + j * 8 + blk + 3 * 8, out[blk + 3]); } } if (skip_line) { @@ -214,10 +208,13 @@ static void fwd_txfm_dct2_size16_neon(const int *src, int *dst, int shift, out[k + 2] = vshlq_s32(acc1, v_shift); } - // Transpose 4x16 and store: process in 4x4 blocks for (int blk = 0; blk < 16; blk += 4) { - transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], - out[blk + 3], dst + j * 16 + blk, 16); + transpose_elems_inplace_s32_4x4(&out[blk + 0], &out[blk + 1], + &out[blk + 2], &out[blk + 3]); + vst1q_s32(dst + j * 16 + blk, out[blk + 0]); + vst1q_s32(dst + j * 16 + blk + 16, out[blk + 1]); + vst1q_s32(dst + j * 16 + blk + 2 * 16, out[blk + 2]); + vst1q_s32(dst + j * 16 + blk + 3 * 16, out[blk + 3]); } } if (skip_line) { @@ -309,8 +306,12 @@ static void fwd_txfm_dct2_size32_neon(const int *src, int *dst, int shift, } for (int blk = 0; blk < 32; blk += 4) { - transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], - out[blk + 3], dst + j * 32 + blk, 32); + transpose_elems_inplace_s32_4x4(&out[blk + 0], &out[blk + 1], + &out[blk + 2], &out[blk + 3]); + vst1q_s32(dst + j * 32 + blk, out[blk + 0]); + vst1q_s32(dst + j * 32 + blk + 32, out[blk + 1]); + vst1q_s32(dst + j * 32 + blk + 2 * 32, out[blk + 2]); + vst1q_s32(dst + j * 32 + blk + 3 * 32, out[blk + 3]); } } if (skip_line) { @@ -358,7 +359,11 @@ static void fwd_txfm_matmul_size4_neon(const int *src, int *dst, int shift, r3 = vmlaq_n_s32(r3, s3, tx_mat[15]); r3 = vshlq_s32(r3, v_shift); - transpose_store_4x4_s32(r0, r1, r2, r3, dst + i * 4, 4); + transpose_elems_inplace_s32_4x4(&r0, &r1, &r2, &r3); + vst1q_s32(dst + i * 4, r0); + vst1q_s32(dst + i * 4 + 4, r1); + vst1q_s32(dst + i * 4 + 2 * 4, r2); + vst1q_s32(dst + i * 4 + 3 * 4, r3); } if (skip_line) { @@ -407,8 +412,12 @@ static void fwd_txfm_matmul_size8_neon(const int *src, int *dst, int shift, } for (int blk = 0; blk < 8; blk += 4) { - transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], - out[blk + 3], dst + i * 8 + blk, 8); + transpose_elems_inplace_s32_4x4(&out[blk + 0], &out[blk + 1], + &out[blk + 2], &out[blk + 3]); + vst1q_s32(dst + i * 8 + blk, out[blk + 0]); + vst1q_s32(dst + i * 8 + blk + 8, out[blk + 1]); + vst1q_s32(dst + i * 8 + blk + 2 * 8, out[blk + 2]); + vst1q_s32(dst + i * 8 + blk + 3 * 8, out[blk + 3]); } } @@ -448,8 +457,12 @@ static void fwd_txfm_matmul_size16_neon(const int *src, int *dst, int shift, } for (int blk = 0; blk < 16; blk += 4) { - transpose_store_4x4_s32(out[blk + 0], out[blk + 1], out[blk + 2], - out[blk + 3], dst + i * 16 + blk, 16); + transpose_elems_inplace_s32_4x4(&out[blk + 0], &out[blk + 1], + &out[blk + 2], &out[blk + 3]); + vst1q_s32(dst + i * 16 + blk, out[blk + 0]); + vst1q_s32(dst + i * 16 + blk + 16, out[blk + 1]); + vst1q_s32(dst + i * 16 + blk + 2 * 16, out[blk + 2]); + vst1q_s32(dst + i * 16 + blk + 3 * 16, out[blk + 3]); } } @@ -483,8 +496,11 @@ static void fwd_txfm_idtx_neon(const int *src, int *dst, int shift, int line, int32x4_t r3 = vshlq_s32( vmlaq_s32(v_offset, vld1q_s32(src + (j + 3) * line + i), v_scale), v_shift); - transpose_store_4x4_s32(r0, r1, r2, r3, dst + i * tx1d_size + j, - tx1d_size); + transpose_elems_inplace_s32_4x4(&r0, &r1, &r2, &r3); + vst1q_s32(dst + i * tx1d_size + j, r0); + vst1q_s32(dst + i * tx1d_size + j + tx1d_size, r1); + vst1q_s32(dst + i * tx1d_size + j + 2 * tx1d_size, r2); + vst1q_s32(dst + i * tx1d_size + j + 3 * tx1d_size, r3); } } @@ -740,6 +756,7 @@ void fwd_txfm_neon(const int16_t *resi, tran_low_t *coeff, int diff_stride, const int sqrt2 = ((log2width + log2height) & 1) ? 1 : 0; if (sqrt2) { const int count = AVMMIN(1024, width * height); + assert((count % 4) == 0); int i = 0; for (; i + 4 <= count; i += 4) { int32x4_t v = vld1q_s32(coeff + i); @@ -749,9 +766,5 @@ void fwd_txfm_neon(const int16_t *resi, tran_low_t *coeff, int diff_stride, int32x2_t r_hi = vrshrn_n_s64(hi, NewSqrt2Bits); vst1q_s32(coeff + i, vcombine_s32(r_lo, r_hi)); } - for (; i < count; i++) { - coeff[i] = - (int32_t)round_shift((int64_t)coeff[i] * NewSqrt2, NewSqrt2Bits); - } } } diff --git a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c index f68ae48574..bd1946e006 100644 --- a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c +++ b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c @@ -16,21 +16,9 @@ #include "av2/common/av2_txfm.h" #include "av2/common/enums.h" +#include "avm_dsp/arm/transpose_neon.h" #include "avm_dsp/txfm_common.h" -static void transpose4x4(int16x8_t in[2], int16x4_t out[4]) { - int32x4x2_t b0 = - vtrnq_s32(vreinterpretq_s32_s16(in[0]), vreinterpretq_s32_s16(in[1])); - int16x4x2_t c0 = vtrn_s16(vreinterpret_s16_s32(vget_low_s32(b0.val[0])), - vreinterpret_s16_s32(vget_high_s32(b0.val[0]))); - int16x4x2_t c1 = vtrn_s16(vreinterpret_s16_s32(vget_low_s32(b0.val[1])), - vreinterpret_s16_s32(vget_high_s32(b0.val[1]))); - out[0] = c0.val[0]; - out[1] = c0.val[1]; - out[2] = c1.val[0]; - out[3] = c1.val[1]; -} - void av2_fwht4x4_neon(const int16_t *input, tran_low_t *output, int stride) { // Load the 4x4 source in transposed form. int16x4_t a1, b1, c1, d1, e; @@ -55,7 +43,11 @@ void av2_fwht4x4_neon(const int16_t *input, tran_low_t *output, int stride) { x[1] = vcombine_s16(d1, b1); int16x4_t s[4]; - transpose4x4(x, s); + s[0] = vget_low_s16(x[0]); + s[1] = vget_high_s16(x[0]); + s[2] = vget_low_s16(x[1]); + s[3] = vget_high_s16(x[1]); + transpose_elems_inplace_s16_4x4(&s[0], &s[1], &s[2], &s[3]); a1 = s[0]; b1 = s[1]; @@ -74,7 +66,11 @@ void av2_fwht4x4_neon(const int16_t *input, tran_low_t *output, int stride) { x[0] = vcombine_s16(a1, c1); x[1] = vcombine_s16(d1, b1); - transpose4x4(x, s); + s[0] = vget_low_s16(x[0]); + s[1] = vget_high_s16(x[0]); + s[2] = vget_low_s16(x[1]); + s[3] = vget_high_s16(x[1]); + transpose_elems_inplace_s16_4x4(&s[0], &s[1], &s[2], &s[3]); vst1q_s32(&output[0], vshll_n_s16(s[0], UNIT_QUANT_SHIFT)); vst1q_s32(&output[4], vshll_n_s16(s[1], UNIT_QUANT_SHIFT)); diff --git a/avm_dsp/arm/fwd_txfm_neon.c b/avm_dsp/arm/fwd_txfm_neon.c index fcfb7d7430..2c345d69b7 100644 --- a/avm_dsp/arm/fwd_txfm_neon.c +++ b/avm_dsp/arm/fwd_txfm_neon.c @@ -289,25 +289,6 @@ static void fdct8_pass_neon(const int32x4_t *in_lo, const int32x4_t *in_hi, vrshrn_n_s64(w3, DCT_CONST_BITS)); } -// Transpose a 4x8 block of int32x4_t values. -// Input: in[0..7], each 4-wide. -// Output: out[0..3] = cols 0-3 from rows 0-3, -// out[4..7] = cols 0-3 from rows 4-7. -static void transpose_4x8_s32(const int32x4_t *in, int32x4_t *out) { - int32x4x2_t r01 = vtrnq_s32(in[0], in[1]); - int32x4x2_t r23 = vtrnq_s32(in[2], in[3]); - int32x4x2_t r45 = vtrnq_s32(in[4], in[5]); - int32x4x2_t r67 = vtrnq_s32(in[6], in[7]); - out[0] = vcombine_s32(vget_low_s32(r01.val[0]), vget_low_s32(r23.val[0])); - out[1] = vcombine_s32(vget_low_s32(r01.val[1]), vget_low_s32(r23.val[1])); - out[2] = vcombine_s32(vget_high_s32(r01.val[0]), vget_high_s32(r23.val[0])); - out[3] = vcombine_s32(vget_high_s32(r01.val[1]), vget_high_s32(r23.val[1])); - out[4] = vcombine_s32(vget_low_s32(r45.val[0]), vget_low_s32(r67.val[0])); - out[5] = vcombine_s32(vget_low_s32(r45.val[1]), vget_low_s32(r67.val[1])); - out[6] = vcombine_s32(vget_high_s32(r45.val[0]), vget_high_s32(r67.val[0])); - out[7] = vcombine_s32(vget_high_s32(r45.val[1]), vget_high_s32(r67.val[1])); -} - void avm_highbd_fdct8x8_neon(const int16_t *input, tran_low_t *final_output, int stride) { // Pass 1: column DCT. Load 8x8 input, multiply by 4, transform columns. @@ -325,22 +306,11 @@ void avm_highbd_fdct8x8_neon(const int16_t *input, tran_low_t *final_output, // After pass 1: tmp_lo[vfreq] has cols 0-3, tmp_hi[vfreq] has cols 4-7. // For pass 2 (row DCT), we need in[col] indexed by column, with lanes // holding different vfreqs. This requires transposing both halves. - int32x4_t tr_lo[8], tr_hi[8]; - transpose_4x8_s32(tmp_lo, tr_lo); - transpose_4x8_s32(tmp_hi, tr_hi); - - // Recombine: tr_lo[i] (i<4) = vfreqs 0-3 for col i, - // tr_lo[i+4] = vfreqs 4-7 for col i. - // For pass 2: in[col], lo half = vfreqs 0-3, hi half = vfreqs 4-7. int32x4_t in2_lo[8], in2_hi[8]; - for (int i = 0; i < 4; i++) { - in2_lo[i] = tr_lo[i]; - in2_hi[i] = tr_lo[i + 4]; - } - for (int i = 0; i < 4; i++) { - in2_lo[i + 4] = tr_hi[i]; - in2_hi[i + 4] = tr_hi[i + 4]; - } + transpose_arrays_s32_4x4(tmp_lo, in2_lo); + transpose_arrays_s32_4x4(tmp_lo + 4, in2_hi); + transpose_arrays_s32_4x4(tmp_hi, in2_lo + 4); + transpose_arrays_s32_4x4(tmp_hi + 4, in2_hi + 4); int32x4_t res_lo[8], res_hi[8]; fdct8_pass_neon(in2_lo, in2_hi, res_lo, res_hi); @@ -349,8 +319,10 @@ void avm_highbd_fdct8x8_neon(const int16_t *input, tran_low_t *final_output, // res_hi[hfreq] has lanes = vfreqs 4-7. // Need to store as final_output[vfreq*8 + hfreq], so transpose output. int32x4_t fin_lo[8], fin_hi[8]; - transpose_4x8_s32(res_lo, fin_lo); - transpose_4x8_s32(res_hi, fin_hi); + transpose_arrays_s32_4x4(res_lo, fin_lo); + transpose_arrays_s32_4x4(res_lo + 4, fin_lo + 4); + transpose_arrays_s32_4x4(res_hi, fin_hi); + transpose_arrays_s32_4x4(res_hi + 4, fin_hi + 4); // fin_lo[vf] (vf<4) = hfreqs 0-3 for vfreq vf // fin_lo[vf+4] = hfreqs 4-7 for vfreq vf diff --git a/test/txfm_misc_test.cc b/test/txfm_misc_test.cc index 57572ce0e5..2dcf778701 100644 --- a/test/txfm_misc_test.cc +++ b/test/txfm_misc_test.cc @@ -321,8 +321,10 @@ static std::vector GenerateFwdParams() { std::vector params; const int seeds[] = { 1, 42, 100, 255, 1000, 2023, 3141, 5678, 7777, 9999 }; const int bds[] = { 8, 10, 12 }; - const TX_SIZE sizes[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32, TX_4X8, - TX_8X4, TX_8X16, TX_16X8, TX_16X32, TX_32X16 }; + const TX_SIZE sizes[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32, TX_4X8, + TX_8X4, TX_8X16, TX_16X8, TX_16X32, TX_32X16, + TX_4X16, TX_16X4, TX_8X32, TX_32X8, TX_4X32, + TX_32X4 }; const TX_TYPE types[] = { DCT_DCT, ADST_DCT, DCT_ADST, ADST_ADST, FLIPADST_DCT, DCT_FLIPADST, FLIPADST_FLIPADST, IDTX, V_DCT, H_DCT @@ -417,4 +419,41 @@ TEST(FwdTxfmVariantExtreme, StrideMismatch) { } } +TEST(FwdTxfmVariantExtreme, MaxResidualNonSquare) { + const TX_SIZE sizes[] = { TX_4X8, TX_8X4, TX_8X16, TX_16X8, + TX_4X16, TX_16X4, TX_8X32, TX_32X8 }; + const int bds[] = { 8, 10, 12 }; + + for (auto bd : bds) { + const int max_resi = (1 << bd) - 1; + for (auto sz : sizes) { + const int txw = tx_size_wide[sz]; + const int txh = tx_size_high[sz]; + + DECLARE_ALIGNED(32, int16_t, input[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, ref_coeff[64 * 64]); + DECLARE_ALIGNED(32, tran_low_t, opt_coeff[64 * 64]); + + for (int k = 0; k < txw * txh; k++) + input[k] = (k & 1) ? max_resi : -max_resi; + memset(ref_coeff, 0, sizeof(ref_coeff)); + memset(opt_coeff, 0, sizeof(opt_coeff)); + + TxfmParam txfm_param; + memset(&txfm_param, 0, sizeof(txfm_param)); + txfm_param.tx_size = sz; + txfm_param.tx_type = DCT_DCT; + txfm_param.bd = bd; + + fwd_txfm_c(input, ref_coeff, txw, &txfm_param); + fwd_txfm(input, opt_coeff, txw, &txfm_param); + + for (int k = 0; k < txw * txh; k++) { + ASSERT_EQ(ref_coeff[k], opt_coeff[k]) + << "max-resi mismatch at " << k << " sz=" << sz << " bd=" << bd; + } + } + } +} + } // namespace From 045f6d45accff10f08c7d3c236a887b521ff29b1 Mon Sep 17 00:00:00 2001 From: Joseph Justiss Date: Fri, 18 Sep 2026 08:30:13 -0700 Subject: [PATCH 5/5] fix clang-format violations --- av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c | 2 +- test/txfm_misc_test.cc | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c index bd1946e006..08b7ec389f 100644 --- a/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c +++ b/av2/encoder/arm/neon/hybrid_fwd_txfm_neon.c @@ -84,7 +84,7 @@ void av2_highbd_fwht4x4_neon(const int16_t *input, tran_low_t *output, } static INLINE int32x4_t round_power_of_two_signed_cctx_neon(int32x4_t v, - int32x4_t bias) { + int32x4_t bias) { int32x4_t round = vsraq_n_s32(bias, v, 31); return vshrq_n_s32(vaddq_s32(round, v), CCTX_PREC_BITS); } diff --git a/test/txfm_misc_test.cc b/test/txfm_misc_test.cc index 2dcf778701..0fa91bbac7 100644 --- a/test/txfm_misc_test.cc +++ b/test/txfm_misc_test.cc @@ -321,10 +321,10 @@ static std::vector GenerateFwdParams() { std::vector params; const int seeds[] = { 1, 42, 100, 255, 1000, 2023, 3141, 5678, 7777, 9999 }; const int bds[] = { 8, 10, 12 }; - const TX_SIZE sizes[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32, TX_4X8, - TX_8X4, TX_8X16, TX_16X8, TX_16X32, TX_32X16, - TX_4X16, TX_16X4, TX_8X32, TX_32X8, TX_4X32, - TX_32X4 }; + const TX_SIZE sizes[] = { TX_4X4, TX_8X8, TX_16X16, TX_32X32, + TX_4X8, TX_8X4, TX_8X16, TX_16X8, + TX_16X32, TX_32X16, TX_4X16, TX_16X4, + TX_8X32, TX_32X8, TX_4X32, TX_32X4 }; const TX_TYPE types[] = { DCT_DCT, ADST_DCT, DCT_ADST, ADST_ADST, FLIPADST_DCT, DCT_FLIPADST, FLIPADST_FLIPADST, IDTX, V_DCT, H_DCT