PR #23633 opened by damitha
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23633
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23633.patch

Add neon .S implemention of vf_overlay.

# Summary of changes

Briefly describe what this PR does and why.

NEON:
 - vf_overlay.overlay_row_44 [OK]
 - vf_overlay.overlay_row_20 [OK]

overlay_row_44_c: 64.8 (1.00x)   overlay_row_44_neon: 29.1 (2.22x)
overlay_row_20_c: 96.9 (1.00x)   overlay_row_20_neon: 47.4 (2.05x)


<!--
If this PR requires new FATE test samples, attach them to the PR and
list their target paths below (relative to the fate-suite root).

Attached filenames must match the sample's filename:

```fate-samples
# e.g. vorbis/new-sample.ogg
```
-->



>From f952a2697d26190a44194811c531a597867f2f1a Mon Sep 17 00:00:00 2001
From: Damitha Gunawardena <[email protected]>
Date: Mon, 29 Jun 2026 12:49:20 +1000
Subject: [PATCH] avfilter/vf_overlay: add aarch64 NEON blend_row (yuv420)

Add neon .S implemention of vf_overlay.
---
 libavfilter/aarch64/Makefile          |   2 +
 libavfilter/aarch64/vf_overlay_init.c |  51 ++++++++
 libavfilter/aarch64/vf_overlay_neon.S |  96 ++++++++++++++
 libavfilter/vf_overlay.c              |   3 +
 libavfilter/vf_overlay.h              |   1 +
 tests/checkasm/Makefile               |   1 +
 tests/checkasm/checkasm.c             |   3 +
 tests/checkasm/checkasm.h             |   1 +
 tests/checkasm/vf_overlay.c           | 172 ++++++++++++++++++++++++++
 9 files changed, 330 insertions(+)
 create mode 100644 libavfilter/aarch64/vf_overlay_init.c
 create mode 100644 libavfilter/aarch64/vf_overlay_neon.S
 create mode 100644 tests/checkasm/vf_overlay.c

diff --git a/libavfilter/aarch64/Makefile b/libavfilter/aarch64/Makefile
index c7b7e18467..f93c46d242 100644
--- a/libavfilter/aarch64/Makefile
+++ b/libavfilter/aarch64/Makefile
@@ -1,7 +1,9 @@
 OBJS-$(CONFIG_BWDIF_FILTER)                  += aarch64/vf_bwdif_init_aarch64.o
 OBJS-$(CONFIG_COLORDETECT_FILTER)            += aarch64/vf_colordetect_init.o
 OBJS-$(CONFIG_NLMEANS_FILTER)                += aarch64/vf_nlmeans_init.o
+OBJS-$(CONFIG_OVERLAY_FILTER)                += aarch64/vf_overlay_init.o
 
 NEON-OBJS-$(CONFIG_BWDIF_FILTER)             += aarch64/vf_bwdif_neon.o
 NEON-OBJS-$(CONFIG_COLORDETECT_FILTER)       += aarch64/vf_colordetect_neon.o
 NEON-OBJS-$(CONFIG_NLMEANS_FILTER)           += aarch64/vf_nlmeans_neon.o
