swscale/graph: reintroduce SwsFrame
authorNiklas Haas <git@haasn.dev>
Fri, 27 Feb 2026 20:53:24 +0000 (21:53 +0100)
committerNiklas Haas <ffmpeg@haasn.dev>
Sun, 1 Mar 2026 21:57:53 +0000 (21:57 +0000)
AVFrame just really doesn't have the semantics we want. However, there a
tangible benefit to having SwsFrame act as a carbon copy of a (subset of)
AVFrame.

This partially reverts commit 67f36272671be9405248386bfbc467a6a772d5ce.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <git@haasn.dev>
libswscale/format.c
libswscale/format.h
libswscale/graph.c
libswscale/graph.h
libswscale/ops.c
libswscale/ops_internal.h
libswscale/vulkan/ops.c

index 56f9e534472f6e81a3fc3f4f37dfd934833219b3..3464a8af7c6a64fd30a36561e88b037c706bad35 100644 (file)
@@ -636,6 +636,18 @@ int sws_is_noop(const AVFrame *dst, const AVFrame *src)
     return 1;
 }
 
+void ff_sws_frame_from_avframe(SwsFrame *dst, const AVFrame *src)
+{
+    dst->format  = src->format;
+    dst->width   = src->width;
+    dst->height  = src->height;
+    dst->avframe = src;
+    for (int i = 0; i < FF_ARRAY_ELEMS(dst->data); i++) {
+        dst->data[i]     = src->data[i];
+        dst->linesize[i] = src->linesize[i];
+    }
+}
+
 #if CONFIG_UNSTABLE
 
 /* Returns the type suitable for a pixel after fully decoding/unpacking it */
index 509554492eb3cae4aa56310e4a58ca3615dc2b6c..d66851893fe17515f865ff46ba9aaa847f8f96fd 100644 (file)
@@ -173,4 +173,38 @@ int ff_sws_encode_colors(SwsContext *ctx, SwsPixelType type, SwsOpList *ops,
                          const SwsFormat *src, const SwsFormat *dst,
                          bool *incomplete);
 
+/**
+ * Represents a view into a single field of frame data.
+ *
+ * Ostensibly, this is a (non-compatible) subset of AVFrame, however, the
+ * semantics are VERY different.
+ *
+ * Unlike AVFrame, this struct does NOT own any data references. All buffers
+ * referenced by a SwsFrame are managed externally. This merely represents
+ * a view into data.
+ *
+ * This struct is not refcounted, and may be freely copied onto the stack.
+ */
+typedef struct SwsFrame {
+    /* Data buffers and line stride */
+    uint8_t *data[4];
+    int linesize[4];
+
+    /**
+     * Dimensions and format
+     */
+    int width, height;
+    enum AVPixelFormat format;
+
+    /**
+     * Pointer to the original AVFrame, if there is a 1:1 correspondence.
+     **/
+    const AVFrame *avframe;
+} SwsFrame;
+
+/**
+ * Initialize a SwsFrame from an AVFrame.
+ */
+void ff_sws_frame_from_avframe(SwsFrame *dst, const AVFrame *src);
+
 #endif /* SWSCALE_FORMAT_H */
index 6a17a12496b6c3b032be970048cfae707c827e4a..0ad7de09dba21375810b6d8777e903ec2fc9d8f7 100644 (file)
@@ -76,31 +76,32 @@ static int frame_alloc_planes(AVFrame *dst)
 
 static int pass_alloc_output(SwsPass *pass)
 {
-    if (!pass || pass->output->frame)
+    if (!pass || pass->output->avframe)
         return 0;
 
     SwsPassBuffer *buffer = pass->output;
-    AVFrame *frame = av_frame_alloc();
-    if (!frame)
+    AVFrame *avframe = av_frame_alloc();
+    if (!avframe)
         return AVERROR(ENOMEM);
-    frame->format = pass->format;
-    frame->width  = buffer->width;
-    frame->height = buffer->height;
+    avframe->format = pass->format;
+    avframe->width  = buffer->width;
+    avframe->height = buffer->height;
 
-    int ret = frame_alloc_planes(frame);
+    int ret = frame_alloc_planes(avframe);
     if (ret < 0) {
-        av_frame_free(&frame);
+        av_frame_free(&avframe);
         return ret;
     }
 
-    buffer->frame = frame;
+    buffer->avframe = avframe;
+    ff_sws_frame_from_avframe(&buffer->frame, avframe);
     return 0;
 }
 
 static void free_buffer(AVRefStructOpaque opaque, void *obj)
 {
     SwsPassBuffer *buffer = obj;
-    av_frame_free(&buffer->frame);
+    av_frame_free(&buffer->avframe);
 }
 
 SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum AVPixelFormat fmt,
