Commit 0c9844d133 for ffmpeg
commit 0c9844d1330b6f449a544c6d16b7c4d19ecc6da9
Author: Ramiro Polla <ramiro.polla@gmail.com>
Date: Tue Oct 20 22:15:49 2026 +0200
swscale/tests/swscale: add -coverage option
Set up each conversion with sws_frame_setup() without running it, and
print the backend and CPU flags of each of its passes.
Also print a summary with how many conversions are supported and an MD5
of the per-conversion lines. The MD5 changes when a conversion switches
to a different backend or CPU flags, even if the count stays the same.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Ramiro Polla <ramiro.polla@gmail.com>
diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c
index 23b41dc729..0e47df117f 100644
--- a/libswscale/tests/swscale.c
+++ b/libswscale/tests/swscale.c
@@ -33,6 +33,7 @@
#include "libavutil/parseutils.h"
#include "libavutil/pixdesc.h"
#include "libavutil/lfg.h"
+#include "libavutil/md5.h"
#include "libavutil/sfc64.h"
#include "libavutil/slicethread.h"
#include "libavutil/thread.h"
@@ -47,6 +48,7 @@
#include "libswscale/swscale.h"
#include "libswscale/format.h"
+#include "libswscale/swscale_internal.h"
#define IMPL_NEW 0
#define IMPL_LEGACY 1
@@ -77,6 +79,7 @@ struct options {
int api;
int pretty;
int backends;
+ int coverage;
};
struct mode {
@@ -133,6 +136,10 @@ static struct {
double speedup_max;
int speedup_count;
+ int coverage_total;
+ int coverage_supported;
+ struct AVMD5 *coverage_md5;
+
struct runner *runners;
AVFrame *ref;
FILE *fp;
@@ -631,6 +638,139 @@ static int init_frame(AVFrame **pframe, const AVFrame *ref,
return 0;
}
+static const char *backend_name(SwsBackend backend)
+{
+ switch (backend) {
+ case SWS_BACKEND_LEGACY: return "legacy";
+ case SWS_BACKEND_C: return "c";
+ case SWS_BACKEND_MEMCPY: return "memcpy";
+ case SWS_BACKEND_X86: return "x86";
+ case SWS_BACKEND_AARCH64: return "aarch64";
+ case SWS_BACKEND_SPIRV: return "spirv";
+ default: return "unknown";
+ }
+}
+
+static void describe_passes(AVBPrint *bp, const SwsGraph *graph)
+{
+ if (graph->noop) {
+ av_bprintf(bp, "noop");
+ return;
+ }
+
+ for (int i = 0; i < graph->num_passes; i++) {
+ const SwsPass *pass = graph->passes[i];
+ av_bprintf(bp, "%s%s", i ? ", " : "", backend_name(pass->backend));
+ if (pass->cpu_flags)
+ av_bprintf(bp, "/0x%x", pass->cpu_flags);
+ }
+}
+
+static void print_coverage_result(const AVFrame *src, const AVFrame *dst,
+ const struct mode *mode,
+ const struct options *opts,
+ const SwsGraph *graph)
+{
+ AVBPrint bp;
+
+ av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
+ print_test_params(&bp, src, dst, mode, opts);
+ av_bprintf(&bp, ", ");
+ if (graph) {
+ describe_passes(&bp, graph);
+ } else {
+ av_bprintf(&bp, "unsupported");
+ }
+ av_bprintf(&bp, "\n");
+
+ av_md5_update(state.coverage_md5, (const uint8_t *) bp.str, strlen(bp.str));
+
+ if (av_log_get_level() >= AV_LOG_INFO) {
+ fputs(bp.str, stdout);
+ fflush(stdout);
+ }
+}
+
+/* Sets up the src -> dst conversion without running it, and records whether
+ * the selected backends support it and which passes they use */
+static int check_coverage(struct runner *runner, const struct test_case *t,
+ const struct mode *mode, const struct options *opts)
+{
+ const AVFrame *ref = state.ref;
+ SwsContext *sws_src_dst = runner->sws_src_dst;
+ AVFrame *src = NULL;
+ AVFrame *dst = NULL;
+ const SwsGraph *graph = NULL;
+ int ret;
+
+ ret = init_frame(&src, ref, ref->width, ref->height, t->src_fmt);
+ if (ret < 0)
+ goto error;
+ ret = init_frame(&dst, ref, t->dst_w, t->dst_h, t->dst_fmt);
+ if (ret < 0)
+ goto error;
+
+ sws_src_dst->flags = mode->flags;
+ sws_src_dst->dither = mode->dither;
+ sws_src_dst->scaler = mode->scaler;
+ sws_src_dst->scaler_sub = mode->scaler_sub;
+ sws_src_dst->threads = opts->threads;
+ sws_src_dst->backends = opts->backends;
+
+ ret = sws_frame_setup(sws_src_dst, dst, src);
+ if (ret < 0 && ret != AVERROR(ENOTSUP))
+ goto error;
+
+ state.coverage_total++;
+ if (ret >= 0) {
+ graph = sws_internal(sws_src_dst)->graph[0];
+ state.coverage_supported++;
+ }
+
+ print_coverage_result(src, dst, mode, opts, graph);
+
+ ret = 0;
+
+error:
+ av_frame_free(&src);
+ av_frame_free(&dst);
+ return ret;
+}
+
+static const SwsFlags coverage_flags[] = {
+ 0,
+ SWS_ACCURATE_RND,
+ SWS_BITEXACT,
+ SWS_ACCURATE_RND | SWS_BITEXACT,
+};
+
+static int run_coverage(struct runner *runner, const struct test_case *t,
+ const struct options *opts)
+{
+ if (opts->flags >= 0)
+ return check_coverage(runner, t, &t->mode, opts);
+
+ for (int i = 0; i < FF_ARRAY_ELEMS(coverage_flags); i++) {
+ struct mode flags_mode = t->mode;
+ flags_mode.flags |= coverage_flags[i];
+ int ret = check_coverage(runner, t, &flags_mode, opts);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static void print_coverage_summary(void)
+{
+ uint8_t md5[16];
+
+ av_md5_final(state.coverage_md5, md5);
+ for (int i = 0; i < sizeof(md5); i++)
+ printf("%02x", md5[i]);
+ printf(" %d/%d\n", state.coverage_supported, state.coverage_total);
+}
+
/* Runs a series of ref -> src -> dst -> out, and compares out vs ref */
static int run_test(struct runner *runner, const struct test_case *t,
const struct options *opts)
@@ -786,6 +926,8 @@ 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);
+ if (opts->coverage)
+ return run_coverage(&state.runners[0], t, opts);
return run_test(&state.runners[0], t, opts);
}
@@ -1149,6 +1291,10 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp)
" Uses the specified cpuflags in the tests\n"
" -pretty <1 or 0>\n"
" Align fields while printing results\n"
+ " -coverage <1 or 0>\n"
+ " Set up each conversion without running it, and print the backend and\n"
+ " CPU flags of each of its passes. Also print a summary with how many\n"
+ " conversions are supported and an MD5 of the per-conversion lines.\n"
" -v <level>\n"
" Enable log verbosity at given level\n"
);
@@ -1282,6 +1428,8 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp)
opts->prob = atof(argv[i + 1]);
} else if (!strcmp(argv[i], "-pretty")) {
opts->pretty = atoi(argv[i + 1]);
+ } else if (!strcmp(argv[i], "-coverage")) {
+ opts->coverage = atoi(argv[i + 1]);
} else if (!strcmp(argv[i], "-v")) {
av_log_set_level(atoi(argv[i + 1]));
} else {
@@ -1292,6 +1440,13 @@ bad_option:
}
}
+ if (opts->coverage &&
+ (state.hw_device_ctx || opts->api == IMPL_LEGACY || opts->runners > 1)) {
+ fprintf(stderr, "-coverage is not supported with -hw, -api legacy or multiple runners\n");
+ ret = AVERROR(EINVAL);
+ goto end;
+ }
+
if (opts->w < 0 || opts->h < 0) {
opts->w = opts->bench ? 1920 : 96;
opts->h = opts->bench ? 1080 : 96;
@@ -1358,9 +1513,18 @@ int main(int argc, char **argv)
state.ref->height = opts.h;
state.ref->format = AV_PIX_FMT_YUVA444P;
- ret = init_ref(state.ref, &opts);
- if (ret < 0)
- goto error;
+ if (opts.coverage) {
+ state.coverage_md5 = av_md5_alloc();
+ if (!state.coverage_md5) {
+ ret = AVERROR(ENOMEM);
+ goto error;
+ }
+ av_md5_init(state.coverage_md5);
+ } else {
+ ret = init_ref(state.ref, &opts);
+ if (ret < 0)
+ goto error;
+ }
if (opts.runners > 1) {
ret = av_thread_message_queue_alloc(&state.queue, opts.runners,
@@ -1380,6 +1544,9 @@ int main(int argc, char **argv)
ret = run_tests(&opts);
}
+ if (opts.coverage && ret >= 0)
+ print_coverage_summary();
+
/* fall through */
error:
avpriv_slicethread_free(&slicethread);
@@ -1390,6 +1557,7 @@ error:
av_buffer_unref(&state.hw_device_ctx);
av_hwframe_constraints_free(&state.hw_device_constr);
av_frame_free(&state.ref);
+ av_freep(&state.coverage_md5);
if (state.fp)
fclose(state.fp);
exit_handler(ret);