+NEON-OBJS-$(CONFIG_OVERLAY_FILTER)           += aarch64/vf_overlay_neon.o
diff --git a/libavfilter/aarch64/vf_overlay_init.c 
b/libavfilter/aarch64/vf_overlay_init.c
new file mode 100644
index 0000000000..1e7fb14fcc
--- /dev/null
+++ b/libavfilter/aarch64/vf_overlay_init.c
@@ -0,0 +1,51 @@
+/*
+ * aarch64 NEON optimizations for the overlay filter (dispatch).
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU Lesser General Public License as published by the
+ * Free Software Foundation; either version 2.1, or (at your option) any
+ * later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful, but WITHOUT ANY
+ * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+#include "libavutil/attributes.h"
+#include "libavutil/cpu.h"
+#include "libavutil/aarch64/cpu.h"
+#include "libavutil/log.h"
+#include "libavfilter/vf_overlay.h"
+
+int ff_overlay_row_44_neon(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                           int w, ptrdiff_t alinesize);
+int ff_overlay_row_20_neon(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                           int w, ptrdiff_t alinesize);
+
+av_cold void ff_overlay_init_aarch64(AVFilterContext *ctx)
+{
+    OverlayContext *s = ctx->priv;
+    const AVFilterLink *main    = ctx->inputs[0];
+    const AVFilterLink *overlay = ctx->inputs[1];
+    int cpu_flags = av_get_cpu_flags();
+
+    if (have_neon(cpu_flags) &&
+        main->format == AV_PIX_FMT_YUV420P &&
+        s->format == OVERLAY_FORMAT_YUV420 &&
+        overlay->alpha_mode != AVALPHA_MODE_PREMULTIPLIED &&
+        !s->main_has_alpha) {
+        s->blend_row[0] = ff_overlay_row_44_neon;
+        s->blend_row[1] = ff_overlay_row_20_neon;
+        s->blend_row[2] = ff_overlay_row_20_neon;
+        av_log(ctx, AV_LOG_VERBOSE, "overlay: aarch64 NEON blend_row enabled 
(yuv420).\n");
+    }
+}
diff --git a/libavfilter/aarch64/vf_overlay_neon.S 
b/libavfilter/aarch64/vf_overlay_neon.S
new file mode 100644
index 0000000000..a488ffa6f6
--- /dev/null
+++ b/libavfilter/aarch64/vf_overlay_neon.S
@@ -0,0 +1,96 @@
+/*
+ * aarch64 NEON overlay blend kernels.
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "libavutil/aarch64/asm.S"
+
+/* d = FAST_DIV255(d*(255-a) + s*a),  FAST_DIV255(x) = ((x+128)*257)>>16
+ * uses umull/umlal straight from bytes and uzp2 for the >>16.
+ *
+ * int ff_overlay_row_44_neon(uint8_t *d, uint8_t *da, uint8_t *s,
+ *                            uint8_t *a, int w, ptrdiff_t alinesize)
+ *   x0=d  x1=da(unused)  x2=s  x3=a  w4=w  x5=alinesize(unused)
+ */
+function ff_overlay_row_44_neon, export=1
+        mov             w6,  #0
+        cmp             w4,  #8
+        b.lt            2f
+        movi            v6.8b,  #0xff           // 255
+        movi            v5.8h,  #0x80           // 128
+        mov             w8,  #257
+        dup             v7.8h,  w8
+        sub             w7,  w4, #8             // process while x <= w-8
+1:      ldr             d0, [x0, x6]            // d
+        ldr             d1, [x2, x6]            // s
+        ldr             d2, [x3, x6]            // a
+        sub             v3.8b,  v6.8b,  v2.8b   // 255 - a
+        umull           v4.8h,  v0.8b,  v3.8b   // d*(255-a)
+        umlal           v4.8h,  v1.8b,  v2.8b   // + s*a
+        add             v4.8h,  v4.8h,  v5.8h   // + 128
+        umull           v16.4s, v4.4h,  v7.h[0] // *257 (lo)
+        umull2          v17.4s, v4.8h,  v7.h[0] // *257 (hi)
+        uzp2            v4.8h,  v16.8h, v17.8h  // high 16 of each = >>16
+        uqxtn           v4.8b,  v4.8h
+        str             d4, [x0, x6]
+        add             x6, x6, #8
+        cmp             w6, w7
+        b.le            1b
+2:      mov             w0, w6
+        ret
+endfunc
+
+/* chroma 4:2:0: average a 2x2 alpha block per pixel; leave the right-edge
+ * column to the scalar remainder loop (process while x+8 < w).
+ * Same args; x5 = alpha linesize (used for the 2nd alpha row).
+ */
+function ff_overlay_row_20_neon, export=1
+        mov             w6,  #0
+        cmp             w4,  #9
+        b.lt            2f
+        movi            v6.8b,  #0xff
+        movi            v5.8h,  #0x80
+        mov             w8,  #257
+        dup             v7.8h,  w8
+        sub             w7,  w4, #9
+1:      add             x9,  x3, x6, lsl #1     // a + 2*x (row0)
+        ld1             {v18.16b}, [x9]
+        add             x10, x9, x5             // row1 (+alinesize)
+        ld1             {v19.16b}, [x10]
+        uaddlp          v18.8h, v18.16b         // a[2i]+a[2i+1]
+        uaddlp          v19.8h, v19.16b
+        add             v20.8h, v18.8h, v19.8h  // 2x2 sum
+        ushr            v20.8h, v20.8h, #2      // /4 = avg alpha
+        xtn             v2.8b,  v20.8h          // back to bytes
+        ldr             d0, [x0, x6]            // d
+        ldr             d1, [x2, x6]            // s
+        sub             v3.8b,  v6.8b,  v2.8b
+        umull           v4.8h,  v0.8b,  v3.8b
+        umlal           v4.8h,  v1.8b,  v2.8b
+        add             v4.8h,  v4.8h,  v5.8h
+        umull           v16.4s, v4.4h,  v7.h[0]
+        umull2          v17.4s, v4.8h,  v7.h[0]
+        uzp2            v4.8h,  v16.8h, v17.8h
+        uqxtn           v4.8b,  v4.8h
+        str             d4, [x0, x6]
+        add             x6, x6, #8
+        cmp             w6, w7
+        b.le            1b
+2:      mov             w0, w6
+        ret
+endfunc
diff --git a/libavfilter/vf_overlay.c b/libavfilter/vf_overlay.c
index d627db0889..f56e380b82 100644
--- a/libavfilter/vf_overlay.c
+++ b/libavfilter/vf_overlay.c
@@ -835,6 +835,9 @@ static int init_slice_fn(AVFilterContext *ctx)
         break;
     }
 
