PR #22652 opened by Niklas Haas (haasn)
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22652
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22652.patch

Switches to marking unused outputs as SWS_COMP_GARBAGE and checking for that 
instead of `next->comps.unused[]`.


>From 959f2387787ebe77c8fd5dc2a64d6d2eccc9cd87 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 14:57:40 +0100
Subject: [PATCH 01/10] swscale/ops: simplify SwsOpList.order_src/dst

Just define these directly as integer arrays; there's really no point in
having them re-use SwsSwizzleOp; the only place this was ever even remotely
relevant was in the no-op check, which any decent compiler should already
be capable of optimizing into a single 32-bit comparison.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c           | 32 +++++++++++++++++++++-----------
 libswscale/ops.h           |  6 +++---
 libswscale/ops_dispatch.c  |  4 ++--
 libswscale/ops_optimizer.c | 12 ++++++------
 libswscale/vulkan/ops.c    |  8 ++++----
 5 files changed, 36 insertions(+), 26 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 88bd289158..7976baeabd 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -323,7 +323,7 @@ void ff_sws_op_list_update_comps(SwsOpList *ops)
             /* Active components are taken from the user-provided values,
              * other components are explicitly stripped */
             for (int i = 0; i < op->rw.elems; i++) {
-                const int idx = op->rw.packed ? i : ops->order_src.in[i];
+                const int idx = op->rw.packed ? i : ops->plane_src[i];
                 op->comps.flags[i] = ops->comps_src.flags[idx];
                 op->comps.min[i]   = ops->comps_src.min[idx];
                 op->comps.max[i]   = ops->comps_src.max[idx];
@@ -566,7 +566,8 @@ SwsOpList *ff_sws_op_list_alloc(void)
     if (!ops)
         return NULL;
 
-    ops->order_src = ops->order_dst = SWS_SWIZZLE(0, 1, 2, 3);
+    for (int i = 0; i < 4; i++)
+        ops->plane_src[i] = ops->plane_dst[i] = i;
     ff_fmt_clear(&ops->src);
     ff_fmt_clear(&ops->dst);
     return ops;
@@ -693,7 +694,7 @@ bool ff_sws_op_list_is_noop(const SwsOpList *ops)
      */
     const int num_planes = read->rw.packed ? 1 : read->rw.elems;
     for (int i = 0; i < num_planes; i++) {
-        if (ops->order_src.in[i] != ops->order_dst.in[i])
+        if (ops->plane_src[i] != ops->plane_dst[i])
             return false;
     }
 
@@ -899,6 +900,20 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const 
bool unused[4])
     }
 }
 
