Commit 25328c55da for ffmpeg

commit 25328c55da48fced6e697e25912c0b4b34d4144b
Author: Niklas Haas <git@haasn.dev>
Date:   Thu Oct 8 13:05:22 2026 +0200

    swscale/ops: split up ff_sws_op_list_update_comps()

    Prerequisite to cleaning this up a bit.

    Signed-off-by: Niklas Haas <git@haasn.dev>

diff --git a/libswscale/ops.c b/libswscale/ops.c
index 7c16b8a4b2..e935ed8f59 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -342,343 +342,350 @@ static void apply_filter_weights(SwsOpPad *pad, const SwsFilterWeights *weights)
     pad->max = ff_add_q64(ff_mul_q64(min, negw), ff_mul_q64(max, posw));
 }

-/* Infer + propagate known information about components */
-void ff_sws_op_list_update_comps(SwsOpList *ops)
+static void update_output_pads(SwsOpList *ops, SwsOp *op,
+                               SwsOpPad **prev_ptr)
 {
-    SwsOpPad prev[4] = {0};
+    SwsOpPad *prev = *prev_ptr;

-    /* Forwards pass, propagates knowledge about the incoming pixel values */
-    for (int n = 0; n < ops->num_ops; n++) {
-        SwsOp *op = &ops->ops[n];
+    SwsCompMask invalid = 0;
+    for (int i = 0; i < 4; i++) {
+        op->outputs[i].type   = SWS_PIXEL_NONE;
+        op->outputs[i].flags  = SWS_COMP_IDENTITY;
+        op->outputs[i].dep_in = SWS_COMP_NONE;
+    }

-        switch (op->op) {
-        case SWS_OP_LINEAR:
-        case SWS_OP_DITHER:
-        case SWS_OP_SWAP_BYTES:
-        case SWS_OP_UNPACK:
-        case SWS_OP_LUT_3D:
-            break; /* special cases, handled below */
-        default: {
-            AVRational64 min[4], max[4];
-            for (int i = 0; i < 4; i++) {
-                min[i] = prev[i].min;
-                max[i] = prev[i].max;
-            }
-            ff_sws_apply_op_q(op, min);
-            ff_sws_apply_op_q(op, max);
-            for (int i = 0; i < 4; i++) {
-                op->outputs[i].min = min[i];
-                op->outputs[i].max = max[i];
-            }
-            break;
-        }
+    switch (op->op) {
+    case SWS_OP_LINEAR:
+    case SWS_OP_DITHER:
+    case SWS_OP_SWAP_BYTES:
+    case SWS_OP_UNPACK:
+    case SWS_OP_LUT_3D:
+        break; /* special cases, handled below */
+    default: {
+        AVRational64 min[4], max[4];
+        for (int i = 0; i < 4; i++) {
+            min[i] = prev[i].min;
+            max[i] = prev[i].max;
         }

-        SwsCompMask invalid = 0;
+        ff_sws_apply_op_q(op, min);
+        ff_sws_apply_op_q(op, max);
         for (int i = 0; i < 4; i++) {
-            op->outputs[i].type   = SWS_PIXEL_NONE;
-            op->outputs[i].flags  = SWS_COMP_IDENTITY;
-            op->outputs[i].dep_in = SWS_COMP_NONE;
+            op->outputs[i].min = min[i];
+            op->outputs[i].max = max[i];
         }
+        break;
+    }
+    }

-        #define FORWARD(I, J, EXPR) do {                                        \
-            SwsOpPad *dst = &op->outputs[I];                                    \
-            const SwsOpPad *src = &prev[J];                                     \
-            SwsCompFlags flags = src->flags;                                    \
-            if (!src->type || dst->type && dst->type != src->type)              \
-                invalid |= SWS_COMP(I);                                         \
-            dst->flags = merge_comp_flags(dst->flags, (EXPR));                  \
-            dst->dep_in |= src->dep_in;                                         \
-            dst->type = src->type;                                              \
-        } while (0)
-
-        #define RESET(I) do {                                                   \
-            SwsOpPad *dst = &op->outputs[I];                                    \
-            dst->type = SWS_PIXEL_NONE;                                         \
-            dst->flags = 0;                                                     \
-            dst->min = dst->max = (AVRational64) {0};                           \
-            dst->dep_in = SWS_COMP_NONE;                                        \
-        } while (0)
+#define FORWARD(I, J, EXPR) do {                                                \
+    SwsOpPad *dst = &op->outputs[I];                                            \
+    const SwsOpPad *src = &prev[J];                                             \
+    SwsCompFlags flags = src->flags;                                            \
+    if (!src->type || dst->type && dst->type != src->type)                      \
+        invalid |= SWS_COMP(I);                                                 \
+    dst->flags = merge_comp_flags(dst->flags, (EXPR));                          \
+    dst->dep_in |= src->dep_in;                                                 \
+    dst->type = src->type;                                                      \
+} while (0)
+
+#define RESET(I) do {                                                           \
+    SwsOpPad *dst = &op->outputs[I];                                            \
+    dst->type = SWS_PIXEL_NONE;                                                 \
+    dst->flags = 0;                                                             \
+    dst->min = dst->max = (AVRational64) {0};                                   \
+    dst->dep_in = SWS_COMP_NONE;                                                \
+} while (0)

