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]