@@ -163,7 +164,7 @@ static int pass_append(SwsGraph *graph, enum AVPixelFormat fmt, int w, int h,
     return 0;
 }
 
-static void frame_shift(const AVFrame *f, const int y, uint8_t *data[4])
+static void frame_shift(const SwsFrame *f, const int y, uint8_t *data[4])
 {
     for (int i = 0; i < 4; i++) {
         if (f->data[i])
@@ -173,7 +174,7 @@ static void frame_shift(const AVFrame *f, const int y, uint8_t *data[4])
     }
 }
 
-static void run_copy(const AVFrame *out, const AVFrame *in, int y, int h,
+static void run_copy(const SwsFrame *out, const SwsFrame *in, int y, int h,
                      const SwsPass *pass)
 {
     uint8_t *in_data[4], *out_data[4];
@@ -199,7 +200,7 @@ static void run_copy(const AVFrame *out, const AVFrame *in, int y, int h,
     }
 }
 
-static void run_rgb0(const AVFrame *out, const AVFrame *in, int y, int h,
+static void run_rgb0(const SwsFrame *out, const SwsFrame *in, int y, int h,
                      const SwsPass *pass)
 {
     SwsInternal *c = pass->priv;
@@ -220,7 +221,7 @@ static void run_rgb0(const AVFrame *out, const AVFrame *in, int y, int h,
     }
 }
 
-static void run_xyz2rgb(const AVFrame *out, const AVFrame *in, int y, int h,
+static void run_xyz2rgb(const SwsFrame *out, const SwsFrame *in, int y, int h,
                         const SwsPass *pass)
 {
     const SwsInternal *c = pass->priv;
@@ -229,7 +230,7 @@ static void run_xyz2rgb(const AVFrame *out, const AVFrame *in, int y, int h,
                     pass->width, h);
 }
 
-static void run_rgb2xyz(const AVFrame *out, const AVFrame *in, int y, int h,
+static void run_rgb2xyz(const SwsFrame *out, const SwsFrame *in, int y, int h,
                         const SwsPass *pass)
 {
     const SwsInternal *c = pass->priv;
@@ -250,7 +251,7 @@ static void free_legacy_swscale(void *priv)
     sws_free_context(&sws);
 }
 
-static void setup_legacy_swscale(const AVFrame *out, const AVFrame *in,
+static void setup_legacy_swscale(const SwsFrame *out, const SwsFrame *in,
                                  const SwsPass *pass)
 {
     SwsContext *sws = pass->priv;
@@ -283,7 +284,7 @@ static inline SwsContext *slice_ctx(const SwsPass *pass, int y)
     return sws;
 }
 
-static void run_legacy_unscaled(const AVFrame *out, const AVFrame *in,
+static void run_legacy_unscaled(const SwsFrame *out, const SwsFrame *in,
                                 int y, int h, const SwsPass *pass)
 {
     SwsContext *sws = slice_ctx(pass, y);
@@ -295,7 +296,7 @@ static void run_legacy_unscaled(const AVFrame *out, const AVFrame *in,
                         out->data, out->linesize);
 }
 
-static void run_legacy_swscale(const AVFrame *out, const AVFrame *in,
+static void run_legacy_swscale(const SwsFrame *out, const SwsFrame *in,
                                int y, int h, const SwsPass *pass)
 {
     SwsContext *sws = slice_ctx(pass, y);
@@ -632,7 +633,7 @@ static void free_lut3d(void *priv)
     ff_sws_lut3d_free(&lut);
 }
 
-static void setup_lut3d(const AVFrame *out, const AVFrame *in, const SwsPass *pass)
+static void setup_lut3d(const SwsFrame *out, const SwsFrame *in, const SwsPass *pass)
 {
     SwsLut3D *lut = pass->priv;
 
@@ -640,7 +641,7 @@ static void setup_lut3d(const AVFrame *out, const AVFrame *in, const SwsPass *pa
     ff_sws_lut3d_update(lut, &pass->graph->src.color);
 }
 
-static void run_lut3d(const AVFrame *out, const AVFrame *in, int y, int h,
+static void run_lut3d(const SwsFrame *out, const SwsFrame *in, int y, int h,
                       const SwsPass *pass)
 {
     SwsLut3D *lut = pass->priv;
@@ -781,14 +782,6 @@ int ff_sws_graph_create(SwsContext *ctx, const SwsFormat *dst, const SwsFormat *
     graph->field = field;
     graph->opts_copy = *ctx;
 
-    for (int i = 0; i < FF_ARRAY_ELEMS(graph->field_tmp); i++) {
-        graph->field_tmp[i] = av_frame_alloc();
-        if (!graph->field_tmp[i]) {
-            ret = AVERROR(ENOMEM);
-            goto error;
-        }
-    }
-
     ret = avpriv_slicethread_create(&graph->slicethread, (void *) graph,
                                     sws_graph_worker, NULL, ctx->threads);
     if (ret == AVERROR(ENOSYS))
@@ -827,9 +820,6 @@ void ff_sws_graph_free(SwsGraph **pgraph)
     }
     av_free(graph->passes);
 
-    for (int i = 0; i < FF_ARRAY_ELEMS(graph->field_tmp); i++)
-        av_frame_free(&graph->field_tmp[i]);
-
     av_free(graph);
     *pgraph = NULL;
 }
@@ -875,22 +865,21 @@ void ff_sws_graph_update_metadata(SwsGraph *graph, const SwsColor *color)
     ff_color_update_dynamic(&graph->src.color, color);
 }
 
-static const AVFrame *get_field(SwsGraph *graph, const AVFrame *frame,
-                                AVFrame *restrict tmp)
+static void get_field(SwsGraph *graph, const AVFrame *avframe, SwsFrame *frame)
 {
-    if (!(frame->flags & AV_FRAME_FLAG_INTERLACED)) {
+    ff_sws_frame_from_avframe(frame, avframe);
+
+    if (!(avframe->flags & AV_FRAME_FLAG_INTERLACED)) {
         av_assert1(!graph->field);
-        return frame;
+        return;
     }
 
-    *tmp = *frame;
-
     if (graph->field == FIELD_BOTTOM) {
         /* Odd rows, offset by one line */
         const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(frame->format);
         for (int i = 0; i < 4; i++) {
-            if (tmp->data[i])
-                tmp->data[i] += frame->linesize[i];
+            if (frame->data[i])
+                frame->data[i] += frame->linesize[i];
             if (desc->flags & AV_PIX_FMT_FLAG_PAL)
                 break;
         }
@@ -898,23 +887,23 @@ static const AVFrame *get_field(SwsGraph *graph, const AVFrame *frame,
 
     /* Take only every second line */
     for (int i = 0; i < 4; i++)
-        tmp->linesize[i] <<= 1;
-
-    return tmp;
+        frame->linesize[i] <<= 1;
 }
 
 void ff_sws_graph_run(SwsGraph *graph, const AVFrame *dst, const AVFrame *src)
 {
     av_assert0(dst->format == graph->dst.hw_format || dst->format == graph->dst.format);
     av_assert0(src->format == graph->src.hw_format || src->format == graph->src.format);
-    const AVFrame *src_field = get_field(graph, src, graph->field_tmp[0]);
-    const AVFrame *dst_field = get_field(graph, dst, graph->field_tmp[1]);
+
+    SwsFrame src_field, dst_field;
+    get_field(graph, dst, &dst_field);
+    get_field(graph, src, &src_field);
 
     for (int i = 0; i < graph->num_passes; i++) {
         const SwsPass *pass = graph->passes[i];
         graph->exec.pass   = pass;
-        graph->exec.input  = pass->input ? pass->input->output->frame : src_field;
-        graph->exec.output = pass->output->frame ? pass->output->frame : dst_field;
+        graph->exec.input  = pass->input ? &pass->input->output->frame : &src_field;
+        graph->exec.output = pass->output->avframe ? &pass->output->frame : &dst_field;
         if (pass->setup)
             pass->setup(graph->exec.output, graph->exec.input, pass);
         avpriv_slicethread_execute(graph->slicethread, pass->num_slices, 0);
index 9ddf2ad1c9fcbc40b972940f3d4e072702280379..88d82452185fc15ebadae3fcf8cabfd595a962d0 100644 (file)
@@ -42,15 +42,17 @@ typedef struct SwsGraph SwsGraph;
  * Output `h` lines of filtered data. `out` and `in` point to the
  * start of the image buffer for this pass.
  */
-typedef void (*sws_filter_run_t)(const AVFrame *out, const AVFrame *in,
+typedef void (*sws_filter_run_t)(const SwsFrame *out, const SwsFrame *in,
                                  int y, int h, const SwsPass *pass);
 
 /**
  * Represents an allocated output buffer for a filter pass.
  */
 typedef struct SwsPassBuffer {
+    SwsFrame frame;
+
     int width, height; /* dimensions of this buffer */
-    AVFrame *frame;
+    AVFrame *avframe;  /* backing storage for `frame` */
 } SwsPassBuffer;
 
 /**
@@ -86,7 +88,7 @@ struct SwsPass {
     /**
      * Called once from the main thread before running the filter. Optional.
      */
-    void (*setup)(const AVFrame *out, const AVFrame *in, const SwsPass *pass);
+    void (*setup)(const SwsFrame *out, const SwsFrame *in, const SwsPass *pass);
 
     /**
      * Optional private state and associated free() function.
@@ -123,20 +125,14 @@ typedef struct SwsGraph {
     SwsFormat src, dst;
     int field;
 
-    /**
-     * Temporary storage to hold individual fields of the input frames.
-     * No actual ownership over the data.
-     */
-    AVFrame *field_tmp[2];
-
     /**
      * Temporary execution state inside ff_sws_graph_run(); used to pass
      * data to worker threads.
      */
     struct {
         const SwsPass *pass; /* current filter pass */
-        const AVFrame *input; /* current filter pass input/output */
-        const AVFrame *output;
+        const SwsFrame *input; /* current filter pass input/output */
+        const SwsFrame *output;
     } exec;
 } SwsGraph;
 
index b3db75868246fbfeec6d936e1fdc7af6ed76d95f..9f53657d8afba2b4689948b707308da8f7befc70 100644 (file)
@@ -945,7 +945,7 @@ static inline void get_row_data(const SwsOpPass *p, const int y,
         out[i] = base->out[i] + (y >> base->out_sub_y[i]) * base->out_stride[i];
 }
 
-static void op_pass_setup(const AVFrame *out, const AVFrame *in,
+static void op_pass_setup(const SwsFrame *out, const SwsFrame *in,
                           const SwsPass *pass)
 {
     const AVPixFmtDescriptor *indesc  = av_pix_fmt_desc_get(in->format);
@@ -1009,8 +1009,8 @@ static void op_pass_setup(const AVFrame *out, const AVFrame *in,
         exec->out_bump[i] = exec->out_stride[i] - blocks_main * exec->block_size_out;
     }
 
-    exec->src_frame_ptr = in;
-    exec->dst_frame_ptr = out;
+    exec->in_frame  = in;
+    exec->out_frame = out;
 }
 
 /* Dispatch kernel over the last column of the image using memcpy */
@@ -1079,7 +1079,7 @@ handle_tail(const SwsOpPass *p, SwsOpExec *exec,
     }
 }
 
-static void op_pass_run(const AVFrame *out, const AVFrame *in, const int y,
+static void op_pass_run(const SwsFrame *out, const SwsFrame *in, const int y,
                         const int h, const SwsPass *pass)
 {
     const SwsOpPass *p = pass->priv;
index e07ef5285f146080ccf26a0dc5f6db5d0929a642..808b9ba4e0b9fd4d90a0bb62207d446991cb4cc5 100644 (file)
@@ -80,8 +80,9 @@ typedef struct SwsOpExec {
     uint8_t in_sub_y[4], out_sub_y[4];
     uint8_t in_sub_x[4], out_sub_x[4];
 
-    const AVFrame *src_frame_ptr;
-    const AVFrame *dst_frame_ptr;
+    /* Pointers back to the original SwsFrame */
+    const SwsFrame *in_frame;
+    const SwsFrame *out_frame;
 } SwsOpExec;
 
 static_assert(sizeof(SwsOpExec) == 24 * sizeof(void *) +
index b77eb924c70ea8bd1176b74a05bb26df9fd9abab..b0ff1dc6f30d0606c50a61d8498c1449f93653f4 100644 (file)
@@ -100,8 +100,8 @@ static void process(const SwsOpExec *exec, const void *priv,
     FFVulkanFunctions *vk = &p->s->vkctx.vkfn;
     ff_vk_exec_start(&p->s->vkctx, ec);
 
-    AVFrame *src_f = (AVFrame *)exec->src_frame_ptr;
-    AVFrame *dst_f = (AVFrame *)exec->dst_frame_ptr;
+    AVFrame *src_f = (AVFrame *) exec->in_frame->avframe;
+    AVFrame *dst_f = (AVFrame *) exec->out_frame->avframe;
     ff_vk_exec_add_dep_frame(&p->s->vkctx, ec, src_f,
                              VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
                              VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT);