Commit 53369160a6 for ffmpeg

commit 53369160a699c0f7fe5cd6ee5e6919a6946e4583
Author: Niklas Haas <git@haasn.dev>
Date:   Tue Oct 20 22:25:24 2026 +0200

    swscale/ops: nuke op->type across the board

    In favor of using the derived types from the output pads, or the tagged
    types where applicable (e.g. for clear ops).

    A few assertions just fall out as a result of no longer having metadata
    that could possibly diverge. One unused function was just deleted instead
    of adapting it.

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

diff --git a/libswscale/format.c b/libswscale/format.c
index 7e653c5101..4af9c91b35 100644
--- a/libswscale/format.c
+++ b/libswscale/format.c
@@ -1130,48 +1130,41 @@ static int decode_pixfmt(SwsOpList *ops, const SwsFormat *fmt)
     /* TODO: handle subsampled or semipacked input formats */
     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op    = SWS_OP_READ,
-        .type  = rw_op.type,
         .rw    = rw_op,
     }));

     if (swapped) {
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
-            .op   = SWS_OP_SWAP_BYTES,
-            .type = rw_op.type,
+            .op = SWS_OP_SWAP_BYTES,
         }));
     }

     if (unpack.pattern[0]) {
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op   = SWS_OP_UNPACK,
-            .type = rw_op.type,
             .pack = unpack,
         }));

         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op   = SWS_OP_CONVERT,
-            .type = rw_op.type,
             .convert.to = pixel_type,
         }));
     }

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op      = SWS_OP_SWIZZLE,
-        .type    = pixel_type,
         .swizzle = swizzle,
     }));

     if (shift.amount) {
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op    = SWS_OP_RSHIFT,
-            .type  = pixel_type,
             .shift = shift,
         }));
     }

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op    = SWS_OP_CLEAR,
-        .type  = pixel_type,
         .clear = fmt_clear(fmt),
     }));

@@ -1195,7 +1188,6 @@ static int encode_pixfmt(SwsOpList *ops, const SwsFormat *fmt)
     if (shift.amount) {
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op    = SWS_OP_LSHIFT,
-            .type  = pixel_type,
             .shift = shift,
         }));
     }
@@ -1205,7 +1197,6 @@ static int encode_pixfmt(SwsOpList *ops, const SwsFormat *fmt)
         av_assert1(!(desc->flags & AV_PIX_FMT_FLAG_ALPHA));
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op   = SWS_OP_CLEAR,
-            .type = pixel_type,
             .clear.type[3] = pixel_type,
             .clear.mask = SWS_COMP(3),
             .clear.value[3] = Q(0),
@@ -1214,20 +1205,17 @@ static int encode_pixfmt(SwsOpList *ops, const SwsFormat *fmt)

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op      = SWS_OP_SWIZZLE,
-        .type    = pixel_type,
         .swizzle = swizzle,
     }));

     if (pack.pattern[0]) {
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op   = SWS_OP_CONVERT,
-            .type = pixel_type,
             .convert.to = rw_op.type,
         }));

         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op   = SWS_OP_PACK,
-            .type = rw_op.type,
             .pack = pack,
         }));
     }
@@ -1235,14 +1223,12 @@ static int encode_pixfmt(SwsOpList *ops, const SwsFormat *fmt)
     if (ff_sws_pixel_type_size(rw_op.type) > 1 &&
         (desc->flags & AV_PIX_FMT_FLAG_BE) != NATIVE_ENDIAN_FLAG) {
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
-            .op   = SWS_OP_SWAP_BYTES,
-            .type = rw_op.type,
+            .op = SWS_OP_SWAP_BYTES,
         }));
     }

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op   = SWS_OP_WRITE,
-        .type = rw_op.type,
         .rw   = rw_op,
     }));

@@ -1400,7 +1386,6 @@ static bool trc_is_hdr(enum AVColorTransferCharacteristic trc)
 }

 static int fmt_dither(SwsContext *ctx, SwsOpList *ops,
-                      const SwsPixelType type,
                       const SwsFormat *src, const SwsFormat *dst)
 {
     SwsDither mode = ctx->dither;
@@ -1423,7 +1408,6 @@ static int fmt_dither(SwsContext *ctx, SwsOpList *ops,
             *bias = (AVRational64) {1, 2};
             return ff_sws_op_list_append(ops, &(SwsOp) {
                 .op   = SWS_OP_DITHER,
-                .type = type,
                 .dither.matrix = bias,
                 .dither.min = *bias,
                 .dither.max = *bias,
@@ -1477,7 +1461,6 @@ static int fmt_dither(SwsContext *ctx, SwsOpList *ops,

         return ff_sws_op_list_append(ops, &(SwsOp) {
             .op     = SWS_OP_DITHER,
-            .type   = type,
             .dither = dither,
         });
     case SWS_DITHER_ED:
@@ -1524,13 +1507,11 @@ static int decode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op         = SWS_OP_CONVERT,
-        .type       = pixel_type,
         .convert.to = type,
     }));

     /* Decode pixel format into standardized range */
     RET(ff_sws_op_list_append(ops, &(SwsOp) {
-        .type = type,
         .op   = SWS_OP_LINEAR,
         .lin  = fmt_decode_range(fmt, type, incomplete),
     }));
@@ -1560,7 +1541,6 @@ static int decode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
         AVRational m12 = av_mul_q(crg, m02);

         return ff_sws_op_list_append(ops, &(SwsOp) {
-            .type = type,
             .op   = SWS_OP_LINEAR,
             .lin  = linear_mat3(type,
                 q1,  q0, m02,
@@ -1572,7 +1552,6 @@ static int decode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,

     case AVCOL_SPC_YCGCO:
         return ff_sws_op_list_append(ops, &(SwsOp) {
-            .type = type,
             .op   = SWS_OP_LINEAR,
             .lin  = linear_mat3(type,
                 q1, av_make_q(-1, 1), av_make_q( 1, 1),
@@ -1635,7 +1614,6 @@ static int encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
         AVRational m22 = av_mul_q(m20, av_div_q(c->cb, cr1));

         RET(ff_sws_op_list_append(ops, &(SwsOp) {
-            .type = type,
             .op   = SWS_OP_LINEAR,
             .lin  = linear_mat3(type,
                 c->cr, c->cg, c->cb,
@@ -1648,7 +1626,6 @@ static int encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,

     case AVCOL_SPC_YCGCO:
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
-            .type = type,
             .op   = SWS_OP_LINEAR,
             .lin  = linear_mat3(type,
                 av_make_q( 1, 4), av_make_q(1, 2), av_make_q( 1, 4),
@@ -1674,7 +1651,6 @@ static int encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
     }

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
-        .type = type,
         .op   = SWS_OP_LINEAR,
         .lin  = fmt_encode_range(dst, type, incomplete),
     }));
@@ -1689,22 +1665,19 @@ static int encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
             range.limit[idx] = intmax_q64(dst->desc->comp[i].depth);
         }

-        RET(fmt_dither(ctx, ops, type, src, dst));
+        RET(fmt_dither(ctx, ops, src, dst));
         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op    = SWS_OP_MAX,
-            .type  = type,
             .clamp = {{ Q(0), Q(0), Q(0), Q(0) }},
         }));

         RET(ff_sws_op_list_append(ops, &(SwsOp) {
             .op    = SWS_OP_MIN,
-            .type  = type,
             .clamp = range,
         }));
     }

     return ff_sws_op_list_append(ops, &(SwsOp) {
-        .type       = type,
         .op         = SWS_OP_CONVERT,
         .convert.to = pixel_type,
     });
@@ -1768,7 +1741,6 @@ static int add_filter(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
     }

     return ff_sws_op_list_append(ops, &(SwsOp) {
-        .type = type,
         .op   = filter,
         .filter.kernel = kernel,
         .filter.type   = type,
@@ -1798,7 +1770,6 @@ static int apply_lut3d(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
     const AVRational64 domain = Q(INPUT_LUT_SIZE - 1);

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
-        .type   = type,
         .op     = SWS_OP_LINEAR,
         .lin    = LINEAR_OP_DATA(type,
             { domain,   Q(0),   Q(0), Q(0), Q(0) },
@@ -1810,20 +1781,17 @@ static int apply_lut3d(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op     = SWS_OP_MAX,
-        .type   = type,
         .clamp  = {{ Q(0), Q(0), Q(0) }},
     }));

     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op     = SWS_OP_MIN,
-        .type   = type,
         .clamp  = {{ domain, domain, domain }},
     }));

     /* Apply the 3DLUT itself */
     RET(ff_sws_op_list_append(ops, &(SwsOp) {
         .op     = SWS_OP_LUT_3D,
-        .type   = type,
         .lut3d.lut     = av_refstruct_ref_c(lut3d),
         .lut3d.dynamic = lut3d->dynamic,
     }));
@@ -1831,7 +1799,6 @@ static int apply_lut3d(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
     /* Normalize back to [0, 1] */
     const AVRational64 inv = ff_inv_q64(Q(UINT16_MAX));
     RET(ff_sws_op_list_append(ops, &(SwsOp) {
-        .type   = type,
         .op     = SWS_OP_LINEAR,
         .lin    = LINEAR_OP_DATA(type,
             {  inv, Q(0), Q(0), Q(0), Q(0) },
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 85fd0b074e..4442ae7c81 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -209,7 +209,6 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4])
     case SWS_OP_WRITE:
         return;
     case SWS_OP_UNPACK: {
-        av_assert1(ff_sws_pixel_type_is_int(op->type));
         ff_sws_pack_op_decode(op, mask, shift);
         unsigned val = x[0].num;
         for (int i = 0; i < 4; i++)
@@ -217,7 +216,6 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4])
         return;
     }
     case SWS_OP_PACK: {
-        av_assert1(ff_sws_pixel_type_is_int(op->type));
         ff_sws_pack_op_decode(op, mask, shift);
         unsigned val = 0;
         for (int i = 0; i < 4; i++)
@@ -226,7 +224,7 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4])
         return;
     }
     case SWS_OP_SWAP_BYTES:
-        switch (op->type) {
+        switch (ff_sws_op_input_type(op)) {
         case SWS_PIXEL_U16:
             for (int i = 0; i < 4; i++) {
                 av_assert2(x[i].num >= 0 && x[i].num <= UINT16_MAX);
@@ -249,14 +247,12 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4])
         }
         return;
     case SWS_OP_LSHIFT: {
-        av_assert1(ff_sws_pixel_type_is_int(op->type));
         AVRational64 mult = Q(1 << op->shift.amount);
         for (int i = 0; i < 4; i++)
             x[i] = x[i].den ? ff_mul_q64(x[i], mult) : x[i];
         return;
     }
     case SWS_OP_RSHIFT: {
-        av_assert1(ff_sws_pixel_type_is_int(op->type));
         for (int i = 0; i < 4; i++)
             x[i] = x[i].den ? Q((x[i].num / x[i].den) >> op->shift.amount) : x[i];
         return;
@@ -274,7 +270,6 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational64 x[4])
         }
         return;
     case SWS_OP_DITHER:
-        av_assert1(!ff_sws_pixel_type_is_int(op->type));
         for (int i = 0; i < 4; i++) {
             if (op->dither.y_offset[i] >= 0 && x[i].den)
                 x[i] = ff_add_q64(x[i], ff_make_q64(1, 2));
@@ -870,17 +865,6 @@ bool ff_sws_op_list_is_noop(const SwsOpList *ops)
     return true;
 }

-int ff_sws_op_list_max_size(const SwsOpList *ops)
-{
-    int max_size = 0;
-    for (int i = 0; i < ops->num_ops; i++) {
-        const int size = ff_sws_pixel_type_size(ops->ops[i].type);
-        max_size = FFMAX(max_size, size);
-    }
-
-    return max_size;
-}
-
 uint32_t ff_sws_linear_mask(const SwsLinearOp *c)
 {
     uint32_t mask = 0;
@@ -966,7 +950,6 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op)
         break;
     case SWS_OP_READ:
     case SWS_OP_WRITE:
-        av_assert0(op->rw.type == op->type);
         av_bprintf(bp, "%-20s: %dx %s %s >> %d", name,
                    op->rw.elems, ff_sws_pixel_type_name(op->rw.type),
                    rw_mode_names[op->rw.mode], op->rw.frac);
@@ -999,7 +982,7 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op)
         break;
     case SWS_OP_CONVERT:
         av_bprintf(bp, "%-20s: %s -> %s", name,
-                   ff_sws_pixel_type_name(op->type),
+                   ff_sws_pixel_type_name(ff_sws_op_input_type(op)),
                    ff_sws_pixel_type_name(op->convert.to));
         break;
     case SWS_OP_DITHER:
