PR #22427 opened by Ramiro Polla (ramiro)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22427
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22427.patch

Includes two commits from @haasn from !22293, some cleanup, and a speedup by 
removing redundant `ref->src` conversions.


>From 89b2f84eb0e83f7446386b42d0949ce9c7ce7cb9 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Wed, 18 Feb 2026 15:07:47 +0100
Subject: [PATCH 01/11] tests/swscale: exclude init time from benchmark

This was originally intended to also include performance gains/losses
due to complicated setup logic, but in practice it just means that changing
the number of iterations dramatically affects the measured speedup; which
makes it harder to do quick bench runs during development.
---
 libswscale/tests/swscale.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 6f6240e4b6..63f566ba18 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -193,7 +193,7 @@ static float get_loss(const float ssim[4])
 }
 
 static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode,
-                        struct options opts)
+                        struct options opts, int64_t *out_time)
 {
     SwsContext *sws_legacy;
     int ret;
@@ -215,8 +215,10 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, 
struct mode mode,
     if ((ret = sws_init_context(sws_legacy, NULL, NULL)) < 0)
         goto error;
 
+    int64_t time = av_gettime_relative();
     for (int i = 0; ret >= 0 && i < opts.iters; i++)
         ret = sws_scale_frame(sws_legacy, dst, src);
+    *out_time = av_gettime_relative() - time;
 
 error:
     sws_freeContext(sws_legacy);
@@ -273,6 +275,12 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
     sws[1]->dither = mode.dither;
     sws[1]->threads = opts.threads;
 
+    if (sws_frame_setup(sws[1], dst, src) < 0) {
+        av_log(NULL, AV_LOG_ERROR, "Failed to setup %s ---> %s\n",
+               av_get_pix_fmt_name(src->format), 
av_get_pix_fmt_name(dst->format));
+        goto error;
+    }
+
     time = av_gettime_relative();
 
     for (int i = 0; i < opts.iters; i++) {
@@ -316,13 +324,11 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
 
     if (!ssim_ref && sws_isSupportedInput(src->format) && 
sws_isSupportedOutput(dst->format)) {
         /* Compare against the legacy swscale API as a reference */
-        time_ref = av_gettime_relative();
-        if (scale_legacy(dst, src, mode, opts) < 0) {
+        if (scale_legacy(dst, src, mode, opts, &time_ref) < 0) {
             av_log(NULL, AV_LOG_ERROR, "Failed ref %s ---> %s\n",
                    av_get_pix_fmt_name(src->format), 
av_get_pix_fmt_name(dst->format));
             goto error;
         }
-        time_ref = av_gettime_relative() - time_ref;
 
         if (sws_scale_frame(sws[2], out, dst) < 0)
             goto error;
-- 
2.52.0


>From 1121523fc1e9fdd1e9d812bc3ae9f7e1c8c22327 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sun, 22 Feb 2026 19:47:58 +0100
Subject: [PATCH 02/11] tests/swscale: unref buffers before each iteration

Otherwise, we always pass frames that already have buffers allocated, which
breaks the no-op refcopy optimizations.

Testing with -p 0.1 -threads 16 -bench 10, on an AMD Ryzen 9 9950X3D:

 Before:
  Overall speedup=2.776x faster, min=0.133x max=629.496x
  yuv444p 1920x1080 -> yuv444p 1920x1080, flags=0x100000 dither=1
     time=9 us, ref=9 us, speedup=1.043x faster

 After:
  Overall speedup=2.721x faster, min=0.140x max=574.034x
  yuv444p 1920x1080 -> yuv444p 1920x1080, flags=0x100000 dither=1
    time=0 us, ref=28 us, speedup=516.504x faster

(The slowdown in the legacy swscale case is from swscale's lack of a no-op
refcopy optimizaton, plus the fact that it's now actually doing memory
work instead of a no-op / redundant memset)

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/tests/swscale.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 63f566ba18..8001f0d105 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -192,6 +192,18 @@ static float get_loss(const float ssim[4])
     return 1.0 - sum;
 }
 
+static void unref_buffers(AVFrame *frame)
+{
+    for (int i = 0; i < FF_ARRAY_ELEMS(frame->buf); i++) {
+        if (!frame->buf[i])
+            break;
+        av_buffer_unref(&frame->buf[i]);
+    }
+
+    memset(frame->data, 0, sizeof(frame->data));
+    memset(frame->linesize, 0, sizeof(frame->linesize));
+}
+
 static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode,
                         struct options opts, int64_t *out_time)
 {
@@ -216,8 +228,10 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, 
struct mode mode,
         goto error;
 
     int64_t time = av_gettime_relative();
-    for (int i = 0; ret >= 0 && i < opts.iters; i++)
+    for (int i = 0; ret >= 0 && i < opts.iters; i++) {
+        unref_buffers(dst);
         ret = sws_scale_frame(sws_legacy, dst, src);
+    }
     *out_time = av_gettime_relative() - time;
 
 error:
@@ -284,6 +298,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
     time = av_gettime_relative();
 
     for (int i = 0; i < opts.iters; i++) {
+        unref_buffers(dst);
         if (sws_scale_frame(sws[1], dst, src) < 0) {
             av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
                    av_get_pix_fmt_name(src->format), 
av_get_pix_fmt_name(dst->format));
-- 
2.52.0


>From 700977c021dc7648c36b87b8d29ab3445974ae63 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Fri, 6 Mar 2026 22:37:37 +0100
Subject: [PATCH 03/11] swscale/tests/swscale: always allocate frame in
 scale_legacy()

Legacy swscale may overwrite the pixel formats in the context (see
handle_formats() in libswscale/utils.c). This may lead to an issue
where, when sws_frame_start() allocates a new frame, it uses the wrong
pixel format.

Instead of fixing the issue in swscale, just make sure dst is always
allocated prior to calling the legacy scaler.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/swscale.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 8001f0d105..dc0e369d19 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -224,14 +224,20 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, 
struct mode mode,
     sws_legacy->dither     = mode.dither;
     sws_legacy->threads    = opts.threads;
 
+    av_frame_unref(dst);
+    dst->width  = sws_legacy->dst_w;
+    dst->height = sws_legacy->dst_h;
+    dst->format = sws_legacy->dst_format;
+    ret = av_frame_get_buffer(dst, 0);
+    if (ret < 0)
+        return ret;
+
     if ((ret = sws_init_context(sws_legacy, NULL, NULL)) < 0)
         goto error;
 
     int64_t time = av_gettime_relative();
-    for (int i = 0; ret >= 0 && i < opts.iters; i++) {
-        unref_buffers(dst);
+    for (int i = 0; ret >= 0 && i < opts.iters; i++)
         ret = sws_scale_frame(sws_legacy, dst, src);
-    }
     *out_time = av_gettime_relative() - time;
 
 error:
-- 
2.52.0


>From 2f7db05195d2ca7051ed6a1ffa5efe42426f6641 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Thu, 5 Mar 2026 21:03:27 +0100
Subject: [PATCH 04/11] swscale/tests/swscale: remove pointless hardcoded
 dimension checks

This is a test tool. If the user wants to create a malformed reference
file and use it that's their problem.

This reverts f70a651b3f7 and c0f0bec2f20.
---
 libswscale/tests/swscale.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index dc0e369d19..6ca15dfb68 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -500,7 +500,7 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, 
struct options opts)
         src_fmt = av_get_pix_fmt(src_fmt_str);
         dst_fmt = av_get_pix_fmt(dst_fmt_str);
         if (src_fmt == AV_PIX_FMT_NONE || dst_fmt == AV_PIX_FMT_NONE ||
-            sw != ref->width || sh != ref->height || dw > 8192 || dh > 8192 ||
+            sw != ref->width || sh != ref->height ||
             mode.dither >= SWS_DITHER_NB) {
             av_log(NULL, AV_LOG_FATAL, "malformed input file\n");
             return -1;
-- 
2.52.0


>From dd5e952e03e6e2e5094398e4bcffd79bec32105c Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Fri, 27 Feb 2026 22:12:31 +0100
Subject: [PATCH 05/11] swscale/tests/swscale: split parse_options() out of
 main()

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/swscale.c | 88 +++++++++++++++++++++-----------------
 1 file changed, 48 insertions(+), 40 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 6ca15dfb68..b7db021446 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -517,25 +517,8 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, 
struct options opts)
     return 0;
 }
 
-int main(int argc, char **argv)
+static int parse_options(int argc, char **argv, struct options *opts, FILE 
**fp)
 {
-    struct options opts = {
-        .src_fmt = AV_PIX_FMT_NONE,
-        .dst_fmt = AV_PIX_FMT_NONE,
-        .w       = 96,
-        .h       = 96,
-        .threads = 1,
-        .iters   = 1,
-        .prob    = 1.0,
-        .flags   = -1,
-        .dither  = -1,
-    };
-
-    AVFrame *rgb = NULL, *ref = NULL;
-    FILE *fp = NULL;
-    AVLFG rand;
-    int ret = -1;
-
     for (int i = 1; i < argc; i += 2) {
         if (!strcmp(argv[i], "-help") || !strcmp(argv[i], "--help")) {
             fprintf(stderr,
@@ -571,63 +554,88 @@ int main(int argc, char **argv)
         if (argv[i][0] != '-' || i + 1 == argc)
             goto bad_option;
         if (!strcmp(argv[i], "-ref")) {
-            fp = fopen(argv[i + 1], "r");
-            if (!fp) {
+            *fp = fopen(argv[i + 1], "r");
+            if (!*fp) {
                 fprintf(stderr, "could not open '%s'\n", argv[i + 1]);
-                goto error;
+                return -1;
             }
         } else if (!strcmp(argv[i], "-cpuflags")) {
             unsigned flags = av_get_cpu_flags();
             int res = av_parse_cpu_caps(&flags, argv[i + 1]);
             if (res < 0) {
                 fprintf(stderr, "invalid cpu flags %s\n", argv[i + 1]);
-                goto error;
+                return -1;
             }
             av_force_cpu_flags(flags);
         } else if (!strcmp(argv[i], "-src")) {
-            opts.src_fmt = av_get_pix_fmt(argv[i + 1]);
-            if (opts.src_fmt == AV_PIX_FMT_NONE) {
+            opts->src_fmt = av_get_pix_fmt(argv[i + 1]);
+            if (opts->src_fmt == AV_PIX_FMT_NONE) {
                 fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]);
-                goto error;
+                return -1;
             }
         } else if (!strcmp(argv[i], "-dst")) {
-            opts.dst_fmt = av_get_pix_fmt(argv[i + 1]);
-            if (opts.dst_fmt == AV_PIX_FMT_NONE) {
+            opts->dst_fmt = av_get_pix_fmt(argv[i + 1]);
+            if (opts->dst_fmt == AV_PIX_FMT_NONE) {
                 fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]);
-                goto error;
+                return -1;
             }
         } else if (!strcmp(argv[i], "-bench")) {
-            opts.bench = 1;
-            opts.iters = atoi(argv[i + 1]);
-            opts.iters = FFMAX(opts.iters, 1);
-            opts.w = 1920;
-            opts.h = 1080;
+            opts->bench = 1;
+            opts->iters = atoi(argv[i + 1]);
+            opts->iters = FFMAX(opts->iters, 1);
+            opts->w = 1920;
+            opts->h = 1080;
         } else if (!strcmp(argv[i], "-flags")) {
             SwsContext *dummy = sws_alloc_context();
             const AVOption *flags_opt = av_opt_find(dummy, "sws_flags", NULL, 
0, 0);
-            ret = av_opt_eval_flags(dummy, flags_opt, argv[i + 1], 
&opts.flags);
+            int ret = av_opt_eval_flags(dummy, flags_opt, argv[i + 1], 
&opts->flags);
             sws_free_context(&dummy);
             if (ret < 0) {
                 fprintf(stderr, "invalid flags %s\n", argv[i + 1]);
-                goto error;
+                return -1;
             }
         } else if (!strcmp(argv[i], "-dither")) {
-            opts.dither = atoi(argv[i + 1]);
+            opts->dither = atoi(argv[i + 1]);
         } else if (!strcmp(argv[i], "-unscaled")) {
-            opts.unscaled = atoi(argv[i + 1]);
+            opts->unscaled = atoi(argv[i + 1]);
         } else if (!strcmp(argv[i], "-threads")) {
-            opts.threads = atoi(argv[i + 1]);
+            opts->threads = atoi(argv[i + 1]);
         } else if (!strcmp(argv[i], "-p")) {
-            opts.prob = atof(argv[i + 1]);
+            opts->prob = atof(argv[i + 1]);
         } else if (!strcmp(argv[i], "-v")) {
             av_log_set_level(atoi(argv[i + 1]));
         } else {
 bad_option:
             fprintf(stderr, "bad option or argument missing (%s) see -help\n", 
argv[i]);
-            goto error;
+            return -1;
         }
     }
 
+    return 0;
+}
+
+int main(int argc, char **argv)
+{
+    struct options opts = {
+        .src_fmt = AV_PIX_FMT_NONE,
+        .dst_fmt = AV_PIX_FMT_NONE,
+        .w       = 96,
+        .h       = 96,
+        .threads = 1,
+        .iters   = 1,
+        .prob    = 1.0,
+        .flags   = -1,
+        .dither  = -1,
+    };
+
+    AVFrame *rgb = NULL, *ref = NULL;
+    FILE *fp = NULL;
+    AVLFG rand;
+    int ret = -1;
+
+    if (parse_options(argc, argv, &opts, &fp) < 0)
+        goto error;
+
     ff_sfc64_init(&prng_state, 0, 0, 0, 12);
     av_lfg_init(&rand, 1);
     signal(SIGINT, exit_handler);
-- 
2.52.0


>From 6c8ed583ef5c523c060f361156e995bdbf862ff3 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Fri, 27 Feb 2026 22:14:25 +0100
Subject: [PATCH 06/11] swscale/tests/swscale: split init_ref() out of main()

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/swscale.c | 54 ++++++++++++++++++++++++--------------
 1 file changed, 35 insertions(+), 19 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index b7db021446..bc960a9be6 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -517,6 +517,39 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, 
struct options opts)
     return 0;
 }
 
+static int init_ref(AVFrame *ref, const struct options *opts)
+{
+    SwsContext *ctx = sws_alloc_context();
+    AVFrame *rgb = av_frame_alloc();
+    AVLFG rand;
+    int ret = -1;
+
+    if (!ctx || !rgb)
+        goto error;
+
+    rgb->width  = opts->w / 12;
+    rgb->height = opts->h / 12;
+    rgb->format = AV_PIX_FMT_RGBA;
+    if (av_frame_get_buffer(rgb, 32) < 0)
+        goto error;
+
+    av_lfg_init(&rand, 1);
+    for (int y = 0; y < rgb->height; y++) {
+        for (int x = 0; x < rgb->width; x++) {
+            for (int c = 0; c < 4; c++)
+                rgb->data[0][y * rgb->linesize[0] + x * 4 + c] = 
av_lfg_get(&rand);
+        }
+    }
+
+    ctx->flags = SWS_BILINEAR;
+    ret = sws_scale_frame(ctx, ref, rgb);
+
+error:
+    sws_free_context(&ctx);
+    av_frame_free(&rgb);
+    return ret;
+}
+
 static int parse_options(int argc, char **argv, struct options *opts, FILE 
**fp)
 {
     for (int i = 1; i < argc; i += 2) {
@@ -628,7 +661,7 @@ int main(int argc, char **argv)
         .dither  = -1,
     };
 
-    AVFrame *rgb = NULL, *ref = NULL;
+    AVFrame *ref = NULL;
     FILE *fp = NULL;
     AVLFG rand;
     int ret = -1;
@@ -647,22 +680,6 @@ int main(int argc, char **argv)
         sws[i]->flags = SWS_BILINEAR;
     }
 
-    rgb = av_frame_alloc();
-    if (!rgb)
-        goto error;
-    rgb->width  = opts.w / 12;
-    rgb->height = opts.h / 12;
-    rgb->format = AV_PIX_FMT_RGBA;
-    if (av_frame_get_buffer(rgb, 32) < 0)
-        goto error;
-
-    for (int y = 0; y < rgb->height; y++) {
-        for (int x = 0; x < rgb->width; x++) {
-            for (int c = 0; c < 4; c++)
-                rgb->data[0][y * rgb->linesize[0] + x * 4 + c] = 
av_lfg_get(&rand);
-        }
-    }
-
     ref = av_frame_alloc();
     if (!ref)
         goto error;
@@ -670,7 +687,7 @@ int main(int argc, char **argv)
     ref->height = opts.h;
     ref->format = AV_PIX_FMT_YUVA444P;
 
-    if (sws_scale_frame(sws[0], ref, rgb) < 0)
+    if (init_ref(ref, &opts) < 0)
         goto error;
 
     ret = fp ? run_file_tests(ref, fp, opts)
@@ -680,7 +697,6 @@ int main(int argc, char **argv)
 error:
     for (int i = 0; i < 3; i++)
         sws_free_context(&sws[i]);
-    av_frame_free(&rgb);
     av_frame_free(&ref);
     if (fp)
         fclose(fp);
-- 
2.52.0


>From 82406734c1f0f26553da70c806781722297046ab Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Tue, 23 Dec 2025 21:52:02 +0100
Subject: [PATCH 07/11] swscale/tests/swscale: pass opts and mode arguments as
 const pointers

---
 libswscale/tests/swscale.c | 80 +++++++++++++++++++-------------------
 1 file changed, 41 insertions(+), 39 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index bc960a9be6..03c8a22c0c 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -204,8 +204,9 @@ static void unref_buffers(AVFrame *frame)
     memset(frame->linesize, 0, sizeof(frame->linesize));
 }
 
-static int scale_legacy(AVFrame *dst, const AVFrame *src, struct mode mode,
-                        struct options opts, int64_t *out_time)
+static int scale_legacy(AVFrame *dst, const AVFrame *src,
+                        const struct mode *mode, const struct options *opts,
+                        int64_t *out_time)
 {
     SwsContext *sws_legacy;
     int ret;
@@ -220,9 +221,9 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, 
struct mode mode,
     sws_legacy->dst_w      = dst->width;
     sws_legacy->dst_h      = dst->height;
     sws_legacy->dst_format = dst->format;
-    sws_legacy->flags      = mode.flags;
-    sws_legacy->dither     = mode.dither;
-    sws_legacy->threads    = opts.threads;
+    sws_legacy->flags      = mode->flags;
+    sws_legacy->dither     = mode->dither;
+    sws_legacy->threads    = opts->threads;
 
     av_frame_unref(dst);
     dst->width  = sws_legacy->dst_w;
@@ -236,7 +237,7 @@ static int scale_legacy(AVFrame *dst, const AVFrame *src, 
struct mode mode,
         goto error;
 
     int64_t time = av_gettime_relative();
-    for (int i = 0; ret >= 0 && i < opts.iters; i++)
+    for (int i = 0; ret >= 0 && i < opts->iters; i++)
         ret = sws_scale_frame(sws_legacy, dst, src);
     *out_time = av_gettime_relative() - time;
 
@@ -247,7 +248,8 @@ error:
 
 /* 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,
-                    int dst_w, int dst_h, struct mode mode, struct options 
opts,
+                    int dst_w, int dst_h,
+                    const struct mode *mode, const struct options *opts,
                     const AVFrame *ref, const float ssim_ref[4])
 {
     AVFrame *src = NULL, *dst = NULL, *out = NULL;
@@ -291,9 +293,9 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
         goto error;
     }
 
-    sws[1]->flags  = mode.flags;
-    sws[1]->dither = mode.dither;
-    sws[1]->threads = opts.threads;
+    sws[1]->flags  = mode->flags;
+    sws[1]->dither = mode->dither;
+    sws[1]->threads = opts->threads;
 
     if (sws_frame_setup(sws[1], dst, src) < 0) {
         av_log(NULL, AV_LOG_ERROR, "Failed to setup %s ---> %s\n",
@@ -303,7 +305,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
 
     time = av_gettime_relative();
 
-    for (int i = 0; i < opts.iters; i++) {
+    for (int i = 0; i < opts->iters; i++) {
         unref_buffers(dst);
         if (sws_scale_frame(sws[1], dst, src) < 0) {
             av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
@@ -324,7 +326,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
     av_log(NULL, AV_LOG_INFO, "%s %dx%d -> %s %3dx%3d, flags=0x%x dither=%u\n",
            av_get_pix_fmt_name(src->format), src->width, src->height,
            av_get_pix_fmt_name(dst->format), dst->width, dst->height,
-           mode.flags, mode.dither);
+           mode->flags, mode->dither);
 
     av_log(NULL, AV_LOG_VERBOSE - 4, "  SSIM {Y=%f U=%f V=%f A=%f}\n",
            ssim[0], ssim[1], ssim[2], ssim[3]);
@@ -336,7 +338,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
         av_log(NULL, level, "%s %dx%d -> %s %3dx%3d, flags=0x%x dither=%u\n",
                av_get_pix_fmt_name(src->format), src->width, src->height,
                av_get_pix_fmt_name(dst->format), dst->width, dst->height,
-               mode.flags, mode.dither);
+               mode->flags, mode->dither);
         av_log(NULL, level, "  loss %g is %s by %g, expected loss %g\n",
                loss, bad ? "WORSE" : "worse", loss - expected_loss, 
expected_loss);
         if (bad)
@@ -384,7 +386,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
         }
     }
 
-    if (opts.bench && time_ref) {
+    if (opts->bench && time_ref) {
         double ratio = (double) time_ref / time;
         if (FFMIN(time, time_ref) > 100 /* don't pollute stats with low 
precision */) {
             speedup_min = FFMIN(speedup_min, ratio);
@@ -395,11 +397,11 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
 
         if (av_log_get_level() >= AV_LOG_INFO) {
             printf("  time=%"PRId64" us, ref=%"PRId64" us, speedup=%.3fx 
%s%s\033[0m\n",
-                   time / opts.iters, time_ref / opts.iters, ratio,
+                   time / opts->iters, time_ref / opts->iters, ratio,
                    speedup_color(ratio), ratio >= 1.0 ? "faster" : "slower");
         }
-    } else if (opts.bench) {
-        av_log(NULL, AV_LOG_INFO, "  time=%"PRId64" us\n", time / opts.iters);
+    } else if (opts->bench) {
+        av_log(NULL, AV_LOG_INFO, "  time=%"PRId64" us\n", time / opts->iters);
     }
 
     fflush(stdout);
@@ -417,10 +419,10 @@ static inline int fmt_is_subsampled(enum AVPixelFormat 
fmt)
            av_pix_fmt_desc_get(fmt)->log2_chroma_h != 0;
 }
 
-static int run_self_tests(const AVFrame *ref, struct options opts)
+static int run_self_tests(const AVFrame *ref, const struct options *opts)
 {
-    const int dst_w[] = { opts.w, opts.w - opts.w / 3, opts.w + opts.w / 3 };
-    const int dst_h[] = { opts.h, opts.h - opts.h / 3, opts.h + opts.h / 3 };
+    const int dst_w[] = { opts->w, opts->w - opts->w / 3, opts->w + opts->w / 
3 };
+    const int dst_h[] = { opts->h, opts->h - opts->h / 3, opts->h + opts->h / 
3 };
 
     enum AVPixelFormat src_fmt, dst_fmt,
                        src_fmt_min = 0,
@@ -428,18 +430,18 @@ static int run_self_tests(const AVFrame *ref, struct 
options opts)
                        src_fmt_max = AV_PIX_FMT_NB - 1,
                        dst_fmt_max = AV_PIX_FMT_NB - 1;
 
-    if (opts.src_fmt != AV_PIX_FMT_NONE)
-        src_fmt_min = src_fmt_max = opts.src_fmt;
-    if (opts.dst_fmt != AV_PIX_FMT_NONE)
-        dst_fmt_min = dst_fmt_max = opts.dst_fmt;
+    if (opts->src_fmt != AV_PIX_FMT_NONE)
+        src_fmt_min = src_fmt_max = opts->src_fmt;
+    if (opts->dst_fmt != AV_PIX_FMT_NONE)
+        dst_fmt_min = dst_fmt_max = opts->dst_fmt;
 
     for (src_fmt = src_fmt_min; src_fmt <= src_fmt_max; src_fmt++) {
-        if (opts.unscaled && fmt_is_subsampled(src_fmt))
+        if (opts->unscaled && fmt_is_subsampled(src_fmt))
             continue;
         if (!sws_test_format(src_fmt, 0) || !sws_test_format(src_fmt, 1))
             continue;
         for (dst_fmt = dst_fmt_min; dst_fmt <= dst_fmt_max; dst_fmt++) {
-            if (opts.unscaled && fmt_is_subsampled(dst_fmt))
+            if (opts->unscaled && fmt_is_subsampled(dst_fmt))
                 continue;
             if (!sws_test_format(dst_fmt, 0) || !sws_test_format(dst_fmt, 1))
                 continue;
@@ -447,24 +449,24 @@ static int run_self_tests(const AVFrame *ref, struct 
options opts)
                 for (int w = 0; w < FF_ARRAY_ELEMS(dst_w); w++) {
                     for (int f = 0; f < FF_ARRAY_ELEMS(flags); f++) {
                         struct mode mode = {
-                            .flags  = opts.flags  >= 0 ? opts.flags  : 
flags[f],
-                            .dither = opts.dither >= 0 ? opts.dither : 
SWS_DITHER_AUTO,
+                            .flags  = opts->flags  >= 0 ? opts->flags  : 
flags[f],
+                            .dither = opts->dither >= 0 ? opts->dither : 
SWS_DITHER_AUTO,
                         };
 
-                        if (ff_sfc64_get(&prng_state) > UINT64_MAX * opts.prob)
+                        if (ff_sfc64_get(&prng_state) > UINT64_MAX * 
opts->prob)
                             continue;
 
                         if (run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h],
-                                     mode, opts, ref, NULL) < 0)
+                                     &mode, opts, ref, NULL) < 0)
                             return -1;
 
-                        if (opts.flags >= 0 || opts.unscaled)
+                        if (opts->flags >= 0 || opts->unscaled)
                             break;
                     }
-                    if (opts.unscaled)
+                    if (opts->unscaled)
                         break;
                 }
-                if (opts.unscaled)
+                if (opts->unscaled)
                     break;
             }
         }
