[PR] WIP: avfilter/vf_overlay: add aarch64 NEON blend_row (yuv420) (PR #23633)

damitha via ffmpeg-devel <[email protected]>
Newsgroups gmane.comp.video.ffmpeg.devel
Message-ID <178270145772.59.15044114207618104733@29965ddac10e>
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.