diff --git a/libswscale/ops.h b/libswscale/ops.h
index 8dcabb2ead..613ff5e538 100644
--- a/libswscale/ops.h
+++ b/libswscale/ops.h
@@ -251,8 +251,7 @@ typedef struct SwsOp     SwsOp;
 typedef struct SwsOpList SwsOpList;

 struct SwsOp {
-    SwsOpType op;      /* operation to perform */
-    SwsPixelType type; /* pixel type to operate on */
+    SwsOpType op;
     union {
         SwsLinearOp     lin;
         SwsReadWriteOp  rw;
@@ -373,11 +372,6 @@ const SwsOp *ff_sws_op_list_output(const SwsOpList *ops);
  */
 bool ff_sws_op_list_is_noop(const SwsOpList *ops);

-/**
- * Returns the size of the largest pixel type used in `ops`.
- */
-int ff_sws_op_list_max_size(const SwsOpList *ops);
-
 /**
  * These will take over ownership of `op` and set it to {0}, even on failure.
  */
diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c
index 4726ca9bf2..7d6b6de770 100644
--- a/libswscale/ops_optimizer.c
+++ b/libswscale/ops_optimizer.c
@@ -45,7 +45,6 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next)
     case SWS_OP_CONVERT:
         for (int i = 0; i < 4; i++)
             op->clear.type[i] = next->convert.to;
-        op->type = next->convert.to;
         av_fallthrough;
     case SWS_OP_LSHIFT:
     case SWS_OP_RSHIFT:
@@ -60,7 +59,6 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next)
     case SWS_OP_FILTER_V:
         for (int i = 0; i < 4; i++)
             op->clear.type[i] = next->filter.type;
-        op->type = next->filter.type;
         return true;
     case SWS_OP_SWIZZLE: {
         const SwsClearOp orig = op->clear;
@@ -71,7 +69,7 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next)
         return true;
     }
     case SWS_OP_SWAP_BYTES:
-        switch (next->type) {
+        switch (ff_sws_op_input_type(next)) {
         case SWS_PIXEL_U16:
         case SWS_PIXEL_U32:
             ff_sws_apply_op_q(next, op->clear.value); /* always representable */
@@ -108,16 +106,12 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next)
     av_assert1(op->op == SWS_OP_SWIZZLE);
     switch (next->op) {
     case SWS_OP_CONVERT:
-        op->type = next->convert.to;
-        av_fallthrough;
     case SWS_OP_SWAP_BYTES:
     case SWS_OP_LSHIFT:
     case SWS_OP_RSHIFT:
     case SWS_OP_SCALE:
-        return true;
     case SWS_OP_FILTER_H:
     case SWS_OP_FILTER_V:
-        op->type = next->filter.type;
         return true;

     /**
@@ -191,12 +185,10 @@ static bool op_commute_filter(SwsOp *op, SwsOp *prev)
     case SWS_OP_SWIZZLE:
     case SWS_OP_SCALE:
     case SWS_OP_DITHER:
-        prev->type = op->filter.type;
         return true;
     case SWS_OP_LINEAR:
         for (int i = 0; i < 4; i++)
             prev->lin.type[i] = op->filter.type;
-        prev->type = op->filter.type;
         return true;
     case SWS_OP_CONVERT:
     case SWS_OP_INVALID:
@@ -375,11 +367,10 @@ retry:

             /* Merge filter with prior conversion */
             if (prev->op == SWS_OP_CONVERT) {
-                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);
+                const SwsPixelType input_type = ff_sws_op_input_type(prev);
+                int size_from = ff_sws_pixel_type_size(input_type);
+                int size_to   = ff_sws_pixel_type_size(prev->convert.to);
                 if (size_from < size_to) {
-                    op->type = prev->type;
                     ff_sws_op_list_remove_at(ops, n - 1, 1);
                     goto retry;
                 }
@@ -427,7 +418,6 @@ retry:
                     op->rw.elems = nb_planes;
                     RET(ff_sws_op_list_insert_at(ops, n + 1, &(SwsOp) {
                         .op = SWS_OP_SWIZZLE,
-                        .type = op->rw.filter.op ? op->rw.filter.type : op->type,
                         .swizzle = swiz,
                     }));
                     goto retry;
@@ -445,7 +435,7 @@ retry:

         case SWS_OP_UNPACK:
             /* Redundant unpack+pack */
-            if (next->op == SWS_OP_PACK && next->type == op->type &&
+            if (next->op == SWS_OP_PACK &&
                 next->pack.pattern[0] == op->pack.pattern[0] &&
                 next->pack.pattern[1] == op->pack.pattern[1] &&
                 next->pack.pattern[2] == op->pack.pattern[2] &&
@@ -585,14 +575,13 @@ retry:

         case SWS_OP_CONVERT:
             /* No-op conversion */
-            if (op->type == op->convert.to) {
+            if (op->convert.to == ff_sws_op_input_type(op)) {
                 ff_sws_op_list_remove_at(ops, n, 1);
                 goto retry;
             }

             /* Transitive conversion */
             if (next->op == SWS_OP_CONVERT) {
-                av_assert1(op->convert.to == next->type);
                 op->convert.to = next->convert.to;
                 ff_sws_op_list_remove_at(ops, n + 1, 1);
                 goto retry;
@@ -704,7 +693,6 @@ retry:
             if (extract_constant_rows(&op->lin, prev->outputs, &clear)) {
                 RET(ff_sws_op_list_insert_at(ops, n + 1, &(SwsOp) {
                     .op    = SWS_OP_CLEAR,
-                    .type  = op->type,
                     .clear = clear,
                 }));
                 goto retry;
@@ -721,7 +709,6 @@ retry:
             if (extract_swizzle(&op->lin, prev->outputs, &swizzle)) {
                 RET(ff_sws_op_list_insert_at(ops, n, &(SwsOp) {
                     .op      = SWS_OP_SWIZZLE,
-                    .type    = op->type,
                     .swizzle = swizzle,
                 }));
                 goto retry;
@@ -746,7 +733,7 @@ retry:
             }

             /* Scaling by exact power of two */
-            if (factor2 && ff_sws_pixel_type_is_int(op->type)) {
+            if (factor2 && ff_sws_pixel_type_is_int(ff_sws_op_input_type(op))) {
                 op->op = factor2 > 0 ? SWS_OP_LSHIFT : SWS_OP_RSHIFT;
                 op->shift.amount = FFABS(factor2);
                 goto retry;
@@ -815,7 +802,6 @@ retry:
                 ff_sws_pixel_type_is_int(next->convert.to) &&
                 op_result_is_exact(op))
             {
-                op->type = next->convert.to;
                 FFSWAP(SwsOp, *op, *next);
                 goto retry;
             }
@@ -829,7 +815,6 @@ retry:
             {
                 for (int i = 0; i < 4; i++)
                     op->lin.type[i] = next->convert.to;
-                op->type = next->convert.to;
                 FFSWAP(SwsOp, *op, *next);
                 goto retry;
             }
@@ -859,7 +844,6 @@ static int select_planes(SwsOpList *ops, SwsCompMask planes)
     /* 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)
@@ -1215,7 +1199,7 @@ 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;
+    const SwsPixelType type = ff_sws_op_input_type(op);
     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);
@@ -1236,7 +1220,6 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index)
     if (swiz_wr.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) {
         ret = ff_sws_op_list_append(ops1, &(SwsOp) {
             .op      = SWS_OP_SWIZZLE,
-            .type    = type,
             .swizzle = swiz_wr,
         });
         if (ret < 0)
@@ -1245,7 +1228,6 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index)

     ret = ff_sws_op_list_append(ops1, &(SwsOp) {
         .op       = SWS_OP_WRITE,
-        .type     = type,
         .rw.type  = type,
         .rw.elems = nb_planes,
     });
@@ -1254,7 +1236,6 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index)

     ret = ff_sws_op_list_insert_at(ops2, 0, &(SwsOp) {
         .op        = SWS_OP_READ,
-        .type      = type,
         .rw.type   = type,
         .rw.elems  = nb_planes,
     });
@@ -1264,7 +1245,6 @@ int ff_sws_op_list_split_at(SwsOpList *ops1, SwsOpList **out_ops2, int index)
     if (swiz_rd.mask != SWS_SWIZZLE(0, 1, 2, 3).mask) {
         ret = ff_sws_op_list_insert_at(ops2, 1, &(SwsOp) {
             .op      = SWS_OP_SWIZZLE,
-            .type    = type,
             .swizzle = swiz_rd,
         });
         if (ret < 0)
diff --git a/libswscale/uops.c b/libswscale/uops.c
index 8d6ad36692..6f8b276d33 100644
--- a/libswscale/uops.c
+++ b/libswscale/uops.c
@@ -47,8 +47,6 @@ static const struct {
 #undef UOP_NAME
 };

-#define Q2PIXEL(val) ff_sws_pixel_from_q64(op->type, val)
-
 void ff_sws_uop_name(const SwsUOp *op, char buf[SWS_UOP_NAME_MAX])
 {
     AVBPrint bp;
@@ -228,16 +226,16 @@ static bool exact_product_f32(float a, float b)
     return !b || result == a;
 }

-static bool exact_prod(SwsPixelType type, SwsPixel coef, const SwsOpPad *input)
+static bool exact_prod(SwsPixel coef, const SwsOpPad *input)
 {
-    if (ff_sws_pixel_type_is_int(type))
+    if (ff_sws_pixel_type_is_int(input->type))
         return true;
     else if (!input->min.den || !input->max.den)
         return false; /* unknown bounds */

-    const SwsPixel min = ff_sws_pixel_from_q64(type, input->min);
-    const SwsPixel max = ff_sws_pixel_from_q64(type, input->max);
-    switch (type) {
+    const SwsPixel min = ff_sws_pixel_from_q64(input->type, input->min);
+    const SwsPixel max = ff_sws_pixel_from_q64(input->type, input->max);
+    switch (input->type) {
     case SWS_PIXEL_F32:
         return exact_product_f32(coef.f32, min.f32) &&
                exact_product_f32(coef.f32, max.f32);
@@ -253,10 +251,10 @@ static bool check_filter_fma(SwsContext *ctx, SwsUOpFlags flags, const SwsOp *op
         return false;
     if (!(ctx->flags & SWS_BITEXACT))
         return true;
-    if (!ff_sws_pixel_type_is_int(op->type))
+    if (!ff_sws_pixel_type_is_int(op->rw.type))
         return false;

-    const int bits = ff_sws_pixel_type_size(op->type) * 8;
+    const int bits = ff_sws_pixel_type_size(op->rw.type) * 8;
     const uint64_t max_val = UINT64_MAX >> (64 - bits);

     /* Maximum value representable losslessly as float. Note that this is
@@ -269,14 +267,14 @@ static int translate_rw_op(SwsContext *ctx, SwsUOpList *ops, SwsUOpFlags flags,
                            const SwsOp *op)
 {
     SwsUOp uop = {
-        .type = op->type,
+        .type = op->rw.type,
         .mask = SWS_COMP_MASK(op->rw.elems > 0, op->rw.elems > 1,
                               op->rw.elems > 2, op->rw.elems > 3),
     };

     /* Non-filtered reads don't care about the exact pixel contents */
     if (!op->rw.filter.op)
-        uop.type = pixel_type_to_int(op->type);
+        uop.type = pixel_type_to_int(op->rw.type);

     const bool is_read = op->op == SWS_OP_READ;
     if (op->rw.filter.op) {
@@ -333,7 +331,7 @@ static int translate_swizzle(SwsUOpList *ops, const SwsOp *op)
 {
     SwsUOp uop = {
         .uop  = SWS_UOP_PERMUTE,
-        .type = pixel_type_to_int(op->type),
+        .type = pixel_type_to_int(ff_sws_op_input_type(op)),
         .mask = ff_sws_comp_mask_needed(op),
     };
     SwsMoveUOp *par = &uop.par.move;
@@ -426,14 +424,14 @@ static int translate_swizzle(SwsUOpList *ops, const SwsOp *op)
 static int translate_dither_op(SwsUOpList *ops, SwsUOpFlags flags, const SwsOp *op)
 {
     SwsUOp uop = {
-        .type = op->type,
+        .type = ff_sws_op_input_type(op),
         .uop  = SWS_UOP_DITHER,
         .par.dither.size_log2 = op->dither.size_log2,
     };

     if ((flags & SWS_UOP_FLAG_ADD) && op->dither.size_log2 == 0) {
         /* Constant offset */
-        const SwsPixel val = Q2PIXEL(op->dither.matrix[0]);
+        const SwsPixel val = ff_sws_pixel_from_q64(uop.type, op->dither.matrix[0]);
         uop.uop = SWS_UOP_ADD;
         for (int i = 0; i < 4; i++) {
             if (!SWS_OP_NEEDED(op, i) || op->dither.y_offset[i] < 0)
@@ -465,7 +463,7 @@ static int translate_dither_op(SwsUOpList *ops, SwsUOpFlags flags, const SwsOp *
         return AVERROR(ENOMEM);

     for (int i = 0; i < size * size; i++)
-        matrix[i] = Q2PIXEL(op->dither.matrix[i]);
+        matrix[i] = ff_sws_pixel_from_q64(uop.type, op->dither.matrix[i]);
     memcpy(&matrix[size * size], matrix, (num_rows - size) * stride);

     return ff_sws_uop_list_append(ops, &uop);
@@ -476,7 +474,7 @@ static int translate_linear_op(SwsContext *ctx, SwsUOpList *ops,
                                const SwsOpPad inputs[4])
 {
     SwsUOp uop = {
-        .type = op->type,
+        .type = ff_sws_op_input_type(op),
         .uop  = SWS_UOP_LINEAR,
     };

@@ -493,20 +491,20 @@ static int translate_linear_op(SwsContext *ctx, SwsUOpList *ops,
         bool nonzero = (op->lin.m[i][4].num != 0);
         for (int j = 0; j < 5; j++) {
             const AVRational64 k = op->lin.m[i][j];
-            const SwsPixel px = Q2PIXEL(k);
+            const SwsPixel px = ff_sws_pixel_from_q64(uop.type, k);
             uop.data.mat4x5[i][j] = px;
             if (k.num == 0)
                 uop.par.lin.zero |= SWS_MASK(i, j);
             else if (j < 4 && k.num == k.den)
                 uop.par.lin.one |= SWS_MASK(i, j);
-            else if (j < 4 && nonzero && (!bitexact || exact_prod(uop.type, px, &inputs[j])))
+            else if (j < 4 && nonzero && (!bitexact || exact_prod(px, &inputs[j])))
                 exact |= SWS_MASK(i, j);
             if (k.num != 0)
                 nonzero = true;
         }
     }

-    if ((flags & SWS_UOP_FLAG_FMA) && !ff_sws_pixel_type_is_int(op->type)) {
+    if ((flags & SWS_UOP_FLAG_FMA) && !ff_sws_pixel_type_is_int(uop.type)) {
         /* multiplication by 1 and 0 are always exact by definition */
         uop.uop = SWS_UOP_LINEAR_FMA;
         uop.par.lin.exact = exact | uop.par.lin.zero | uop.par.lin.one;
@@ -537,8 +535,8 @@ static int translate_op(SwsContext *ctx, SwsUOpList *uops, SwsUOpFlags flags,

     /* Default handling for "simple" ops */
     SwsUOp uop = {
-        .type = op->type,
         .uop  = SWS_UOP_INVALID,
+        .type = ff_sws_op_input_type(op),
         .mask = ff_sws_comp_mask_needed(op),
     };

@@ -568,23 +566,24 @@ static int translate_op(SwsContext *ctx, SwsUOpList *uops, SwsUOpFlags flags,
         break;
     case SWS_OP_CLEAR:
         uop.uop = SWS_UOP_CLEAR;
-        uop.type = pixel_type_to_int(op->type);
         uop.mask &= op->clear.mask;
         for (int i = 0; i < 4; i++) {
             if (!SWS_COMP_TEST(op->clear.mask, i))
                 continue;
             const AVRational64 v = op->clear.value[i];
-            const SwsPixel px = Q2PIXEL(op->clear.value[i]);
+            const SwsPixelType type = op->clear.type[i];
+            const SwsPixel px = ff_sws_pixel_from_q64(type, v);
+            uop.type = pixel_type_to_int(type);
             uop.data.vec4[i] = px;
             if (v.num == 0)
                 uop.par.clear.zero |= SWS_COMP(i);
-            else if (ff_sws_pixel_is_1s(op->type, px))
+            else if (ff_sws_pixel_is_1s(type, px))
                 uop.par.clear.one |= SWS_COMP(i);
         }
         break;
     case SWS_OP_SCALE:
         uop.uop = SWS_UOP_SCALE;
-        uop.data.scalar = Q2PIXEL(op->scale.factor);
+        uop.data.scalar = ff_sws_pixel_from_q64(uop.type, op->scale.factor);
         break;
     case SWS_OP_MIN:
     case SWS_OP_MAX:
@@ -592,12 +591,12 @@ static int translate_op(SwsContext *ctx, SwsUOpList *uops, SwsUOpFlags flags,
         uop.mask &= ff_sws_comp_mask_q4(op->clamp.limit);
         for (int i = 0; i < 4; i++) {
             if (SWS_COMP_TEST(uop.mask, i))
-                uop.data.vec4[i] = Q2PIXEL(op->clamp.limit[i]);
+                uop.data.vec4[i] = ff_sws_pixel_from_q64(uop.type, op->clamp.limit[i]);
         }
         break;
     case SWS_OP_SWAP_BYTES:
         uop.uop = SWS_UOP_SWAP_BYTES;
-        uop.type = pixel_type_to_int(op->type);
+        uop.type = pixel_type_to_int(uop.type);
         break;
     case SWS_OP_LUT_3D:
         uop.uop = SWS_UOP_LUT_3D;
@@ -616,11 +615,15 @@ int ff_sws_ops_translate(SwsContext *ctx, const SwsOpList *ops,
                          SwsUOpFlags flags, SwsUOpList *uops)
 {
     const SwsOpPad *inputs = ops->inputs;
-    for (int i = 0; i < ops->num_ops; i++) {
-        const SwsOp *op = &ops->ops[i];
-        const int pixel_size = ff_sws_pixel_type_size(op->type);
-        if (pixel_size > uops->pixel_size_max)
-            uops->pixel_size_max = pixel_size;
+    for (int n = 0; n < ops->num_ops; n++) {
+        const SwsOp *op = &ops->ops[n];
+        for (int i = 0; i < 4; i++) {
+            if (!SWS_OP_NEEDED(op, i))
+                continue;
+            const int pixel_size = ff_sws_pixel_type_size(op->outputs[i].type);
+            if (pixel_size > uops->pixel_size_max)
+                uops->pixel_size_max = pixel_size;
+        }

         int ret = translate_op(ctx, uops, flags, op, inputs);
         if (ret < 0)
diff --git a/libswscale/vulkan/ops.c b/libswscale/vulkan/ops.c
index 7754abfe97..910aac11e3 100644
--- a/libswscale/vulkan/ops.c
+++ b/libswscale/vulkan/ops.c
@@ -548,13 +548,14 @@ static void define_shader_consts(SwsContext *sws, const SwsOpList *ops,
     for (int n = 0; n < ops->num_ops; n++) {
         const SwsOp *op = &ops->ops[n];
         OpIDs *oid = &id->op[n];
+        SwsPixelType type_in = ff_sws_op_input_type(op);
         switch (op->op) {
         case SWS_OP_CLEAR:
             for (int i = 0; i < 4; i++) {
                 if (!SWS_COMP_TEST(op->clear.mask, i))
                     continue;
                 AVRational64 cv = op->clear.value[i];
-                if (op->type == SWS_PIXEL_F32) {
+                if (op->clear.type[i] == SWS_PIXEL_F32) {
                     float q = (float)cv.num/cv.den;
                     oid->comp[i] = spi_OpConstantFloat(spi, f32_type, q);
                 } else {
@@ -587,7 +588,7 @@ static void define_shader_consts(SwsContext *sws, const SwsOpList *ops,
         }
         case SWS_OP_SCALE: {
             int tmp;
-            if (op->type == SWS_PIXEL_F32) {
+            if (type_in == SWS_PIXEL_F32) {
                 float q = op->scale.factor.num/(float)op->scale.factor.den;
                 tmp = spi_OpConstantFloat(spi, f32_type, q);
                 tmp = spi_OpConstantComposite(spi, id->f32vec4_type,
@@ -608,7 +609,7 @@ static void define_shader_consts(SwsContext *sws, const SwsOpList *ops,
                 AVRational64 cl = op->clamp.limit[i];
                 if (!op->clamp.limit[i].den) {
                     continue;
-                } else if (op->type == SWS_PIXEL_F32) {
+                } else if (type_in == SWS_PIXEL_F32) {
                     float q = (float)cl.num/((float)cl.den);
                     tmp = spi_OpConstantFloat(spi, f32_type, q);
                 } else {
@@ -781,8 +782,9 @@ static int insert_vmat_linear(SPICtx *spi, SPIRVIDs *id, const LinearIDs *lin,
 static int insert_bitexact_linear(const SwsOp *op, SPICtx *spi, SPIRVIDs *id,
                                   const LinearIDs *lin, int data)
 {
-    int type_s = op->type == SWS_PIXEL_F32 ? id->f32_type : id->u32_type;
-    int type_v = op->type == SWS_PIXEL_F32 ? id->f32vec4_type : id->u32vec4_type;
+    const SwsPixelType op_type = ff_sws_op_input_type(op);
+    int type_s = op_type == SWS_PIXEL_F32 ? id->f32_type : id->u32_type;
+    int type_v = op_type == SWS_PIXEL_F32 ? id->f32vec4_type : id->u32vec4_type;

     int tmp[4];
     tmp[0] = spi_OpCompositeExtract(spi, type_s, data, 0);
@@ -799,7 +801,7 @@ static int insert_bitexact_linear(const SwsOp *op, SPICtx *spi, SPIRVIDs *id,

     int res[4];
     for (int j = 0; j < 4; j++) {
-        res[j] = op->type == SWS_PIXEL_F32 ? id->f32_0 : id->u32_cid[0];
+        res[j] = op_type == SWS_PIXEL_F32 ? id->f32_0 : id->u32_cid[0];
         if (op->lin.m[j][0].num)
             res[j] = spi_OpFMul(spi, type_s, tmp[0], lin->m[j][0]);

@@ -1144,7 +1146,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,

     /* Initialize main data state */
     int data;
-    if (ops->ops[0].type == SWS_PIXEL_F32)
+    if (ff_sws_op_output_type(&ops->ops[0]) == SWS_PIXEL_F32)
         data = spi_OpCompositeConstruct(spi, id->f32vec4_type,
                                         id->f32_0, id->f32_0,
                                         id->f32_0, id->f32_0);
@@ -1157,13 +1159,15 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
     for (int n = 0; n < ops->num_ops; n++) {
         const SwsOp *op = &ops->ops[n];
         const OpIDs *oid = &id->op[n];
-        SwsPixelType cur_type = op->op == SWS_OP_CONVERT ?
-                                op->convert.to : op->type;
-        int type_v = cur_type == SWS_PIXEL_F32 ?
+        SwsPixelType type_in  = ff_sws_op_input_type(op);
+        SwsPixelType type_out = ff_sws_op_output_type(op);
+        if (op->op == SWS_OP_WRITE)
+            type_out = type_in;
+        int type_v = type_out == SWS_PIXEL_F32 ?
                      id->f32vec4_type : id->u32vec4_type;
-        int type_s = cur_type == SWS_PIXEL_F32 ?
+        int type_s = type_out == SWS_PIXEL_F32 ?
                      id->f32_type : id->u32_type;
-        int uid = cur_type == SWS_PIXEL_F32 ?
+        int uid = type_out == SWS_PIXEL_F32 ?
                   id->f32_0 : id->u32_p;

         switch (op->op) {
@@ -1224,9 +1228,9 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
                                        op->swizzle.in[3]);
             break;
         case SWS_OP_CONVERT:
-            if (op->type == SWS_PIXEL_F32 && type_s == id->u32_type)
+            if (type_in == SWS_PIXEL_F32 && type_s == id->u32_type)
                 data = spi_OpConvertFToU(spi, type_v, data);
-            else if (op->type != SWS_PIXEL_F32 && type_s == id->f32_type)
+            else if (type_in != SWS_PIXEL_F32 && type_s == id->f32_type)
                 data = spi_OpConvertUToF(spi, type_v, data);
             break;
         case SWS_OP_LSHIFT:
@@ -1236,14 +1240,14 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
             data = spi_OpShiftRightLogical(spi, type_v, data, oid->vec);
             break;
         case SWS_OP_SCALE:
-            if (op->type == SWS_PIXEL_F32)
+            if (type_in == SWS_PIXEL_F32)
                 data = spi_OpFMul(spi, type_v, data, oid->vec);
             else
                 data = spi_OpIMul(spi, type_v, data, oid->vec);
             break;
         case SWS_OP_MIN:
         case SWS_OP_MAX: {
-            int t = op->type == SWS_PIXEL_F32 ?
+            int t = type_in == SWS_PIXEL_F32 ?
                     op->op == SWS_OP_MIN ? GLSLstd450FMin : GLSLstd450FMax :
                     op->op == SWS_OP_MIN ? GLSLstd450UMin : GLSLstd450UMax;
             for (int i = 0; i < 4; i++) {
@@ -1279,7 +1283,7 @@ static int add_ops_spirv(SwsContext *sws, VulkanPriv *p, FFVulkanOpsCtx *s,
             break;
         }
         case SWS_OP_LINEAR: {
-            if (op->type != SWS_PIXEL_F32)
+            if (type_in != SWS_PIXEL_F32)
                 return AVERROR(ENOTSUP);
             if (sws->flags & SWS_BITEXACT)
                 data = insert_bitexact_linear(op, spi, id, &oid->lin, data);