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

This adds a checkasm test for VVC intra prediction DSP functions: dc, planar, 
mip and angular. Block sizes from 4x4 to 64x64 including non-square are tested. 
Angular prediction covers all standard modes with ref_idx 0-2. MIP covers all 
valid mode_id values per size_id, both transposed and non-transposed.

Chroma coverage is left todo.


>From f9189555f0b150b8678dbbb8256f132e26fa51bc Mon Sep 17 00:00:00 2001
From: Hankang Li <[email protected]>
Date: Mon, 13 Apr 2026 08:34:18 -0700
Subject: [PATCH] tests/checkasm: Add checkasm test for vvc intra prediction

---
 tests/checkasm/Makefile   |   2 +-
 tests/checkasm/checkasm.c |   1 +
 tests/checkasm/checkasm.h |   1 +
 tests/checkasm/vvc_pred.c | 376 ++++++++++++++++++++++++++++++++++++++
 tests/fate/checkasm.mak   |   1 +
 5 files changed, 380 insertions(+), 1 deletion(-)
 create mode 100644 tests/checkasm/vvc_pred.c

diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile
index bf2d6b7ec2..2ff5bd5383 100644
--- a/tests/checkasm/Makefile
+++ b/tests/checkasm/Makefile
@@ -58,7 +58,7 @@ AVCODECOBJS-$(CONFIG_V210_ENCODER)      += v210enc.o
 AVCODECOBJS-$(CONFIG_VORBIS_DECODER)    += vorbisdsp.o
 AVCODECOBJS-$(CONFIG_VP6_DECODER)       += vp6dsp.o
 AVCODECOBJS-$(CONFIG_VP9_DECODER)       += vp9dsp.o
-AVCODECOBJS-$(CONFIG_VVC_DECODER)       += vvc_alf.o vvc_mc.o vvc_sao.o
+AVCODECOBJS-$(CONFIG_VVC_DECODER)       += vvc_alf.o vvc_mc.o vvc_sao.o 
vvc_pred.o
 
 CHECKASMOBJS-$(CONFIG_AVCODEC)          += $(AVCODECOBJS-yes)
 
