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

Allows multiple passes to share a single output buffer reference. We always 
allocate an output buffer so that subpasses can share the same output buffer 
reference while still allowing that reference to implicitly point to the final 
output image.


>From e898205378fd3e25354f79656abab0fb690ac0aa Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sun, 18 Jan 2026 18:39:01 +0100
Subject: [PATCH 1/2] swscale/graph: switch SwsPass.output to refstruct

Allows multiple passes to share a single output buffer reference. We always
allocate an output buffer so that subpasses can share the same output buffer
reference while still allowing that reference to implicitly point to the
final output image.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/graph.c | 67 +++++++++++++++++++++++++++++++++-------------
 libswscale/graph.h |  2 +-
 2 files changed, 49 insertions(+), 20 deletions(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index 9d9ca53b00..c5c6fb059f 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -25,6 +25,7 @@
 #include "libavutil/mem.h"
 #include "libavutil/opt.h"
 #include "libavutil/pixdesc.h"
+#include "libavutil/refstruct.h"
 #include "libavutil/slicethread.h"
 
 #include "libswscale/swscale.h"
@@ -38,13 +39,32 @@
 
 static int pass_alloc_output(SwsPass *pass)
 {
-    if (!pass || pass->output.fmt != AV_PIX_FMT_NONE)
+    if (!pass || pass->output->fmt != AV_PIX_FMT_NONE)
         return 0;
-    pass->output.fmt = pass->format;
-    return av_image_alloc(pass->output.data, pass->output.linesize, 
pass->width,
+    pass->output->fmt = pass->format;
+    return av_image_alloc(pass->output->data, pass->output->linesize, 
pass->width,
                           pass->num_slices * pass->slice_h, pass->format, 64);
 }
 
+static void pass_free(SwsPass *pass)
+{
+    if (!pass)
+        return;
+
+    if (pass->free)
+        pass->free(pass->priv);
+
+    av_refstruct_unref(&pass->output);
+    av_free(pass);
+}
+
+static void free_output_img(AVRefStructOpaque opaque, void *obj)
+{
+    SwsImg *img = obj;
+    if (img->fmt != AV_PIX_FMT_NONE)
+        av_free(img->data[0]);
+}
+
 SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum AVPixelFormat fmt,
                                int width, int height, SwsPass *input,
                                int align, void *priv, sws_filter_run_t run)
@@ -61,11 +81,23 @@ SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum 
AVPixelFormat fmt,
     pass->width  = width;
     pass->height = height;
     pass->input  = input;
-    pass->output.fmt = AV_PIX_FMT_NONE;
+
+    /**
+     * Allocate an output SwsImg in any case, even if we end up not using it,
+     * because other passes may want to ref this SwsImg and still inherit the
+     * decision about whether or not a buffer needs to be allocated.
+     */
+    pass->output = av_refstruct_alloc_ext(sizeof(*pass->output), 0, NULL,
+                                          free_output_img);
+    if (!pass->output) {
+        pass_free(pass);
+        return NULL;
+    }
+    pass->output->fmt = AV_PIX_FMT_NONE;
 
     ret = pass_alloc_output(input);
     if (ret < 0) {
-        av_free(pass);
+        pass_free(pass);
         return NULL;
     }
 
@@ -79,8 +111,11 @@ SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum 
AVPixelFormat fmt,
     }
 
     ret = av_dynarray_add_nofree(&graph->passes, &graph->num_passes, pass);
-    if (ret < 0)
-        av_freep(&pass);
+    if (ret < 0) {
+        pass_free(pass);
+        return NULL;
+    }
+
     return pass;
 }
 