-        switch (op->op) {
-        case SWS_OP_READ:
-            /* Active components are taken from the user-provided values,
-             * other components are explicitly stripped */
-            for (int i = 0; i < op->rw.elems; i++) {
-                int idx = 0;
-                switch (op->rw.mode) {
-                case SWS_RW_PALETTE: idx = i; break;
-                case SWS_RW_PACKED:  idx = i; break;
-                case SWS_RW_PLANAR:  idx = ops->plane_src[i]; break;
-                }
-
-                const SwsOpPad *src = &ops->inputs[idx];
-                SwsOpPad *dst = &op->outputs[i];
-                dst->type   = op->rw.type;
-                dst->flags  = src->flags & SWS_COMP_DIRTY;
-                dst->min    = src->min;
-                dst->max    = src->max;
-                dst->dep_in = SWS_COMP(i);
-
-                if (op->rw.filter.op) {
-                    apply_filter_weights(dst, op->rw.filter.kernel);
-                    dst->type = op->rw.filter.type;
-                } else if (op->rw.mode == SWS_RW_PLANAR && !op->rw.frac) {
-                    /* 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 no-op, which is covered by the no-op check. */
-                    dst->flags |= SWS_COMP_COPY;
-                }
+    switch (op->op) {
+    case SWS_OP_READ:
+        /* Active components are taken from the user-provided values,
+         * other components are explicitly stripped */
+        for (int i = 0; i < op->rw.elems; i++) {
+            int idx = 0;
+            switch (op->rw.mode) {
+            case SWS_RW_PALETTE: idx = i; break;
+            case SWS_RW_PACKED:  idx = i; break;
+            case SWS_RW_PLANAR:  idx = ops->plane_src[i]; break;
             }

-            /* Forward unused/untouched components */
-            for (int i = op->rw.elems; i < 4; i++)
-                FORWARD(i, i, flags);
-            break;
-        case SWS_OP_SWAP_BYTES:
-            for (int i = 0; i < 4; i++) {
-                FORWARD(i, i, (flags ^ SWS_COMP_SWAPPED) & SWS_COMP_DIRTY);
-                op->outputs[i].min = prev[i].min;
-                op->outputs[i].max = prev[i].max;
+            SwsOpPad *src = &ops->inputs[idx];
+            SwsOpPad *dst = &op->outputs[i];
+            dst->type   = op->rw.type;
+            dst->flags  = src->flags & SWS_COMP_DIRTY;
+            dst->min    = src->min;
+            dst->max    = src->max;
+            dst->dep_in = SWS_COMP(i);
+
+            if (op->rw.filter.op) {
+                apply_filter_weights(dst, op->rw.filter.kernel);
+                dst->type = op->rw.filter.type;
+            } else if (op->rw.mode == SWS_RW_PLANAR && !op->rw.frac) {
+                /* 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 no-op, which is covered by the no-op check. */
+                dst->flags |= SWS_COMP_COPY;
             }
-            break;
-        case SWS_OP_WRITE:
-            for (int i = 0; i < op->rw.elems; i++)
-                av_assert1(prev[i].type == op->rw.type);
-            for (int i = 0; i < 4; i++)
-                FORWARD(i, i, flags);
-            break;
-        case SWS_OP_LSHIFT:
-        case SWS_OP_RSHIFT:
-            for (int i = 0; i < 4; i++)
-                FORWARD(i, i, flags & SWS_COMP_DIRTY);
-            break;
-        case SWS_OP_MIN:
-        case SWS_OP_MAX: {
-            for (int i = 0; i < 4; i++) {
-                SwsOpPad *dst = &op->outputs[i];
-                FORWARD(i, i, flags);
-                if (op->clamp.limit[i].den)
-                    dst->flags &= SWS_COMP_DIRTY;
+        }

-                /* Reset undefined bounds to known range */
-                AVRational64 *bound = op->op == SWS_OP_MIN ? &dst->max : &dst->min;
-                if (!bound->den)
-                    *bound = op->clamp.limit[i];
-            }
-            break;
+        /* Forward unused/untouched components */
+        for (int i = op->rw.elems; i < 4; i++)
+            FORWARD(i, i, flags);
+        break;
+    case SWS_OP_SWAP_BYTES:
+        for (int i = 0; i < 4; i++) {
+            FORWARD(i, i, (flags ^ SWS_COMP_SWAPPED) & SWS_COMP_DIRTY);
+            op->outputs[i].min = prev[i].min;
+            op->outputs[i].max = prev[i].max;
         }
-        case SWS_OP_DITHER:
-            for (int i = 0; i < 4; i++) {
-                FORWARD(i, i, flags);
-                op->outputs[i].min = prev[i].min;
-                op->outputs[i].max = prev[i].max;
-                if (op->dither.y_offset[i] < 0)
-                    continue;
-                /* Strip zero flag because of the nonzero dithering offset */
-                op->outputs[i].flags &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY;
-                op->outputs[i].min = ff_add_q64(op->outputs[i].min, op->dither.min);
-                op->outputs[i].max = ff_add_q64(op->outputs[i].max, op->dither.max);
-            }
-            break;
-        case SWS_OP_UNPACK:
-            for (int i = 0; i < 4; i++) {
-                const int pattern = op->pack.pattern[i];
-                if (pattern) {
-                    av_assert1(pattern < 32);
-                    FORWARD(i, 0, flags & SWS_COMP_DIRTY);
-                    op->outputs[i].min = Q(0);
-                    op->outputs[i].max = Q((1ULL << pattern) - 1);
-                } else
-                    RESET(i);
-            }
-            break;
-        case SWS_OP_PACK:
-            for (int i = 0; i < 4; i++) {
-                if (op->pack.pattern[i])
-                    FORWARD(0, i, flags & SWS_COMP_DIRTY);
-                if (i > 0) /* clear remaining comps for sanity */
-                    RESET(i);
-            }
-            break;
-        case SWS_OP_CLEAR:
-            for (int i = 0; i < 4; i++) {
-                if (SWS_COMP_TEST(op->clear.mask, i)) {
-                    op->outputs[i].type = op->clear.type[i];
-                    op->outputs[i].flags = SWS_COMP_CONST;
-                    if (op->clear.value[i].num == 0)
-                        op->outputs[i].flags |= SWS_COMP_ZERO;
-                    if (op->clear.value[i].den == 1)
-                        op->outputs[i].flags |= SWS_COMP_EXACT;
-                } else {
-                    FORWARD(i, i, flags);
-                }
-            }
-            break;
-        case SWS_OP_SWIZZLE:
-            for (int i = 0; i < 4; i++)
-                FORWARD(i, op->swizzle.in[i], flags);
-            break;
-        case SWS_OP_CONVERT:
-            for (int i = 0; i < 4; i++) {
-                FORWARD(i, i, flags);
-                op->outputs[i].type = op->convert.to;
-                if (!(prev[i].flags & SWS_COMP_EXACT))
-                    op->outputs[i].flags &= SWS_COMP_DIRTY;
-                if (ff_sws_pixel_type_is_int(op->convert.to))
+        break;
+    case SWS_OP_WRITE:
+        av_assert0(op->rw.elems <= 4);
+        for (int i = 0; i < op->rw.elems; i++)
+            av_assert1(prev[i].type == op->rw.type);
+        for (int i = 0; i < 4; i++)
+            FORWARD(i, i, flags);
+        break;
+    case SWS_OP_LSHIFT:
+    case SWS_OP_RSHIFT:
+        for (int i = 0; i < 4; i++)
+            FORWARD(i, i, flags & SWS_COMP_DIRTY);
+        break;
+    case SWS_OP_MIN:
+    case SWS_OP_MAX: {
+        for (int i = 0; i < 4; i++) {
+            SwsOpPad *dst = &op->outputs[i];
+            FORWARD(i, i, flags);
+            if (op->clamp.limit[i].den)
+                dst->flags &= SWS_COMP_DIRTY;
+
+            /* Reset undefined bounds to known range */
+            AVRational64 *bound = op->op == SWS_OP_MIN ? &dst->max : &dst->min;
+            if (!bound->den)
+                *bound = op->clamp.limit[i];
+        }
+        break;
+    }
+    case SWS_OP_DITHER:
+        for (int i = 0; i < 4; i++) {
+            FORWARD(i, i, flags);
+            op->outputs[i].min = prev[i].min;
+            op->outputs[i].max = prev[i].max;
+            if (op->dither.y_offset[i] < 0)
+                continue;
+            /* Strip zero flag because of the nonzero dithering offset */
+            op->outputs[i].flags &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY;
+            op->outputs[i].min = ff_add_q64(op->outputs[i].min, op->dither.min);
+            op->outputs[i].max = ff_add_q64(op->outputs[i].max, op->dither.max);
+        }
+        break;
+    case SWS_OP_UNPACK:
+        for (int i = 0; i < 4; i++) {
+            const int pattern = op->pack.pattern[i];
+            if (pattern) {
+                av_assert1(pattern < 32);
+                FORWARD(i, 0, flags & SWS_COMP_DIRTY);
+                op->outputs[i].min = Q(0);
+                op->outputs[i].max = Q((1ULL << pattern) - 1);
+            } else
+                RESET(i);
+        }
+        break;
+    case SWS_OP_PACK:
+        for (int i = 0; i < 4; i++) {
+            if (op->pack.pattern[i])
+                FORWARD(0, i, flags & SWS_COMP_DIRTY);
+            if (i > 0) /* clear remaining comps for sanity */
+                RESET(i);
+        }
+        break;
+    case SWS_OP_CLEAR:
+        for (int i = 0; i < 4; i++) {
+            if (SWS_COMP_TEST(op->clear.mask, i)) {
+                op->outputs[i].type = op->clear.type[i];
+                op->outputs[i].flags = SWS_COMP_CONST;
+                if (op->clear.value[i].num == 0)
+                    op->outputs[i].flags |= SWS_COMP_ZERO;
+                if (op->clear.value[i].den == 1)
                     op->outputs[i].flags |= SWS_COMP_EXACT;
+            } else {
+                FORWARD(i, i, flags);
             }
-            break;
-        case SWS_OP_LINEAR:
-            for (int i = 0; i < 4; i++) {
-                SwsOpPad *dst = &op->outputs[i];
-                bool first = true;
-                dst->type = op->lin.type[i];
-                dst->min = dst->max = Q(0);
-                for (int j = 0; j < 4; j++) {
-                    const SwsOpPad *src = &prev[j];
-                    const AVRational64 k = op->lin.m[i][j];
-                    AVRational64 mink = ff_mul_q64(src->min, k);
-                    AVRational64 maxk = ff_mul_q64(src->max, k);
-                    if (k.num) {
-                        FORWARD(i, j, flags);
-                        if (k.den != 1) /* fractional coefficient */
-                            dst->flags &= ~SWS_COMP_EXACT;
-                        if (k.num < 0)
-                            FFSWAP(AVRational64, mink, maxk);
-                        dst->min = ff_add_q64(dst->min, mink);
-                        dst->max = ff_add_q64(dst->max, maxk);
-                        if (!first || ff_cmp_q64(k, Q(1)))
-                            dst->flags &= SWS_COMP_DIRTY;
-                        first = false;
-                    }
-                }
-                if (op->lin.m[i][4].num) { /* nonzero offset */
-                    dst->flags &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY;
-                    if (op->lin.m[i][4].den != 1) /* fractional offset */
+        }
+        break;
+    case SWS_OP_SWIZZLE:
+        for (int i = 0; i < 4; i++)
+            FORWARD(i, op->swizzle.in[i], flags);
+        break;
+    case SWS_OP_CONVERT:
+        for (int i = 0; i < 4; i++) {
+            FORWARD(i, i, flags);
+            op->outputs[i].type = op->convert.to;
+            if (!(prev[i].flags & SWS_COMP_EXACT))
+                op->outputs[i].flags &= SWS_COMP_DIRTY;
+            if (ff_sws_pixel_type_is_int(op->convert.to))
+                op->outputs[i].flags |= SWS_COMP_EXACT;
+        }
+        break;
+    case SWS_OP_LINEAR:
+        for (int i = 0; i < 4; i++) {
+            SwsOpPad *dst = &op->outputs[i];
+            bool first = true;
+            dst->type = op->lin.type[i];
+            dst->min = dst->max = Q(0);
+            for (int j = 0; j < 4; j++) {
+                const SwsOpPad *src = &prev[j];
+                const AVRational64 k = op->lin.m[i][j];
+                AVRational64 mink = ff_mul_q64(src->min, k);
+                AVRational64 maxk = ff_mul_q64(src->max, k);
+                if (k.num) {
+                    FORWARD(i, j, flags);
+                    if (k.den != 1) /* fractional coefficient */
                         dst->flags &= ~SWS_COMP_EXACT;
-                    dst->min = ff_add_q64(dst->min, op->lin.m[i][4]);
-                    dst->max = ff_add_q64(dst->max, op->lin.m[i][4]);
+                    if (k.num < 0)
+                        FFSWAP(AVRational64, mink, maxk);
+                    dst->min = ff_add_q64(dst->min, mink);
+                    dst->max = ff_add_q64(dst->max, maxk);
+                    if (!first || ff_cmp_q64(k, Q(1)))
+                        dst->flags &= SWS_COMP_DIRTY;
+                    first = false;
                 }
             }
-            break;
-        case SWS_OP_SCALE:
-            for (int i = 0; i < 4; i++) {
-                FORWARD(i, i, flags & SWS_COMP_DIRTY);
-                if (op->scale.factor.den != 1) /* fractional scale */
-                    op->outputs[i].flags &= ~SWS_COMP_EXACT;
-                if (op->scale.factor.num < 0)
-                    FFSWAP(AVRational64, op->outputs[i].min, op->outputs[i].max);
-            }
-            break;
-        case SWS_OP_FILTER_H:
-        case SWS_OP_FILTER_V: {
-            for (int i = 0; i < 4; i++) {
-                FORWARD(i, i, flags & SWS_COMP_DIRTY);
-                apply_filter_weights(&op->outputs[i], op->filter.kernel);
-                op->outputs[i].type = op->filter.type;
+            if (op->lin.m[i][4].num) { /* nonzero offset */
+                dst->flags &= ~SWS_COMP_ZERO & SWS_COMP_DIRTY;
+                if (op->lin.m[i][4].den != 1) /* fractional offset */
+                    dst->flags &= ~SWS_COMP_EXACT;
+                dst->min = ff_add_q64(dst->min, op->lin.m[i][4]);
+                dst->max = ff_add_q64(dst->max, op->lin.m[i][4]);
             }
-            break;
         }
-        case SWS_OP_LUT_3D:
-            for (int i = 0; i < 3; i++) {
-                /* 3x3 dependency matrix; strip all information */
-                for (int j = 0; j < 3; j++)
-                    FORWARD(i, j, flags & 0);
-                /* LUT output domain is always scaled to full 16-bit range */
-                op->outputs[i].min = Q(0);
-                op->outputs[i].max = Q(UINT16_MAX);
-            }
-            /* Pass through alpha channel untouched */
-            FORWARD(3, 3, flags);
-            op->outputs[3].min = prev[3].min;
-            op->outputs[3].max = prev[3].max;
-            break;
-        case SWS_OP_INVALID:
-        case SWS_OP_TYPE_NB:
-            av_unreachable("Invalid operation type!");
+        break;
+    case SWS_OP_SCALE:
+        for (int i = 0; i < 4; i++) {
+            FORWARD(i, i, flags & SWS_COMP_DIRTY);
+            if (op->scale.factor.den != 1) /* fractional scale */
+                op->outputs[i].flags &= ~SWS_COMP_EXACT;
+            if (op->scale.factor.num < 0)
+                FFSWAP(AVRational64, op->outputs[i].min, op->outputs[i].max);
         }
-
-        /* Explicitly reset any pads that did not receive valid data */
+        break;
+    case SWS_OP_FILTER_H:
+    case SWS_OP_FILTER_V: {
         for (int i = 0; i < 4; i++) {
-            if (!op->outputs[i].type || SWS_COMP_TEST(invalid, i))
-                RESET(i);
+            FORWARD(i, i, flags & SWS_COMP_DIRTY);
+            apply_filter_weights(&op->outputs[i], op->filter.kernel);
+            op->outputs[i].type = op->filter.type;
         }
+        break;
+    }
+    case SWS_OP_LUT_3D:
+        for (int i = 0; i < 3; i++) {
+            /* 3x3 dependency matrix; strip all information */
+            for (int j = 0; j < 3; j++)
+                FORWARD(i, j, flags & 0);
+            /* LUT output domain is always scaled to full 16-bit range */
+            op->outputs[i].min = Q(0);
+            op->outputs[i].max = Q(UINT16_MAX);
+        }
+        /* Pass through alpha channel untouched */
+        FORWARD(3, 3, flags);
+        op->outputs[3].min = prev[3].min;
+        op->outputs[3].max = prev[3].max;
+        break;
+    case SWS_OP_INVALID:
+    case SWS_OP_TYPE_NB:
+        av_unreachable("Invalid operation type!");
+    }

-        memcpy(prev, op->outputs, sizeof(prev));
+    for (int i = 0; i < 4; i++) {
+        if (!op->outputs[i].type || SWS_COMP_TEST(invalid, i))
+            RESET(i);
     }

-    #undef FORWARD
-    #undef RESET
+    *prev_ptr = op->outputs;

-    /* Backwards pass, solves for output component dependencies */
-    SwsCompMask need_out[4] = {0};
+#undef FORWARD
+#undef RESET
+}

-    for (int n = ops->num_ops - 1; n >= 0; n--) {
-        SwsOp *op = &ops->ops[n];
-        SwsCompMask need_in[4] = {0};
+static void update_input_pads(SwsOpList *ops, SwsOp *op, SwsCompMask need_out[4])
+{
+    SwsCompMask need_in[4] = {0};

-        for (int i = 0; i < 4; i++)
-            op->outputs[i].dep_out = need_out[i];
+    for (int i = 0; i < 4; i++)
+        op->outputs[i].dep_out = need_out[i];

-        switch (op->op) {
-        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) ? SWS_COMP(i) : 0;
-            for (int i = op->rw.elems; i < 4; i++)
-                need_in[i] = need_out[i];
-            break;
-        case SWS_OP_SWAP_BYTES:
-        case SWS_OP_LSHIFT:
-        case SWS_OP_RSHIFT:
-        case SWS_OP_CONVERT:
-        case SWS_OP_DITHER:
-        case SWS_OP_MIN:
-        case SWS_OP_MAX:
-        case SWS_OP_SCALE:
-        case SWS_OP_FILTER_H:
-        case SWS_OP_FILTER_V:
-            for (int i = 0; i < 4; i++)
+    switch (op->op) {
+    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) ? SWS_COMP(i) : 0;
+        for (int i = op->rw.elems; i < 4; i++)
+            need_in[i] = need_out[i];
+        break;
+    case SWS_OP_SWAP_BYTES:
+    case SWS_OP_LSHIFT:
+    case SWS_OP_RSHIFT:
+    case SWS_OP_CONVERT:
+    case SWS_OP_DITHER:
+    case SWS_OP_MIN:
+    case SWS_OP_MAX:
+    case SWS_OP_SCALE:
+    case SWS_OP_FILTER_H:
+    case SWS_OP_FILTER_V:
+        for (int i = 0; i < 4; i++)
+            need_in[i] = need_out[i];
+        break;
+    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 && op->pack.pattern[i]; i++)
+            need_in[i] = need_out[0];
+        break;
+    case SWS_OP_CLEAR:
+        for (int i = 0; i < 4; i++) {
+            if (!SWS_COMP_TEST(op->clear.mask, i))
                 need_in[i] = need_out[i];
-            break;
-        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 && op->pack.pattern[i]; i++)
-                need_in[i] = need_out[0];
-            break;
-        case SWS_OP_CLEAR:
-            for (int i = 0; i < 4; i++) {
-                if (!SWS_COMP_TEST(op->clear.mask, i))
-                    need_in[i] = need_out[i];
-            }
-            break;
-        case SWS_OP_SWIZZLE:
-            for (int i = 0; i < 4; i++)
-                need_in[op->swizzle.in[i]] |= need_out[i];
-            break;
-        case SWS_OP_LINEAR:
-            for (int i = 0; i < 4; i++) {
-                for (int j = 0; j < 4; j++) {
-                    if (op->lin.m[i][j].num)
-                        need_in[j] |= need_out[i];
-                }
+        }
+        break;
+    case SWS_OP_SWIZZLE:
+        for (int i = 0; i < 4; i++)
+            need_in[op->swizzle.in[i]] |= need_out[i];
+        break;
+    case SWS_OP_LINEAR:
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < 4; j++) {
+                if (op->lin.m[i][j].num)
+                    need_in[j] |= need_out[i];
             }
-            break;
-        case SWS_OP_LUT_3D:
-            for (int i = 0; i < 3; i++)
-                need_in[i] = need_out[0] | need_out[1] | need_out[2];
-            need_in[3] = need_out[3];
-            break;
         }
-
-        memcpy(need_out, need_in, sizeof(need_in));
+        break;
+    case SWS_OP_LUT_3D:
+        for (int i = 0; i < 3; i++)
+            need_in[i] = need_out[0] | need_out[1] | need_out[2];
+        need_in[3] = need_out[3];
+        break;
     }
+
+    memcpy(need_out, need_in, sizeof(need_in));
+}
+
+/* Infer + propagate known information about components */
+void ff_sws_op_list_update_comps(SwsOpList *ops)
+{
+    /* Forward pass, solves for output pad metadata */
+    SwsOpPad dummy[4] = {0}, *prev = dummy;
+    for (int n = 0; n < ops->num_ops; n++)
+        update_output_pads(ops, &ops->ops[n], &prev);
+
+    /* Backwards pass, solves for output component dependencies */
+    SwsCompMask need_out[4] = {0};
+    for (int n = ops->num_ops - 1; n >= 0; n--)
+        update_input_pads(ops, &ops->ops[n], need_out);
 }

 static void op_uninit(SwsOp *op)