@@ -473,7 +475,7 @@ static int run_self_tests(const AVFrame *ref, struct 
options opts)
     return 0;
 }
 
-static int run_file_tests(const AVFrame *ref, FILE *fp, struct options opts)
+static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options 
*opts)
 {
     char buf[256];
     int ret;
@@ -506,11 +508,11 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, 
struct options opts)
             return -1;
         }
 
-        if (opts.src_fmt != AV_PIX_FMT_NONE && src_fmt != opts.src_fmt ||
-            opts.dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts.dst_fmt)
+        if (opts->src_fmt != AV_PIX_FMT_NONE && src_fmt != opts->src_fmt ||
+            opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt)
             continue;
 
-        if (run_test(src_fmt, dst_fmt, dw, dh, mode, opts, ref, ssim) < 0)
+        if (run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, ssim) < 0)
             return -1;
     }
 
@@ -690,8 +692,8 @@ int main(int argc, char **argv)
     if (init_ref(ref, &opts) < 0)
         goto error;
 
-    ret = fp ? run_file_tests(ref, fp, opts)
-             : run_self_tests(ref, opts);
+    ret = fp ? run_file_tests(ref, fp, &opts)
+             : run_self_tests(ref, &opts);
 
     /* fall through */
 error:
-- 
2.52.0


