PR #25844 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25844 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25844.patch
These should hopefully be uncontroversial. >From 2e65ff23c675e8b01e56185e32ab1355a514189a Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 28 Oct 2026 19:37:29 +0100 Subject: [PATCH 1/8] swscale/ops_dispatch: always split off unresolved filters Instead of only doing so after compilation of the whole op list failed with AVERROR(ENOTSUP). No backend currently implements standalone filter ops, so this does not change the result; but it avoids splitting op lists that are unsupported for other reasons, e.g. by some CPUs only. This can be revisited once a backend actually implements standalone filters. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index 21e5535f14b..bfc776c43ac 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -743,11 +743,7 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, } } - ret = compile_single(args, ops, link, input, output); - if (ret != AVERROR(ENOTSUP)) - goto fail; /* either success or a hard error */ - - /* Find any unresolved filter */ + /* Split off any unresolved filters, since no backend implements these */ for (int idx = 1; idx < ops->num_ops - 1; idx++) { const SwsOp *op = &ops->ops[idx]; if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) { @@ -766,11 +762,11 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, } } - /* If we didn't find any more operations to eliminate, then this ops list - * is simply unsupported by any of the available backends */ - av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n"); - ff_sws_op_list_print(ctx, AV_LOG_WARNING, AV_LOG_TRACE, ops); - ret = AVERROR(ENOTSUP); + ret = compile_single(args, ops, link, input, output); + if (ret == AVERROR(ENOTSUP)) { + av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n"); + ff_sws_op_list_print(ctx, AV_LOG_WARNING, AV_LOG_TRACE, ops); + } fail: ff_sws_op_list_free(&ops); -- 2.54.0 >From 658d01210e43a1209e4e0840deeffcae60a0036c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 29 Oct 2026 15:14:48 +0100 Subject: [PATCH 2/8] swscale/uops: move expand32() from x86 to header Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/uops.h | 11 +++++++++++ libswscale/x86/ops.c | 14 +------------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/libswscale/uops.h b/libswscale/uops.h index 4d25107927b..2a48746fc65 100644 --- a/libswscale/uops.h +++ b/libswscale/uops.h @@ -101,6 +101,17 @@ static inline SwsPixel ff_sws_pixel_from_q64(SwsPixelType type, AVRational64 val return (SwsPixel) {0}; } +/* Expand pixel value to 32-bits by repeating as necessary */ +static inline SwsPixel ff_sws_pixel_expand32(SwsPixelType type, SwsPixel val) +{ + switch (type) { + case SWS_PIXEL_U8: val.u32 = val.u8 * 0x01010101u; break; + case SWS_PIXEL_U16: val.u32 = val.u16 * 0x00010001u; break; + default: break; + } + return val; +} + static inline bool ff_sws_pixel_is_1s(SwsPixelType type, SwsPixel val) { switch (ff_sws_pixel_type_size(type)) { diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index 9cc5ba7a0aa..f1349a9ec93 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -622,22 +622,10 @@ do { return 0; } -/* Expand pixel value to 32-bits by repeating as necessary */ -static uint32_t expand32(const SwsPixelType type, const SwsPixel value) -{ - switch (type) { - case SWS_PIXEL_U8: return value.u8 * 0x01010101u; - case SWS_PIXEL_U16: return value.u16 * 0x00010001u; - case SWS_PIXEL_U32: return value.u32; - case SWS_PIXEL_F32: return value.u32; /* reinterpret */ - default: return 0; - } -} - static void normalize_clear(SwsUOp *uop) { for (int i = 0; i < 4; i++) - uop->data.vec4[i].u32 = expand32(uop->type, uop->data.vec4[i]); + uop->data.vec4[i] = ff_sws_pixel_expand32(uop->type, uop->data.vec4[i]); } static int compile_uops_x86(SwsContext *ctx, const SwsUOpList *uops, SwsCompiledOp *out) -- 2.54.0 >From 3fe56043df09071075a0a83cd6f9e091c4226fac Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 28 Oct 2026 18:16:55 +0100 Subject: [PATCH 3/8] swscale/uops: always expand clear values to 32 bits This is backwards compatible, as reading the original pixel type from the expanded value gives the same result regardless of endianness. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/uops.c | 2 +- libswscale/uops.h | 4 ++++ tests/checkasm/sw_ops.c | 11 ++++++++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/libswscale/uops.c b/libswscale/uops.c index 54d5581107f..4f19ee33f00 100644 --- a/libswscale/uops.c +++ b/libswscale/uops.c @@ -603,7 +603,7 @@ static int translate_op(SwsContext *ctx, SwsUOpList *uops, SwsUOpFlags flags, const SwsPixelType type = op->clear.type[i]; const SwsPixel px = ff_sws_pixel_from_q64(type, v); uop.type = pixel_type_to_int(flags, type); - uop.data.vec4[i] = px; + uop.data.vec4[i] = ff_sws_pixel_expand32(type, px); if (v.num == 0) uop.par.clear.zero |= SWS_COMP(i); else if (ff_sws_pixel_is_1s(type, px)) diff --git a/libswscale/uops.h b/libswscale/uops.h index 2a48746fc65..f89ee5ef52c 100644 --- a/libswscale/uops.h +++ b/libswscale/uops.h @@ -255,6 +255,10 @@ typedef struct SwsPackUOp { uint8_t pattern[4]; /* bit depth pattern, from MSB to LSB */ } SwsPackUOp; +/** + * The clear values (SwsUOp.data.vec4) are always expanded to repeating 32-bit + * patterns, so they remain valid if the uop is executed as a smaller type. + */ typedef struct SwsClearUOp { SwsCompMask one; /* mask of coefficients equal to all 1s */ SwsCompMask zero; /* mask of coefficients equal to all 0s */ diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index 0d17d8bd7df..01d473564dc 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -488,6 +488,15 @@ static void check_vec4(const char *name, SwsUOp *uop) check_simple(name, uop); } +static void check_clear(const char *name, SwsUOp *uop) +{ + /* Clear values are always expanded to 32 bits, see SwsClearUOp */ + for (int i = 0; i < 4; i++) + uop->data.vec4[i] = ff_sws_pixel_expand32(uop->type, rndpx(uop->type)); + + check_simple(name, uop); +} + #define MK_RANGES(R) ((const unsigned[]) { R, R, R, R }) static void check_read(const char *name, SwsUOp *uop) @@ -715,7 +724,7 @@ void checkasm_check_sw_ops(void) CHECK_FOR(PACK, check_pack); CHECK_FOR(LSHIFT, check_simple); CHECK_FOR(RSHIFT, check_simple); - CHECK_FOR(CLEAR, check_vec4); + CHECK_FOR(CLEAR, check_clear); CHECK_FOR(LINEAR, check_linear); CHECK_FOR(DITHER, check_dither); CHECK_FOR(LUT_3D, check_lut_3d); -- 2.54.0 >From ea507fbad76a5f328be76c09640d9b43ef06c00b Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 28 Oct 2026 19:41:20 +0100 Subject: [PATCH 4/8] swscale/x86/ops: don't expand clear values They are already expanded to 32 bits, as documented for SwsClearUOp. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/x86/ops.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index f1349a9ec93..e477c3e917e 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -622,12 +622,6 @@ do { return 0; } -static void normalize_clear(SwsUOp *uop) -{ - for (int i = 0; i < 4; i++) - uop->data.vec4[i] = ff_sws_pixel_expand32(uop->type, uop->data.vec4[i]); -} - static int compile_uops_x86(SwsContext *ctx, const SwsUOpList *uops, SwsCompiledOp *out) { int ret, mmsize = get_mmsize(); @@ -662,8 +656,6 @@ static int compile_uops_x86(SwsContext *ctx, const SwsUOpList *uops, SwsCompiled int op_block_size = out->block_size; if (uop_is_type_invariant(uop->uop)) { - if (uop->uop == SWS_UOP_CLEAR) - normalize_clear(uop); op_block_size *= ff_sws_pixel_type_size(uop->type); uop->type = SWS_PIXEL_U8; } -- 2.54.0 >From f88fac8d981fa56fda67ba154a5113350acdf6bc Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 29 Oct 2026 16:49:48 +0100 Subject: [PATCH 5/8] swscale/x86/ops_int: fix pointer increment in WRITE_PLANAR The output pointer was advanced by the number of pixels instead of the number of bytes, unlike READ_PLANAR. This made no difference so far, as planar writes are only ever instantiated for SWS_PIXEL_U8. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- libswscale/x86/ops_int.asm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libswscale/x86/ops_int.asm b/libswscale/x86/ops_int.asm index c5d38b69ff3..20b34dc7394 100644 --- a/libswscale/x86/ops_int.asm +++ b/libswscale/x86/ops_int.asm @@ -200,10 +200,10 @@ IF Y, movu [out1q + mmsize], my2 IF Z, movu [out2q + mmsize], mz2 IF W, movu [out3q + mmsize], mw2 %endif -IF X, add out0q, BLOCK_WIDTH -IF Y, add out1q, BLOCK_WIDTH -IF Z, add out2q, BLOCK_WIDTH -IF W, add out3q, BLOCK_WIDTH +IF X, add out0q, BLOCK_SIZE +IF Y, add out1q, BLOCK_SIZE +IF Z, add out2q, BLOCK_SIZE +IF W, add out3q, BLOCK_SIZE RET %endmacro -- 2.54.0 >From 3b2d243f8ea47426c0549c7ca28632211f950f74 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 4 Nov 2026 22:14:52 +0100 Subject: [PATCH 6/8] tests/checkasm/sw_ops: test SWS_UOP_ADD with per-component constants SWS_UOP_ADD reads data.vec4, but check_scalar() only randomized data.scalar, i.e. the first component. Co-Authored-By: Claude Opus 5.5 <[email protected]> Signed-off-by: Niklas Haas <[email protected]> --- tests/checkasm/sw_ops.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index 01d473564dc..298618029f5 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -474,12 +474,6 @@ static void check_simple(const char *name, const SwsUOp *uop) check_range(name, uop, NULL); } -static void check_scalar(const char *name, SwsUOp *uop) -{ - uop->data.scalar = rndpx(uop->type); - check_simple(name, uop); -} - static void check_vec4(const char *name, SwsUOp *uop) { for (int i = 0; i < 4; i++) @@ -717,7 +711,7 @@ void checkasm_check_sw_ops(void) CHECK_FOR(TO_U32, check_cast); CHECK_FOR(TO_F32, check_cast); CHECK_FOR(SCALE, check_scale); - CHECK_FOR(ADD, check_scalar); + CHECK_FOR(ADD, check_vec4); CHECK_FOR(MIN, check_vec4); CHECK_FOR(MAX, check_vec4); CHECK_FOR(UNPACK, check_unpack); -- 2.54.0 >From 011dd623d57eaf759afc49ecec2b1d7595598d57 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 1 Sep 2026 18:42:09 +0200 Subject: [PATCH 7/8] swscale/filters: constrain SWS_FILTER_BICUBIC parameter range The filter is not really useful outside of these parameters, and clamping the domain lets us reason about the expected overshoot. In particular, B < 0 is completely broken, as is B >= 3 (already identified in the code). C is more relaxed, but outside [-1.5, 2.0] it would also overshoot by too much (relative to the upcoming change). There's a final constraint of 2B + C <= 3. Limiting both parameters to [0, 1] solves all problems at once and is the standard practice. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/filters.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libswscale/filters.c b/libswscale/filters.c index fb54d2bbd8b..3073ed8cdc5 100644 --- a/libswscale/filters.c +++ b/libswscale/filters.c @@ -148,7 +148,8 @@ static bool validate_params(const SwsFilterFunction *fun, SwsScaler scaler) case SWS_SCALE_LANCZOS: return fun->params[0] >= 1.0 && fun->params[0] <= RADIUS_MAX; /* radius */ case SWS_SCALE_BICUBIC: - return fun->params[0] < 3.0; /* B param (division by zero) */ + return fun->params[0] >= 0.0 && fun->params[0] <= 1.0 /* B */ && + fun->params[1] >= 0.0 && fun->params[1] <= 1.0 /* C */; default: return true; } -- 2.54.0 >From e58175e3ced923d89f81b903c6262f71799683d8 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 1 Sep 2026 21:29:41 +0200 Subject: [PATCH 8/8] swscale/filters: properly handle filter tails When the filter overlaps an image boundary, we need to add all the weights past the edge to the appropriate edge weight. Apart from generating correct filtering behavior near boundaries, this also fixes an edge case where the `wsum < 0` guard could fire, as well as some examples of catastrophic filter overshoot. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/filters.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/libswscale/filters.c b/libswscale/filters.c index 3073ed8cdc5..811c5729ecd 100644 --- a/libswscale/filters.c +++ b/libswscale/filters.c @@ -82,13 +82,11 @@ static void compute_row(SwsFilterWeights *f, const SwsFilterFunction *fun, return; } - /* First pixel that is actually within the filter envelope */ - const double start_pos = src_pos - radius; - int64_t start_idx = ceil(start_pos); - start_idx = FFMAX(start_idx, 0); /* edge clamping */ - start_idx = FFMIN(start_idx, f->src_size - f->filter_size); - const double offset = start_idx - src_pos; - *pos = start_idx; + /* First and last pixels that are actually within the filter envelope */ + const int start = ceil (src_pos - radius); + const int stop = floor(src_pos + radius); + const int clamped = av_clip(start, 0, f->src_size - f->filter_size); + *pos = clamped; /** * Generate raw filter weights with maximum precision. Sum the positive @@ -96,14 +94,26 @@ static void compute_row(SwsFilterWeights *f, const SwsFilterFunction *fun, * summation order should already give the best precision because abs(w) * is monotonically decreasing */ - const double base = stretch_inv * offset; + const double base = stretch_inv * (clamped - src_pos); double wsum_pos = 0.0, wsum_neg = 0.0; for (int i = 0; i < f->filter_size; i++) { - tmp[i] = scaler_sample(fun, base + stretch_inv * i); - if (tmp[i] >= 0) - wsum_pos += tmp[i]; + /* Fold extra edge weights into the first/last weight in case the + * filter got truncated; loop over all needed samples */ + int i0 = i, i1 = i; + if (i == 0) + i0 = start - clamped; + if (i == f->filter_size - 1) + i1 = stop - clamped; + + double w = 0.0; + for (int k = i0; k <= i1; k++) + w += scaler_sample(fun, base + stretch_inv * k); + + if (w >= 0) + wsum_pos += w; else - wsum_neg += tmp[i]; + wsum_neg += w; + tmp[i] = w; } const double wsum = wsum_pos + wsum_neg; -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
