PR #22671 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22671 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22671.patch
Ultimately, this field was clunky and annoying to use; especially since it kept requiring carrying around references to the *next* operation. The previously merged series https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22652 started replacing this in favor of marking unused components as SWS_COMP_GARBAGE instead. This commit finishes up that effort, which requires a handful of other cleanup steps along the way. Ultimately, this leads to a much nicer conceptualization of SWS_OP_CLEAR, and fewer hacks in `op_match()`. One of the changes we picked up along the way is the switch from a "hand-coded" list of linear masks to a programmatically generated one, currently still extracted (by hand) from `libswscale/tests/sws_ops -summarize`. However, I have plans to expand this by enabling automatic generation of these (and other) patterns as well; in the same style as the existing `make sws_ops_entries_aarch64`, and create a new common target `make sws_ops_entries` that will generate the ops patterns for all platforms, including C and x86. This would also allow us to ultimately generate the entire list of kernels programmatically, rather than having to adjust them by hand. That should also boost performance by guaranteeing that no unnecessary operations are ever executed. Depends on https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22662 >From 78ae1367ba8d035e3dace5c37b1d87f99c7db8ec Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 18:29:35 +0100 Subject: [PATCH 01/24] swscale/vulkan/ops: simplify QTYPE macro There's no reason for this macro to hard-code op->c.q4[i]. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/vulkan/ops.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c index 4d684116ca..dd86c44580 100644 --- a/libswscale/vulkan/ops.c +++ b/libswscale/vulkan/ops.c @@ -181,8 +181,7 @@ static void add_desc_read_write(FFVulkanDescriptorSetBinding *out_desc, } #define QSTR "(%i/%i%s)" -#define QTYPE(i) op->c.q4[i].num, op->c.q4[i].den, \ - cur_type == SWS_PIXEL_F32 ? ".0f" : "" +#define QTYPE(Q) (Q).num, (Q).den, cur_type == SWS_PIXEL_F32 ? ".0f" : "" static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, SwsOpList *ops, FFVulkanShader *shd) @@ -278,7 +277,7 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, if (!op->c.q4[i].den) continue; av_bprintf(&shd->src, " %s.%c = %s"QSTR";\n", type_name, - "xyzw"[i], type_s, QTYPE(i)); + "xyzw"[i], type_s, QTYPE(op->c.q4[i])); } break; } -- 2.52.0 >From 7facee83bcfab2977e2c506fb4b7b7ba3bd2c524 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 18:33:46 +0100 Subject: [PATCH 02/24] swscale/vulkan/ops: simplify using QSTR/QTYPE to print all rationals Now this helper is actually helping, rather than just existing. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/vulkan/ops.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c index dd86c44580..fece479cd3 100644 --- a/libswscale/vulkan/ops.c +++ b/libswscale/vulkan/ops.c @@ -282,8 +282,8 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, break; } case SWS_OP_SCALE: - av_bprintf(&shd->src, " %s = %s*%i/%i;\n", - type_name, type_name, op->c.q.num, op->c.q.den); + av_bprintf(&shd->src, " %s = %s * "QSTR";\n", + type_name, type_name, QTYPE(op->c.q)); break; case SWS_OP_MIN: case SWS_OP_MAX: @@ -293,9 +293,7 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, av_bprintf(&shd->src, " %s.%c = %s(%s.%c, "QSTR");\n", type_name, "xyzw"[i], op->op == SWS_OP_MIN ? "min" : "max", - type_name, "xyzw"[i], - op->c.q4[i].num, op->c.q4[i].den, - cur_type == SWS_PIXEL_F32 ? ".0f" : ""); + type_name, "xyzw"[i], QTYPE(op->c.q4[i])); } break; case SWS_OP_LSHIFT: @@ -321,9 +319,8 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, for (int i = 0; i < size; i++) { av_bprintf(&shd->src, " { "); for (int j = 0; j < size; j++) - av_bprintf(&shd->src, "%i/%i.0, ", - op->dither.matrix[i*size + j].num, - op->dither.matrix[i*size + j].den); + av_bprintf(&shd->src, QSTR", ", + QTYPE(op->dither.matrix[i*size + j])); av_bprintf(&shd->src, "}, %s\n", i == (size - 1) ? "\n };" : ""); } for (int i = 0; i < 4; i++) { @@ -339,16 +336,15 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, case SWS_OP_LINEAR: for (int i = 0; i < 4; i++) { if (op->lin.m[i][4].num) - av_bprintf(&shd->src, " tmp.%c = (%i/%i.0);\n", "xyzw"[i], - op->lin.m[i][4].num, op->lin.m[i][4].den); + av_bprintf(&shd->src, " tmp.%c = "QSTR";\n", "xyzw"[i], + QTYPE(op->lin.m[i][4])); else av_bprintf(&shd->src, " tmp.%c = 0;\n", "xyzw"[i]); for (int j = 0; j < 4; j++) { if (!op->lin.m[i][j].num) continue; - av_bprintf(&shd->src, " tmp.%c += f32.%c*(%i/%i.0);\n", - "xyzw"[i], "xyzw"[j], - op->lin.m[i][j].num, op->lin.m[i][j].den); + av_bprintf(&shd->src, " tmp.%c += f32.%c*"QSTR";\n", + "xyzw"[i], "xyzw"[j], QTYPE(op->lin.m[i][j])); } } av_bprintf(&shd->src, " f32 = tmp;\n"); -- 2.52.0 >From 91f5b832818cdad4892df88006777607084725a1 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 17:57:47 +0100 Subject: [PATCH 03/24] swscale/ops_chain: remove unused helper function Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_chain.c | 12 ------------ libswscale/ops_chain.h | 1 - 2 files changed, 13 deletions(-) diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index df7867c248..65bd3029d3 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -269,18 +269,6 @@ int ff_sws_setup_u8(const SwsImplParams *params, SwsImplResult *out) return 0; } -int ff_sws_setup_u(const SwsImplParams *params, SwsImplResult *out) -{ - const SwsOp *op = params->op; - switch (op->type) { - case SWS_PIXEL_U8: out->priv.u8[0] = op->c.u; return 0; - case SWS_PIXEL_U16: out->priv.u16[0] = op->c.u; return 0; - case SWS_PIXEL_U32: out->priv.u32[0] = op->c.u; return 0; - case SWS_PIXEL_F32: out->priv.f32[0] = op->c.u; return 0; - default: return AVERROR(EINVAL); - } -} - int ff_sws_setup_q(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; diff --git a/libswscale/ops_chain.h b/libswscale/ops_chain.h index 60c83e6e9e..f9afe2b733 100644 --- a/libswscale/ops_chain.h +++ b/libswscale/ops_chain.h @@ -141,7 +141,6 @@ typedef struct SwsOpEntry { } SwsOpEntry; /* Setup helpers */ -int ff_sws_setup_u(const SwsImplParams *params, SwsImplResult *out); int ff_sws_setup_u8(const SwsImplParams *params, SwsImplResult *out); int ff_sws_setup_q(const SwsImplParams *params, SwsImplResult *out); int ff_sws_setup_q4(const SwsImplParams *params, SwsImplResult *out); -- 2.52.0 >From 51b457c759bfc5bd7d8dd58dc1553c75ef1dfcd0 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 18:01:25 +0100 Subject: [PATCH 04/24] swscale/ops_chain: split generic setup helpers into op-specific helpers This has the side benefit of avoiding a division by zero when setting up the clear constants for SWS_OP_CLEAR, since we can now continue out of the loop in this case. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/aarch64/ops.c | 6 +++-- libswscale/ops_chain.c | 43 +++++++++++++++++++++++++++--------- libswscale/ops_chain.h | 9 ++++---- libswscale/ops_tmpl_common.c | 8 +++---- libswscale/ops_tmpl_int.c | 4 ++-- libswscale/x86/ops.c | 8 +++---- 6 files changed, 51 insertions(+), 27 deletions(-) diff --git a/libswscale/aarch64/ops.c b/libswscale/aarch64/ops.c index 4b204efd08..753aef51ae 100644 --- a/libswscale/aarch64/ops.c +++ b/libswscale/aarch64/ops.c @@ -143,12 +143,14 @@ static int aarch64_setup(SwsOpList *ops, int block_size, int n, } break; case SWS_OP_CLEAR: + ff_sws_setup_clear(&(const SwsImplParams) { .op = op }, out); + break; case SWS_OP_MIN: case SWS_OP_MAX: - ff_sws_setup_q4(&(const SwsImplParams) { .op = op }, out); + ff_sws_setup_clamp(&(const SwsImplParams) { .op = op }, out); break; case SWS_OP_SCALE: - ff_sws_setup_q(&(const SwsImplParams) { .op = op }, out); + ff_sws_setup_scale(&(const SwsImplParams) { .op = op }, out); break; case SWS_OP_LINEAR: return aarch64_setup_linear(p, op, out); diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index 65bd3029d3..c51c2b8e67 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -263,33 +263,54 @@ int ff_sws_op_compile_tables(SwsContext *ctx, const SwsOpTable *const tables[], #define q2pixel(type, q) ((q).den ? (type) (q).num / (q).den : 0) -int ff_sws_setup_u8(const SwsImplParams *params, SwsImplResult *out) +int ff_sws_setup_shift(const SwsImplParams *params, SwsImplResult *out) { out->priv.u8[0] = params->op->c.u; return 0; } -int ff_sws_setup_q(const SwsImplParams *params, SwsImplResult *out) +int ff_sws_setup_scale(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; + const AVRational factor = op->c.q; switch (op->type) { - case SWS_PIXEL_U8: out->priv.u8[0] = q2pixel(uint8_t, op->c.q); return 0; - case SWS_PIXEL_U16: out->priv.u16[0] = q2pixel(uint16_t, op->c.q); return 0; - case SWS_PIXEL_U32: out->priv.u32[0] = q2pixel(uint32_t, op->c.q); return 0; - case SWS_PIXEL_F32: out->priv.f32[0] = q2pixel(float, op->c.q); return 0; + case SWS_PIXEL_U8: out->priv.u8[0] = q2pixel(uint8_t, factor); return 0; + case SWS_PIXEL_U16: out->priv.u16[0] = q2pixel(uint16_t, factor); return 0; + case SWS_PIXEL_U32: out->priv.u32[0] = q2pixel(uint32_t, factor); return 0; + case SWS_PIXEL_F32: out->priv.f32[0] = q2pixel(float, factor); return 0; default: return AVERROR(EINVAL); } } -int ff_sws_setup_q4(const SwsImplParams *params, SwsImplResult *out) +int ff_sws_setup_clamp(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; for (int i = 0; i < 4; i++) { + const AVRational limit = op->c.q4[i]; switch (op->type) { - case SWS_PIXEL_U8: out->priv.u8[i] = q2pixel(uint8_t, op->c.q4[i]); break; - case SWS_PIXEL_U16: out->priv.u16[i] = q2pixel(uint16_t, op->c.q4[i]); break; - case SWS_PIXEL_U32: out->priv.u32[i] = q2pixel(uint32_t, op->c.q4[i]); break; - case SWS_PIXEL_F32: out->priv.f32[i] = q2pixel(float, op->c.q4[i]); break; + case SWS_PIXEL_U8: out->priv.u8[i] = q2pixel(uint8_t, limit); break; + case SWS_PIXEL_U16: out->priv.u16[i] = q2pixel(uint16_t, limit); break; + case SWS_PIXEL_U32: out->priv.u32[i] = q2pixel(uint32_t, limit); break; + case SWS_PIXEL_F32: out->priv.f32[i] = q2pixel(float, limit); break; + default: return AVERROR(EINVAL); + } + } + + return 0; +} + +int ff_sws_setup_clear(const SwsImplParams *params, SwsImplResult *out) +{ + const SwsOp *op = params->op; + for (int i = 0; i < 4; i++) { + const AVRational value = op->c.q4[i]; + if (!value.den) + continue; + switch (op->type) { + case SWS_PIXEL_U8: out->priv.u8[i] = q2pixel(uint8_t, value); break; + case SWS_PIXEL_U16: out->priv.u16[i] = q2pixel(uint16_t, value); break; + case SWS_PIXEL_U32: out->priv.u32[i] = q2pixel(uint32_t, value); break; + case SWS_PIXEL_F32: out->priv.f32[i] = q2pixel(float, value); break; default: return AVERROR(EINVAL); } } diff --git a/libswscale/ops_chain.h b/libswscale/ops_chain.h index f9afe2b733..fc0cd930f8 100644 --- a/libswscale/ops_chain.h +++ b/libswscale/ops_chain.h @@ -140,10 +140,11 @@ typedef struct SwsOpEntry { bool (*check)(const SwsImplParams *params); /* optional, return true if supported */ } SwsOpEntry; -/* Setup helpers */ -int ff_sws_setup_u8(const SwsImplParams *params, SwsImplResult *out); -int ff_sws_setup_q(const SwsImplParams *params, SwsImplResult *out); -int ff_sws_setup_q4(const SwsImplParams *params, SwsImplResult *out); +/* Setup helpers for common/trivial operation types */ +int ff_sws_setup_shift(const SwsImplParams *params, SwsImplResult *out); +int ff_sws_setup_scale(const SwsImplParams *params, SwsImplResult *out); +int ff_sws_setup_clamp(const SwsImplParams *params, SwsImplResult *out); +int ff_sws_setup_clear(const SwsImplParams *params, SwsImplResult *out); static inline void ff_op_priv_free(SwsOpPriv *priv) { diff --git a/libswscale/ops_tmpl_common.c b/libswscale/ops_tmpl_common.c index 5a0a399f32..3817a437e5 100644 --- a/libswscale/ops_tmpl_common.c +++ b/libswscale/ops_tmpl_common.c @@ -85,7 +85,7 @@ DECL_IMPL(clear##_##X##Y##Z##W) } \ \ DECL_ENTRY(clear##_##X##Y##Z##W, \ - .setup = ff_sws_setup_q4, \ + .setup = ff_sws_setup_clear, \ .op = SWS_OP_CLEAR, \ .flexible = true, \ .unused = { !X, !Y, !Z, !W }, \ @@ -141,13 +141,13 @@ DECL_PATTERN(max) WRAP_COMMON_PATTERNS(min, .op = SWS_OP_MIN, - .setup = ff_sws_setup_q4, + .setup = ff_sws_setup_clamp, .flexible = true, ); WRAP_COMMON_PATTERNS(max, .op = SWS_OP_MAX, - .setup = ff_sws_setup_q4, + .setup = ff_sws_setup_clamp, .flexible = true, ); @@ -172,7 +172,7 @@ DECL_PATTERN(scale) WRAP_COMMON_PATTERNS(scale, .op = SWS_OP_SCALE, - .setup = ff_sws_setup_q, + .setup = ff_sws_setup_scale, .flexible = true, ); diff --git a/libswscale/ops_tmpl_int.c b/libswscale/ops_tmpl_int.c index 960d0d9527..b010513bc5 100644 --- a/libswscale/ops_tmpl_int.c +++ b/libswscale/ops_tmpl_int.c @@ -383,13 +383,13 @@ DECL_PATTERN(rshift) WRAP_COMMON_PATTERNS(lshift, .op = SWS_OP_LSHIFT, - .setup = ff_sws_setup_u8, + .setup = ff_sws_setup_shift, .flexible = true, ); WRAP_COMMON_PATTERNS(rshift, .op = SWS_OP_RSHIFT, - .setup = ff_sws_setup_u8, + .setup = ff_sws_setup_shift, .flexible = true, ); #endif /* BIT_DEPTH != 8 */ diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index 51c65c8ebb..734ffbac27 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -185,20 +185,20 @@ static int setup_shift(const SwsImplParams *params, SwsImplResult *out) #define DECL_MIN_MAX(EXT) \ DECL_COMMON_PATTERNS(F32, min##EXT, \ .op = SWS_OP_MIN, \ - .setup = ff_sws_setup_q4, \ + .setup = ff_sws_setup_clamp, \ .flexible = true, \ ); \ \ DECL_COMMON_PATTERNS(F32, max##EXT, \ .op = SWS_OP_MAX, \ - .setup = ff_sws_setup_q4, \ + .setup = ff_sws_setup_clamp, \ .flexible = true, \ ); #define DECL_SCALE(EXT) \ DECL_COMMON_PATTERNS(F32, scale##EXT, \ .op = SWS_OP_SCALE, \ - .setup = ff_sws_setup_q, \ + .setup = ff_sws_setup_scale, \ .flexible = true, \ ); @@ -941,7 +941,7 @@ static void normalize_clear(SwsOp *op) int i; } c; - ff_sws_setup_q4(&(const SwsImplParams) { .op = op }, &res); + ff_sws_setup_clear(&(const SwsImplParams) { .op = op }, &res); for (int i = 0; i < 4; i++) { if (!op->c.q4[i].den) -- 2.52.0 >From e3615b5ed229d5d68a7eb266a7df5833c19d48a8 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 29 Mar 2026 12:57:51 +0200 Subject: [PATCH 05/24] swscale/ops: define flags_identity as an enum constant Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 1e6d68b210..8534b35c34 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -250,12 +250,15 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) av_unreachable("Invalid operation type!"); } -/* merge_comp_flags() forms a monoid with flags_identity as the null element */ -static const SwsCompFlags flags_identity = SWS_COMP_ZERO | SWS_COMP_EXACT; +enum { + SWS_COMP_IDENTITY = SWS_COMP_ZERO | SWS_COMP_EXACT, +}; + +/* merge_comp_flags() forms a monoid with SWS_COMP_IDENTITY as the null element */ static SwsCompFlags merge_comp_flags(SwsCompFlags a, SwsCompFlags b) { const SwsCompFlags flags_or = SWS_COMP_GARBAGE; - const SwsCompFlags flags_and = SWS_COMP_ZERO | SWS_COMP_EXACT; + const SwsCompFlags flags_and = SWS_COMP_IDENTITY; return ((a & b) & flags_and) | ((a | b) & flags_or); } @@ -390,7 +393,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) } break; case SWS_OP_PACK: { - SwsCompFlags flags = flags_identity; + SwsCompFlags flags = SWS_COMP_IDENTITY; for (int i = 0; i < 4; i++) { if (op->pack.pattern[i]) flags = merge_comp_flags(flags, prev.flags[i]); @@ -426,7 +429,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_LINEAR: for (int i = 0; i < 4; i++) { - SwsCompFlags flags = flags_identity; + SwsCompFlags flags = SWS_COMP_IDENTITY; AVRational min = Q(0), max = Q(0); for (int j = 0; j < 4; j++) { const AVRational k = op->lin.m[i][j]; -- 2.52.0 >From b0b9afdb42a5b71faebabc465719e2455e21a7fb Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 29 Mar 2026 13:02:35 +0200 Subject: [PATCH 06/24] swscale/ops: move comment (cosmetic) Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 8534b35c34..01d995252e 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -369,12 +369,12 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) clear_undefined_values(op->comps.min, op->c.q4); break; case SWS_OP_DITHER: - /* Strip zero flag because of the nonzero dithering offset */ for (int i = 0; i < 4; i++) { op->comps.min[i] = prev.min[i]; op->comps.max[i] = prev.max[i]; if (op->dither.y_offset[i] < 0) continue; + /* Strip zero flag because of the nonzero dithering offset */ op->comps.flags[i] = prev.flags[i] & ~SWS_COMP_ZERO; op->comps.min[i] = av_add_q(op->comps.min[i], op->dither.min); op->comps.max[i] = av_add_q(op->comps.max[i], op->dither.max); -- 2.52.0 >From aa07c78eeddfe5d0b558bb23e8ab17f5994e4571 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 18:45:26 +0100 Subject: [PATCH 07/24] swscale/ops: split SwsConst up into op-specific structs It was a bit clunky, lacked semantic contextual information, and made it harder to reason about the effects of extending this struct. There should be zero runtime overhead as a result of the fact that this is already a big union. I made the changes in this commit by hand, but due to the length and noise level of the commit, I used Opus 4.6 to verify that I did not accidentally introduce any bugs or typos. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/aarch64/ops_impl_conv.c | 10 ++-- libswscale/format.c | 65 ++++++++++---------- libswscale/ops.c | 46 +++++++------- libswscale/ops.h | 41 ++++++++----- libswscale/ops_chain.c | 18 +++--- libswscale/ops_memcpy.c | 8 +-- libswscale/ops_optimizer.c | 96 +++++++++++++++--------------- libswscale/tests/sws_ops.c | 7 ++- libswscale/vulkan/ops.c | 12 ++-- libswscale/x86/ops.c | 10 ++-- tests/checkasm/sw_ops.c | 78 ++++++++++++------------ 11 files changed, 205 insertions(+), 186 deletions(-) diff --git a/libswscale/aarch64/ops_impl_conv.c b/libswscale/aarch64/ops_impl_conv.c index fdc398392f..8f37af0101 100644 --- a/libswscale/aarch64/ops_impl_conv.c +++ b/libswscale/aarch64/ops_impl_conv.c @@ -181,14 +181,14 @@ static int convert_to_aarch64_impl(SwsContext *ctx, const SwsOpList *ops, int n, break; case AARCH64_SWS_OP_LSHIFT: case AARCH64_SWS_OP_RSHIFT: - out->shift = op->c.u; + out->shift = op->shift.amount; break; case AARCH64_SWS_OP_CLEAR: out->mask = 0; - MASK_SET(out->mask, 0, !!op->c.q4[0].den); - MASK_SET(out->mask, 1, !!op->c.q4[1].den); - MASK_SET(out->mask, 2, !!op->c.q4[2].den); - MASK_SET(out->mask, 3, !!op->c.q4[3].den); + MASK_SET(out->mask, 0, !!op->clear.value[0].den); + MASK_SET(out->mask, 1, !!op->clear.value[1].den); + MASK_SET(out->mask, 2, !!op->clear.value[2].den); + MASK_SET(out->mask, 3, !!op->clear.value[3].den); break; case AARCH64_SWS_OP_EXPAND: case AARCH64_SWS_OP_CONVERT: diff --git a/libswscale/format.c b/libswscale/format.c index 6fa279cc18..a26349585a 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -827,7 +827,7 @@ static int cmp_comp(const void *a, const void *b) { } static int fmt_analyze_regular(const AVPixFmtDescriptor *desc, SwsReadWriteOp *rw_op, - SwsSwizzleOp *swizzle, int *shift) + SwsSwizzleOp *swizzle, SwsShiftOp *shift) { if (desc->nb_components == 2) { /* YA formats */ @@ -848,7 +848,7 @@ static int fmt_analyze_regular(const AVPixFmtDescriptor *desc, SwsReadWriteOp *r *swizzle = swiz; } - *shift = desc->comp[0].shift; + *shift = (SwsShiftOp) { desc->comp[0].shift }; *rw_op = (SwsReadWriteOp) { .elems = desc->nb_components, .packed = desc->nb_components > 1 && !(desc->flags & AV_PIX_FMT_FLAG_PLANAR), @@ -857,8 +857,9 @@ static int fmt_analyze_regular(const AVPixFmtDescriptor *desc, SwsReadWriteOp *r } static int fmt_analyze(enum AVPixelFormat fmt, SwsReadWriteOp *rw_op, - SwsPackOp *pack_op, SwsSwizzleOp *swizzle, int *shift, - SwsPixelType *pixel_type, SwsPixelType *raw_type) + SwsPackOp *pack_op, SwsSwizzleOp *swizzle, + SwsShiftOp *shift, SwsPixelType *pixel_type, + SwsPixelType *raw_type) { const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); if (!desc) @@ -889,7 +890,7 @@ static int fmt_analyze(enum AVPixelFormat fmt, SwsReadWriteOp *rw_op, *rw_op = info.rw; *pack_op = info.pack; *swizzle = info.swizzle; - *shift = info.shift; + *shift = (SwsShiftOp) { info.shift }; if (info.pack.pattern[0]) { const int sum = info.pack.pattern[0] + info.pack.pattern[1] + @@ -923,17 +924,17 @@ static SwsSwizzleOp swizzle_inv(SwsSwizzleOp swiz) { * it will end up getting pushed towards the output or optimized away entirely * by the optimization pass. */ -static SwsConst fmt_clear(enum AVPixelFormat fmt) +static SwsClearOp fmt_clear(enum AVPixelFormat fmt) { const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); const bool has_chroma = desc->nb_components >= 3; const bool has_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA; - SwsConst c = {0}; + SwsClearOp c = {0}; if (!has_chroma) - c.q4[1] = c.q4[2] = Q0; + c.value[1] = c.value[2] = Q0; if (!has_alpha) - c.q4[3] = Q0; + c.value[3] = Q0; return c; } @@ -952,7 +953,7 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) SwsSwizzleOp swizzle; SwsPackOp unpack; SwsComps *comps = &ops->comps_src; - int shift; + SwsShiftOp shift; RET(fmt_analyze(fmt, &rw_op, &unpack, &swizzle, &shift, &pixel_type, &raw_type)); @@ -974,7 +975,7 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) * canonical order {Y, U, V, A} */ const int is_ya = desc->nb_components == 2; for (int c = 0; c < desc->nb_components; c++) { - const int bits = desc->comp[c].depth + shift; + const int bits = desc->comp[c].depth + shift.amount; const int idx = swizzle.in[is_ya ? 3 * c : c]; comps->min[idx] = Q0; if (bits < 32) /* FIXME: AVRational is limited to INT_MAX */ @@ -1016,18 +1017,18 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) .swizzle = swizzle, })); - if (shift) { + if (shift.amount) { RET(ff_sws_op_list_append(ops, &(SwsOp) { - .op = SWS_OP_RSHIFT, - .type = pixel_type, - .c.u = shift, + .op = SWS_OP_RSHIFT, + .type = pixel_type, + .shift = shift, })); } RET(ff_sws_op_list_append(ops, &(SwsOp) { - .op = SWS_OP_CLEAR, - .type = pixel_type, - .c = fmt_clear(fmt), + .op = SWS_OP_CLEAR, + .type = pixel_type, + .clear = fmt_clear(fmt), })); return 0; @@ -1040,16 +1041,16 @@ int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) SwsReadWriteOp rw_op; SwsSwizzleOp swizzle; SwsPackOp pack; - int shift; + SwsShiftOp shift; RET(fmt_analyze(fmt, &rw_op, &pack, &swizzle, &shift, &pixel_type, &raw_type)); - if (shift) { + if (shift.amount) { RET(ff_sws_op_list_append(ops, &(SwsOp) { - .op = SWS_OP_LSHIFT, - .type = pixel_type, - .c.u = shift, + .op = SWS_OP_LSHIFT, + .type = pixel_type, + .shift = shift, })); } @@ -1059,7 +1060,7 @@ int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) RET(ff_sws_op_list_append(ops, &(SwsOp) { .op = SWS_OP_CLEAR, .type = pixel_type, - .c.q4[3] = Q0, + .clear.value[3] = Q0, })); } @@ -1513,26 +1514,26 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, })); if (!(dst->desc->flags & AV_PIX_FMT_FLAG_FLOAT)) { - SwsConst range = {0}; + SwsClampOp range = {0}; const bool is_ya = dst->desc->nb_components == 2; for (int i = 0; i < dst->desc->nb_components; i++) { /* Clamp to legal pixel range */ const int idx = i * (is_ya ? 3 : 1); - range.q4[idx] = Q((1 << dst->desc->comp[i].depth) - 1); + range.limit[idx] = Q((1 << dst->desc->comp[i].depth) - 1); } RET(fmt_dither(ctx, ops, type, src, dst)); RET(ff_sws_op_list_append(ops, &(SwsOp) { - .op = SWS_OP_MAX, - .type = type, - .c.q4 = { Q0, Q0, Q0, Q0 }, + .op = SWS_OP_MAX, + .type = type, + .clamp = {{ Q0, Q0, Q0, Q0 }}, })); RET(ff_sws_op_list_append(ops, &(SwsOp) { - .op = SWS_OP_MIN, - .type = type, - .c = range, + .op = SWS_OP_MIN, + .type = type, + .clamp = range, })); } diff --git a/libswscale/ops.c b/libswscale/ops.c index 01d995252e..0824f4af48 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -177,13 +177,13 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) return; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (op->c.q4[i].den) - x[i] = op->c.q4[i]; + if (op->clear.value[i].den) + x[i] = op->clear.value[i]; } return; case SWS_OP_LSHIFT: { av_assert1(ff_sws_pixel_type_is_int(op->type)); - AVRational mult = Q(1 << op->c.u); + AVRational mult = Q(1 << op->shift.amount); for (int i = 0; i < 4; i++) x[i] = x[i].den ? av_mul_q(x[i], mult) : x[i]; return; @@ -191,7 +191,7 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) case SWS_OP_RSHIFT: { av_assert1(ff_sws_pixel_type_is_int(op->type)); for (int i = 0; i < 4; i++) - x[i] = x[i].den ? Q((x[i].num / x[i].den) >> op->c.u) : x[i]; + x[i] = x[i].den ? Q((x[i].num / x[i].den) >> op->shift.amount) : x[i]; return; } case SWS_OP_SWIZZLE: { @@ -219,11 +219,11 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) return; case SWS_OP_MIN: for (int i = 0; i < 4; i++) - x[i] = av_min_q(x[i], op->c.q4[i]); + x[i] = av_min_q(x[i], op->clamp.limit[i]); return; case SWS_OP_MAX: for (int i = 0; i < 4; i++) - x[i] = av_max_q(x[i], op->c.q4[i]); + x[i] = av_max_q(x[i], op->clamp.limit[i]); return; case SWS_OP_LINEAR: { av_assert1(!ff_sws_pixel_type_is_int(op->type)); @@ -238,7 +238,7 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) } case SWS_OP_SCALE: for (int i = 0; i < 4; i++) - x[i] = x[i].den ? av_mul_q(x[i], op->c.q) : x[i]; + x[i] = x[i].den ? av_mul_q(x[i], op->scale.factor) : x[i]; return; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: @@ -362,11 +362,11 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_MIN: propagate_flags(op, &prev); - clear_undefined_values(op->comps.max, op->c.q4); + clear_undefined_values(op->comps.max, op->clamp.limit); break; case SWS_OP_MAX: propagate_flags(op, &prev); - clear_undefined_values(op->comps.min, op->c.q4); + clear_undefined_values(op->comps.min, op->clamp.limit); break; case SWS_OP_DITHER: for (int i = 0; i < 4; i++) { @@ -405,11 +405,11 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) } case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (op->c.q4[i].den) { + if (op->clear.value[i].den) { op->comps.flags[i] = 0; - if (op->c.q4[i].num == 0) + if (op->clear.value[i].num == 0) op->comps.flags[i] |= SWS_COMP_ZERO; - if (op->c.q4[i].den == 1) + if (op->clear.value[i].den == 1) op->comps.flags[i] |= SWS_COMP_EXACT; } else { op->comps.flags[i] = prev.flags[i]; @@ -460,9 +460,9 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_SCALE: for (int i = 0; i < 4; i++) { op->comps.flags[i] = prev.flags[i]; - if (op->c.q.den != 1) /* fractional scale */ + if (op->scale.factor.den != 1) /* fractional scale */ op->comps.flags[i] &= ~SWS_COMP_EXACT; - if (op->c.q.num < 0) + if (op->scale.factor.num < 0) FFSWAP(AVRational, op->comps.min[i], op->comps.max[i]); } break; @@ -524,7 +524,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clear.value[i].den) need_in[i] = need_out[i]; } break; @@ -839,10 +839,10 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) op->rw.filter == SWS_OP_FILTER_H ? 'H' : 'V'); break; case SWS_OP_LSHIFT: - av_bprintf(bp, "%-20s: << %u", name, op->c.u); + av_bprintf(bp, "%-20s: << %u", name, op->shift.amount); break; case SWS_OP_RSHIFT: - av_bprintf(bp, "%-20s: >> %u", name, op->c.u); + av_bprintf(bp, "%-20s: >> %u", name, op->shift.amount); break; case SWS_OP_PACK: case SWS_OP_UNPACK: @@ -852,7 +852,7 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) break; case SWS_OP_CLEAR: av_bprintf(bp, "%-20s: ", name); - print_q4(bp, op->c.q4, true, op->comps.flags); + print_q4(bp, op->clear.value, true, op->comps.flags); break; case SWS_OP_SWIZZLE: av_bprintf(bp, "%-20s: %d%d%d%d", name, @@ -872,11 +872,11 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) break; case SWS_OP_MIN: av_bprintf(bp, "%-20s: x <= ", name); - print_q4(bp, op->c.q4, true, op->comps.flags); + print_q4(bp, op->clamp.limit, true, op->comps.flags); break; case SWS_OP_MAX: av_bprintf(bp, "%-20s: ", name); - print_q4(bp, op->c.q4, true, op->comps.flags); + print_q4(bp, op->clamp.limit, true, op->comps.flags); av_bprintf(bp, " <= x"); break; case SWS_OP_LINEAR: @@ -892,9 +892,9 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) av_bprintf(bp, "]"); break; case SWS_OP_SCALE: - av_bprintf(bp, "%-20s: * %d", name, op->c.q.num); - if (op->c.q.den != 1) - av_bprintf(bp, "/%d", op->c.q.den); + av_bprintf(bp, "%-20s: * %d", name, op->scale.factor.num); + if (op->scale.factor.den != 1) + av_bprintf(bp, "/%d", op->scale.factor.den); break; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: { diff --git a/libswscale/ops.h b/libswscale/ops.h index a9e24ff5a9..c1723dfc6a 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -55,15 +55,15 @@ typedef enum SwsOpType { /* Bit manipulation operations. Defined for integers only. */ SWS_OP_UNPACK, /* split tightly packed data into components */ SWS_OP_PACK, /* compress components into tightly packed data */ - SWS_OP_LSHIFT, /* logical left shift of raw pixel values by (u8) */ - SWS_OP_RSHIFT, /* right shift of raw pixel values by (u8) */ + SWS_OP_LSHIFT, /* logical left shift of raw pixel values */ + SWS_OP_RSHIFT, /* right shift of raw pixel values */ /* Generic arithmetic. Defined and implemented for all types */ SWS_OP_CLEAR, /* clear pixel values */ SWS_OP_CONVERT, /* convert (cast) between formats */ - SWS_OP_MIN, /* numeric minimum (q4) */ - SWS_OP_MAX, /* numeric maximum (q4) */ - SWS_OP_SCALE, /* multiplication by scalar (q) */ + SWS_OP_MIN, /* numeric minimum */ + SWS_OP_MAX, /* numeric maximum */ + SWS_OP_SCALE, /* multiplication by scalar */ /* Floating-point only arithmetic operations. */ SWS_OP_LINEAR, /* generalized linear affine transform */ @@ -87,16 +87,6 @@ typedef enum SwsCompFlags { #define SWS_OP_NEEDED(op, idx) (!((op)->comps.flags[idx] & SWS_COMP_GARBAGE)) -typedef union SwsConst { - /* Generic constant value */ - AVRational q4[4]; - AVRational q; - unsigned u; -} SwsConst; - -static_assert(sizeof(SwsConst) == sizeof(AVRational) * 4, - "First field of SwsConst should span the entire union"); - typedef struct SwsComps { SwsCompFlags flags[4]; /* knowledge about (output) component contents */ bool unused[4]; /* which input components are definitely unused */ @@ -152,11 +142,27 @@ typedef struct SwsSwizzleOp { #define SWS_SWIZZLE(X,Y,Z,W) ((SwsSwizzleOp) { .in = {X, Y, Z, W} }) +typedef struct SwsShiftOp { + uint8_t amount; /* number of bits to shift */ +} SwsShiftOp; + +typedef struct SwsClearOp { + AVRational value[4]; /* value to set, or {0, 0} for no-op */ +} SwsClearOp; + typedef struct SwsConvertOp { SwsPixelType to; /* type of pixel to convert to */ bool expand; /* if true, integers are expanded to the full range */ } SwsConvertOp; +typedef struct SwsClampOp { + AVRational limit[4]; /* per-component min/max value */ +} SwsClampOp; + +typedef struct SwsScaleOp { + AVRational factor; /* scalar multiplication factor */ +} SwsScaleOp; + typedef struct SwsDitherOp { AVRational *matrix; /* tightly packed dither matrix (refstruct) */ AVRational min, max; /* minimum/maximum value in `matrix` */ @@ -217,10 +223,13 @@ typedef struct SwsOp { SwsReadWriteOp rw; SwsPackOp pack; SwsSwizzleOp swizzle; + SwsShiftOp shift; + SwsClearOp clear; SwsConvertOp convert; + SwsClampOp clamp; + SwsScaleOp scale; SwsDitherOp dither; SwsFilterOp filter; - SwsConst c; }; /** diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index c51c2b8e67..87f0591c31 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -115,7 +115,9 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) if (op->op == SWS_OP_CLEAR) { /* Clear pattern must match exactly, regardless of `entry->flexible` */ for (int i = 0; i < 4; i++) { - if (SWS_OP_NEEDED(op, i) && entry->unused[i] != !!op->c.q4[i].den) + if (!SWS_OP_NEEDED(op, i)) + continue; + if (entry->unused[i] != !!op->clear.value[i].den) return 0; } } @@ -147,9 +149,9 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) return score; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den || !SWS_OP_NEEDED(op, i)) + if (!op->clear.value[i].den || !SWS_OP_NEEDED(op, i)) continue; - if (av_cmp_q(op->c.q4[i], Q(entry->clear_value))) + if (av_cmp_q(op->clear.value[i], Q(entry->clear_value))) return 0; } return score; @@ -188,7 +190,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) score += av_popcount(SWS_MASK_ALL ^ entry->linear_mask); return score; case SWS_OP_SCALE: - return av_cmp_q(op->c.q, entry->scale) ? 0 : score; + return av_cmp_q(op->scale.factor, entry->scale) ? 0 : score; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: return score; @@ -265,14 +267,14 @@ int ff_sws_op_compile_tables(SwsContext *ctx, const SwsOpTable *const tables[], int ff_sws_setup_shift(const SwsImplParams *params, SwsImplResult *out) { - out->priv.u8[0] = params->op->c.u; + out->priv.u8[0] = params->op->shift.amount; return 0; } int ff_sws_setup_scale(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; - const AVRational factor = op->c.q; + const AVRational factor = op->scale.factor; switch (op->type) { case SWS_PIXEL_U8: out->priv.u8[0] = q2pixel(uint8_t, factor); return 0; case SWS_PIXEL_U16: out->priv.u16[0] = q2pixel(uint16_t, factor); return 0; @@ -286,7 +288,7 @@ int ff_sws_setup_clamp(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; for (int i = 0; i < 4; i++) { - const AVRational limit = op->c.q4[i]; + const AVRational limit = op->clamp.limit[i]; switch (op->type) { case SWS_PIXEL_U8: out->priv.u8[i] = q2pixel(uint8_t, limit); break; case SWS_PIXEL_U16: out->priv.u16[i] = q2pixel(uint16_t, limit); break; @@ -303,7 +305,7 @@ int ff_sws_setup_clear(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; for (int i = 0; i < 4; i++) { - const AVRational value = op->c.q4[i]; + const AVRational value = op->clear.value[i]; if (!value.den) continue; switch (op->type) { diff --git a/libswscale/ops_memcpy.c b/libswscale/ops_memcpy.c index a84067da6d..b7b56900ee 100644 --- a/libswscale/ops_memcpy.c +++ b/libswscale/ops_memcpy.c @@ -86,20 +86,20 @@ static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out) case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clear.value[i].den) continue; - if (op->c.q4[i].den != 1) + if (op->clear.value[i].den != 1) return AVERROR(ENOTSUP); /* Ensure all bytes to be cleared are the same, because we * can't memset on multi-byte sequences */ - uint8_t val = op->c.q4[i].num & 0xFF; + uint8_t val = op->clear.value[i].num & 0xFF; uint32_t ref = val; switch (ff_sws_pixel_type_size(op->type)) { case 2: ref *= 0x101; break; case 4: ref *= 0x1010101; break; } - if (ref != op->c.q4[i].num) + if (ref != op->clear.value[i].num) return AVERROR(ENOTSUP); p.clear_value[i] = val; p.index[i] = -1; diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index fd3c1ef969..f44a44e1b7 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -39,7 +39,7 @@ */ static bool op_commute_clear(SwsOp *op, SwsOp *next) { - SwsOp tmp; + SwsClearOp tmp; av_assert1(op->op == SWS_OP_CLEAR); switch (next->op) { @@ -56,21 +56,21 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next) case SWS_OP_SWIZZLE: case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: - ff_sws_apply_op_q(next, op->c.q4); + ff_sws_apply_op_q(next, op->clear.value); return true; case SWS_OP_SWAP_BYTES: switch (next->type) { case SWS_PIXEL_U16: - ff_sws_apply_op_q(next, op->c.q4); /* always works */ + ff_sws_apply_op_q(next, op->clear.value); /* always works */ return true; case SWS_PIXEL_U32: for (int i = 0; i < 4; i++) { - uint32_t v = av_bswap32(op->c.q4[i].num); + uint32_t v = av_bswap32(op->clear.value[i].num); if (v > INT_MAX) return false; /* can't represent as AVRational anymore */ - tmp.c.q4[i] = Q(v); + tmp.value[i] = Q(v); } - op->c = tmp.c; + op->clear = tmp; return true; default: return false; @@ -124,14 +124,14 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next) */ case SWS_OP_MIN: case SWS_OP_MAX: { - const SwsConst c = next->c; + const SwsClampOp c = next->clamp; for (int i = 0; i < 4; i++) { if (!SWS_OP_NEEDED(op, i)) continue; const int j = op->swizzle.in[i]; - if (seen[j] && av_cmp_q(next->c.q4[j], c.q4[i])) + if (seen[j] && av_cmp_q(next->clamp.limit[j], c.limit[i])) return false; - next->c.q4[j] = c.q4[i]; + next->clamp.limit[j] = c.limit[i]; seen[j] = true; } return true; @@ -238,9 +238,9 @@ static int exact_log2_q(const AVRational x) * the corresponding scaling factor, or 0 otherwise. */ static bool extract_scalar(const SwsLinearOp *c, SwsComps comps, SwsComps prev, - SwsConst *out_scale) + SwsScaleOp *out_scale) { - SwsConst scale = {0}; + SwsScaleOp scale = {0}; /* There are components not on the main diagonal */ if (c->mask & ~SWS_MASK_DIAG4) @@ -251,21 +251,21 @@ static bool extract_scalar(const SwsLinearOp *c, SwsComps comps, SwsComps prev, if ((prev.flags[i] & SWS_COMP_ZERO) || (comps.flags[i] & SWS_COMP_GARBAGE)) continue; - if (scale.q.den && av_cmp_q(s, scale.q)) + if (scale.factor.den && av_cmp_q(s, scale.factor)) return false; - scale.q = s; + scale.factor = s; } - if (scale.q.den) + if (scale.factor.den) *out_scale = scale; - return scale.q.den; + return scale.factor.den; } /* Extracts an integer clear operation (subset) from the given linear op. */ static bool extract_constant_rows(SwsLinearOp *c, SwsComps prev, - SwsConst *out_clear) + SwsClearOp *out_clear) { - SwsConst clear = {0}; + SwsClearOp clear = {0}; bool ret = false; for (int i = 0; i < 4; i++) { @@ -275,7 +275,7 @@ static bool extract_constant_rows(SwsLinearOp *c, SwsComps prev, (prev.flags[j] & SWS_COMP_ZERO); /* input is zero */ } if (const_row && (c->mask & SWS_MASK_ROW(i))) { - clear.q4[i] = c->m[i][4]; + clear.value[i] = c->m[i][4]; for (int j = 0; j < 5; j++) c->m[i][j] = Q(i == j); c->mask &= ~SWS_MASK_ROW(i); @@ -428,13 +428,13 @@ retry: case SWS_OP_RSHIFT: /* Two shifts in the same direction */ if (next->op == op->op) { - op->c.u += next->c.u; + op->shift.amount += next->shift.amount; ff_sws_op_list_remove_at(ops, n + 1, 1); goto retry; } /* No-op shift */ - if (!op->c.u) { + if (!op->shift.amount) { ff_sws_op_list_remove_at(ops, n, 1); goto retry; } @@ -442,19 +442,19 @@ retry: case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clear.value[i].den) continue; if ((prev->comps.flags[i] & SWS_COMP_ZERO) && !(prev->comps.flags[i] & SWS_COMP_GARBAGE) && - op->c.q4[i].num == 0) + op->clear.value[i].num == 0) { /* Redundant clear-to-zero of zero component */ - op->c.q4[i].den = 0; + op->clear.value[i].den = 0; } else if (!SWS_OP_NEEDED(op, i)) { /* Unnecessary clear of unused component */ - op->c.q4[i] = (AVRational) {0, 0}; - } else if (op->c.q4[i].den) { + op->clear.value[i] = (AVRational) {0, 0}; + } else if (op->clear.value[i].den) { noop = false; } } @@ -467,8 +467,8 @@ retry: /* Transitive clear */ if (next->op == SWS_OP_CLEAR) { for (int i = 0; i < 4; i++) { - if (next->c.q4[i].den) - op->c.q4[i] = next->c.q4[i]; + if (next->clear.value[i].den) + op->clear.value[i] = next->clear.value[i]; } ff_sws_op_list_remove_at(ops, n + 1, 1); goto retry; @@ -548,7 +548,8 @@ retry: if (next->op == SWS_OP_SCALE && !op->convert.expand && ff_sws_pixel_type_is_int(op->type) && ff_sws_pixel_type_is_int(op->convert.to) && - !av_cmp_q(next->c.q, ff_sws_pixel_expand(op->type, op->convert.to))) + !av_cmp_q(next->scale.factor, + ff_sws_pixel_expand(op->type, op->convert.to))) { op->convert.expand = true; ff_sws_op_list_remove_at(ops, n + 1, 1); @@ -558,9 +559,9 @@ retry: case SWS_OP_MIN: for (int i = 0; i < 4; i++) { - if (!SWS_OP_NEEDED(op, i) || !op->c.q4[i].den) + if (!SWS_OP_NEEDED(op, i) || !op->clamp.limit[i].den) continue; - if (av_cmp_q(op->c.q4[i], prev->comps.max[i]) < 0) + if (av_cmp_q(op->clamp.limit[i], prev->comps.max[i]) < 0) noop = false; } @@ -572,9 +573,9 @@ retry: case SWS_OP_MAX: for (int i = 0; i < 4; i++) { - if (!SWS_OP_NEEDED(op, i) || !op->c.q4[i].den) + if (!SWS_OP_NEEDED(op, i) || !op->clamp.limit[i].den) continue; - if (av_cmp_q(prev->comps.min[i], op->c.q4[i]) < 0) + if (av_cmp_q(prev->comps.min[i], op->clamp.limit[i]) < 0) noop = false; } @@ -604,7 +605,8 @@ retry: case SWS_OP_LINEAR: { SwsSwizzleOp swizzle; - SwsConst c; + SwsClearOp clear; + SwsScaleOp scale; /* No-op (identity) linear operation */ if (!op->lin.mask) { @@ -654,20 +656,20 @@ retry: } /* Convert constant rows to explicit clear instruction */ - if (extract_constant_rows(&op->lin, prev->comps, &c)) { + if (extract_constant_rows(&op->lin, prev->comps, &clear)) { RET(ff_sws_op_list_insert_at(ops, n + 1, &(SwsOp) { .op = SWS_OP_CLEAR, .type = op->type, .comps = op->comps, - .c = c, + .clear = clear, })); goto retry; } /* Multiplication by scalar constant */ - if (extract_scalar(&op->lin, op->comps, prev->comps, &c)) { - op->op = SWS_OP_SCALE; - op->c = c; + if (extract_scalar(&op->lin, op->comps, prev->comps, &scale)) { + op->op = SWS_OP_SCALE; + op->scale = scale; goto retry; } @@ -684,20 +686,20 @@ retry: } case SWS_OP_SCALE: { - const int factor2 = exact_log2_q(op->c.q); + const int factor2 = exact_log2_q(op->scale.factor); /* No-op scaling */ - if (op->c.q.num == 1 && op->c.q.den == 1) { + if (op->scale.factor.num == 1 && op->scale.factor.den == 1) { ff_sws_op_list_remove_at(ops, n, 1); goto retry; } /* Merge consecutive scaling operations (that don't overflow) */ if (next->op == SWS_OP_SCALE) { - int64_t p = op->c.q.num * (int64_t) next->c.q.num; - int64_t q = op->c.q.den * (int64_t) next->c.q.den; + int64_t p = op->scale.factor.num * (int64_t) next->scale.factor.num; + int64_t q = op->scale.factor.den * (int64_t) next->scale.factor.den; if (FFABS(p) <= INT_MAX && FFABS(q) <= INT_MAX) { - av_reduce(&op->c.q.num, &op->c.q.den, p, q, INT_MAX); + av_reduce(&op->scale.factor.num, &op->scale.factor.den, p, q, INT_MAX); ff_sws_op_list_remove_at(ops, n + 1, 1); goto retry; } @@ -706,7 +708,7 @@ retry: /* Scaling by exact power of two */ if (factor2 && ff_sws_pixel_type_is_int(op->type)) { op->op = factor2 > 0 ? SWS_OP_LSHIFT : SWS_OP_RSHIFT; - op->c.u = FFABS(factor2); + op->shift.amount = FFABS(factor2); goto retry; } break; @@ -760,7 +762,7 @@ retry: case SWS_OP_SCALE: /* Scaling by integer before conversion to int */ - if (op->c.q.den == 1 && next->op == SWS_OP_CONVERT && + if (op->scale.factor.den == 1 && next->op == SWS_OP_CONVERT && ff_sws_pixel_type_is_int(next->convert.to)) { op->type = next->convert.to; @@ -812,9 +814,9 @@ int ff_sws_solve_shuffle(const SwsOpList *const ops, uint8_t shuffle[], case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clear.value[i].den) continue; - if (op->c.q4[i].num != 0 || !clear_val) + if (op->clear.value[i].num != 0 || !clear_val) return AVERROR(ENOTSUP); mask[i] = 0x1010101ul * clear_val; } diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 02049156e7..05378be18d 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -65,11 +65,16 @@ static int register_op(SwsContext *ctx, void *opaque, SwsOp *op) } break; case SWS_OP_SCALE: + op->scale.factor = (AVRational) { 0, 1 }; + break; case SWS_OP_MIN: case SWS_OP_MAX: + for (int i = 0; i < 4; i++) + op->clamp.limit[i] = (AVRational) { 0, 1 }; + break; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) - op->c.q4[i] = (AVRational) { 0, !!op->c.q4[i].den }; + op->clear.value[i] = (AVRational) { 0, !!op->clear.value[i].den }; break; case SWS_OP_DITHER: /* Strip arbitrary offset */ diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c index fece479cd3..adf81864a4 100644 --- a/libswscale/vulkan/ops.c +++ b/libswscale/vulkan/ops.c @@ -274,32 +274,32 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, } case SWS_OP_CLEAR: { for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clear.value[i].den) continue; av_bprintf(&shd->src, " %s.%c = %s"QSTR";\n", type_name, - "xyzw"[i], type_s, QTYPE(op->c.q4[i])); + "xyzw"[i], type_s, QTYPE(op->clear.value[i])); } break; } case SWS_OP_SCALE: av_bprintf(&shd->src, " %s = %s * "QSTR";\n", - type_name, type_name, QTYPE(op->c.q)); + type_name, type_name, QTYPE(op->scale.factor)); break; case SWS_OP_MIN: case SWS_OP_MAX: for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clamp.limit[i].den) continue; av_bprintf(&shd->src, " %s.%c = %s(%s.%c, "QSTR");\n", type_name, "xyzw"[i], op->op == SWS_OP_MIN ? "min" : "max", - type_name, "xyzw"[i], QTYPE(op->c.q4[i])); + type_name, "xyzw"[i], QTYPE(op->clamp.limit[i])); } break; case SWS_OP_LSHIFT: case SWS_OP_RSHIFT: av_bprintf(&shd->src, " %s %s= %i;\n", type_name, - op->op == SWS_OP_LSHIFT ? "<<" : ">>", op->c.u); + op->op == SWS_OP_LSHIFT ? "<<" : ">>", op->shift.amount); break; case SWS_OP_CONVERT: if (ff_sws_pixel_type_is_int(cur_type) && op->convert.expand) { diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index 734ffbac27..ab113aa780 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -133,7 +133,7 @@ static int setup_clear(const SwsImplParams *params, SwsImplResult *out) { const SwsOp *op = params->op; for (int i = 0; i < 4; i++) - out->priv.u32[i] = (uint32_t) op->c.q4[i].num; + out->priv.u32[i] = (uint32_t) op->clear.value[i].num; return 0; } @@ -165,7 +165,7 @@ static int setup_clear(const SwsImplParams *params, SwsImplResult *out) static int setup_shift(const SwsImplParams *params, SwsImplResult *out) { - out->priv.u16[0] = params->op->c.u; + out->priv.u16[0] = params->op->shift.amount; return 0; } @@ -944,7 +944,7 @@ static void normalize_clear(SwsOp *op) ff_sws_setup_clear(&(const SwsImplParams) { .op = op }, &res); for (int i = 0; i < 4; i++) { - if (!op->c.q4[i].den) + if (!op->clear.value[i].den) continue; switch (ff_sws_pixel_type_size(op->type)) { case 1: c.u32 = 0x1010101U * res.priv.u8[i]; break; @@ -952,8 +952,8 @@ static void normalize_clear(SwsOp *op) case 4: c.u32 = res.priv.u32[i]; break; } - op->c.q4[i].num = c.i; - op->c.q4[i].den = 1; + op->clear.value[i].num = c.i; + op->clear.value[i].den = 1; } } diff --git a/tests/checkasm/sw_ops.c b/tests/checkasm/sw_ops.c index 570fbfc825..39a053172c 100644 --- a/tests/checkasm/sw_ops.c +++ b/tests/checkasm/sw_ops.c @@ -498,45 +498,45 @@ static void check_clear(void) const AVRational zero = (AVRational) { 0, 1}; const AVRational none = {0}; - const SwsConst patterns[] = { + const SwsClearOp patterns[] = { /* Zero only */ - {.q4 = { none, none, none, zero }}, - {.q4 = { zero, none, none, none }}, + {{ none, none, none, zero }}, + {{ zero, none, none, none }}, /* Alpha only */ - {.q4 = { none, none, none, alpha }}, - {.q4 = { alpha, none, none, none }}, + {{ none, none, none, alpha }}, + {{ alpha, none, none, none }}, /* Chroma only */ - {.q4 = { chroma, chroma, none, none }}, - {.q4 = { none, chroma, chroma, none }}, - {.q4 = { none, none, chroma, chroma }}, - {.q4 = { chroma, none, chroma, none }}, - {.q4 = { none, chroma, none, chroma }}, + {{ chroma, chroma, none, none }}, + {{ none, chroma, chroma, none }}, + {{ none, none, chroma, chroma }}, + {{ chroma, none, chroma, none }}, + {{ none, chroma, none, chroma }}, /* Alpha+chroma */ - {.q4 = { chroma, chroma, none, alpha }}, - {.q4 = { none, chroma, chroma, alpha }}, - {.q4 = { alpha, none, chroma, chroma }}, - {.q4 = { chroma, none, chroma, alpha }}, - {.q4 = { alpha, chroma, none, chroma }}, + {{ chroma, chroma, none, alpha }}, + {{ none, chroma, chroma, alpha }}, + {{ alpha, none, chroma, chroma }}, + {{ chroma, none, chroma, alpha }}, + {{ alpha, chroma, none, chroma }}, /* Random values */ - {.q4 = { none, rndq(t), rndq(t), rndq(t) }}, - {.q4 = { none, rndq(t), rndq(t), rndq(t) }}, - {.q4 = { none, rndq(t), rndq(t), rndq(t) }}, - {.q4 = { none, rndq(t), rndq(t), rndq(t) }}, + {{ none, rndq(t), rndq(t), rndq(t) }}, + {{ none, rndq(t), rndq(t), rndq(t) }}, + {{ none, rndq(t), rndq(t), rndq(t) }}, + {{ none, rndq(t), rndq(t), rndq(t) }}, }; for (int i = 0; i < FF_ARRAY_ELEMS(patterns); i++) { CHECK(FMT("clear_pattern_%s[%d]", type, i), 4, 4, t, t, { - .op = SWS_OP_CLEAR, - .type = t, - .c = patterns[i], + .op = SWS_OP_CLEAR, + .type = t, + .clear = patterns[i], }); } } else if (!ff_sws_pixel_type_is_int(t)) { /* Floating point YUV doesn't exist, only alpha needs to be cleared */ CHECK(FMT("clear_alpha_%s", type), 4, 4, t, t, { - .op = SWS_OP_CLEAR, - .type = t, - .c.q4[3] = { 0, 1 }, + .op = SWS_OP_CLEAR, + .type = t, + .clear.value[3] = { 0, 1 }, }); } } @@ -551,15 +551,15 @@ static void check_shift(void) for (int shift = 1; shift <= 8; shift++) { CHECK_COMMON(FMT("lshift%d_%s", shift, type), t, t, { - .op = SWS_OP_LSHIFT, - .type = t, - .c.u = shift, + .op = SWS_OP_LSHIFT, + .type = t, + .shift = { shift }, }); CHECK_COMMON(FMT("rshift%d_%s", shift, type), t, t, { - .op = SWS_OP_RSHIFT, - .type = t, - .c.u = shift, + .op = SWS_OP_RSHIFT, + .type = t, + .shift = { shift }, }); } } @@ -697,13 +697,13 @@ static void check_min_max(void) CHECK_COMMON(FMT("min_%s", type), t, t, { .op = SWS_OP_MIN, .type = t, - .c.q4 = { rndq(t), rndq(t), rndq(t), rndq(t) }, + .clamp = {{ rndq(t), rndq(t), rndq(t), rndq(t) }}, }); CHECK_COMMON(FMT("max_%s", type), t, t, { .op = SWS_OP_MAX, .type = t, - .c.q4 = { rndq(t), rndq(t), rndq(t), rndq(t) }, + .clamp = {{ rndq(t), rndq(t), rndq(t), rndq(t) }}, }); } } @@ -776,15 +776,15 @@ static void check_scale(void) const unsigned scale = rnd() & max; const unsigned range = max / (scale ? scale : 1); CHECK_COMMON_RANGE(FMT("scale_%s", type), range, t, t, { - .op = SWS_OP_SCALE, - .type = t, - .c.q = { scale, 1 }, + .op = SWS_OP_SCALE, + .type = t, + .scale = {{ scale, 1 }}, }); } else { CHECK_COMMON(FMT("scale_%s", type), t, t, { - .op = SWS_OP_SCALE, - .type = t, - .c.q = rndq(t), + .op = SWS_OP_SCALE, + .type = t, + .scale = { rndq(t) }, }); } } -- 2.52.0 >From ac519dfdec5f2ef129e26437b4c4c51b337d0348 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 18:47:54 +0100 Subject: [PATCH 08/24] swscale/ops_optimizer: fix commutation of U32 clear + swap_bytes This accidentally unconditionally overwrote the entire clear mask, since Q(n) always set the denominator to 1. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index f44a44e1b7..f5a4b3f039 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -65,6 +65,8 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next) return true; case SWS_PIXEL_U32: for (int i = 0; i < 4; i++) { + if (!op->clear.value[i].den) + continue; uint32_t v = av_bswap32(op->clear.value[i].num); if (v > INT_MAX) return false; /* can't represent as AVRational anymore */ -- 2.52.0 >From 5ff2c2fe090454e88a3997733c7a8426c6deb1c4 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 28 Mar 2026 15:21:47 +0100 Subject: [PATCH 09/24] swscale/ops: add SwsCompMask and related helpers This new type will be used over the following commits to simplify the codebase. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 32 ++++++++++++++++++++++++++++++++ libswscale/ops.h | 28 ++++++++++++++++++++++++++-- 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 0824f4af48..e6b0710df1 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -125,6 +125,38 @@ const char *ff_sws_op_type_name(SwsOpType op) return "ERR"; } +SwsCompMask ff_sws_comp_mask_q4(const AVRational q[4]) +{ + SwsCompMask mask = 0; + for (int i = 0; i < 4; i++) { + if (q[i].den) + mask |= SWS_COMP(i); + } + return mask; +} + +SwsCompMask ff_sws_comp_mask_swizzle(const SwsCompMask mask, const SwsSwizzleOp swiz) +{ + SwsCompMask res = 0; + for (int i = 0; i < 4; i++) { + const int src = swiz.in[i]; + if (SWS_COMP_TEST(mask, src)) + res |= SWS_COMP(i); + } + + return res; +} + +SwsCompMask ff_sws_comp_mask_needed(const SwsOp *op) +{ + SwsCompMask mask = 0; + for (int i = 0; i < 4; i++) { + if (SWS_OP_NEEDED(op, i)) + mask |= SWS_COMP(i); + } + return mask; +} + /* biased towards `a` */ static AVRational av_min_q(AVRational a, AVRational b) { diff --git a/libswscale/ops.h b/libswscale/ops.h index c1723dfc6a..a6a52d7060 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -78,6 +78,26 @@ typedef enum SwsOpType { const char *ff_sws_op_type_name(SwsOpType op); +/** + * Bit-mask of components. Exact meaning depends on the usage context. + */ +typedef uint8_t SwsCompMask; +enum { + SWS_COMP_NONE = 0, + SWS_COMP_ALL = 0b1111, +#define SWS_COMP(X) (1 << (X)) +#define SWS_COMP_TEST(mask, X) (!!((mask) & SWS_COMP(X))) +#define SWS_COMP_INV(mask) ((mask) ^ SWS_COMP_ALL) +#define SWS_COMP_MASK(X, Y, Z, W) \ + (((X) ? SWS_COMP(0) : 0) | \ + ((Y) ? SWS_COMP(1) : 0) | \ + ((Z) ? SWS_COMP(2) : 0) | \ + ((W) ? SWS_COMP(3) : 0)) +}; + +/* Compute SwsCompMask from values with denominator != 0 */ +SwsCompMask ff_sws_comp_mask_q4(const AVRational q[4]); + typedef enum SwsCompFlags { SWS_COMP_GARBAGE = 1 << 0, /* contents are undefined / garbage data */ SWS_COMP_EXACT = 1 << 1, /* value is an exact integer */ @@ -85,8 +105,6 @@ typedef enum SwsCompFlags { SWS_COMP_SWAPPED = 1 << 3, /* byte order is swapped */ } SwsCompFlags; -#define SWS_OP_NEEDED(op, idx) (!((op)->comps.flags[idx] & SWS_COMP_GARBAGE)) - typedef struct SwsComps { SwsCompFlags flags[4]; /* knowledge about (output) component contents */ bool unused[4]; /* which input components are definitely unused */ @@ -141,6 +159,7 @@ typedef struct SwsSwizzleOp { } SwsSwizzleOp; #define SWS_SWIZZLE(X,Y,Z,W) ((SwsSwizzleOp) { .in = {X, Y, Z, W} }) +SwsCompMask ff_sws_comp_mask_swizzle(SwsCompMask mask, SwsSwizzleOp swiz); typedef struct SwsShiftOp { uint8_t amount; /* number of bits to shift */ @@ -242,6 +261,11 @@ typedef struct SwsOp { SwsComps comps; } SwsOp; +#define SWS_OP_NEEDED(op, idx) (!((op)->comps.flags[idx] & SWS_COMP_GARBAGE)) + +/* Compute SwsCompMask from a mask of needed components */ +SwsCompMask ff_sws_comp_mask_needed(const SwsOp *op); + /** * Describe an operation in human-readable form. */ -- 2.52.0 >From e01b5eca55dd9fd6da2e0c2352dc317eac565257 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 29 Mar 2026 19:32:45 +0200 Subject: [PATCH 10/24] swscale/ops: add explicit clear mask to SwsClearOp Instead of implicitly testing for NaN values. This is mostly a straightforward translation, but we need some slight extra boilerplate to ensure the mask is correctly updated when e.g. commuting past a swizzle. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 10 ++++++++-- libswscale/ops.c | 6 +++--- libswscale/ops.h | 3 ++- libswscale/ops_chain.c | 4 ++-- libswscale/ops_memcpy.c | 2 +- libswscale/ops_optimizer.c | 21 +++++++++++++-------- libswscale/tests/sws_ops.c | 2 +- libswscale/vulkan/ops.c | 2 +- libswscale/x86/ops.c | 2 +- 9 files changed, 32 insertions(+), 20 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index a26349585a..ad85f46cfa 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -931,10 +931,15 @@ static SwsClearOp fmt_clear(enum AVPixelFormat fmt) const bool has_alpha = desc->flags & AV_PIX_FMT_FLAG_ALPHA; SwsClearOp c = {0}; - if (!has_chroma) + if (!has_chroma) { + c.mask |= SWS_COMP(1) | SWS_COMP(2); c.value[1] = c.value[2] = Q0; - if (!has_alpha) + } + + if (!has_alpha) { + c.mask |= SWS_COMP(3); c.value[3] = Q0; + } return c; } @@ -1060,6 +1065,7 @@ int ff_sws_encode_pixfmt(SwsOpList *ops, enum AVPixelFormat fmt) RET(ff_sws_op_list_append(ops, &(SwsOp) { .op = SWS_OP_CLEAR, .type = pixel_type, + .clear.mask = SWS_COMP(3), .clear.value[3] = Q0, })); } diff --git a/libswscale/ops.c b/libswscale/ops.c index e6b0710df1..6ff8d9e86b 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -209,7 +209,7 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) return; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (op->clear.value[i].den) + if (SWS_COMP_TEST(op->clear.mask, i)) x[i] = op->clear.value[i]; } return; @@ -437,7 +437,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) } case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (op->clear.value[i].den) { + if (SWS_COMP_TEST(op->clear.mask, i)) { op->comps.flags[i] = 0; if (op->clear.value[i].num == 0) op->comps.flags[i] |= SWS_COMP_ZERO; @@ -556,7 +556,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) need_in[i] = need_out[i]; } break; diff --git a/libswscale/ops.h b/libswscale/ops.h index a6a52d7060..122e392177 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -166,7 +166,8 @@ typedef struct SwsShiftOp { } SwsShiftOp; typedef struct SwsClearOp { - AVRational value[4]; /* value to set, or {0, 0} for no-op */ + SwsCompMask mask; /* mask of components to clear */ + AVRational value[4]; /* value to set */ } SwsClearOp; typedef struct SwsConvertOp { diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index 87f0591c31..2e915cdd63 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -117,7 +117,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) for (int i = 0; i < 4; i++) { if (!SWS_OP_NEEDED(op, i)) continue; - if (entry->unused[i] != !!op->clear.value[i].den) + if (entry->unused[i] != SWS_COMP_TEST(op->clear.mask, i)) return 0; } } @@ -149,7 +149,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) return score; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den || !SWS_OP_NEEDED(op, i)) + if (!SWS_COMP_TEST(op->clear.mask, i) || !SWS_OP_NEEDED(op, i)) continue; if (av_cmp_q(op->clear.value[i], Q(entry->clear_value))) return 0; diff --git a/libswscale/ops_memcpy.c b/libswscale/ops_memcpy.c index b7b56900ee..a8b56e30a2 100644 --- a/libswscale/ops_memcpy.c +++ b/libswscale/ops_memcpy.c @@ -86,7 +86,7 @@ static int compile(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out) case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) continue; if (op->clear.value[i].den != 1) return AVERROR(ENOTSUP); diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index f5a4b3f039..55924a4378 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -53,11 +53,14 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next) case SWS_OP_MAX: case SWS_OP_SCALE: case SWS_OP_READ: - case SWS_OP_SWIZZLE: case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: ff_sws_apply_op_q(next, op->clear.value); return true; + case SWS_OP_SWIZZLE: + op->clear.mask = ff_sws_comp_mask_swizzle(op->clear.mask, next->swizzle); + ff_sws_apply_op_q(next, op->clear.value); + return true; case SWS_OP_SWAP_BYTES: switch (next->type) { case SWS_PIXEL_U16: @@ -65,7 +68,7 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next) return true; case SWS_PIXEL_U32: for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) continue; uint32_t v = av_bswap32(op->clear.value[i].num); if (v > INT_MAX) @@ -277,6 +280,7 @@ static bool extract_constant_rows(SwsLinearOp *c, SwsComps prev, (prev.flags[j] & SWS_COMP_ZERO); /* input is zero */ } if (const_row && (c->mask & SWS_MASK_ROW(i))) { + clear.mask |= SWS_COMP(i); clear.value[i] = c->m[i][4]; for (int j = 0; j < 5; j++) c->m[i][j] = Q(i == j); @@ -444,7 +448,7 @@ retry: case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) continue; if ((prev->comps.flags[i] & SWS_COMP_ZERO) && @@ -452,11 +456,11 @@ retry: op->clear.value[i].num == 0) { /* Redundant clear-to-zero of zero component */ - op->clear.value[i].den = 0; + op->clear.mask ^= SWS_COMP(i); } else if (!SWS_OP_NEEDED(op, i)) { /* Unnecessary clear of unused component */ - op->clear.value[i] = (AVRational) {0, 0}; - } else if (op->clear.value[i].den) { + op->clear.mask ^= SWS_COMP(i); + } else { noop = false; } } @@ -469,9 +473,10 @@ retry: /* Transitive clear */ if (next->op == SWS_OP_CLEAR) { for (int i = 0; i < 4; i++) { - if (next->clear.value[i].den) + if (SWS_COMP_TEST(next->clear.mask, i)) op->clear.value[i] = next->clear.value[i]; } + op->clear.mask |= next->clear.mask; ff_sws_op_list_remove_at(ops, n + 1, 1); goto retry; } @@ -816,7 +821,7 @@ int ff_sws_solve_shuffle(const SwsOpList *const ops, uint8_t shuffle[], case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) continue; if (op->clear.value[i].num != 0 || !clear_val) return AVERROR(ENOTSUP); diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 05378be18d..1256c73ec6 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -74,7 +74,7 @@ static int register_op(SwsContext *ctx, void *opaque, SwsOp *op) break; case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) - op->clear.value[i] = (AVRational) { 0, !!op->clear.value[i].den }; + op->clear.value[i] = (AVRational) { 0, SWS_COMP_TEST(op->clear.mask, i) }; break; case SWS_OP_DITHER: /* Strip arbitrary offset */ diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c index adf81864a4..5dabbb1d40 100644 --- a/libswscale/vulkan/ops.c +++ b/libswscale/vulkan/ops.c @@ -274,7 +274,7 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s, } case SWS_OP_CLEAR: { for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) continue; av_bprintf(&shd->src, " %s.%c = %s"QSTR";\n", type_name, "xyzw"[i], type_s, QTYPE(op->clear.value[i])); diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index ab113aa780..e8be642f66 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -944,7 +944,7 @@ static void normalize_clear(SwsOp *op) ff_sws_setup_clear(&(const SwsImplParams) { .op = op }, &res); for (int i = 0; i < 4; i++) { - if (!op->clear.value[i].den) + if (!SWS_COMP_TEST(op->clear.mask, i)) continue; switch (ff_sws_pixel_type_size(op->type)) { case 1: c.u32 = 0x1010101U * res.priv.u8[i]; break; -- 2.52.0 >From 7bd8e4635c57b8ad3c2585249a65166bc513611c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 12:59:40 +0200 Subject: [PATCH 11/24] swscale/ops: add SwsCompMask parameter to print_q4() Instead of implicitly excluding NAN values if ignore_den0 is set. This gives callers more explicit control over which values to print, and in doing so, makes sure "unintended" NaN values are properly printed as such. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 6ff8d9e86b..7f42a29a84 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -818,11 +818,9 @@ static char describe_comp_flags(SwsCompFlags flags) return '.'; } -static void print_q(AVBPrint *bp, const AVRational q, bool ignore_den0) +static void print_q(AVBPrint *bp, const AVRational q) { - if (!q.den && ignore_den0) { - av_bprintf(bp, "_"); - } else if (!q.den) { + if (!q.den) { av_bprintf(bp, "%s", q.num > 0 ? "inf" : q.num < 0 ? "-inf" : "nan"); } else if (q.den == 1) { av_bprintf(bp, "%d", q.num); @@ -833,17 +831,16 @@ static void print_q(AVBPrint *bp, const AVRational q, bool ignore_den0) } } -static void print_q4(AVBPrint *bp, const AVRational q4[4], bool ignore_den0, - const SwsCompFlags flags[4]) +static void print_q4(AVBPrint *bp, const AVRational q4[4], SwsCompMask mask) { av_bprintf(bp, "{"); for (int i = 0; i < 4; i++) { if (i) av_bprintf(bp, " "); - if (flags[i] & SWS_COMP_GARBAGE) { + if (!SWS_COMP_TEST(mask, i)) { av_bprintf(bp, "_"); } else { - print_q(bp, q4[i], ignore_den0); + print_q(bp, q4[i]); } } av_bprintf(bp, "}"); @@ -852,6 +849,7 @@ static void print_q4(AVBPrint *bp, const AVRational q4[4], bool ignore_den0, void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) { const char *name = ff_sws_op_type_name(op->op); + const SwsCompMask mask = ff_sws_comp_mask_needed(op); switch (op->op) { case SWS_OP_INVALID: @@ -884,7 +882,7 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) break; case SWS_OP_CLEAR: av_bprintf(bp, "%-20s: ", name); - print_q4(bp, op->clear.value, true, op->comps.flags); + print_q4(bp, op->clear.value, mask & op->clear.mask); break; case SWS_OP_SWIZZLE: av_bprintf(bp, "%-20s: %d%d%d%d", name, @@ -904,11 +902,11 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) break; case SWS_OP_MIN: av_bprintf(bp, "%-20s: x <= ", name); - print_q4(bp, op->clamp.limit, true, op->comps.flags); + print_q4(bp, op->clamp.limit, mask & ff_sws_comp_mask_q4(op->clamp.limit)); break; case SWS_OP_MAX: av_bprintf(bp, "%-20s: ", name); - print_q4(bp, op->clamp.limit, true, op->comps.flags); + print_q4(bp, op->clamp.limit, mask & ff_sws_comp_mask_q4(op->clamp.limit)); av_bprintf(bp, " <= x"); break; case SWS_OP_LINEAR: @@ -917,7 +915,7 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) av_bprintf(bp, "%s[", i ? " " : ""); for (int j = 0; j < 5; j++) { av_bprintf(bp, j ? " " : ""); - print_q(bp, op->lin.m[i][j], false); + print_q(bp, op->lin.m[i][j]); } av_bprintf(bp, "]"); } @@ -967,7 +965,8 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC); for (int i = 0; i < ops->num_ops; i++) { - const SwsOp *op = &ops->ops[i]; + const SwsOp *op = &ops->ops[i]; + const SwsCompMask mask = ff_sws_comp_mask_needed(op); av_bprint_clear(&bp); av_bprintf(&bp, " [%3s %c%c%c%c] ", ff_sws_pixel_type_name(op->type), @@ -994,9 +993,9 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, { av_bprint_clear(&bp); av_bprintf(&bp, " min: "); - print_q4(&bp, op->comps.min, false, op->comps.flags); + print_q4(&bp, op->comps.min, mask); av_bprintf(&bp, ", max: "); - print_q4(&bp, op->comps.max, false, op->comps.flags); + print_q4(&bp, op->comps.max, mask); av_assert0(av_bprint_is_complete(&bp)); av_log(log, lev_extra, "%s\n", bp.str); } -- 2.52.0 >From fef564367df57fd9ecc57088566c4a1a2ed8059d Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 13:27:22 +0200 Subject: [PATCH 12/24] swscale/ops: simplify comps range check This does come with a slight change in behavior, as we now don't print the range information in the case that the range is only known for *unused* components. However, in practice, that's already guaranteed by update_comps() stripping the range info explicitly in this case. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 7f42a29a84..939ed62de8 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -986,11 +986,10 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, av_assert0(av_bprint_is_complete(&bp)); av_log(log, lev, "%s\n", bp.str); - if (op->comps.min[0].den || op->comps.min[1].den || - op->comps.min[2].den || op->comps.min[3].den || - op->comps.max[0].den || op->comps.max[1].den || - op->comps.max[2].den || op->comps.max[3].den) - { + /* Only print value ranges if any are relevant */ + SwsCompMask range_mask = ff_sws_comp_mask_q4(op->comps.min) | + ff_sws_comp_mask_q4(op->comps.max); + if (range_mask & mask) { av_bprint_clear(&bp); av_bprintf(&bp, " min: "); print_q4(&bp, op->comps.min, mask); -- 2.52.0 >From d3c544bfad5f1e9566e2aa7f10a6321eb4d4a315 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 13:30:01 +0200 Subject: [PATCH 13/24] swscale/ops: don't strip range metadata for unused components As alluded to by the previous commit, this is now no longer necessary to prevent their print-out. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 939ed62de8..8f50083842 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -519,10 +519,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) bool need_in[4] = { false, false, false, false }; for (int i = 0; i < 4; i++) { - if (!need_out[i]) { + if (!need_out[i]) op->comps.flags[i] = SWS_COMP_GARBAGE; - op->comps.min[i] = op->comps.max[i] = (AVRational) {0}; - } } switch (op->op) { -- 2.52.0 >From 6efc60cffb5b6d5af274a59048f77a49913cee5b Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 13:44:08 +0200 Subject: [PATCH 14/24] swscale/ops_chain: simplify SwsClearOp checking Since this now has an explicit mask, we can just check that directly, instead of relying on the unused comps hack/trick. Additionally, this also allows us to distinguish between fixed value and arbitrary value clears by just having the SwsOpEntry contain NAN values iff they support any clear value. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_chain.c | 17 ++++++----------- libswscale/ops_chain.h | 2 +- libswscale/ops_tmpl_common.c | 2 +- libswscale/x86/ops.c | 10 +++++----- 4 files changed, 13 insertions(+), 18 deletions(-) diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index 2e915cdd63..a90f908f85 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -112,16 +112,6 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) } } - if (op->op == SWS_OP_CLEAR) { - /* Clear pattern must match exactly, regardless of `entry->flexible` */ - for (int i = 0; i < 4; i++) { - if (!SWS_OP_NEEDED(op, i)) - continue; - if (entry->unused[i] != SWS_COMP_TEST(op->clear.mask, i)) - return 0; - } - } - /* Flexible variants always match, but lower the score to prioritize more * specific implementations if they exist */ if (entry->flexible) @@ -148,10 +138,15 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) } return score; case SWS_OP_CLEAR: + /* Clear mask must match exactly */ + if (op->clear.mask != entry->clear.mask) + return 0; for (int i = 0; i < 4; i++) { if (!SWS_COMP_TEST(op->clear.mask, i) || !SWS_OP_NEEDED(op, i)) continue; - if (av_cmp_q(op->clear.value[i], Q(entry->clear_value))) + else if (!entry->clear.value[i].den) + continue; /* Any clear value supported */ + else if (av_cmp_q(op->clear.value[i], entry->clear.value[i])) return 0; } return score; diff --git a/libswscale/ops_chain.h b/libswscale/ops_chain.h index fc0cd930f8..31e0e6a403 100644 --- a/libswscale/ops_chain.h +++ b/libswscale/ops_chain.h @@ -128,9 +128,9 @@ typedef struct SwsOpEntry { SwsPackOp pack; SwsSwizzleOp swizzle; SwsConvertOp convert; + SwsClearOp clear; uint32_t linear_mask; /* subset of SwsLinearOp */ int dither_size; /* subset of SwsDitherOp */ - int clear_value; /* clear value for integer clears */ AVRational scale; /* scale factor for SWS_OP_SCALE */ }; diff --git a/libswscale/ops_tmpl_common.c b/libswscale/ops_tmpl_common.c index 3817a437e5..29b910bee3 100644 --- a/libswscale/ops_tmpl_common.c +++ b/libswscale/ops_tmpl_common.c @@ -87,7 +87,7 @@ DECL_IMPL(clear##_##X##Y##Z##W) DECL_ENTRY(clear##_##X##Y##Z##W, \ .setup = ff_sws_setup_clear, \ .op = SWS_OP_CLEAR, \ - .flexible = true, \ + .clear.mask = SWS_COMP_MASK(!X, !Y, !Z, !W), \ .unused = { !X, !Y, !Z, !W }, \ ); diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index e8be642f66..cc8f4d6e02 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -118,15 +118,15 @@ static int setup_swap_bytes(const SwsImplParams *params, SwsImplResult *out) #define DECL_CLEAR_ALPHA(EXT, IDX) \ DECL_ASM(U8, clear_alpha##IDX##EXT, \ .op = SWS_OP_CLEAR, \ - .clear_value = -1, \ - .unused[IDX] = true, \ + .clear.mask = SWS_COMP(IDX), \ + .clear.value[IDX] = Q(-1), \ ); \ #define DECL_CLEAR_ZERO(EXT, IDX) \ DECL_ASM(U8, clear_zero##IDX##EXT, \ .op = SWS_OP_CLEAR, \ - .clear_value = 0, \ - .unused[IDX] = true, \ + .clear.mask = SWS_COMP(IDX), \ + .clear.value[IDX] = Q(0), \ ); static int setup_clear(const SwsImplParams *params, SwsImplResult *out) @@ -141,7 +141,7 @@ static int setup_clear(const SwsImplParams *params, SwsImplResult *out) DECL_PATTERN(U8, clear##EXT, X, Y, Z, W, \ .op = SWS_OP_CLEAR, \ .setup = setup_clear, \ - .flexible = true, \ + .clear.mask = SWS_COMP_MASK(!X, !Y, !Z, !W), \ ); #define DECL_SWIZZLE(EXT, X, Y, Z, W) \ -- 2.52.0 >From 98ebe268ec5b99d52cc25961615b1c017588d135 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 14:25:44 +0200 Subject: [PATCH 15/24] swscale/ops: print linear mask exactly Useful for getting an exact list of linear masks from e.g. `libswscale/tests/sws_ops -summarize` rgb24 -> yuv444p: [ u8 +++X] SWS_OP_READ : 3 elem(s) packed >> 0 min: {0 0 0 _}, max: {255 255 255 _} [ u8 +++X] SWS_OP_CONVERT : u8 -> f32 min: {0 0 0 _}, max: {255 255 255 _} - [f32 ...X] SWS_OP_LINEAR : matrix3+off3 [...] + [f32 ...X] SWS_OP_LINEAR : matrix3+off3 (0x5ef7) [...] min: {16 16 16 _}, max: {235 240 240 _} [f32 ...X] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} min: {16.001953 16.001953 16.001953 _}, max: {235.998047 240.998047 240.998047 _} [f32 +++X] SWS_OP_CONVERT : f32 -> u8 min: {16 16 16 _}, max: {235 240 240 _} [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 (X = unused, z = byteswapped, + = exact, 0 = zero) Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 4 +++- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 8f50083842..88a7907542 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -908,7 +908,9 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) av_bprintf(bp, " <= x"); break; case SWS_OP_LINEAR: - av_bprintf(bp, "%-20s: %s [", name, describe_lin_mask(op->lin.mask)); + av_bprintf(bp, "%-20s: %s (0x%x) [", name, describe_lin_mask(op->lin.mask), + (unsigned) op->lin.mask); + for (int i = 0; i < 4; i++) { av_bprintf(bp, "%s[", i ? " " : ""); for (int j = 0; j < 5; j++) { diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index 0dc23800a6..2b31936cd0 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -374319dfd2b74cb5b69dac68b627fa9b +1c9911ad5f1ee16f2d1eec0f635c7abf -- 2.52.0 >From ea8ea1c9e4742e68ce4e8905c0bb5298dd555866 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 14:29:36 +0200 Subject: [PATCH 16/24] swscale/tests/sws_ops: print linear pattern in -summarize mode Gives a better idea of what the corresponding pattern looks like. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/sws_ops.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 1256c73ec6..c412427df7 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -60,8 +60,10 @@ static int register_op(SwsContext *ctx, void *opaque, SwsOp *op) switch (op->op) { case SWS_OP_LINEAR: for (int i = 0; i < 4; i++) { - for (int j = 0; j < 5; j++) - op->lin.m[i][j] = (AVRational) { 0, 1 }; + for (int j = 0; j < 5; j++) { + op->lin.m[i][j].num = !!(op->lin.mask & SWS_MASK(i, j)); + op->lin.m[i][j].den = 1; + } } break; case SWS_OP_SCALE: -- 2.52.0 >From 3ad07a41820b9f60e1292f2472a905ba3a690c1d Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 14:53:26 +0200 Subject: [PATCH 17/24] swscale/ops_backend: use generated list of linear masks Instead of hand-coding a list of "likely" patterns, we can now leverage the power of libswscale/swscale/sws_ops -summarize to get an exact list of all linear masks that we need to implement. I've pre-emptively defined this in ops_internal.h so that other backends can also use it. Overall speedup=1.183x faster on a quick bench run. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_internal.h | 19 +++++++++++++++++++ libswscale/ops_tmpl_float.c | 38 +++++++------------------------------ 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index 91509ce67d..1cfc60fda9 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -28,6 +28,25 @@ #define Q(N) ((AVRational) { N, 1 }) +/* Exracted from libswscale/tests/sws_ops -summarize */ +#define FOREACH_LINEAR(MACRO, ...) \ + MACRO(0x40000, __VA_ARGS__) \ + MACRO(0x1041, __VA_ARGS__) \ + MACRO(0x40, __VA_ARGS__) \ + MACRO(0x5251, __VA_ARGS__) \ + MACRO(0x41041, __VA_ARGS__) \ + MACRO(0x7, __VA_ARGS__) \ + MACRO(0x11, __VA_ARGS__) \ + MACRO(0x40011, __VA_ARGS__) \ + MACRO(0x5ee7, __VA_ARGS__) \ + MACRO(0x5ef4, __VA_ARGS__) \ + MACRO(0x5ef5, __VA_ARGS__) \ + MACRO(0x5ef7, __VA_ARGS__) \ + MACRO(0x45ef5, __VA_ARGS__) \ + MACRO(0x45ef7, __VA_ARGS__) \ + MACRO(0x17, __VA_ARGS__) \ + MACRO(0x40007, __VA_ARGS__) + static inline AVRational ff_sws_pixel_expand(SwsPixelType from, SwsPixelType to) { const int src = ff_sws_pixel_type_size(from); diff --git a/libswscale/ops_tmpl_float.c b/libswscale/ops_tmpl_float.c index 0d00714ff4..04c8454843 100644 --- a/libswscale/ops_tmpl_float.c +++ b/libswscale/ops_tmpl_float.c @@ -20,6 +20,7 @@ #include "libavutil/avassert.h" +#include "ops_internal.h" #include "ops_backend.h" #ifndef BIT_DEPTH @@ -188,32 +189,20 @@ DECL_FUNC(linear_mask, const uint32_t mask) CONTINUE(block_t, x, y, z, w); } -#define WRAP_LINEAR(NAME, MASK) \ -DECL_IMPL(linear_##NAME) \ +#define REF_LINEAR(MASK, ...) &fn(op_linear_##MASK), +#define WRAP_LINEAR(MASK, ...) \ +DECL_IMPL(linear_##MASK) \ { \ CALL(linear_mask, MASK); \ } \ \ -DECL_ENTRY(linear_##NAME, \ +DECL_ENTRY(linear_##MASK, \ .op = SWS_OP_LINEAR, \ .setup = fn(setup_linear), \ .linear_mask = (MASK), \ ); -WRAP_LINEAR(luma, SWS_MASK_LUMA) -WRAP_LINEAR(alpha, SWS_MASK_ALPHA) -WRAP_LINEAR(lumalpha, SWS_MASK_LUMA | SWS_MASK_ALPHA) -WRAP_LINEAR(dot3, 0x7) -WRAP_LINEAR(row0, SWS_MASK_ROW(0)) -WRAP_LINEAR(row0a, SWS_MASK_ROW(0) | SWS_MASK_ALPHA) -WRAP_LINEAR(diag3, SWS_MASK_DIAG3) -WRAP_LINEAR(diag4, SWS_MASK_DIAG4) -WRAP_LINEAR(diagoff3, SWS_MASK_DIAG3 | SWS_MASK_OFF3) -WRAP_LINEAR(matrix3, SWS_MASK_MAT3) -WRAP_LINEAR(affine3, SWS_MASK_MAT3 | SWS_MASK_OFF3) -WRAP_LINEAR(affine3a, SWS_MASK_MAT3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA) -WRAP_LINEAR(matrix4, SWS_MASK_MAT4) -WRAP_LINEAR(affine4, SWS_MASK_MAT4 | SWS_MASK_OFF4) +FOREACH_LINEAR(WRAP_LINEAR) static const SwsOpTable fn(op_table_float) = { .block_size = SWS_BLOCK_SIZE, @@ -241,20 +230,7 @@ static const SwsOpTable fn(op_table_float) = { &fn(op_clear_0111), &fn(op_clear_0011), - &fn(op_linear_luma), - &fn(op_linear_alpha), - &fn(op_linear_lumalpha), - &fn(op_linear_dot3), - &fn(op_linear_row0), - &fn(op_linear_row0a), - &fn(op_linear_diag3), - &fn(op_linear_diag4), - &fn(op_linear_diagoff3), - &fn(op_linear_matrix3), - &fn(op_linear_affine3), - &fn(op_linear_affine3a), - &fn(op_linear_matrix4), - &fn(op_linear_affine4), + FOREACH_LINEAR(REF_LINEAR) &fn(op_filter1_v), &fn(op_filter2_v), -- 2.52.0 >From 64efbdbb5575aae289bda40706573b94a5c6147f Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:02:54 +0200 Subject: [PATCH 18/24] swscale/x86/ops_float: fix typo in linear_row First vector is %2, not %3. This was never triggered before because all of the existing masks never hit this exact case. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/x86/ops_float.asm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libswscale/x86/ops_float.asm b/libswscale/x86/ops_float.asm index 75ee0cf7f7..4f09ab846e 100644 --- a/libswscale/x86/ops_float.asm +++ b/libswscale/x86/ops_float.asm @@ -327,7 +327,7 @@ op dither%1 IF COL(2), mulps m14, %4 IF COL(3), mulps m15, %5 IF COL(0), addps %1, m12 - IF NOP(0) && COL(4), addps %1, %3 ; first vector was not reused + IF NOP(0) && COL(4), addps %1, %2 ; first vector was not reused IF COL(1), addps %1, m13 IF NOP(1), addps %1, %3 IF COL(2), addps %1, m14 -- 2.52.0 >From aaf8d01ff1cff08fd9cf81238e4121e390b378ef Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:09:43 +0200 Subject: [PATCH 19/24] swscale/x86/ops_float: use generated list of linear masks If this ever changes, it should trigger linker errors due to the fact that the linear functions contain the exact mask. This is very much by design. Overall speedup=1.101x faster on a quick bench run. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/x86/ops.c | 36 ++++------------------- libswscale/x86/ops_float.asm | 55 +++++++++++++++--------------------- 2 files changed, 28 insertions(+), 63 deletions(-) diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index cc8f4d6e02..d6160c3ac8 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -277,13 +277,15 @@ static int setup_linear(const SwsImplParams *params, SwsImplResult *out) return 0; } -#define DECL_LINEAR(EXT, NAME, MASK) \ - DECL_ASM(F32, NAME##EXT, \ +#define DECL_LINEAR(MASK, EXT) \ + DECL_ASM(F32, linear_##MASK##EXT, \ .op = SWS_OP_LINEAR, \ .setup = setup_linear, \ .linear_mask = (MASK), \ ); +#define REF_LINEAR(MASK, EXT) &op_linear_##MASK##EXT, + static bool check_filter_fma(const SwsImplParams *params) { const SwsOp *op = params->op; @@ -730,20 +732,7 @@ static const SwsOpTable ops16##EXT = { DECL_DITHER(DECL_ASM, EXT, 6) \ DECL_DITHER(DECL_ASM, EXT, 7) \ DECL_DITHER(DECL_ASM, EXT, 8) \ - DECL_LINEAR(EXT, luma, SWS_MASK_LUMA) \ - DECL_LINEAR(EXT, alpha, SWS_MASK_ALPHA) \ - DECL_LINEAR(EXT, lumalpha, SWS_MASK_LUMA | SWS_MASK_ALPHA) \ - DECL_LINEAR(EXT, dot3, 0x7) \ - DECL_LINEAR(EXT, row0, SWS_MASK_ROW(0)) \ - DECL_LINEAR(EXT, row0a, SWS_MASK_ROW(0) | SWS_MASK_ALPHA) \ - DECL_LINEAR(EXT, diag3, SWS_MASK_DIAG3) \ - DECL_LINEAR(EXT, diag4, SWS_MASK_DIAG4) \ - DECL_LINEAR(EXT, diagoff3, SWS_MASK_DIAG3 | SWS_MASK_OFF3) \ - DECL_LINEAR(EXT, matrix3, SWS_MASK_MAT3) \ - DECL_LINEAR(EXT, affine3, SWS_MASK_MAT3 | SWS_MASK_OFF3) \ - DECL_LINEAR(EXT, affine3a, SWS_MASK_MAT3 | SWS_MASK_OFF3 | SWS_MASK_ALPHA) \ - DECL_LINEAR(EXT, matrix4, SWS_MASK_MAT4) \ - DECL_LINEAR(EXT, affine4, SWS_MASK_MAT4 | SWS_MASK_OFF4) \ + FOREACH_LINEAR(DECL_LINEAR, EXT) \ DECL_FILTERS_GENERIC(EXT, U8) \ DECL_FILTERS_GENERIC(EXT, U16) \ DECL_FILTERS_GENERIC(EXT, F32) \ @@ -784,20 +773,7 @@ static const SwsOpTable ops32##EXT = { &op_dither6##EXT, \ &op_dither7##EXT, \ &op_dither8##EXT, \ - &op_luma##EXT, \ - &op_alpha##EXT, \ - &op_lumalpha##EXT, \ - &op_dot3##EXT, \ - &op_row0##EXT, \ - &op_row0a##EXT, \ - &op_diag3##EXT, \ - &op_diag4##EXT, \ - &op_diagoff3##EXT, \ - &op_matrix3##EXT, \ - &op_affine3##EXT, \ - &op_affine3a##EXT, \ - &op_matrix4##EXT, \ - &op_affine4##EXT, \ + FOREACH_LINEAR(REF_LINEAR, EXT) \ REF_FILTERS(filter_fma_v, _U8##EXT), \ REF_FILTERS(filter_fma_v, _U16##EXT), \ REF_FILTERS(filter_fma_v, _F32##EXT), \ diff --git a/libswscale/x86/ops_float.asm b/libswscale/x86/ops_float.asm index 4f09ab846e..5083d005d2 100644 --- a/libswscale/x86/ops_float.asm +++ b/libswscale/x86/ops_float.asm @@ -287,20 +287,7 @@ op dither%1 ; Linear transformations %xdefine MASK(I, J) (1 << (5 * (I) + (J))) -%xdefine MASK_OFF(I) MASK(I, 4) %xdefine MASK_ROW(I) (0x1F << (5 * (I))) -%xdefine MASK_COL(J) (0x8421 << J) -%xdefine MASK_ALL (1 << 20) - 1 -%xdefine MASK_LUMA MASK(0, 0) | MASK_OFF(0) -%xdefine MASK_ALPHA MASK(3, 3) | MASK_OFF(3) -%xdefine MASK_DIAG3 MASK(0, 0) | MASK(1, 1) | MASK(2, 2) -%xdefine MASK_OFF3 MASK_OFF(0) | MASK_OFF(1) | MASK_OFF(2) -%xdefine MASK_MAT3 MASK(0, 0) | MASK(0, 1) | MASK(0, 2) |\ - MASK(1, 0) | MASK(1, 1) | MASK(1, 2) |\ - MASK(2, 0) | MASK(2, 1) | MASK(2, 2) -%xdefine MASK_DIAG4 MASK_DIAG3 | MASK(3, 3) -%xdefine MASK_OFF4 MASK_OFF3 | MASK_OFF(3) -%xdefine MASK_MAT4 MASK_ALL & ~MASK_OFF4 %macro linear_row 7 ; res, x, y, z, w, row, mask %define COL(J) ((%7) & MASK(%6, J)) ; true if mask contains component J @@ -348,17 +335,17 @@ op dither%1 IF ROW(3), mova %4, m11 %endmacro -%macro linear_mask 2 ; name, mask -op %1 +%macro linear_mask 1 ; mask +op linear_%1 mov tmp0q, [implq + SwsOpImpl.priv] ; address of matrix - linear_inner mx, my, mz, mw, %2 - linear_inner mx2, my2, mz2, mw2, %2 + linear_inner mx, my, mz, mw, %1 + linear_inner mx2, my2, mz2, mw2, %1 CONTINUE %endmacro ; specialized functions for very simple cases -%macro linear_dot3 0 -op dot3 +%macro linear_0x7 0 +op linear_0x7 mov tmp0q, [implq + SwsOpImpl.priv] vbroadcastss m12, [tmp0q + 0] vbroadcastss m13, [tmp0q + 4] @@ -378,20 +365,22 @@ op dot3 %endmacro %macro linear_fns 0 - linear_dot3 - linear_mask luma, MASK_LUMA - linear_mask alpha, MASK_ALPHA - linear_mask lumalpha, MASK_LUMA | MASK_ALPHA - linear_mask row0, MASK_ROW(0) - linear_mask row0a, MASK_ROW(0) | MASK_ALPHA - linear_mask diag3, MASK_DIAG3 - linear_mask diag4, MASK_DIAG4 - linear_mask diagoff3, MASK_DIAG3 | MASK_OFF3 - linear_mask matrix3, MASK_MAT3 - linear_mask affine3, MASK_MAT3 | MASK_OFF3 - linear_mask affine3a, MASK_MAT3 | MASK_OFF3 | MASK_ALPHA - linear_mask matrix4, MASK_MAT4 - linear_mask affine4, MASK_MAT4 | MASK_OFF4 + linear_mask 0x40000 + linear_mask 0x1041 + linear_mask 0x40 + linear_mask 0x5251 + linear_mask 0x41041 + linear_0x7 + linear_mask 0x11 + linear_mask 0x40011 + linear_mask 0x5ee7 + linear_mask 0x5ef4 + linear_mask 0x5ef5 + linear_mask 0x5ef7 + linear_mask 0x45ef5 + linear_mask 0x45ef7 + linear_mask 0x17 + linear_mask 0x40007 %endmacro ;--------------------------------------------------------- -- 2.52.0 >From 5cdbcdc368e95b05b23fc1a6033639b25fb79308 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:11:27 +0200 Subject: [PATCH 20/24] swscale/ops_chain: check for exact linear mask match Makes this logic a lot simpler and less brittle. We can trivially adjust the list of linear masks that are required, whenever it changes as a result of any future modifications. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_chain.c | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index a90f908f85..5ff1d0947b 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -68,10 +68,6 @@ int ff_sws_op_chain_append(SwsOpChain *chain, SwsFuncPtr func, * Likewise, if `ref->comps` has any components marked as unused, they must be * marked as unused in `ops` as well. * - * For SWS_OP_LINEAR, `ref->linear.mask` must be a strict superset of - * `op->linear.mask`, but may not contain any columns explicitly ignored by - * `op->comps.unused`. - * * For unfiltered SWS_OP_READ/SWS_OP_WRITE, SWS_OP_SWAP_BYTES and * SWS_OP_SWIZZLE, the exact type is not checked, just the size. * @@ -172,17 +168,8 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) av_assert1(entry->flexible); break; case SWS_OP_LINEAR: - /* All required elements must be present */ - if (op->lin.mask & ~entry->linear_mask) + if (op->lin.mask != entry->linear_mask) return 0; - /* To avoid operating on possibly undefined memory, filter out - * implementations that operate on more input components */ - for (int i = 0; i < 4; i++) { - if ((entry->linear_mask & SWS_MASK_COL(i)) && op->comps.unused[i]) - return 0; - } - /* Prioritize smaller implementations */ - score += av_popcount(SWS_MASK_ALL ^ entry->linear_mask); return score; case SWS_OP_SCALE: return av_cmp_q(op->scale.factor, entry->scale) ? 0 : score; -- 2.52.0 >From 08ebdea97f5e3a59bf2ac279802b80999a2c2b01 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:17:11 +0200 Subject: [PATCH 21/24] swscale/ops_chain: replace SwsOpEntry.unused by SwsCompMask Needed to allow us to phase out SwsComps.unused altogether. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_backend.h | 3 ++- libswscale/ops_chain.c | 18 ++++++------------ libswscale/ops_chain.h | 2 +- libswscale/ops_tmpl_common.c | 1 - libswscale/x86/ops.c | 5 +++-- 5 files changed, 12 insertions(+), 17 deletions(-) diff --git a/libswscale/ops_backend.h b/libswscale/ops_backend.h index f0b89d51c0..8085c40249 100644 --- a/libswscale/ops_backend.h +++ b/libswscale/ops_backend.h @@ -134,6 +134,7 @@ static inline int ff_setup_memdup(const void *c, size_t size, SwsImplResult *out static const SwsOpEntry fn(op_##NAME) = { \ .func = (SwsFuncPtr) fn(NAME), \ .type = PIXEL_TYPE, \ + .mask = SWS_COMP_ALL, \ __VA_ARGS__ \ } @@ -148,7 +149,7 @@ static inline int ff_setup_memdup(const void *c, size_t size, SwsImplResult *out } \ \ DECL_ENTRY(FUNC##_##X##Y##Z##W, \ - .unused = { !X, !Y, !Z, !W }, \ + .mask = SWS_COMP_MASK(X, Y, Z, W), \ __VA_ARGS__ \ ) diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index 5ff1d0947b..f40a0081f8 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -64,10 +64,6 @@ int ff_sws_op_chain_append(SwsOpChain *chain, SwsFuncPtr func, * Match an operation against a reference operation. Returns a score for how * well the reference matches the operation, or 0 if there is no match. * - * If `ref->comps` has any flags set, they must be set in `op` as well. - * Likewise, if `ref->comps` has any components marked as unused, they must be - * marked as unused in `ops` as well. - * * For unfiltered SWS_OP_READ/SWS_OP_WRITE, SWS_OP_SWAP_BYTES and * SWS_OP_SWIZZLE, the exact type is not checked, just the size. * @@ -99,14 +95,12 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry) break; } - for (int i = 0; i < 4; i++) { - if (entry->unused[i]) { - if (op->comps.unused[i]) - score += 1; /* Operating on fewer components is better .. */ - else - return 0; /* .. but not too few! */ - } - } + const SwsCompMask needed = ff_sws_comp_mask_needed(op); + if (needed & ~entry->mask) + return 0; /* Entry doesn't compute all needed components */ + + /* Otherwise, operating on fewer components is better */ + score += av_popcount(~entry->mask & SWS_COMP_ALL); /* Flexible variants always match, but lower the score to prioritize more * specific implementations if they exist */ diff --git a/libswscale/ops_chain.h b/libswscale/ops_chain.h index 31e0e6a403..9fe2c06613 100644 --- a/libswscale/ops_chain.h +++ b/libswscale/ops_chain.h @@ -120,8 +120,8 @@ typedef struct SwsOpEntry { /* Kernel metadata; reduced size subset of SwsOp */ SwsOpType op; SwsPixelType type; + SwsCompMask mask; /* mask of active components (after operation) */ bool flexible; /* if true, only the type and op are matched */ - bool unused[4]; /* for kernels which operate on a subset of components */ union { /* extra data defining the operation, unless `flexible` is true */ SwsReadWriteOp rw; diff --git a/libswscale/ops_tmpl_common.c b/libswscale/ops_tmpl_common.c index 29b910bee3..cdd34dbdd8 100644 --- a/libswscale/ops_tmpl_common.c +++ b/libswscale/ops_tmpl_common.c @@ -88,7 +88,6 @@ DECL_ENTRY(clear##_##X##Y##Z##W, .setup = ff_sws_setup_clear, \ .op = SWS_OP_CLEAR, \ .clear.mask = SWS_COMP_MASK(!X, !Y, !Z, !W), \ - .unused = { !X, !Y, !Z, !W }, \ ); WRAP_CLEAR(1, 1, 1, 0) /* rgba alpha */ diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index d6160c3ac8..5260bd97e8 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -28,6 +28,7 @@ #define DECL_ENTRY(TYPE, NAME, ...) \ static const SwsOpEntry op_##NAME = { \ .type = SWS_PIXEL_##TYPE, \ + .mask = SWS_COMP_ALL, \ __VA_ARGS__ \ } @@ -39,7 +40,7 @@ #define DECL_PATTERN(TYPE, NAME, X, Y, Z, W, ...) \ DECL_ASM(TYPE, p##X##Y##Z##W##_##NAME, \ - .unused = { !X, !Y, !Z, !W }, \ + .mask = SWS_COMP_MASK(X, Y, Z, W), \ __VA_ARGS__ \ ) @@ -110,7 +111,7 @@ static int setup_swap_bytes(const SwsImplParams *params, SwsImplResult *out) #define DECL_SWAP_BYTES(EXT, TYPE, X, Y, Z, W) \ DECL_ENTRY(TYPE, p##X##Y##Z##W##_swap_bytes_##TYPE##EXT, \ .op = SWS_OP_SWAP_BYTES, \ - .unused = { !X, !Y, !Z, !W }, \ + .mask = SWS_COMP_MASK(X, Y, Z, W), \ .func = ff_p##X##Y##Z##W##_shuffle##EXT, \ .setup = setup_swap_bytes, \ ); -- 2.52.0 >From 3c7ea886d95611fe233a8ebb7f4d93d71794074e Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:18:44 +0200 Subject: [PATCH 22/24] swscale/ops: nuke SwsComps.unused Finally, remove the last relic of this accursed design mistake. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 5 +---- libswscale/ops.h | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 88a7907542..fe99be36fe 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -572,10 +572,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; } - for (int i = 0; i < 4; i++) { - need_out[i] = need_in[i]; - op->comps.unused[i] = !need_in[i]; - } + memcpy(need_out, need_in, sizeof(need_in)); } } diff --git a/libswscale/ops.h b/libswscale/ops.h index 122e392177..1a3349d367 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -107,7 +107,6 @@ typedef enum SwsCompFlags { typedef struct SwsComps { SwsCompFlags flags[4]; /* knowledge about (output) component contents */ - bool unused[4]; /* which input components are definitely unused */ /* Keeps track of the known possible value range, or {0, 0} for undefined * or (unknown range) floating point inputs */ -- 2.52.0 >From 64e1ab7b32a5b13ab99c56eb8a3816a864c8bfaa Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:21:33 +0200 Subject: [PATCH 23/24] swscale/ops_backend: simplify SWS_OP_CLEAR declarations Mark the components to be cleared, not the components to be preserved. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_tmpl_common.c | 32 ++++++++++++++++---------------- libswscale/ops_tmpl_float.c | 8 ++++---- libswscale/ops_tmpl_int.c | 18 +++++++++--------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/libswscale/ops_tmpl_common.c b/libswscale/ops_tmpl_common.c index cdd34dbdd8..8d2569da55 100644 --- a/libswscale/ops_tmpl_common.c +++ b/libswscale/ops_tmpl_common.c @@ -65,13 +65,13 @@ DECL_PATTERN(clear) { SWS_LOOP for (int i = 0; i < SWS_BLOCK_SIZE; i++) { - if (!X) + if (X) x[i] = impl->priv.px[0]; - if (!Y) + if (Y) y[i] = impl->priv.px[1]; - if (!Z) + if (Z) z[i] = impl->priv.px[2]; - if (!W) + if (W) w[i] = impl->priv.px[3]; } @@ -87,22 +87,22 @@ DECL_IMPL(clear##_##X##Y##Z##W) DECL_ENTRY(clear##_##X##Y##Z##W, \ .setup = ff_sws_setup_clear, \ .op = SWS_OP_CLEAR, \ - .clear.mask = SWS_COMP_MASK(!X, !Y, !Z, !W), \ + .clear.mask = SWS_COMP_MASK(X, Y, Z, W), \ ); -WRAP_CLEAR(1, 1, 1, 0) /* rgba alpha */ -WRAP_CLEAR(0, 1, 1, 1) /* argb alpha */ -WRAP_CLEAR(1, 0, 1, 1) /* ya alpha */ +WRAP_CLEAR(0, 0, 0, 1) /* rgba alpha */ +WRAP_CLEAR(1, 0, 0, 0) /* argb alpha */ +WRAP_CLEAR(0, 1, 0, 0) /* ya alpha */ -WRAP_CLEAR(0, 0, 1, 1) /* vuya chroma */ -WRAP_CLEAR(1, 0, 0, 1) /* yuva chroma */ -WRAP_CLEAR(1, 1, 0, 0) /* ayuv chroma */ -WRAP_CLEAR(0, 1, 0, 1) /* uyva chroma */ -WRAP_CLEAR(1, 0, 1, 0) /* xvyu chroma */ +WRAP_CLEAR(1, 1, 0, 0) /* vuya chroma */ +WRAP_CLEAR(0, 1, 1, 0) /* yuva chroma */ +WRAP_CLEAR(0, 0, 1, 1) /* ayuv chroma */ +WRAP_CLEAR(1, 0, 1, 0) /* uyva chroma */ +WRAP_CLEAR(0, 1, 0, 1) /* xvyu chroma */ -WRAP_CLEAR(1, 0, 0, 0) /* gray -> yuva */ -WRAP_CLEAR(0, 1, 0, 0) /* gray -> ayuv */ -WRAP_CLEAR(0, 0, 1, 0) /* gray -> vuya */ +WRAP_CLEAR(0, 1, 1, 1) /* gray -> yuva */ +WRAP_CLEAR(1, 0, 1, 1) /* gray -> ayuv */ +WRAP_CLEAR(1, 1, 0, 1) /* gray -> vuya */ DECL_PATTERN(min) { diff --git a/libswscale/ops_tmpl_float.c b/libswscale/ops_tmpl_float.c index 04c8454843..4c0f59dd83 100644 --- a/libswscale/ops_tmpl_float.c +++ b/libswscale/ops_tmpl_float.c @@ -211,7 +211,7 @@ static const SwsOpTable fn(op_table_float) = { REF_COMMON_PATTERNS(convert_uint16), REF_COMMON_PATTERNS(convert_uint32), - &fn(op_clear_1110), + &fn(op_clear_0001), REF_COMMON_PATTERNS(min), REF_COMMON_PATTERNS(max), REF_COMMON_PATTERNS(scale), @@ -226,9 +226,9 @@ static const SwsOpTable fn(op_table_float) = { &fn(op_dither7), &fn(op_dither8), - &fn(op_clear_1110), - &fn(op_clear_0111), - &fn(op_clear_0011), + &fn(op_clear_0001), + &fn(op_clear_1000), + &fn(op_clear_1100), FOREACH_LINEAR(REF_LINEAR) diff --git a/libswscale/ops_tmpl_int.c b/libswscale/ops_tmpl_int.c index b010513bc5..b53b00deaa 100644 --- a/libswscale/ops_tmpl_int.c +++ b/libswscale/ops_tmpl_int.c @@ -543,17 +543,17 @@ static const SwsOpTable fn(op_table_int) = { REF_COMMON_PATTERNS(scale), REF_COMMON_PATTERNS(convert_float), - &fn(op_clear_1110), - &fn(op_clear_0111), - &fn(op_clear_0011), - &fn(op_clear_1011), - &fn(op_clear_1001), - &fn(op_clear_1100), - &fn(op_clear_0101), - &fn(op_clear_1010), + &fn(op_clear_0001), &fn(op_clear_1000), + &fn(op_clear_1100), &fn(op_clear_0100), - &fn(op_clear_0010), + &fn(op_clear_0110), + &fn(op_clear_0011), + &fn(op_clear_1010), + &fn(op_clear_0101), + &fn(op_clear_0111), + &fn(op_clear_1011), + &fn(op_clear_1101), &fn(op_swizzle_3012), &fn(op_swizzle_3021), -- 2.52.0 >From 67674d5917565ce7ecc0a22a4cb757f9f59085ec Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 31 Mar 2026 16:24:05 +0200 Subject: [PATCH 24/24] swscale/x86/ops: simplify SWS_OP_CLEAR patterns Make the components to be cleared, not the components to be preserved. Signed-off-by: Niklas Haas <[email protected]> --- libswscale/x86/ops.c | 36 +++++++++++++------------- libswscale/x86/ops_int.asm | 52 ++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/libswscale/x86/ops.c b/libswscale/x86/ops.c index 5260bd97e8..8c5ac9f555 100644 --- a/libswscale/x86/ops.c +++ b/libswscale/x86/ops.c @@ -582,16 +582,16 @@ static int setup_filter_4x4_h(const SwsImplParams *params, SwsImplResult *out) DECL_CLEAR_ZERO(EXT, 0) \ DECL_CLEAR_ZERO(EXT, 1) \ DECL_CLEAR_ZERO(EXT, 3) \ - DECL_CLEAR(EXT, 1, 1, 1, 0) \ - DECL_CLEAR(EXT, 0, 1, 1, 1) \ - DECL_CLEAR(EXT, 0, 0, 1, 1) \ - DECL_CLEAR(EXT, 1, 0, 0, 1) \ - DECL_CLEAR(EXT, 1, 1, 0, 0) \ - DECL_CLEAR(EXT, 0, 1, 0, 1) \ - DECL_CLEAR(EXT, 1, 0, 1, 0) \ + DECL_CLEAR(EXT, 0, 0, 0, 1) \ DECL_CLEAR(EXT, 1, 0, 0, 0) \ - DECL_CLEAR(EXT, 0, 1, 0, 0) \ - DECL_CLEAR(EXT, 0, 0, 1, 0) \ + DECL_CLEAR(EXT, 1, 1, 0, 0) \ + DECL_CLEAR(EXT, 0, 1, 1, 0) \ + DECL_CLEAR(EXT, 0, 0, 1, 1) \ + DECL_CLEAR(EXT, 1, 0, 1, 0) \ + DECL_CLEAR(EXT, 0, 1, 0, 1) \ + DECL_CLEAR(EXT, 0, 1, 1, 1) \ + DECL_CLEAR(EXT, 1, 0, 1, 1) \ + DECL_CLEAR(EXT, 1, 1, 0, 1) \ \ static const SwsOpTable ops8##EXT = { \ .cpu_flags = AV_CPU_FLAG_##FLAG, \ @@ -649,16 +649,16 @@ static const SwsOpTable ops8##EXT = { &op_clear_zero0##EXT, \ &op_clear_zero1##EXT, \ &op_clear_zero3##EXT, \ - REF_PATTERN(clear##EXT, 1, 1, 1, 0), \ - REF_PATTERN(clear##EXT, 0, 1, 1, 1), \ - REF_PATTERN(clear##EXT, 0, 0, 1, 1), \ - REF_PATTERN(clear##EXT, 1, 0, 0, 1), \ - REF_PATTERN(clear##EXT, 1, 1, 0, 0), \ - REF_PATTERN(clear##EXT, 0, 1, 0, 1), \ - REF_PATTERN(clear##EXT, 1, 0, 1, 0), \ + REF_PATTERN(clear##EXT, 0, 0, 0, 1), \ REF_PATTERN(clear##EXT, 1, 0, 0, 0), \ - REF_PATTERN(clear##EXT, 0, 1, 0, 0), \ - REF_PATTERN(clear##EXT, 0, 0, 1, 0), \ + REF_PATTERN(clear##EXT, 1, 1, 0, 0), \ + REF_PATTERN(clear##EXT, 0, 1, 1, 0), \ + REF_PATTERN(clear##EXT, 0, 0, 1, 1), \ + REF_PATTERN(clear##EXT, 1, 0, 1, 0), \ + REF_PATTERN(clear##EXT, 0, 1, 0, 1), \ + REF_PATTERN(clear##EXT, 0, 1, 1, 1), \ + REF_PATTERN(clear##EXT, 1, 0, 1, 1), \ + REF_PATTERN(clear##EXT, 1, 1, 0, 1), \ NULL \ }, \ }; diff --git a/libswscale/x86/ops_int.asm b/libswscale/x86/ops_int.asm index 657a40df65..2a27c3c8c5 100644 --- a/libswscale/x86/ops_int.asm +++ b/libswscale/x86/ops_int.asm @@ -618,47 +618,45 @@ IF V2, mova %3, %2 CONTINUE tmp0q %endmacro -; note: the pattern is inverted for these functions; i.e. X=1 implies that we -; *keep* the X component, not that we clear it %macro clear_generic 0 op clear LOAD_CONT tmp0q %if cpuflag(avx2) - IF !X, vpbroadcastd mx, [implq + SwsOpImpl.priv + 0] - IF !Y, vpbroadcastd my, [implq + SwsOpImpl.priv + 4] - IF !Z, vpbroadcastd mz, [implq + SwsOpImpl.priv + 8] - IF !W, vpbroadcastd mw, [implq + SwsOpImpl.priv + 12] + IF X, vpbroadcastd mx, [implq + SwsOpImpl.priv + 0] + IF Y, vpbroadcastd my, [implq + SwsOpImpl.priv + 4] + IF Z, vpbroadcastd mz, [implq + SwsOpImpl.priv + 8] + IF W, vpbroadcastd mw, [implq + SwsOpImpl.priv + 12] %else ; !cpuflag(avx2) - IF !X, movd mx, [implq + SwsOpImpl.priv + 0] - IF !Y, movd my, [implq + SwsOpImpl.priv + 4] - IF !Z, movd mz, [implq + SwsOpImpl.priv + 8] - IF !W, movd mw, [implq + SwsOpImpl.priv + 12] - IF !X, pshufd mx, mx, 0 - IF !Y, pshufd my, my, 0 - IF !Z, pshufd mz, mz, 0 - IF !W, pshufd mw, mw, 0 + IF X, movd mx, [implq + SwsOpImpl.priv + 0] + IF Y, movd my, [implq + SwsOpImpl.priv + 4] + IF Z, movd mz, [implq + SwsOpImpl.priv + 8] + IF W, movd mw, [implq + SwsOpImpl.priv + 12] + IF X, pshufd mx, mx, 0 + IF Y, pshufd my, my, 0 + IF Z, pshufd mz, mz, 0 + IF W, pshufd mw, mw, 0 %endif %if V2 - IF !X, mova mx2, mx - IF !Y, mova my2, my - IF !Z, mova mz2, mz - IF !W, mova mw2, mw + IF X, mova mx2, mx + IF Y, mova my2, my + IF Z, mova mz2, mz + IF W, mova mw2, mw %endif CONTINUE tmp0q %endmacro %macro clear_funcs 0 - decl_pattern 1, 1, 1, 0, clear_generic - decl_pattern 0, 1, 1, 1, clear_generic - decl_pattern 0, 0, 1, 1, clear_generic - decl_pattern 1, 0, 1, 1, clear_generic - decl_pattern 1, 0, 0, 1, clear_generic - decl_pattern 1, 1, 0, 0, clear_generic - decl_pattern 0, 1, 0, 1, clear_generic - decl_pattern 1, 0, 1, 0, clear_generic + decl_pattern 0, 0, 0, 1, clear_generic decl_pattern 1, 0, 0, 0, clear_generic + decl_pattern 1, 1, 0, 0, clear_generic decl_pattern 0, 1, 0, 0, clear_generic - decl_pattern 0, 0, 1, 0, clear_generic + decl_pattern 0, 1, 1, 0, clear_generic + decl_pattern 0, 0, 1, 1, clear_generic + decl_pattern 1, 0, 1, 0, clear_generic + decl_pattern 0, 1, 0, 1, clear_generic + decl_pattern 0, 1, 1, 1, clear_generic + decl_pattern 1, 0, 1, 1, clear_generic + decl_pattern 1, 1, 0, 1, clear_generic %endmacro ;--------------------------------------------------------- -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
