PR #25420 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25420 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25420.patch
e.g. on my end: * `-scaler none -backends unstable`: 1.76s -> 0.14s * `-scaler bicubic`: 34.6s -> 2.54s Or when benchmarking, with `-scaler none -p 0.1 -backends unstable -bench 10`: * `-threads 0`: 16.87s * `-threads :0`: 9.84s (since it now also parallelizes previously serial setup time etc.) * `-threads 8:4`: 6.52s (best of both worlds) Open question: should we do this in FATE as well? >From 698aa6d6fb67d40cc7058bc8b96d721fdae96e73 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 15:17:14 +0200 Subject: [PATCH 01/11] swscale/tests/swscale: group global state into a struct Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 62 ++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 49bfa2a053f..e8afe591814 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -93,20 +93,24 @@ const SwsFlags flags[] = { SWS_BICUBIC | SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP, }; -static FFSFC64 prng_state; - /* reused between tests for efficiency */ static SwsContext *sws_ref_src; static SwsContext *sws_src_dst; static SwsContext *sws_dst_out; -static AVBufferRef *hw_device_ctx = NULL; -static AVHWFramesConstraints *hw_device_constr = NULL; +static struct { + FFSFC64 prng_state; -static double speedup_logavg; -static double speedup_min = 1e10; -static double speedup_max = 0; -static int speedup_count; + AVBufferRef *hw_device_ctx; + AVHWFramesConstraints *hw_device_constr; + + double speedup_logavg; + double speedup_min; + double speedup_max; + int speedup_count; +} state = { + .speedup_min = 1e10, +}; static const char *speedup_color(double ratio) { @@ -121,11 +125,11 @@ static const char *speedup_color(double ratio) static void exit_handler(int sig) { - if (speedup_count) { - double ratio = exp(speedup_logavg / speedup_count); + if (state.speedup_count) { + double ratio = exp(state.speedup_logavg / state.speedup_count); fprintf(stderr, "Overall speedup=%.3fx %s%s\033[0m, min=%.3fx max=%.3fx\n", ratio, speedup_color(ratio), ratio >= 1.0 ? "faster" : "slower", - speedup_min, speedup_max); + state.speedup_min, state.speedup_max); } exit(sig); @@ -369,7 +373,7 @@ static int scale_hw(AVFrame *dst, const AVFrame *src, sws_hw->threads = opts->threads; sws_hw->backends = opts->backends; - in_ref = av_hwframe_ctx_alloc(hw_device_ctx); + in_ref = av_hwframe_ctx_alloc(state.hw_device_ctx); if (!in_ref) { ret = AVERROR(ENOMEM); goto error; @@ -388,7 +392,7 @@ static int scale_hw(AVFrame *dst, const AVFrame *src, goto error; } - out_ref = av_hwframe_ctx_alloc(hw_device_ctx); + out_ref = av_hwframe_ctx_alloc(state.hw_device_ctx); if (!out_ref) { ret = AVERROR(ENOMEM); goto error; @@ -516,10 +520,10 @@ static void print_results(const AVFrame *ref, const AVFrame *src, const AVFrame double ratio = ((double) ref_r->time / ref_r->iters) / ((double) r->time / opts->iters); if (FFMIN(r->time, ref_r->time) > 100 /* don't pollute stats with low precision */) { - speedup_min = FFMIN(speedup_min, ratio); - speedup_max = FFMAX(speedup_max, ratio); - speedup_logavg += log(ratio); - speedup_count++; + state.speedup_min = FFMIN(state.speedup_min, ratio); + state.speedup_max = FFMAX(state.speedup_max, ratio); + state.speedup_logavg += log(ratio); + state.speedup_count++; } printf(" (ref=%*"PRId64"/%u us), speedup=%*.3fx %s%s\033[0m", @@ -635,7 +639,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, goto error; ret = (opts->api == IMPL_LEGACY) ? scale_legacy(dst, src, mode, opts, &r.time) - : hw_device_ctx ? scale_hw(dst, src, mode, opts, &r.time) + : state.hw_device_ctx ? scale_hw(dst, src, mode, opts, &r.time) : scale_new(dst, src, mode, opts, &r.time); if (ret < 0) goto error; @@ -705,8 +709,8 @@ static inline int fmt_is_supported_by_hw(enum AVPixelFormat fmt) fmt == AV_PIX_FMT_P412 || fmt == AV_PIX_FMT_P416) return 0; for (int i = 0; - hw_device_constr->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) { - if (hw_device_constr->valid_sw_formats[i] == fmt) + state.hw_device_constr->valid_sw_formats[i] != AV_PIX_FMT_NONE; i++) { + if (state.hw_device_constr->valid_sw_formats[i] == fmt) return 1; } return 0; @@ -715,7 +719,7 @@ static inline int fmt_is_supported_by_hw(enum AVPixelFormat fmt) static inline int fmt_disabled(const struct options *opts, enum AVPixelFormat fmt) { const int scaler_sub = opts->scaler_sub ? opts->scaler_sub : opts->scaler; - return (hw_device_constr && !fmt_is_supported_by_hw(fmt)) || + return (state.hw_device_constr && !fmt_is_supported_by_hw(fmt)) || (scaler_sub < 0 && fmt_is_subsampled(fmt)); } @@ -778,7 +782,7 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) if (opts->scaler > SWS_SCALE_AUTO && opts->w == dst_w && opts->h == dst_h) continue; - if (ff_sfc64_get(&prng_state) <= UINT64_MAX * opts->prob) { + if (ff_sfc64_get(&state.prng_state) <= UINT64_MAX * opts->prob) { ret = run_test(src_fmt, dst_fmt, dst_w, dst_h, &mode, opts, ref, &src, NULL); if (ret < 0) @@ -1104,7 +1108,7 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) opts->api = ret; } else if (!strcmp(argv[i], "-hw")) { const int hw_default = !strcmp(argv[i + 1], "default"); - ret = av_hwdevice_ctx_create(&hw_device_ctx, + ret = av_hwdevice_ctx_create(&state.hw_device_ctx, AV_HWDEVICE_TYPE_VULKAN, hw_default ? NULL : argv[i + 1], NULL, 0); if (ret < 0) { @@ -1117,9 +1121,9 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) } goto end; } - hw_device_constr = av_hwdevice_get_hwframe_constraints(hw_device_ctx, - NULL); - if (!hw_device_constr) { + state.hw_device_constr = + av_hwdevice_get_hwframe_constraints(state.hw_device_ctx, NULL); + if (!state.hw_device_constr) { fprintf(stderr, "Failed to retrieve Vulkan device constraints '%s'\n", argv[i + 1]); ret = AVERROR(ENOMEM); @@ -1183,7 +1187,7 @@ int main(int argc, char **argv) if (ret < 0) goto error; - ff_sfc64_init(&prng_state, 0, 0, 0, 12); + ff_sfc64_init(&state.prng_state, 0, 0, 0, 12); signal(SIGINT, exit_handler); sws_ref_src = sws_alloc_context(); @@ -1219,8 +1223,8 @@ error: sws_free_context(&sws_ref_src); sws_free_context(&sws_src_dst); sws_free_context(&sws_dst_out); - av_buffer_unref(&hw_device_ctx); - av_hwframe_constraints_free(&hw_device_constr); + av_buffer_unref(&state.hw_device_ctx); + av_hwframe_constraints_free(&state.hw_device_constr); av_frame_free(&ref); if (fp) fclose(fp); -- 2.54.0 >From 846d1ba312e51b4c5987ced492df56480ffcb2c2 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 15:29:16 +0200 Subject: [PATCH 02/11] swscale/tests/swscale: move SwsContexts into a runner struct Preparation for running tests from multiple threads. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 76 +++++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index e8afe591814..1b3b8590869 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -82,6 +82,13 @@ struct test_results { int iters; }; +struct runner { + /* reused between tests for efficiency */ + SwsContext *sws_ref_src; + SwsContext *sws_src_dst; + SwsContext *sws_dst_out; +}; + const SwsFlags flags[] = { 0, // test defaults SWS_FAST_BILINEAR, @@ -93,11 +100,6 @@ const SwsFlags flags[] = { SWS_BICUBIC | SWS_FULL_CHR_H_INT | SWS_FULL_CHR_H_INP, }; -/* reused between tests for efficiency */ -static SwsContext *sws_ref_src; -static SwsContext *sws_src_dst; -static SwsContext *sws_dst_out; - static struct { FFSFC64 prng_state; @@ -108,6 +110,8 @@ static struct { double speedup_min; double speedup_max; int speedup_count; + + struct runner runner; } state = { .speedup_min = 1e10, }; @@ -268,10 +272,11 @@ static int checked_sws_scale_frame(SwsContext *c, AVFrame *dst, const AVFrame *s return ret; } -static int scale_new(AVFrame *dst, const AVFrame *src, +static int scale_new(struct runner *runner, AVFrame *dst, const AVFrame *src, const struct mode *mode, const struct options *opts, int64_t *out_time) { + SwsContext *sws_src_dst = runner->sws_src_dst; sws_src_dst->flags = mode->flags; sws_src_dst->dither = mode->dither; sws_src_dst->scaler = mode->scaler; @@ -589,7 +594,8 @@ static int init_frame(AVFrame **pframe, const AVFrame *ref, } /* Runs a series of ref -> src -> dst -> out, and compares out vs ref */ -static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, +static int run_test(struct runner *runner, + enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, int dst_w, int dst_h, const struct mode *mode, const struct options *opts, const AVFrame *ref, AVFrame **psrc, @@ -626,7 +632,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, } } - ret = checked_sws_scale_frame(sws_ref_src, src, ref); + ret = checked_sws_scale_frame(runner->sws_ref_src, src, ref); if (ret < 0) { av_frame_free(&src); goto error; @@ -640,7 +646,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, ret = (opts->api == IMPL_LEGACY) ? scale_legacy(dst, src, mode, opts, &r.time) : state.hw_device_ctx ? scale_hw(dst, src, mode, opts, &r.time) - : scale_new(dst, src, mode, opts, &r.time); + : scale_new(runner, dst, src, mode, opts, &r.time); if (ret < 0) goto error; @@ -648,7 +654,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, if (ret < 0) goto error; - ret = checked_sws_scale_frame(sws_dst_out, out, dst); + ret = checked_sws_scale_frame(runner->sws_dst_out, out, dst); if (ret < 0) goto error; @@ -723,12 +729,13 @@ static inline int fmt_disabled(const struct options *opts, enum AVPixelFormat fm (scaler_sub < 0 && fmt_is_subsampled(fmt)); } -static inline int test_formats(const struct options *opts, +static inline int test_formats(const struct runner *runner, + const struct options *opts, enum AVPixelFormat src, enum AVPixelFormat dst) { /* Test auxiliary conversions */ - if (!ff_sws_test_pixfmt_backend(sws_ref_src->backends, src, 1) || - !ff_sws_test_pixfmt_backend(sws_dst_out->backends, dst, 0)) + if (!ff_sws_test_pixfmt_backend(runner->sws_ref_src->backends, src, 1) || + !ff_sws_test_pixfmt_backend(runner->sws_dst_out->backends, dst, 0)) return 0; /* Test main conversion */ @@ -765,7 +772,7 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) for (dst_fmt = dst_fmt_min; dst_fmt <= dst_fmt_max; dst_fmt++) { if (fmt_disabled(opts, dst_fmt)) continue; - if (!test_formats(opts, src_fmt, dst_fmt)) + if (!test_formats(&state.runner, opts, src_fmt, dst_fmt)) continue; for (int h = 0; h < FF_ARRAY_ELEMS(dst_h_values); h++) { for (int w = 0; w < FF_ARRAY_ELEMS(dst_w_values); w++) { @@ -783,7 +790,7 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) continue; if (ff_sfc64_get(&state.prng_state) <= UINT64_MAX * opts->prob) { - ret = run_test(src_fmt, dst_fmt, dst_w, dst_h, + ret = run_test(&state.runner, src_fmt, dst_fmt, dst_w, dst_h, &mode, opts, ref, &src, NULL); if (ret < 0) goto error; @@ -873,7 +880,7 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt) continue; - ret = run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &src, &r); + ret = run_test(&state.runner, src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &src, &r); if (ret < 0) goto error; } @@ -885,6 +892,27 @@ error: return ret; } +static int runner_init(struct runner *runner) +{ + runner->sws_ref_src = sws_alloc_context(); + runner->sws_src_dst = sws_alloc_context(); + runner->sws_dst_out = sws_alloc_context(); + if (!runner->sws_ref_src || !runner->sws_src_dst || !runner->sws_dst_out) + return AVERROR(ENOMEM); + runner->sws_ref_src->flags = SWS_BILINEAR | SWS_BITEXACT | SWS_ACCURATE_RND; + runner->sws_dst_out->flags = SWS_BILINEAR | SWS_BITEXACT | SWS_ACCURATE_RND; + runner->sws_ref_src->backends = SWS_BACKEND_ALL; + runner->sws_dst_out->backends = SWS_BACKEND_ALL; + return 0; +} + +static void runner_free(struct runner *runner) +{ + sws_free_context(&runner->sws_ref_src); + sws_free_context(&runner->sws_src_dst); + sws_free_context(&runner->sws_dst_out); +} + static int init_ref(AVFrame *ref, const struct options *opts) { SwsContext *ctx = sws_alloc_context(); @@ -1190,17 +1218,9 @@ int main(int argc, char **argv) ff_sfc64_init(&state.prng_state, 0, 0, 0, 12); signal(SIGINT, exit_handler); - sws_ref_src = sws_alloc_context(); - sws_src_dst = sws_alloc_context(); - sws_dst_out = sws_alloc_context(); - if (!sws_ref_src || !sws_src_dst || !sws_dst_out) { - ret = AVERROR(ENOMEM); + ret = runner_init(&state.runner); + if (ret < 0) goto error; - } - sws_ref_src->flags = SWS_BILINEAR | SWS_BITEXACT | SWS_ACCURATE_RND; - sws_dst_out->flags = SWS_BILINEAR | SWS_BITEXACT | SWS_ACCURATE_RND; - sws_ref_src->backends = SWS_BACKEND_ALL; - sws_dst_out->backends = SWS_BACKEND_ALL; ref = av_frame_alloc(); if (!ref) { @@ -1220,9 +1240,7 @@ int main(int argc, char **argv) /* fall through */ error: - sws_free_context(&sws_ref_src); - sws_free_context(&sws_src_dst); - sws_free_context(&sws_dst_out); + runner_free(&state.runner); av_buffer_unref(&state.hw_device_ctx); av_hwframe_constraints_free(&state.hw_device_constr); av_frame_free(&ref); -- 2.54.0 >From f8522a336c4fd5aa1e5f055da821dfa0d28b25fc Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 15:29:30 +0200 Subject: [PATCH 03/11] swscale/tests/swscale: move cached source frame into the runner struct Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 1b3b8590869..d92018b789f 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -87,6 +87,7 @@ struct runner { SwsContext *sws_ref_src; SwsContext *sws_src_dst; SwsContext *sws_dst_out; + AVFrame *src; }; const SwsFlags flags[] = { @@ -598,10 +599,9 @@ static int run_test(struct runner *runner, enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, int dst_w, int dst_h, const struct mode *mode, const struct options *opts, - const AVFrame *ref, AVFrame **psrc, - const struct test_results *ref_r) + const AVFrame *ref, const struct test_results *ref_r) { - AVFrame *src = *psrc; + AVFrame *src = runner->src; AVFrame *dst = NULL, *out = NULL; const int comps = fmt_comps(src_fmt) & fmt_comps(dst_fmt); int ret; @@ -620,7 +620,7 @@ static int run_test(struct runner *runner, struct test_results r = { 0 }; if (!src || src->format != src_fmt) { - av_frame_free(psrc); + av_frame_free(&runner->src); ret = init_frame(&src, ref, ref->width, ref->height, src_fmt); if (ret < 0) goto error; @@ -637,7 +637,7 @@ static int run_test(struct runner *runner, av_frame_free(&src); goto error; } - *psrc = src; + runner->src = src; } ret = init_frame(&dst, ref, dst_w, dst_h, dst_fmt); @@ -757,8 +757,6 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) src_fmt_max = AV_PIX_FMT_NB - 1, dst_fmt_max = AV_PIX_FMT_NB - 1; - AVFrame *src = NULL; - int ret = 0; if (opts->src_fmt != AV_PIX_FMT_NONE) @@ -791,7 +789,7 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) if (ff_sfc64_get(&state.prng_state) <= UINT64_MAX * opts->prob) { ret = run_test(&state.runner, src_fmt, dst_fmt, dst_w, dst_h, - &mode, opts, ref, &src, NULL); + &mode, opts, ref, NULL); if (ret < 0) goto error; } @@ -815,7 +813,6 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) ret = 0; error: - av_frame_free(&src); return ret; } @@ -824,8 +821,6 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op char buf[256]; int ret = 0; - AVFrame *src = NULL; - for (int line = 1; fgets(buf, sizeof(buf), fp); line++) { char src_fmt_str[21], dst_fmt_str[21]; enum AVPixelFormat src_fmt; @@ -880,7 +875,7 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt) continue; - ret = run_test(&state.runner, src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &src, &r); + ret = run_test(&state.runner, src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &r); if (ret < 0) goto error; } @@ -888,7 +883,6 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op ret = 0; error: - av_frame_free(&src); return ret; } @@ -911,6 +905,7 @@ static void runner_free(struct runner *runner) sws_free_context(&runner->sws_ref_src); sws_free_context(&runner->sws_src_dst); sws_free_context(&runner->sws_dst_out); + av_frame_free(&runner->src); } static int init_ref(AVFrame *ref, const struct options *opts) -- 2.54.0 >From 327902ea5e3926f39c124d84835d0bb780eed027 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 13:41:03 +0200 Subject: [PATCH 04/11] swscale/tests/swscale: print result lines atomically Compose each message with AVBPrint and print it with a single call, so that output from concurrently running tests can't interleave. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 86 +++++++++++++++++++++----------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index d92018b789f..f12453a9236 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -29,6 +29,7 @@ #undef HAVE_AV_CONFIG_H #include "libavutil/cpu.h" #include "libavutil/avstring.h" +#include "libavutil/bprint.h" #include "libavutil/parseutils.h" #include "libavutil/pixdesc.h" #include "libavutil/lfg.h" @@ -480,22 +481,22 @@ error: return ret; } -static void print_test_params(char *buf, size_t buf_size, +static void print_test_params(AVBPrint *bp, const AVFrame *src, const AVFrame *dst, const struct mode *mode, const struct options *opts) { - snprintf(buf, buf_size, - "%-*s %*dx%*d -> %-*s %*dx%*d, flags=0x%0*x dither=%u scaler=%d/%d", - opts->pretty ? 14 : 0, av_get_pix_fmt_name(src->format), - opts->pretty ? 4 : 0, src->width, - opts->pretty ? 4 : 0, src->height, - opts->pretty ? 14 : 0, av_get_pix_fmt_name(dst->format), - opts->pretty ? 4 : 0, dst->width, - opts->pretty ? 4 : 0, dst->height, - opts->pretty ? 8 : 0, mode->flags, - mode->dither, - mode->scaler, - mode->scaler_sub); + av_bprintf(bp, + "%-*s %*dx%*d -> %-*s %*dx%*d, flags=0x%0*x dither=%u scaler=%d/%d", + opts->pretty ? 14 : 0, av_get_pix_fmt_name(src->format), + opts->pretty ? 4 : 0, src->width, + opts->pretty ? 4 : 0, src->height, + opts->pretty ? 14 : 0, av_get_pix_fmt_name(dst->format), + opts->pretty ? 4 : 0, dst->width, + opts->pretty ? 4 : 0, dst->height, + opts->pretty ? 8 : 0, mode->flags, + mode->dither, + mode->scaler, + mode->scaler_sub); } static void print_results(const AVFrame *ref, const AVFrame *src, const AVFrame *dst, @@ -505,23 +506,23 @@ static void print_results(const AVFrame *ref, const AVFrame *src, const AVFrame const struct test_results *ref_r, float expected_loss) { - char buf[128]; + AVBPrint bprint, *bp = &bprint; + av_bprint_init(bp, 0, AV_BPRINT_SIZE_AUTOMATIC); if (av_log_get_level() >= AV_LOG_INFO) { - print_test_params(buf, sizeof(buf), src, dst, mode, opts); - printf("%s", buf); + print_test_params(bp, src, dst, mode, opts); if (!opts->bench || !ref_r) { - printf(", SSIM={Y=%f U=%f V=%f A=%f} loss=%e", - r->ssim[0], r->ssim[1], r->ssim[2], r->ssim[3], - r->loss); + av_bprintf(bp, ", SSIM={Y=%f U=%f V=%f A=%f} loss=%e", + r->ssim[0], r->ssim[1], r->ssim[2], r->ssim[3], + r->loss); if (ref_r) - printf(" (ref=%e)", ref_r->loss); + av_bprintf(bp, " (ref=%e)", ref_r->loss); } if (opts->bench) { - printf(", time=%*"PRId64"/%u us", - opts->pretty ? 7 : 0, r->time, opts->iters); + av_bprintf(bp, ", time=%*"PRId64"/%u us", + opts->pretty ? 7 : 0, r->time, opts->iters); if (ref_r) { double ratio = ((double) ref_r->time / ref_r->iters) / ((double) r->time / opts->iters); @@ -532,28 +533,33 @@ static void print_results(const AVFrame *ref, const AVFrame *src, const AVFrame state.speedup_count++; } - printf(" (ref=%*"PRId64"/%u us), speedup=%*.3fx %s%s\033[0m", - opts->pretty ? 7 : 0, ref_r->time, ref_r->iters, - opts->pretty ? 6 : 0, ratio, - speedup_color(ratio), ratio >= 1.0 ? "faster" : "slower"); + av_bprintf(bp, " (ref=%*"PRId64"/%u us), speedup=%*.3fx %s%s\033[0m", + opts->pretty ? 7 : 0, ref_r->time, ref_r->iters, + opts->pretty ? 6 : 0, ratio, + speedup_color(ratio), ratio >= 1.0 ? "faster" : "slower"); } } - printf("\n"); + av_bprintf(bp, "\n"); - fflush(stdout); + if (av_bprint_is_complete(bp)) { + fputs(bp->str, stdout); + fflush(stdout); + } } if (r->loss - expected_loss > 1e-4 && dst_w >= ref->width && dst_h >= ref->height) { const int bad = r->loss - expected_loss > 1e-2; const int level = bad ? AV_LOG_ERROR : AV_LOG_WARNING; const char *worse_str = bad ? "WORSE" : "worse"; + av_bprint_clear(bp); if (bad) { - print_test_params(buf, sizeof(buf), src, dst, mode, opts); - av_log(NULL, level, "%s\n", buf); + print_test_params(bp, src, dst, mode, opts); + av_bprintf(bp, "\n"); } - av_log(NULL, level, - " loss %e is %s by %e, expected loss %e\n", - r->loss, worse_str, r->loss - expected_loss, expected_loss); + av_bprintf(bp, " loss %e is %s by %e, expected loss %e\n", + r->loss, worse_str, r->loss - expected_loss, expected_loss); + if (av_bprint_is_complete(bp)) + av_log(NULL, level, "%s", bp->str); } if (ref_r && r->loss - ref_r->loss > 1e-4) { @@ -569,14 +575,16 @@ static void print_results(const AVFrame *ref, const AVFrame *src, const AVFrame const int bad = r->loss - ref_r->loss > 1e-2 && dst_bits > 1; const int level = bad ? AV_LOG_ERROR : AV_LOG_WARNING; const char *worse_str = bad ? "WORSE" : "worse"; + av_bprint_clear(bp); if (bad) { - print_test_params(buf, sizeof(buf), src, dst, mode, opts); - av_log(NULL, level, "%s\n", buf); + print_test_params(bp, src, dst, mode, opts); + av_bprintf(bp, "\n"); } - av_log(NULL, level, - " loss %e is %s by %e, ref loss %e SSIM={Y=%f U=%f V=%f A=%f}\n", - r->loss, worse_str, r->loss - ref_r->loss, ref_r->loss, - ref_r->ssim[0], ref_r->ssim[1], ref_r->ssim[2], ref_r->ssim[3]); + av_bprintf(bp, " loss %e is %s by %e, ref loss %e SSIM={Y=%f U=%f V=%f A=%f}\n", + r->loss, worse_str, r->loss - ref_r->loss, ref_r->loss, + ref_r->ssim[0], ref_r->ssim[1], ref_r->ssim[2], ref_r->ssim[3]); + if (av_bprint_is_complete(bp)) + av_log(NULL, level, "%s", bp->str); } } -- 2.54.0 >From 8b221201360abd3271d86379496102348fd2e54b Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 13:50:54 +0200 Subject: [PATCH 05/11] swscale/tests/swscale: protect speedup stats with a mutex Preparation for running tests from multiple threads. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index f12453a9236..081d8eb9fc2 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -34,6 +34,7 @@ #include "libavutil/pixdesc.h" #include "libavutil/lfg.h" #include "libavutil/sfc64.h" +#include "libavutil/thread.h" #include "libavutil/frame.h" #include "libavutil/opt.h" #include "libavutil/time.h" @@ -108,6 +109,7 @@ static struct { AVBufferRef *hw_device_ctx; AVHWFramesConstraints *hw_device_constr; + AVMutex speedup_lock; double speedup_logavg; double speedup_min; double speedup_max; @@ -115,7 +117,8 @@ static struct { struct runner runner; } state = { - .speedup_min = 1e10, + .speedup_lock = AV_MUTEX_INITIALIZER, + .speedup_min = 1e10, }; static const char *speedup_color(double ratio) @@ -527,10 +530,12 @@ static void print_results(const AVFrame *ref, const AVFrame *src, const AVFrame double ratio = ((double) ref_r->time / ref_r->iters) / ((double) r->time / opts->iters); if (FFMIN(r->time, ref_r->time) > 100 /* don't pollute stats with low precision */) { + ff_mutex_lock(&state.speedup_lock); state.speedup_min = FFMIN(state.speedup_min, ratio); state.speedup_max = FFMAX(state.speedup_max, ratio); state.speedup_logavg += log(ratio); state.speedup_count++; + ff_mutex_unlock(&state.speedup_lock); } av_bprintf(bp, " (ref=%*"PRId64"/%u us), speedup=%*.3fx %s%s\033[0m", -- 2.54.0 >From 19717b6023210d3ab45be8197cdfcea797f0842e Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 14:03:36 +0200 Subject: [PATCH 06/11] swscale/tests/swscale: allocate an array of runners Preparation for running tests from multiple threads. No functional change, as there is still only a single runner. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 081d8eb9fc2..581de67599e 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -57,6 +57,7 @@ struct options { int dst_w; int dst_h; int threads; + int runners; int iters; int bench; int flags; @@ -115,7 +116,7 @@ static struct { double speedup_max; int speedup_count; - struct runner runner; + struct runner *runners; } state = { .speedup_lock = AV_MUTEX_INITIALIZER, .speedup_min = 1e10, @@ -783,7 +784,7 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) for (dst_fmt = dst_fmt_min; dst_fmt <= dst_fmt_max; dst_fmt++) { if (fmt_disabled(opts, dst_fmt)) continue; - if (!test_formats(&state.runner, opts, src_fmt, dst_fmt)) + if (!test_formats(&state.runners[0], opts, src_fmt, dst_fmt)) continue; for (int h = 0; h < FF_ARRAY_ELEMS(dst_h_values); h++) { for (int w = 0; w < FF_ARRAY_ELEMS(dst_w_values); w++) { @@ -801,7 +802,7 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) continue; if (ff_sfc64_get(&state.prng_state) <= UINT64_MAX * opts->prob) { - ret = run_test(&state.runner, src_fmt, dst_fmt, dst_w, dst_h, + ret = run_test(&state.runners[0], src_fmt, dst_fmt, dst_w, dst_h, &mode, opts, ref, NULL); if (ret < 0) goto error; @@ -888,7 +889,7 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt) continue; - ret = run_test(&state.runner, src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &r); + ret = run_test(&state.runners[0], src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &r); if (ret < 0) goto error; } @@ -1204,6 +1205,7 @@ int main(int argc, char **argv) .dst_w = -1, .dst_h = -1, .threads = 1, + .runners = 1, .iters = 1, .prob = 1.0, .flags = -1, @@ -1226,9 +1228,17 @@ int main(int argc, char **argv) ff_sfc64_init(&state.prng_state, 0, 0, 0, 12); signal(SIGINT, exit_handler); - ret = runner_init(&state.runner); - if (ret < 0) + state.runners = av_calloc(opts.runners, sizeof(*state.runners)); + if (!state.runners) { + ret = AVERROR(ENOMEM); goto error; + } + + for (int i = 0; i < opts.runners; i++) { + ret = runner_init(&state.runners[i]); + if (ret < 0) + goto error; + } ref = av_frame_alloc(); if (!ref) { @@ -1248,7 +1258,9 @@ int main(int argc, char **argv) /* fall through */ error: - runner_free(&state.runner); + for (int i = 0; state.runners && i < opts.runners; i++) + runner_free(&state.runners[i]); + av_freep(&state.runners); av_buffer_unref(&state.hw_device_ctx); av_hwframe_constraints_free(&state.hw_device_constr); av_frame_free(&ref); -- 2.54.0 >From 26ef1d1ca2fbdeb7a5de47802fcfc6571730d85f Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 15:00:48 +0200 Subject: [PATCH 07/11] swscale/tests/swscale: move reference frame and file into global state Preparation for running tests from multiple threads. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 581de67599e..77f290e16aa 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -117,6 +117,8 @@ static struct { int speedup_count; struct runner *runners; + AVFrame *ref; + FILE *fp; } state = { .speedup_lock = AV_MUTEX_INITIALIZER, .speedup_min = 1e10, @@ -1214,10 +1216,7 @@ int main(int argc, char **argv) .scaler_sub = SWS_SCALE_AUTO, }; - AVFrame *ref = NULL; - FILE *fp = NULL; - - int ret = parse_options(argc, argv, &opts, &fp); + int ret = parse_options(argc, argv, &opts, &state.fp); if (ret == AVERROR(ENOTSUP)) { ret = 0; goto error; @@ -1240,21 +1239,21 @@ int main(int argc, char **argv) goto error; } - ref = av_frame_alloc(); - if (!ref) { + state.ref = av_frame_alloc(); + if (!state.ref) { ret = AVERROR(ENOMEM); goto error; } - ref->width = opts.w; - ref->height = opts.h; - ref->format = AV_PIX_FMT_YUVA444P; + state.ref->width = opts.w; + state.ref->height = opts.h; + state.ref->format = AV_PIX_FMT_YUVA444P; - ret = init_ref(ref, &opts); + ret = init_ref(state.ref, &opts); if (ret < 0) goto error; - ret = fp ? run_file_tests(ref, fp, &opts) - : run_self_tests(ref, &opts); + ret = state.fp ? run_file_tests(state.ref, state.fp, &opts) + : run_self_tests(state.ref, &opts); /* fall through */ error: @@ -1263,8 +1262,8 @@ error: av_freep(&state.runners); av_buffer_unref(&state.hw_device_ctx); av_hwframe_constraints_free(&state.hw_device_constr); - av_frame_free(&ref); - if (fp) - fclose(fp); + av_frame_free(&state.ref); + if (state.fp) + fclose(state.fp); exit_handler(ret); } -- 2.54.0 >From be846456492626ae1bdc5e68f861ade85c225dcc Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 16:08:51 +0200 Subject: [PATCH 08/11] swscale/tests/swscale: pass test parameters to run_test() as a struct So that a test can be described by a single value. No functional change. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 69 ++++++++++++++++++++++++++------------ 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 77f290e16aa..7cfe2e93ce6 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -93,6 +93,15 @@ struct runner { AVFrame *src; }; +struct test_case { + enum AVPixelFormat src_fmt; + enum AVPixelFormat dst_fmt; + int dst_w, dst_h; + struct mode mode; + int has_ref; + struct test_results ref_r; +}; + const SwsFlags flags[] = { 0, // test defaults SWS_FAST_BILINEAR, @@ -611,22 +620,21 @@ static int init_frame(AVFrame **pframe, const AVFrame *ref, } /* Runs a series of ref -> src -> dst -> out, and compares out vs ref */ -static int run_test(struct runner *runner, - enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, - int dst_w, int dst_h, - const struct mode *mode, const struct options *opts, - const AVFrame *ref, const struct test_results *ref_r) +static int run_test(struct runner *runner, const struct test_case *t, + const struct options *opts) { + const AVFrame *ref = state.ref; + const struct test_results *ref_r = t->has_ref ? &t->ref_r : NULL; AVFrame *src = runner->src; AVFrame *dst = NULL, *out = NULL; - const int comps = fmt_comps(src_fmt) & fmt_comps(dst_fmt); + const int comps = fmt_comps(t->src_fmt) & fmt_comps(t->dst_fmt); int ret; /* Estimate the expected amount of loss from bit depth reduction */ const float c1 = 0.01 * 0.01; /* stabilization constant */ const float ref_var = 1.0 / 12.0; /* uniformly distributed signal */ - const float src_var = estimate_quantization_noise(src_fmt); - const float dst_var = estimate_quantization_noise(dst_fmt); + const float src_var = estimate_quantization_noise(t->src_fmt); + const float dst_var = estimate_quantization_noise(t->dst_fmt); const float out_var = estimate_quantization_noise(ref->format); const float total_var = src_var + dst_var + out_var; const float ssim_luma = (2 * ref_var + c1) / (2 * ref_var + total_var + c1); @@ -635,9 +643,9 @@ static int run_test(struct runner *runner, struct test_results r = { 0 }; - if (!src || src->format != src_fmt) { + if (!src || src->format != t->src_fmt) { av_frame_free(&runner->src); - ret = init_frame(&src, ref, ref->width, ref->height, src_fmt); + ret = init_frame(&src, ref, ref->width, ref->height, t->src_fmt); if (ret < 0) goto error; if (opts->align_src) { @@ -656,13 +664,13 @@ static int run_test(struct runner *runner, runner->src = src; } - ret = init_frame(&dst, ref, dst_w, dst_h, dst_fmt); + ret = init_frame(&dst, ref, t->dst_w, t->dst_h, t->dst_fmt); if (ret < 0) goto error; - ret = (opts->api == IMPL_LEGACY) ? scale_legacy(dst, src, mode, opts, &r.time) - : state.hw_device_ctx ? scale_hw(dst, src, mode, opts, &r.time) - : scale_new(runner, dst, src, mode, opts, &r.time); + ret = (opts->api == IMPL_LEGACY) ? scale_legacy(dst, src, &t->mode, opts, &r.time) + : state.hw_device_ctx ? scale_hw(dst, src, &t->mode, opts, &r.time) + : scale_new(runner, dst, src, &t->mode, opts, &r.time); if (ret < 0) goto error; @@ -690,7 +698,7 @@ static int run_test(struct runner *runner, } r.loss = get_loss(r.ssim); - if ((opts->api != IMPL_LEGACY) && r.loss - expected_loss > 1e-2 && dst_w >= ref->width && dst_h >= ref->height) { + if ((opts->api != IMPL_LEGACY) && r.loss - expected_loss > 1e-2 && t->dst_w >= ref->width && t->dst_h >= ref->height) { ret = -1; goto bad_loss; } @@ -704,8 +712,8 @@ static int run_test(struct runner *runner, bad_loss: print_results(ref, src, dst, - dst_w, dst_h, - mode, opts, + t->dst_w, t->dst_h, + &t->mode, opts, &r, ref_r, expected_loss); @@ -762,7 +770,7 @@ static inline int test_formats(const struct runner *runner, ff_sws_test_pixfmt_backend(backend, dst, 1); } -static int run_self_tests(const AVFrame *ref, const struct options *opts) +static int run_self_tests(const struct options *opts) { const int dst_w_values[] = { opts->w, opts->w - opts->w / 3, opts->w + opts->w / 3 }; const int dst_h_values[] = { opts->h, opts->h - opts->h / 3, opts->h + opts->h / 3 }; @@ -804,8 +812,15 @@ static int run_self_tests(const AVFrame *ref, const struct options *opts) continue; if (ff_sfc64_get(&state.prng_state) <= UINT64_MAX * opts->prob) { - ret = run_test(&state.runners[0], src_fmt, dst_fmt, dst_w, dst_h, - &mode, opts, ref, NULL); + const struct test_case test = { + .src_fmt = src_fmt, + .dst_fmt = dst_fmt, + .dst_w = dst_w, + .dst_h = dst_h, + .mode = mode, + }; + + ret = run_test(&state.runners[0], &test, opts); if (ret < 0) goto error; } @@ -891,7 +906,17 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt) continue; - ret = run_test(&state.runners[0], src_fmt, dst_fmt, dw, dh, &mode, opts, ref, &r); + const struct test_case test = { + .src_fmt = src_fmt, + .dst_fmt = dst_fmt, + .dst_w = dw, + .dst_h = dh, + .mode = mode, + .has_ref = 1, + .ref_r = r, + }; + + ret = run_test(&state.runners[0], &test, opts); if (ret < 0) goto error; } @@ -1253,7 +1278,7 @@ int main(int argc, char **argv) goto error; ret = state.fp ? run_file_tests(state.ref, state.fp, &opts) - : run_self_tests(state.ref, &opts); + : run_self_tests(&opts); /* fall through */ error: -- 2.54.0 >From 124215b4f88e689e3a89145744fa398a628c181d Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 14:26:17 +0200 Subject: [PATCH 09/11] swscale/tests/swscale: move test generation into run_tests() So that it can be used as the slice threading main callback. No functional change. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 7cfe2e93ce6..35de4960fb3 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -949,6 +949,12 @@ static void runner_free(struct runner *runner) av_frame_free(&runner->src); } +static int run_tests(const struct options *opts) +{ + return state.fp ? run_file_tests(state.ref, state.fp, opts) + : run_self_tests(opts); +} + static int init_ref(AVFrame *ref, const struct options *opts) { SwsContext *ctx = sws_alloc_context(); @@ -1277,8 +1283,7 @@ int main(int argc, char **argv) if (ret < 0) goto error; - ret = state.fp ? run_file_tests(state.ref, state.fp, &opts) - : run_self_tests(&opts); + ret = run_tests(&opts); /* fall through */ error: -- 2.54.0 >From da3eb4efda372f2a82f7b28bc8b6b0baf388e28b Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 15:03:18 +0200 Subject: [PATCH 10/11] swscale/tests/swscale: add support for running tests concurrently When using more than one runner, tests are generated on the main thread and handed to the runner threads through a message queue. Not yet exposed as an option. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 67 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 35de4960fb3..5d6f9ab8d71 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -34,7 +34,9 @@ #include "libavutil/pixdesc.h" #include "libavutil/lfg.h" #include "libavutil/sfc64.h" +#include "libavutil/slicethread.h" #include "libavutil/thread.h" +#include "libavutil/threadmessage.h" #include "libavutil/frame.h" #include "libavutil/opt.h" #include "libavutil/time.h" @@ -128,6 +130,9 @@ static struct { struct runner *runners; AVFrame *ref; FILE *fp; + + /* Tests waiting for a runner, if running concurrently */ + AVThreadMessageQueue *queue; } state = { .speedup_lock = AV_MUTEX_INITIALIZER, .speedup_min = 1e10, @@ -770,6 +775,13 @@ static inline int test_formats(const struct runner *runner, ff_sws_test_pixfmt_backend(backend, dst, 1); } +static int push_test(const struct test_case *t, const struct options *opts) +{ + if (state.queue) + return av_thread_message_queue_send(state.queue, (void *) t, 0); + return run_test(&state.runners[0], t, opts); +} + static int run_self_tests(const struct options *opts) { const int dst_w_values[] = { opts->w, opts->w - opts->w / 3, opts->w + opts->w / 3 }; @@ -820,7 +832,7 @@ static int run_self_tests(const struct options *opts) .mode = mode, }; - ret = run_test(&state.runners[0], &test, opts); + ret = push_test(&test, opts); if (ret < 0) goto error; } @@ -916,7 +928,7 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options *op .ref_r = r, }; - ret = run_test(&state.runners[0], &test, opts); + ret = push_test(&test, opts); if (ret < 0) goto error; } @@ -955,6 +967,35 @@ static int run_tests(const struct options *opts) : run_self_tests(opts); } +static int run_producer(void *priv) +{ + const struct options *opts = priv; + int ret = run_tests(opts); + /* Let the runners finish all remaining tests */ + av_thread_message_queue_set_err_recv(state.queue, AVERROR_EOF); + return ret; +} + +static int run_runner(void *priv, int jobnr, int threadnr, int nb_jobs, int nb_threads) +{ + const struct options *opts = priv; + struct runner *runner = &state.runners[jobnr]; + struct test_case test; + + while (!av_thread_message_queue_recv(state.queue, &test, 0)) { + int ret = run_test(runner, &test, opts); + if (ret < 0) { + /* Stop the producer and all other runners */ + av_thread_message_queue_set_err_send(state.queue, ret); + av_thread_message_queue_set_err_recv(state.queue, ret); + av_thread_message_flush(state.queue); + return ret; + } + } + + return 0; +} + static int init_ref(AVFrame *ref, const struct options *opts) { SwsContext *ctx = sws_alloc_context(); @@ -1247,6 +1288,8 @@ int main(int argc, char **argv) .scaler_sub = SWS_SCALE_AUTO, }; + AVSliceThread *slicethread = NULL; + int ret = parse_options(argc, argv, &opts, &state.fp); if (ret == AVERROR(ENOTSUP)) { ret = 0; @@ -1283,10 +1326,28 @@ int main(int argc, char **argv) if (ret < 0) goto error; - ret = run_tests(&opts); + if (opts.runners > 1) { + ret = av_thread_message_queue_alloc(&state.queue, opts.runners, + sizeof(struct test_case)); + if (ret < 0) + goto error; + + ret = avpriv_slicethread_create2(&slicethread, &opts, run_runner, + run_producer, opts.runners); + if (ret < 0) { + fprintf(stderr, "Failed to create %d runner threads: %s\n", + opts.runners, av_err2str(ret)); + goto error; + } + ret = avpriv_slicethread_execute2(slicethread, opts.runners, 1); + } else { + ret = run_tests(&opts); + } /* fall through */ error: + avpriv_slicethread_free(&slicethread); + av_thread_message_queue_free(&state.queue); for (int i = 0; state.runners && i < opts.runners; i++) runner_free(&state.runners[i]); av_freep(&state.runners); -- 2.54.0 >From 68eac9579cecb252c74ac78643697b3919e1face Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 24 Oct 2026 15:04:14 +0200 Subject: [PATCH 11/11] swscale/tests/swscale: allow running tests concurrently with -threads -threads now accepts [threads][:runners], where runners is the number of tests to run concurrently, each using the given number of threads per conversion. For example, -threads :0 runs one single-threaded test per CPU core. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 42 +++++++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 5d6f9ab8d71..1c1e14b0bbc 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -1054,6 +1054,35 @@ static int parse_size(struct options *opts, const char *str, char **pbuf) return ret; } +/* Parses [threads][:runners] */ +static int parse_threads(struct options *opts, const char *str) +{ + long threads = 1, runners = 1; + char *end; + + if (*str != ':') { + threads = strtol(str, &end, 10); + if (end == str) + return AVERROR(EINVAL); + str = end; + } + + if (*str == ':') { + str++; + runners = strtol(str, &end, 10); + if (end == str) + return AVERROR(EINVAL); + str = end; + } + + if (*str || threads < 0 || threads > INT_MAX || runners < 0 || runners > INT_MAX) + return AVERROR(EINVAL); + + opts->threads = threads; + opts->runners = runners ? runners : av_cpu_count(); + return 0; +} + static int parse_implementation(const char *str) { if (!strcmp(str, "legacy")) @@ -1111,8 +1140,11 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) " -hw <device>\n" " Use Vulkan hardware acceleration on the specified device for the main conversion\n" " If 'default', use the default device, and skip all tests if none is available\n" - " -threads <threads>\n" - " Use the specified number of threads\n" + " -threads [<threads>][:<runners>]\n" + " Use the specified number of threads per conversion (default: 1)\n" + " Optionally run multiple tests concurrently on <runners> threads (default: 1)\n" + " A value of 0 picks the number of threads automatically\n" + " Benchmark results may be skewed when using multiple runners\n" " -cpuflags <cpuflags>\n" " Uses the specified cpuflags in the tests\n" " -pretty <1 or 0>\n" @@ -1241,7 +1273,11 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) goto end; } } else if (!strcmp(argv[i], "-threads")) { - opts->threads = atoi(argv[i + 1]); + ret = parse_threads(opts, argv[i + 1]); + if (ret < 0) { + fprintf(stderr, "invalid thread count %s\n", argv[i + 1]); + goto end; + } } else if (!strcmp(argv[i], "-p")) { opts->prob = atof(argv[i + 1]); } else if (!strcmp(argv[i], "-pretty")) { -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