+static void desc_plane_order(AVBPrint *bp, int nb_planes, const uint8_t *order)
+{
+    bool inorder = true;
+    for (int i = 0; i < nb_planes; i++)
+        inorder &= order[i] == i;
+    if (inorder)
+        return;
+
+    av_bprintf(bp, ", via {");
+    for (int i = 0; i < nb_planes; i++)
+        av_bprintf(bp, "%s%d", i ? ", " : "", order[i]);
+    av_bprintf(bp, "}");
+}
+
 void ff_sws_op_list_print(void *log, int lev, int lev_extra,
                           const SwsOpList *ops)
 {
@@ -928,14 +943,9 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
         ff_sws_op_desc(&bp, op, next->comps.unused);
 
         if (op->op == SWS_OP_READ || op->op == SWS_OP_WRITE) {
-            SwsSwizzleOp order = op->op == SWS_OP_READ ? ops->order_src : 
ops->order_dst;
-            if (order.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) {
-                const int planes = op->rw.packed ? 1 : op->rw.elems;
-                av_bprintf(&bp, ", via {");
-                for (int i = 0; i < planes; i++)
-                    av_bprintf(&bp, "%s%d", i ? ", " : "", order.in[i]);
-                av_bprintf(&bp, "}");
-            }
+            const int planes = op->rw.packed ? 1 : op->rw.elems;
+            desc_plane_order(&bp, planes,
+                op->op == SWS_OP_READ ? ops->plane_src : ops->plane_dst);
         }
 
         av_assert0(av_bprint_is_complete(&bp));
diff --git a/libswscale/ops.h b/libswscale/ops.h
index 827ec04094..a6bbceced4 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -255,13 +255,13 @@ typedef struct SwsOpList {
     /* Metadata associated with this operation list */
     SwsFormat src, dst;
 
-    /* Input/output plane pointer swizzle mask */
-    SwsSwizzleOp order_src, order_dst;
+    /* Input/output plane indices */
+    uint8_t plane_src[4], plane_dst[4];
 
     /**
      * Source component metadata associated with pixel values from each
      * corresponding component (in plane/memory order, i.e. not affected by
-     * `order_src`). Lets the optimizer know additional information about
+     * `plane_src`). Lets the optimizer know additional information about
      * the value range and/or pixel data to expect.
      *
      * The default value of {0} is safe to pass in the case that no additional
diff --git a/libswscale/ops_dispatch.c b/libswscale/ops_dispatch.c
index c1893f3db8..f842196265 100644
--- a/libswscale/ops_dispatch.c
+++ b/libswscale/ops_dispatch.c
@@ -418,8 +418,8 @@ static int compile(SwsGraph *graph, const SwsOpList *ops, 
SwsPass *input,
     };
 
     for (int i = 0; i < 4; i++) {
-        p->idx_in[i]  = i < p->planes_in  ? ops->order_src.in[i] : -1;
-        p->idx_out[i] = i < p->planes_out ? ops->order_dst.in[i] : -1;
+        p->idx_in[i]  = i < p->planes_in  ? ops->plane_src[i] : -1;
+        p->idx_out[i] = i < p->planes_out ? ops->plane_dst[i] : -1;
     }
 
     const SwsFilterWeights *filter = read->rw.kernel;
diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c
index 542df8e956..c364d3efc6 100644
--- a/libswscale/ops_optimizer.c
+++ b/libswscale/ops_optimizer.c
@@ -385,7 +385,7 @@ retry:
 
                     const int idx = nb_planes++;
                     av_assert1(idx <= i);
-                    ops->order_src.in[idx] = ops->order_src.in[i];
+                    ops->plane_src[idx] = ops->plane_src[i];
                     swiz.in[i] = idx;
                 }
 
@@ -501,7 +501,7 @@ retry:
                 for (int dst = 0; dst < prev->rw.elems; dst++) {
                     const int src = op->swizzle.in[dst];
                     if (src > dst && src < prev->rw.elems) {
-                        FFSWAP(int, ops->order_src.in[dst], 
ops->order_src.in[src]);
+                        FFSWAP(int, ops->plane_src[dst], ops->plane_src[src]);
                         for (int i = dst; i < 4; i++) {
                             if (op->swizzle.in[i] == dst)
                                 op->swizzle.in[i] = src;
@@ -517,7 +517,7 @@ retry:
                 for (int dst = 0; dst < next->rw.elems; dst++) {
                     const int src = op->swizzle.in[dst];
                     if (src > dst && src < next->rw.elems) {
-                        FFSWAP(int, ops->order_dst.in[dst], 
ops->order_dst.in[src]);
+                        FFSWAP(int, ops->plane_dst[dst], ops->plane_dst[src]);
                         FFSWAP(int, op->swizzle.in[dst], op->swizzle.in[src]);
                         goto retry;
                     }
@@ -963,15 +963,15 @@ int ff_sws_op_list_subpass(SwsOpList *ops1, SwsOpList 
**out_rest)
 
     /* Determine metadata for the intermediate format */
     const SwsPixelType type = op->type;
-    ops2->order_src = SWS_SWIZZLE(0, 1, 2, 3);
     ops2->comps_src = prev->comps;
     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);
-
-    ops1->order_dst = SWS_SWIZZLE(0, 1, 2, 3);
     ops1->dst = ops2->src;
 
+    for (int i = 0; i < nb_planes; i++)
+        ops1->plane_dst[i] = ops2->plane_src[i] = i;
+
     ff_sws_op_list_remove_at(ops1, idx, ops1->num_ops - idx);
     ff_sws_op_list_remove_at(ops2, 0, idx);
     op = NULL; /* the above command may invalidate op */
diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c
index f04a0800d2..4d684116ca 100644
--- a/libswscale/vulkan/ops.c
+++ b/libswscale/vulkan/ops.c
@@ -245,11 +245,11 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s,
                 return AVERROR(ENOTSUP);
             } else if (op->rw.packed) {
                 GLSLF(1, %s = %s(imageLoad(src_img[%i], pos));                 
,
-                      type_name, type_v, ops->order_src.in[0]);
+                      type_name, type_v, ops->plane_src[0]);
             } else {
                 for (int i = 0; i < op->rw.elems; i++)
                     GLSLF(1, %s.%c = %s(imageLoad(src_img[%i], pos)[0]);       
,
-                          type_name, "xyzw"[i], type_s, ops->order_src.in[i]);
+                          type_name, "xyzw"[i], type_s, ops->plane_src[i]);
             }
             break;
         }
@@ -258,11 +258,11 @@ static int add_ops_glsl(VulkanPriv *p, FFVulkanOpsCtx *s,
                 return AVERROR(ENOTSUP);
             } else if (op->rw.packed) {
                 GLSLF(1, imageStore(dst_img[%i], pos, %s(%s));                 
 ,
-                      ops->order_dst.in[0], type_v, type_name);
+                      ops->plane_dst[0], type_v, type_name);
             } else {
                 for (int i = 0; i < op->rw.elems; i++)
                     GLSLF(1, imageStore(dst_img[%i], pos, %s(%s[%i]));         
,
-                          ops->order_dst.in[i], type_v, type_name, i);
+                          ops->plane_dst[i], type_v, type_name, i);
             }
             break;
         }
