PR #23322 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23322 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23322.patch
Re-submitting this here as a formal PR. >From 5d44f43ea2007780b922edabc487f2f39844eed1 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 1 Jun 2026 17:34:30 +0200 Subject: [PATCH 1/7] swscale: add missing validation for newly added enums Gives slightly better error messages for invalid values. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/swscale.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libswscale/swscale.c b/libswscale/swscale.c index 25bd8fc293..ca7eddbdef 100644 --- a/libswscale/swscale.c +++ b/libswscale/swscale.c @@ -1441,6 +1441,9 @@ static int validate_params(SwsContext *ctx) VALIDATE(threads, 0, SWS_MAX_THREADS); VALIDATE(dither, 0, SWS_DITHER_NB - 1) VALIDATE(alpha_blend, 0, SWS_ALPHA_BLEND_NB - 1) + VALIDATE(intent, 0, SWS_INTENT_NB - 1); + VALIDATE(scaler, 0, SWS_SCALE_NB - 1) + VALIDATE(scaler_sub, 0, SWS_SCALE_NB - 1) return 0; } -- 2.52.0 >From d188ff3cf1b8e2acea84aa753ac37dea0cbdab43 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 1 Jun 2026 17:17:35 +0200 Subject: [PATCH 2/7] swscale: add new SwsContext.backends option This allows constraining the set of available backends. This serves as a better replacement for the "unstable" flag, which is a bit ambiguous. Allows users to, for example, opt into the memcpy or x86 backend, while excluding e.g. the upcoming JIT backends. Signed-off-by: Niklas Haas <[email protected]> --- doc/APIchanges | 3 +++ doc/scaler.texi | 41 +++++++++++++++++++++++++++++++++++ libswscale/aarch64/ops.c | 1 + libswscale/graph.c | 12 ++++++++-- libswscale/ops_backend.c | 1 + libswscale/ops_dispatch.c | 5 ++++- libswscale/ops_dispatch.h | 1 + libswscale/ops_memcpy.c | 1 + libswscale/options.c | 13 +++++++++++ libswscale/swscale.h | 39 +++++++++++++++++++++++++++++++-- libswscale/swscale_internal.h | 2 ++ libswscale/utils.c | 12 ++++++++++ libswscale/version.h | 2 +- libswscale/vulkan/ops.c | 2 ++ libswscale/x86/ops.c | 1 + 15 files changed, 130 insertions(+), 6 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index 4695a38e8a..325d50e6b8 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28 API changes, most recent first: +2026-06-xx - xxxxxxxxxx - lsws 9.8.100 - swscale.h + Add enum SwsBackend, SwsBackendFlags and SwsContext.backends + 2026-05-xx - xxxxxxxxxx - lavf 62.19.100 - avformat.h Add AVStreamGroupLayeredVideo Add AVStreamGroup.params.layered_video diff --git a/doc/scaler.texi b/doc/scaler.texi index aecc3ad248..a66c8217f8 100644 --- a/doc/scaler.texi +++ b/doc/scaler.texi @@ -191,6 +191,47 @@ No blending @end table +@item sws_backends +Set the allowed swscale backends. This is a flags option, so multiple backends +may be combined. + +@table @samp +@item auto +Automatic selection. Equal to either @samp{stable} or @samp{all} depending on +whether or not the @samp{unstable} flag is set. This is the default value. + +@item stable +All stable backends. + +@item unstable +All unstable backends. + +@item all +All available backends. + +@item legacy +Legacy swscale code. + +@item c +Template-based reference code. + +@item memcpy +Fast path using libc @code{memcpy}. + +@item x86 +x86 SIMD kernels. + +@item aarch64 +AArch64 NEON kernels. + +@item spirv +Vulkan SPIR-V backend. + +@item glsl +Vulkan GLSL backend. + +@end table + @end table @c man end SCALER OPTIONS diff --git a/libswscale/aarch64/ops.c b/libswscale/aarch64/ops.c index 753aef51ae..4598a8db6b 100644 --- a/libswscale/aarch64/ops.c +++ b/libswscale/aarch64/ops.c @@ -254,6 +254,7 @@ error: /*********************************************************************/ const SwsOpBackend backend_aarch64 = { .name = "aarch64", + .flags = SWS_BACKEND_AARCH64, .compile = aarch64_compile, .hw_format = AV_PIX_FMT_NONE, }; diff --git a/libswscale/graph.c b/libswscale/graph.c index 2ee1bd84a2..37c11f9dd5 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -548,6 +548,9 @@ static int add_legacy_sws_pass(SwsGraph *graph, const SwsFormat *src, { int ret, warned = 0; SwsContext *const ctx = graph->ctx; + const SwsBackend backend = ff_sws_enabled_backends(ctx); + if (!(backend & SWS_BACKEND_LEGACY)) + return AVERROR(ENOTSUP); if (src->hw_format != AV_PIX_FMT_NONE || dst->hw_format != AV_PIX_FMT_NONE) return AVERROR(ENOTSUP); @@ -626,8 +629,12 @@ static int add_convert_pass(SwsGraph *graph, const SwsFormat *src, SwsContext *ctx = graph->ctx; int ret = AVERROR(ENOTSUP); - /* Mark the entire new ops infrastructure as experimental for now */ - if (!(ctx->flags & SWS_UNSTABLE)) + /* Preemptively skip the ops list generation if the backend was + * constrained to the legacy implementation only. This would + * normally also fail in ff_sws_compile_pass() with the same + * error, but this way saves a bit of unnecessary overhead */ + const SwsBackend backends = ff_sws_enabled_backends(ctx); + if (backends == SWS_BACKEND_LEGACY) goto fail; SwsOpList *ops; @@ -908,6 +915,7 @@ static int opts_equal(const SwsContext *c1, const SwsContext *c2) c1->intent == c2->intent && c1->scaler == c2->scaler && c1->scaler_sub == c2->scaler_sub && + c1->backends == c2->backends && !memcmp(c1->scaler_params, c2->scaler_params, sizeof(c1->scaler_params)); } diff --git a/libswscale/ops_backend.c b/libswscale/ops_backend.c index 07919539e5..254814ee37 100644 --- a/libswscale/ops_backend.c +++ b/libswscale/ops_backend.c @@ -111,6 +111,7 @@ static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out) const SwsOpBackend backend_c = { .name = "c", + .flags = SWS_BACKEND_C, .compile = compile, .hw_format = AV_PIX_FMT_NONE, }; diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index 8b04ef1c96..f73f801a20 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -28,6 +28,7 @@ #include "ops.h" #include "ops_internal.h" #include "ops_dispatch.h" +#include "swscale_internal.h" typedef struct SwsOpPass { SwsCompiledOp comp; @@ -96,10 +97,12 @@ int ff_sws_ops_compile(SwsContext *ctx, const SwsOpBackend *backend, if (backend) return compile_backend(ctx, backend, ops, out); + const SwsBackend enabled = ff_sws_enabled_backends(ctx); for (int n = 0; ff_sws_op_backends[n]; n++) { const SwsOpBackend *backend = ff_sws_op_backends[n]; if (ops->src.hw_format != backend->hw_format || - ops->dst.hw_format != backend->hw_format) + ops->dst.hw_format != backend->hw_format || + !(enabled & backend->flags)) continue; if (compile_backend(ctx, backend, ops, out) < 0) continue; diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index be771da9a9..1678cc4bf2 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -129,6 +129,7 @@ void ff_sws_compiled_op_unref(SwsCompiledOp *comp); typedef struct SwsOpBackend { const char *name; /* Descriptive name for this backend */ + SwsBackend flags; /* Set of SWS_BACKEND_* */ /** * Compile an operation list to an implementation chain. May modify `ops` diff --git a/libswscale/ops_memcpy.c b/libswscale/ops_memcpy.c index 7fcd230d7b..769de96747 100644 --- a/libswscale/ops_memcpy.c +++ b/libswscale/ops_memcpy.c @@ -143,6 +143,7 @@ static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out) const SwsOpBackend backend_murder = { .name = "memcpy", + .flags = SWS_BACKEND_MEMCPY, .compile = compile, .hw_format = AV_PIX_FMT_NONE, }; diff --git a/libswscale/options.c b/libswscale/options.c index 8109f1d23a..960c6b91dc 100644 --- a/libswscale/options.c +++ b/libswscale/options.c @@ -106,6 +106,19 @@ static const AVOption swscale_options[] = { { "saturation", "saturation mapping", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_INTENT_SATURATION }, .flags = VE, .unit = "intent" }, { "absolute_colorimetric", "absolute colorimetric clipping", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_INTENT_ABSOLUTE_COLORIMETRIC }, .flags = VE, .unit = "intent" }, + { "sws_backends", "set allowed swscale backends", OFFSET(backends), AV_OPT_TYPE_FLAGS, { .i64 = 0 }, .flags = VE, .unit = "sws_backend", .max = UINT_MAX }, + { "auto", "automatic selection", 0, AV_OPT_TYPE_CONST, { .i64 = 0 }, .flags = VE, .unit = "sws_backend" }, + { "stable", "All stable backends", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_STABLE }, .flags = VE, .unit = "sws_backend" }, + { "unstable", "All unstable backends", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_UNSTABLE }, .flags = VE, .unit = "sws_backend" }, + { "all", "All available backends", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_ALL }, .flags = VE, .unit = "sws_backend" }, + { "legacy", "legacy swscale code", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_LEGACY }, .flags = VE, .unit = "sws_backend" }, + { "c", "template-based reference code", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_C }, .flags = VE, .unit = "sws_backend" }, + { "memcpy", "fast path using libc memcpy", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_MEMCPY }, .flags = VE, .unit = "sws_backend" }, + { "x86", "x86 SIMD kernels", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_X86 }, .flags = VE, .unit = "sws_backend" }, + { "aarch64", "AArch64 NEON kernels", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_AARCH64 }, .flags = VE, .unit = "sws_backend" }, + { "spirv", "Vulkan SPIR-V backend", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_SPIRV }, .flags = VE, .unit = "sws_backend" }, + { "glsl", "Vulkan GLSL backend", 0, AV_OPT_TYPE_CONST, { .i64 = SWS_BACKEND_GLSL }, .flags = VE, .unit = "sws_backend" }, + { NULL } }; diff --git a/libswscale/swscale.h b/libswscale/swscale.h index fa0769aa23..8954f1a7d8 100644 --- a/libswscale/swscale.h +++ b/libswscale/swscale.h @@ -107,6 +107,29 @@ typedef enum SwsScaler { SWS_SCALE_MAX_ENUM = 0x7FFFFFFF, ///< force size to 32 bits, not a valid filter type } SwsScaler; +typedef enum SwsBackend { + /* Stable backends */ + SWS_BACKEND_LEGACY = (1 << 0), ///< Legacy bespoke format-specific code + SWS_BACKEND_STABLE = SWS_BACKEND_LEGACY, + + /* Unstable backends (auto-selected only if SWS_UNSTABLE is enabled) */ + SWS_BACKEND_C = (1 << 1), ///< Template-based C reference implementation + SWS_BACKEND_MEMCPY = (1 << 2), ///< Fast path using libc memcpy() / memset() + SWS_BACKEND_X86 = (1 << 3), ///< Chained x86 SIMD kernels + SWS_BACKEND_AARCH64 = (1 << 4), ///< Chained AArch64 NEON kernels + SWS_BACKEND_SPIRV = (1 << 5), ///< Vulkan SPIR-V backend + SWS_BACKEND_GLSL = (1 << 6), ///< Vulkan GLSL backend + SWS_BACKEND_UNSTABLE = SWS_BACKEND_C | + SWS_BACKEND_MEMCPY | + SWS_BACKEND_X86 | + SWS_BACKEND_AARCH64 | + SWS_BACKEND_SPIRV | + SWS_BACKEND_GLSL, + + SWS_BACKEND_ALL = SWS_BACKEND_STABLE | SWS_BACKEND_UNSTABLE, + SWS_BACKEND_MAX_ENUM = 0x7FFFFFFF, ///< force size to 32 bits, not a valid backend +} SwsBackend; + typedef enum SwsFlags { /** * Return an error on underspecified conversions. Without this flag, @@ -280,6 +303,16 @@ typedef struct SwsContext { */ SwsScaler scaler_sub; + /** + * Bitmask of SWS_BACKEND_*. If non-zero, this will restrict the available + * backends to the specified set. If left as zero, a default set of + * backends will be selected automatically (based on SWS_UNSTABLE). + * + * Note: This is only relevant for the new API (sws_scale_frame()). The + * stateful legacy API always implies SWS_BACKEND_LEGACY. + */ + SwsBackend backends; + /* Remember to add new fields to graph.c:opts_equal() */ } SwsContext; @@ -299,7 +332,8 @@ void sws_free_context(SwsContext **ctx); ***************************/ /** - * Test if a given (software) pixel format is supported. + * Test if a given (software) pixel format is supported by any backend, + * excluding unstable backends. * * @param output If 0, test if compatible with the source/input frame; * otherwise, with the destination/output frame. @@ -310,7 +344,8 @@ void sws_free_context(SwsContext **ctx); int sws_test_format(enum AVPixelFormat format, int output); /** - * Test if a given hardware pixel format is supported. + * Test if a given hardware pixel format is supported by any backend, + * excluding unstable backends. * * @param format The hardware format to check, or AV_PIX_FMT_NONE. * diff --git a/libswscale/swscale_internal.h b/libswscale/swscale_internal.h index f50b769f1f..9a822cb8e4 100644 --- a/libswscale/swscale_internal.h +++ b/libswscale/swscale_internal.h @@ -81,6 +81,8 @@ static inline SwsInternal *sws_internal(const SwsContext *sws) return (SwsInternal *) sws; } +SwsBackend ff_sws_enabled_backends(const SwsContext *ctx); + typedef struct Range { unsigned int start; unsigned int len; diff --git a/libswscale/utils.c b/libswscale/utils.c index 41c1cc5bb6..6ecba2c0b3 100644 --- a/libswscale/utils.c +++ b/libswscale/utils.c @@ -68,6 +68,18 @@ #include "vulkan/ops.h" #endif +SwsBackend ff_sws_enabled_backends(const SwsContext *ctx) +{ + if (ctx->backends) + return ctx->backends; + + SwsBackend fallback = SWS_BACKEND_STABLE; + if (ctx->flags & SWS_UNSTABLE) + fallback |= SWS_BACKEND_UNSTABLE; + + return fallback; +} + /** * Allocate and return an SwsContext without performing initialization. */ diff --git a/libswscale/version.h b/libswscale/version.h index 4c6af261e6..c0610fec1e 100644 --- a/libswscale/version.h +++ b/libswscale/version.h @@ -28,7 +28,7 @@ #include "version_major.h" -#define LIBSWSCALE_VERSION_MINOR 7 +#define LIBSWSCALE_VERSION_MINOR 8 #define LIBSWSCALE_VERSION_MICRO 100 #define LIBSWSCALE_VERSION_INT AV_VERSION_INT(LIBSWSCALE_VERSION_MAJOR, \ diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c index c944e120c5..5f289baf12 100644 --- a/libswscale/vulkan/ops.c +++ b/libswscale/vulkan/ops.c @@ -1689,6 +1689,7 @@ static int compile_spirv(SwsContext *sws, SwsOpList *ops, SwsCompiledOp *out) const SwsOpBackend backend_spirv = { .name = "spirv", + .flags = SWS_BACKEND_SPIRV, .compile = compile_spirv, .hw_format = AV_PIX_FMT_VULKAN, }; @@ -1702,6 +1703,7 @@ static int compile_glsl(SwsContext *sws, SwsOpList *ops, SwsCompiledOp *out) const SwsOpBackend backend_glsl = { .name = "glsl", + .flags = SWS_BACKEND_GLSL, .compile = compile_glsl, .hw_format = AV_PIX_FMT_VULKAN, }; diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index 20369652cc..c8dde4d5d4 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -1054,6 +1054,7 @@ static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out) const SwsOpBackend backend_x86 = { .name = "x86", + .flags = SWS_BACKEND_X86, .compile = compile, .hw_format = AV_PIX_FMT_NONE, }; -- 2.52.0 >From 25b95fc5c542954e27a64a0db692e1e0e1f960cf Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 1 Jun 2026 17:32:03 +0200 Subject: [PATCH 3/7] swscale/tests/swscale: add -backends option Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index a6a034c420..cd83f3ccad 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -60,6 +60,7 @@ struct options { int unscaled; int legacy; int pretty; + int backends; }; struct mode { @@ -244,6 +245,7 @@ static int scale_new(AVFrame *dst, const AVFrame *src, sws_src_dst->flags = mode->flags; sws_src_dst->dither = mode->dither; sws_src_dst->threads = opts->threads; + sws_src_dst->backends = opts->backends; int ret = sws_frame_setup(sws_src_dst, dst, src); if (ret < 0) { @@ -898,6 +900,8 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) " Test with a specific combination of flags\n" " -dither <mode>\n" " Test with a specific dither mode\n" + " -backend <backends>\n" + " Restrict to the given set of allowed swscale backends\n" " -unscaled <1 or 0>\n" " If 1, test only conversions that do not involve scaling\n" " -legacy <1 or 0>\n" @@ -966,6 +970,15 @@ static int parse_options(int argc, char **argv, struct options *opts, FILE **fp) fprintf(stderr, "invalid flags %s\n", argv[i + 1]); return -1; } + } else if (!strcmp(argv[i], "-backends")) { + SwsContext *dummy = sws_alloc_context(); + const AVOption *backends_opt = av_opt_find(dummy, "sws_backends", NULL, 0, 0); + int ret = av_opt_eval_flags(dummy, backends_opt, argv[i + 1], &opts->backends); + sws_free_context(&dummy); + if (ret < 0) { + fprintf(stderr, "invalid backends %s\n", argv[i + 1]); + return -1; + } } else if (!strcmp(argv[i], "-dither")) { opts->dither = atoi(argv[i + 1]); } else if (!strcmp(argv[i], "-unscaled")) { -- 2.52.0 >From 7804d07934219cf73d02a896eab97cf97fc630df Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 1 Jun 2026 18:04:29 +0200 Subject: [PATCH 4/7] swscale/format: generalize ff_test_fmt() to take SwsBackend This allows us to test support in either the legacy code, or the ops-based code, or both. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 45 +++++++++++++++++++++++++++++++++++++++----- libswscale/format.h | 13 ++++++++++++- libswscale/swscale.c | 5 +++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index 7782a0233d..22e75e71dc 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -554,11 +554,27 @@ bool ff_infer_colors(SwsColor *src, SwsColor *dst) return incomplete; } -int sws_test_format(enum AVPixelFormat format, int output) +/* Variant of sws_test_format() for the ops-based code */ +static int test_format_ops(enum AVPixelFormat format, int output); +static int test_format_legacy(enum AVPixelFormat format, int output) { return output ? sws_isSupportedOutput(format) : sws_isSupportedInput(format); } +int ff_sws_test_pixfmt_backend(const SwsBackend backends, + enum AVPixelFormat format, int output) +{ + /* The only non-ops backend is the legacy backend */ + const SwsBackend backends_ops = SWS_BACKEND_ALL ^ SWS_BACKEND_LEGACY; + return (backends & SWS_BACKEND_LEGACY) && test_format_legacy(format, output) || + (backends & backends_ops) && test_format_ops(format, output); +} + +int sws_test_format(enum AVPixelFormat format, int output) +{ + return ff_sws_test_pixfmt_backend(SWS_BACKEND_STABLE, format, output); +} + int sws_test_hw_format(enum AVPixelFormat format) { switch (format) { @@ -611,10 +627,10 @@ static int test_loc(enum AVChromaLocation loc) return (unsigned)loc < AVCHROMA_LOC_NB; } -int ff_test_fmt(const SwsFormat *fmt, int output) +int ff_test_fmt(const SwsBackend backends, const SwsFormat *fmt, int output) { return fmt->width > 0 && fmt->height > 0 && - sws_test_format (fmt->format, output) && + ff_sws_test_pixfmt_backend(backends, fmt->format, output) && sws_test_colorspace(fmt->csp, output) && sws_test_primaries (fmt->color.prim, output) && sws_test_transfer (fmt->color.trc, output) && @@ -627,7 +643,7 @@ int sws_test_frame(const AVFrame *frame, int output) { for (int field = 0; field < 2; field++) { const SwsFormat fmt = ff_fmt_from_frame(frame, field); - if (!ff_test_fmt(&fmt, output)) + if (!ff_test_fmt(SWS_BACKEND_STABLE, &fmt, output)) return 0; if (!fmt.interlaced) break; @@ -907,6 +923,18 @@ static int fmt_analyze(enum AVPixelFormat fmt, SwsReadWriteOp *rw_op, return 0; } +static int test_format_ops(enum AVPixelFormat format, int output) +{ + SwsReadWriteOp rw; + SwsSwizzleOp swizzle; + SwsPackOp pack; + SwsShiftOp shift; + SwsPixelType pixel_type, raw_type; + int ret = fmt_analyze(format, &rw, &pack, &swizzle, &shift, + &pixel_type, &raw_type); + return ret == 0; +} + static SwsSwizzleOp swizzle_inv(SwsSwizzleOp swiz) { /* Input[x] =: Output[swizzle.x] */ unsigned out[4]; @@ -1670,4 +1698,11 @@ fail: return ret; } -#endif /* CONFIG_UNSTABLE */ +#else /* !CONFIG_UNSTABLE */ + +static int test_format_ops(enum AVPixelFormat format, int output) +{ + return 0; +} + +#endif diff --git a/libswscale/format.h b/libswscale/format.h index 844ea353b3..67f25d7006 100644 --- a/libswscale/format.h +++ b/libswscale/format.h @@ -149,7 +149,18 @@ static inline int ff_fmt_align(enum AVPixelFormat fmt) } } -int ff_test_fmt(const SwsFormat *fmt, int output); +/* Internal helper to test a format for either the legacy backend or the + * ops-based backends, depending on `backends` (must be nonzero). */ +int ff_sws_test_pixfmt_backend(const SwsBackend backends, + enum AVPixelFormat format, int output); + +/** + * Statically test if a given format is supported by the given set of + * backends. This is a heuristic, which may have false positives if a + * specific backend does not actually implement all operations that would be + * required to support the format. + */ +int ff_test_fmt(SwsBackend backends, const SwsFormat *fmt, int output); /* Returns true if the formats are incomplete, false otherwise */ bool ff_infer_colors(SwsColor *src, SwsColor *dst); diff --git a/libswscale/swscale.c b/libswscale/swscale.c index ca7eddbdef..558e332eb2 100644 --- a/libswscale/swscale.c +++ b/libswscale/swscale.c @@ -1488,6 +1488,7 @@ int sws_frame_setup(SwsContext *ctx, const AVFrame *dst, const AVFrame *src) } int dst_width = dst->width; + const SwsBackend backends = ff_sws_enabled_backends(ctx); for (int field = 0; field < 2; field++) { SwsFormat src_fmt = ff_fmt_from_frame(src, field); SwsFormat dst_fmt = ff_fmt_from_frame(dst, field); @@ -1499,8 +1500,8 @@ int sws_frame_setup(SwsContext *ctx, const AVFrame *dst, const AVFrame *src) goto fail; } - src_ok = ff_test_fmt(&src_fmt, 0); - dst_ok = ff_test_fmt(&dst_fmt, 1); + src_ok = ff_test_fmt(backends, &src_fmt, 0); + dst_ok = ff_test_fmt(backends, &dst_fmt, 1); if ((!src_ok || !dst_ok) && !ff_props_equal(&src_fmt, &dst_fmt)) { err_msg = src_ok ? "Unsupported output" : "Unsupported input"; ret = AVERROR(ENOTSUP); -- 2.52.0 >From 89baec50d60cbff9b1d03e2bddbd08bc5b0d8c5c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 3 Jun 2026 21:10:30 +0200 Subject: [PATCH 5/7] swscale/graph: add metadata about backends in use Not currently publicly visible, but useful inside the test framework nonetheless. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/graph.c | 2 ++ libswscale/graph.h | 2 ++ libswscale/ops_dispatch.c | 11 ++++++++--- libswscale/ops_dispatch.h | 3 +++ 4 files changed, 15 insertions(+), 3 deletions(-) diff --git a/libswscale/graph.c b/libswscale/graph.c index 37c11f9dd5..a5547de44d 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -487,6 +487,7 @@ static int init_legacy_subpass(SwsGraph *graph, SwsContext *sws, setup_legacy_swscale, sws, free_legacy_swscale, &pass); if (ret < 0) return ret; + pass->backend = SWS_BACKEND_LEGACY; /** * For slice threading, we need to create sub contexts, similar to how @@ -853,6 +854,7 @@ int ff_sws_graph_init(SwsGraph *graph, SwsContext *ctx, const SwsFormat *dst, /* Resolve output buffers for all intermediate passes */ for (int i = 0; i < graph->num_passes; i++) { + graph->backend |= graph->passes[i]->backend; ret = pass_alloc_output(graph->passes[i]->input); if (ret < 0) goto error; diff --git a/libswscale/graph.h b/libswscale/graph.h index 8098aaed06..adf4b19675 100644 --- a/libswscale/graph.h +++ b/libswscale/graph.h @@ -81,6 +81,7 @@ struct SwsPass { * are always equal to (or smaller than, for the last slice) `slice_h`. */ SwsPassFunc run; + SwsBackend backend; /* backend this pass is using, or 0 */ enum AVPixelFormat format; /* new pixel format */ int width, height; /* new output size */ int slice_h; /* filter granularity */ @@ -124,6 +125,7 @@ typedef struct SwsGraph { int num_threads; /* resolved at init() time */ bool incomplete; /* set during init() if formats had to be inferred */ bool noop; /* set during init() if the graph is a no-op */ + SwsBackend backend; /* backends this graph is using, set during init() */ AVBufferRef *hw_frames_ref; diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index f73f801a20..927cb75509 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -77,6 +77,7 @@ static int compile_backend(SwsContext *ctx, const SwsOpBackend *backend, goto fail; } + compiled.backend = backend; *out = compiled; av_log(ctx, AV_LOG_VERBOSE, "Compiled using backend '%s': " @@ -501,9 +502,12 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, if (p->comp.opaque) { SwsCompiledOp c = *comp; av_free(p); - return ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height, - input, c.slice_align, c.func_opaque, - NULL, c.priv, c.free, output); + ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height, + input, c.slice_align, c.func_opaque, + NULL, c.priv, c.free, output); + if (ret >= 0) + (*output)->backend = comp->backend->flags; + return ret; } const SwsOp *read = ff_sws_op_list_input(ops); @@ -585,6 +589,7 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, if (ret < 0) return ret; + (*output)->backend = comp->backend->flags; align_pass(input, comp->block_size, comp->over_read, p->pixel_bits_in); align_pass(*output, comp->block_size, comp->over_write, p->pixel_bits_out); return 0; diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index 1678cc4bf2..a2aa00a813 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -111,6 +111,9 @@ typedef struct SwsCompiledOp { */ bool opaque; + /* Set by ff_sws_ops_compile(), informative */ + const struct SwsOpBackend *backend; + /* Execution parameters for all functions */ int slice_align; /* slice height alignment */ int cpu_flags; /* active set of CPU flags (informative) */ -- 2.52.0 >From 3ded0a52dedbebdbf0df0fc6c4dc4def24a1c4c5 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 3 Jun 2026 21:29:23 +0200 Subject: [PATCH 6/7] swscale/graph: move legacy fallback out of add_convert_pass() Signed-off-by: Niklas Haas <[email protected]> --- libswscale/graph.c | 43 ++++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 21 deletions(-) diff --git a/libswscale/graph.c b/libswscale/graph.c index a5547de44d..acd0ce615e 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -622,13 +622,12 @@ static int add_legacy_sws_pass(SwsGraph *graph, const SwsFormat *src, * Format conversion and scaling * *********************************/ -#if CONFIG_UNSTABLE -static int add_convert_pass(SwsGraph *graph, const SwsFormat *src, - const SwsFormat *dst, SwsPass *input, - SwsPass **output) +static int add_ops_convert_pass(SwsGraph *graph, const SwsFormat *src, + const SwsFormat *dst, SwsPass *input, + SwsPass **output) { +#if CONFIG_UNSTABLE SwsContext *ctx = graph->ctx; - int ret = AVERROR(ENOTSUP); /* Preemptively skip the ops list generation if the backend was * constrained to the legacy implementation only. This would @@ -636,12 +635,12 @@ static int add_convert_pass(SwsGraph *graph, const SwsFormat *src, * error, but this way saves a bit of unnecessary overhead */ const SwsBackend backends = ff_sws_enabled_backends(ctx); if (backends == SWS_BACKEND_LEGACY) - goto fail; + return AVERROR(ENOTSUP); SwsOpList *ops; - ret = ff_sws_op_list_generate(ctx, src, dst, &ops, &graph->incomplete); + int ret = ff_sws_op_list_generate(ctx, src, dst, &ops, &graph->incomplete); if (ret < 0) - goto fail; + return ret; av_log(ctx, AV_LOG_VERBOSE, "Conversion pass for %s -> %s:\n", av_get_pix_fmt_name(src->format), av_get_pix_fmt_name(dst->format)); @@ -649,22 +648,24 @@ static int add_convert_pass(SwsGraph *graph, const SwsFormat *src, av_log(ctx, AV_LOG_DEBUG, "Unoptimized operation list:\n"); ff_sws_op_list_print(ctx, AV_LOG_DEBUG, AV_LOG_TRACE, ops); - ret = ff_sws_compile_pass(graph, NULL, &ops, SWS_OP_FLAG_OPTIMIZE, input, output); - if (ret < 0) - goto fail; - - ret = 0; - /* fall through */ - -fail: - if (ret == AVERROR(ENOTSUP)) - return add_legacy_sws_pass(graph, src, dst, input, output); - return ret; -} + return ff_sws_compile_pass(graph, NULL, &ops, SWS_OP_FLAG_OPTIMIZE, input, output); #else -#define add_convert_pass add_legacy_sws_pass + return AVERROR(ENOTSUP); #endif +} +static int add_convert_pass(SwsGraph *graph, const SwsFormat *src, + const SwsFormat *dst, SwsPass *input, + SwsPass **output) +{ + int ret; + + ret = add_ops_convert_pass(graph, src, dst, input, output); + if (ret == AVERROR(ENOTSUP)) + ret = add_legacy_sws_pass(graph, src, dst, input, output); + + return 0; +} /************************** * Gamut and tone mapping * -- 2.52.0 >From 9c7f584f0924016406e09ab2d2cea70e378be526 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 3 Jun 2026 21:31:21 +0200 Subject: [PATCH 7/7] swscale/graph: only prefer unstable backends with SWS_UNSTABLE If the user passes `-backends all` but without `-flags unstable`, then the default/legacy backend will be picked unless it doesn't support a given pixel format. This allows gradually opting into the new code to handle more pixel formats than what the legacy backend currently supports, without disturbing the predictable output/behavior. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/graph.c | 13 +++++++++++-- libswscale/swscale.h | 6 +++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/libswscale/graph.c b/libswscale/graph.c index acd0ce615e..1c6a34dee1 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -658,11 +658,20 @@ static int add_convert_pass(SwsGraph *graph, const SwsFormat *src, const SwsFormat *dst, SwsPass *input, SwsPass **output) { + SwsContext *ctx = graph->ctx; int ret; - ret = add_ops_convert_pass(graph, src, dst, input, output); - if (ret == AVERROR(ENOTSUP)) + if (ctx->flags & SWS_UNSTABLE) { + /* Prefer unstable ops backend over legacy backend */ + ret = add_ops_convert_pass(graph, src, dst, input, output); + if (ret == AVERROR(ENOTSUP)) + ret = add_legacy_sws_pass(graph, src, dst, input, output); + } else { + /* Prefer legacy backend for stability reasons */ ret = add_legacy_sws_pass(graph, src, dst, input, output); + if (ret == AVERROR(ENOTSUP)) + ret = add_ops_convert_pass(graph, src, dst, input, output); + } return 0; } diff --git a/libswscale/swscale.h b/libswscale/swscale.h index 8954f1a7d8..9b53ebbdff 100644 --- a/libswscale/swscale.h +++ b/libswscale/swscale.h @@ -180,9 +180,9 @@ typedef enum SwsFlags { SWS_BITEXACT = 1 << 19, /** - * Allow using experimental new code paths. This may be faster, slower, - * or produce different output, with semantics subject to change at any - * point in time. For testing and debugging purposes only. + * Allow/prefer using experimental new code paths. This may be faster, + * slower, or produce different output, with semantics subject to change + * at any point in time. For testing and debugging purposes only. */ SWS_UNSTABLE = 1 << 20, -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