@@ -679,8 +714,8 @@ static void sws_graph_worker(void *priv, int jobnr, int 
threadnr, int nb_jobs,
 {
     SwsGraph *graph = priv;
     const SwsPass *pass = graph->exec.pass;
-    const SwsImg *input  = pass->input ? &pass->input->output : 
&graph->exec.input;
-    const SwsImg *output = pass->output.fmt != AV_PIX_FMT_NONE ? &pass->output 
: &graph->exec.output;
+    const SwsImg *input  = pass->input ? pass->input->output : 
&graph->exec.input;
+    const SwsImg *output = pass->output->fmt != AV_PIX_FMT_NONE ? pass->output 
: &graph->exec.output;
     const int slice_y = jobnr * pass->slice_h;
     const int slice_h = FFMIN(pass->slice_h, pass->height - slice_y);
 
@@ -733,14 +768,8 @@ void ff_sws_graph_free(SwsGraph **pgraph)
 
     avpriv_slicethread_free(&graph->slicethread);
 
-    for (int i = 0; i < graph->num_passes; i++) {
-        SwsPass *pass = graph->passes[i];
-        if (pass->free)
-            pass->free(pass->priv);
-        if (pass->output.fmt != AV_PIX_FMT_NONE)
-            av_free(pass->output.data[0]);
-        av_free(pass);
-    }
+    for (int i = 0; i < graph->num_passes; i++)
+        pass_free(graph->passes[i]);
     av_free(graph->passes);
 
     av_free(graph);
@@ -804,8 +833,8 @@ void ff_sws_graph_run(SwsGraph *graph, uint8_t *const 
out_data[4],
         const SwsPass *pass = graph->passes[i];
         graph->exec.pass = pass;
         if (pass->setup) {
-            pass->setup(pass->output.fmt != AV_PIX_FMT_NONE ? &pass->output : 
out,
-                        pass->input ? &pass->input->output : in, pass);
+            pass->setup(pass->output->fmt != AV_PIX_FMT_NONE ? pass->output : 
out,
+                        pass->input ? pass->input->output : in, pass);
         }
         avpriv_slicethread_execute(graph->slicethread, pass->num_slices, 0);
     }
diff --git a/libswscale/graph.h b/libswscale/graph.h
index b829bac88c..31e5de1a75 100644
--- a/libswscale/graph.h
+++ b/libswscale/graph.h
@@ -88,7 +88,7 @@ struct SwsPass {
     /**
      * Filter output buffer. Allocated on demand and freed automatically.
      */
-    SwsImg output;
+    SwsImg *output; /* refstruct */
 
     /**
      * Called once from the main thread before running the filter. Optional.
-- 
2.52.0


>From 01398481cb74f7eeb412e8e5125af0797285cd03 Mon Sep 17 00:00:00 2001
From: Niklas Haas <[email protected]>
Date: Sun, 18 Jan 2026 19:28:37 +0100
Subject: [PATCH 2/2] swscale/graph: allow passes to reuse an existing output
 buffer ref

Adds an extra optional argument to ff_sws_graph_add_pass which allows passes
to inherit the output buffer from a difference pass. Allows, for example,
multiple passes to write to subsets of the output planes, or for a pass to
directly modify a previous pass's contents in-place.

Sponsored-by: Sovereign Tech Fund
Signed-off-by: Niklas Haas <[email protected]>
---
 libswscale/graph.c | 40 +++++++++++++++++++++++-----------------
 libswscale/graph.h |  4 +++-
 libswscale/ops.c   |  2 +-
 3 files changed, 27 insertions(+), 19 deletions(-)

diff --git a/libswscale/graph.c b/libswscale/graph.c
index c5c6fb059f..4fcc080548 100644
--- a/libswscale/graph.c
+++ b/libswscale/graph.c
@@ -67,7 +67,8 @@ static void free_output_img(AVRefStructOpaque opaque, void 
*obj)
 
 SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum AVPixelFormat fmt,
                                int width, int height, SwsPass *input,
-                               int align, void *priv, sws_filter_run_t run)
+                               SwsImg *output, int align, void *priv,
+                               sws_filter_run_t run)
 {
     int ret;
     SwsPass *pass = av_mallocz(sizeof(*pass));
@@ -82,18 +83,23 @@ SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum 
AVPixelFormat fmt,
     pass->height = height;
     pass->input  = input;
 
-    /**
-     * Allocate an output SwsImg in any case, even if we end up not using it,
-     * because other passes may want to ref this SwsImg and still inherit the
-     * decision about whether or not a buffer needs to be allocated.
-     */
-    pass->output = av_refstruct_alloc_ext(sizeof(*pass->output), 0, NULL,
-                                          free_output_img);
-    if (!pass->output) {
-        pass_free(pass);
-        return NULL;
+    if (output) {
+        av_assert0(output->fmt == AV_PIX_FMT_NONE || output->fmt == fmt);
+        pass->output = av_refstruct_ref(output);
+    } else {
+        /**
+        * Allocate an output SwsImg in any case, even if we end up not using 
it,
+        * because other passes may want to ref this SwsImg and still inherit 
the
+        * decision about whether or not a buffer needs to be allocated.
+        */
+        pass->output = av_refstruct_alloc_ext(sizeof(*pass->output), 0, NULL,
+                                            free_output_img);
+        if (!pass->output) {
+            pass_free(pass);
+            return NULL;
+        }
+        pass->output->fmt = AV_PIX_FMT_NONE;
     }
-    pass->output->fmt = AV_PIX_FMT_NONE;
 
     ret = pass_alloc_output(input);
     if (ret < 0) {
@@ -123,7 +129,7 @@ SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum 
AVPixelFormat fmt,
 static int pass_append(SwsGraph *graph, enum AVPixelFormat fmt, int w, int h,
                        SwsPass **pass, int align, void *priv, sws_filter_run_t 
run)
 {
-    SwsPass *new = ff_sws_graph_add_pass(graph, fmt, w, h, *pass, align, priv, 
run);
+    SwsPass *new = ff_sws_graph_add_pass(graph, fmt, w, h, *pass, NULL, align, 
priv, run);
     if (!new)
         return AVERROR(ENOMEM);
     *pass = new;
@@ -368,7 +374,7 @@ static int init_legacy_subpass(SwsGraph *graph, SwsContext 
*sws,
         }
     }
 
-    pass = ff_sws_graph_add_pass(graph, sws->dst_format, dst_w, dst_h, input, 
align, sws,
+    pass = ff_sws_graph_add_pass(graph, sws->dst_format, dst_w, dst_h, input, 
NULL, align, sws,
                                  c->convert_unscaled ? run_legacy_unscaled : 
run_legacy_swscale);
     if (!pass) {
         sws_free_context(&sws);
@@ -659,8 +665,8 @@ static int adapt_colors(SwsGraph *graph, SwsFormat src, 
SwsFormat dst,
         return ret;
     }
 
-    pass = ff_sws_graph_add_pass(graph, fmt_out, src.width, src.height,
-                                 input, 1, lut, run_lut3d);
+    pass = ff_sws_graph_add_pass(graph, fmt_out, src.width, src.height, input,
+                                 NULL, 1, lut, run_lut3d);
     if (!pass) {
         ff_sws_lut3d_free(&lut);
         return AVERROR(ENOMEM);
@@ -701,7 +707,7 @@ static int init_passes(SwsGraph *graph)
 
         /* Add threaded memcpy pass */
         pass = ff_sws_graph_add_pass(graph, dst.format, dst.width, dst.height,
-                                     pass, 1, NULL, run_copy);
+                                     pass, NULL, 1, NULL, run_copy);
         if (!pass)
             return AVERROR(ENOMEM);
     }
diff --git a/libswscale/graph.h b/libswscale/graph.h
index 31e5de1a75..c96a216407 100644
--- a/libswscale/graph.h
+++ b/libswscale/graph.h
@@ -151,6 +151,7 @@ int ff_sws_graph_create(SwsContext *ctx, const SwsFormat 
*dst, const SwsFormat *
  * @param w      Width of the output image.
  * @param h      Height of the output image.
  * @param input  Previous pass to read from, or NULL for the input image.
+ * @param output If non-NULL, re-use this SwsImg ref as the output buffer.
  * @param align  Minimum slice alignment for this pass, or 0 for no threading.
  * @param priv   Private state for the filter run function.
  * @param run    Filter function to run.
@@ -158,7 +159,8 @@ int ff_sws_graph_create(SwsContext *ctx, const SwsFormat 
*dst, const SwsFormat *
  */
 SwsPass *ff_sws_graph_add_pass(SwsGraph *graph, enum AVPixelFormat fmt,
                                int width, int height, SwsPass *input,
-                               int align, void *priv, sws_filter_run_t run);
+                               SwsImg *output, int align, void *priv,
+                               sws_filter_run_t run);
 
 /**
  * Uninitialize any state associate with this filter graph and free it.
diff --git a/libswscale/ops.c b/libswscale/ops.c
index 83eb8e162e..dac646d3f7 100644
--- a/libswscale/ops.c
+++ b/libswscale/ops.c
@@ -1076,7 +1076,7 @@ int ff_sws_compile_pass(SwsGraph *graph, SwsOpList *ops, 
int flags, SwsFormat ds
     };
 
     pass = ff_sws_graph_add_pass(graph, dst.format, dst.width, dst.height, 
input,
-                                 1, p, op_pass_run);
+                                 NULL, 1, p, op_pass_run);
     if (!pass) {
         ret = AVERROR(ENOMEM);
         goto fail;
-- 
2.52.0

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

Reply via email to