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]

Reply via email to