-- 
2.52.0


>From bc6c2f49df9352045a89e74de7a96b3fc07cb4d9 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 16:04:24 +0100
Subject: [PATCH 02/10] swscale/ops: slightly refactor unused[] computation

Needed for the upcoming removal of op->comps.unused[]. This keeps the
dependency array entirely within the ff_sws_op_list_update_comps() function,
apart from being arguably simpler and easier to follow.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 56 ++++++++++++++++++------------------------------
 1 file changed, 21 insertions(+), 35 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 7976baeabd..09e97c8120 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -293,7 +293,6 @@ static void apply_filter_weights(SwsComps *comps, const 
SwsComps *prev,
 /* Infer + propagate known information about components */
 void ff_sws_op_list_update_comps(SwsOpList *ops)
 {
-    SwsComps next = { .unused = {true, true, true, true} };
     SwsComps prev = { .flags = {
         SWS_COMP_GARBAGE, SWS_COMP_GARBAGE, SWS_COMP_GARBAGE, SWS_COMP_GARBAGE,
     }};
@@ -468,16 +467,18 @@ void ff_sws_op_list_update_comps(SwsOpList *ops)
     }
 
     /* Backwards pass, solves for component dependencies */
+    bool need_out[4] = { false, false, false, false };
     for (int n = ops->num_ops - 1; n >= 0; n--) {
         SwsOp *op = &ops->ops[n];
+        bool need_in[4] = { false, false, false, false };
 
         switch (op->op) {
         case SWS_OP_READ:
         case SWS_OP_WRITE:
             for (int i = 0; i < op->rw.elems; i++)
-                op->comps.unused[i] = op->op == SWS_OP_READ;
+                need_in[i] = op->op == SWS_OP_WRITE;
             for (int i = op->rw.elems; i < 4; i++)
-                op->comps.unused[i] = next.unused[i];
+                need_in[i] = need_out[i];
             break;
         case SWS_OP_SWAP_BYTES:
         case SWS_OP_LSHIFT:
@@ -490,55 +491,40 @@ void ff_sws_op_list_update_comps(SwsOpList *ops)
         case SWS_OP_FILTER_H:
         case SWS_OP_FILTER_V:
             for (int i = 0; i < 4; i++)
-                op->comps.unused[i] = next.unused[i];
+                need_in[i] = need_out[i];
             break;
-        case SWS_OP_UNPACK: {
-            bool unused = true;
-            for (int i = 0; i < 4; i++) {
-                if (op->pack.pattern[i])
-                    unused &= next.unused[i];
-                op->comps.unused[i] = i > 0;
-            }
-            op->comps.unused[0] = unused;
+        case SWS_OP_UNPACK:
+            for (int i = 0; i < 4 && op->pack.pattern[i]; i++)
+                need_in[0] |= need_out[i];
             break;
-        }
         case SWS_OP_PACK:
-            for (int i = 0; i < 4; i++) {
-                if (op->pack.pattern[i])
-                    op->comps.unused[i] = next.unused[0];
-                else
-                    op->comps.unused[i] = true;
-            }
+            for (int i = 0; i < 4 && op->pack.pattern[i]; i++)
+                need_in[i] = need_out[0];
             break;
         case SWS_OP_CLEAR:
             for (int i = 0; i < 4; i++) {
-                if (op->c.q4[i].den)
-                    op->comps.unused[i] = true;
-                else
-                    op->comps.unused[i] = next.unused[i];
+                if (!op->c.q4[i].den)
+                    need_in[i] = need_out[i];
             }
             break;
-        case SWS_OP_SWIZZLE: {
-            bool unused[4] = { true, true, true, true };
+        case SWS_OP_SWIZZLE:
             for (int i = 0; i < 4; i++)
-                unused[op->swizzle.in[i]] &= next.unused[i];
-            for (int i = 0; i < 4; i++)
-                op->comps.unused[i] = unused[i];
+                need_in[op->swizzle.in[i]] |= need_out[i];
             break;
-        }
         case SWS_OP_LINEAR:
-            for (int j = 0; j < 4; j++) {
-                bool unused = true;
-                for (int i = 0; i < 4; i++) {
+            for (int i = 0; i < 4; i++) {
+                for (int j = 0; j < 4; j++) {
                     if (op->lin.m[i][j].num)
-                        unused &= next.unused[i];
+                        need_in[j] |= need_out[i];
                 }
-                op->comps.unused[j] = unused;
             }
             break;
         }
 
-        next = op->comps;
+        for (int i = 0; i < 4; i++) {
+            need_out[i] = need_in[i];
+            op->comps.unused[i] = !need_in[i];
+        }
     }
 }
 
-- 
2.52.0


>From 4cefc1673a5651cbbb56d98f18b9e9dfa8961e7c Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 16:24:51 +0100
Subject: [PATCH 03/10] swscale/ops: mark all unused components as GARBAGE

This only affects the print-out of the SWS_OP_WRITE at the end of every op,
list, because the ops list print-out was otherwise already checking the unused
mask.

 rgb24 -> bgr24:
   [ u8 XXXX -> +++X] SWS_OP_READ         : 3 elem(s) packed >> 0
     min: {0 0 0 _}, max: {255 255 255 _}
   [ u8 ...X -> +++X] SWS_OP_SWIZZLE      : 2103
     min: {0 0 0 _}, max: {255 255 255 _}
-  [ u8 ...X -> +++X] SWS_OP_WRITE        : 3 elem(s) packed >> 0
+  [ u8 ...X -> XXXX] SWS_OP_WRITE        : 3 elem(s) packed >> 0
     min: {0 0 0 _}, max: {255 255 255 _}
     (X = unused, z = byteswapped, + = exact, 0 = zero)

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 5 +++++
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 09e97c8120..1220f70c42 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -472,6 +472,11 @@ void ff_sws_op_list_update_comps(SwsOpList *ops)
         SwsOp *op = &ops->ops[n];
         bool need_in[4] = { false, false, false, false };
 
+        for (int i = 0; i < 4; i++) {
+            if (!need_out[i])
+                op->comps.flags[i] = SWS_COMP_GARBAGE;
+        }
+
         switch (op->op) {
         case SWS_OP_READ:
         case SWS_OP_WRITE:
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index 4385d809e7..cafa68665d 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-e5a6788fa43852d75544e7fb6ae7744d
+da5cdcd09fffb274b454a64f1d95b073
-- 
2.52.0


>From 08951549d8ec7e13fd71e658fbc9e72f93dd3a76 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 16:31:58 +0100
Subject: [PATCH 04/10] swscale/ops: strip value range from garbage components

Just removes the unnecessary value range after the WRITE, as a result of
the previous change and the fact that we already skipped printing these for
unused components.

 rgb24 -> bgr24:
   [ u8 XXXX -> +++X] SWS_OP_READ         : 3 elem(s) packed >> 0
     min: {0 0 0 _}, max: {255 255 255 _}
   [ u8 ...X -> +++X] SWS_OP_SWIZZLE      : 2103
     min: {0 0 0 _}, max: {255 255 255 _}
   [ u8 ...X -> XXXX] SWS_OP_WRITE        : 3 elem(s) packed >> 0
-    min: {0 0 0 _}, max: {255 255 255 _}
     (X = unused, z = byteswapped, + = exact, 0 = zero)

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 4 +++-
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 1220f70c42..56fd1f81ec 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -473,8 +473,10 @@ void ff_sws_op_list_update_comps(SwsOpList *ops)
         bool need_in[4] = { false, false, false, false };
 
         for (int i = 0; i < 4; i++) {
-            if (!need_out[i])
+            if (!need_out[i]) {
                 op->comps.flags[i] = SWS_COMP_GARBAGE;
+                op->comps.min[i] = op->comps.max[i] = (AVRational) {0};
+            }
         }
 
         switch (op->op) {
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index cafa68665d..48b038b6e5 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-da5cdcd09fffb274b454a64f1d95b073
+3fcc132b8a56c71f099055ce4ff410f6
-- 
2.52.0


>From 27105d0178d654edf71706e83670ebad88f2ea33 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 16:33:38 +0100
Subject: [PATCH 05/10] swscale/ops: test for SWS_COMP_GARBAGE instead of
 next->comps.unused

When printing/describing operations.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c           | 18 +++++++++---------
 libswscale/ops.h           |  2 +-
 libswscale/tests/sws_ops.c |  3 +--
 3 files changed, 11 insertions(+), 12 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 56fd1f81ec..93d6350864 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -788,13 +788,13 @@ static void print_q(AVBPrint *bp, const AVRational q, 
bool ignore_den0)
 }
 
 static void print_q4(AVBPrint *bp, const AVRational q4[4], bool ignore_den0,
-                     const bool unused[4])
+                     const SwsCompFlags flags[4])
 {
     av_bprintf(bp, "{");
     for (int i = 0; i < 4; i++) {
         if (i)
             av_bprintf(bp, " ");
-        if (unused && unused[i]) {
+        if (flags[i] & SWS_COMP_GARBAGE) {
             av_bprintf(bp, "_");
         } else {
             print_q(bp, q4[i], ignore_den0);
@@ -803,7 +803,7 @@ static void print_q4(AVBPrint *bp, const AVRational q4[4], 
bool ignore_den0,
     av_bprintf(bp, "}");
 }
 
-void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4])
+void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op)
 {
     const char *name  = ff_sws_op_type_name(op->op);
 
@@ -838,7 +838,7 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const 
bool unused[4])
         break;
     case SWS_OP_CLEAR:
         av_bprintf(bp, "%-20s: ", name);
-        print_q4(bp, op->c.q4, true, unused);
+        print_q4(bp, op->c.q4, true, op->comps.flags);
         break;
     case SWS_OP_SWIZZLE:
         av_bprintf(bp, "%-20s: %d%d%d%d", name,
@@ -858,11 +858,11 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const 
bool unused[4])
         break;
     case SWS_OP_MIN:
         av_bprintf(bp, "%-20s: x <= ", name);
-        print_q4(bp, op->c.q4, true, unused);
+        print_q4(bp, op->c.q4, true, op->comps.flags);
         break;
     case SWS_OP_MAX:
         av_bprintf(bp, "%-20s: ", name);
-        print_q4(bp, op->c.q4, true, unused);
+        print_q4(bp, op->c.q4, true, op->comps.flags);
         av_bprintf(bp, " <= x");
         break;
     case SWS_OP_LINEAR:
@@ -933,7 +933,7 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra,
                    next->comps.unused[2] ? 'X' : 
describe_comp_flags(op->comps.flags[2]),
                    next->comps.unused[3] ? 'X' : 
describe_comp_flags(op->comps.flags[3]));
 
-        ff_sws_op_desc(&bp, op, next->comps.unused);
+        ff_sws_op_desc(&bp, op);
 
         if (op->op == SWS_OP_READ || op->op == SWS_OP_WRITE) {
             const int planes = op->rw.packed ? 1 : op->rw.elems;
@@ -951,9 +951,9 @@ void ff_sws_op_list_print(void *log, int lev, int lev_extra,
         {
             av_bprint_clear(&bp);
             av_bprintf(&bp, "    min: ");
-            print_q4(&bp, op->comps.min, false, next->comps.unused);
+            print_q4(&bp, op->comps.min, false, op->comps.flags);
             av_bprintf(&bp, ", max: ");
-            print_q4(&bp, op->comps.max, false, next->comps.unused);
+            print_q4(&bp, op->comps.max, false, op->comps.flags);
             av_assert0(av_bprint_is_complete(&bp));
             av_log(log, lev_extra, "%s\n", bp.str);
         }
diff --git a/libswscale/ops.h b/libswscale/ops.h
index a6bbceced4..abeaf97d27 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -233,7 +233,7 @@ typedef struct SwsOp {
 /**
  * Describe an operation in human-readable form.
  */
-void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4]);
+void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op);
 
 /**
  * Frees any allocations associated with an SwsOp and sets it to {0}.
diff --git a/libswscale/tests/sws_ops.c b/libswscale/tests/sws_ops.c
index 91b3d8c4b7..02049156e7 100644
--- a/libswscale/tests/sws_ops.c
+++ b/libswscale/tests/sws_ops.c
@@ -50,7 +50,6 @@ static int cmp_str(const void *a, const void *b)
     return strcmp(a, b);
 }
 
-static const bool unused[4] = { false, false, false, false };
 static int register_op(SwsContext *ctx, void *opaque, SwsOp *op)
 {
     struct AVTreeNode **root = opaque;
@@ -80,7 +79,7 @@ static int register_op(SwsContext *ctx, void *opaque, SwsOp 
*op)
     }
 
     av_bprint_init(&bp, 0, AV_BPRINT_SIZE_AUTOMATIC);
-    ff_sws_op_desc(&bp, op, unused);
+    ff_sws_op_desc(&bp, op);
     int ret = av_bprint_finalize(&bp, &desc);
     if (ret < 0)
         return ret;
-- 
2.52.0


>From e042a5ac8c68970bc3e300fee44d8dccde13faba Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 17:07:33 +0100
Subject: [PATCH 06/10] swscale/ops: remove redundant unused mask from ops
 printout

This is now fully redundant with the previous op's output; because unused
components are always marked as garbage on the input side.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 6 +-----
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 93d6350864..d6c9b16bd0 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -922,12 +922,8 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
         const SwsOp *op   = &ops->ops[i];
         const SwsOp *next = i + 1 < ops->num_ops ? &ops->ops[i + 1] : op;
         av_bprint_clear(&bp);
-        av_bprintf(&bp, "  [%3s %c%c%c%c -> %c%c%c%c] ",
+        av_bprintf(&bp, "  [%3s %c%c%c%c] ",
                    ff_sws_pixel_type_name(op->type),
-                   op->comps.unused[0] ? 'X' : '.',
-                   op->comps.unused[1] ? 'X' : '.',
-                   op->comps.unused[2] ? 'X' : '.',
-                   op->comps.unused[3] ? 'X' : '.',
                    next->comps.unused[0] ? 'X' : 
describe_comp_flags(op->comps.flags[0]),
                    next->comps.unused[1] ? 'X' : 
describe_comp_flags(op->comps.flags[1]),
                    next->comps.unused[2] ? 'X' : 
describe_comp_flags(op->comps.flags[2]),
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index 48b038b6e5..159dbbba2a 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-3fcc132b8a56c71f099055ce4ff410f6
+a872c7d917c532ba92397c1410f04388
-- 
2.52.0


>From b5b42ad59353eed420f49bd856bc23c4d29daf68 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 17:08:48 +0100
Subject: [PATCH 07/10] swscale/ops: remove unneeded conditional on
 describe_comp_flags

next->comps.unused[] is redundant with SWS_COMP_GARBAGE now.

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index d6c9b16bd0..0a2a991833 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -920,14 +920,13 @@ void ff_sws_op_list_print(void *log, int lev, int 
lev_extra,
 
     for (int i = 0; i < ops->num_ops; i++) {
         const SwsOp *op   = &ops->ops[i];
-        const SwsOp *next = i + 1 < ops->num_ops ? &ops->ops[i + 1] : op;
         av_bprint_clear(&bp);
         av_bprintf(&bp, "  [%3s %c%c%c%c] ",
                    ff_sws_pixel_type_name(op->type),
-                   next->comps.unused[0] ? 'X' : 
describe_comp_flags(op->comps.flags[0]),
-                   next->comps.unused[1] ? 'X' : 
describe_comp_flags(op->comps.flags[1]),
-                   next->comps.unused[2] ? 'X' : 
describe_comp_flags(op->comps.flags[2]),
-                   next->comps.unused[3] ? 'X' : 
describe_comp_flags(op->comps.flags[3]));
+                   describe_comp_flags(op->comps.flags[0]),
+                   describe_comp_flags(op->comps.flags[1]),
+                   describe_comp_flags(op->comps.flags[2]),
+                   describe_comp_flags(op->comps.flags[3]));
 
         ff_sws_op_desc(&bp, op);
 
-- 
2.52.0


>From 388d8e3a32ef9e951518fa1d7a6e32fa58b3eb75 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 17:49:46 +0100
Subject: [PATCH 08/10] swscale/ops: only print SWS_OP_SCALE denom if not 1

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.c            | 4 +++-
 tests/ref/fate/sws-ops-list | 2 +-
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 0a2a991833..d86d8b0239 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -878,7 +878,9 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op)
         av_bprintf(bp, "]");
         break;
     case SWS_OP_SCALE:
-        av_bprintf(bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den);
+        av_bprintf(bp, "%-20s: * %d", name, op->c.q.num);
+        if (op->c.q.den != 1)
+            av_bprintf(bp, "/%d", op->c.q.den);
         break;
     case SWS_OP_FILTER_H:
     case SWS_OP_FILTER_V: {
diff --git a/tests/ref/fate/sws-ops-list b/tests/ref/fate/sws-ops-list
index 159dbbba2a..f24c95f388 100644
--- a/tests/ref/fate/sws-ops-list
+++ b/tests/ref/fate/sws-ops-list
@@ -1 +1 @@
-a872c7d917c532ba92397c1410f04388
+a2ed0581163448a2c398ee9992e8eaf6
-- 
2.52.0


>From 75bdfacc61877963d6d8fbeb86e47df09bc33f8f Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 17:16:24 +0100
Subject: [PATCH 09/10] swscale/ops_optimizer: check COMP_GARBAGE instead of
 next->comps.unused

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops.h           |  2 ++
 libswscale/ops_optimizer.c | 34 ++++++++++++++++++----------------
 2 files changed, 20 insertions(+), 16 deletions(-)

diff --git a/libswscale/ops.h b/libswscale/ops.h
index abeaf97d27..e3acbd4b18 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -85,6 +85,8 @@ typedef enum SwsCompFlags {
     SWS_COMP_SWAPPED = 1 << 3, /* byte order is swapped */
 } SwsCompFlags;
 
+#define SWS_OP_NEEDED(op, idx) (!((op)->comps.flags[idx] & SWS_COMP_GARBAGE))
+
 typedef union SwsConst {
     /* Generic constant value */
     AVRational q4[4];
diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c
index c364d3efc6..fd3c1ef969 100644
--- a/libswscale/ops_optimizer.c
+++ b/libswscale/ops_optimizer.c
@@ -126,7 +126,7 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next)
     case SWS_OP_MAX: {
         const SwsConst c = next->c;
         for (int i = 0; i < 4; i++) {
-            if (next->comps.unused[i])
+            if (!SWS_OP_NEEDED(op, i))
                 continue;
             const int j = op->swizzle.in[i];
             if (seen[j] && av_cmp_q(next->c.q4[j], c.q4[i]))
@@ -140,7 +140,7 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next)
     case SWS_OP_DITHER: {
         const SwsDitherOp d = next->dither;
         for (int i = 0; i < 4; i++) {
-            if (next->comps.unused[i])
+            if (!SWS_OP_NEEDED(op, i))
                 continue;
             const int j = op->swizzle.in[i];
             if (seen[j] && next->dither.y_offset[j] != d.y_offset[i])
@@ -237,7 +237,7 @@ static int exact_log2_q(const AVRational x)
  * If a linear operation can be reduced to a scalar multiplication, returns
  * the corresponding scaling factor, or 0 otherwise.
  */
-static bool extract_scalar(const SwsLinearOp *c, SwsComps prev, SwsComps next,
+static bool extract_scalar(const SwsLinearOp *c, SwsComps comps, SwsComps prev,
                            SwsConst *out_scale)
 {
     SwsConst scale = {0};
@@ -248,7 +248,8 @@ static bool extract_scalar(const SwsLinearOp *c, SwsComps 
prev, SwsComps next,
 
     for (int i = 0; i < 4; i++) {
         const AVRational s = c->m[i][i];
-        if ((prev.flags[i] & SWS_COMP_ZERO) || next.unused[i])
+        if ((prev.flags[i]  & SWS_COMP_ZERO) ||
+            (comps.flags[i] & SWS_COMP_GARBAGE))
             continue;
         if (scale.q.den && av_cmp_q(s, scale.q))
             return false;
@@ -363,10 +364,11 @@ retry:
         /* common helper variable */
         bool noop = true;
 
-        if (next->comps.unused[0] && next->comps.unused[1] &&
-            next->comps.unused[2] && next->comps.unused[3])
+        if (!SWS_OP_NEEDED(op, 0) && !SWS_OP_NEEDED(op, 1) &&
+            !SWS_OP_NEEDED(op, 2) && !SWS_OP_NEEDED(op, 3) &&
+            op->op != SWS_OP_WRITE)
         {
-            /* Remove completely unused operations */
+            /* Remove any operation whose output is not needed */
             ff_sws_op_list_remove_at(ops, n, 1);
             goto retry;
         }
@@ -378,7 +380,7 @@ retry:
                 SwsSwizzleOp swiz = SWS_SWIZZLE(0, 1, 2, 3);
                 int nb_planes = 0;
                 for (int i = 0; i < op->rw.elems; i++) {
-                    if (next->comps.unused[i]) {
+                    if (!SWS_OP_NEEDED(op, i)) {
                         swiz.in[i] = 3 - (i - nb_planes); /* map to unused 
plane */
                         continue;
                     }
@@ -449,7 +451,7 @@ retry:
                 {
                     /* Redundant clear-to-zero of zero component */
                     op->c.q4[i].den = 0;
-                } else if (next->comps.unused[i]) {
+                } else if (!SWS_OP_NEEDED(op, i)) {
                     /* Unnecessary clear of unused component */
                     op->c.q4[i] = (AVRational) {0, 0};
                 } else if (op->c.q4[i].den) {
@@ -475,7 +477,7 @@ retry:
 
         case SWS_OP_SWIZZLE:
             for (int i = 0; i < 4; i++) {
-                if (next->comps.unused[i])
+                if (!SWS_OP_NEEDED(op, i))
                     continue;
                 if (op->swizzle.in[i] != i)
                     noop = false;
@@ -556,7 +558,7 @@ retry:
 
         case SWS_OP_MIN:
             for (int i = 0; i < 4; i++) {
-                if (next->comps.unused[i] || !op->c.q4[i].den)
+                if (!SWS_OP_NEEDED(op, i) || !op->c.q4[i].den)
                     continue;
                 if (av_cmp_q(op->c.q4[i], prev->comps.max[i]) < 0)
                     noop = false;
@@ -570,7 +572,7 @@ retry:
 
         case SWS_OP_MAX:
             for (int i = 0; i < 4; i++) {
-                if (next->comps.unused[i] || !op->c.q4[i].den)
+                if (!SWS_OP_NEEDED(op, i) || !op->c.q4[i].den)
                     continue;
                 if (av_cmp_q(prev->comps.min[i], op->c.q4[i]) < 0)
                     noop = false;
@@ -586,7 +588,7 @@ retry:
             for (int i = 0; i < 4; i++) {
                 if (op->dither.y_offset[i] < 0)
                     continue;
-                if (next->comps.unused[i] || (prev->comps.flags[i] & 
SWS_COMP_EXACT)) {
+                if (!SWS_OP_NEEDED(op, i) || (prev->comps.flags[i] & 
SWS_COMP_EXACT)) {
                     op->dither.y_offset[i] = -1; /* unnecessary dither */
                     goto retry;
                 } else {
@@ -643,7 +645,7 @@ retry:
             /* Optimize away unused rows */
             for (int i = 0; i < 4; i++) {
                 const uint32_t row = SWS_MASK_ROW(i);
-                if (!next->comps.unused[i] || !(op->lin.mask & row))
+                if (SWS_OP_NEEDED(op, i) || !(op->lin.mask & row))
                     continue;
                 for (int j = 0; j < 5; j++)
                     op->lin.m[i][j] = Q(i == j);
@@ -663,7 +665,7 @@ retry:
             }
 
             /* Multiplication by scalar constant */
-            if (extract_scalar(&op->lin, prev->comps, next->comps, &c)) {
+            if (extract_scalar(&op->lin, op->comps, prev->comps, &c)) {
                 op->op = SWS_OP_SCALE;
                 op->c  = c;
                 goto retry;
@@ -954,7 +956,7 @@ int ff_sws_op_list_subpass(SwsOpList *ops1, SwsOpList 
**out_rest)
     SwsSwizzleOp swiz_wr = SWS_SWIZZLE(0, 1, 2, 3);
     SwsSwizzleOp swiz_rd = SWS_SWIZZLE(0, 1, 2, 3);
     for (int i = 0; i < 4; i++) {
-        if (!op->comps.unused[i]) {
+        if (SWS_OP_NEEDED(prev, i)) {
             const int o = nb_planes++;
             swiz_wr.in[o] = i;
             swiz_rd.in[i] = o;
-- 
2.52.0


>From 5c77815bc0cb4e9832a644c9444d1a94370c99cd Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sat, 28 Mar 2026 20:19:17 +0100
Subject: [PATCH 10/10] swscale/ops_chain: check SWS_COMP_GARBAGE instead of
 next->comps.unused

Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/ops_chain.c | 16 +++++++---------
 1 file changed, 7 insertions(+), 9 deletions(-)

diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c
index 092133426e..7a5706a525 100644
--- a/libswscale/ops_chain.c
+++ b/libswscale/ops_chain.c
@@ -75,11 +75,11 @@ int ff_sws_op_chain_append(SwsOpChain *chain, SwsFuncPtr 
func,
  * For unfiltered SWS_OP_READ/SWS_OP_WRITE, SWS_OP_SWAP_BYTES and
  * SWS_OP_SWIZZLE, the exact type is not checked, just the size.
  *
- * Components set in `next.unused` are ignored when matching. If `flexible`
+ * Components marked SWS_COMP_GARBAGE are ignored when matching. If `flexible`
  * is true, the op body is ignored - only the operation, pixel type, and
  * component masks are checked.
  */
-static int op_match(const SwsOp *op, const SwsOpEntry *entry, const SwsComps 
next)
+static int op_match(const SwsOp *op, const SwsOpEntry *entry)
 {
     int score = 10;
     if (op->op != entry->op)
@@ -115,7 +115,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry 
*entry, const SwsComps nex
     if (op->op == SWS_OP_CLEAR) {
         /* Clear pattern must match exactly, regardless of `entry->flexible` */
         for (int i = 0; i < 4; i++) {
-            if (!next.unused[i] && entry->unused[i] != !!op->c.q4[i].den)
+            if (SWS_OP_NEEDED(op, i) && entry->unused[i] != !!op->c.q4[i].den)
                 return 0;
         }
     }
@@ -147,9 +147,9 @@ static int op_match(const SwsOp *op, const SwsOpEntry 
*entry, const SwsComps nex
         return score;
     case SWS_OP_CLEAR:
         for (int i = 0; i < 4; i++) {
-            if (!op->c.q4[i].den)
+            if (!op->c.q4[i].den || !SWS_OP_NEEDED(op, i))
                 continue;
-            if (av_cmp_q(op->c.q4[i], Q(entry->clear_value)) && 
!next.unused[i])
+            if (av_cmp_q(op->c.q4[i], Q(entry->clear_value)))
                 return 0;
         }
         return score;
@@ -159,7 +159,7 @@ static int op_match(const SwsOp *op, const SwsOpEntry 
*entry, const SwsComps nex
         break;
     case SWS_OP_SWIZZLE:
         for (int i = 0; i < 4; i++) {
-            if (op->swizzle.in[i] != entry->swizzle.in[i] && !next.unused[i])
+            if (SWS_OP_NEEDED(op, i) && op->swizzle.in[i] != 
entry->swizzle.in[i])
                 return 0;
         }
         return score;
@@ -204,8 +204,6 @@ int ff_sws_op_compile_tables(SwsContext *ctx, const 
SwsOpTable *const tables[],
                              int num_tables, SwsOpList *ops, const int 
block_size,
                              SwsOpChain *chain)
 {
-    static const SwsOp dummy = { .comps.unused = { true, true, true, true }};
-    const SwsOp *next = ops->num_ops > 1 ? &ops->ops[1] : &dummy;
     const unsigned cpu_flags = av_get_cpu_flags();
     const SwsOpEntry *best = NULL;
     const SwsOpTable *best_table = NULL;
@@ -226,7 +224,7 @@ int ff_sws_op_compile_tables(SwsContext *ctx, const 
SwsOpTable *const tables[],
         params.table = table;
         for (int i = 0; table->entries[i]; i++) {
             const SwsOpEntry *entry = table->entries[i];
-            int score = op_match(op, entry, next->comps);
+            int score = op_match(op, entry);
             if (score <= best_score)
                 continue;
             if (entry->check && !entry->check(&params))
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to