>From 704329aead4807a195b6c8b23e1b91e7b845a860 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Tue, 23 Dec 2025 21:03:35 +0100
Subject: [PATCH 08/11] swscale/tests/swscale: give names to SwsContext
 variables

---
 libswscale/tests/swscale.c | 41 ++++++++++++++++++++++----------------
 1 file changed, 24 insertions(+), 17 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 03c8a22c0c..c86b99071f 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -70,7 +70,11 @@ const SwsFlags flags[] = {
 };
 
 static FFSFC64 prng_state;
-static SwsContext *sws[3]; /* reused between tests for efficiency */
+
+/* reused between tests for efficiency */
+static SwsContext *sws_ref_src;
+static SwsContext *sws_src_dst;
+static SwsContext *sws_dst_out;
 
 static double speedup_logavg;
 static double speedup_min = 1e10;
@@ -287,17 +291,17 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
     dst->width  = dst_w;
     dst->height = dst_h;
 
-    if (sws_scale_frame(sws[0], src, ref) < 0) {
+    if (sws_scale_frame(sws_ref_src, src, ref) < 0) {
         av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
                av_get_pix_fmt_name(ref->format), 
av_get_pix_fmt_name(src->format));
         goto error;
     }
 
-    sws[1]->flags  = mode->flags;
-    sws[1]->dither = mode->dither;
-    sws[1]->threads = opts->threads;
+    sws_src_dst->flags  = mode->flags;
+    sws_src_dst->dither = mode->dither;
+    sws_src_dst->threads = opts->threads;
 
