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]