diff --git a/tests/checkasm/checkasm.c b/tests/checkasm/checkasm.c
index 8629f26788..3d67201a22 100644
--- a/tests/checkasm/checkasm.c
+++ b/tests/checkasm/checkasm.c
@@ -297,6 +297,7 @@ static const struct {
         { "vvc_alf", checkasm_check_vvc_alf },
         { "vvc_mc",  checkasm_check_vvc_mc  },
         { "vvc_sao", checkasm_check_vvc_sao },
+        { "vvc_pred", checkasm_check_vvc_pred },
     #endif
 #endif
 #if CONFIG_AVFILTER
diff --git a/tests/checkasm/checkasm.h b/tests/checkasm/checkasm.h
index 43c158e97b..cfae1fbb5d 100644
--- a/tests/checkasm/checkasm.h
+++ b/tests/checkasm/checkasm.h
@@ -176,6 +176,7 @@ void checkasm_check_vorbisdsp(void);
 void checkasm_check_vvc_alf(void);
 void checkasm_check_vvc_mc(void);
 void checkasm_check_vvc_sao(void);
+void checkasm_check_vvc_pred(void);
 
 struct CheckasmPerf;
 
diff --git a/tests/checkasm/vvc_pred.c b/tests/checkasm/vvc_pred.c
new file mode 100644
index 0000000000..472b29415c
--- /dev/null
+++ b/tests/checkasm/vvc_pred.c
@@ -0,0 +1,376 @@
+/*
+ * 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 "libavcodec/vvc/dsp.h"
+#include "libavcodec/vvc/intra.h"
+#include "libavutil/intreadwrite.h"
+#include "libavutil/mem_internal.h"
+
+static const uint32_t pixel_mask[3] = { 0xffffffff, 0x03ff03ff, 0x0fff0fff };
+static const int block_size[] = { 4, 8, 16, 32, 64 };
+
+#define SIZEOF_PIXEL ((bit_depth + 7) / 8)
+#define PIXEL_STRIDE (MAX_TB_SIZE * 2)                  // 2 * max transform 
size
+#define BUF_SIZE     (2 * PIXEL_STRIDE * PIXEL_STRIDE)  // Enough for 64x64 
with stride=128
+#define REF_SIZE     (2 * (6 * MAX_TB_SIZE + 5))        // 2 * IntraEdgeParams 
reference arrays size
+#define REF_OFFSET   (MAX_TB_SIZE + 3)                  // Same as offset from 
prepare_intra_edge_params
+
+#define randomize_buffers()                                                 \
+    do {                                                                    \
+        uint32_t mask = pixel_mask[(bit_depth - 8) >> 1];                   \
+        for (int i = 0; i < BUF_SIZE; i += 4) {                             \
+            uint32_t r = rnd() & mask;                                      \
+            AV_WN32A(buf0 + i, r);                                          \
+            AV_WN32A(buf1 + i, r);                                          \
+        }                                                                   \
+        /* Make sure reference buffer writes don't go out of bounds */      \
+        for (int i = -(REF_OFFSET * SIZEOF_PIXEL);                          \
+             i < REF_SIZE - REF_OFFSET * SIZEOF_PIXEL; i += 4) {            \
+            uint32_t r = rnd() & mask;                                      \
+            AV_WN32A(top + i, r);                                           \
+            AV_WN32A(left + i, r);                                          \
+        }                                                                   \
+    } while (0)
+
+// From prepare_intra_edge_params in avcodec/vvc/intra_template.c
+static int derive_filter_flag(const int mode, const int ref_idx, const int w, 
const int h)
+{
+    if (ff_vvc_ref_filter_flag_derive(mode) || ref_idx)
+        return 0;
+    const int min_dist_ver_hor = FFMIN(abs(mode - 50), abs(mode - 18));
+    const int intra_hor_ver_dist_thres[] = {24, 14, 2, 0, 0};
+    const int ntbs = (av_log2(w) + av_log2(h)) >> 1;
+    return min_dist_ver_hor > intra_hor_ver_dist_thres[ntbs - 2];
+}
+
+static void check_pred_dc(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    int log2_h, log2_w;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_top,
+                 const uint8_t *_left, const int w, const int h, const 
ptrdiff_t stride);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+
+            if (check_func(c->intra.pred_dc, "vvc_pred_dc_%dx%d_%d",
+                           w, h, bit_depth)) {
+                randomize_buffers();
+                call_ref(buf0, top, left, w, h, stride);
+                call_new(buf1, top, left, w, h, stride);
+                if (memcmp(buf0, buf1, BUF_SIZE))
+                    fail();
+                if (w == h)
+                    bench_new(buf1, top, left, w, h, stride);
+            }
+        }
+    }
+
+    report("pred_dc");
+}
+
+static void check_pred_planar(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    int log2_h, log2_w;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_top,
+                 const uint8_t *_left, const int w, const int h, const 
ptrdiff_t stride);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+
+            if (check_func(c->intra.pred_planar, "vvc_pred_planar_%dx%d_%d",
+                           w, h, bit_depth)) {
+                randomize_buffers();
+                call_ref(buf0, top, left, w, h, stride);
+                call_new(buf1, top, left, w, h, stride);
+                if (memcmp(buf0, buf1, BUF_SIZE))
+                    fail();
+                if (w == h)
+                    bench_new(buf1, top, left, w, h, stride);
+            }
+        }
+    }
+
+    report("pred_planar");
+}
+
+static void check_pred_v(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    int log2_h, log2_w;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_top,
+                 const int w, const int h, const ptrdiff_t stride);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+
+            if (check_func(c->intra.pred_v, "vvc_pred_v_%dx%d_%d",
+                           w, h, bit_depth)) {
+                randomize_buffers();
+                call_ref(buf0, top, w, h, stride);
+                call_new(buf1, top, w, h, stride);
+                if (memcmp(buf0, buf1, BUF_SIZE))
+                    fail();
+                if (w == h)
+                    bench_new(buf1, top, w, h, stride);
+            }
+        }
+    }
+
+    report("pred_v");
+}
+
+static void check_pred_h(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    int log2_h, log2_w;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_left,
+                 const int w, const int h, const ptrdiff_t stride);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+
+            if (check_func(c->intra.pred_h, "vvc_pred_h_%dx%d_%d",
+                           w, h, bit_depth)) {
+                randomize_buffers();
+                call_ref(buf0, left, w, h, stride);
+                call_new(buf1, left, w, h, stride);
+                if (memcmp(buf0, buf1, BUF_SIZE))
+                    fail();
+                if (w == h)
+                    bench_new(buf1, left, w, h, stride);
+            }
+        }
+    }
+
+    report("pred_h");
+}
+
+static void check_pred_mip(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    static const int mip_modes[] = {16, 8, 6};
+    int log2_h, log2_w, mode_id;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_top,
+                 const uint8_t *_left, const int w, const int h,
+                 const ptrdiff_t stride, int mode_id, int is_transposed);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+            int size_id = ff_vvc_get_mip_size_id(w, h);
+
+            for (mode_id = 0; mode_id < mip_modes[size_id]; mode_id++) {
+                if (check_func(c->intra.pred_mip, 
"vvc_pred_mip_%dx%d_mode%d_%d",
+                               w, h, mode_id, bit_depth)) {
+                    randomize_buffers();
+                    call_ref(buf0, top, left, w, h, stride, mode_id, 0);
+                    call_new(buf1, top, left, w, h, stride, mode_id, 0);
+                    if (memcmp(buf0, buf1, BUF_SIZE))
+                        fail();
+
+                    /* Test transposed */
+                    randomize_buffers();
+                    call_ref(buf0, top, left, w, h, stride, mode_id, 1);
+                    call_new(buf1, top, left, w, h, stride, mode_id, 1);
+                    if (memcmp(buf0, buf1, BUF_SIZE))
+                        fail();
+
+                    if (w == h)
+                        bench_new(buf1, top, left, w, h, stride, mode_id, 0);
+                }
+            }
+        }
+    }
+
+    report("pred_mip");
+}
+
+static void check_pred_angular_v(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    // Set modes to bench
+    static const int bench_modes[] = { INTRA_DIAG, 42, 58, INTRA_VDIAG };
+    int log2_h, log2_w, mode, ref_idx;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_top, const uint8_t 
*_left,
+                 const int w, const int h, const ptrdiff_t stride, const int 
c_idx,
+                 const int mode, const int ref_idx, const int filter_flag,
+                 const int need_pdpc);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+
+            for (mode = INTRA_DIAG; mode <= INTRA_VDIAG; mode++) {
+                // Skip vertical (pred_v)
+                if (mode == INTRA_VERT)
+                    continue;
+
+                const char *mode_category;
+                if (mode == INTRA_DIAG)
+                    mode_category = "Diag";
+                else if (mode == INTRA_VDIAG)
+                    mode_category = "Vdiag";
+                else if (mode < INTRA_VERT)
+                    mode_category = "Vneg";
+                else if (mode > INTRA_VERT)
+                    mode_category = "Vpos";
+
+                for (ref_idx = 0; ref_idx <= 2; ref_idx++) {
+                    if (check_func(c->intra.pred_angular_v, 
"vvc_pred_angular_v_%dx%d_%s_mode%d_ref%d_%d",
+                                   w, h, mode_category, mode, ref_idx, 
bit_depth)) {
+                        int filter_flag = derive_filter_flag(mode, ref_idx, w, 
h);
+                        int need_pdpc   = ff_vvc_need_pdpc(w, h, 0, mode, 
ref_idx);
+
+                        randomize_buffers();
+                        call_ref(buf0, top, left, w, h, stride, 0, mode, 
ref_idx, filter_flag, need_pdpc);
+                        call_new(buf1, top, left, w, h, stride, 0, mode, 
ref_idx, filter_flag, need_pdpc);
+                        if (memcmp(buf0, buf1, BUF_SIZE))
+                            fail();
+                        for (int i = 0; i < FF_ARRAY_ELEMS(bench_modes); i++) {
+                            if (mode == bench_modes[i] && w == h && ref_idx == 
0)
+                                bench_new(buf1, top, left, w, h, stride, 0, 
mode, ref_idx, filter_flag, need_pdpc);
+                        }
+                    }
+                }
+                // TODO: Add c_idx=1 (chroma)
+            }
+        }
+    }
+    report("pred_angular_v");
+}
+
+static void check_pred_angular_h(VVCDSPContext *c,
+                          uint8_t *buf0, uint8_t *buf1,
+                          uint8_t *top, uint8_t *left, int bit_depth)
+{
+    // Set modes to bench
+    static const int bench_modes[] = { 2, 10, 26 };
+    int log2_h, log2_w, mode, ref_idx;
+
+    declare_func(void, uint8_t *_src, const uint8_t *_top, const uint8_t 
*_left,
+                 const int w, const int h, const ptrdiff_t stride, const int 
c_idx,
+                 const int mode, const int ref_idx, const int filter_flag,
+                 const int need_pdpc);
+
+    /* Test all sizes 4x4 up to 64x64, including non-square */
+    for (log2_w = 2; log2_w <= 6; log2_w++) {
+        for (log2_h = 2; log2_h <= 6; log2_h++) {
+            int w = block_size[log2_w - 2];
+            int h = block_size[log2_h - 2];
+            ptrdiff_t stride = PIXEL_STRIDE * SIZEOF_PIXEL;
+
+            for (mode = 2; mode < INTRA_DIAG; mode++) {
+                // Skip horizontal (pred_h)
+                if (mode == INTRA_HORZ)
+                    continue;
+
+                const char *mode_category;
+                if (mode == 2)
+                    mode_category = "Hdiag";
+                else if (mode < INTRA_HORZ)
+                    mode_category = "Hpos";
+                else if (mode > INTRA_HORZ)
+                    mode_category = "Hneg";
+
+                for (ref_idx = 0; ref_idx <= 2; ref_idx++) {
+                    if (check_func(c->intra.pred_angular_h, 
"vvc_pred_angular_h_%dx%d_%s_mode%d_ref%d_%d",
+                                   w, h, mode_category, mode, ref_idx, 
bit_depth)) {
+                        int filter_flag = derive_filter_flag(mode, ref_idx, w, 
h);
+                        int need_pdpc   = ff_vvc_need_pdpc(w, h, 0, mode, 
ref_idx);
+
+                        randomize_buffers();
+                        call_ref(buf0, top, left, w, h, stride, 0, mode, 
ref_idx, filter_flag, need_pdpc);
+                        call_new(buf1, top, left, w, h, stride, 0, mode, 
ref_idx, filter_flag, need_pdpc);
+                        if (memcmp(buf0, buf1, BUF_SIZE))
+                            fail();
+                        for (int i = 0; i < FF_ARRAY_ELEMS(bench_modes); i++) {
+                            if (mode == bench_modes[i] && w == h && ref_idx == 
0)
+                                bench_new(buf1, top, left, w, h, stride, 0, 
mode, ref_idx, filter_flag, need_pdpc);
+                        }
+                    }
+                }
+                // TODO: Add c_idx=1 (chroma)
+            }
+        }
+    }
+    report("pred_angular_h");
+}
+
+void checkasm_check_vvc_pred(void)
+{
+    LOCAL_ALIGNED_32(uint8_t, buf0, [BUF_SIZE]);
+    LOCAL_ALIGNED_32(uint8_t, buf1, [BUF_SIZE]);
+    LOCAL_ALIGNED_32(uint8_t, top_buf, [REF_SIZE + 16]);
+    LOCAL_ALIGNED_32(uint8_t, left_buf, [REF_SIZE + 16]);
+
+    int bit_depth;
+
+    for (bit_depth = 8; bit_depth <= 12; bit_depth += 2) {
+        VVCDSPContext c;
+
+        uint8_t *top  = top_buf  + REF_OFFSET * SIZEOF_PIXEL;
+        uint8_t *left = left_buf + REF_OFFSET * SIZEOF_PIXEL;
+
+        ff_vvc_dsp_init(&c, bit_depth);
+        check_pred_dc(&c, buf0, buf1, top, left, bit_depth);
+        check_pred_planar(&c, buf0, buf1, top, left, bit_depth);
+        check_pred_v(&c, buf0, buf1, top, left, bit_depth);
+        check_pred_h(&c, buf0, buf1, top, left, bit_depth);
+        check_pred_mip(&c, buf0, buf1, top, left, bit_depth);
+        check_pred_angular_v(&c, buf0, buf1, top, left, bit_depth);
+        check_pred_angular_h(&c, buf0, buf1, top, left, bit_depth);
+    }
+}
diff --git a/tests/fate/checkasm.mak b/tests/fate/checkasm.mak
index 2fb1f693b8..b2e3b94797 100644
--- a/tests/fate/checkasm.mak
+++ b/tests/fate/checkasm.mak
@@ -93,6 +93,7 @@ FATE_CHECKASM = fate-checkasm-aacencdsp                       
          \
                 fate-checkasm-vvc_alf                                   \
                 fate-checkasm-vvc_mc                                    \
                 fate-checkasm-vvc_sao                                   \
+                fate-checkasm-vvc_pred                                  \
 
 $(FATE_CHECKASM): tests/checkasm/checkasm$(EXESUF)
 $(FATE_CHECKASM): CMD = run tests/checkasm/checkasm$(EXESUF) 
--test=$(@:fate-checkasm-%=%)
-- 
2.52.0

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

Reply via email to