-    if (sws_frame_setup(sws[1], dst, src) < 0) {
+    if (sws_frame_setup(sws_src_dst, dst, src) < 0) {
         av_log(NULL, AV_LOG_ERROR, "Failed to setup %s ---> %s\n",
                av_get_pix_fmt_name(src->format), 
av_get_pix_fmt_name(dst->format));
         goto error;
@@ -307,7 +311,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
 
     for (int i = 0; i < opts->iters; i++) {
         unref_buffers(dst);
-        if (sws_scale_frame(sws[1], dst, src) < 0) {
+        if (sws_scale_frame(sws_src_dst, dst, src) < 0) {
             av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
                    av_get_pix_fmt_name(src->format), 
av_get_pix_fmt_name(dst->format));
             goto error;
@@ -316,7 +320,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
 
     time = av_gettime_relative() - time;
 
-    if (sws_scale_frame(sws[2], out, dst) < 0) {
+    if (sws_scale_frame(sws_dst_out, out, dst) < 0) {
         av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
                av_get_pix_fmt_name(dst->format), 
av_get_pix_fmt_name(out->format));
         goto error;
@@ -353,7 +357,7 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
             goto error;
         }
 
-        if (sws_scale_frame(sws[2], out, dst) < 0)
+        if (sws_scale_frame(sws_dst_out, out, dst) < 0)
             goto error;
 
         get_ssim(ssim_sws, out, ref, comps);
@@ -675,12 +679,14 @@ int main(int argc, char **argv)
     av_lfg_init(&rand, 1);
     signal(SIGINT, exit_handler);
 
-    for (int i = 0; i < 3; i++) {
-        sws[i] = sws_alloc_context();
-        if (!sws[i])
-            goto error;
-        sws[i]->flags = SWS_BILINEAR;
-    }
+    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)
+        goto error;
+    sws_ref_src->flags = SWS_BILINEAR;
+    sws_src_dst->flags = SWS_BILINEAR;
+    sws_dst_out->flags = SWS_BILINEAR;
 
     ref = av_frame_alloc();
     if (!ref)
@@ -697,8 +703,9 @@ int main(int argc, char **argv)
 
     /* fall through */
 error:
-    for (int i = 0; i < 3; i++)
-        sws_free_context(&sws[i]);
+    sws_free_context(&sws_ref_src);
+    sws_free_context(&sws_src_dst);
+    sws_free_context(&sws_dst_out);
     av_frame_free(&ref);
     if (fp)
         fclose(fp);
-- 
2.52.0


>From 7af3c3c06577d78b80b210c4e3d796d49644f1f1 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Mon, 2 Mar 2026 16:21:29 +0100
Subject: [PATCH 09/11] swscale/tests/swscale: make auxiliary conversions
 bitexact

This prevents the propagation of dither_error across frames, and should
also improve reproducibility across platforms.

Also remove setting of flags for sws_src_dst early on, since it will
inevitable be overwritten during the tests.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/swscale.c | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index c86b99071f..e2367c7127 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -547,7 +547,7 @@ static int init_ref(AVFrame *ref, const struct options 
*opts)
         }
     }
 
-    ctx->flags = SWS_BILINEAR;
+    ctx->flags = SWS_BILINEAR | SWS_BITEXACT;
     ret = sws_scale_frame(ctx, ref, rgb);
 
 error:
@@ -684,9 +684,8 @@ int main(int argc, char **argv)
     sws_dst_out = sws_alloc_context();
     if (!sws_ref_src || !sws_src_dst || !sws_dst_out)
         goto error;
-    sws_ref_src->flags = SWS_BILINEAR;
-    sws_src_dst->flags = SWS_BILINEAR;
-    sws_dst_out->flags = SWS_BILINEAR;
+    sws_ref_src->flags = SWS_BILINEAR | SWS_BITEXACT;
+    sws_dst_out->flags = SWS_BILINEAR | SWS_BITEXACT;
 
     ref = av_frame_alloc();
     if (!ref)
-- 
2.52.0


>From 543d8670a7afc52a696bad366a2652f1a78b4fd0 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Tue, 3 Mar 2026 11:25:14 +0100
Subject: [PATCH 10/11] swscale/tests/swscale: avoid redundant ref->src
 conversions

The ref->src conversion only needs to be performed once per source
pixel format.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/swscale.c | 71 +++++++++++++++++++++++++-------------
 1 file changed, 47 insertions(+), 24 deletions(-)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index e2367c7127..d24510c722 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -254,9 +254,9 @@ error:
 static int run_test(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 float ssim_ref[4])
+                    const AVFrame *ref, AVFrame *src, const float ssim_ref[4])
 {
-    AVFrame *src = NULL, *dst = NULL, *out = NULL;
+    AVFrame *dst = NULL, *out = NULL;
     float ssim[4], ssim_sws[4];
     const int comps = fmt_comps(src_fmt) & fmt_comps(dst_fmt);
     int64_t time, time_ref = 0;
@@ -274,29 +274,33 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
     const float expected_loss = get_loss(ssim_expected);
     float loss;
 
-    src = av_frame_alloc();
     dst = av_frame_alloc();
     out = av_frame_alloc();
-    if (!src || !dst || !out)
+    if (!dst || !out)
         goto error;
 
-    av_frame_copy_props(src, ref);
+    if (src->format != src_fmt) {
+        av_frame_unref(src);
+        av_frame_copy_props(src, ref);
+        src->width  = ref->width;
+        src->height = ref->height;
+        src->format = src_fmt;
+        if (sws_scale_frame(sws_ref_src, src, ref) < 0) {
+            av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
+                   av_get_pix_fmt_name(ref->format), 
av_get_pix_fmt_name(src->format));
+            goto error;
+        }
+    }
+
     av_frame_copy_props(dst, ref);
     av_frame_copy_props(out, ref);