+#if ARCH_AARCH64 && HAVE_NEON
+    ff_overlay_init_aarch64(ctx);
+#endif
 #if ARCH_X86 && HAVE_X86ASM
     ff_overlay_init_x86(ctx);
 #endif
diff --git a/libavfilter/vf_overlay.h b/libavfilter/vf_overlay.h
index 22e84362b7..44931b46fd 100644
--- a/libavfilter/vf_overlay.h
+++ b/libavfilter/vf_overlay.h
@@ -83,5 +83,6 @@ typedef struct OverlayContext {
 } OverlayContext;
 
 void ff_overlay_init_x86(AVFilterContext *ctx);
+void ff_overlay_init_aarch64(AVFilterContext *ctx);
 
 #endif /* AVFILTER_OVERLAY_H */
diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile
index bf2d6b7ec2..d7dc00ee32 100644
--- a/tests/checkasm/Makefile
+++ b/tests/checkasm/Makefile
@@ -77,6 +77,7 @@ AVFILTEROBJS-$(CONFIG_HFLIP_FILTER)      += vf_hflip.o
 AVFILTEROBJS-$(CONFIG_IDET_FILTER)       += vf_idet.o
 AVFILTEROBJS-$(CONFIG_THRESHOLD_FILTER)  += vf_threshold.o
 AVFILTEROBJS-$(CONFIG_NLMEANS_FILTER)    += vf_nlmeans.o
