This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit b99247953902b62986801053a9f252dc8fa11a75 Author: Niklas Haas <[email protected]> AuthorDate: Sat Jun 20 16:12:51 2026 +0200 Commit: Niklas Haas <[email protected]> CommitDate: Tue Jun 23 11:48:13 2026 +0000 swscale/ops: keep track of copied/cleared components These represent components which have not (yet) been modified from their input values (i.e. after a read, or clear). Such components can be basically passed through via a refcopy (where applicable), as well as helping to distinguish dissimilar types of plane for (plane splitting). Generates benign diffs like: gray 16x16 -> yuv444p 16x16: - [ u8 +XXX] SWS_OP_READ : 1 elem(s) planar >> 0 + [ u8 =XXX] SWS_OP_READ : 1 elem(s) planar >> 0 min: {0 _ _ _}, max: {255 _ _ _} - [ u8 +XXX] SWS_OP_CONVERT : u8 -> f32 + [ u8 =XXX] SWS_OP_CONVERT : u8 -> f32 min: {0 _ _ _}, max: {255 _ _ _} [f32 .XXX] SWS_OP_LINEAR : luma [[73/85 0 0 0 16] [0 1 0 0 0] [0 0 1 0 0] [0 0 0 1 0]] min: {16 _ _ _}, max: {235 _ _ _} [f32 .XXX] SWS_OP_DITHER : 16x16 matrix + {0 -1 -1 -1} min: {16.001953 _ _ _}, max: {235.998047 _ _ _} [f32 +XXX] SWS_OP_CONVERT : f32 -> u8 min: {16 _ _ _}, max: {235 _ _ _} - [ u8 +++X] SWS_OP_CLEAR : {_ 128 128 _} + [ u8 +$$X] SWS_OP_CLEAR : {_ 128 128 _} min: {16 128 128 _}, max: {235 128 128 _} [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 - (X = unused, z = byteswapped, + = exact, 0 = zero) + ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) translated micro-ops: u8_read_planar_x u8_to_f32_x f32_linear_x_x000x f32_dither_x_0_16x16 f32_to_u8_x u8_clear_yz_xx u8_write_planar_xyz Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 91 ++++++++++++++++++++++++++------------------- libswscale/ops.h | 2 + tests/ref/fate/sws-ops-list | 2 +- 3 files changed, 56 insertions(+), 39 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index ec7f358005..28739276f1 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -305,11 +305,14 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) av_unreachable("Invalid operation type!"); } -/* merge_comp_flags() forms a monoid with SWS_COMP_IDENTITY as the null element */ enum { - SWS_COMP_IDENTITY = SWS_COMP_ZERO | SWS_COMP_EXACT, + SWS_COMP_IDENTITY = SWS_COMP_ZERO | SWS_COMP_EXACT | + SWS_COMP_COPY | SWS_COMP_CONST, + + SWS_COMP_DIRTY = ~(SWS_COMP_COPY | SWS_COMP_CONST), }; +/* 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; @@ -317,29 +320,13 @@ static SwsCompFlags merge_comp_flags(SwsCompFlags a, SwsCompFlags b) return ((a & b) & flags_and) | ((a | b) & flags_or); } -/* Linearly propagate flags per component */ -static void propagate_flags(SwsOp *op, const SwsComps *prev) -{ - for (int i = 0; i < 4; i++) - op->comps.flags[i] = prev->flags[i]; -} - -/* Clear undefined values in dst with src */ -static void clear_undefined_values(AVRational dst[4], const AVRational src[4]) -{ - for (int i = 0; i < 4; i++) { - if (dst[i].den == 0) - dst[i] = src[i]; - } -} - static void apply_filter_weights(SwsComps *comps, const SwsComps *prev, const SwsFilterWeights *weights) { const AVRational posw = { weights->sum_positive, SWS_FILTER_SCALE }; const AVRational negw = { weights->sum_negative, SWS_FILTER_SCALE }; for (int i = 0; i < 4; i++) { - comps->flags[i] = prev->flags[i]; + comps->flags[i] = prev->flags[i] & SWS_COMP_DIRTY; /* Only point sampling preserves exactness */ if (weights->filter_size != 1) comps->flags[i] &= ~SWS_COMP_EXACT; @@ -391,9 +378,19 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) } av_assert0(!(ops->comps_src.flags[idx] & SWS_COMP_GARBAGE)); - op->comps.flags[i] = ops->comps_src.flags[idx]; + op->comps.flags[i] = ops->comps_src.flags[idx] & SWS_COMP_DIRTY; op->comps.min[i] = ops->comps_src.min[idx]; op->comps.max[i] = ops->comps_src.max[idx]; + + /** + * Don't mark packed or fractional reads as a copy, because the + * read operation implicitly unpacks the data into separate + * components. The only case in which op lists involving such + * reads can be refcopies is in the case of a true noop, which + * is already covered by the no-op check. + */ + if (op->rw.mode == SWS_RW_PLANAR && !op->rw.frac) + op->comps.flags[i] |= SWS_COMP_COPY; } if (op->rw.filter.op) { @@ -403,7 +400,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_SWAP_BYTES: for (int i = 0; i < 4; i++) { - op->comps.flags[i] = prev.flags[i] ^ SWS_COMP_SWAPPED; + op->comps.flags[i] = (prev.flags[i] ^ SWS_COMP_SWAPPED) & SWS_COMP_DIRTY; op->comps.min[i] = prev.min[i]; op->comps.max[i] = prev.max[i]; } @@ -411,27 +408,35 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_WRITE: for (int i = 0; i < op->rw.elems; i++) av_assert1(!(prev.flags[i] & SWS_COMP_GARBAGE)); - av_fallthrough; + for (int i = 0; i < 4; i++) + op->comps.flags[i] = prev.flags[i]; + break; case SWS_OP_LSHIFT: case SWS_OP_RSHIFT: - propagate_flags(op, &prev); + for (int i = 0; i < 4; i++) + op->comps.flags[i] = prev.flags[i] & SWS_COMP_DIRTY; break; case SWS_OP_MIN: - propagate_flags(op, &prev); - 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->clamp.limit); + case SWS_OP_MAX: { + AVRational *bound = op->op == SWS_OP_MIN ? op->comps.max : op->comps.min; + for (int i = 0; i < 4; i++) { + op->comps.flags[i] = prev.flags[i]; + if (op->clamp.limit[i].den) + op->comps.flags[i] &= SWS_COMP_DIRTY; + if (!bound[i].den) /* reset undefined bounds to known range */ + bound[i] = op->clamp.limit[i]; + } break; + } case SWS_OP_DITHER: for (int i = 0; i < 4; i++) { - op->comps.min[i] = prev.min[i]; - op->comps.max[i] = prev.max[i]; + op->comps.flags[i] = prev.flags[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.flags[i] &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY; 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); } @@ -441,7 +446,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) const int pattern = op->pack.pattern[i]; if (pattern) { av_assert1(pattern < 32); - op->comps.flags[i] = prev.flags[0]; + op->comps.flags[i] = prev.flags[0] & SWS_COMP_DIRTY; op->comps.min[i] = Q(0); op->comps.max[i] = Q((1ULL << pattern) - 1); } else @@ -456,13 +461,13 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) if (i > 0) /* clear remaining comps for sanity */ op->comps.flags[i] = SWS_COMP_GARBAGE; } - op->comps.flags[0] = flags; + op->comps.flags[0] = flags & SWS_COMP_DIRTY; break; } case SWS_OP_CLEAR: for (int i = 0; i < 4; i++) { if (SWS_COMP_TEST(op->clear.mask, i)) { - op->comps.flags[i] = 0; + op->comps.flags[i] = SWS_COMP_CONST; if (op->clear.value[i].num == 0) op->comps.flags[i] |= SWS_COMP_ZERO; if (op->clear.value[i].den == 1) @@ -479,6 +484,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_CONVERT: for (int i = 0; i < 4; i++) { op->comps.flags[i] = prev.flags[i]; + if (!(prev.flags[i] & SWS_COMP_EXACT) || op->convert.expand) + op->comps.flags[i] &= SWS_COMP_DIRTY; if (ff_sws_pixel_type_is_int(op->convert.to)) op->comps.flags[i] |= SWS_COMP_EXACT; } @@ -487,6 +494,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) for (int i = 0; i < 4; i++) { SwsCompFlags flags = SWS_COMP_IDENTITY; AVRational min = Q(0), max = Q(0); + bool first = true; for (int j = 0; j < 4; j++) { const AVRational k = op->lin.m[i][j]; AVRational mink = av_mul_q(prev.min[j], k); @@ -499,10 +507,13 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) FFSWAP(AVRational, mink, maxk); min = av_add_q(min, mink); max = av_add_q(max, maxk); + if (!first || av_cmp_q(k, Q(1))) + flags &= SWS_COMP_DIRTY; + first = false; } } if (op->lin.m[i][4].num) { /* nonzero offset */ - flags &= ~SWS_COMP_ZERO; + flags &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY; if (op->lin.m[i][4].den != 1) /* fractional offset */ flags &= ~SWS_COMP_EXACT; min = av_add_q(min, op->lin.m[i][4]); @@ -515,7 +526,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_SCALE: for (int i = 0; i < 4; i++) { - op->comps.flags[i] = prev.flags[i]; + op->comps.flags[i] = prev.flags[i] & SWS_COMP_DIRTY; if (op->scale.factor.den != 1) /* fractional scale */ op->comps.flags[i] &= ~SWS_COMP_EXACT; if (op->scale.factor.num < 0) @@ -831,6 +842,10 @@ static char describe_comp_flags(SwsCompFlags flags) return '0'; else if (flags & SWS_COMP_SWAPPED) return 'z'; + else if (flags & SWS_COMP_CONST) + return '$'; + else if (flags & SWS_COMP_COPY) + return '='; else if (flags & SWS_COMP_EXACT) return '+'; else @@ -1026,7 +1041,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, } - av_log(log, lev, " (X = unused, z = byteswapped, + = exact, 0 = zero)\n"); + av_log(log, lev, " ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero)\n"); } #define DUMMY_SIZE 16 diff --git a/libswscale/ops.h b/libswscale/ops.h index 25ab7b752f..a7d8066b94 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -74,6 +74,8 @@ typedef enum SwsCompFlags { SWS_COMP_EXACT = 1 << 1, /* value is an exact integer */ SWS_COMP_ZERO = 1 << 2, /* known to be a constant zero */ SWS_COMP_SWAPPED = 1 << 3, /* byte order is swapped */ + SWS_COMP_COPY = 1 << 4, /* value is unmodified from the source plane */ + SWS_COMP_CONST = 1 << 5, /* value is a fixed constant */ } SwsCompFlags; typedef struct SwsComps { diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index f5ed4c676d..d62bc51dbe 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -31a9d8a35355bed66e8c64dda5246828 +e9435c106ceeb7a19638e982380f3660 _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