-    src->width  = out->width  = ref->width;
-    src->height = out->height = ref->height;
+    out->width  = ref->width;
+    out->height = ref->height;
     out->format = ref->format;
-    src->format = src_fmt;
     dst->format = dst_fmt;
     dst->width  = dst_w;
     dst->height = dst_h;
 
-    if (sws_scale_frame(sws_ref_src, src, ref) < 0) {
-        av_log(NULL, AV_LOG_ERROR, "Failed %s ---> %s\n",
-               av_get_pix_fmt_name(ref->format), 
av_get_pix_fmt_name(src->format));
-        goto error;
-    }
-
     sws_src_dst->flags  = mode->flags;
     sws_src_dst->dither = mode->dither;
     sws_src_dst->threads = opts->threads;
@@ -411,7 +415,6 @@ static int run_test(enum AVPixelFormat src_fmt, enum 
AVPixelFormat dst_fmt,
     fflush(stdout);
     ret = 0; /* fall through */
  error:
-    av_frame_free(&src);
     av_frame_free(&dst);
     av_frame_free(&out);
     return ret;
@@ -434,6 +437,12 @@ 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 = av_frame_alloc();
+    if (!src)
+        return AVERROR(ENOMEM);
+
+    int ret = 0;
+
     if (opts->src_fmt != AV_PIX_FMT_NONE)
         src_fmt_min = src_fmt_max = opts->src_fmt;
     if (opts->dst_fmt != AV_PIX_FMT_NONE)
@@ -460,9 +469,10 @@ static int run_self_tests(const AVFrame *ref, const struct 
options *opts)
                         if (ff_sfc64_get(&prng_state) > UINT64_MAX * 
opts->prob)
                             continue;
 
-                        if (run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h],
-                                     &mode, opts, ref, NULL) < 0)
-                            return -1;
+                        ret = run_test(src_fmt, dst_fmt, dst_w[w], dst_h[h],
+                                       &mode, opts, ref, src, NULL);
+                        if (ret < 0)
+                            goto error;
 
                         if (opts->flags >= 0 || opts->unscaled)
                             break;
