PR #22685 opened by Jun Zhao (mypopydev) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22685 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22685.patch
These three commits add NEON-optimized reference sample filtering for HEVC 8-bit intra prediction on AArch64. The first commit extracts the 3-tap [1,2,1]>>2 filter and strong intra smoothing from the inline code in intra_pred() into standalone functions dispatched through new HEVCPredContext function pointers. The input arrays are padded by 16 bytes to allow safe 32-byte SIMD loads. The second commit adds checkasm tests covering both filter functions across all applicable block sizes and bit depths. The third commit provides the NEON implementations. The 3-tap filter uses a sliding-window approach with ld1/ext to form prev/curr/next, widening arithmetic via uaddl/ushll, and rounding narrow via rshrn. An overlap-last-16 technique eliminates partial-store overhead for the tail. Strong smoothing preloads weight tables into v0-v7 and processes 16 samples per fully-unrolled block using umull/umlal/rshrn. Speedups on Apple M4: 3-tap 3.6x/2.5x/1.8x for 8x8/16x16/32x32, strong smoothing 1.7x. From 494bd855b6ec3acbe8e196ac086aaa07ec4bbd5d Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Thu, 2 Apr 2026 08:23:26 +0800 Subject: [PATCH 1/3] lavc/hevc: extract reference sample filter into function pointers Extract 3-tap [1,2,1]>>2 and strong intra smoothing from intra_pred() into HEVCPredContext function pointers, preparing for arch-specific overrides. pred_filter_3tap[3] indexed by log2_size - 3 (sizes 8/16/32). pred_filter_strong for 32x32 luma only. Signed-off-by: Jun Zhao <[email protected]> --- libavcodec/hevc/pred.c | 6 ++- libavcodec/hevc/pred.h | 6 +++ libavcodec/hevc/pred_template.c | 74 +++++++++++++++++++++++---------- 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/libavcodec/hevc/pred.c b/libavcodec/hevc/pred.c index 88306c23c4..38840f904d 100644 --- a/libavcodec/hevc/pred.c +++ b/libavcodec/hevc/pred.c @@ -58,7 +58,11 @@ void ff_hevc_pred_init(HEVCPredContext *hpc, int bit_depth) hpc->pred_angular[0] = FUNC(pred_angular_0, depth); \ hpc->pred_angular[1] = FUNC(pred_angular_1, depth); \ hpc->pred_angular[2] = FUNC(pred_angular_2, depth); \ - hpc->pred_angular[3] = FUNC(pred_angular_3, depth); + hpc->pred_angular[3] = FUNC(pred_angular_3, depth); \ + hpc->pred_filter_3tap[0] = FUNC(pred_filter_3tap, depth); \ + hpc->pred_filter_3tap[1] = FUNC(pred_filter_3tap, depth); \ + hpc->pred_filter_3tap[2] = FUNC(pred_filter_3tap, depth); \ + hpc->pred_filter_strong = FUNC(pred_filter_strong, depth); switch (bit_depth) { case 9: diff --git a/libavcodec/hevc/pred.h b/libavcodec/hevc/pred.h index c4bd72b1a3..d9ab70b81b 100644 --- a/libavcodec/hevc/pred.h +++ b/libavcodec/hevc/pred.h @@ -40,6 +40,12 @@ typedef struct HEVCPredContext { void (*pred_angular[4])(uint8_t *src, const uint8_t *top, const uint8_t *left, ptrdiff_t stride, int c_idx, int mode); + + void (*pred_filter_3tap[3])(uint8_t *filtered_left, uint8_t *filtered_top, + const uint8_t *left, const uint8_t *top, + int size); + void (*pred_filter_strong)(uint8_t *filtered_top, uint8_t *left, + const uint8_t *top); } HEVCPredContext; void ff_hevc_pred_init(HEVCPredContext *hpc, int bit_depth); diff --git a/libavcodec/hevc/pred_template.c b/libavcodec/hevc/pred_template.c index b4c2fcf506..09e6bc2e4d 100644 --- a/libavcodec/hevc/pred_template.c +++ b/libavcodec/hevc/pred_template.c @@ -25,6 +25,48 @@ #include "bit_depth_template.c" #include "pred.h" +static void FUNC(pred_filter_3tap)(uint8_t *_filtered_left, + uint8_t *_filtered_top, + const uint8_t *_left, + const uint8_t *_top, + int size) +{ + pixel *filtered_left = (pixel *)_filtered_left; + pixel *filtered_top = (pixel *)_filtered_top; + const pixel *left = (const pixel *)_left; + const pixel *top = (const pixel *)_top; + int i; + int n = 2 * size; + + filtered_left[n - 1] = left[n - 1]; + filtered_top[n - 1] = top[n - 1]; + for (i = n - 2; i >= 0; i--) + filtered_left[i] = (left[i + 1] + 2 * left[i] + left[i - 1] + 2) >> 2; + filtered_top[-1] = + filtered_left[-1] = (left[0] + 2 * left[-1] + top[0] + 2) >> 2; + for (i = n - 2; i >= 0; i--) + filtered_top[i] = (top[i + 1] + 2 * top[i] + top[i - 1] + 2) >> 2; +} + +static void FUNC(pred_filter_strong)(uint8_t *_filtered_top, + uint8_t *_left, + const uint8_t *_top) +{ + pixel *filtered_top = (pixel *)_filtered_top; + pixel *left = (pixel *)_left; + const pixel *top = (const pixel *)_top; + int i; + + filtered_top[-1] = top[-1]; + filtered_top[63] = top[63]; + for (i = 0; i < 63; i++) + filtered_top[i] = ((64 - (i + 1)) * top[-1] + + (i + 1) * top[63] + 32) >> 6; + for (i = 0; i < 63; i++) + left[i] = ((64 - (i + 1)) * left[-1] + + (i + 1) * left[63] + 32) >> 6; +} + #define POS(x, y) src[(x) + stride * (y)] static av_always_inline void FUNC(intra_pred)(HEVCLocalContext *lc, @@ -98,9 +140,11 @@ do { \ enum IntraPredMode mode = c_idx ? lc->tu.intra_pred_mode_c : lc->tu.intra_pred_mode; pixel4 a; - pixel left_array[2 * MAX_TB_SIZE + 1]; + // Extra 16 bytes of padding allow NEON 3-tap filter to do + // 32-byte loads without reading past the allocation. + pixel left_array[2 * MAX_TB_SIZE + 1 + 16]; pixel filtered_left_array[2 * MAX_TB_SIZE + 1]; - pixel top_array[2 * MAX_TB_SIZE + 1]; + pixel top_array[2 * MAX_TB_SIZE + 1 + 16]; pixel filtered_top_array[2 * MAX_TB_SIZE + 1]; pixel *left = left_array + 1; @@ -299,28 +343,14 @@ do { \ log2_size == 5 && FFABS(top[-1] + top[63] - 2 * top[31]) < threshold && FFABS(left[-1] + left[63] - 2 * left[31]) < threshold) { - // We can't just overwrite values in top because it could be - // a pointer into src - filtered_top[-1] = top[-1]; - filtered_top[63] = top[63]; - for (i = 0; i < 63; i++) - filtered_top[i] = ((64 - (i + 1)) * top[-1] + - (i + 1) * top[63] + 32) >> 6; - for (i = 0; i < 63; i++) - left[i] = ((64 - (i + 1)) * left[-1] + - (i + 1) * left[63] + 32) >> 6; + s->hpc.pred_filter_strong((uint8_t *)filtered_top, + (uint8_t *)left, + (const uint8_t *)top); top = filtered_top; } else { - filtered_left[2 * size - 1] = left[2 * size - 1]; - filtered_top[2 * size - 1] = top[2 * size - 1]; - for (i = 2 * size - 2; i >= 0; i--) - filtered_left[i] = (left[i + 1] + 2 * left[i] + - left[i - 1] + 2) >> 2; - filtered_top[-1] = - filtered_left[-1] = (left[0] + 2 * left[-1] + top[0] + 2) >> 2; - for (i = 2 * size - 2; i >= 0; i--) - filtered_top[i] = (top[i + 1] + 2 * top[i] + - top[i - 1] + 2) >> 2; + s->hpc.pred_filter_3tap[log2_size - 3]( + (uint8_t *)filtered_left, (uint8_t *)filtered_top, + (const uint8_t *)left, (const uint8_t *)top, size); left = filtered_left; top = filtered_top; } -- 2.52.0 From 7479a6bb67f0e670bb49048e97b068a883b4ed53 Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Thu, 2 Apr 2026 08:23:33 +0800 Subject: [PATCH 2/3] tests/checkasm: add hevc_pred filter_3tap and filter_strong tests Test 3-tap for 8x8/16x16/32x32 (both filtered_left and filtered_top outputs). Test strong smoothing for filtered_top and in-place left modification. Signed-off-by: Jun Zhao <[email protected]> --- tests/checkasm/hevc_pred.c | 129 +++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) diff --git a/tests/checkasm/hevc_pred.c b/tests/checkasm/hevc_pred.c index 7de9d23e60..fe98aab208 100644 --- a/tests/checkasm/hevc_pred.c +++ b/tests/checkasm/hevc_pred.c @@ -51,6 +51,16 @@ static const uint32_t pixel_mask[3] = { 0xffffffff, 0x01ff01ff, 0x03ff03ff }; } \ } while (0) +#define randomize_ref_buffers() \ + do { \ + uint32_t mask = pixel_mask[bit_depth - 8]; \ + for (int i = -4; i < PRED_SIZE; i += 4) { \ + uint32_t r = rnd() & mask; \ + AV_WN32A(top + i, r); \ + AV_WN32A(left + i, r); \ + } \ + } while (0) + static void check_pred_dc(HEVCPredContext *h, uint8_t *buf0, uint8_t *buf1, uint8_t *top, uint8_t *left, int bit_depth) @@ -190,6 +200,109 @@ static void check_pred_angular(HEVCPredContext *h, } } +static void check_pred_filter_3tap(HEVCPredContext *h, + uint8_t *top, uint8_t *left, int bit_depth) +{ + const char *const block_name[] = { "8x8", "16x16", "32x32" }; + const int block_size[] = { 8, 16, 32 }; + int i; + + /* 3-tap filter: out[i] = (in[i+1] + 2*in[i] + in[i-1] + 2) >> 2 + * Filters 2*size-1 samples (indices 0..2*size-2) plus corner [-1]. + * Output: filtered_left[-1..2*size-1] and filtered_top[-1..2*size-1] */ + declare_func(void, uint8_t *filtered_left, uint8_t *filtered_top, + const uint8_t *left, const uint8_t *top, int size); + + for (i = 0; i < 3; i++) { + int size = block_size[i]; + int n = 2 * size; + + if (check_func(h->pred_filter_3tap[i], + "hevc_pred_filter_3tap_%s_%d", + block_name[i], bit_depth)) { + /* Allocate output buffers with space for [-1] indexing. + * Need n+1 elements: indices [-1..n-1] = n+1 pixels. + * Use (n+1)*SIZEOF_PIXEL bytes starting at offset SIZEOF_PIXEL. */ + LOCAL_ALIGNED_32(uint8_t, fl_ref_buf, [PRED_SIZE + 16]); + LOCAL_ALIGNED_32(uint8_t, fl_new_buf, [PRED_SIZE + 16]); + LOCAL_ALIGNED_32(uint8_t, ft_ref_buf, [PRED_SIZE + 16]); + LOCAL_ALIGNED_32(uint8_t, ft_new_buf, [PRED_SIZE + 16]); + uint8_t *fl_ref = fl_ref_buf + 8; + uint8_t *fl_new = fl_new_buf + 8; + uint8_t *ft_ref = ft_ref_buf + 8; + uint8_t *ft_new = ft_new_buf + 8; + + randomize_ref_buffers(); + /* Clear output buffers so comparison is clean */ + memset(fl_ref_buf, 0, PRED_SIZE + 16); + memset(fl_new_buf, 0, PRED_SIZE + 16); + memset(ft_ref_buf, 0, PRED_SIZE + 16); + memset(ft_new_buf, 0, PRED_SIZE + 16); + + call_ref(fl_ref, ft_ref, left, top, size); + call_new(fl_new, ft_new, left, top, size); + + /* Compare filtered_left[-1..2*size-1] and filtered_top[-1..2*size-1] */ + if (memcmp(fl_ref - SIZEOF_PIXEL, fl_new - SIZEOF_PIXEL, + (n + 1) * SIZEOF_PIXEL)) + fail(); + if (memcmp(ft_ref - SIZEOF_PIXEL, ft_new - SIZEOF_PIXEL, + (n + 1) * SIZEOF_PIXEL)) + fail(); + + bench_new(fl_new, ft_new, left, top, size); + } + } +} + +static void check_pred_filter_strong(HEVCPredContext *h, + uint8_t *top, uint8_t *left, + int bit_depth) +{ + /* Strong intra smoothing: only 32x32 luma. + * Interpolates top into filtered_top[0..62], sets filtered_top[-1] and [63]. + * Modifies left[0..62] in-place. */ + declare_func(void, uint8_t *filtered_top, uint8_t *left, + const uint8_t *top); + + if (check_func(h->pred_filter_strong, + "hevc_pred_filter_strong_%d", bit_depth)) { + LOCAL_ALIGNED_32(uint8_t, ft_ref_buf, [PRED_SIZE + 16]); + LOCAL_ALIGNED_32(uint8_t, ft_new_buf, [PRED_SIZE + 16]); + LOCAL_ALIGNED_32(uint8_t, left_ref_buf, [PRED_SIZE + 16]); + LOCAL_ALIGNED_32(uint8_t, left_new_buf, [PRED_SIZE + 16]); + uint8_t *ft_ref = ft_ref_buf + 8; + uint8_t *ft_new = ft_new_buf + 8; + uint8_t *left_ref = left_ref_buf + 8; + uint8_t *left_new = left_new_buf + 8; + + randomize_ref_buffers(); + memset(ft_ref_buf, 0, PRED_SIZE + 16); + memset(ft_new_buf, 0, PRED_SIZE + 16); + + /* Copy left so both ref and new start with the same input + * (left is modified in-place) */ + memcpy(left_ref_buf, left - 8, PRED_SIZE + 16); + memcpy(left_new_buf, left - 8, PRED_SIZE + 16); + + call_ref(ft_ref, left_ref, top); + call_new(ft_new, left_new, top); + + /* Compare filtered_top[-1..63] = 65 pixels */ + if (memcmp(ft_ref - SIZEOF_PIXEL, ft_new - SIZEOF_PIXEL, + 65 * SIZEOF_PIXEL)) + fail(); + + /* Compare left[-1..63] = 65 pixels (left[-1] is unchanged, + * left[0..62] are modified, left[63] is unchanged) */ + if (memcmp(left_ref - SIZEOF_PIXEL, left_new - SIZEOF_PIXEL, + 65 * SIZEOF_PIXEL)) + fail(); + + bench_new(ft_new, left_new, top); + } +} + void checkasm_check_hevc_pred(void) { LOCAL_ALIGNED_32(uint8_t, buf0, [BUF_SIZE]); @@ -224,4 +337,20 @@ void checkasm_check_hevc_pred(void) check_pred_angular(&h, buf0, buf1, top, left, bit_depth); } report("pred_angular"); + + for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) { + HEVCPredContext h; + + ff_hevc_pred_init(&h, bit_depth); + check_pred_filter_3tap(&h, top, left, bit_depth); + } + report("pred_filter_3tap"); + + for (bit_depth = 8; bit_depth <= 10; bit_depth += 2) { + HEVCPredContext h; + + ff_hevc_pred_init(&h, bit_depth); + check_pred_filter_strong(&h, top, left, bit_depth); + } + report("pred_filter_strong"); } -- 2.52.0 From 36b3e060b9befa4698f70de9375b88190a6230e3 Mon Sep 17 00:00:00 2001 From: Jun Zhao <[email protected]> Date: Thu, 2 Apr 2026 08:23:48 +0800 Subject: [PATCH 3/3] lavc/hevc: add aarch64 NEON for reference sample filtering 3-tap [1,2,1]>>2: size-specialized entry points (8x8/16x16/32x32); 32-byte ld1 + ext sliding window + uaddl/ushll/rshrn. Overlap-last technique for tail avoids partial stores. Caller pads input arrays by 16 bytes to guarantee safe over-read. Strong smoothing (32x32): preloaded weight tables, umull/umlal with 16 samples per fully-unrolled block. checkasm --bench (Apple M4): filter_3tap_8x8_8_neon: 3.6x filter_3tap_16x16_8_neon: 2.5x filter_3tap_32x32_8_neon: 1.8x filter_strong_8_neon: 1.7x Signed-off-by: Jun Zhao <[email protected]> --- libavcodec/aarch64/hevcpred_init_aarch64.c | 23 ++ libavcodec/aarch64/hevcpred_neon.S | 243 +++++++++++++++++++++ 2 files changed, 266 insertions(+) diff --git a/libavcodec/aarch64/hevcpred_init_aarch64.c b/libavcodec/aarch64/hevcpred_init_aarch64.c index 55f3e2e731..41b967caa0 100644 --- a/libavcodec/aarch64/hevcpred_init_aarch64.c +++ b/libavcodec/aarch64/hevcpred_init_aarch64.c @@ -49,6 +49,24 @@ void ff_hevc_pred_planar_16x16_8_neon(uint8_t *src, const uint8_t *top, void ff_hevc_pred_planar_32x32_8_neon(uint8_t *src, const uint8_t *top, const uint8_t *left, ptrdiff_t stride); +// 3-tap reference sample filter +void ff_hevc_pred_filter_3tap_8x8_8_neon(uint8_t *filtered_left, + uint8_t *filtered_top, + const uint8_t *left, + const uint8_t *top, int size); +void ff_hevc_pred_filter_3tap_16x16_8_neon(uint8_t *filtered_left, + uint8_t *filtered_top, + const uint8_t *left, + const uint8_t *top, int size); +void ff_hevc_pred_filter_3tap_32x32_8_neon(uint8_t *filtered_left, + uint8_t *filtered_top, + const uint8_t *left, + const uint8_t *top, int size); + +// Strong intra smoothing +void ff_hevc_pred_filter_strong_8_neon(uint8_t *filtered_top, uint8_t *left, + const uint8_t *top); + static void pred_dc_neon(uint8_t *src, const uint8_t *top, const uint8_t *left, ptrdiff_t stride, int log2_size, int c_idx) @@ -84,5 +102,10 @@ av_cold void ff_hevc_pred_init_aarch64(HEVCPredContext *hpc, int bit_depth) hpc->pred_planar[1] = ff_hevc_pred_planar_8x8_8_neon; hpc->pred_planar[2] = ff_hevc_pred_planar_16x16_8_neon; hpc->pred_planar[3] = ff_hevc_pred_planar_32x32_8_neon; + + hpc->pred_filter_3tap[0] = ff_hevc_pred_filter_3tap_8x8_8_neon; + hpc->pred_filter_3tap[1] = ff_hevc_pred_filter_3tap_16x16_8_neon; + hpc->pred_filter_3tap[2] = ff_hevc_pred_filter_3tap_32x32_8_neon; + hpc->pred_filter_strong = ff_hevc_pred_filter_strong_8_neon; } } diff --git a/libavcodec/aarch64/hevcpred_neon.S b/libavcodec/aarch64/hevcpred_neon.S index 26d4c887ab..1ee0b403d4 100644 --- a/libavcodec/aarch64/hevcpred_neon.S +++ b/libavcodec/aarch64/hevcpred_neon.S @@ -835,3 +835,246 @@ const planar_weights_32, align=4 .byte 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 .byte 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32 endconst + +// ============================================================================= +// Reference Sample Filtering — 3-tap filter [1,2,1]>>2 +// ============================================================================= + +/* + * 3-tap filter: out[i] = (in[i-1] + 2*in[i] + in[i+1] + 2) >> 2 + * + * Signature: void ff_hevc_pred_filter_3tap_NxN_8_neon( + * uint8_t *filtered_left, // x0 + * uint8_t *filtered_top, // x1 + * const uint8_t *left, // x2 + * const uint8_t *top, // x3 + * int size) // w4 — (unused, size is hardcoded per entry point) + * + * All pointers point to index [0]; caller has adjusted for [-1] accessibility. + * + * NEON approach: load 32 bytes via ld1, use ext #1/#2 to form + * prev(v0)/curr(v2)/next(v3) → uaddl + ushll → rshrn (rounding). + * Caller pads input arrays by 16 bytes to guarantee safe 32-byte loads. + * Forward processing is safe since output goes to a separate buffer. + */ + +// Load 32 bytes from [x12], compute 16 filtered samples into v4.16b +.macro filter_3tap_compute + ld1 {v0.16b, v1.16b}, [x12] + ext v2.16b, v0.16b, v1.16b, #1 // curr + ext v3.16b, v0.16b, v1.16b, #2 // next + uaddl v4.8h, v0.8b, v3.8b // prev + next (lo) + uaddl2 v5.8h, v0.16b, v3.16b // prev + next (hi) + ushll v6.8h, v2.8b, #1 // 2*curr (lo) + ushll2 v7.8h, v2.16b, #1 // 2*curr (hi) + add v4.8h, v4.8h, v6.8h + add v5.8h, v5.8h, v7.8h + rshrn v4.8b, v4.8h, #2 // (sum+2)>>2 (lo) + rshrn2 v4.16b, v5.8h, #2 // (sum+2)>>2 (hi) +.endm + +// Filter one array. n_reg holds 2*size; filters n-1 samples, copies last. +// Caller guarantees 16 bytes of readable padding beyond in[n-1]. +// For total >= 16, overlap-last-16 avoids partial stores. +// For total < 16 (8x8), overlap-last-8 stores two 8-byte halves. +.macro filter_3tap_array x_out, x_in, n_reg + // Copy last element: out[n-1] = in[n-1] + sub x10, \n_reg, #1 + ldrb w11, [\x_in, x10] + strb w11, [\x_out, x10] + + sub x12, \x_in, #1 // x12 = load ptr (&in[-1]) + mov x13, \x_out // x13 = store ptr (&out[0]) + sub x14, \n_reg, #1 // x14 = total samples to filter + + cmp x14, #16 + b.lt 6f // total < 16: small path (8x8) + + // --- Main loop: 16 samples per iteration --- +1: filter_3tap_compute + st1 {v4.16b}, [x13], #16 + add x12, x12, #16 + sub x14, x14, #16 + cmp x14, #16 + b.ge 1b + + // --- Overlap last 16 samples --- + cbz x14, 9f + add x12, \x_in, \n_reg + sub x12, x12, #18 // &in[n-18] + add x13, \x_out, \n_reg + sub x13, x13, #17 // &out[n-17] + filter_3tap_compute + st1 {v4.16b}, [x13] + b 9f + + // --- Small path: total < 16 (8x8, total=15) --- + // Store as two overlapping 8-byte halves: out[0..7] + out[7..14] +6: filter_3tap_compute + str d4, [x13] // out[0..7] + ext v4.16b, v4.16b, v4.16b, #7 + str d4, [x13, #7] // out[7..14] +9: +.endm + +// Corner: filtered[-1] = (left[0] + 2*left[-1] + top[0] + 2) >> 2 +.macro filter_3tap_corner + ldrb w10, [x2] // left[0] + ldrb w11, [x2, #-1] // left[-1] + ldrb w12, [x3] // top[0] + add w10, w10, w12 // left[0] + top[0] + add w10, w10, w11, lsl #1 // + 2*left[-1] + add w10, w10, #2 // + 2 + lsr w10, w10, #2 // >> 2 + strb w10, [x0, #-1] // filtered_left[-1] + strb w10, [x1, #-1] // filtered_top[-1] +.endm + +function ff_hevc_pred_filter_3tap_8x8_8_neon, export=1 + mov x4, #16 // n = 2*8 + filter_3tap_array x0, x2, x4 + filter_3tap_corner + filter_3tap_array x1, x3, x4 + ret +endfunc + +function ff_hevc_pred_filter_3tap_16x16_8_neon, export=1 + mov x4, #32 // n = 2*16 + filter_3tap_array x0, x2, x4 + filter_3tap_corner + filter_3tap_array x1, x3, x4 + ret +endfunc + +function ff_hevc_pred_filter_3tap_32x32_8_neon, export=1 + mov x4, #64 // n = 2*32 + filter_3tap_array x0, x2, x4 + filter_3tap_corner + filter_3tap_array x1, x3, x4 + ret +endfunc + +.purgem filter_3tap_compute +.purgem filter_3tap_array +.purgem filter_3tap_corner + +// ============================================================================= +// Reference Sample Filtering — Strong Intra Smoothing +// ============================================================================= + +/* + * Strong intra smoothing (32x32 luma only): + * filtered_top[i] = ((64-(i+1))*top[-1] + (i+1)*top[63] + 32) >> 6 + * left[i] = ((64-(i+1))*left[-1] + (i+1)*left[63] + 32) >> 6 + * for i = 0..62 + * + * Also sets: filtered_top[-1] = top[-1], filtered_top[63] = top[63] + * + * Signature: void ff_hevc_pred_filter_strong_8_neon( + * uint8_t *filtered_top, // x0 + * uint8_t *left, // x1 + * const uint8_t *top) // x2 + * + * NEON approach: multiply 8-bit weights by 8-bit start/end values. + * weights_dec = {63,62,...,1} and weights_inc = {1,2,...,63} + * prod = dec*start + inc*end → rshrn #6 (rounding shift right) + * Preload all 64 weight bytes into v0-v7, process 16 samples per + * fully-unrolled block (4 blocks total, last stores 15 bytes). + */ + +// Precomputed weight tables in .rodata +const filter_strong_weights_inc, align=4 + .byte 1, 2, 3, 4, 5, 6, 7, 8 + .byte 9, 10, 11, 12, 13, 14, 15, 16 + .byte 17, 18, 19, 20, 21, 22, 23, 24 + .byte 25, 26, 27, 28, 29, 30, 31, 32 + .byte 33, 34, 35, 36, 37, 38, 39, 40 + .byte 41, 42, 43, 44, 45, 46, 47, 48 + .byte 49, 50, 51, 52, 53, 54, 55, 56 + .byte 57, 58, 59, 60, 61, 62, 63, 0 +endconst + +const filter_strong_weights_dec, align=4 + .byte 63, 62, 61, 60, 59, 58, 57, 56 + .byte 55, 54, 53, 52, 51, 50, 49, 48 + .byte 47, 46, 45, 44, 43, 42, 41, 40 + .byte 39, 38, 37, 36, 35, 34, 33, 32 + .byte 31, 30, 29, 28, 27, 26, 25, 24 + .byte 23, 22, 21, 20, 19, 18, 17, 16 + .byte 15, 14, 13, 12, 11, 10, 9, 8 + .byte 7, 6, 5, 4, 3, 2, 1, 0 +endconst + +// Macro: apply strong smoothing to one 63-element array +// x_out = output pointer (index 0), w_start = start pixel, w_end = end pixel +// Weights must be preloaded: v0-v3 = dec weights, v4-v7 = inc weights (16b each) +// Fully unrolled: 4 blocks of 16, last block stores only 15 bytes +.macro strong_smooth x_out, w_start, w_end + dup v16.16b, \w_start // broadcast start + dup v17.16b, \w_end // broadcast end + mov x14, \x_out + + // Block 0: indices 0-15 + umull v18.8h, v0.8b, v16.8b // dec_lo * start + umull2 v19.8h, v0.16b, v16.16b // dec_hi * start + umlal v18.8h, v4.8b, v17.8b // + inc_lo * end + umlal2 v19.8h, v4.16b, v17.16b // + inc_hi * end + rshrn v20.8b, v18.8h, #6 + rshrn2 v20.16b, v19.8h, #6 + st1 {v20.16b}, [x14], #16 + + // Block 1: indices 16-31 + umull v18.8h, v1.8b, v16.8b + umull2 v19.8h, v1.16b, v16.16b + umlal v18.8h, v5.8b, v17.8b + umlal2 v19.8h, v5.16b, v17.16b + rshrn v20.8b, v18.8h, #6 + rshrn2 v20.16b, v19.8h, #6 + st1 {v20.16b}, [x14], #16 + + // Block 2: indices 32-47 + umull v18.8h, v2.8b, v16.8b + umull2 v19.8h, v2.16b, v16.16b + umlal v18.8h, v6.8b, v17.8b + umlal2 v19.8h, v6.16b, v17.16b + rshrn v20.8b, v18.8h, #6 + rshrn2 v20.16b, v19.8h, #6 + st1 {v20.16b}, [x14], #16 + + // Block 3: indices 48-62 (store 15 bytes: 8+4+2+1) + umull v18.8h, v3.8b, v16.8b + umull2 v19.8h, v3.16b, v16.16b + umlal v18.8h, v7.8b, v17.8b + umlal2 v19.8h, v7.16b, v17.16b + rshrn v20.8b, v18.8h, #6 + rshrn2 v20.16b, v19.8h, #6 + st1 {v20.8b}, [x14], #8 // indices 48-55 + st1 {v20.s}[2], [x14], #4 // indices 56-59 + st1 {v20.h}[6], [x14], #2 // indices 60-61 + st1 {v20.b}[14], [x14] // index 62 +.endm + +function ff_hevc_pred_filter_strong_8_neon, export=1 + ldrb w10, [x2, #-1] // top[-1] + strb w10, [x0, #-1] // filtered_top[-1] + ldrb w11, [x2, #63] // top[63] + strb w11, [x0, #63] // filtered_top[63] + + // Preload all weight tables into registers (shared across both passes) + movrel x8, filter_strong_weights_dec + movrel x9, filter_strong_weights_inc + ld1 {v0.16b, v1.16b, v2.16b, v3.16b}, [x8] + ld1 {v4.16b, v5.16b, v6.16b, v7.16b}, [x9] + + // Smooth top → filtered_top[0..62] + strong_smooth x0, w10, w11 + + // Smooth left → left[0..62] in-place + ldrb w10, [x1, #-1] // left[-1] + ldrb w11, [x1, #63] // left[63] + strong_smooth x1, w10, w11 + + ret +endfunc + +.purgem strong_smooth -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
