[PR] swscale/ops: add support for chroma up/downscaling and subsampled formats (PR #23555)
Niklas Haas via ffmpeg-devel <[email protected]>
| Newsgroups | gmane.comp.video.ffmpeg.devel |
|---|---|
| Message-ID | <178213059343.59.13537206110829018557@29965ddac10e> |
PR #23555 opened by Niklas Haas (haasn) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23555 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23555.patch This depends on #23467. I'm not really satisfied with this series yet, and would like to make further changes before merging. However, I wanted to submit the PR already for the purposes of the STF milestone delivery. I would as such appreciate a review/approval that this code satisfies the milestone as-is. Concretely, what I am currently unsatisfied with and would like to improve: - [ ] We currently generate all filters ahead of time, this is very wasteful and makes the uops-macros generation quite slow. It would be much better to defer calculation of the filters until ops list compilation - [ ] Accordingly, I also want the ability to merge subsequent filter passes with identical scaler parameters. This would improve both the quality and performance of e.g. scaling yuv420p, since the chroma plane could be directly scaled from the input resolution to the output resolution I consider the above two to be hard blockers before merging. Long-term, I also want to add: - [ ] The ability to have multiple read ops in a single pass, so that we can merge the luma plane with the chroma vscale path into a single pass, rather than always going via a temporary intermediate buffer - [ ] (Maybe) refactoring SwsOpList into an arbitrary DAG of operations, which can be translated into a series of linear passes during compilation; this would go hand-in-hand with the previous idea. However, that is a separate project which exceeds the scope of this (already bloated) milestone >From 80ca6aa518dd3aaa2f6d2e683647ba235d6a088c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:12:51 +0200 Subject: [PATCH 01/44] 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 -- 2.52.0 >From 6b30a25c5636e8873d4876699189402c8b89ee2c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 16 Jun 2026 11:51:52 +0200 Subject: [PATCH 02/44] swscale/ops: fix stale comment Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libswscale/ops.h b/libswscale/ops.h index a7d8066b94..0e8c91f00d 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -57,7 +57,7 @@ typedef enum SwsOpType { SWS_OP_LINEAR, /* generalized linear affine transform */ SWS_OP_DITHER, /* add dithering noise */ - /* Filtering operations. Always output floating point. */ + /* Filtering operations. */ SWS_OP_FILTER_H, /* horizontal filtering */ SWS_OP_FILTER_V, /* vertical filtering */ -- 2.52.0 >From 8098bb77358f77630e64f546b2dc16fde5ac9fc9 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:13:56 +0200 Subject: [PATCH 03/44] swscale/ops: fix noop check ignoring read/write filters Fixes a few cases where we previously didn't actually scale: gbrpf32le 16x16 -> gbrpf32le 16x32: - (no-op) + [f32 ...X] SWS_OP_READ : 3 elem(s) planar >> 0 + 2 tap bilinear filter (V) + min: {nan nan nan _}, max: {nan nan nan _} + [f32 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 + ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) + translated micro-ops: + f32_read_planar_fv_xyz_f32 + u32_write_planar_xyz Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 3 ++- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 28739276f1..0f52f7a77c 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -752,7 +752,8 @@ bool ff_sws_op_list_is_noop(const SwsOpList *ops) read->type != write->type || read->rw.mode != write->rw.mode || read->rw.elems != write->rw.elems || - read->rw.frac != write->rw.frac) + read->rw.frac != write->rw.frac || + read->rw.filter.op || write->rw.filter.op) return false; /** diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index d62bc51dbe..89fd98ade9 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -e9435c106ceeb7a19638e982380f3660 +6e80d07a48c056bfbef85a25e4679693 -- 2.52.0 >From 44333a12228aa35b0fb9ca621d31cd3b72fcbb0c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Tue, 10 Feb 2026 22:32:09 +0100 Subject: [PATCH 04/44] swscale/graph: add a function to allow reusing output buffers Used for plane splitting, among other things. (e.g. plane passthrough) Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/graph.c | 16 ++++++++++++++++ libswscale/graph.h | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/libswscale/graph.c b/libswscale/graph.c index a99cd7cadf..cabb61b5fd 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -229,6 +229,22 @@ fail: return ret; } +void ff_sws_pass_link_output(SwsPass *dst, const SwsPass *src) +{ + if (!dst || !src || dst == src) + return; + + av_assert0(dst->format == src->format); + SwsPassBuffer *keep = src->output, *drop = dst->output; + + av_assert1(keep->width == drop->width); + av_assert1(keep->height == drop->height); + keep->width_align = FFMAX(keep->width_align, drop->width_align); + keep->width_pad = FFMAX(keep->width_pad, drop->width_pad); + + av_refstruct_replace(&dst->output, src->output); +} + static void frame_shift(const SwsFrame *f, const int y, uint8_t *data[4]) { for (int i = 0; i < 4; i++) { diff --git a/libswscale/graph.h b/libswscale/graph.h index cb06f480cc..e467a43e45 100644 --- a/libswscale/graph.h +++ b/libswscale/graph.h @@ -199,6 +199,16 @@ int ff_sws_graph_add_pass(SwsGraph *graph, enum AVPixelFormat fmt, void *priv, void (*free)(void *priv), SwsPass **out_pass); +/** + * Link the output buffers to a different pass, rather than allocating + * new image buffers. This allows reusing the same buffer for multiple passes, + * e.g. in the case of in-place passes or partial passes that modify different + * planes. + * + * Any existing buffer on `dst` will be ignored/unref'd. + **/ +void ff_sws_pass_link_output(SwsPass *dst, const SwsPass *src); + /** * Remove all passes added since the given index. */ -- 2.52.0 >From d8adc1d56d127dc584fe7f7f15acb88293e8719e Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:17:27 +0200 Subject: [PATCH 05/44] swscale/ops_dispatch: don't assume first operation is a read Makes ff_sws_compile_pass() more robust; will be needed for plane splitting. Besides, it's perfectly valid to have an operation list that starts with e.g. SWS_OP_CLEAR. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 32 ++++++++++++++++++-------------- libswscale/ops_dispatch.h | 3 +-- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index ac14a5eaf3..c8f4dcbbcc 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -496,7 +496,7 @@ static int rw_pixel_bits(const SwsOp *op) static void align_pass(SwsPass *pass, int block_size, const int *over_rw, int pixel_bits) { - if (!pass) + if (!pass || pixel_bits <= 0) return; /* Add at least as many pixels as needed to cover the padding requirement */ @@ -541,18 +541,23 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(src->format); const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(dst->format); - const SwsOp *read = ff_sws_op_list_input(ops); const SwsOp *write = ff_sws_op_list_output(ops); - p->palette_idx = read->rw.mode == SWS_RW_PALETTE ? ops->plane_src[1] : -1; - p->planes_in = rw_data_planes(read); - p->planes_out = rw_data_planes(write); - p->pixel_bits_in = rw_pixel_bits(read); + p->planes_out = rw_data_planes(write); p->pixel_bits_out = rw_pixel_bits(write); + p->palette_idx = -1; p->exec_base = (SwsOpExec) { .width = dst->width, .height = dst->height, }; + const SwsOp *read = ff_sws_op_list_input(ops); + if (read) { + p->planes_in = rw_data_planes(read); + p->pixel_bits_in = rw_pixel_bits(read); + if (read->rw.mode == SWS_RW_PALETTE) + p->palette_idx = ops->plane_src[1]; + } + const int64_t block_bits_in = (int64_t) comp->block_size * p->pixel_bits_in; const int64_t block_bits_out = (int64_t) comp->block_size * p->pixel_bits_out; if (block_bits_in & 0x7 || block_bits_out & 0x7) { @@ -586,8 +591,8 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, p->idx_out[i] = idx; } - const SwsFilterWeights *filter = read->rw.filter.kernel; - if (read->rw.filter.op == SWS_OP_FILTER_V) { + const SwsFilterWeights *filter = read ? read->rw.filter.kernel : NULL; + if (read && read->rw.filter.op == SWS_OP_FILTER_V) { p->offsets_y = av_refstruct_ref(filter->offsets); /* Compute relative pointer bumps for each output line */ @@ -605,7 +610,7 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, } bump[filter->dst_size - 1] = 0; p->exec_base.in_bump_y = bump; - } else if (read->rw.filter.op == SWS_OP_FILTER_H) { + } else if (read && read->rw.filter.op == SWS_OP_FILTER_H) { /* Compute pixel offset map for each output line */ const int pixels = FFALIGN(filter->dst_size, p->comp.block_size); int32_t *offset = av_malloc_array(pixels, sizeof(*offset)); @@ -640,8 +645,9 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, return ret; (*output)->backend = comp->backend->flags; - align_pass(input, comp->block_size, comp->over_read, p->pixel_bits_in); align_pass(*output, comp->block_size, comp->over_write, p->pixel_bits_out); + if (read) + align_pass(input, comp->block_size, comp->over_read, p->pixel_bits_in); return 0; fail: @@ -665,11 +671,9 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, goto out; } - const SwsOp *read = ff_sws_op_list_input(ops); const SwsOp *write = ff_sws_op_list_output(ops); - if (!read || !write) { - av_log(ctx, AV_LOG_ERROR, "First and last operations must be a read " - "and write, respectively.\n"); + if (!write) { + av_log(ctx, AV_LOG_ERROR, "Last operation must be SWS_OP_WRITE.\n"); ret = AVERROR(EINVAL); goto out; } diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index 4097b8e947..b08d030d73 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -163,8 +163,7 @@ int ff_sws_ops_compile(SwsContext *ctx, const SwsOpBackend *backend, const SwsOpList *ops, SwsCompiledOp *out); /** - * Resolves an operation list to a graph pass. The first and last operations - * must be a read/write respectively. + * Resolves an operation list to a graph pass. The last op must be a write. * * @param backend Force the use of a specific backend (Optional) * @param ops Operations to compile. Ownership passes to this function, and -- 2.52.0 >From 7c5df46ae399eeadd695c6764e1aa8390e2a8c4f Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 13:06:24 +0200 Subject: [PATCH 06/44] swscale/ops_dispatch: move no-op check after optimization pass Otherwise, this will false negative if the redundant operations haven't been optimized away yet, resulting in unnecessary memcpy operations. Fixes: a5341560839ad9d5432978e0a760d6771142c712 Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index c8f4dcbbcc..5dcbcaaaac 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -664,13 +664,6 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, SwsOpList *ops = *pops; int ret = 0; - /* Check if the whole operation graph is an end-to-end no-op */ - if (ff_sws_op_list_is_noop(ops)) { - if (output) - *output = input; - goto out; - } - const SwsOp *write = ff_sws_op_list_output(ops); if (!write) { av_log(ctx, AV_LOG_ERROR, "Last operation must be SWS_OP_WRITE.\n"); @@ -686,6 +679,13 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, ff_sws_op_list_print(ctx, AV_LOG_DEBUG, AV_LOG_TRACE, ops); } + /* Check if the whole operation graph is an end-to-end no-op */ + if (ff_sws_op_list_is_noop(ops)) { + if (output) + *output = input; + goto out; + } + ret = compile(graph, backend, ops, input, output); if (ret != AVERROR(ENOTSUP)) goto out; -- 2.52.0 >From 63b0208183af7a0bdf7e94bf5bdacf20eec444f8 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 12:09:20 +0200 Subject: [PATCH 07/44] swscale/ops_dispatch: move compile flags from ops.h Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.h | 5 ----- libswscale/ops_dispatch.h | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/libswscale/ops.h b/libswscale/ops.h index 0e8c91f00d..38e9fbcc26 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -364,11 +364,6 @@ void ff_sws_op_list_update_comps(SwsOpList *ops); */ int ff_sws_op_list_optimize(SwsOpList *ops); -enum SwsOpCompileFlags { - /* Automatically optimize the operations when compiling */ - SWS_OP_FLAG_OPTIMIZE = 1 << 0, -}; - /** * Helper function to enumerate over all possible (optimized) operation lists, * under the current set of options in `ctx`, and run the given callback on diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index b08d030d73..a558486e66 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -162,6 +162,11 @@ extern const SwsOpBackend *const ff_sws_op_backends[]; int ff_sws_ops_compile(SwsContext *ctx, const SwsOpBackend *backend, const SwsOpList *ops, SwsCompiledOp *out); +enum SwsOpCompileFlags { + /* Automatically optimize the operations when compiling */ + SWS_OP_FLAG_OPTIMIZE = 1 << 0, +}; + /** * Resolves an operation list to a graph pass. The last op must be a write. * -- 2.52.0 >From 9eb50e3106ef0249573763dd5b209f7242793b13 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 13:06:38 +0200 Subject: [PATCH 08/44] swscale/ops_dispatch: add SWS_OP_FLAG_DRY_RUN Avoids us having to write awkward code like `output ? &pass : NULL`. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 9 +++++---- libswscale/ops_dispatch.h | 8 +++++--- libswscale/tests/sws_ops.c | 3 ++- libswscale/uops.c | 3 ++- 4 files changed, 14 insertions(+), 9 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index 5dcbcaaaac..4af53676f0 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -512,7 +512,8 @@ static void align_pass(SwsPass *pass, int block_size, const int *over_rw, } static int compile(SwsGraph *graph, const SwsOpBackend *backend, - const SwsOpList *ops, SwsPass *input, SwsPass **output) + const SwsOpList *ops, int flags, SwsPass *input, + SwsPass **output) { SwsContext *ctx = graph->ctx; SwsOpPass *p = av_mallocz(sizeof(*p)); @@ -522,7 +523,7 @@ static int compile(SwsGraph *graph, const SwsOpBackend *backend, int ret = ff_sws_ops_compile(ctx, backend, ops, &p->comp); if (ret < 0) goto fail; - else if (!output) + else if (flags & SWS_OP_FLAG_DRY_RUN) goto fail; /* nothing to do, just return */ const SwsCompiledOp *comp = &p->comp; @@ -686,7 +687,7 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, goto out; } - ret = compile(graph, backend, ops, input, output); + ret = compile(graph, backend, ops, flags, input, output); if (ret != AVERROR(ENOTSUP)) goto out; @@ -705,7 +706,7 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, goto out; } - ret = compile(graph, backend, ops, prev, output ? &prev : NULL); + ret = compile(graph, backend, ops, flags, prev, &prev); if (ret < 0) { ff_sws_op_list_free(&rest); goto out; diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index a558486e66..4581cf93eb 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -165,6 +165,9 @@ int ff_sws_ops_compile(SwsContext *ctx, const SwsOpBackend *backend, enum SwsOpCompileFlags { /* Automatically optimize the operations when compiling */ SWS_OP_FLAG_OPTIMIZE = 1 << 0, + + /* Discard the compiled op lists instead of generating passes */ + SWS_OP_FLAG_DRY_RUN = 1 << 1, }; /** @@ -175,9 +178,8 @@ enum SwsOpCompileFlags { * will be set to NULL, even on failure. * @param flags Set of SwsOpCompileFlags * @param input The input for the compiled passes. (Optional) - * @param output The resulting final output pass will be stored here. If NULL, - * no output passes are created, and any compiled functions are - * instead immediately freed. + * @param output The resulting final output pass will be stored here. + * Optional if using SWS_OP_FLAG_DRY_RUN. */ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, SwsOpList **ops, int flags, SwsPass *input, diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index 5264cd55d1..a247eb3f76 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -93,7 +93,8 @@ static int print_passes(SwsContext *ctx, void *graph, SwsOpList *ops) return AVERROR(ENOMEM); pass_idx = 0; - return ff_sws_compile_pass(graph, &backend_print, ©, 0, NULL, NULL); + const int flags = SWS_OP_FLAG_DRY_RUN; + return ff_sws_compile_pass(graph, &backend_print, ©, flags, NULL, NULL); } static void log_stdout(void *avcl, int level, const char *fmt, va_list vl) { diff --git a/libswscale/uops.c b/libswscale/uops.c index 6045dd4bfd..6873c2f2aa 100644 --- a/libswscale/uops.c +++ b/libswscale/uops.c @@ -952,7 +952,8 @@ static int register_all_uops(SwsContext *ctx, void *graph, SwsOpList *ops) if (!copy) return AVERROR(ENOMEM); - return ff_sws_compile_pass(graph, &backend_uops, ©, 0, NULL, NULL); + const int flags = SWS_OP_FLAG_DRY_RUN; + return ff_sws_compile_pass(graph, &backend_uops, ©, flags, NULL, NULL); } static const SwsFlags flags[] = { -- 2.52.0 >From 8e87ae4373051c4b3c1f3ff10707ebc131bc6752 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 13:11:13 +0200 Subject: [PATCH 09/44] swscale/ops_dispatch: group compilation args into struct This will make it easier to keep passing around these parameters in helper functions in the upcoming refactor. Take the opportunity to also rename the plain `compile` function to `compile_single`. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index 4af53676f0..f17f40ebab 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -511,19 +511,26 @@ static void align_pass(SwsPass *pass, int block_size, const int *over_rw, buf->width_pad = FFMAX(buf->width_pad, pad_max); } -static int compile(SwsGraph *graph, const SwsOpBackend *backend, - const SwsOpList *ops, int flags, SwsPass *input, - SwsPass **output) +/* Unchanging part of parameter list */ +typedef struct CompileArgs { + const SwsOpBackend *backend; + SwsGraph *graph; + int flags; +} CompileArgs; + +static int compile_single(const CompileArgs *args, const SwsOpList *ops, + SwsPass *input, SwsPass **output) { + SwsGraph *graph = args->graph; SwsContext *ctx = graph->ctx; SwsOpPass *p = av_mallocz(sizeof(*p)); if (!p) return AVERROR(ENOMEM); - int ret = ff_sws_ops_compile(ctx, backend, ops, &p->comp); + int ret = ff_sws_ops_compile(ctx, args->backend, ops, &p->comp); if (ret < 0) goto fail; - else if (flags & SWS_OP_FLAG_DRY_RUN) + else if (args->flags & SWS_OP_FLAG_DRY_RUN) goto fail; /* nothing to do, just return */ const SwsCompiledOp *comp = &p->comp; @@ -687,7 +694,13 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, goto out; } - ret = compile(graph, backend, ops, flags, input, output); + const CompileArgs args = { + .backend = backend, + .graph = graph, + .flags = flags, + }; + + ret = compile_single(&args, ops, input, output); if (ret != AVERROR(ENOTSUP)) goto out; @@ -706,7 +719,7 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, goto out; } - ret = compile(graph, backend, ops, flags, prev, &prev); + ret = compile_single(&args, ops, prev, &prev); if (ret < 0) { ff_sws_op_list_free(&rest); goto out; -- 2.52.0 >From 70a73cce10432b5a807369a42d8b1d61c03a2168 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:17:58 +0200 Subject: [PATCH 10/44] swscale/tests/sws_ops: only print actually compiled ops lists We already have the unoptimized reference ops; printing each intermediate stage here is just noise that makes this file harder to scroll through IMO. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/sws_ops.c | 15 +++++++-------- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index a247eb3f76..ce7672040b 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -34,20 +34,19 @@ static int pass_idx; static int print_ops(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out) { - if (pass_idx > 0) - av_log(NULL, AV_LOG_INFO, " Sub-pass #%d:\n", pass_idx); - - ff_sws_op_list_print(NULL, AV_LOG_INFO, AV_LOG_INFO, ops); - SwsUOpList *uops = ff_sws_uop_list_alloc(); if (!uops) return AVERROR(ENOMEM); int ret = ff_sws_ops_translate(ctx, ops, 0, uops); - if (ret == AVERROR(ENOTSUP)) { - av_log(NULL, AV_LOG_INFO, " Retrying with split passes:\n"); + if (ret == AVERROR(ENOTSUP)) goto fail; - } else if (ret < 0) { + + if (pass_idx > 0) + av_log(NULL, AV_LOG_INFO, " Sub-pass #%d:\n", pass_idx); + + ff_sws_op_list_print(NULL, AV_LOG_INFO, AV_LOG_INFO, ops); + if (ret < 0) { av_log(NULL, AV_LOG_ERROR, "Error translating ops: %s\n", av_err2str(ret)); goto fail; } diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index 89fd98ade9..ee37afb811 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -6e80d07a48c056bfbef85a25e4679693 +f14d6b179843b86854e9f80b599b5d86 -- 2.52.0 >From e75fa1e03ccc284b707939b771e660ae4140931c Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 12:23:40 +0200 Subject: [PATCH 11/44] swscale/ops_optimizer: extract subpass splitting logic to helper I will also delete the old name in an upcoming commit. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_internal.h | 17 +++++++++++++++++ libswscale/ops_optimizer.c | 38 +++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index 9d8da6bbb5..01bc921697 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -80,6 +80,23 @@ static inline void ff_sws_pack_op_decode(const SwsOp *op, uint64_t mask[4], int int ff_sws_solve_shuffle(const SwsOpList *ops, uint8_t shuffle[], int size, uint8_t clear_val, int *read_bytes, int *write_bytes); +/** + * Split an op list into two at the given index. The split will be mediated + * by a set of planar read/write operations, plus a swizzle (if necessary) + * to re-order only used components. If a split is performed, both output + * lists will be optimized before returning. + * + * @param ops1 The first part of the split op list. Will be modified in-place. + * @param ops2 The second part of the split op list will be returned here, or + * NULL if no split was necessary. + * @param index The index of the operation to split before. The operation + * itself will be absent from `ops1` and instead moved to the + * start of `ops2`. + * + * Returnse 0 or a negative error code. + */ +int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **ops2, int index); + /** * Eliminate SWS_OP_FILTER_* operations by merging them with prior SWS_OP_READ * operations. This may require splitting the op list into multiple subpasses, diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index e41af380e9..103df0bcd6 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -956,24 +956,16 @@ static void get_input_size(const SwsOpList *ops, SwsFormat *fmt) } } -int ff_sws_op_list_subpass(SwsOpList *ops1, SwsOpList **out_rest) +int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) { - const SwsOp *op; - int ret, idx; - - for (idx = 0; idx < ops1->num_ops; idx++) { - op = &ops1->ops[idx]; - if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) - break; - } - - if (idx == ops1->num_ops) { - *out_rest = NULL; + int ret; + if (index <= 0 || index >= ops1->num_ops) { + *out_ops2 = NULL; return 0; } - av_assert0(idx > 0); - const SwsOp *prev = &ops1->ops[idx - 1]; + const SwsOp *op = &ops1->ops[index]; + const SwsOp *prev = &ops1->ops[index - 1]; SwsOpList *ops2 = ff_sws_op_list_duplicate(ops1); if (!ops2) @@ -1010,8 +1002,8 @@ int ff_sws_op_list_subpass(SwsOpList *ops1, SwsOpList **out_rest) ops2->comps_src.max[i] = prev->comps.max[idx]; } - ff_sws_op_list_remove_at(ops1, idx, ops1->num_ops - idx); - ff_sws_op_list_remove_at(ops2, 0, idx); + ff_sws_op_list_remove_at(ops1, index, ops1->num_ops - index); + ff_sws_op_list_remove_at(ops2, 0, index); op = NULL; /* the above command may invalidate op */ if (swiz_wr.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) { @@ -1058,10 +1050,22 @@ int ff_sws_op_list_subpass(SwsOpList *ops1, SwsOpList **out_rest) if (ret < 0) goto fail; - *out_rest = ops2; + *out_ops2 = ops2; return 0; fail: ff_sws_op_list_free(&ops2); return ret; } + +int ff_sws_op_list_subpass(SwsOpList *ops, SwsOpList **out_rest) +{ + for (int idx = 0; idx < ops->num_ops; idx++) { + const SwsOp *op = &ops->ops[idx]; + if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) + return ff_sws_op_list_split_at(ops, out_rest, idx); + } + + *out_rest = NULL; + return 0; +} -- 2.52.0 >From 2bf2ac1a8721bcc81088856636ad8f4886d908a5 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 13:24:39 +0200 Subject: [PATCH 12/44] swscale/ops_dispatch: substantially refactor subpass compilation Instead of a loop with fixed structure, this function now recursively calls itself as many times as needed to satisfy all criteria. This is absolutely needed for the upcoming refactor which will allow for also splitting apart ops lists as needed to e.g. handle partially subsampled ops lists, which may need a complex sequence of filtering and merge steps to be fully satisfied. This does modify the way in which subpasses are compiled slightly, in that each new subpass first tried again un-split, rather than a single split resulting in all subsequent passes being split as well. This is mostly a benign change, though it might matter one day. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 87 ++++++++++++++++++++++----------------- 1 file changed, 49 insertions(+), 38 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index f17f40ebab..efb00233e6 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -30,6 +30,12 @@ #include "ops_dispatch.h" #include "swscale_internal.h" +#define RET(x) \ + do { \ + if ((ret = (x)) < 0) \ + goto fail; \ + } while (0) + typedef struct SwsOpPass { SwsCompiledOp comp; SwsOpExec exec_base; @@ -663,6 +669,44 @@ fail: return ret; } +/* Takes over ownership of *pops, even on failure */ +static int compile_subpass(const CompileArgs *args, SwsOpList **pops, + SwsPass *input, SwsPass **output) +{ + SwsContext *ctx = args->graph->ctx; + SwsOpList *ops = *pops; + SwsOpList *rest = NULL; + SwsPass *tmp = NULL; + *pops = NULL; + + int ret = compile_single(args, ops, input, output); + if (ret != AVERROR(ENOTSUP)) + goto fail; /* either success or a hard error */ + + /* Find any unresolved filter */ + for (int idx = 1; idx < ops->num_ops - 1; idx++) { + const SwsOp *op = &ops->ops[idx]; + if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) { + RET(ff_sws_op_list_split_at(ops, &rest, idx)); + /* Serial split: feed first pass into second */ + RET(compile_subpass(args, &ops, input, &tmp)); + RET(compile_subpass(args, &rest, tmp, output)); + return 0; + } + } + + /* If we didn't find any more operations to eliminate, then this ops list + * is simply unsupported by any of the available backends */ + av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n"); + ff_sws_op_list_print(ctx, AV_LOG_WARNING, AV_LOG_TRACE, ops); + ret = AVERROR(ENOTSUP); + +fail: + ff_sws_op_list_free(&ops); + ff_sws_op_list_free(&rest); + return ret; +} + int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, SwsOpList **pops, int flags, SwsPass *input, SwsPass **output) @@ -700,48 +744,15 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, .flags = flags, }; - ret = compile_single(&args, ops, input, output); - if (ret != AVERROR(ENOTSUP)) + ret = compile_subpass(&args, &ops, input, output); + if (ret < 0) goto out; - av_log(ctx, AV_LOG_DEBUG, "Retrying with separated filter passes.\n"); - SwsPass *prev = input; - bool first = true; - while (ops) { - SwsOpList *rest; - ret = ff_sws_op_list_subpass(ops, &rest); - if (ret < 0) - goto out; - - if (first && !rest) { - /* No point in compiling an unsplit pass again */ - ret = AVERROR(ENOTSUP); - goto out; - } - - ret = compile_single(&args, ops, prev, &prev); - if (ret < 0) { - ff_sws_op_list_free(&rest); - goto out; - } - - ff_sws_op_list_free(&ops); - first = false; - ops = rest; - } - - if (output) { - /* Return last subpass successfully compiled */ - av_log(ctx, AV_LOG_VERBOSE, "Using %d separate passes.\n", - graph->num_passes - passes_orig); - *output = prev; - } + const int num_passes = graph->num_passes - passes_orig; + if (num_passes > 1) + av_log(ctx, AV_LOG_VERBOSE, "Using %d separate passes.\n", num_passes); out: - if (ret == AVERROR(ENOTSUP)) { - av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n"); - ff_sws_op_list_print(ctx, AV_LOG_WARNING, AV_LOG_TRACE, ops); - } if (ret < 0) ff_sws_graph_rollback(graph, passes_orig); ff_sws_op_list_free(&ops); -- 2.52.0 >From 6641048214fe878e4e68bd22d503d7a6965b2200 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 17 Jun 2026 18:35:51 +0200 Subject: [PATCH 13/44] swscale/ops_dispatch: avoid possible infinite recursion If the filter cannot actually be optimized into the read (for whatever reason), this code would previously loop infinitely. Bail out cleanly instead. The FFSWAP is there to make the error message print the remainder (the one containing unsplittable ops), rather than the noop list. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index efb00233e6..a0a23185a8 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -688,6 +688,13 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, const SwsOp *op = &ops->ops[idx]; if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) { RET(ff_sws_op_list_split_at(ops, &rest, idx)); + if (ff_sws_op_list_is_noop(ops)) { + /* Prevent infinite recursion by avoiding splitting in a way + * that does not meaningfully reduce the number of operations + * performed in the second part. */ + FFSWAP(SwsOpList *, ops, rest); + break; + } /* Serial split: feed first pass into second */ RET(compile_subpass(args, &ops, input, &tmp)); RET(compile_subpass(args, &rest, tmp, output)); -- 2.52.0 >From 28766df820be5ef8ce7c750df97d7cb2efe5b7ab Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 17 Jun 2026 17:29:40 +0200 Subject: [PATCH 14/44] swscale/ops: remove now-unneeded function Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_internal.h | 10 ---------- libswscale/ops_optimizer.c | 12 ------------ 2 files changed, 22 deletions(-) diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index 01bc921697..f1da34a272 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -97,14 +97,4 @@ int ff_sws_solve_shuffle(const SwsOpList *ops, uint8_t shuffle[], int size, */ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **ops2, int index); -/** - * Eliminate SWS_OP_FILTER_* operations by merging them with prior SWS_OP_READ - * operations. This may require splitting the op list into multiple subpasses, - * along filter boundaries. After this function, `ops` will no longer contain - * bare filtering operations. The remainder, if any, is output to `out_rest`. - * - * Returns 0 or a negative error code. - */ -int ff_sws_op_list_subpass(SwsOpList *ops, SwsOpList **out_rest); - #endif /* SWSCALE_OPS_INTERNAL_H */ diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 103df0bcd6..ebb9fa026f 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -1057,15 +1057,3 @@ fail: ff_sws_op_list_free(&ops2); return ret; } - -int ff_sws_op_list_subpass(SwsOpList *ops, SwsOpList **out_rest) -{ - for (int idx = 0; idx < ops->num_ops; idx++) { - const SwsOp *op = &ops->ops[idx]; - if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) - return ff_sws_op_list_split_at(ops, out_rest, idx); - } - - *out_rest = NULL; - return 0; -} -- 2.52.0 >From 248a668f7dbfaadef6891e11deac16a84a4632fc Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:23:39 +0200 Subject: [PATCH 15/44] swscale/uops: add a helper to print a comp mask as a string For debugging/logging purposes exclusively. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/uops.h | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libswscale/uops.h b/libswscale/uops.h index 533d036ca6..0098c46e05 100644 --- a/libswscale/uops.h +++ b/libswscale/uops.h @@ -78,6 +78,19 @@ enum { ((W) ? SWS_COMP(3) : 0)) }; + +#define ff_sws_comp_mask_str(mask) ff_sws_comp_mask_print(mask, (char[5]){0}) +static inline char *ff_sws_comp_mask_print(SwsCompMask mask, char buf[5]) +{ + char *ptr = buf; + for (int c = 0; c < 4; c++) { + if (SWS_COMP_TEST(mask, c)) + *ptr++ = "xyzw"[c]; + } + *ptr = '\0'; + return buf; +} + typedef uint32_t SwsUOpFlags; typedef enum SwsUOpFlagBits { SWS_UOP_FLAG_NONE = 0, -- 2.52.0 >From 6bfc448b45c39bf54bba45d0f4e4f57ba04ad3a2 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:25:55 +0200 Subject: [PATCH 16/44] swscale/uops: simplify uop mask printing slightly We can re-use the helper we just added. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/uops.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/libswscale/uops.c b/libswscale/uops.c index 6873c2f2aa..17796d0869 100644 --- a/libswscale/uops.c +++ b/libswscale/uops.c @@ -136,13 +136,8 @@ void ff_sws_uop_name(const SwsUOp *op, char buf[SWS_UOP_NAME_MAX]) av_bprintf(&bp, "%s_", ff_sws_pixel_type_name(op->type)); av_bprintf(&bp, "%s", uop_names[op->uop].abbr); - if (op->mask) { - av_bprint_chars(&bp, '_', 1); - for (int i = 0; i < 4; i++) { - if (SWS_COMP_TEST(op->mask, i)) - av_bprint_chars(&bp, "xyzw"[i], 1); - } - } + if (op->mask) + av_bprintf(&bp, "_%s", ff_sws_comp_mask_str(op->mask)); const SwsUOpParams *par = &op->par; switch (op->uop) { -- 2.52.0 >From 08ae76d61876c6ad11a1894ee305e97443697699 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 13:27:09 +0200 Subject: [PATCH 17/44] swscale/ops_optimizer: add ff_sws_op_list_split_planes() Can be used to extract a reduced subset of operations affecting only certain output planes, e.g. splitting an op list into a "memcpy" and a "non-memcpy" part, or splitting apart op lists for independent or subsampled planes. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_internal.h | 13 ++++++++ libswscale/ops_optimizer.c | 62 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index f1da34a272..54c79ba67a 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -97,4 +97,17 @@ int ff_sws_solve_shuffle(const SwsOpList *ops, uint8_t shuffle[], int size, */ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **ops2, int index); +/** + * Reduce an op list into a reduced subset that operates only on a given + * subset of planes. No effect if the output is not planar, or if the plane + * mask is empty or equal to all planes. + * + * @param ops1 Updated in-place to contain only the selected planes. + * @param ops2 The removed remainder is returned here, or NULL if no-op. + * @param planes A mask of the plane indices to keep. + * + * Returns 0 or a negative error code. + */ +int ff_sws_op_list_split_planes(SwsOpList *ops1, SwsOpList **ops2, SwsCompMask planes); + #endif /* SWSCALE_OPS_INTERNAL_H */ diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index ebb9fa026f..34d2c9eccc 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -808,6 +808,68 @@ retry: return 0; } +static int select_planes(SwsOpList *ops, SwsCompMask planes) +{ + SwsSwizzleOp swiz = SWS_SWIZZLE(0, 1, 2, 3); + SwsOp *write = &ops->ops[ops->num_ops - 1]; + av_assert0(write->op == SWS_OP_WRITE); + + write->rw.elems = 0; + for (int src = 0; src < 4; src++) { + if (!SWS_COMP_TEST(planes, src)) + continue; /* plane not selected */ + const int dst = write->rw.elems++; + av_assert2(src >= dst); + swiz.in[dst] = src; + FFSWAP(int, ops->plane_dst[dst], ops->plane_dst[src]); + } + + /* Insert swizzle to select desired planes */ + int ret = ff_sws_op_list_insert_at(ops, ops->num_ops - 1, &(SwsOp) { + .op = SWS_OP_SWIZZLE, + .type = write->type, + .swizzle = swiz, + }); + if (ret < 0) + return ret; + + /* The optimizer will take care of the rest */ + return ff_sws_op_list_optimize(ops); +} + +int ff_sws_op_list_split_planes(SwsOpList *ops1, SwsOpList **out_ops2, SwsCompMask planes) +{ + const SwsOp *write = ff_sws_op_list_output(ops1); + if (!write || write->rw.mode != SWS_RW_PLANAR) { + *out_ops2 = NULL; + return 0; + } + + const SwsCompMask full = SWS_COMP_ELEMS(write->rw.elems); + const SwsCompMask mask1 = planes & full; + const SwsCompMask mask2 = full ^ mask1; + if (!mask1 || !mask2) { + /* Nothing to filter */ + *out_ops2 = NULL; + return 0; + } + + SwsOpList *ops2 = ff_sws_op_list_duplicate(ops1); + if (!ops2) + return AVERROR(ENOMEM); + + int ret; + if ((ret = select_planes(ops1, mask1)) < 0 || + (ret = select_planes(ops2, mask2)) < 0) + { + ff_sws_op_list_free(&ops2); + return ret; + } + + *out_ops2 = ops2; + return 0; +} + int ff_sws_solve_shuffle(const SwsOpList *const ops, uint8_t shuffle[], int size, uint8_t clear_val, int *read_bytes, int *write_bytes) -- 2.52.0 >From 6bbc8708f1f29af049044a1f4f9bc9af7b50e101 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 13:43:59 +0200 Subject: [PATCH 18/44] swscale/ops_dispatch: add option to link subpass outputs together Not needed currently but will be used for parallel splits. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index a0a23185a8..e1f9504cde 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -525,7 +525,7 @@ typedef struct CompileArgs { } CompileArgs; static int compile_single(const CompileArgs *args, const SwsOpList *ops, - SwsPass *input, SwsPass **output) + SwsPass *link, SwsPass *input, SwsPass **output) { SwsGraph *graph = args->graph; SwsContext *ctx = graph->ctx; @@ -542,14 +542,17 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, const SwsCompiledOp *comp = &p->comp; const SwsFormat *src = &ops->src; const SwsFormat *dst = &ops->dst; + av_assert0(!link || link->format == dst->format); if (p->comp.opaque) { SwsCompiledOp c = *comp; av_free(p); ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height, input, 0, c.slice_align, c.func_opaque, NULL, c.priv, c.free, output); - if (ret >= 0) + if (ret >= 0) { (*output)->backend = comp->backend->flags; + ff_sws_pass_link_output(*output, link); + } return ret; } @@ -659,6 +662,7 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, return ret; (*output)->backend = comp->backend->flags; + ff_sws_pass_link_output(*output, link); align_pass(*output, comp->block_size, comp->over_write, p->pixel_bits_out); if (read) align_pass(input, comp->block_size, comp->over_read, p->pixel_bits_in); @@ -671,7 +675,7 @@ fail: /* Takes over ownership of *pops, even on failure */ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, - SwsPass *input, SwsPass **output) + SwsPass *link, SwsPass *input, SwsPass **output) { SwsContext *ctx = args->graph->ctx; SwsOpList *ops = *pops; @@ -679,7 +683,7 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, SwsPass *tmp = NULL; *pops = NULL; - int ret = compile_single(args, ops, input, output); + int ret = compile_single(args, ops, link, input, output); if (ret != AVERROR(ENOTSUP)) goto fail; /* either success or a hard error */ @@ -696,8 +700,8 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, break; } /* Serial split: feed first pass into second */ - RET(compile_subpass(args, &ops, input, &tmp)); - RET(compile_subpass(args, &rest, tmp, output)); + RET(compile_subpass(args, &ops, NULL, input, &tmp)); + RET(compile_subpass(args, &rest, link, tmp, output)); return 0; } } @@ -751,7 +755,7 @@ int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, .flags = flags, }; - ret = compile_subpass(&args, &ops, input, output); + ret = compile_subpass(&args, &ops, NULL, input, output); if (ret < 0) goto out; -- 2.52.0 >From 1cb39c2f9a9c17c73e22ee7742c97e625c2ab9e0 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 12 Jun 2026 17:37:39 +0200 Subject: [PATCH 19/44] swscale/ops_dispatch: add option to split const/copied subpasses This already helps performance as-is, but will help performance massively once we add the ability for the memcpy backend to do a refcopy instead of an actual copy. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 32 +++++++++++++++++++++++++++++++- libswscale/ops_dispatch.h | 3 +++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index e1f9504cde..dd166a5f92 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -673,17 +673,47 @@ fail: return ret; } +/* Return a mask of all planes matching any flag in `flags` */ +static SwsCompMask plane_mask_flags(const SwsOp *op, SwsCompFlags flags) +{ + SwsCompMask planes = 0; + for (int c = 0; c < 4; c++) { + if (op->comps.flags[c] & flags) + planes |= SWS_COMP(c); + } + + return planes; +} + /* Takes over ownership of *pops, even on failure */ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, SwsPass *link, SwsPass *input, SwsPass **output) { + int ret; SwsContext *ctx = args->graph->ctx; SwsOpList *ops = *pops; SwsOpList *rest = NULL; SwsPass *tmp = NULL; *pops = NULL; - int ret = compile_single(args, ops, link, input, output); + if (args->flags & SWS_OP_FLAG_SPLIT_MEMCPY) { + /* Split off copied and constant planes into a separate subpass, + * since these are likely to be handled by the memcpy backend */ + av_assert0(ops->num_ops >= 2); + const SwsOp *prev = &ops->ops[ops->num_ops - 2]; + SwsCompMask planes = plane_mask_flags(prev, SWS_COMP_COPY | SWS_COMP_CONST); + RET(ff_sws_op_list_split_planes(ops, &rest, planes)); + if (rest) { + /* Parallel split: share input and link all outputs together */ + av_log(ctx, AV_LOG_DEBUG, "Splitting const/memcpy planes: %s\n", + ff_sws_comp_mask_str(planes)); + RET(compile_subpass(args, &ops, link, input, &tmp)); + RET(compile_subpass(args, &rest, tmp, input, output)); + return 0; + } + } + + ret = compile_single(args, ops, link, input, output); if (ret != AVERROR(ENOTSUP)) goto fail; /* either success or a hard error */ diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index 4581cf93eb..2cc4aa3490 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -168,6 +168,9 @@ enum SwsOpCompileFlags { /* Discard the compiled op lists instead of generating passes */ SWS_OP_FLAG_DRY_RUN = 1 << 1, + + /* Split off copied/cleared planes into separate subpasses */ + SWS_OP_FLAG_SPLIT_MEMCPY = 1 << 2, }; /** -- 2.52.0 >From e83001ebd3928829ba2079cdccecb03c4a443cd1 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 16:27:09 +0200 Subject: [PATCH 20/44] swscale/tests/sws_ops: split passes when printing ops lists This affects a large number of conversions across the board, either: 1. Lifting a constant alpha/chroma clear out from the conversion pass: rgb24 16x16 -> yuva444p 16x16: + [ u8 $XXX] SWS_OP_CLEAR : {255 _ _ _} + [ u8 XXXX] SWS_OP_WRITE : 1 elem(s) planar >> 0, via {3} + ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) + translated micro-ops: + u8_clear_x_1 + u8_write_planar_x + Sub-pass #1: [ u8 +++X] SWS_OP_READ : 3 elem(s) packed >> 0 [ u8 +++X] SWS_OP_CONVERT : u8 -> f32 [f32 ...X] SWS_OP_LINEAR : matrix3+off3 [...] [f32 ...X] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} [f32 +++X] SWS_OP_CONVERT : f32 -> u8 - [ u8 +++$] SWS_OP_CLEAR : {_ _ _ 255} - [ u8 XXXX] SWS_OP_WRITE : 4 elem(s) planar >> 0 + [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) gray 16x16 -> yuv444p 16x16: + [ u8 $$XX] SWS_OP_CLEAR : {128 128 _ _} + [ u8 XXXX] SWS_OP_WRITE : 2 elem(s) planar >> 0, via {2, 1} + ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) + translated micro-ops: + u8_clear_xy_xx + u8_write_planar_xy + Sub-pass #1: [ u8 =XXX] SWS_OP_READ : 1 elem(s) planar >> 0 [ u8 =XXX] SWS_OP_CONVERT : u8 -> f32 [f32 .XXX] SWS_OP_LINEAR : luma [...] [f32 .XXX] SWS_OP_DITHER : 16x16 matrix + {0 -1 -1 -1} [f32 +XXX] SWS_OP_CONVERT : f32 -> u8 - [ u8 +$$X] SWS_OP_CLEAR : {_ 128 128 _} - [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 + [ u8 XXXX] SWS_OP_WRITE : 1 elem(s) planar >> 0 ('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 + u8_write_planar_x or 2. Passing through a plane that was previously unmodified by an ops chain: gbrap 16x16 -> yuva444p 16x16: - [ u8 ====] SWS_OP_READ : 4 elem(s) planar >> 0, via {2, 0, 1, 3} - [ u8 ====] SWS_OP_CONVERT : u8 -> f32 - [f32 ...=] SWS_OP_LINEAR : matrix3+off3 [...] - [f32 ...=] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} - [f32 +++=] SWS_OP_CONVERT : f32 -> u8 - [ u8 XXXX] SWS_OP_WRITE : 4 elem(s) planar >> 0 + [ u8 =XXX] SWS_OP_READ : 1 elem(s) planar >> 0, via {3} + [ u8 XXXX] SWS_OP_WRITE : 1 elem(s) planar >> 0, via {3} ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) translated micro-ops: - u8_read_planar_xyzw - u8_to_f32_xyzw + u8_read_planar_x + u8_write_planar_x + Sub-pass #1: + [ u8 ===X] SWS_OP_READ : 3 elem(s) planar >> 0, via {2, 0, 1} + [ u8 ===X] SWS_OP_CONVERT : u8 -> f32 + [f32 ...X] SWS_OP_LINEAR : matrix3+off3 [...] + [f32 ...X] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} + [f32 +++X] SWS_OP_CONVERT : f32 -> u8 + [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 + ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) + translated micro-ops: + u8_read_planar_xyz + u8_to_f32_xyz f32_linear_xyz_xxx0x_xxx0x_xxx0x f32_dither_xyz_0_3_2_16x16 - f32_to_u8_xyzw - u8_write_planar_xyzw + f32_to_u8_xyz + u8_write_planar_xyz (Op lists are abridged slightly for brevity) Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/sws_ops.c | 2 +- tests/ref/fate/sws-ops-list | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c index ce7672040b..0da59b473f 100644 --- a/libswscale/tests/sws_ops.c +++ b/libswscale/tests/sws_ops.c @@ -92,7 +92,7 @@ static int print_passes(SwsContext *ctx, void *graph, SwsOpList *ops) return AVERROR(ENOMEM); pass_idx = 0; - const int flags = SWS_OP_FLAG_DRY_RUN; + const int flags = SWS_OP_FLAG_DRY_RUN | SWS_OP_FLAG_SPLIT_MEMCPY; return ff_sws_compile_pass(graph, &backend_print, ©, flags, NULL, NULL); } static void log_stdout(void *avcl, int level, const char *fmt, va_list vl) diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index ee37afb811..b5fc179208 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -f14d6b179843b86854e9f80b599b5d86 +133f2f3066e2a853f6e77be084c9cd75 -- 2.52.0 >From 0b0150ed27da16abf351e8f93e89e00334d498cc Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 11:09:22 +0200 Subject: [PATCH 21/44] swscale/uops: split planes when generating ops lists This updates uops_macros.h and the graph.c implementation in lockstep, otherwise we'd have an intermediate commit with a bunch of broken formats. Overall speedup=1.008x faster, min=0.144x max=5.550x The min/max numbers are mostly measurement noise, but the real speedup for affected formats is anywhere from 0.9x to around 2x-3x. It's worth noting that the speedup for the formats which currently regress is because we don't yet refcopy the planes, but I have another series in the works which will take care of this soon. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/graph.c | 3 ++- libswscale/uops.c | 2 +- libswscale/uops_macros.h | 34 ++++++++++++++-------------------- 3 files changed, 17 insertions(+), 22 deletions(-) diff --git a/libswscale/graph.c b/libswscale/graph.c index cabb61b5fd..019f9207fb 100644 --- a/libswscale/graph.c +++ b/libswscale/graph.c @@ -639,7 +639,8 @@ static int add_ops_convert_pass(SwsGraph *graph, const SwsFormat *src, av_log(ctx, AV_LOG_DEBUG, "Unoptimized operation list:\n"); ff_sws_op_list_print(ctx, AV_LOG_DEBUG, AV_LOG_TRACE, ops); - return ff_sws_compile_pass(graph, NULL, &ops, SWS_OP_FLAG_OPTIMIZE, input, output); + const int flags = SWS_OP_FLAG_OPTIMIZE | SWS_OP_FLAG_SPLIT_MEMCPY; + return ff_sws_compile_pass(graph, NULL, &ops, flags, input, output); #else return AVERROR(ENOTSUP); #endif diff --git a/libswscale/uops.c b/libswscale/uops.c index 17796d0869..096621466c 100644 --- a/libswscale/uops.c +++ b/libswscale/uops.c @@ -947,7 +947,7 @@ static int register_all_uops(SwsContext *ctx, void *graph, SwsOpList *ops) if (!copy) return AVERROR(ENOMEM); - const int flags = SWS_OP_FLAG_DRY_RUN; + const int flags = SWS_OP_FLAG_DRY_RUN | SWS_OP_FLAG_SPLIT_MEMCPY; return ff_sws_compile_pass(graph, &backend_uops, ©, flags, NULL, NULL); } diff --git a/libswscale/uops_macros.h b/libswscale/uops_macros.h index 2c88870a66..a382988361 100644 --- a/libswscale/uops_macros.h +++ b/libswscale/uops_macros.h @@ -117,7 +117,6 @@ MACRO(__VA_ARGS__, u8_permute_zyxw , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 2, 1, 0, 3) \ MACRO(__VA_ARGS__, u8_permute_zywx , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 2, 1, 3, 0) \ MACRO(__VA_ARGS__, u8_permute_zwxy , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 2, 3, 0, 1) \ - MACRO(__VA_ARGS__, u8_permute_zwyx , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 2, 3, 1, 0) \ MACRO(__VA_ARGS__, u8_permute_wxyz , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 3, 0, 1, 2) \ MACRO(__VA_ARGS__, u8_permute_wxzy , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 3, 0, 2, 1) \ MACRO(__VA_ARGS__, u8_permute_wyxz , SWS_PIXEL_U8 , SWS_UOP_PERMUTE , 0x0, 3, 1, 0, 2) \ @@ -136,7 +135,6 @@ MACRO(__VA_ARGS__, u8_permute_zyxw , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 1, 0, 3}) \ MACRO(__VA_ARGS__, u8_permute_zywx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 1, 3, 0}) \ MACRO(__VA_ARGS__, u8_permute_zwxy , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 3, 0, 1}) \ - MACRO(__VA_ARGS__, u8_permute_zwyx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 3, 1, 0}) \ MACRO(__VA_ARGS__, u8_permute_wxyz , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 0, 1, 2}) \ MACRO(__VA_ARGS__, u8_permute_wxzy , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 0, 2, 1}) \ MACRO(__VA_ARGS__, u8_permute_wyxz , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 1, 0, 2}) \ @@ -167,7 +165,6 @@ MACRO(__VA_ARGS__, u8_move_yz_xx , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_wz_zx , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 2, 3, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_xyz_yzw , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 3, 0, 1, 2, 0, 0, 0, 1, 2, 3, 0, 0, 0) \ - MACRO(__VA_ARGS__, u8_move_xzy_zyw , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 3, 0, 2, 1, 0, 0, 0, 2, 1, 3, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_yzw_xxx , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 3, 1, 2, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_zwy_xyx , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 3, 2, 3, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_wyz_yzx , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 3, 3, 1, 2, 0, 0, 0, 1, 2, 0, 0, 0, 0) \ @@ -202,7 +199,6 @@ MACRO(__VA_ARGS__, u8_move_yz_xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {1, 2, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_wz_zx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {3, 2, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_xyz_yzw , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {0, 1, 2, 0, 0, 0}, .par.move.src = {1, 2, 3, 0, 0, 0}) \ - MACRO(__VA_ARGS__, u8_move_xzy_zyw , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {0, 2, 1, 0, 0, 0}, .par.move.src = {2, 1, 3, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_yzw_xxx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {1, 2, 3, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_zwy_xyx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {2, 3, 1, 0, 0, 0}, .par.move.src = {0, 1, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_wyz_yzx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {3, 1, 2, 0, 0, 0}, .par.move.src = {1, 2, 0, 0, 0, 0}) \ @@ -316,14 +312,14 @@ MACRO(__VA_ARGS__, u8_clear_xy_xx , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0x3, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u8_clear_xz_xx , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0x5, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u8_clear_yz_xx , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0x6, 0x00000, 0x00000) \ + MACRO(__VA_ARGS__, u8_clear_xyz_1xx , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0x7, 0x00001, 0x00000) \ MACRO(__VA_ARGS__, u8_clear_w_0 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0x8, 0x00000, 0x00008) \ MACRO(__VA_ARGS__, u8_clear_w_1 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0x8, 0x00008, 0x00000) \ MACRO(__VA_ARGS__, u8_clear_xyw_xx0 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xb, 0x00000, 0x00008) \ MACRO(__VA_ARGS__, u8_clear_xyw_xx1 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xb, 0x00008, 0x00000) \ MACRO(__VA_ARGS__, u8_clear_zw_xx , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xc, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u8_clear_xzw_1xx , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xd, 0x00001, 0x00000) \ - MACRO(__VA_ARGS__, u8_clear_xzw_xx1 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xd, 0x00008, 0x00000) \ - MACRO(__VA_ARGS__, u8_clear_yzw_xx1 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xe, 0x00008, 0x00000) + MACRO(__VA_ARGS__, u8_clear_xzw_xx1 , SWS_PIXEL_U8 , SWS_UOP_CLEAR , 0xd, 0x00008, 0x00000) #define SWS_FOR_STRUCT_U8_CLEAR(MACRO, ...) \ MACRO(__VA_ARGS__, u8_clear_x_0 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x1, .par.clear.one = 0x0, .par.clear.zero = 0x1) \ MACRO(__VA_ARGS__, u8_clear_x_1 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x1, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ @@ -331,14 +327,14 @@ MACRO(__VA_ARGS__, u8_clear_xy_xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x3, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u8_clear_xz_xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x5, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u8_clear_yz_xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x6, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ + MACRO(__VA_ARGS__, u8_clear_xyz_1xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x7, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u8_clear_w_0 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x8, .par.clear.one = 0x0, .par.clear.zero = 0x8) \ MACRO(__VA_ARGS__, u8_clear_w_1 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0x8, .par.clear.one = 0x8, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u8_clear_xyw_xx0 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xb, .par.clear.one = 0x0, .par.clear.zero = 0x8) \ MACRO(__VA_ARGS__, u8_clear_xyw_xx1 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xb, .par.clear.one = 0x8, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u8_clear_zw_xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xc, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u8_clear_xzw_1xx , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xd, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ - MACRO(__VA_ARGS__, u8_clear_xzw_xx1 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xd, .par.clear.one = 0x8, .par.clear.zero = 0x0) \ - MACRO(__VA_ARGS__, u8_clear_yzw_xx1 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xe, .par.clear.one = 0x8, .par.clear.zero = 0x0) + MACRO(__VA_ARGS__, u8_clear_xzw_xx1 , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_CLEAR , .mask = 0xd, .par.clear.one = 0x8, .par.clear.zero = 0x0) #define SWS_FOR_U8_LINEAR(MACRO, ...) #define SWS_FOR_STRUCT_U8_LINEAR(MACRO, ...) #define SWS_FOR_U8_LINEAR_FMA(MACRO, ...) @@ -430,7 +426,6 @@ MACRO(__VA_ARGS__, u16_permute_zxyw , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 0, 1, 3) \ MACRO(__VA_ARGS__, u16_permute_zyxw , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 1, 0, 3) \ MACRO(__VA_ARGS__, u16_permute_zywx , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 1, 3, 0) \ - MACRO(__VA_ARGS__, u16_permute_zwyx , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 3, 1, 0) \ MACRO(__VA_ARGS__, u16_permute_wxyz , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 0, 1, 2) \ MACRO(__VA_ARGS__, u16_permute_wxzy , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 0, 2, 1) \ MACRO(__VA_ARGS__, u16_permute_wyxz , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 1, 0, 2) \ @@ -445,7 +440,6 @@ MACRO(__VA_ARGS__, u16_permute_zxyw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 0, 1, 3}) \ MACRO(__VA_ARGS__, u16_permute_zyxw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 1, 0, 3}) \ MACRO(__VA_ARGS__, u16_permute_zywx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 1, 3, 0}) \ - MACRO(__VA_ARGS__, u16_permute_zwyx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 3, 1, 0}) \ MACRO(__VA_ARGS__, u16_permute_wxyz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 0, 1, 2}) \ MACRO(__VA_ARGS__, u16_permute_wxzy , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 0, 2, 1}) \ MACRO(__VA_ARGS__, u16_permute_wyxz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 1, 0, 2}) \ @@ -469,7 +463,6 @@ MACRO(__VA_ARGS__, u16_move_yz_xx , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_wz_zx , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 2, 3, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_xyz_yzw , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 3, 0, 1, 2, 0, 0, 0, 1, 2, 3, 0, 0, 0) \ - MACRO(__VA_ARGS__, u16_move_xzy_zyw , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 3, 0, 2, 1, 0, 0, 0, 2, 1, 3, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_zwy_xyx , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 3, 2, 3, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_wzy_zyx , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 3, 3, 2, 1, 0, 0, 0, 2, 1, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_txy_xyt , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 3, -1, 0, 1, 0, 0, 0, 0, 1, -1, 0, 0, 0) \ @@ -494,7 +487,6 @@ MACRO(__VA_ARGS__, u16_move_yz_xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {1, 2, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_wz_zx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {3, 2, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_xyz_yzw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {0, 1, 2, 0, 0, 0}, .par.move.src = {1, 2, 3, 0, 0, 0}) \ - MACRO(__VA_ARGS__, u16_move_xzy_zyw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {0, 2, 1, 0, 0, 0}, .par.move.src = {2, 1, 3, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_zwy_xyx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {2, 3, 1, 0, 0, 0}, .par.move.src = {0, 1, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_wzy_zyx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {3, 2, 1, 0, 0, 0}, .par.move.src = {2, 1, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_txy_xyt , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 3, .par.move.dst = {-1, 0, 1, 0, 0, 0}, .par.move.src = {0, 1, -1, 0, 0, 0}) \ @@ -630,29 +622,31 @@ MACRO(__VA_ARGS__, u16_rshift_xyz_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x7, .par.shift.amount = 4) \ MACRO(__VA_ARGS__, u16_rshift_xyz_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x7, .par.shift.amount = 6) #define SWS_FOR_U16_CLEAR(MACRO, ...) \ + MACRO(__VA_ARGS__, u16_clear_x_x , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x1, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_x_1 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x1, 0x00001, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_y_1 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x2, 0x00002, 0x00000) \ - MACRO(__VA_ARGS__, u16_clear_yz_xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x6, 0x00000, 0x00000) \ + MACRO(__VA_ARGS__, u16_clear_xy_xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x3, 0x00000, 0x00000) \ + MACRO(__VA_ARGS__, u16_clear_xyz_xxx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x7, 0x00000, 0x00000) \ + MACRO(__VA_ARGS__, u16_clear_xyz_1xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x7, 0x00001, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_w_x , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x8, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_w_0 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x8, 0x00000, 0x00008) \ MACRO(__VA_ARGS__, u16_clear_w_1 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x8, 0x00008, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_zw_xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0xc, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_xzw_xx0 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0xd, 0x00000, 0x00008) \ - MACRO(__VA_ARGS__, u16_clear_xzw_1xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0xd, 0x00001, 0x00000) \ - MACRO(__VA_ARGS__, u16_clear_yzw_xxx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0xe, 0x00000, 0x00000) \ - MACRO(__VA_ARGS__, u16_clear_yzw_xx1 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0xe, 0x00008, 0x00000) + MACRO(__VA_ARGS__, u16_clear_xzw_1xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0xd, 0x00001, 0x00000) #define SWS_FOR_STRUCT_U16_CLEAR(MACRO, ...) \ + MACRO(__VA_ARGS__, u16_clear_x_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x1, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_x_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x1, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_y_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x2, .par.clear.one = 0x2, .par.clear.zero = 0x0) \ - MACRO(__VA_ARGS__, u16_clear_yz_xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x6, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ + MACRO(__VA_ARGS__, u16_clear_xy_xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x3, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ + MACRO(__VA_ARGS__, u16_clear_xyz_xxx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x7, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ + MACRO(__VA_ARGS__, u16_clear_xyz_1xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x7, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_w_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x8, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_w_0 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x8, .par.clear.one = 0x0, .par.clear.zero = 0x8) \ MACRO(__VA_ARGS__, u16_clear_w_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x8, .par.clear.one = 0x8, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_zw_xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0xc, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_xzw_xx0 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0xd, .par.clear.one = 0x0, .par.clear.zero = 0x8) \ - MACRO(__VA_ARGS__, u16_clear_xzw_1xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0xd, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ - MACRO(__VA_ARGS__, u16_clear_yzw_xxx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0xe, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ - MACRO(__VA_ARGS__, u16_clear_yzw_xx1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0xe, .par.clear.one = 0x8, .par.clear.zero = 0x0) + MACRO(__VA_ARGS__, u16_clear_xzw_1xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0xd, .par.clear.one = 0x1, .par.clear.zero = 0x0) #define SWS_FOR_U16_LINEAR(MACRO, ...) #define SWS_FOR_STRUCT_U16_LINEAR(MACRO, ...) #define SWS_FOR_U16_LINEAR_FMA(MACRO, ...) -- 2.52.0 >From a562259e202748d62bf964d183a82525e5f1f8a7 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 16:51:03 +0200 Subject: [PATCH 22/44] swscale/ops: fix merge_comp_flags() for SWS_COMP_SWAPPED This violates the documentation (monoid property). It's a bit arbitrary whether to consider this an OR-type or AND-type flag, since mixing swapped and non-swapped components is almost surely a bug, but keeping it as an OR-type makes sure such cases at least show up in the result. Sponsored-by: Sovereign Tech Fund 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 0f52f7a77c..1bff5d5b2c 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -315,7 +315,7 @@ enum { /* 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_or = SWS_COMP_GARBAGE | SWS_COMP_SWAPPED; const SwsCompFlags flags_and = SWS_COMP_IDENTITY; return ((a & b) & flags_and) | ((a | b) & flags_or); } -- 2.52.0 >From 09d472643e8a087267d806075ddb7839f20da6a5 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 16:53:54 +0200 Subject: [PATCH 23/44] swscale/ops: refactor comp flags propagation slightly Introduce a unified FORWARD() helper macro that can be used for any type of op, whether it is independent per component or more complex. By initializing every op to the same IDENTITY state, we can leverage the monoid property to make this work for naive propagations as well. As an aside, we also properly zero out the unrelated fields when discarding a component (i.e. marking it as GARBAGE). This will make a couple of up-coming refactors a bit easier. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 70 +++++++++++++++++++++++++++--------------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 1bff5d5b2c..0fe248996c 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -365,6 +365,19 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; } + for (int i = 0; i < 4; i++) + op->comps.flags[i] = SWS_COMP_IDENTITY; + + #define FORWARD(I, J, EXPR) do { \ + SwsCompMask flags = prev.flags[J]; \ + op->comps.flags[I] = merge_comp_flags(op->comps.flags[I], (EXPR)); \ + } while (0) + + #define RESET(I) do { \ + op->comps.flags[I] = SWS_COMP_GARBAGE; \ + op->comps.min[I] = op->comps.max[I] = (AVRational) {0}; \ + } while (0) + switch (op->op) { case SWS_OP_READ: /* Active components are taken from the user-provided values, @@ -400,27 +413,27 @@ 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) & SWS_COMP_DIRTY; - op->comps.min[i] = prev.min[i]; - op->comps.max[i] = prev.max[i]; + FORWARD(i, i, (flags ^ SWS_COMP_SWAPPED) & SWS_COMP_DIRTY); + op->comps.min[i] = prev.min[i]; + op->comps.max[i] = prev.max[i]; } break; case SWS_OP_WRITE: for (int i = 0; i < op->rw.elems; i++) av_assert1(!(prev.flags[i] & SWS_COMP_GARBAGE)); for (int i = 0; i < 4; i++) - op->comps.flags[i] = prev.flags[i]; + FORWARD(i, i, flags); break; case SWS_OP_LSHIFT: case SWS_OP_RSHIFT: for (int i = 0; i < 4; i++) - op->comps.flags[i] = prev.flags[i] & SWS_COMP_DIRTY; + FORWARD(i, i, flags & SWS_COMP_DIRTY); break; case SWS_OP_MIN: 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]; + FORWARD(i, i, flags); if (op->clamp.limit[i].den) op->comps.flags[i] &= SWS_COMP_DIRTY; if (!bound[i].den) /* reset undefined bounds to known range */ @@ -430,9 +443,9 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) } case SWS_OP_DITHER: for (int i = 0; i < 4; i++) { - op->comps.flags[i] = prev.flags[i]; - op->comps.min[i] = prev.min[i]; - op->comps.max[i] = prev.max[i]; + FORWARD(i, i, flags); + 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 */ @@ -446,24 +459,21 @@ 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] & SWS_COMP_DIRTY; - op->comps.min[i] = Q(0); - op->comps.max[i] = Q((1ULL << pattern) - 1); + FORWARD(i, 0, flags & SWS_COMP_DIRTY); + op->comps.min[i] = Q(0); + op->comps.max[i] = Q((1ULL << pattern) - 1); } else - op->comps.flags[i] = SWS_COMP_GARBAGE; + RESET(i); } break; - case SWS_OP_PACK: { - SwsCompFlags flags = SWS_COMP_IDENTITY; + case SWS_OP_PACK: for (int i = 0; i < 4; i++) { if (op->pack.pattern[i]) - flags = merge_comp_flags(flags, prev.flags[i]); + FORWARD(0, i, flags & SWS_COMP_DIRTY); if (i > 0) /* clear remaining comps for sanity */ - op->comps.flags[i] = SWS_COMP_GARBAGE; + RESET(i); } - 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)) { @@ -473,17 +483,17 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) if (op->clear.value[i].den == 1) op->comps.flags[i] |= SWS_COMP_EXACT; } else { - op->comps.flags[i] = prev.flags[i]; + FORWARD(i, i, flags); } } break; case SWS_OP_SWIZZLE: for (int i = 0; i < 4; i++) - op->comps.flags[i] = prev.flags[op->swizzle.in[i]]; + FORWARD(i, op->swizzle.in[i], flags); break; case SWS_OP_CONVERT: for (int i = 0; i < 4; i++) { - op->comps.flags[i] = prev.flags[i]; + FORWARD(i, i, flags); 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)) @@ -492,7 +502,6 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; case SWS_OP_LINEAR: 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++) { @@ -500,33 +509,32 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) AVRational mink = av_mul_q(prev.min[j], k); AVRational maxk = av_mul_q(prev.max[j], k); if (k.num) { - flags = merge_comp_flags(flags, prev.flags[j]); + FORWARD(i, j, flags); if (k.den != 1) /* fractional coefficient */ - flags &= ~SWS_COMP_EXACT; + op->comps.flags[i] &= ~SWS_COMP_EXACT; if (k.num < 0) 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; + op->comps.flags[i] &= SWS_COMP_DIRTY; first = false; } } if (op->lin.m[i][4].num) { /* nonzero offset */ - flags &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY; + op->comps.flags[i] &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY; if (op->lin.m[i][4].den != 1) /* fractional offset */ - flags &= ~SWS_COMP_EXACT; + op->comps.flags[i] &= ~SWS_COMP_EXACT; min = av_add_q(min, op->lin.m[i][4]); max = av_add_q(max, op->lin.m[i][4]); } - op->comps.flags[i] = flags; op->comps.min[i] = min; op->comps.max[i] = max; } break; case SWS_OP_SCALE: for (int i = 0; i < 4; i++) { - op->comps.flags[i] = prev.flags[i] & SWS_COMP_DIRTY; + FORWARD(i, i, flags & SWS_COMP_DIRTY); if (op->scale.factor.den != 1) /* fractional scale */ op->comps.flags[i] &= ~SWS_COMP_EXACT; if (op->scale.factor.num < 0) @@ -555,7 +563,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) for (int i = 0; i < 4; i++) { if (!need_out[i]) - op->comps.flags[i] = SWS_COMP_GARBAGE; + RESET(i); } switch (op->op) { -- 2.52.0 >From e1d645814cdef35e26b6796f1b9cbfb7d94ab849 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 17:05:19 +0200 Subject: [PATCH 24/44] swscale/ops: solve for component dependencies Needed to determine reverse dependencies of different input planes, for appropriately splitting op lists involving subsampled planes. Generates benign diffs that just reflect the new addition, e.g.: yuva444p 16x16 -> rgb24 16x16: [ u8 ===X] SWS_OP_READ : 3 elem(s) planar >> 0 min: {0 0 0 _}, max: {255 255 255 _} + inputs: {x y z _}, outputs: {xyz yz xy _} [ u8 ===X] SWS_OP_CONVERT : u8 -> f32 min: {0 0 0 _}, max: {255 255 255 _} + inputs: {x y z _}, outputs: {xyz yz xy _} [f32 ...X] SWS_OP_LINEAR : matrix3+off3 [[85/73 0 1.596027 0 -222.921566] [85/73 -0.391762 -0.812968 0 135.575295] [85/73 2.017232 0 0 -276.835851] [0 0 0 1 0]] min: {-222.921566 -171.630839 -276.835851 _}, max: {480.983073 432.493103 534.476153 _} + inputs: {xz xyz xy _}, outputs: {x y z _} [f32 ...X] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} min: {-222.919612 -171.628886 -276.833898 _}, max: {481.981120 433.491150 535.474200 _} + inputs: {xz xyz xy _}, outputs: {x y z _} [f32 ...X] SWS_OP_MAX : {0 0 0 _} <= x min: {0 0 0 _}, max: {481.981120 433.491150 535.474200 _} + inputs: {xz xyz xy _}, outputs: {x y z _} [f32 ...X] SWS_OP_MIN : x <= {255 255 255 _} min: {0 0 0 _}, max: {255 255 255 _} + inputs: {xz xyz xy _}, outputs: {x y z _} [f32 +++X] SWS_OP_CONVERT : f32 -> u8 min: {0 0 0 _}, max: {255 255 255 _} + inputs: {xz xyz xy _}, outputs: {x y z _} [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) packed >> 0 ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 46 +++++++++++++++++++++++++++++++------ libswscale/ops.h | 3 +++ tests/ref/fate/sws-ops-list | 2 +- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/libswscale/ops.c b/libswscale/ops.c index 0fe248996c..c40dfd45eb 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -326,7 +326,8 @@ static void apply_filter_weights(SwsComps *comps, const SwsComps *prev, 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] & SWS_COMP_DIRTY; + comps->flags[i] = prev->flags[i] & SWS_COMP_DIRTY; + comps->dep_in[i] = prev->dep_in[i]; /* Only point sampling preserves exactness */ if (weights->filter_size != 1) comps->flags[i] &= ~SWS_COMP_EXACT; @@ -365,17 +366,21 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) break; } - for (int i = 0; i < 4; i++) - op->comps.flags[i] = SWS_COMP_IDENTITY; + for (int i = 0; i < 4; i++) { + op->comps.flags[i] = SWS_COMP_IDENTITY; + op->comps.dep_in[i] = SWS_COMP_NONE; + } #define FORWARD(I, J, EXPR) do { \ SwsCompMask flags = prev.flags[J]; \ op->comps.flags[I] = merge_comp_flags(op->comps.flags[I], (EXPR)); \ + op->comps.dep_in[I] |= prev.dep_in[J]; \ } while (0) #define RESET(I) do { \ op->comps.flags[I] = SWS_COMP_GARBAGE; \ op->comps.min[I] = op->comps.max[I] = (AVRational) {0}; \ + op->comps.dep_in[I] = SWS_COMP_NONE; \ } while (0) switch (op->op) { @@ -394,6 +399,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) 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]; + op->comps.dep_in[i] = SWS_COMP(idx); /** * Don't mark packed or fractional reads as a copy, because the @@ -555,13 +561,15 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) prev = op->comps; } - /* Backwards pass, solves for component dependencies */ - bool need_out[4] = { false, false, false, false }; + /* Backwards pass, solves for output component dependencies */ + SwsCompMask need_out[4] = {0}; + for (int n = ops->num_ops - 1; n >= 0; n--) { SwsOp *op = &ops->ops[n]; - bool need_in[4] = { false, false, false, false }; + SwsCompMask need_in[4] = {0}; for (int i = 0; i < 4; i++) { + op->comps.dep_out[i] = need_out[i]; if (!need_out[i]) RESET(i); } @@ -570,7 +578,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_READ: case SWS_OP_WRITE: for (int i = 0; i < op->rw.elems; i++) - need_in[i] = op->op == SWS_OP_WRITE; + need_in[i] = (op->op == SWS_OP_WRITE) ? SWS_COMP(i) : 0; for (int i = op->rw.elems; i < 4; i++) need_in[i] = need_out[i]; break; @@ -861,6 +869,17 @@ static char describe_comp_flags(SwsCompFlags flags) return '.'; } +static void print_deps(AVBPrint *bp, const SwsCompMask *deps) +{ + av_bprintf(bp, "{"); + for (int i = 0; i < 4; i++) { + if (i) + av_bprintf(bp, " "); + av_bprintf(bp, "%s", deps[i] ? ff_sws_comp_mask_str(deps[i]) : "_"); + } + av_bprintf(bp, "}"); +} + static void print_q(AVBPrint *bp, const AVRational q) { if (!q.den) { @@ -1048,6 +1067,19 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, av_log(log, lev_extra, "%s\n", bp.str); } + bool has_deps = false; + for (int i = 0; i < 4; i++) + has_deps |= op->comps.dep_in[i] || op->comps.dep_out[i]; + if (has_deps) { + av_bprint_clear(&bp); + av_bprintf(&bp, " inputs: "); + print_deps(&bp, op->comps.dep_in); + av_bprintf(&bp, ", outputs: "); + print_deps(&bp, op->comps.dep_out); + av_assert0(av_bprint_is_complete(&bp)); + av_log(log, lev_extra, "%s\n", bp.str); + } + } av_log(log, lev, " ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero)\n"); diff --git a/libswscale/ops.h b/libswscale/ops.h index 38e9fbcc26..e0f580f411 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -84,6 +84,9 @@ typedef struct SwsComps { /* Keeps track of the known possible value range, or {0, 0} for undefined * or (unknown range) floating point inputs */ AVRational min[4], max[4]; + + /* Keeps track of input (forward) and output (reverse) dependencies */ + SwsCompMask dep_in[4], dep_out[4]; } SwsComps; typedef enum SwsReadWriteMode { diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index b5fc179208..3fdebe715e 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -133f2f3066e2a853f6e77be084c9cd75 +d01af541d2f2f7200946201e4861785c -- 2.52.0 >From 3151b301a9537ff28d150148544f1797112922f3 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 17:17:27 +0200 Subject: [PATCH 25/44] swscale/ops: keep track of per-component resolution for each op Adds metadata for the conceptual plane size for each op. This is needed for keeping track of subsampled planes, which may need to be split into subpasses for each uniquely sized component. It's important to note that this metadata will not actually survive the ops compilation/translation stage; at this point all op lists should be fully resolved into distinct passes that operate at a single resolution only. rgb24 16x16 -> rgb24 16x32: [ u8 +++X] SWS_OP_READ : 3 elem(s) packed >> 0 min: {0 0 0 _}, max: {255 255 255 _} inputs: {x y z _}, outputs: {x y z _} + size: {16x16 16x16 16x16 _} [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) planar >> 0 ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) translated micro-ops: u8_read_packed_xyz u8_write_planar_xyz Sub-pass #1: [ u8 ...X] SWS_OP_READ : 3 elem(s) planar >> 0 + 2 tap bilinear filter (V) min: {0 0 0 _}, max: {255 255 255 _} inputs: {x y z _}, outputs: {x y z _} + size: {16x32 16x32 16x32 _} [f32 ...X] SWS_OP_DITHER : 16x16 matrix + {0 3 2 -1} min: {1/512 1/512 1/512 _}, max: {255.998047 255.998047 255.998047 _} inputs: {x y z _}, outputs: {x y z _} + size: {16x32 16x32 16x32 _} [f32 ...X] SWS_OP_MIN : x <= {255 255 255 _} min: {1/512 1/512 1/512 _}, max: {255 255 255 _} inputs: {x y z _}, outputs: {x y z _} + size: {16x32 16x32 16x32 _} [f32 +++X] SWS_OP_CONVERT : f32 -> u8 min: {0 0 0 _}, max: {255 255 255 _} inputs: {x y z _}, outputs: {x y z _} + size: {16x32 16x32 16x32 _} [ u8 XXXX] SWS_OP_WRITE : 3 elem(s) packed >> 0 ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero) translated micro-ops: u8_read_planar_fv_xyz_f32 f32_dither_xyz_0_3_2_16x16 f32_min_xyz f32_to_u8_xyz u8_write_packed_xyz Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 7 +++ libswscale/ops.c | 85 +++++++++++++++++++++++++++---------- libswscale/ops.h | 3 ++ libswscale/ops_optimizer.c | 2 + tests/ref/fate/sws-ops-list | 2 +- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index a424bb2cfc..36b2d1082c 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -1037,6 +1037,13 @@ int ff_sws_decode_pixfmt(SwsOpList *ops, const SwsFormat *fmt) for (int i = 0; i < rw_op.elems; i++) { comps->flags[i] = (integer ? SWS_COMP_EXACT : 0) | (swapped ? SWS_COMP_SWAPPED : 0); + + /* Calculate plane dimensions */ + const bool chroma = i == 1 || i == 2; + const int sub_x = chroma ? desc->log2_chroma_w : 0; + const int sub_y = chroma ? desc->log2_chroma_h : 0; + comps->width[i] = AV_CEIL_RSHIFT(fmt->width, sub_x); + comps->height[i] = AV_CEIL_RSHIFT(fmt->height, sub_y); } /* Generate value range information for simple unpacked formats */ diff --git a/libswscale/ops.c b/libswscale/ops.c index c40dfd45eb..0b728ce1f8 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -320,22 +320,39 @@ static SwsCompFlags merge_comp_flags(SwsCompFlags a, SwsCompFlags b) return ((a & b) & flags_and) | ((a | b) & flags_or); } -static void apply_filter_weights(SwsComps *comps, const SwsComps *prev, - const SwsFilterWeights *weights) +static void merge_size(int *size, int ref_size) { + if (!ref_size) + return; + av_assert1(*size == 0 || *size == ref_size); + *size = ref_size; +} + +static void apply_filter(SwsComps *comps, int idx, SwsOpType filter, + const SwsFilterWeights *weights) +{ + if (!filter) + return; + + comps->flags[idx] &= SWS_COMP_DIRTY; + + /* Only point sampling preserves exactness */ + if (weights->filter_size != 1) + comps->flags[idx] &= ~SWS_COMP_EXACT; + + /* Update min/max assuming extremes */ 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] & SWS_COMP_DIRTY; - comps->dep_in[i] = prev->dep_in[i]; - /* Only point sampling preserves exactness */ - if (weights->filter_size != 1) - comps->flags[i] &= ~SWS_COMP_EXACT; - /* Update min/max assuming extremes */ - comps->min[i] = av_add_q(av_mul_q(prev->min[i], posw), - av_mul_q(prev->max[i], negw)); - comps->max[i] = av_add_q(av_mul_q(prev->min[i], negw), - av_mul_q(prev->max[i], posw)); + const AVRational prev_min = comps->min[idx]; + const AVRational prev_max = comps->max[idx]; + comps->min[idx] = av_add_q(av_mul_q(prev_min, posw), av_mul_q(prev_max, negw)); + comps->max[idx] = av_add_q(av_mul_q(prev_min, negw), av_mul_q(prev_max, posw)); + + /* Update plane dimensions */ + int *sizes = filter == SWS_OP_FILTER_H ? comps->width : comps->height; + if (sizes[idx]) { + av_assert1(sizes[idx] == weights->src_size); + sizes[idx] = weights->dst_size; } } @@ -355,8 +372,6 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_DITHER: case SWS_OP_SWAP_BYTES: case SWS_OP_UNPACK: - case SWS_OP_FILTER_H: - case SWS_OP_FILTER_V: break; /* special cases, handled below */ default: memcpy(op->comps.min, prev.min, sizeof(prev.min)); @@ -369,16 +384,20 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) for (int i = 0; i < 4; i++) { op->comps.flags[i] = SWS_COMP_IDENTITY; op->comps.dep_in[i] = SWS_COMP_NONE; + op->comps.width[i] = op->comps.height[i] = 0; } #define FORWARD(I, J, EXPR) do { \ SwsCompMask flags = prev.flags[J]; \ op->comps.flags[I] = merge_comp_flags(op->comps.flags[I], (EXPR)); \ op->comps.dep_in[I] |= prev.dep_in[J]; \ + merge_size(&op->comps.width[I], prev.width[J]); \ + merge_size(&op->comps.height[I], prev.height[J]); \ } while (0) #define RESET(I) do { \ op->comps.flags[I] = SWS_COMP_GARBAGE; \ + op->comps.width[I] = op->comps.height[I] = 0; \ op->comps.min[I] = op->comps.max[I] = (AVRational) {0}; \ op->comps.dep_in[I] = SWS_COMP_NONE; \ } while (0) @@ -400,6 +419,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) op->comps.min[i] = ops->comps_src.min[idx]; op->comps.max[i] = ops->comps_src.max[idx]; op->comps.dep_in[i] = SWS_COMP(idx); + op->comps.width[i] = ops->comps_src.width[idx]; + op->comps.height[i] = ops->comps_src.height[idx]; /** * Don't mark packed or fractional reads as a copy, because the @@ -410,11 +431,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) */ if (op->rw.mode == SWS_RW_PLANAR && !op->rw.frac) op->comps.flags[i] |= SWS_COMP_COPY; - } - if (op->rw.filter.op) { - const SwsComps prev = op->comps; - apply_filter_weights(&op->comps, &prev, op->rw.filter.kernel); + apply_filter(&op->comps, i, op->rw.filter.op, op->rw.filter.kernel); } break; case SWS_OP_SWAP_BYTES: @@ -488,6 +506,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) op->comps.flags[i] |= SWS_COMP_ZERO; if (op->clear.value[i].den == 1) op->comps.flags[i] |= SWS_COMP_EXACT; + op->comps.width[i] = 0; + op->comps.height[i] = 0; } else { FORWARD(i, i, flags); } @@ -548,10 +568,12 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) } break; case SWS_OP_FILTER_H: - case SWS_OP_FILTER_V: { - apply_filter_weights(&op->comps, &prev, op->filter.kernel); + case SWS_OP_FILTER_V: + for (int i = 0; i < 4; i++) { + FORWARD(i, i, flags); + apply_filter(&op->comps, i, op->op, op->filter.kernel); + } break; - } case SWS_OP_INVALID: case SWS_OP_TYPE_NB: @@ -1080,6 +1102,25 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra, av_log(log, lev_extra, "%s\n", bp.str); } + bool has_size = false; + for (int j = 0; j < 4; j++) + has_size |= op->comps.width[j] || op->comps.height[j]; + if (has_size) { + av_bprint_clear(&bp); + av_bprintf(&bp, " size: {"); + for (int j = 0; j < 4; j++) { + if (j) + av_bprintf(&bp, " "); + if (SWS_COMP_TEST(mask, j)) + av_bprintf(&bp, "%dx%d", op->comps.width[j], op->comps.height[j]); + else + av_bprintf(&bp, "_"); + } + av_bprintf(&bp, "}"); + av_assert0(av_bprint_is_complete(&bp)); + av_log(log, lev_extra, "%s\n", bp.str); + } + } av_log(log, lev, " ('X' unused, 'z' byteswapped, '=' copied, '$' const, '+' integer, '0' zero)\n"); diff --git a/libswscale/ops.h b/libswscale/ops.h index e0f580f411..05ff758a0a 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -87,6 +87,9 @@ typedef struct SwsComps { /* Keeps track of input (forward) and output (reverse) dependencies */ SwsCompMask dep_in[4], dep_out[4]; + + /* Current resolution of each component, or 0 if unknown/indeterminate */ + int width[4], height[4]; } SwsComps; typedef enum SwsReadWriteMode { diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 34d2c9eccc..1623a5045f 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -1062,6 +1062,8 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) ops2->comps_src.flags[i] = prev->comps.flags[idx]; ops2->comps_src.min[i] = prev->comps.min[idx]; ops2->comps_src.max[i] = prev->comps.max[idx]; + ops2->comps_src.width[i] = prev->comps.width[idx]; + ops2->comps_src.height[i] = prev->comps.height[idx]; } ff_sws_op_list_remove_at(ops1, index, ops1->num_ops - index); diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list index 3fdebe715e..822c3d243e 100644 --- a/tests/ref/fate/sws-ops-list +++ b/tests/ref/fate/sws-ops-list @@ -1 +1 @@ -d01af541d2f2f7200946201e4861785c +46c93abcd55c22a11782708cbdc1ba21 -- 2.52.0 >From bc1eacb417e12487b58b3cb635967007ea5dee63 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Thu, 18 Jun 2026 16:05:20 +0200 Subject: [PATCH 26/44] swscale/ops_optimizer: use op metadata to determine size More robust than inferring this from the read op. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 41 ++++++++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 1623a5045f..88003dab1f 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -1005,17 +1005,26 @@ static enum AVPixelFormat get_planar_fmt(SwsPixelType type, int nb_planes) return AV_PIX_FMT_NONE; } -static void get_input_size(const SwsOpList *ops, SwsFormat *fmt) +static int get_op_size(const SwsOp *op, int *out_w, int *out_h) { - fmt->width = ops->src.width; - fmt->height = ops->src.height; - - const SwsOp *read = ff_sws_op_list_input(ops); - if (read && read->rw.filter.op == SWS_OP_FILTER_V) { - fmt->height = read->rw.filter.kernel->dst_size; - } else if (read && read->rw.filter.op == SWS_OP_FILTER_H) { - fmt->width = read->rw.filter.kernel->dst_size; + bool first = true; + for (int i = 0; i < 4; i++) { + if (!SWS_OP_NEEDED(op, i)) + continue; + const int comp_w = op->comps.width[i]; + const int comp_h = op->comps.height[i]; + if (!comp_w || !comp_h) + return AVERROR(EINVAL); + if (first) { + *out_w = comp_w; + *out_h = comp_h; + first = false; + } else if (*out_w != comp_w || *out_h != comp_h) { + return AVERROR(EINVAL); + } } + + return 0; } int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) @@ -1029,6 +1038,11 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) const SwsOp *op = &ops1->ops[index]; const SwsOp *prev = &ops1->ops[index - 1]; + int inter_w, inter_h; + ret = get_op_size(prev, &inter_w, &inter_h); + if (ret < 0) + return ret; + SwsOpList *ops2 = ff_sws_op_list_duplicate(ops1); if (!ops2) return AVERROR(ENOMEM); @@ -1052,8 +1066,9 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) /* Determine metadata for the intermediate format */ const SwsPixelType type = op->type; ops2->src.format = get_planar_fmt(type, nb_planes); - ops2->src.desc = av_pix_fmt_desc_get(ops2->src.format); - get_input_size(ops1, &ops2->src); + ops2->src.desc = av_pix_fmt_desc_get(ops2->src.format); + ops2->src.width = inter_w; + ops2->src.height = inter_h; ops1->dst = ops2->src; for (int i = 0; i < nb_planes; i++) { @@ -1062,8 +1077,8 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) ops2->comps_src.flags[i] = prev->comps.flags[idx]; ops2->comps_src.min[i] = prev->comps.min[idx]; ops2->comps_src.max[i] = prev->comps.max[idx]; - ops2->comps_src.width[i] = prev->comps.width[idx]; - ops2->comps_src.height[i] = prev->comps.height[idx]; + ops2->comps_src.width[i] = inter_w; + ops2->comps_src.height[i] = inter_h; } ff_sws_op_list_remove_at(ops1, index, ops1->num_ops - index); -- 2.52.0 >From 1be595a629baed8681965c357d6d9edf99431a5e Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 11:41:54 +0200 Subject: [PATCH 27/44] swscale/ops: add ff_sws_comp_mask_swizzle_inv() For when the swizzle op is *before* the comp mask, rather than after. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 13 +++++++++++++ libswscale/ops.h | 1 + 2 files changed, 14 insertions(+) diff --git a/libswscale/ops.c b/libswscale/ops.c index 0b728ce1f8..c49409db06 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -157,6 +157,19 @@ void ff_sws_comp_mask_swizzle(SwsCompMask *mask, const SwsSwizzleOp *swiz) *mask = res; } +void ff_sws_comp_mask_swizzle_inv(SwsCompMask *mask, const SwsSwizzleOp *swiz) +{ + const SwsCompMask orig = *mask; + SwsCompMask res = 0; + for (int i = 0; i < 4; i++) { + const int src = swiz->in[i]; + if (SWS_COMP_TEST(orig, i)) + res |= SWS_COMP(src); + } + + *mask = res; +} + SwsCompMask ff_sws_comp_mask_needed(const SwsOp *op) { SwsCompMask mask = 0; diff --git a/libswscale/ops.h b/libswscale/ops.h index 05ff758a0a..80cd91592f 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -157,6 +157,7 @@ typedef struct SwsSwizzleOp { #define SWS_SWIZZLE(X,Y,Z,W) ((SwsSwizzleOp) { .in = {X, Y, Z, W} }) void ff_sws_comp_mask_swizzle(SwsCompMask *mask, const SwsSwizzleOp *swiz); +void ff_sws_comp_mask_swizzle_inv(SwsCompMask *mask, const SwsSwizzleOp *swiz); typedef struct SwsShiftOp { uint8_t amount; /* number of bits to shift */ -- 2.52.0 >From 56db56d285dc96fa7a1b60a3e7a36eab39b8be9d Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 17:38:41 +0200 Subject: [PATCH 28/44] swscale/ops: add SwsCompMask to SwsFilterOp To allow scaling only some components. Needed for chroma subsampling. This commit merely adds the metadata itself. Future commits will add the integration into the optimizer etc. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 17 ++++++++++------- libswscale/format.h | 4 +++- libswscale/ops.c | 6 +++++- libswscale/ops.h | 1 + 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index 36b2d1082c..2a71cf7294 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -1696,7 +1696,8 @@ static SwsScaler get_scaler_fallback(SwsContext *ctx) } static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, - SwsOpType filter, int src_size, int dst_size) + SwsCompMask comps, SwsOpType filter, + int src_size, int dst_size) { if (src_size == dst_size) return 0; /* no-op */ @@ -1717,10 +1718,10 @@ static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, int mean = sqrt((int64_t) src_size * dst_size); if (mean == src_size || mean == dst_size) return AVERROR_BUG; /* sanity, prevent infinite loop */ - ret = add_filter(ctx, type, ops, filter, src_size, mean); + ret = add_filter(ctx, type, ops, comps, filter, src_size, mean); if (ret < 0) return ret; - return add_filter(ctx, type, ops, filter, mean, dst_size); + return add_filter(ctx, type, ops, comps, filter, mean, dst_size); } else if (ret < 0) { return ret; } @@ -1730,22 +1731,24 @@ static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, .op = filter, .filter.kernel = kernel, .filter.type = type, + .filter.comps = comps, }); } int ff_sws_add_filters(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, - const SwsFormat *src, const SwsFormat *dst) + SwsCompMask comps, const SwsFormat *src, + const SwsFormat *dst) { /** * Always perform horizontal scaling first, since it's much more likely to * benefit from small integer optimizations; we should maybe flip the order * here if we're downscaling the vertical resolution by a lot, though. */ - int ret = add_filter(ctx, type, ops, SWS_OP_FILTER_H, src->width, dst->width); + int ret = add_filter(ctx, type, ops, comps, SWS_OP_FILTER_H, src->width, dst->width); if (ret < 0) return ret; - return add_filter(ctx, type, ops, SWS_OP_FILTER_V, src->height, dst->height); + return add_filter(ctx, type, ops, comps, SWS_OP_FILTER_V, src->height, dst->height); } int ff_sws_op_list_generate(SwsContext *ctx, const SwsFormat *src, @@ -1770,7 +1773,7 @@ int ff_sws_op_list_generate(SwsContext *ctx, const SwsFormat *src, ret = ff_sws_decode_colors(ctx, type, ops, src, incomplete); if (ret < 0) goto fail; - ret = ff_sws_add_filters(ctx, type, ops, src, dst); + ret = ff_sws_add_filters(ctx, type, ops, SWS_COMP_ALL, src, dst); if (ret < 0) goto fail; ret = ff_sws_encode_colors(ctx, type, ops, src, dst, incomplete); diff --git a/libswscale/format.h b/libswscale/format.h index ea2ab7dc41..3545e1758c 100644 --- a/libswscale/format.h +++ b/libswscale/format.h @@ -27,6 +27,7 @@ #include "libavutil/pixdesc.h" #include "swscale.h" +#include "uops.h" static inline int ff_q_isnan(const AVRational a) { @@ -198,7 +199,8 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, * Returns 0 on success, or a negative error code on failure. */ int ff_sws_add_filters(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, - const SwsFormat *src, const SwsFormat *dst); + SwsCompMask comps, const SwsFormat *src, + const SwsFormat *dst); /** * Generate an SwsOpList defining a conversion from `src` to `dst`. diff --git a/libswscale/ops.c b/libswscale/ops.c index c49409db06..5916995a68 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -584,7 +584,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_FILTER_V: for (int i = 0; i < 4; i++) { FORWARD(i, i, flags); - apply_filter(&op->comps, i, op->op, op->filter.kernel); + if (SWS_COMP_TEST(op->filter.comps, i)) + apply_filter(&op->comps, i, op->op, op->filter.kernel); } break; @@ -1035,6 +1036,9 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op) av_bprintf(bp, "%-20s: %d -> %d %s (%d taps)", name, kernel->src_size, kernel->dst_size, kernel->name, kernel->filter_size); + const SwsCompMask comps = op->filter.comps & mask; + if (comps != mask) /* non-uniform filter */ + av_bprintf(bp, " on %s", ff_sws_comp_mask_str(comps)); break; } case SWS_OP_TYPE_NB: diff --git a/libswscale/ops.h b/libswscale/ops.h index 80cd91592f..14dc2ebb8f 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -232,6 +232,7 @@ uint32_t ff_sws_linear_mask(const SwsLinearOp *c); typedef struct SwsFilterOp { SwsFilterWeights *kernel; /* filter kernel (refstruct) */ SwsPixelType type; /* pixel type to store result as */ + SwsCompMask comps; /* affected components */ } SwsFilterOp; typedef struct SwsOp { -- 2.52.0 >From ef24bd808f6ea5dd77994d657daf702afc4937aa Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 17:40:57 +0200 Subject: [PATCH 29/44] swscale/ops: add ff_sws_op_is_independent() helper Needed to know where to properly split op lists involving subsampling; to maximize the chance of fusing redundant work into the (ideally planar) write. Sponsored-by: Soveriegn Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ libswscale/ops.h | 7 +++++++ 2 files changed, 52 insertions(+) diff --git a/libswscale/ops.c b/libswscale/ops.c index 5916995a68..506d918e03 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -193,6 +193,51 @@ int ff_sws_rw_op_planes(const SwsOp *op) return 0; } +bool ff_sws_op_is_independent(const SwsOp *op) +{ + switch (op->op) { + case SWS_OP_READ: + case SWS_OP_SWAP_BYTES: + case SWS_OP_SWIZZLE: + case SWS_OP_UNPACK: + case SWS_OP_LSHIFT: + case SWS_OP_RSHIFT: + case SWS_OP_CLEAR: + case SWS_OP_CONVERT: + case SWS_OP_MIN: + case SWS_OP_MAX: + case SWS_OP_SCALE: + case SWS_OP_DITHER: + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: + return true; + case SWS_OP_WRITE: + /* SWS_RW_PACKED implies mixing multiple planes together */ + return op->rw.mode != SWS_RW_PACKED; + case SWS_OP_PACK: + return false; + case SWS_OP_LINEAR: + /* Test if any row has more than a single coefficient */ + for (int i = 0; i < 4; i++) { + bool seen = false; + for (int j = 0; j < 4; j++) { + if (op->lin.m[i][j].num) { + if (seen) + return false; + seen = true; + } + } + } + return true; + case SWS_OP_INVALID: + case SWS_OP_TYPE_NB: + break; + } + + av_unreachable("Invalid operation type!"); + return false; +} + /* biased towards `a` */ static AVRational av_min_q(AVRational a, AVRational b) { diff --git a/libswscale/ops.h b/libswscale/ops.h index 14dc2ebb8f..2f0430e17a 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -287,6 +287,13 @@ void ff_sws_op_uninit(SwsOp *op); */ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]); +/** + * Return whether or not an op affects each plane independently; i.e. there + * are no operations that introduce cross-plane interections. Note that e.g. + * swizzles and unpacks are also considered independent by this definition. + **/ +bool ff_sws_op_is_independent(const SwsOp *op); + /** * Helper struct for representing a list of operations. */ -- 2.52.0 >From adfeb620bf247336ff9e366476c6d77b27461ab5 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 17 Jun 2026 19:50:25 +0200 Subject: [PATCH 30/44] swscale/ops_optimizer: enforce filter uniformity when commuting It would be nice if we could also fix this to allow promotion when the linear op doesn't involve cross-plane dependencies, but I'm not sure if that would ever come up in a real world op list. This includes the case of multiple read ops, which might become a thing in the near future. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 88003dab1f..6fc46c491e 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -178,6 +178,12 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next) return false; } +static bool filter_is_uniform(const SwsOp *op) +{ + const SwsCompMask needed = ff_sws_comp_mask_needed(op); + return (op->filter.comps & needed) == needed; +} + /** * Try to commute a filter op with the previous operation. Makes any * adjustments to the operations as needed, but does not perform the actual @@ -190,9 +196,14 @@ static bool op_commute_filter(SwsOp *op, SwsOp *prev) av_assert0(!ff_sws_pixel_type_is_int(op->filter.type)); switch (prev->op) { + case SWS_OP_LINEAR: + if (!filter_is_uniform(op)) + return false; /* might break cross-component operations */ + prev->type = op->filter.type; + op->filter.comps = SWS_COMP_ALL; /* act on all linear inputs */ + return true; case SWS_OP_SWIZZLE: case SWS_OP_SCALE: - case SWS_OP_LINEAR: case SWS_OP_DITHER: prev->type = op->filter.type; return true; @@ -370,8 +381,10 @@ retry: goto retry; } - /* Merge filter with prior conversion */ - if (prev->op == SWS_OP_CONVERT && !prev->convert.expand) { + /* Merge uniform filter with prior conversion */ + if (prev->op == SWS_OP_CONVERT && !prev->convert.expand && + filter_is_uniform(op)) + { int size_from = ff_sws_pixel_type_size(prev->type); int size_to = ff_sws_pixel_type_size(op->type); av_assert1(prev->convert.to == op->type); @@ -748,7 +761,9 @@ retry: case SWS_OP_FILTER_V: /* Merge with prior simple planar read */ if (prev->op == SWS_OP_READ && !prev->rw.filter.op && - prev->rw.mode == SWS_RW_PLANAR && !prev->rw.frac) { + prev->rw.mode == SWS_RW_PLANAR && !prev->rw.frac && + (op->filter.comps & needed) == SWS_COMP_ELEMS(prev->rw.elems)) + { prev->rw.filter.op = op->op; prev->rw.filter.kernel = av_refstruct_ref(op->filter.kernel); prev->rw.filter.type = op->filter.type; -- 2.52.0 >From 133531ddc22b50a7c5c62902f6196492cb08d416 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Wed, 17 Jun 2026 20:12:09 +0200 Subject: [PATCH 31/44] swscale/ops_optimizer: update filter comps when commuting past swizzle Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 6fc46c491e..b0eb05b0ea 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -120,6 +120,7 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next) return true; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: + ff_sws_comp_mask_swizzle_inv(&next->filter.comps, &op->swizzle); op->type = next->filter.type; return true; @@ -203,6 +204,8 @@ static bool op_commute_filter(SwsOp *op, SwsOp *prev) op->filter.comps = SWS_COMP_ALL; /* act on all linear inputs */ return true; case SWS_OP_SWIZZLE: + ff_sws_comp_mask_swizzle_inv(&op->filter.comps, &prev->swizzle); + av_fallthrough; case SWS_OP_SCALE: case SWS_OP_DITHER: prev->type = op->filter.type; -- 2.52.0 >From 1eb743648c7dab07c600fecaa729d47ebafc2947 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 19 Jun 2026 15:49:12 +0200 Subject: [PATCH 32/44] swscale/ops_optimizer: allow re-ordering filter ops Ensure all horizontal filters come before equivalent vertical filters. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index b0eb05b0ea..392bccf125 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -222,9 +222,13 @@ static bool op_commute_filter(SwsOp *op, SwsOp *prev) case SWS_OP_CLEAR: case SWS_OP_MIN: case SWS_OP_MAX: + return false; case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: - return false; + /* Allow commuting horizontal filters before vertical filters; this + * promotes fusion between adjacent similar filter types */ + return op->op == SWS_OP_FILTER_H && prev->op == SWS_OP_FILTER_V && + op->type == prev->type && op->filter.type == prev->filter.type; case SWS_OP_TYPE_NB: break; } -- 2.52.0 >From a316431f65a0ede07eca55873459adb7a4639359 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 19 Jun 2026 16:15:49 +0200 Subject: [PATCH 33/44] swscale/ops_optimizer: eliminate no-op filters May be left-over after plane splitting. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 392bccf125..6f01bcbc5f 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -766,6 +766,13 @@ retry: case SWS_OP_FILTER_H: case SWS_OP_FILTER_V: + /* Eliminate redundant no-op filter */ + if (!(op->filter.comps & needed)) { + av_assert0(op->type == op->filter.type); + ff_sws_op_list_remove_at(ops, n, 1); + goto retry; + } + /* Merge with prior simple planar read */ if (prev->op == SWS_OP_READ && !prev->rw.filter.op && prev->rw.mode == SWS_RW_PLANAR && !prev->rw.frac && -- 2.52.0 >From 26843723254681e32afd00f66b6671e96b685b1a Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 17:41:21 +0200 Subject: [PATCH 34/44] swscale/ops_optimizer: avoid introducing noop passes while splitting The problem with splitting off noop passes is that they distort the ops metadata, including e.g. the plane swizzle mask and pixel format. We need to prevent these noop cases from actually modifying the ops list in question, the simplest way of which is to just prevent them from being created in the first place. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_optimizer.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 6f01bcbc5f..fe6f91e544 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -1056,6 +1056,11 @@ static int get_op_size(const SwsOp *op, int *out_w, int *out_h) return 0; } +static bool rw_is_simple(const SwsReadWriteOp *rw) +{ + return rw->mode == SWS_RW_PLANAR && !rw->frac && !rw->filter.op; +} + int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) { int ret; @@ -1066,6 +1071,16 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) const SwsOp *op = &ops1->ops[index]; const SwsOp *prev = &ops1->ops[index - 1]; + const SwsOp *read = ff_sws_op_list_input(ops1); + const SwsOp *write = ff_sws_op_list_output(ops1); + + if ((op == write && rw_is_simple(&write->rw)) || + (op == read && rw_is_simple(&read->rw))) + { + /* Avoid pointless splitting of already planar read/writes */ + *out_ops2 = NULL; + return 0; + } int inter_w, inter_h; ret = get_op_size(prev, &inter_w, &inter_h); -- 2.52.0 >From 52f24397f7437c928614de987e7c5b4d442128ae Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 13:43:04 +0200 Subject: [PATCH 35/44] swscale/ops_optimizer: tweak op index slightly when splitting This prefers pushing swizzles and clears to the output list. Pushing the clears to the end is just a no-brainer; while pushing swizzles to the second half can save a bit of work for a plane duplication. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_internal.h | 4 ++++ libswscale/ops_optimizer.c | 14 +++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index 54c79ba67a..cf4b1f2473 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -86,6 +86,10 @@ int ff_sws_solve_shuffle(const SwsOpList *ops, uint8_t shuffle[], int size, * to re-order only used components. If a split is performed, both output * lists will be optimized before returning. * + * Note that the exact place of the split may be adjusted slightly to + * better optimize the resulting two lists; for example, SWS_OP_CLEAR is + * always moved to the second list, since it voids any prior work. + * * @param ops1 The first part of the split op list. Will be modified in-place. * @param ops2 The second part of the split op list will be returned here, or * NULL if no split was necessary. diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index fe6f91e544..21da13524a 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -1063,7 +1063,18 @@ static bool rw_is_simple(const SwsReadWriteOp *rw) int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) { - int ret; + /* Push some operations to the output list; in particular, swizzles and + * clears, since they just rearrange/clear data without computation */ + while (index > 0 && index <= ops1->num_ops) { + switch (ops1->ops[index - 1].op) { + case SWS_OP_CLEAR: + case SWS_OP_SWIZZLE: + index--; + continue; + } + break; + } + if (index <= 0 || index >= ops1->num_ops) { *out_ops2 = NULL; return 0; @@ -1073,6 +1084,7 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index) const SwsOp *prev = &ops1->ops[index - 1]; const SwsOp *read = ff_sws_op_list_input(ops1); const SwsOp *write = ff_sws_op_list_output(ops1); + int ret; if ((op == write && rw_is_simple(&write->rw)) || (op == read && rw_is_simple(&read->rw))) -- 2.52.0 >From d41d523627ddea5f00ec323352ff875b7ba9c7a4 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 12:26:18 +0200 Subject: [PATCH 36/44] swscale/ops_dispatch: split apart subsampled planar writes Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 52 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index dd166a5f92..26c84669d1 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -685,6 +685,44 @@ static SwsCompMask plane_mask_flags(const SwsOp *op, SwsCompFlags flags) return planes; } +/* Try to split off a subgroup of planes with a shared unique size. Returns + * true if such a subgroup exists, false if all planes are the same size. */ +static bool plane_split_sizes(const SwsOpList *ops, const SwsOp *rw, + SwsCompMask *out_comps) +{ + const SwsFormat *fmt; + const uint8_t *plane_idx; + if (rw->rw.mode != SWS_RW_PLANAR) + return false; + + if (rw->op == SWS_OP_READ) { + fmt = &ops->src; + plane_idx = ops->plane_src; + } else { + fmt = &ops->dst; + plane_idx = ops->plane_dst; + } + + int width, height; + SwsCompMask planes = 0; + for (int i = 0; i < rw->rw.elems; i++) { + const int idx = plane_idx[i]; + const bool chroma = idx == 1 || idx == 2; + const int sub_x = chroma ? fmt->desc->log2_chroma_w : 0; + const int sub_y = chroma ? fmt->desc->log2_chroma_h : 0; + const int w = AV_CEIL_RSHIFT(fmt->width, sub_x); + const int h = AV_CEIL_RSHIFT(fmt->height, sub_y); + if (!i || (w == width && h == height)) { + planes |= SWS_COMP(i); + width = w; + height = h; + } + } + + *out_comps = planes; + return planes != SWS_COMP_ELEMS(rw->rw.elems); +} + /* Takes over ownership of *pops, even on failure */ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, SwsPass *link, SwsPass *input, SwsPass **output) @@ -694,6 +732,7 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, SwsOpList *ops = *pops; SwsOpList *rest = NULL; SwsPass *tmp = NULL; + SwsCompMask planes; *pops = NULL; if (args->flags & SWS_OP_FLAG_SPLIT_MEMCPY) { @@ -713,6 +752,19 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, } } + const SwsOp *write = ff_sws_op_list_output(ops); + if (plane_split_sizes(ops, write, &planes)) { + /* Packed subsampled formats don't exist yet */ + av_assert0(write->rw.mode == SWS_RW_PLANAR); + RET(ff_sws_op_list_split_planes(ops, &rest, planes)); + av_log(ctx, AV_LOG_DEBUG, "Splitting subsampled output planes %s\n", + ff_sws_comp_mask_str(planes)); + av_assert0(rest); + RET(compile_subpass(args, &ops, link, input, &tmp)); + RET(compile_subpass(args, &rest, tmp, input, output)); + return 0; + } + ret = compile_single(args, ops, link, input, output); if (ret != AVERROR(ENOTSUP)) goto fail; /* either success or a hard error */ -- 2.52.0 >From 4c4832f6f3b6bbaf03da40f2fb5040ff1702a85b Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 22 Jun 2026 10:50:25 +0200 Subject: [PATCH 37/44] swscale/ops_dispatch: add general solver for subsampled ops lists This will always decompose op lists into a sequence of resolvable subpasses (i.e. subpasses with determinate output size), though the generated sequence is currently horrifically inefficient for anything resembling a packed output (or indeed, anything that has a cross-plane dependency between luma and chroma), on account of the luma plane going through a completely redundant memcpy-and-convert-to-f32 pass. This could be improved by adding the ability to have multiple read ops in a single ops list, which would allow directly reading the luma plane after upscaling the chrome plane in a single pass. However, that will require a substantial amount of additional work. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 72 ++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index 26c84669d1..00b70d860a 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -685,6 +685,20 @@ static SwsCompMask plane_mask_flags(const SwsOp *op, SwsCompFlags flags) return planes; } +/* Find all forward dependencies of a given mask of input planes */ +static SwsCompMask op_find_deps(const SwsOp *op, SwsCompMask input) +{ + SwsCompMask comps = 0; + for (int i = 0; i < 4; i++) { + if (!SWS_OP_NEEDED(op, i)) + continue; + if (op->comps.dep_in[i] & input) + comps |= SWS_COMP(i); + } + + return comps; +} + /* Try to split off a subgroup of planes with a shared unique size. Returns * true if such a subgroup exists, false if all planes are the same size. */ static bool plane_split_sizes(const SwsOpList *ops, const SwsOp *rw, @@ -728,18 +742,22 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, SwsPass *link, SwsPass *input, SwsPass **output) { int ret; - SwsContext *ctx = args->graph->ctx; - SwsOpList *ops = *pops; - SwsOpList *rest = NULL; - SwsPass *tmp = NULL; + SwsContext *ctx = args->graph->ctx; + SwsOpList *ops = *pops; + SwsOpList *ops2 = NULL; + SwsOpList *rest = NULL; + SwsPass *tmp = NULL; + SwsPass *tmp2 = NULL; SwsCompMask planes; *pops = NULL; + av_assert0(ops->num_ops >= 2); + const SwsOp *write = ff_sws_op_list_output(ops); + const SwsOp *prev = &write[-1]; + if (args->flags & SWS_OP_FLAG_SPLIT_MEMCPY) { /* Split off copied and constant planes into a separate subpass, * since these are likely to be handled by the memcpy backend */ - av_assert0(ops->num_ops >= 2); - const SwsOp *prev = &ops->ops[ops->num_ops - 2]; SwsCompMask planes = plane_mask_flags(prev, SWS_COMP_COPY | SWS_COMP_CONST); RET(ff_sws_op_list_split_planes(ops, &rest, planes)); if (rest) { @@ -752,7 +770,6 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, } } - const SwsOp *write = ff_sws_op_list_output(ops); if (plane_split_sizes(ops, write, &planes)) { /* Packed subsampled formats don't exist yet */ av_assert0(write->rw.mode == SWS_RW_PLANAR); @@ -765,6 +782,43 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, return 0; } + const SwsOp *read = ff_sws_op_list_input(ops); + if (read && plane_split_sizes(ops, read, &planes)) { + /* If we hit this branch, then the write is uniform but the input is + * not; so find the first non-independent op and split just before it, + * to ensure the non-uniform ops leading up to that point see a planar + * intermediate buffer which can be split along */ + for (int idx = 1; idx < ops->num_ops; idx++) { + const SwsOp *op = &ops->ops[idx]; + if (ff_sws_op_is_independent(op) && op != write) + continue; + /* Perform a three-way Y split, with the scaled/unscaled inputs + * taking separate input path that merge back into a single + * intermediate buffer for the uniform filtered output */ + RET(ff_sws_op_list_split_at(ops, &rest, idx)); + + /* Split along the dependencies of the input plane mask */ + const SwsOp *prev_split = &ops->ops[ops->num_ops - 2]; + planes = op_find_deps(prev_split, planes); + RET(ff_sws_op_list_split_planes(ops, &ops2, planes)); + + if (rest) { + av_log(ctx, AV_LOG_DEBUG, "Splitting three-way at index %d: %s\n", + idx, ff_sws_comp_mask_str(planes)); + av_assert0(!ff_sws_op_list_is_noop(rest)); + RET(compile_subpass(args, &ops, NULL, input, &tmp)); + RET(compile_subpass(args, &ops2, tmp, input, &tmp2)); + RET(compile_subpass(args, &rest, link, tmp2, output)); + } else { + av_log(ctx, AV_LOG_DEBUG, "Splitting disjoint output planes 0x%x\n", planes); + RET(compile_subpass(args, &ops, link, input, &tmp)); + RET(compile_subpass(args, &ops2, tmp, input, output)); + } + + return 0; + } + } + ret = compile_single(args, ops, link, input, output); if (ret != AVERROR(ENOTSUP)) goto fail; /* either success or a hard error */ @@ -774,7 +828,7 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, const SwsOp *op = &ops->ops[idx]; if (op->op == SWS_OP_FILTER_H || op->op == SWS_OP_FILTER_V) { RET(ff_sws_op_list_split_at(ops, &rest, idx)); - if (ff_sws_op_list_is_noop(ops)) { + if (!rest) { /* Prevent infinite recursion by avoiding splitting in a way * that does not meaningfully reduce the number of operations * performed in the second part. */ @@ -782,6 +836,7 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, break; } /* Serial split: feed first pass into second */ + av_assert0(!ff_sws_op_list_is_noop(ops)); RET(compile_subpass(args, &ops, NULL, input, &tmp)); RET(compile_subpass(args, &rest, link, tmp, output)); return 0; @@ -796,6 +851,7 @@ static int compile_subpass(const CompileArgs *args, SwsOpList **pops, fail: ff_sws_op_list_free(&ops); + ff_sws_op_list_free(&ops2); ff_sws_op_list_free(&rest); return ret; } -- 2.52.0 >From aacbbda51349060e81915b62ab21c377f83ece64 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sun, 21 Jun 2026 12:28:45 +0200 Subject: [PATCH 38/44] swscale/ops_dispatch: use plane size as dispatch dimensions Instead of using the image size here. When we split op lists by grouping together subsampled planes, those planes should see a reduced dispatch size. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index 00b70d860a..b2cf464f2c 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -209,10 +209,9 @@ static int op_pass_setup(const SwsFrame *out, const SwsFrame *in, { const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(in->format); const bool float_in = indesc->flags & AV_PIX_FMT_FLAG_FLOAT; - const int width = out->width; - SwsOpPass *p = pass->priv; SwsOpExec *exec = &p->exec_base; + const int width = exec->width; const SwsCompiledOp *comp = &p->comp; /* Set up main loop parameters */ @@ -562,10 +561,6 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, p->planes_out = rw_data_planes(write); p->pixel_bits_out = rw_pixel_bits(write); p->palette_idx = -1; - p->exec_base = (SwsOpExec) { - .width = dst->width, - .height = dst->height, - }; const SwsOp *read = ff_sws_op_list_input(ops); if (read) { @@ -597,6 +592,7 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, p->idx_in[i] = idx; } + SwsOpExec *exec = &p->exec_base; for (int i = 0; i < p->planes_out; i++) { const int idx = ops->plane_dst[i]; const int chroma = idx == 1 || idx == 2; @@ -606,6 +602,17 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, p->exec_base.out_sub_y[i] = sub_y; p->exec_base.block_size_out[i] = block_bits_out >> 3; p->idx_out[i] = idx; + + const int plane_w = AV_CEIL_RSHIFT(dst->width, sub_x); + const int plane_h = AV_CEIL_RSHIFT(dst->height, sub_y); + if (!exec->width || !exec->height) { + /* Set output size from first plane */ + exec->width = plane_w; + exec->height = plane_h; + } else { + av_assert0(exec->width == plane_w); + av_assert0(exec->height == plane_h); + } } const SwsFilterWeights *filter = read ? read->rw.filter.kernel : NULL; @@ -655,8 +662,9 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, p->filter_size_h = filter->filter_size; } + av_assert0(exec->height); ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height, - input, 0, comp->slice_align, op_pass_run, + input, exec->height, comp->slice_align, op_pass_run, op_pass_setup, p, op_pass_free, output); if (ret < 0) return ret; @@ -666,6 +674,9 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, align_pass(*output, comp->block_size, comp->over_write, p->pixel_bits_out); if (read) align_pass(input, comp->block_size, comp->over_read, p->pixel_bits_in); + + av_log(ctx, AV_LOG_DEBUG, "Sub-pass dispatch dimensions = %dx%d\n", + exec->width, exec->height); return 0; fail: -- 2.52.0 >From a46a2ab2e1d66b17fdf61a4b3029ee47aeee14d8 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 22 Jun 2026 11:59:30 +0200 Subject: [PATCH 39/44] swscale/ops_dispatch: precompute input plane pixel size The main motivating factor here is a desire to remove in_sub_x altogether. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index b2cf464f2c..c6e32bfe93 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -54,6 +54,7 @@ typedef struct SwsOpPass { int palette_idx; int *offsets_y; int filter_size_h; + size_t input_bytes[4]; bool memcpy_first; bool memcpy_last; bool memcpy_out; @@ -231,8 +232,7 @@ static int op_pass_setup(const SwsFrame *out, const SwsFrame *in, size_t input_bytes = in->linesize[idx]; if (p->filter_size_h && float_in) { /* Floating point inputs may contain NaN / Infinity in the padding */ - const int plane_w = AV_CEIL_RSHIFT(in->width, exec->in_sub_x[i]); - input_bytes = pixel_bytes(plane_w, p->pixel_bits_in, AV_ROUND_UP); + input_bytes = p->input_bytes[i]; } size_t safe_bytes = safe_bytes_pad(input_bytes, comp->over_read[i]); @@ -590,6 +590,9 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, p->exec_base.in_sub_y[i] = sub_y; p->exec_base.block_size_in[i] = block_bits_in >> 3; p->idx_in[i] = idx; + + const int plane_w = AV_CEIL_RSHIFT(src->width, sub_x); + p->input_bytes[i] = pixel_bytes(plane_w, p->pixel_bits_in, AV_ROUND_UP); } SwsOpExec *exec = &p->exec_base; -- 2.52.0 >From 292e2f905f6b772b63d3c541f943a8b35f413d3f Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 22 Jun 2026 11:32:05 +0200 Subject: [PATCH 40/44] swscale/ops_dispatch: remove SwsOpExec.out_sub_x/y It turns out this is not really necessary, as we can always split op lists involving subsampled planes into separate passes, one pass to compute each output plane. This makes the loop dispatch more natural to implement, and doesn't really have any performance downsides - in the worst case, this slightly slows down something like yuvu -> yuv420p, but not by a whole lot. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 11 ++++------- libswscale/ops_dispatch.h | 14 +++++++++----- libswscale/x86/ops_include.asm | 2 -- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index c6e32bfe93..b7a2f0b041 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -148,15 +148,15 @@ static void op_pass_free(void *ptr) av_free(p); } -static inline void get_row_data(const SwsOpPass *p, const int y_dst, +static inline void get_row_data(const SwsOpPass *p, const int y, const uint8_t *in[4], uint8_t *out[4]) { const SwsOpExec *base = &p->exec_base; - const int y_src = p->offsets_y ? p->offsets_y[y_dst] : y_dst; + const int y_src = p->offsets_y ? p->offsets_y[y] : y; for (int i = 0; i < p->planes_in; i++) in[i] = base->in[i] + (y_src >> base->in_sub_y[i]) * base->in_stride[i]; for (int i = 0; i < p->planes_out; i++) - out[i] = base->out[i] + (y_dst >> base->out_sub_y[i]) * base->out_stride[i]; + out[i] = base->out[i] + y * base->out_stride[i]; } static inline int get_lines_in(const SwsOpPass *p, const int y, const int h, @@ -468,9 +468,8 @@ static void op_pass_run(const SwsFrame *out, const SwsFrame *in, const int y, comp->func(&tail, comp->priv, num_blocks - tail_blocks, y, num_blocks, y + h); for (int i = 0; memcpy_out && i < p->planes_out; i++) { - const int lines = h >> tail.out_sub_y[i]; copy_lines(exec.out[i], exec.out_stride[i], - tail.out[i], tail.out_stride[i], lines, p->tail_size_out); + tail.out[i], tail.out_stride[i], h, p->tail_size_out); } } @@ -601,8 +600,6 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, const int chroma = idx == 1 || idx == 2; const int sub_x = chroma ? outdesc->log2_chroma_w : 0; const int sub_y = chroma ? outdesc->log2_chroma_h : 0; - p->exec_base.out_sub_x[i] = sub_x; - p->exec_base.out_sub_y[i] = sub_y; p->exec_base.block_size_out[i] = block_bits_out >> 3; p->idx_out[i] = idx; diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index 2cc4aa3490..5723972c30 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -52,14 +52,18 @@ typedef struct SwsOpExec { ptrdiff_t out_bump[4]; /* Extra metadata, may or may not be useful */ - int32_t width, height; /* Overall output image dimensions */ + int32_t width, height; /* Current pass output dimensions */ int32_t slice_y, slice_h; /* Start and height of current slice */ int32_t block_size_in[4]; /* Size of a block of pixels in bytes */ int32_t block_size_out[4]; - /* Subsampling factors for each plane */ - uint8_t in_sub_y[4], out_sub_y[4]; - uint8_t in_sub_x[4], out_sub_x[4]; + /** + * Subsampling factors for each input plane. Note that output planes + * are never subsampled within a single pass, since the pass dispatch + * dimensions must match the plane size being operated on. + */ + uint8_t in_sub_y[4]; + uint8_t in_sub_x[4]; /** * Line bump; determines how many additional lines to advance (after @@ -82,7 +86,7 @@ typedef struct SwsOpExec { static_assert(sizeof(SwsOpExec) == 24 * sizeof(void *) + 12 * sizeof(int32_t) + - 16 * sizeof(uint8_t) + + 8 * sizeof(uint8_t) + 2 * sizeof(void *), "SwsOpExec layout mismatch"); diff --git a/libswscale/x86/ops_include.asm b/libswscale/x86/ops_include.asm index 073ed31e57..2d69074ac5 100644 --- a/libswscale/x86/ops_include.asm +++ b/libswscale/x86/ops_include.asm @@ -126,9 +126,7 @@ struc SwsOpExec .block_size_in resd 4 .block_size_out resd 4 .in_sub_y4 resb 4 - .out_sub_y4 resb 4 .in_sub_x4 resb 4 - .out_sub_x4 resb 4 .in_bump_y resq 1 .in_offset_x resq 1 endstruc -- 2.52.0 >From 8b82fa9c6e59dff5904bfc2cf4bd38081faa583a Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 22 Jun 2026 12:00:30 +0200 Subject: [PATCH 41/44] swscale/ops_dispatch: remove SwsOpExec.in_sub_x/y Unlike the previous commit, this one does actually have some potential ramifications later on; since we'd eventually want to be able to fuse op lists involving multiple read ops (e.g. chroma vscale + luma read) into a single op pass. However, this can't be done currently anyways due to a plethora of places that assume each op list has a maximum of 1 read op, so this will require a bit of refactoring regardless. In the meantime, we handle subsampled planes by splitting all op lists into parallel branches for each plane, which resolves the plane size ambiguity without requiring these fields. (In fact, applying in_sub_y again would lead to double application, corrupting data) Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops_dispatch.c | 18 +++++++----------- libswscale/ops_dispatch.h | 9 --------- libswscale/x86/ops_include.asm | 2 -- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c index b7a2f0b041..a1c8b27285 100644 --- a/libswscale/ops_dispatch.c +++ b/libswscale/ops_dispatch.c @@ -148,26 +148,25 @@ static void op_pass_free(void *ptr) av_free(p); } -static inline void get_row_data(const SwsOpPass *p, const int y, +static inline void get_row_data(const SwsOpPass *p, const int y_dst, const uint8_t *in[4], uint8_t *out[4]) { const SwsOpExec *base = &p->exec_base; - const int y_src = p->offsets_y ? p->offsets_y[y] : y; + const int y_src = p->offsets_y ? p->offsets_y[y_dst] : y_dst; for (int i = 0; i < p->planes_in; i++) - in[i] = base->in[i] + (y_src >> base->in_sub_y[i]) * base->in_stride[i]; + in[i] = base->in[i] + y_src * base->in_stride[i]; for (int i = 0; i < p->planes_out; i++) - out[i] = base->out[i] + y * base->out_stride[i]; + out[i] = base->out[i] + y_dst * base->out_stride[i]; } static inline int get_lines_in(const SwsOpPass *p, const int y, const int h, const int plane) { - const SwsOpExec *base = &p->exec_base; if (!p->offsets_y) - return h >> base->in_sub_y[plane]; + return h; - const int y0 = p->offsets_y[y] >> base->in_sub_y[plane]; - const int y1 = p->offsets_y[y + h - 1] >> base->in_sub_y[plane]; + const int y0 = p->offsets_y[y]; + const int y1 = p->offsets_y[y + h - 1]; return y1 - y0 + 1; } @@ -584,9 +583,6 @@ static int compile_single(const CompileArgs *args, const SwsOpList *ops, const int idx = ops->plane_src[i]; const int chroma = idx == 1 || idx == 2; const int sub_x = chroma ? indesc->log2_chroma_w : 0; - const int sub_y = chroma ? indesc->log2_chroma_h : 0; - p->exec_base.in_sub_x[i] = sub_x; - p->exec_base.in_sub_y[i] = sub_y; p->exec_base.block_size_in[i] = block_bits_in >> 3; p->idx_in[i] = idx; diff --git a/libswscale/ops_dispatch.h b/libswscale/ops_dispatch.h index 5723972c30..a9a430c515 100644 --- a/libswscale/ops_dispatch.h +++ b/libswscale/ops_dispatch.h @@ -57,14 +57,6 @@ typedef struct SwsOpExec { int32_t block_size_in[4]; /* Size of a block of pixels in bytes */ int32_t block_size_out[4]; - /** - * Subsampling factors for each input plane. Note that output planes - * are never subsampled within a single pass, since the pass dispatch - * dimensions must match the plane size being operated on. - */ - uint8_t in_sub_y[4]; - uint8_t in_sub_x[4]; - /** * Line bump; determines how many additional lines to advance (after * incrementing normally to the next line), for each filtered output line. @@ -86,7 +78,6 @@ typedef struct SwsOpExec { static_assert(sizeof(SwsOpExec) == 24 * sizeof(void *) + 12 * sizeof(int32_t) + - 8 * sizeof(uint8_t) + 2 * sizeof(void *), "SwsOpExec layout mismatch"); diff --git a/libswscale/x86/ops_include.asm b/libswscale/x86/ops_include.asm index 2d69074ac5..93b82fca9a 100644 --- a/libswscale/x86/ops_include.asm +++ b/libswscale/x86/ops_include.asm @@ -125,8 +125,6 @@ struc SwsOpExec .slice_h resd 1 .block_size_in resd 4 .block_size_out resd 4 - .in_sub_y4 resb 4 - .in_sub_x4 resb 4 .in_bump_y resq 1 .in_offset_x resq 1 endstruc -- 2.52.0 >From d6125822c8d5e89fef4326f0528eb650a607a2bd Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Fri, 19 Jun 2026 15:45:38 +0200 Subject: [PATCH 42/44] swscale/format: insert chroma upscaling/downscaling ops as needed Following the usual philosophy of the ops approach, we always generate an ops list that incluse the fully general path (i.e. always upscaling chroma to the full luma resolation before/after any pixel format conversions), and then rely on the optimizer to eliminate redundant filtering operations where they become unnecessary. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 83 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index 2a71cf7294..bfa950bcf1 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -1420,6 +1420,10 @@ static int fmt_dither(SwsContext *ctx, SwsOpList *ops, return AVERROR(EINVAL); } +static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, + SwsCompMask comps, SwsOpType filter, double offset, + int src_size, int dst_size, double virt_size); + static inline SwsLinearOp linear_mat3(const AVRational m00, const AVRational m01, const AVRational m02, const AVRational m10, const AVRational m11, const AVRational m12, @@ -1477,6 +1481,54 @@ void ff_sws_chroma_pos(const SwsFormat *fmt, bool *incomplete, *out_y_pos = y_pos; } +static int add_scale_chroma(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, + const SwsFormat *fmt, bool upscale, bool *incomplete) +{ + const AVPixFmtDescriptor *desc = fmt->desc; + const SwsCompMask chroma = SWS_COMP(1) | SWS_COMP(2); + const int chroma_w = AV_CEIL_RSHIFT(fmt->width, desc->log2_chroma_w); + const int chroma_h = AV_CEIL_RSHIFT(fmt->height, desc->log2_chroma_h); + const int scale_x = 1 << desc->log2_chroma_w; + const int scale_y = 1 << desc->log2_chroma_h; + double virtual_w, virtual_h, offset_x, offset_y; + int src_w, src_h, dst_w, dst_h, pos_x, pos_y; + ff_sws_chroma_pos(fmt, incomplete, &pos_x, &pos_y); + + /* Translate chroma position in luma grid to normalized sample offsets */ + offset_x = 0.5 - (pos_x + 128) / (256.0 * scale_x); + offset_y = 0.5 - (pos_y + 128) / (256.0 * scale_y); + + if (upscale) { /* Chroma upscaling */ + src_w = chroma_w; + src_h = chroma_h; + dst_w = fmt->width; + dst_h = fmt->height; + virtual_w = chroma_w * scale_x; + virtual_h = chroma_h * scale_y; + } else { /* Chroma downscaling */ + src_w = fmt->width; + src_h = fmt->height; + dst_w = chroma_w; + dst_h = chroma_h; + virtual_w = fmt->width / (double) scale_x; + virtual_h = fmt->height / (double) scale_y; + offset_x *= -scale_x; + offset_y *= -scale_y; + } + + if (desc->log2_chroma_w) { + RET(add_filter(ctx, type, ops, chroma, SWS_OP_FILTER_H, offset_x, + src_w, dst_w, virtual_w)); + } + + if (desc->log2_chroma_h) { + RET(add_filter(ctx, type, ops, chroma, SWS_OP_FILTER_V, offset_y, + src_h, dst_h, virtual_h)); + } + + return 0; +} + int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, const SwsFormat *fmt, bool *incomplete) { @@ -1491,6 +1543,9 @@ int ff_sws_decode_colors(SwsContext *ctx, SwsPixelType type, .convert.to = type, })); + /* Upscale subsampled chroma if needed */ + RET(add_scale_chroma(ctx, type, ops, fmt, true, incomplete)); + /* Decode pixel format into standardized range */ RET(ff_sws_op_list_append(ops, &(SwsOp) { .type = type, @@ -1637,6 +1692,9 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, .lin = fmt_encode_range(dst, incomplete), })); + /* Downscaled subsampled chroma if needed */ + RET(add_scale_chroma(ctx, type, ops, dst, false, incomplete)); + if (!(dst->desc->flags & AV_PIX_FMT_FLAG_FLOAT)) { SwsClampOp range = {0}; @@ -1696,16 +1754,19 @@ static SwsScaler get_scaler_fallback(SwsContext *ctx) } static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, - SwsCompMask comps, SwsOpType filter, - int src_size, int dst_size) + SwsCompMask comps, SwsOpType filter, double offset, + int src_size, int dst_size, double virtual_size) { - if (src_size == dst_size) + + if (src_size == dst_size && !virtual_size && !offset) return 0; /* no-op */ SwsFilterParams params = { .scaler = get_scaler_fallback(ctx), .src_size = src_size, .dst_size = dst_size, + .virtual_size = virtual_size, + .offset = offset, }; for (int i = 0; i < SWS_NUM_SCALER_PARAMS; i++) @@ -1714,14 +1775,18 @@ static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, SwsFilterWeights *kernel; int ret = ff_sws_filter_generate(ctx, ¶ms, &kernel); if (ret == AVERROR(ENOTSUP)) { - /* Filter size exceeds limit; cascade with geometric mean size */ + /* Filter size exceeds limit; cascade with geometric mean size; move + * all offset and virtual_size adjustments to the output stage to + * simplify the math; this is theoretical anyways because chroma + * scaling in practice is not likely to hit the filter size limit */ int mean = sqrt((int64_t) src_size * dst_size); if (mean == src_size || mean == dst_size) return AVERROR_BUG; /* sanity, prevent infinite loop */ - ret = add_filter(ctx, type, ops, comps, filter, src_size, mean); + ret = add_filter(ctx, type, ops, comps, filter, 0.0, src_size, mean, 0.0); if (ret < 0) return ret; - return add_filter(ctx, type, ops, comps, filter, mean, dst_size); + return add_filter(ctx, type, ops, comps, filter, offset, mean, dst_size, + virtual_size); } else if (ret < 0) { return ret; } @@ -1744,11 +1809,13 @@ int ff_sws_add_filters(SwsContext *ctx, SwsPixelType type, SwsOpList *ops, * benefit from small integer optimizations; we should maybe flip the order * here if we're downscaling the vertical resolution by a lot, though. */ - int ret = add_filter(ctx, type, ops, comps, SWS_OP_FILTER_H, src->width, dst->width); + int ret = add_filter(ctx, type, ops, comps, SWS_OP_FILTER_H, 0.0, + src->width, dst->width, 0.0); if (ret < 0) return ret; - return add_filter(ctx, type, ops, comps, SWS_OP_FILTER_V, src->height, dst->height); + return add_filter(ctx, type, ops, comps, SWS_OP_FILTER_V, 0.0, + src->height, dst->height, 0.0); } int ff_sws_op_list_generate(SwsContext *ctx, const SwsFormat *src, -- 2.52.0 >From f906937402464edcbab74157e1031d354e4a287e Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Mon, 22 Jun 2026 13:52:28 +0200 Subject: [PATCH 43/44] swscale/tests/swscale: estimate chroma quantization noise separately This function completely over-estimates the quantization noise of low bit depth RGB signals (e.g. rgb4) because it only looks at luma loss, but at those low bit depths, chroma noise is also a significant source of noise. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/tests/swscale.c | 54 +++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/libswscale/tests/swscale.c b/libswscale/tests/swscale.c index 6de9327ba9..554c14f68d 100644 --- a/libswscale/tests/swscale.c +++ b/libswscale/tests/swscale.c @@ -132,7 +132,8 @@ static void exit_handler(int sig) } /* Estimate luma variance assuming uniform dither noise distribution */ -static float estimate_quantization_noise(enum AVPixelFormat fmt) +static void estimate_quantization_noise(enum AVPixelFormat fmt, float *out_luma, + float *out_chroma) { const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(fmt); float variance = 1.0 / 12; @@ -141,16 +142,29 @@ static float estimate_quantization_noise(enum AVPixelFormat fmt) variance *= (8 - desc->comp[0].depth); } + const float q0 = 1.0 / (1 << desc->comp[0].depth); + const float q1 = 1.0 / (1 << desc->comp[1].depth); + const float q2 = 1.0 / (1 << desc->comp[2].depth); if (desc->flags & AV_PIX_FMT_FLAG_FLOAT) { - return 0.0; + *out_luma = *out_chroma = 0.0; } else if (desc->flags & AV_PIX_FMT_FLAG_RGB) { - const float r = 0.299 / (1 << desc->comp[0].depth); - const float g = 0.587 / (1 << desc->comp[1].depth); - const float b = 0.114 / (1 << desc->comp[2].depth); - return (r * r + g * g + b * b) * variance; + const float yr = 0.299 * q0; + const float yg = 0.587 * q1; + const float yb = 0.114 * q2; + *out_luma = (yr * yr + yg * yg + yb * yb) * variance; + + const float vr = q0 * q0 * variance; + const float vb = q2 * q2 * variance; + const float cb = 0.564 * 0.564 * (*out_luma + (1 - 2 * 0.114) * vb); + const float cr = 0.713 * 0.713 * (*out_luma + (1 - 2 * 0.299) * vr); + *out_chroma = 0.5 * (cb + cr); + } else if (desc->nb_components < 3) { + *out_luma = q0 * q0 * variance; + *out_chroma = 0.0; } else { - const float y = 1.0 / (1 << desc->comp[0].depth); - return y * y * variance; + av_assert0(desc->comp[1].depth == desc->comp[2].depth); + *out_luma = q0 * q0 * variance; + *out_chroma = q1 * q1 * variance; } } @@ -579,12 +593,15 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, /* Estimate the expected amount of loss from bit depth reduction */ const float c1 = 0.01 * 0.01; /* stabilization constant */ const float ref_var = 1.0 / 12.0; /* uniformly distributed signal */ - const float src_var = estimate_quantization_noise(src_fmt); - const float dst_var = estimate_quantization_noise(dst_fmt); - const float out_var = estimate_quantization_noise(ref->format); - const float total_var = src_var + dst_var + out_var; - const float ssim_luma = (2 * ref_var + c1) / (2 * ref_var + total_var + c1); - const float ssim_expected[4] = { ssim_luma, 1, 1, 1 }; /* for simplicity */ + float src_luma, src_chroma, dst_luma, dst_chroma, out_luma, out_chroma; + estimate_quantization_noise(src_fmt, &src_luma, &src_chroma); + estimate_quantization_noise(dst_fmt, &dst_luma, &dst_chroma); + estimate_quantization_noise(ref->format, &out_luma, &out_chroma); + const float total_luma = src_luma + dst_luma + out_luma; + const float total_chroma = src_chroma + dst_chroma + out_chroma; + const float ssim_luma = (2 * ref_var + c1) / (2 * ref_var + total_luma + c1); + const float ssim_chroma = (2 * ref_var + c1) / (2 * ref_var + total_chroma + c1); + const float ssim_expected[4] = { ssim_luma, ssim_chroma, ssim_chroma, 1 }; const float expected_loss = get_loss(ssim_expected); struct test_results r = { 0 }; @@ -636,10 +653,15 @@ static int run_test(enum AVPixelFormat src_fmt, enum AVPixelFormat dst_fmt, * it an unfair advantage when compared against a bit exact reference. * Work around this by ensuring that the resulting SSIM score is not * higher than it theoretically "should" be. */ - if (src_var > dst_var) { - const float src_loss = (2 * ref_var + c1) / (2 * ref_var + src_var + c1); + if (src_luma > dst_luma) { + const float src_loss = (2 * ref_var + c1) / (2 * ref_var + src_luma + c1); r.ssim[0] = FFMIN(r.ssim[0], src_loss); } + if (src_chroma > dst_chroma) { + const float src_loss = (2 * ref_var + c1) / (2 * ref_var + src_chroma + c1); + r.ssim[1] = FFMIN(r.ssim[1], src_loss); + r.ssim[2] = FFMIN(r.ssim[2], src_loss); + } } r.loss = get_loss(r.ssim); -- 2.52.0 >From 0f90f511d3cb51b0ae285a6ac526eb4bcfd8c165 Mon Sep 17 00:00:00 2001 From: Niklas Haas <[email protected]> Date: Sat, 20 Jun 2026 11:45:07 +0200 Subject: [PATCH 44/44] swscale/format: enable subsampled formats This also implies regenerating the uops macros to cover new additions implied by the new set of subsampled passes. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/format.c | 4 - libswscale/uops_macros.h | 156 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/libswscale/format.c b/libswscale/format.c index bfa950bcf1..a17aeed823 100644 --- a/libswscale/format.c +++ b/libswscale/format.c @@ -913,10 +913,6 @@ static int fmt_analyze(enum AVPixelFormat fmt, SwsReadWriteOp *rw_op, if (!desc) return AVERROR(EINVAL); - /* No support for subsampled formats at the moment */ - if (desc->log2_chroma_w || desc->log2_chroma_h) - return AVERROR(ENOTSUP); - /* No support for semi-planar formats at the moment */ if (desc->flags & AV_PIX_FMT_FLAG_PLANAR && av_pix_fmt_count_planes(fmt) < desc->nb_components) diff --git a/libswscale/uops_macros.h b/libswscale/uops_macros.h index a382988361..bc3fd0b498 100644 --- a/libswscale/uops_macros.h +++ b/libswscale/uops_macros.h @@ -156,6 +156,7 @@ MACRO(__VA_ARGS__, u8_move_x_z , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_x_w , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_y_x , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ + MACRO(__VA_ARGS__, u8_move_y_z , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_y_w , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_z_x , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u8_move_w_x , SWS_PIXEL_U8 , SWS_UOP_MOVE , 0x0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ @@ -190,6 +191,7 @@ MACRO(__VA_ARGS__, u8_move_x_z , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {0, 0, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_x_w , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {0, 0, 0, 0, 0, 0}, .par.move.src = {3, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_y_x , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ + MACRO(__VA_ARGS__, u8_move_y_z , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_y_w , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {3, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_z_x , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {2, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u8_move_w_x , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {3, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ @@ -243,10 +245,14 @@ #define SWS_FOR_STRUCT_U8_TO_U8(MACRO, ...) #define SWS_FOR_U8_TO_U16(MACRO, ...) \ MACRO(__VA_ARGS__, u8_to_u16_x , SWS_PIXEL_U8 , SWS_UOP_TO_U16 , 0x1) \ + MACRO(__VA_ARGS__, u8_to_u16_y , SWS_PIXEL_U8 , SWS_UOP_TO_U16 , 0x2) \ + MACRO(__VA_ARGS__, u8_to_u16_z , SWS_PIXEL_U8 , SWS_UOP_TO_U16 , 0x4) \ MACRO(__VA_ARGS__, u8_to_u16_xyz , SWS_PIXEL_U8 , SWS_UOP_TO_U16 , 0x7) \ MACRO(__VA_ARGS__, u8_to_u16_yzw , SWS_PIXEL_U8 , SWS_UOP_TO_U16 , 0xe) #define SWS_FOR_STRUCT_U8_TO_U16(MACRO, ...) \ MACRO(__VA_ARGS__, u8_to_u16_x , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_TO_U16 , .mask = 0x1) \ + MACRO(__VA_ARGS__, u8_to_u16_y , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_TO_U16 , .mask = 0x2) \ + MACRO(__VA_ARGS__, u8_to_u16_z , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_TO_U16 , .mask = 0x4) \ MACRO(__VA_ARGS__, u8_to_u16_xyz , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_TO_U16 , .mask = 0x7) \ MACRO(__VA_ARGS__, u8_to_u16_yzw , .type = SWS_PIXEL_U8 , .uop = SWS_UOP_TO_U16 , .mask = 0xe) #define SWS_FOR_U8_TO_U32(MACRO, ...) \ @@ -418,6 +424,7 @@ #define SWS_FOR_U16_WRITE_BIT(MACRO, ...) #define SWS_FOR_STRUCT_U16_WRITE_BIT(MACRO, ...) #define SWS_FOR_U16_PERMUTE(MACRO, ...) \ + MACRO(__VA_ARGS__, u16_permute_xzyw , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 0, 2, 1, 3) \ MACRO(__VA_ARGS__, u16_permute_xzwy , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 0, 2, 3, 1) \ MACRO(__VA_ARGS__, u16_permute_xwzy , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 0, 3, 2, 1) \ MACRO(__VA_ARGS__, u16_permute_yxzw , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 1, 0, 2, 3) \ @@ -426,12 +433,14 @@ MACRO(__VA_ARGS__, u16_permute_zxyw , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 0, 1, 3) \ MACRO(__VA_ARGS__, u16_permute_zyxw , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 1, 0, 3) \ MACRO(__VA_ARGS__, u16_permute_zywx , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 1, 3, 0) \ + MACRO(__VA_ARGS__, u16_permute_zwxy , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 2, 3, 0, 1) \ MACRO(__VA_ARGS__, u16_permute_wxyz , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 0, 1, 2) \ MACRO(__VA_ARGS__, u16_permute_wxzy , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 0, 2, 1) \ MACRO(__VA_ARGS__, u16_permute_wyxz , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 1, 0, 2) \ MACRO(__VA_ARGS__, u16_permute_wyzx , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 1, 2, 0) \ MACRO(__VA_ARGS__, u16_permute_wzyx , SWS_PIXEL_U16, SWS_UOP_PERMUTE , 0x0, 3, 2, 1, 0) #define SWS_FOR_STRUCT_U16_PERMUTE(MACRO, ...) \ + MACRO(__VA_ARGS__, u16_permute_xzyw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {0, 2, 1, 3}) \ MACRO(__VA_ARGS__, u16_permute_xzwy , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {0, 2, 3, 1}) \ MACRO(__VA_ARGS__, u16_permute_xwzy , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {0, 3, 2, 1}) \ MACRO(__VA_ARGS__, u16_permute_yxzw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {1, 0, 2, 3}) \ @@ -440,6 +449,7 @@ MACRO(__VA_ARGS__, u16_permute_zxyw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 0, 1, 3}) \ MACRO(__VA_ARGS__, u16_permute_zyxw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 1, 0, 3}) \ MACRO(__VA_ARGS__, u16_permute_zywx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 1, 3, 0}) \ + MACRO(__VA_ARGS__, u16_permute_zwxy , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {2, 3, 0, 1}) \ MACRO(__VA_ARGS__, u16_permute_wxyz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 0, 1, 2}) \ MACRO(__VA_ARGS__, u16_permute_wxzy , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 0, 2, 1}) \ MACRO(__VA_ARGS__, u16_permute_wyxz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PERMUTE , .mask = 0x0, .par.swizzle.in = {3, 1, 0, 2}) \ @@ -456,8 +466,10 @@ MACRO(__VA_ARGS__, u16_move_x_z , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_x_w , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_y_x , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ + MACRO(__VA_ARGS__, u16_move_y_z , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_y_w , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_w_x , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ + MACRO(__VA_ARGS__, u16_move_xy_zw , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 2, 0, 1, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_xz_zw , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 2, 0, 2, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_yx_xw , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 2, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u16_move_yz_xx , SWS_PIXEL_U16, SWS_UOP_MOVE , 0x0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ @@ -480,8 +492,10 @@ MACRO(__VA_ARGS__, u16_move_x_z , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {0, 0, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_x_w , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {0, 0, 0, 0, 0, 0}, .par.move.src = {3, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_y_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ + MACRO(__VA_ARGS__, u16_move_y_z , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_y_w , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {3, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_w_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {3, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ + MACRO(__VA_ARGS__, u16_move_xy_zw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {0, 1, 0, 0, 0, 0}, .par.move.src = {2, 3, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_xz_zw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {0, 2, 0, 0, 0, 0}, .par.move.src = {2, 3, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_yx_xw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {0, 3, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u16_move_yz_xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {1, 2, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ @@ -503,16 +517,24 @@ MACRO(__VA_ARGS__, u16_swap_bytes_x , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x1) \ MACRO(__VA_ARGS__, u16_swap_bytes_y , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x2) \ MACRO(__VA_ARGS__, u16_swap_bytes_xy , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x3) \ + MACRO(__VA_ARGS__, u16_swap_bytes_z , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x4) \ + MACRO(__VA_ARGS__, u16_swap_bytes_xz , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x5) \ + MACRO(__VA_ARGS__, u16_swap_bytes_yz , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x6) \ MACRO(__VA_ARGS__, u16_swap_bytes_xyz , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x7) \ MACRO(__VA_ARGS__, u16_swap_bytes_xw , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0x9) \ + MACRO(__VA_ARGS__, u16_swap_bytes_zw , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0xc) \ MACRO(__VA_ARGS__, u16_swap_bytes_yzw , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0xe) \ MACRO(__VA_ARGS__, u16_swap_bytes_xyzw , SWS_PIXEL_U16, SWS_UOP_SWAP_BYTES , 0xf) #define SWS_FOR_STRUCT_U16_SWAP_BYTES(MACRO, ...) \ MACRO(__VA_ARGS__, u16_swap_bytes_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x1) \ MACRO(__VA_ARGS__, u16_swap_bytes_y , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x2) \ MACRO(__VA_ARGS__, u16_swap_bytes_xy , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x3) \ + MACRO(__VA_ARGS__, u16_swap_bytes_z , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x4) \ + MACRO(__VA_ARGS__, u16_swap_bytes_xz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x5) \ + MACRO(__VA_ARGS__, u16_swap_bytes_yz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x6) \ MACRO(__VA_ARGS__, u16_swap_bytes_xyz , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x7) \ MACRO(__VA_ARGS__, u16_swap_bytes_xw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0x9) \ + MACRO(__VA_ARGS__, u16_swap_bytes_zw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0xc) \ MACRO(__VA_ARGS__, u16_swap_bytes_yzw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0xe) \ MACRO(__VA_ARGS__, u16_swap_bytes_xyzw , .type = SWS_PIXEL_U16, .uop = SWS_UOP_SWAP_BYTES , .mask = 0xf) #define SWS_FOR_U16_EXPAND_BIT(MACRO, ...) \ @@ -578,8 +600,28 @@ MACRO(__VA_ARGS__, u16_pack_xyz_555 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PACK , .mask = 0x7, .par.pack.pattern = {5, 5, 5, 0}) \ MACRO(__VA_ARGS__, u16_pack_xyz_565 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_PACK , .mask = 0x7, .par.pack.pattern = {5, 6, 5, 0}) #define SWS_FOR_U16_LSHIFT(MACRO, ...) \ + MACRO(__VA_ARGS__, u16_lshift_x_1 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 1) \ + MACRO(__VA_ARGS__, u16_lshift_x_2 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 2) \ + MACRO(__VA_ARGS__, u16_lshift_x_3 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 3) \ MACRO(__VA_ARGS__, u16_lshift_x_4 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 4) \ + MACRO(__VA_ARGS__, u16_lshift_x_5 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 5) \ MACRO(__VA_ARGS__, u16_lshift_x_6 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 6) \ + MACRO(__VA_ARGS__, u16_lshift_x_7 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 7) \ + MACRO(__VA_ARGS__, u16_lshift_x_8 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x1, 8) \ + MACRO(__VA_ARGS__, u16_lshift_y_1 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x2, 1) \ + MACRO(__VA_ARGS__, u16_lshift_y_2 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x2, 2) \ + MACRO(__VA_ARGS__, u16_lshift_y_4 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x2, 4) \ + MACRO(__VA_ARGS__, u16_lshift_y_6 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x2, 6) \ + MACRO(__VA_ARGS__, u16_lshift_y_8 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x2, 8) \ + MACRO(__VA_ARGS__, u16_lshift_xy_4 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x3, 4) \ + MACRO(__VA_ARGS__, u16_lshift_xy_6 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x3, 6) \ + MACRO(__VA_ARGS__, u16_lshift_z_1 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x4, 1) \ + MACRO(__VA_ARGS__, u16_lshift_z_2 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x4, 2) \ + MACRO(__VA_ARGS__, u16_lshift_z_4 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x4, 4) \ + MACRO(__VA_ARGS__, u16_lshift_z_6 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x4, 6) \ + MACRO(__VA_ARGS__, u16_lshift_z_8 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x4, 8) \ + MACRO(__VA_ARGS__, u16_lshift_yz_4 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x6, 4) \ + MACRO(__VA_ARGS__, u16_lshift_yz_6 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x6, 6) \ MACRO(__VA_ARGS__, u16_lshift_xyz_1 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x7, 1) \ MACRO(__VA_ARGS__, u16_lshift_xyz_2 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x7, 2) \ MACRO(__VA_ARGS__, u16_lshift_xyz_3 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0x7, 3) \ @@ -594,8 +636,28 @@ MACRO(__VA_ARGS__, u16_lshift_yzw_6 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0xe, 6) \ MACRO(__VA_ARGS__, u16_lshift_yzw_8 , SWS_PIXEL_U16, SWS_UOP_LSHIFT , 0xe, 8) #define SWS_FOR_STRUCT_U16_LSHIFT(MACRO, ...) \ + MACRO(__VA_ARGS__, u16_lshift_x_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 1) \ + MACRO(__VA_ARGS__, u16_lshift_x_2 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 2) \ + MACRO(__VA_ARGS__, u16_lshift_x_3 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 3) \ MACRO(__VA_ARGS__, u16_lshift_x_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_lshift_x_5 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 5) \ MACRO(__VA_ARGS__, u16_lshift_x_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 6) \ + MACRO(__VA_ARGS__, u16_lshift_x_7 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 7) \ + MACRO(__VA_ARGS__, u16_lshift_x_8 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 8) \ + MACRO(__VA_ARGS__, u16_lshift_y_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x2, .par.shift.amount = 1) \ + MACRO(__VA_ARGS__, u16_lshift_y_2 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x2, .par.shift.amount = 2) \ + MACRO(__VA_ARGS__, u16_lshift_y_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x2, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_lshift_y_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x2, .par.shift.amount = 6) \ + MACRO(__VA_ARGS__, u16_lshift_y_8 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x2, .par.shift.amount = 8) \ + MACRO(__VA_ARGS__, u16_lshift_xy_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x3, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_lshift_xy_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x3, .par.shift.amount = 6) \ + MACRO(__VA_ARGS__, u16_lshift_z_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x4, .par.shift.amount = 1) \ + MACRO(__VA_ARGS__, u16_lshift_z_2 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x4, .par.shift.amount = 2) \ + MACRO(__VA_ARGS__, u16_lshift_z_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x4, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_lshift_z_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x4, .par.shift.amount = 6) \ + MACRO(__VA_ARGS__, u16_lshift_z_8 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x4, .par.shift.amount = 8) \ + MACRO(__VA_ARGS__, u16_lshift_yz_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x6, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_lshift_yz_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x6, .par.shift.amount = 6) \ MACRO(__VA_ARGS__, u16_lshift_xyz_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x7, .par.shift.amount = 1) \ MACRO(__VA_ARGS__, u16_lshift_xyz_2 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x7, .par.shift.amount = 2) \ MACRO(__VA_ARGS__, u16_lshift_xyz_3 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_LSHIFT , .mask = 0x7, .par.shift.amount = 3) \ @@ -613,17 +675,24 @@ MACRO(__VA_ARGS__, u16_rshift_x_4 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x1, 4) \ MACRO(__VA_ARGS__, u16_rshift_x_6 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x1, 6) \ MACRO(__VA_ARGS__, u16_rshift_y_4 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x2, 4) \ + MACRO(__VA_ARGS__, u16_rshift_xy_4 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x3, 4) \ + MACRO(__VA_ARGS__, u16_rshift_xy_6 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x3, 6) \ + MACRO(__VA_ARGS__, u16_rshift_xz_4 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x5, 4) \ MACRO(__VA_ARGS__, u16_rshift_xyz_4 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x7, 4) \ MACRO(__VA_ARGS__, u16_rshift_xyz_6 , SWS_PIXEL_U16, SWS_UOP_RSHIFT , 0x7, 6) #define SWS_FOR_STRUCT_U16_RSHIFT(MACRO, ...) \ MACRO(__VA_ARGS__, u16_rshift_x_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x1, .par.shift.amount = 4) \ MACRO(__VA_ARGS__, u16_rshift_x_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x1, .par.shift.amount = 6) \ MACRO(__VA_ARGS__, u16_rshift_y_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x2, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_rshift_xy_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x3, .par.shift.amount = 4) \ + MACRO(__VA_ARGS__, u16_rshift_xy_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x3, .par.shift.amount = 6) \ + MACRO(__VA_ARGS__, u16_rshift_xz_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x5, .par.shift.amount = 4) \ MACRO(__VA_ARGS__, u16_rshift_xyz_4 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x7, .par.shift.amount = 4) \ MACRO(__VA_ARGS__, u16_rshift_xyz_6 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_RSHIFT , .mask = 0x7, .par.shift.amount = 6) #define SWS_FOR_U16_CLEAR(MACRO, ...) \ MACRO(__VA_ARGS__, u16_clear_x_x , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x1, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_x_1 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x1, 0x00001, 0x00000) \ + MACRO(__VA_ARGS__, u16_clear_y_x , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x2, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_y_1 , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x2, 0x00002, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_xy_xx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x3, 0x00000, 0x00000) \ MACRO(__VA_ARGS__, u16_clear_xyz_xxx , SWS_PIXEL_U16, SWS_UOP_CLEAR , 0x7, 0x00000, 0x00000) \ @@ -637,6 +706,7 @@ #define SWS_FOR_STRUCT_U16_CLEAR(MACRO, ...) \ MACRO(__VA_ARGS__, u16_clear_x_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x1, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_x_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x1, .par.clear.one = 0x1, .par.clear.zero = 0x0) \ + MACRO(__VA_ARGS__, u16_clear_y_x , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x2, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_y_1 , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x2, .par.clear.one = 0x2, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_xy_xx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x3, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ MACRO(__VA_ARGS__, u16_clear_xyz_xxx , .type = SWS_PIXEL_U16, .uop = SWS_UOP_CLEAR , .mask = 0x7, .par.clear.one = 0x0, .par.clear.zero = 0x0) \ @@ -752,6 +822,7 @@ MACRO(__VA_ARGS__, u32_move_x_z , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_x_w , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_y_x , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ + MACRO(__VA_ARGS__, u32_move_y_z , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_y_w , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 1, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_z_x , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_w_x , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 1, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ @@ -759,6 +830,7 @@ MACRO(__VA_ARGS__, u32_move_xz_zw , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 0, 2, 0, 0, 0, 0, 2, 3, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_yz_xx , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 1, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_zx_xw , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 2, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0) \ + MACRO(__VA_ARGS__, u32_move_zy_yx , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 2, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_wx_xy , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 3, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_wy_yx , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 3, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0) \ MACRO(__VA_ARGS__, u32_move_wz_zx , SWS_PIXEL_U32, SWS_UOP_MOVE , 0x0, 2, 3, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0) \ @@ -785,6 +857,7 @@ MACRO(__VA_ARGS__, u32_move_x_z , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {0, 0, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_x_w , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {0, 0, 0, 0, 0, 0}, .par.move.src = {3, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_y_x , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ + MACRO(__VA_ARGS__, u32_move_y_z , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_y_w , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {1, 0, 0, 0, 0, 0}, .par.move.src = {3, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_z_x , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {2, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_w_x , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 1, .par.move.dst = {3, 0, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ @@ -792,6 +865,7 @@ MACRO(__VA_ARGS__, u32_move_xz_zw , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {0, 2, 0, 0, 0, 0}, .par.move.src = {2, 3, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_yz_xx , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {1, 2, 0, 0, 0, 0}, .par.move.src = {0, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_zx_xw , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {2, 0, 0, 0, 0, 0}, .par.move.src = {0, 3, 0, 0, 0, 0}) \ + MACRO(__VA_ARGS__, u32_move_zy_yx , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {2, 1, 0, 0, 0, 0}, .par.move.src = {1, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_wx_xy , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {3, 0, 0, 0, 0, 0}, .par.move.src = {0, 1, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_wy_yx , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {3, 1, 0, 0, 0, 0}, .par.move.src = {1, 0, 0, 0, 0, 0}) \ MACRO(__VA_ARGS__, u32_move_wz_zx , .type = SWS_PIXEL_U32, .uop = SWS_UOP_MOVE , .mask = 0x0, .par.move.num_moves = 2, .par.move.dst = {3, 2, 0, 0, 0, 0}, .par.move.src = {2, 0, 0, 0, 0, 0}) \ @@ -838,24 +912,32 @@ #define SWS_FOR_U32_TO_U16(MACRO, ...) \ MACRO(__VA_ARGS__, u32_to_u16_y , SWS_PIXEL_U32, SWS_UOP_TO_U16 , 0x2) \ MACRO(__VA_ARGS__, u32_to_u16_z , SWS_PIXEL_U32, SWS_UOP_TO_U16 , 0x4) \ + MACRO(__VA_ARGS__, u32_to_u16_xz , SWS_PIXEL_U32, SWS_UOP_TO_U16 , 0x5) \ MACRO(__VA_ARGS__, u32_to_u16_xyz , SWS_PIXEL_U32, SWS_UOP_TO_U16 , 0x7) \ + MACRO(__VA_ARGS__, u32_to_u16_yw , SWS_PIXEL_U32, SWS_UOP_TO_U16 , 0xa) \ MACRO(__VA_ARGS__, u32_to_u16_yzw , SWS_PIXEL_U32, SWS_UOP_TO_U16 , 0xe) #define SWS_FOR_STRUCT_U32_TO_U16(MACRO, ...) \ MACRO(__VA_ARGS__, u32_to_u16_y , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_U16 , .mask = 0x2) \ MACRO(__VA_ARGS__, u32_to_u16_z , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_U16 , .mask = 0x4) \ + MACRO(__VA_ARGS__, u32_to_u16_xz , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_U16 , .mask = 0x5) \ MACRO(__VA_ARGS__, u32_to_u16_xyz , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_U16 , .mask = 0x7) \ + MACRO(__VA_ARGS__, u32_to_u16_yw , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_U16 , .mask = 0xa) \ MACRO(__VA_ARGS__, u32_to_u16_yzw , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_U16 , .mask = 0xe) #define SWS_FOR_U32_TO_U32(MACRO, ...) #define SWS_FOR_STRUCT_U32_TO_U32(MACRO, ...) #define SWS_FOR_U32_TO_F32(MACRO, ...) \ MACRO(__VA_ARGS__, u32_to_f32_y , SWS_PIXEL_U32, SWS_UOP_TO_F32 , 0x2) \ MACRO(__VA_ARGS__, u32_to_f32_z , SWS_PIXEL_U32, SWS_UOP_TO_F32 , 0x4) \ + MACRO(__VA_ARGS__, u32_to_f32_xz , SWS_PIXEL_U32, SWS_UOP_TO_F32 , 0x5) \ MACRO(__VA_ARGS__, u32_to_f32_xyz , SWS_PIXEL_U32, SWS_UOP_TO_F32 , 0x7) \ + MACRO(__VA_ARGS__, u32_to_f32_yw , SWS_PIXEL_U32, SWS_UOP_TO_F32 , 0xa) \ MACRO(__VA_ARGS__, u32_to_f32_yzw , SWS_PIXEL_U32, SWS_UOP_TO_F32 , 0xe) #define SWS_FOR_STRUCT_U32_TO_F32(MACRO, ...) \ MACRO(__VA_ARGS__, u32_to_f32_y , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_F32 , .mask = 0x2) \ MACRO(__VA_ARGS__, u32_to_f32_z , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_F32 , .mask = 0x4) \ + MACRO(__VA_ARGS__, u32_to_f32_xz , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_F32 , .mask = 0x5) \ MACRO(__VA_ARGS__, u32_to_f32_xyz , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_F32 , .mask = 0x7) \ + MACRO(__VA_ARGS__, u32_to_f32_yw , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_F32 , .mask = 0xa) \ MACRO(__VA_ARGS__, u32_to_f32_yzw , .type = SWS_PIXEL_U32, .uop = SWS_UOP_TO_F32 , .mask = 0xe) #define SWS_FOR_U32_SCALE(MACRO, ...) \ MACRO(__VA_ARGS__, u32_scale_x , SWS_PIXEL_U32, SWS_UOP_SCALE , 0x1) \ @@ -882,8 +964,12 @@ MACRO(__VA_ARGS__, u32_pack_xyzw_2aaa , .type = SWS_PIXEL_U32, .uop = SWS_UOP_PACK , .mask = 0xf, .par.pack.pattern = {2, 10, 10, 10}) \ MACRO(__VA_ARGS__, u32_pack_xyzw_aaa2 , .type = SWS_PIXEL_U32, .uop = SWS_UOP_PACK , .mask = 0xf, .par.pack.pattern = {10, 10, 10, 2}) #define SWS_FOR_U32_LSHIFT(MACRO, ...) \ + MACRO(__VA_ARGS__, u32_lshift_x_1 , SWS_PIXEL_U32, SWS_UOP_LSHIFT , 0x1, 1) \ + MACRO(__VA_ARGS__, u32_lshift_x_2 , SWS_PIXEL_U32, SWS_UOP_LSHIFT , 0x1, 2) \ MACRO(__VA_ARGS__, u32_lshift_xyz_2 , SWS_PIXEL_U32, SWS_UOP_LSHIFT , 0x7, 2) #define SWS_FOR_STRUCT_U32_LSHIFT(MACRO, ...) \ + MACRO(__VA_ARGS__, u32_lshift_x_1 , .type = SWS_PIXEL_U32, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 1) \ + MACRO(__VA_ARGS__, u32_lshift_x_2 , .type = SWS_PIXEL_U32, .uop = SWS_UOP_LSHIFT , .mask = 0x1, .par.shift.amount = 2) \ MACRO(__VA_ARGS__, u32_lshift_xyz_2 , .type = SWS_PIXEL_U32, .uop = SWS_UOP_LSHIFT , .mask = 0x7, .par.shift.amount = 2) #define SWS_FOR_U32_RSHIFT(MACRO, ...) #define SWS_FOR_STRUCT_U32_RSHIFT(MACRO, ...) @@ -973,40 +1059,56 @@ #define SWS_FOR_STRUCT_F32_EXPAND_QUAD(MACRO, ...) #define SWS_FOR_F32_TO_U8(MACRO, ...) \ MACRO(__VA_ARGS__, f32_to_u8_x , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x1) \ + MACRO(__VA_ARGS__, f32_to_u8_y , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x2) \ MACRO(__VA_ARGS__, f32_to_u8_xy , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x3) \ + MACRO(__VA_ARGS__, f32_to_u8_z , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x4) \ + MACRO(__VA_ARGS__, f32_to_u8_yz , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x6) \ MACRO(__VA_ARGS__, f32_to_u8_xyz , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x7) \ MACRO(__VA_ARGS__, f32_to_u8_xw , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0x9) \ MACRO(__VA_ARGS__, f32_to_u8_yzw , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0xe) \ MACRO(__VA_ARGS__, f32_to_u8_xyzw , SWS_PIXEL_F32, SWS_UOP_TO_U8 , 0xf) #define SWS_FOR_STRUCT_F32_TO_U8(MACRO, ...) \ MACRO(__VA_ARGS__, f32_to_u8_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x1) \ + MACRO(__VA_ARGS__, f32_to_u8_y , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x2) \ MACRO(__VA_ARGS__, f32_to_u8_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x3) \ + MACRO(__VA_ARGS__, f32_to_u8_z , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x4) \ + MACRO(__VA_ARGS__, f32_to_u8_yz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x6) \ MACRO(__VA_ARGS__, f32_to_u8_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_to_u8_xw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0x9) \ MACRO(__VA_ARGS__, f32_to_u8_yzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0xe) \ MACRO(__VA_ARGS__, f32_to_u8_xyzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U8 , .mask = 0xf) #define SWS_FOR_F32_TO_U16(MACRO, ...) \ MACRO(__VA_ARGS__, f32_to_u16_x , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x1) \ + MACRO(__VA_ARGS__, f32_to_u16_y , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x2) \ MACRO(__VA_ARGS__, f32_to_u16_xy , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x3) \ + MACRO(__VA_ARGS__, f32_to_u16_z , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x4) \ + MACRO(__VA_ARGS__, f32_to_u16_yz , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x6) \ MACRO(__VA_ARGS__, f32_to_u16_xyz , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x7) \ MACRO(__VA_ARGS__, f32_to_u16_xw , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0x9) \ MACRO(__VA_ARGS__, f32_to_u16_yzw , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0xe) \ MACRO(__VA_ARGS__, f32_to_u16_xyzw , SWS_PIXEL_F32, SWS_UOP_TO_U16 , 0xf) #define SWS_FOR_STRUCT_F32_TO_U16(MACRO, ...) \ MACRO(__VA_ARGS__, f32_to_u16_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x1) \ + MACRO(__VA_ARGS__, f32_to_u16_y , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x2) \ MACRO(__VA_ARGS__, f32_to_u16_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x3) \ + MACRO(__VA_ARGS__, f32_to_u16_z , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x4) \ + MACRO(__VA_ARGS__, f32_to_u16_yz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x6) \ MACRO(__VA_ARGS__, f32_to_u16_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_to_u16_xw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0x9) \ MACRO(__VA_ARGS__, f32_to_u16_yzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0xe) \ MACRO(__VA_ARGS__, f32_to_u16_xyzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U16 , .mask = 0xf) #define SWS_FOR_F32_TO_U32(MACRO, ...) \ MACRO(__VA_ARGS__, f32_to_u32_x , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0x1) \ + MACRO(__VA_ARGS__, f32_to_u32_xy , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0x3) \ + MACRO(__VA_ARGS__, f32_to_u32_yz , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0x6) \ MACRO(__VA_ARGS__, f32_to_u32_xyz , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0x7) \ MACRO(__VA_ARGS__, f32_to_u32_xw , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0x9) \ MACRO(__VA_ARGS__, f32_to_u32_yzw , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0xe) \ MACRO(__VA_ARGS__, f32_to_u32_xyzw , SWS_PIXEL_F32, SWS_UOP_TO_U32 , 0xf) #define SWS_FOR_STRUCT_F32_TO_U32(MACRO, ...) \ MACRO(__VA_ARGS__, f32_to_u32_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U32 , .mask = 0x1) \ + MACRO(__VA_ARGS__, f32_to_u32_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U32 , .mask = 0x3) \ + MACRO(__VA_ARGS__, f32_to_u32_yz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U32 , .mask = 0x6) \ MACRO(__VA_ARGS__, f32_to_u32_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U32 , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_to_u32_xw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U32 , .mask = 0x9) \ MACRO(__VA_ARGS__, f32_to_u32_yzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_TO_U32 , .mask = 0xe) \ @@ -1015,20 +1117,26 @@ #define SWS_FOR_STRUCT_F32_TO_F32(MACRO, ...) #define SWS_FOR_F32_SCALE(MACRO, ...) \ MACRO(__VA_ARGS__, f32_scale_x , SWS_PIXEL_F32, SWS_UOP_SCALE , 0x1) \ + MACRO(__VA_ARGS__, f32_scale_y , SWS_PIXEL_F32, SWS_UOP_SCALE , 0x2) \ MACRO(__VA_ARGS__, f32_scale_xy , SWS_PIXEL_F32, SWS_UOP_SCALE , 0x3) \ + MACRO(__VA_ARGS__, f32_scale_z , SWS_PIXEL_F32, SWS_UOP_SCALE , 0x4) \ MACRO(__VA_ARGS__, f32_scale_xyz , SWS_PIXEL_F32, SWS_UOP_SCALE , 0x7) \ MACRO(__VA_ARGS__, f32_scale_yzw , SWS_PIXEL_F32, SWS_UOP_SCALE , 0xe) \ MACRO(__VA_ARGS__, f32_scale_xyzw , SWS_PIXEL_F32, SWS_UOP_SCALE , 0xf) #define SWS_FOR_STRUCT_F32_SCALE(MACRO, ...) \ MACRO(__VA_ARGS__, f32_scale_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0x1) \ + MACRO(__VA_ARGS__, f32_scale_y , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0x2) \ MACRO(__VA_ARGS__, f32_scale_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0x3) \ + MACRO(__VA_ARGS__, f32_scale_z , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0x4) \ MACRO(__VA_ARGS__, f32_scale_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_scale_yzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0xe) \ MACRO(__VA_ARGS__, f32_scale_xyzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_SCALE , .mask = 0xf) #define SWS_FOR_F32_ADD(MACRO, ...) \ MACRO(__VA_ARGS__, f32_add_x , SWS_PIXEL_F32, SWS_UOP_ADD , 0x1) \ + MACRO(__VA_ARGS__, f32_add_y , SWS_PIXEL_F32, SWS_UOP_ADD , 0x2) \ MACRO(__VA_ARGS__, f32_add_xy , SWS_PIXEL_F32, SWS_UOP_ADD , 0x3) \ MACRO(__VA_ARGS__, f32_add_xz , SWS_PIXEL_F32, SWS_UOP_ADD , 0x5) \ + MACRO(__VA_ARGS__, f32_add_yz , SWS_PIXEL_F32, SWS_UOP_ADD , 0x6) \ MACRO(__VA_ARGS__, f32_add_xyz , SWS_PIXEL_F32, SWS_UOP_ADD , 0x7) \ MACRO(__VA_ARGS__, f32_add_w , SWS_PIXEL_F32, SWS_UOP_ADD , 0x8) \ MACRO(__VA_ARGS__, f32_add_xw , SWS_PIXEL_F32, SWS_UOP_ADD , 0x9) \ @@ -1036,8 +1144,10 @@ MACRO(__VA_ARGS__, f32_add_xyzw , SWS_PIXEL_F32, SWS_UOP_ADD , 0xf) #define SWS_FOR_STRUCT_F32_ADD(MACRO, ...) \ MACRO(__VA_ARGS__, f32_add_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x1) \ + MACRO(__VA_ARGS__, f32_add_y , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x2) \ MACRO(__VA_ARGS__, f32_add_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x3) \ MACRO(__VA_ARGS__, f32_add_xz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x5) \ + MACRO(__VA_ARGS__, f32_add_yz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x6) \ MACRO(__VA_ARGS__, f32_add_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_add_w , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x8) \ MACRO(__VA_ARGS__, f32_add_xw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0x9) \ @@ -1045,14 +1155,20 @@ MACRO(__VA_ARGS__, f32_add_xyzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_ADD , .mask = 0xf) #define SWS_FOR_F32_MIN(MACRO, ...) \ MACRO(__VA_ARGS__, f32_min_x , SWS_PIXEL_F32, SWS_UOP_MIN , 0x1) \ + MACRO(__VA_ARGS__, f32_min_y , SWS_PIXEL_F32, SWS_UOP_MIN , 0x2) \ MACRO(__VA_ARGS__, f32_min_xy , SWS_PIXEL_F32, SWS_UOP_MIN , 0x3) \ + MACRO(__VA_ARGS__, f32_min_z , SWS_PIXEL_F32, SWS_UOP_MIN , 0x4) \ + MACRO(__VA_ARGS__, f32_min_yz , SWS_PIXEL_F32, SWS_UOP_MIN , 0x6) \ MACRO(__VA_ARGS__, f32_min_xyz , SWS_PIXEL_F32, SWS_UOP_MIN , 0x7) \ MACRO(__VA_ARGS__, f32_min_xw , SWS_PIXEL_F32, SWS_UOP_MIN , 0x9) \ MACRO(__VA_ARGS__, f32_min_yzw , SWS_PIXEL_F32, SWS_UOP_MIN , 0xe) \ MACRO(__VA_ARGS__, f32_min_xyzw , SWS_PIXEL_F32, SWS_UOP_MIN , 0xf) #define SWS_FOR_STRUCT_F32_MIN(MACRO, ...) \ MACRO(__VA_ARGS__, f32_min_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x1) \ + MACRO(__VA_ARGS__, f32_min_y , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x2) \ MACRO(__VA_ARGS__, f32_min_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x3) \ + MACRO(__VA_ARGS__, f32_min_z , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x4) \ + MACRO(__VA_ARGS__, f32_min_yz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x6) \ MACRO(__VA_ARGS__, f32_min_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_min_xw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0x9) \ MACRO(__VA_ARGS__, f32_min_yzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MIN , .mask = 0xe) \ @@ -1060,12 +1176,14 @@ #define SWS_FOR_F32_MAX(MACRO, ...) \ MACRO(__VA_ARGS__, f32_max_x , SWS_PIXEL_F32, SWS_UOP_MAX , 0x1) \ MACRO(__VA_ARGS__, f32_max_xy , SWS_PIXEL_F32, SWS_UOP_MAX , 0x3) \ + MACRO(__VA_ARGS__, f32_max_yz , SWS_PIXEL_F32, SWS_UOP_MAX , 0x6) \ MACRO(__VA_ARGS__, f32_max_xyz , SWS_PIXEL_F32, SWS_UOP_MAX , 0x7) \ MACRO(__VA_ARGS__, f32_max_xw , SWS_PIXEL_F32, SWS_UOP_MAX , 0x9) \ MACRO(__VA_ARGS__, f32_max_xyzw , SWS_PIXEL_F32, SWS_UOP_MAX , 0xf) #define SWS_FOR_STRUCT_F32_MAX(MACRO, ...) \ MACRO(__VA_ARGS__, f32_max_x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MAX , .mask = 0x1) \ MACRO(__VA_ARGS__, f32_max_xy , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MAX , .mask = 0x3) \ + MACRO(__VA_ARGS__, f32_max_yz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MAX , .mask = 0x6) \ MACRO(__VA_ARGS__, f32_max_xyz , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MAX , .mask = 0x7) \ MACRO(__VA_ARGS__, f32_max_xw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MAX , .mask = 0x9) \ MACRO(__VA_ARGS__, f32_max_xyzw , .type = SWS_PIXEL_F32, .uop = SWS_UOP_MAX , .mask = 0xf) @@ -1084,6 +1202,8 @@ MACRO(__VA_ARGS__, f32_linear_x_x000x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x1, 0x41040, 0xbefae) \ MACRO(__VA_ARGS__, f32_linear_x_xxx00 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x1, 0x41040, 0xbefb8) \ MACRO(__VA_ARGS__, f32_linear_y_0x000 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x2, 0x41001, 0xbefbe) \ + MACRO(__VA_ARGS__, f32_linear_yz_xxx0x_xxx0x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x6, 0x40001, 0xba11e) \ + MACRO(__VA_ARGS__, f32_linear_yz_0x00x_00x0x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x6, 0x40001, 0xbadbe) \ MACRO(__VA_ARGS__, f32_linear_xyz_xxx0x_xxx0x_xxx0x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x7, 0x40000, 0xba108) \ MACRO(__VA_ARGS__, f32_linear_xyz_x0x0x_xxx0x_xx00x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x7, 0x40000, 0xbb10a) \ MACRO(__VA_ARGS__, f32_linear_xyz_xxx00_xxx0x_xxx0x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x7, 0x40000, 0xba118) \ @@ -1091,8 +1211,10 @@ MACRO(__VA_ARGS__, f32_linear_xyz_x0000_0x000_00x00 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x7, 0x40000, 0xbefbe) \ MACRO(__VA_ARGS__, f32_linear_xyz_10x0x_1xx0x_1x00x , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x7, 0x40421, 0xbb10a) \ MACRO(__VA_ARGS__, f32_linear_w_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x8, 0x01041, 0xbefbe) \ + MACRO(__VA_ARGS__, f32_linear_xw_xxx0x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x9, 0x01040, 0xbefa8) \ MACRO(__VA_ARGS__, f32_linear_xw_x000x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x9, 0x01040, 0xbefae) \ MACRO(__VA_ARGS__, f32_linear_xw_xxx00_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x9, 0x01040, 0xbefb8) \ + MACRO(__VA_ARGS__, f32_linear_xw_x0000_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0x9, 0x01040, 0xbefbe) \ MACRO(__VA_ARGS__, f32_linear_xyzw_xxx0x_xxx0x_xxx0x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0xf, 0x00000, 0xba108) \ MACRO(__VA_ARGS__, f32_linear_xyzw_x0x0x_xxx0x_xx00x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0xf, 0x00000, 0xbb10a) \ MACRO(__VA_ARGS__, f32_linear_xyzw_x0000_0x000_00x00_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR , 0xf, 0x00000, 0xbefbe) @@ -1101,6 +1223,8 @@ MACRO(__VA_ARGS__, f32_linear_x_x000x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x1, .par.lin.one = 0x41040, .par.lin.zero = 0xbefae) \ MACRO(__VA_ARGS__, f32_linear_x_xxx00 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x1, .par.lin.one = 0x41040, .par.lin.zero = 0xbefb8) \ MACRO(__VA_ARGS__, f32_linear_y_0x000 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x2, .par.lin.one = 0x41001, .par.lin.zero = 0xbefbe) \ + MACRO(__VA_ARGS__, f32_linear_yz_xxx0x_xxx0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x6, .par.lin.one = 0x40001, .par.lin.zero = 0xba11e) \ + MACRO(__VA_ARGS__, f32_linear_yz_0x00x_00x0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x6, .par.lin.one = 0x40001, .par.lin.zero = 0xbadbe) \ MACRO(__VA_ARGS__, f32_linear_xyz_xxx0x_xxx0x_xxx0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xba108) \ MACRO(__VA_ARGS__, f32_linear_xyz_x0x0x_xxx0x_xx00x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xbb10a) \ MACRO(__VA_ARGS__, f32_linear_xyz_xxx00_xxx0x_xxx0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xba118) \ @@ -1108,8 +1232,10 @@ MACRO(__VA_ARGS__, f32_linear_xyz_x0000_0x000_00x00 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xbefbe) \ MACRO(__VA_ARGS__, f32_linear_xyz_10x0x_1xx0x_1x00x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x7, .par.lin.one = 0x40421, .par.lin.zero = 0xbb10a) \ MACRO(__VA_ARGS__, f32_linear_w_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x8, .par.lin.one = 0x1041, .par.lin.zero = 0xbefbe) \ + MACRO(__VA_ARGS__, f32_linear_xw_xxx0x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefa8) \ MACRO(__VA_ARGS__, f32_linear_xw_x000x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefae) \ MACRO(__VA_ARGS__, f32_linear_xw_xxx00_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefb8) \ + MACRO(__VA_ARGS__, f32_linear_xw_x0000_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefbe) \ MACRO(__VA_ARGS__, f32_linear_xyzw_xxx0x_xxx0x_xxx0x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xba108) \ MACRO(__VA_ARGS__, f32_linear_xyzw_x0x0x_xxx0x_xx00x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xbb10a) \ MACRO(__VA_ARGS__, f32_linear_xyzw_x0000_0x000_00x00_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xbefbe) @@ -1123,6 +1249,10 @@ MACRO(__VA_ARGS__, f32_linear_fma_x_xxX00 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x1, 0x41040, 0xbefb8, 0xffffc) \ MACRO(__VA_ARGS__, f32_linear_fma_x_xXX00 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x1, 0x41040, 0xbefb8, 0xffffe) \ MACRO(__VA_ARGS__, f32_linear_fma_y_0x000 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x2, 0x41001, 0xbefbe, 0xfffbf) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_xxx0x_xxx0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x6, 0x40001, 0xba11e, 0xfa11f) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_XxX0x_XXX0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x6, 0x40001, 0xba11e, 0xfbdbf) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_XXX0x_XXX0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x6, 0x40001, 0xba11e, 0xfbdff) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_0X00x_00X0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x6, 0x40001, 0xbadbe, 0xfbdff) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_xxx0x_xxx0x_xxx0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x7, 0x40000, 0xba108, 0xfa108) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_XXX0x_XxX0x_XXX0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x7, 0x40000, 0xba108, 0xfbdaf) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_XXX0x_XXX0x_XXX0x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x7, 0x40000, 0xba108, 0xfbdef) \ @@ -1134,10 +1264,13 @@ MACRO(__VA_ARGS__, f32_linear_fma_xyz_x0000_0x000_00x00 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x7, 0x40000, 0xbefbe, 0xfefbe) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_10X0x_1XX0x_1X00x , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x7, 0x40421, 0xbb10a, 0xfbdef) \ MACRO(__VA_ARGS__, f32_linear_fma_w_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x8, 0x01041, 0xbefbe, 0xbffff) \ + MACRO(__VA_ARGS__, f32_linear_fma_xw_xxx0x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefa8, 0xbffe8) \ + MACRO(__VA_ARGS__, f32_linear_fma_xw_XXX0x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefa8, 0xbffef) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_x000x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefae, 0xbffee) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_X000x_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefae, 0xbffef) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_xxx00_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefb8, 0xbfff8) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_xXX00_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefb8, 0xbfffe) \ + MACRO(__VA_ARGS__, f32_linear_fma_xw_x0000_000x0 , SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0x9, 0x01040, 0xbefbe, 0xbfffe) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_xxx0x_xxx0x_xxx0x_000x0, SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0xf, 0x00000, 0xba108, 0xba108) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_XXX0x_XXX0x_XXX0x_000x0, SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0xf, 0x00000, 0xba108, 0xbbdef) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_X0X0x_XXX0x_XX00x_000x0, SWS_PIXEL_F32, SWS_UOP_LINEAR_FMA , 0xf, 0x00000, 0xbb10a, 0xbbdef) \ @@ -1152,6 +1285,10 @@ MACRO(__VA_ARGS__, f32_linear_fma_x_xxX00 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x1, .par.lin.one = 0x41040, .par.lin.zero = 0xbefb8, .par.lin.exact = 0xffffc) \ MACRO(__VA_ARGS__, f32_linear_fma_x_xXX00 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x1, .par.lin.one = 0x41040, .par.lin.zero = 0xbefb8, .par.lin.exact = 0xffffe) \ MACRO(__VA_ARGS__, f32_linear_fma_y_0x000 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x2, .par.lin.one = 0x41001, .par.lin.zero = 0xbefbe, .par.lin.exact = 0xfffbf) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_xxx0x_xxx0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x6, .par.lin.one = 0x40001, .par.lin.zero = 0xba11e, .par.lin.exact = 0xfa11f) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_XxX0x_XXX0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x6, .par.lin.one = 0x40001, .par.lin.zero = 0xba11e, .par.lin.exact = 0xfbdbf) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_XXX0x_XXX0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x6, .par.lin.one = 0x40001, .par.lin.zero = 0xba11e, .par.lin.exact = 0xfbdff) \ + MACRO(__VA_ARGS__, f32_linear_fma_yz_0X00x_00X0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x6, .par.lin.one = 0x40001, .par.lin.zero = 0xbadbe, .par.lin.exact = 0xfbdff) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_xxx0x_xxx0x_xxx0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xba108, .par.lin.exact = 0xfa108) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_XXX0x_XxX0x_XXX0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xba108, .par.lin.exact = 0xfbdaf) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_XXX0x_XXX0x_XXX0x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xba108, .par.lin.exact = 0xfbdef) \ @@ -1163,20 +1300,31 @@ MACRO(__VA_ARGS__, f32_linear_fma_xyz_x0000_0x000_00x00 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x7, .par.lin.one = 0x40000, .par.lin.zero = 0xbefbe, .par.lin.exact = 0xfefbe) \ MACRO(__VA_ARGS__, f32_linear_fma_xyz_10X0x_1XX0x_1X00x , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x7, .par.lin.one = 0x40421, .par.lin.zero = 0xbb10a, .par.lin.exact = 0xfbdef) \ MACRO(__VA_ARGS__, f32_linear_fma_w_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x8, .par.lin.one = 0x1041, .par.lin.zero = 0xbefbe, .par.lin.exact = 0xbffff) \ + MACRO(__VA_ARGS__, f32_linear_fma_xw_xxx0x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefa8, .par.lin.exact = 0xbffe8) \ + MACRO(__VA_ARGS__, f32_linear_fma_xw_XXX0x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefa8, .par.lin.exact = 0xbffef) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_x000x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefae, .par.lin.exact = 0xbffee) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_X000x_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefae, .par.lin.exact = 0xbffef) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_xxx00_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefb8, .par.lin.exact = 0xbfff8) \ MACRO(__VA_ARGS__, f32_linear_fma_xw_xXX00_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefb8, .par.lin.exact = 0xbfffe) \ + MACRO(__VA_ARGS__, f32_linear_fma_xw_x0000_000x0 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0x9, .par.lin.one = 0x1040, .par.lin.zero = 0xbefbe, .par.lin.exact = 0xbfffe) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_xxx0x_xxx0x_xxx0x_000x0, .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xba108, .par.lin.exact = 0xba108) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_XXX0x_XXX0x_XXX0x_000x0, .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xba108, .par.lin.exact = 0xbbdef) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_X0X0x_XXX0x_XX00x_000x0, .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xbb10a, .par.lin.exact = 0xbbdef) \ MACRO(__VA_ARGS__, f32_linear_fma_xyzw_x0000_0x000_00x00_000x0, .type = SWS_PIXEL_F32, .uop = SWS_UOP_LINEAR_FMA , .mask = 0xf, .par.lin.one = 0x0, .par.lin.zero = 0xbefbe, .par.lin.exact = 0xbefbe) #define SWS_FOR_F32_DITHER(MACRO, ...) \ MACRO(__VA_ARGS__, f32_dither_x_0_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x1, 0, 0, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_x_5_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x1, 5, 0, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_y_0_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x2, 0, 0, 0, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_y_3_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x2, 0, 3, 0, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_xy_0_3_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x3, 0, 3, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_xy_0_5_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x3, 0, 5, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_xy_2_3_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x3, 2, 3, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_xy_3_2_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x3, 3, 2, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_xy_5_0_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x3, 5, 0, 0, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_z_0_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x4, 0, 0, 0, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_z_2_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x4, 0, 0, 2, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_xz_0_2_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x5, 0, 0, 2, 0, 4) \ + MACRO(__VA_ARGS__, f32_dither_yz_3_2_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x6, 0, 3, 2, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_xyz_0_0_0_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x7, 0, 0, 0, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_xyz_0_3_2_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x7, 0, 3, 2, 0, 4) \ MACRO(__VA_ARGS__, f32_dither_xyz_2_0_3_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0x7, 2, 0, 3, 0, 4) \ @@ -1198,10 +1346,18 @@ MACRO(__VA_ARGS__, f32_dither_xyzw_5_2_3_0_16x16 , SWS_PIXEL_F32, SWS_UOP_DITHER , 0xf, 5, 2, 3, 0, 4) #define SWS_FOR_STRUCT_F32_DITHER(MACRO, ...) \ MACRO(__VA_ARGS__, f32_dither_x_0_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x1, .par.dither = { .y_offset = {0, 0, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_x_5_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x1, .par.dither = { .y_offset = {5, 0, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_y_0_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x2, .par.dither = { .y_offset = {0, 0, 0, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_y_3_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x2, .par.dither = { .y_offset = {0, 3, 0, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xy_0_3_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x3, .par.dither = { .y_offset = {0, 3, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_xy_0_5_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x3, .par.dither = { .y_offset = {0, 5, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_xy_2_3_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x3, .par.dither = { .y_offset = {2, 3, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_xy_3_2_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x3, .par.dither = { .y_offset = {3, 2, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_xy_5_0_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x3, .par.dither = { .y_offset = {5, 0, 0, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_z_0_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x4, .par.dither = { .y_offset = {0, 0, 0, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_z_2_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x4, .par.dither = { .y_offset = {0, 0, 2, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xz_0_2_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x5, .par.dither = { .y_offset = {0, 0, 2, 0}, .size_log2 = 4 }) \ + MACRO(__VA_ARGS__, f32_dither_yz_3_2_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x6, .par.dither = { .y_offset = {0, 3, 2, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xyz_0_0_0_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x7, .par.dither = { .y_offset = {0, 0, 0, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xyz_0_3_2_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x7, .par.dither = { .y_offset = {0, 3, 2, 0}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xyz_2_0_3_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0x7, .par.dither = { .y_offset = {2, 0, 3, 0}, .size_log2 = 4 }) \ -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]