@@ -476,13 +486,21 @@ static int run_self_tests(const AVFrame *ref, const 
struct options *opts)
         }
     }
 
-    return 0;
+    ret = 0;
+
+error:
+    av_frame_free(&src);
+    return ret;
 }
 
 static int run_file_tests(const AVFrame *ref, FILE *fp, const struct options 
*opts)
 {
     char buf[256];
-    int ret;
+    int ret = 0;
+
+    AVFrame *src = av_frame_alloc();
+    if (!src)
+        return AVERROR(ENOMEM);
 
     while (fgets(buf, sizeof(buf), fp)) {
         char src_fmt_str[21], dst_fmt_str[21];
@@ -509,18 +527,23 @@ static int run_file_tests(const AVFrame *ref, FILE *fp, 
const struct options *op
             sw != ref->width || sh != ref->height ||
             mode.dither >= SWS_DITHER_NB) {
             av_log(NULL, AV_LOG_FATAL, "malformed input file\n");
-            return -1;
+            goto error;
         }
 
         if (opts->src_fmt != AV_PIX_FMT_NONE && src_fmt != opts->src_fmt ||
             opts->dst_fmt != AV_PIX_FMT_NONE && dst_fmt != opts->dst_fmt)
             continue;
 
-        if (run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, ssim) < 0)
-            return -1;
+        ret = run_test(src_fmt, dst_fmt, dw, dh, &mode, opts, ref, src, ssim);
+        if (ret < 0)
+            goto error;
     }
 
