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]

Reply via email to