* [FFmpeg-devel] [PR] swscale/graph: switch SwsPass.output to refstruct (PR #21653)
@ 2026-02-05 15:07 Niklas Haas via ffmpeg-devel
0 siblings, 0 replies; only message in thread
From: Niklas Haas via ffmpeg-devel @ 2026-02-05 15:07 UTC (permalink / raw)
To: ffmpeg-devel; +Cc: Niklas Haas
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 <git@haasn.dev>
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 <git@haasn.dev>
---
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 <git@haasn.dev>
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 <git@haasn.dev>
---
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 -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-02-05 15:08 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-02-05 15:07 [FFmpeg-devel] [PR] swscale/graph: switch SwsPass.output to refstruct (PR #21653) Niklas Haas via ffmpeg-devel
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git