-    return 0;
+    ret = 0;
+
+error:
+    av_frame_free(&src);
+    return ret;
 }
 
 static int init_ref(AVFrame *ref, const struct options *opts)
-- 
2.52.0


>From f8e4e5c9a6a33c1ec1a41471ba06cb0ec254cc02 Mon Sep 17 00:00:00 2001
From: Ramiro Polla <[email protected]>
Date: Fri, 6 Mar 2026 16:32:29 +0100
Subject: [PATCH 11/11] swscale/tests/swscale: add -s option to set frame size

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Ramiro Polla <[email protected]>
---
 libswscale/tests/swscale.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index d24510c722..e0fdebc3ff 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -28,6 +28,7 @@
 
 #undef HAVE_AV_CONFIG_H
 #include "libavutil/cpu.h"
+#include "libavutil/parseutils.h"
 #include "libavutil/pixdesc.h"
 #include "libavutil/lfg.h"
 #include "libavutil/sfc64.h"
@@ -596,6 +597,8 @@ static int parse_options(int argc, char **argv, struct 
options *opts, FILE **fp)
                     "       Only test the specified destination pixel format\n"
                     "   -src <pixfmt>\n"
                     "       Only test the specified source pixel format\n"
+                    "   -s <size>\n"
+                    "       Set frame size (WxH or abbreviation)\n"
                     "   -bench <iters>\n"
                     "       Run benchmarks with the specified number of 
iterations. This mode also increases the size of the test images\n"
                     "   -flags <flags>\n"
@@ -641,6 +644,11 @@ static int parse_options(int argc, char **argv, struct 
options *opts, FILE **fp)
                 fprintf(stderr, "invalid pixel format %s\n", argv[i + 1]);
                 return -1;
             }
+        } else if (!strcmp(argv[i], "-s")) {
+            if (av_parse_video_size(&opts->w, &opts->h, argv[i + 1]) < 0) {
+                fprintf(stderr, "invalid frame size %s\n", argv[i + 1]);
+                return -1;
+            }
         } else if (!strcmp(argv[i], "-bench")) {
             opts->bench = 1;
             opts->iters = atoi(argv[i + 1]);
-- 
2.52.0

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

Reply via email to