+AVFILTEROBJS-$(CONFIG_OVERLAY_FILTER)    += vf_overlay.o
 AVFILTEROBJS-$(CONFIG_SOBEL_FILTER)      += vf_convolution.o
 
 CHECKASMOBJS-$(CONFIG_AVFILTER) += $(AVFILTEROBJS-yes)
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 8629f26788..e15e459532 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -339,6 +339,9 @@ static const struct {
     #if CONFIG_NLMEANS_FILTER
         { "vf_nlmeans", checkasm_check_nlmeans },
     #endif
+    #if CONFIG_OVERLAY_FILTER
+        { "vf_overlay", checkasm_check_vf_overlay },
+    #endif
     #if CONFIG_THRESHOLD_FILTER
         { "vf_threshold", checkasm_check_vf_threshold },
     #endif
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 43c158e97b..dc770bfa91 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -132,6 +132,7 @@ void checkasm_check_mpegvideo_unquantize(void);
 void checkasm_check_mpegvideoencdsp(void);
 void checkasm_check_nlmeans(void);
 void checkasm_check_opusdsp(void);
+void checkasm_check_vf_overlay(void);
 void checkasm_check_pixblockdsp(void);
 void checkasm_check_pixelutils(void);
 void checkasm_check_png(void);
diff --git a/tests/checkasm/vf_overlay.c b/tests/checkasm/vf_overlay.c
new file mode 100644
index 0000000000..7c6f699c4e
--- /dev/null
+++ b/tests/checkasm/vf_overlay.c
@@ -0,0 +1,172 @@
+/*
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with FFmpeg; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include "checkasm.h"
+#include "libavutil/cpu.h"
+#include "libavutil/mem_internal.h"
+
+#if ARCH_AARCH64
+#include "libavutil/aarch64/cpu.h"
+#endif
+#if ARCH_X86
+#include "libavutil/x86/cpu.h"
+#endif
+
+/* blend_row signature, see OverlayContext::blend_row in 
libavfilter/vf_overlay.h */
+typedef int (*blend_row)(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                         int w, ptrdiff_t alinesize);
+
+#if ARCH_AARCH64
+int ff_overlay_row_44_neon(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                           int w, ptrdiff_t alinesize);
+int ff_overlay_row_20_neon(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                           int w, ptrdiff_t alinesize);
+#endif
+#if ARCH_X86 && HAVE_X86ASM
+int ff_overlay_row_44_sse4(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                           int w, ptrdiff_t alinesize);
+int ff_overlay_row_20_sse4(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                           int w, ptrdiff_t alinesize);
+#endif
+
+#define FAST_DIV255(x) ((((x) + 128) * 257) >> 16)
+
+#define WIDTH  359
+#define PAD    32
+/* two alpha samples per output pixel, two rows, plus slack for the SIMD 
overread */
+#define ASTRIDE (2 * WIDTH + PAD)
+
+/* straight-alpha blend, one alpha sample per pixel (Y / 4:4:4 plane) */
+static int ref_overlay_row_44(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                              int w, ptrdiff_t alinesize)
+{
+    for (int x = 0; x < w; x++)
+        d[x] = FAST_DIV255(d[x] * (255 - a[x]) + s[x] * a[x]);
+    return w;
+}
+
+/* straight-alpha blend, 2x2 averaged alpha per pixel (4:2:0 chroma plane) */
+static int ref_overlay_row_20(uint8_t *d, uint8_t *da, uint8_t *s, uint8_t *a,
+                              int w, ptrdiff_t alinesize)
+{
+    for (int x = 0; x < w; x++) {
+        int alpha = (a[2 * x] + a[2 * x + 1] +
+                     a[2 * x + alinesize] + a[2 * x + 1 + alinesize]) >> 2;
+        d[x] = FAST_DIV255(d[x] * (255 - alpha) + s[x] * alpha);
+    }
+    return w;
+}
+
+static blend_row get_row_44(void)
+{
+    int flags = av_get_cpu_flags();
+#if ARCH_AARCH64
+    if (have_neon(flags))
+        return ff_overlay_row_44_neon;
+#elif ARCH_X86 && HAVE_X86ASM
+    if (EXTERNAL_SSE4(flags))
+        return ff_overlay_row_44_sse4;
+#endif
+    return ref_overlay_row_44;
+}
+
+static blend_row get_row_20(void)
+{
+    int flags = av_get_cpu_flags();
+#if ARCH_AARCH64
+    if (have_neon(flags))
+        return ff_overlay_row_20_neon;
+#elif ARCH_X86 && HAVE_X86ASM
+    if (EXTERNAL_SSE4(flags))
+        return ff_overlay_row_20_sse4;
+#endif
+    return ref_overlay_row_20;
+}
+
+static void check_row_44(void)
+{
+    LOCAL_ALIGNED_32(uint8_t, d0,      [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, src,     [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, alpha,   [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, dst_ref, [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, dst_new, [WIDTH + PAD]);
+
+    declare_func(int, uint8_t *, uint8_t *, uint8_t *, uint8_t *, int, 
ptrdiff_t);
+
+    for (int i = 0; i < WIDTH + PAD; i++) {
+        d0[i]    = rnd() & 0xff;
+        src[i]   = rnd() & 0xff;
+        alpha[i] = rnd() & 0xff;
+    }
+
+    if (check_func(get_row_44(), "overlay_row_44")) {
+        for (int w = 1; w <= WIDTH; w++) {
+            int cref, cnew;
+            memcpy(dst_ref, d0, WIDTH + PAD);
+            memcpy(dst_new, d0, WIDTH + PAD);
+            cref = call_ref(dst_ref, NULL, src, alpha, w, 0);
+            cnew = call_new(dst_new, NULL, src, alpha, w, 0);
+            if (cnew > cref || memcmp(dst_ref, dst_new, cnew))
+                fail();
+        }
+        memcpy(dst_new, d0, WIDTH + PAD);
+        bench_new(dst_new, NULL, src, alpha, WIDTH, 0);
+    }
+}
+
+static void check_row_20(void)
+{
+    LOCAL_ALIGNED_32(uint8_t, d0,      [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, src,     [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, alpha,   [2 * ASTRIDE]);
+    LOCAL_ALIGNED_32(uint8_t, dst_ref, [WIDTH + PAD]);
+    LOCAL_ALIGNED_32(uint8_t, dst_new, [WIDTH + PAD]);
+
+    declare_func(int, uint8_t *, uint8_t *, uint8_t *, uint8_t *, int, 
ptrdiff_t);
+
+    for (int i = 0; i < WIDTH + PAD; i++) {
+        d0[i]  = rnd() & 0xff;
+        src[i] = rnd() & 0xff;
+    }
+    for (int i = 0; i < 2 * ASTRIDE; i++)
+        alpha[i] = rnd() & 0xff;
+
+    if (check_func(get_row_20(), "overlay_row_20")) {
+        for (int w = 1; w <= WIDTH; w++) {
+            int cref, cnew;
+            memcpy(dst_ref, d0, WIDTH + PAD);
+            memcpy(dst_new, d0, WIDTH + PAD);
+            cref = call_ref(dst_ref, NULL, src, alpha, w, ASTRIDE);
+            cnew = call_new(dst_new, NULL, src, alpha, w, ASTRIDE);
+            if (cnew > cref || memcmp(dst_ref, dst_new, cnew))
+                fail();
+        }
+        memcpy(dst_new, d0, WIDTH + PAD);
+        bench_new(dst_new, NULL, src, alpha, WIDTH, ASTRIDE);
+    }
+}
+
+void checkasm_check_vf_overlay(void)
+{
+    check_row_44();
+    report("overlay_row_44");
+
+    check_row_20();
+    report("overlay_row_20");
+}
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to