FFmpeg
ops_dispatch.c
Go to the documentation of this file.
1 /**
2  * Copyright (C) 2025 Niklas Haas
3  *
4  * This file is part of FFmpeg.
5  *
6  * FFmpeg is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * FFmpeg is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with FFmpeg; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19  */
20 
21 #include "libavutil/avassert.h"
22 #include "libavutil/cpu.h"
23 #include "libavutil/mathematics.h"
24 #include "libavutil/mem.h"
25 #include "libavutil/mem_internal.h"
26 #include "libavutil/refstruct.h"
27 
28 #include "ops.h"
29 #include "ops_internal.h"
30 #include "ops_dispatch.h"
31 #include "swscale_internal.h"
32 
33 typedef struct SwsOpPass {
37  size_t num_blocks;
42  int planes_in;
46  int idx_in[4];
47  int idx_out[4];
48  int *offsets_y;
52  bool memcpy_out;
53  size_t tail_blocks;
54  uint8_t *tail_buf; /* extra memory for fixing unpadded tails */
55  unsigned int tail_buf_size;
56 } SwsOpPass;
57 
58 static int compile_backend(SwsContext *ctx, const SwsOpBackend *backend,
59  const SwsOpList *ops, SwsCompiledOp *out)
60 {
61  SwsOpList *copy;
62  SwsCompiledOp compiled = {0};
63  int ret = 0;
64 
66  if (!copy)
67  return AVERROR(ENOMEM);
68 
69  /* Ensure these are always set during compilation */
71 
72  ret = backend->compile(ctx, copy, &compiled);
73  if (ret < 0) {
74  int msg_lev = ret == AVERROR(ENOTSUP) ? AV_LOG_TRACE : AV_LOG_ERROR;
75  av_log(ctx, msg_lev, "Backend '%s' failed to compile operations: %s\n",
76  backend->name, av_err2str(ret));
77  goto fail;
78  }
79 
80  compiled.backend = backend;
81  *out = compiled;
82 
83  av_log(ctx, AV_LOG_VERBOSE, "Compiled using backend '%s': "
84  "block size = %d, over-read = {%d %d %d %d}, over-write = {%d %d %d %d}, "
85  "cpu flags = 0x%x\n", backend->name, out->block_size,
86  out->over_read[0], out->over_read[1],
87  out->over_read[2], out->over_read[3],
88  out->over_write[0], out->over_write[1],
89  out->over_write[2], out->over_write[3],
90  out->cpu_flags);
91 
93 
94 fail:
96  return ret;
97 }
98 
100  const SwsOpList *ops, SwsCompiledOp *out)
101 {
102  if (backend)
103  return compile_backend(ctx, backend, ops, out);
104 
105  const SwsBackend enabled = ff_sws_enabled_backends(ctx);
106  for (int n = 0; ff_sws_op_backends[n]; n++) {
107  const SwsOpBackend *backend = ff_sws_op_backends[n];
108  if (ops->src.hw_format != backend->hw_format ||
109  ops->dst.hw_format != backend->hw_format ||
110  !(enabled & backend->flags))
111  continue;
112  if (compile_backend(ctx, backend, ops, out) < 0)
113  continue;
114 
115  return 0;
116  }
117 
118  return AVERROR(ENOTSUP);
119 }
120 
122 {
123  if (comp->free)
124  comp->free(comp->priv);
125 
126  *comp = (SwsCompiledOp) {0};
127 }
128 
129 static void op_pass_free(void *ptr)
130 {
131  SwsOpPass *p = ptr;
132  if (!p)
133  return;
134 
135  ff_sws_compiled_op_unref(&p->comp);
136  av_refstruct_unref(&p->offsets_y);
137  av_free(p->exec_base.in_bump_y);
138  av_free(p->exec_base.in_offset_x);
139  av_free(p->tail_buf);
140  av_free(p);
141 }
142 
143 static inline void get_row_data(const SwsOpPass *p, const int y_dst,
144  const uint8_t *in[4], uint8_t *out[4])
145 {
146  const SwsOpExec *base = &p->exec_base;
147  const int y_src = p->offsets_y ? p->offsets_y[y_dst] : y_dst;
148  for (int i = 0; i < p->planes_in; i++)
149  in[i] = base->in[i] + (y_src >> base->in_sub_y[i]) * base->in_stride[i];
150  for (int i = 0; i < p->planes_out; i++)
151  out[i] = base->out[i] + (y_dst >> base->out_sub_y[i]) * base->out_stride[i];
152 }
153 
154 static inline int get_lines_in(const SwsOpPass *p, const int y, const int h,
155  const int plane)
156 {
157  const SwsOpExec *base = &p->exec_base;
158  if (!p->offsets_y)
159  return h >> base->in_sub_y[plane];
160 
161  const int y0 = p->offsets_y[y] >> base->in_sub_y[plane];
162  const int y1 = p->offsets_y[y + h - 1] >> base->in_sub_y[plane];
163  return y1 - y0 + 1;
164 }
165 
166 static inline size_t pixel_bytes(size_t pixels, int pixel_bits,
167  enum AVRounding rounding)
168 {
169  const uint64_t bits = (uint64_t) pixels * pixel_bits;
170  switch (rounding) {
171  case AV_ROUND_ZERO:
172  case AV_ROUND_DOWN:
173  return bits >> 3;
174  case AV_ROUND_INF:
175  case AV_ROUND_UP:
176  return (bits + 7) >> 3;
177  default:
178  av_unreachable("Invalid rounding mode");
179  return (size_t) -1;
180  }
181 }
182 
183 static size_t safe_bytes_pad(int linesize, int plane_pad)
184 {
185  av_assert1(linesize);
186  int64_t safe_bytes = FFABS((int64_t) linesize) - plane_pad;
187  return FFMAX(safe_bytes, 0);
188 }
189 
190 static size_t safe_blocks_offset(size_t num_blocks, unsigned block_size,
191  ptrdiff_t safe_offset,
192  const int32_t *offset_bytes)
193 {
194  size_t safe_blocks = num_blocks;
195  while (safe_blocks && offset_bytes[safe_blocks * block_size - 1] > safe_offset)
196  safe_blocks--;
197  return safe_blocks;
198 }
199 
200 static int op_pass_setup(const SwsFrame *out, const SwsFrame *in,
201  const SwsPass *pass)
202 {
203  const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(in->format);
204  const bool float_in = indesc->flags & AV_PIX_FMT_FLAG_FLOAT;
205 
206  SwsOpPass *p = pass->priv;
207  SwsOpExec *exec = &p->exec_base;
208  const SwsCompiledOp *comp = &p->comp;
209 
210  /* Set up main loop parameters */
211  const unsigned block_size = comp->block_size;
212  const size_t num_blocks = (pass->width + block_size - 1) / block_size;
213  const size_t aligned_w = num_blocks * block_size;
214  if (aligned_w < pass->width) /* overflow */
215  return AVERROR(EINVAL);
216  p->num_blocks = num_blocks;
217  p->memcpy_first = false;
218  p->memcpy_last = false;
219  p->memcpy_out = false;
220 
221  size_t safe_blocks = num_blocks;
222  for (int i = 0; i < p->planes_in; i++) {
223  const int idx = p->idx_in[i];
224  size_t input_bytes = in->linesize[idx];
225  if (p->filter_size_h && float_in) {
226  /* Floating point inputs may contain NaN / Infinity in the padding */
227  const int plane_w = AV_CEIL_RSHIFT(in->width, exec->in_sub_x[i]);
228  input_bytes = pixel_bytes(plane_w, p->pixel_bits_in, AV_ROUND_UP);
229  }
230 
231  size_t safe_bytes = safe_bytes_pad(input_bytes, comp->over_read[i]);
232  size_t safe_blocks_in;
233  if (exec->in_offset_x) {
234  size_t filter_size = pixel_bytes(p->filter_size_h, p->pixel_bits_in,
235  AV_ROUND_UP);
236  safe_blocks_in = safe_blocks_offset(num_blocks, block_size,
237  safe_bytes - filter_size,
238  exec->in_offset_x);
239  } else {
240  safe_blocks_in = safe_bytes / exec->block_size_in[i];
241  }
242 
243  if (safe_blocks_in < num_blocks) {
244  p->memcpy_first |= in->linesize[idx] < 0;
245  p->memcpy_last |= in->linesize[idx] > 0;
246  safe_blocks = FFMIN(safe_blocks, safe_blocks_in);
247  }
248 
249  size_t loop_size = num_blocks * exec->block_size_in[i];
250  exec->in[i] = in->data[idx];
251  exec->in_stride[i] = in->linesize[idx];
252  exec->in_bump[i] = in->linesize[idx] - loop_size;
253  }
254 
255  for (int i = 0; i < p->planes_out; i++) {
256  const int idx = p->idx_out[i];
257  size_t safe_bytes = safe_bytes_pad(out->linesize[idx], comp->over_write[i]);
258  size_t safe_blocks_out = safe_bytes / exec->block_size_out[i];
259  if (safe_blocks_out < num_blocks) {
260  p->memcpy_out = true;
261  safe_blocks = FFMIN(safe_blocks, safe_blocks_out);
262  }
263 
264  size_t loop_size = num_blocks * exec->block_size_out[i];
265  exec->out[i] = out->data[idx];
266  exec->out_stride[i] = out->linesize[idx];
267  exec->out_bump[i] = out->linesize[idx] - loop_size;
268  }
269 
270  const bool memcpy_in = p->memcpy_first || p->memcpy_last;
271  if (!memcpy_in && !p->memcpy_out) {
272  av_assert0(safe_blocks == num_blocks);
273  return 0;
274  }
275 
276  /* Set-up tail section parameters and buffers */
277  SwsOpExec *tail = &p->exec_tail;
278  const int align = av_cpu_max_align();
279  size_t alloc_size = 0;
280  *tail = *exec;
281 
282  const size_t safe_width = safe_blocks * block_size;
283  const size_t tail_size = pass->width - safe_width;
284  p->tail_off_out = pixel_bytes(safe_width, p->pixel_bits_out, AV_ROUND_DOWN);
285  p->tail_size_out = pixel_bytes(tail_size, p->pixel_bits_out, AV_ROUND_UP);
286  p->tail_blocks = num_blocks - safe_blocks;
287 
288  if (exec->in_offset_x) {
289  p->tail_off_in = exec->in_offset_x[safe_width];
290  p->tail_size_in = exec->in_offset_x[pass->width - 1] - p->tail_off_in;
291  p->tail_size_in += pixel_bytes(p->filter_size_h, p->pixel_bits_in, AV_ROUND_UP);
292  } else {
293  p->tail_off_in = pixel_bytes(safe_width, p->pixel_bits_in, AV_ROUND_DOWN);
294  p->tail_size_in = pixel_bytes(tail_size, p->pixel_bits_in, AV_ROUND_UP);
295  }
296 
297  const size_t alloc_width = aligned_w - safe_width;
298  for (int i = 0; memcpy_in && i < p->planes_in; i++) {
299  size_t needed_size;
300  if (exec->in_offset_x) {
301  /* The input offset map is already padded to multiples of the block
302  * size, and clamps the input offsets to the image boundaries; so
303  * we just need to compensate for the comp->over_read */
304  needed_size = p->tail_size_in;
305  } else {
306  needed_size = pixel_bytes(alloc_width, p->pixel_bits_in, AV_ROUND_UP);
307  }
308  size_t loop_size = p->tail_blocks * exec->block_size_in[i];
309  tail->in_stride[i] = FFALIGN(needed_size + comp->over_read[i], align);
310  tail->in_bump[i] = tail->in_stride[i] - loop_size;
311  alloc_size += tail->in_stride[i] * in->height;
312  }
313 
314  for (int i = 0; p->memcpy_out && i < p->planes_out; i++) {
315  size_t needed_size = pixel_bytes(alloc_width, p->pixel_bits_out, AV_ROUND_UP);
316  size_t loop_size = p->tail_blocks * exec->block_size_out[i];
317  tail->out_stride[i] = FFALIGN(needed_size + comp->over_write[i], align);
318  tail->out_bump[i] = tail->out_stride[i] - loop_size;
319  alloc_size += tail->out_stride[i] * out->height;
320  }
321 
322  if (memcpy_in && exec->in_offset_x) {
323  /* `in_offset_x` is indexed relative to the line start, not the start
324  * of the section being processed; so we need to over-allocate this
325  * array to the full width of the image, even though we will only
326  * partially fill in the offsets relevant to the tail region */
327  alloc_size += aligned_w * sizeof(*exec->in_offset_x);
328  }
329 
330  av_fast_mallocz(&p->tail_buf, &p->tail_buf_size, alloc_size);
331  if (!p->tail_buf)
332  return AVERROR(ENOMEM);
333 
334  uint8_t *tail_buf = p->tail_buf;
335  for (int i = 0; memcpy_in && i < p->planes_in; i++) {
336  tail->in[i] = tail_buf;
337  tail_buf += tail->in_stride[i] * in->height;
338  }
339 
340  for (int i = 0; p->memcpy_out && i < p->planes_out; i++) {
341  tail->out[i] = tail_buf;
342  tail_buf += tail->out_stride[i] * out->height;
343  }
344 
345  if (memcpy_in && exec->in_offset_x) {
346  tail->in_offset_x = (int32_t *) tail_buf;
347  for (int i = safe_width; i < aligned_w; i++)
348  tail->in_offset_x[i] = exec->in_offset_x[i] - p->tail_off_in;
349  }
350 
351  return 0;
352 }
353 
354 static void copy_lines(uint8_t *dst, const size_t dst_stride,
355  const uint8_t *src, const size_t src_stride,
356  const int h, const size_t bytes)
357 {
358  for (int y = 0; y < h; y++) {
359  memcpy(dst, src, bytes);
360  dst += dst_stride;
361  src += src_stride;
362  }
363 }
364 
365 static void op_pass_run(const SwsFrame *out, const SwsFrame *in, const int y,
366  const int h, const SwsPass *pass)
367 {
368  const SwsOpPass *p = pass->priv;
369  const SwsCompiledOp *comp = &p->comp;
370 
371  /* Fill exec metadata for this slice */
372  DECLARE_ALIGNED_32(SwsOpExec, exec) = p->exec_base;
373  exec.slice_y = y;
374  exec.slice_h = h;
375 
376  /**
377  * To ensure safety, we need to consider the following:
378  *
379  * 1. We can overread the input, unless this is the last line of an
380  * unpadded buffer. All defined operations can handle arbitrary pixel
381  * input, so overread of arbitrary data is fine. For flipped images,
382  * this condition is actually *inverted* to where the first line is
383  * the one at the end of the buffer.
384  *
385  * 2. We can overwrite the output, as long as we don't write more than the
386  * amount of pixels that fit into one linesize. So we always need to
387  * memcpy the last column on the output side if unpadded.
388  */
389 
390  const bool memcpy_in = p->memcpy_last && y + h == pass->height ||
391  p->memcpy_first && y == 0;
392  const bool memcpy_out = p->memcpy_out;
393  const size_t num_blocks = p->num_blocks;
394  const size_t tail_blocks = p->tail_blocks;
395 
396  get_row_data(p, y, exec.in, exec.out);
397  if (!memcpy_in && !memcpy_out) {
398  /* Fast path (fully aligned/padded inputs and outputs) */
399  comp->func(&exec, comp->priv, 0, y, num_blocks, y + h);
400  return;
401  }
402 
403  /* Non-aligned case (slow path); process main blocks as normal, and
404  * a separate tail (via memcpy into an appropriately padded buffer) */
405  if (num_blocks > tail_blocks) {
406  for (int i = 0; i < 4; i++) {
407  /* We process fewer blocks, so the in_bump needs to be increased
408  * to reflect that the plane pointers are left on the last block,
409  * not the end of the processed line, after each loop iteration */
410  exec.in_bump[i] += exec.block_size_in[i] * tail_blocks;
411  exec.out_bump[i] += exec.block_size_out[i] * tail_blocks;
412  }
413 
414  comp->func(&exec, comp->priv, 0, y, num_blocks - tail_blocks, y + h);
415  }
416 
417  DECLARE_ALIGNED_32(SwsOpExec, tail) = p->exec_tail;
418  tail.slice_y = y;
419  tail.slice_h = h;
420 
421  for (int i = 0; i < p->planes_in; i++) {
422  /* Input offsets are relative to the base pointer */
423  if (!exec.in_offset_x || memcpy_in)
424  exec.in[i] += p->tail_off_in;
425  tail.in[i] += y * tail.in_stride[i];
426  }
427  for (int i = 0; i < p->planes_out; i++) {
428  exec.out[i] += p->tail_off_out;
429  tail.out[i] += y * tail.out_stride[i];
430  }
431 
432  for (int i = 0; i < p->planes_in; i++) {
433  if (memcpy_in) {
434  const int lines = get_lines_in(p, y, h, i);
435  copy_lines((uint8_t *) tail.in[i], tail.in_stride[i],
436  exec.in[i], exec.in_stride[i], lines, p->tail_size_in);
437  } else {
438  /* Reuse input pointers directly */
439  const size_t loop_size = tail_blocks * exec.block_size_in[i];
440  tail.in[i] = exec.in[i];
441  tail.in_stride[i] = exec.in_stride[i];
442  tail.in_bump[i] = exec.in_stride[i] - loop_size;
443  }
444  }
445 
446  for (int i = 0; !memcpy_out && i < p->planes_out; i++) {
447  /* Reuse output pointers directly */
448  const size_t loop_size = tail_blocks * exec.block_size_out[i];
449  tail.out[i] = exec.out[i];
450  tail.out_stride[i] = exec.out_stride[i];
451  tail.out_bump[i] = exec.out_stride[i] - loop_size;
452  }
453 
454  /* Dispatch kernel over tail */
455  av_assert1(tail_blocks > 0);
456  comp->func(&tail, comp->priv, num_blocks - tail_blocks, y, num_blocks, y + h);
457 
458  for (int i = 0; memcpy_out && i < p->planes_out; i++) {
459  const int lines = h >> tail.out_sub_y[i];
460  copy_lines(exec.out[i], exec.out_stride[i],
461  tail.out[i], tail.out_stride[i], lines, p->tail_size_out);
462  }
463 }
464 
465 static int rw_pixel_bits(const SwsOp *op)
466 {
467  int elems = 0;
468  switch (op->rw.mode) {
469  case SWS_RW_PLANAR: elems = 1; break;
470  case SWS_RW_PACKED: elems = op->rw.elems; break;
471  }
472 
473  const int size = ff_sws_pixel_type_size(op->type);
474  const int bits = 8 >> op->rw.frac;
475  av_assert1(bits >= 1);
476  return elems * size * bits;
477 }
478 
479 static void align_pass(SwsPass *pass, int block_size, const int *over_rw,
480  int pixel_bits)
481 {
482  if (!pass)
483  return;
484 
485  /* Add at least as many pixels as needed to cover the padding requirement */
486  int pad_max = 0;
487  for (int i = 0; i < 4; i++) {
488  const int pad = (over_rw[i] * 8 + pixel_bits - 1) / pixel_bits;
489  pad_max = FFMAX(pad_max, pad);
490  }
491 
492  SwsPassBuffer *buf = pass->output;
493  buf->width_align = FFMAX(buf->width_align, block_size);
494  buf->width_pad = FFMAX(buf->width_pad, pad_max);
495 }
496 
497 static int compile(SwsGraph *graph, const SwsOpBackend *backend,
498  const SwsOpList *ops, SwsPass *input, SwsPass **output)
499 {
500  SwsContext *ctx = graph->ctx;
501  SwsOpPass *p = av_mallocz(sizeof(*p));
502  if (!p)
503  return AVERROR(ENOMEM);
504 
505  int ret = ff_sws_ops_compile(ctx, backend, ops, &p->comp);
506  if (ret < 0)
507  goto fail;
508  else if (!output)
509  goto fail; /* nothing to do, just return */
510 
511  const SwsCompiledOp *comp = &p->comp;
512  const SwsFormat *src = &ops->src;
513  const SwsFormat *dst = &ops->dst;
514  if (p->comp.opaque) {
515  SwsCompiledOp c = *comp;
516  av_free(p);
517  ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height,
518  input, c.slice_align, c.func_opaque,
519  NULL, c.priv, c.free, output);
520  if (ret >= 0)
521  (*output)->backend = comp->backend->flags;
522  return ret;
523  }
524 
525  const AVPixFmtDescriptor *indesc = av_pix_fmt_desc_get(src->format);
526  const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(dst->format);
527  const SwsOp *read = ff_sws_op_list_input(ops);
528  const SwsOp *write = ff_sws_op_list_output(ops);
529  p->planes_in = ff_sws_rw_op_planes(read);
530  p->planes_out = ff_sws_rw_op_planes(write);
531  p->pixel_bits_in = rw_pixel_bits(read);
532  p->pixel_bits_out = rw_pixel_bits(write);
533  p->exec_base = (SwsOpExec) {
534  .width = dst->width,
535  .height = dst->height,
536  };
537 
538  const int64_t block_bits_in = (int64_t) comp->block_size * p->pixel_bits_in;
539  const int64_t block_bits_out = (int64_t) comp->block_size * p->pixel_bits_out;
540  if (block_bits_in & 0x7 || block_bits_out & 0x7) {
541  av_log(ctx, AV_LOG_ERROR, "Block size must be byte-aligned.\n");
542  ret = AVERROR(EINVAL);
543  goto fail;
544  }
545 
546  for (int i = 0; i < 4; i++)
547  p->idx_in[i] = p->idx_out[i] = -1;
548 
549  for (int i = 0; i < p->planes_in; i++) {
550  const int idx = ops->plane_src[i];
551  const int chroma = idx == 1 || idx == 2;
552  const int sub_x = chroma ? indesc->log2_chroma_w : 0;
553  const int sub_y = chroma ? indesc->log2_chroma_h : 0;
554  p->exec_base.in_sub_x[i] = sub_x;
555  p->exec_base.in_sub_y[i] = sub_y;
556  p->exec_base.block_size_in[i] = block_bits_in >> 3;
557  p->idx_in[i] = idx;
558  }
559 
560  for (int i = 0; i < p->planes_out; i++) {
561  const int idx = ops->plane_dst[i];
562  const int chroma = idx == 1 || idx == 2;
563  const int sub_x = chroma ? outdesc->log2_chroma_w : 0;
564  const int sub_y = chroma ? outdesc->log2_chroma_h : 0;
565  p->exec_base.out_sub_x[i] = sub_x;
566  p->exec_base.out_sub_y[i] = sub_y;
567  p->exec_base.block_size_out[i] = block_bits_out >> 3;
568  p->idx_out[i] = idx;
569  }
570 
571  const SwsFilterWeights *filter = read->rw.filter.kernel;
572  if (read->rw.filter.op == SWS_OP_FILTER_V) {
573  p->offsets_y = av_refstruct_ref(filter->offsets);
574 
575  /* Compute relative pointer bumps for each output line */
576  int32_t *bump = av_malloc_array(filter->dst_size, sizeof(*bump));
577  if (!bump) {
578  ret = AVERROR(ENOMEM);
579  goto fail;
580  }
581 
582  int line = filter->offsets[0];
583  for (int y = 0; y < filter->dst_size - 1; y++) {
584  int next = filter->offsets[y + 1];
585  bump[y] = next - line - 1;
586  line = next;
587  }
588  bump[filter->dst_size - 1] = 0;
589  p->exec_base.in_bump_y = bump;
590  } else if (read->rw.filter.op == SWS_OP_FILTER_H) {
591  /* Compute pixel offset map for each output line */
592  const int pixels = FFALIGN(filter->dst_size, p->comp.block_size);
593  int32_t *offset = av_malloc_array(pixels, sizeof(*offset));
594  if (!offset) {
595  ret = AVERROR(ENOMEM);
596  goto fail;
597  }
598  p->exec_base.in_offset_x = offset;
599 
600  for (int x = 0; x < filter->dst_size; x++) {
601  /* Sanity check; if the tap would land on a half-pixel, we cannot
602  * reasonably expect the implementation to know about this. Just
603  * error out in such (theoretical) cases. */
604  int64_t bits = (int64_t) filter->offsets[x] * p->pixel_bits_in;
605  if ((bits & 0x7) || (bits >> 3) > INT32_MAX) {
606  ret = AVERROR(EINVAL);
607  goto fail;
608  }
609  offset[x] = bits >> 3;
610  }
611  for (int x = filter->dst_size; x < pixels; x++)
612  offset[x] = offset[filter->dst_size - 1];
613  for (int i = 0; i < 4; i++)
614  p->exec_base.block_size_in[i] = 0; /* ptr does not advance */
615  p->filter_size_h = filter->filter_size;
616  }
617 
618  ret = ff_sws_graph_add_pass(graph, dst->format, dst->width, dst->height,
619  input, comp->slice_align, op_pass_run,
621  if (ret < 0)
622  return ret;
623 
624  (*output)->backend = comp->backend->flags;
625  align_pass(input, comp->block_size, comp->over_read, p->pixel_bits_in);
626  align_pass(*output, comp->block_size, comp->over_write, p->pixel_bits_out);
627  return 0;
628 
629 fail:
630  op_pass_free(p);
631  return ret;
632 }
633 
634 int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend,
635  SwsOpList **pops, int flags, SwsPass *input,
636  SwsPass **output)
637 {
638  const int passes_orig = graph->num_passes;
639  SwsContext *ctx = graph->ctx;
640  SwsOpList *ops = *pops;
641  int ret = 0;
642 
643  /* Check if the whole operation graph is an end-to-end no-op */
644  if (ff_sws_op_list_is_noop(ops)) {
645  if (output)
646  *output = input;
647  goto out;
648  }
649 
650  const SwsOp *read = ff_sws_op_list_input(ops);
651  const SwsOp *write = ff_sws_op_list_output(ops);
652  if (!read || !write) {
653  av_log(ctx, AV_LOG_ERROR, "First and last operations must be a read "
654  "and write, respectively.\n");
655  ret = AVERROR(EINVAL);
656  goto out;
657  }
658 
659  if (flags & SWS_OP_FLAG_OPTIMIZE) {
661  if (ret < 0)
662  goto out;
663  av_log(ctx, AV_LOG_DEBUG, "Operation list after optimizing:\n");
665  }
666 
667  ret = compile(graph, backend, ops, input, output);
668  if (ret != AVERROR(ENOTSUP))
669  goto out;
670 
671  av_log(ctx, AV_LOG_DEBUG, "Retrying with separated filter passes.\n");
672  SwsPass *prev = input;
673  bool first = true;
674  while (ops) {
675  SwsOpList *rest;
676  ret = ff_sws_op_list_subpass(ops, &rest);
677  if (ret < 0)
678  goto out;
679 
680  if (first && !rest) {
681  /* No point in compiling an unsplit pass again */
682  ret = AVERROR(ENOTSUP);
683  goto out;
684  }
685 
686  ret = compile(graph, backend, ops, prev, output ? &prev : NULL);
687  if (ret < 0) {
688  ff_sws_op_list_free(&rest);
689  goto out;
690  }
691 
692  ff_sws_op_list_free(&ops);
693  first = false;
694  ops = rest;
695  }
696 
697  if (output) {
698  /* Return last subpass successfully compiled */
699  av_log(ctx, AV_LOG_VERBOSE, "Using %d separate passes.\n",
700  graph->num_passes - passes_orig);
701  *output = prev;
702  }
703 
704 out:
705  if (ret == AVERROR(ENOTSUP)) {
706  av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n");
708  }
709  if (ret < 0)
710  ff_sws_graph_rollback(graph, passes_orig);
711  ff_sws_op_list_free(&ops);
712  *pops = NULL;
713  return ret;
714 }
flags
const SwsFlags flags[]
Definition: swscale.c:85
SwsOpPass::tail_buf
uint8_t * tail_buf
Definition: ops_dispatch.c:54
copy_lines
static void copy_lines(uint8_t *dst, const size_t dst_stride, const uint8_t *src, const size_t src_stride, const int h, const size_t bytes)
Definition: ops_dispatch.c:354
AV_ROUND_UP
@ AV_ROUND_UP
Round toward +infinity.
Definition: mathematics.h:134
SwsOpPass::tail_buf_size
unsigned int tail_buf_size
Definition: ops_dispatch.c:55
ff_sws_op_list_free
void ff_sws_op_list_free(SwsOpList **p_ops)
Definition: ops.c:637
AV_LOG_WARNING
#define AV_LOG_WARNING
Something somehow does not look correct.
Definition: log.h:216
ff_sws_rw_op_planes
int ff_sws_rw_op_planes(const SwsOp *op)
Return the number of planes involved in a read/write operation.
Definition: ops.c:169
SwsGraph::ctx
SwsContext * ctx
Definition: graph.h:123
SwsPass
Represents a single filter pass in the scaling graph.
Definition: graph.h:75
AVERROR
Filter the word “frame” indicates either a video frame or a group of audio as stored in an AVFrame structure Format for each input and each output the list of supported formats For video that means pixel format For audio that means channel sample they are references to shared objects When the negotiation mechanism computes the intersection of the formats supported at each end of a all references to both lists are replaced with a reference to the intersection And when a single format is eventually chosen for a link amongst the remaining all references to the list are updated That means that if a filter requires that its input and output have the same format amongst a supported all it has to do is use a reference to the same list of formats query_formats can leave some formats unset and return AVERROR(EAGAIN) to cause the negotiation mechanism toagain later. That can be used by filters with complex requirements to use the format negotiated on one link to set the formats supported on another. Frame references ownership and permissions
SwsOpPass::idx_in
int idx_in[4]
Definition: ops_dispatch.c:46
SwsOpPass::tail_size_out
int tail_size_out
Definition: ops_dispatch.c:41
ff_sws_op_list_duplicate
SwsOpList * ff_sws_op_list_duplicate(const SwsOpList *ops)
Returns a duplicate of ops, or NULL on OOM.
Definition: ops.c:651
SWS_RW_PLANAR
@ SWS_RW_PLANAR
Note: 1-component reads are either SWS_RW_PLANAR or SWS_RW_PACKED, depending on the underlying interp...
Definition: ops.h:97
mem_internal.h
out
static FILE * out
Definition: movenc.c:55
SwsOpPass::exec_tail
SwsOpExec exec_tail
Definition: ops_dispatch.c:36
comp
static void comp(unsigned char *dst, ptrdiff_t dst_stride, unsigned char *src, ptrdiff_t src_stride, int add)
Definition: eamad.c:79
SwsOpBackend::flags
SwsBackend flags
Definition: ops_dispatch.h:135
SwsOpExec::in_bump
ptrdiff_t in_bump[4]
Pointer bump, difference between stride and processed line size.
Definition: ops_dispatch.h:51
ff_sws_op_list_input
const SwsOp * ff_sws_op_list_input(const SwsOpList *ops)
Returns the input operation for a given op list, or NULL if there is none (e.g.
Definition: ops.c:688
av_pix_fmt_desc_get
const AVPixFmtDescriptor * av_pix_fmt_desc_get(enum AVPixelFormat pix_fmt)
Definition: pixdesc.c:3456
SwsOpExec::out_stride
ptrdiff_t out_stride[4]
Definition: ops_dispatch.h:42
SwsOpExec::in
const uint8_t * in[4]
Definition: ops_dispatch.h:37
int64_t
long long int64_t
Definition: coverity.c:34
output
filter_frame For filters that do not use the this method is called when a frame is pushed to the filter s input It can be called at any time except in a reentrant way If the input frame is enough to produce output
Definition: filter_design.txt:226
AV_PIX_FMT_FLAG_FLOAT
#define AV_PIX_FMT_FLAG_FLOAT
The pixel format contains IEEE-754 floating point values.
Definition: pixdesc.h:158
ops.h
SwsFilterWeights
Represents a computed filter kernel.
Definition: filters.h:64
SwsOpExec::block_size_out
int32_t block_size_out[4]
Definition: ops_dispatch.h:58
chroma
static av_always_inline void chroma(WaveformContext *s, AVFrame *in, AVFrame *out, int component, int intensity, int offset_y, int offset_x, int column, int mirror, int jobnr, int nb_jobs)
Definition: vf_waveform.c:1639
AV_ROUND_ZERO
@ AV_ROUND_ZERO
Round toward zero.
Definition: mathematics.h:131
AVRounding
AVRounding
Rounding methods.
Definition: mathematics.h:130
AV_LOG_VERBOSE
#define AV_LOG_VERBOSE
Detailed information.
Definition: log.h:226
base
uint8_t base
Definition: vp3data.h:128
filter
void(* filter)(uint8_t *src, int stride, int qscale)
Definition: h263dsp.c:29
SwsFrame::width
int width
Dimensions and format.
Definition: format.h:229
mathematics.h
ops_dispatch.h
FFMAX
#define FFMAX(a, b)
Definition: macros.h:47
SwsOpExec::in_stride
ptrdiff_t in_stride[4]
Definition: ops_dispatch.h:41
SwsOpPass::tail_blocks
size_t tail_blocks
Definition: ops_dispatch.c:53
SwsOpBackend::name
const char * name
Definition: ops_dispatch.h:134
SwsOpPass::idx_out
int idx_out[4]
Definition: ops_dispatch.c:47
ff_sws_pixel_type_size
int ff_sws_pixel_type_size(SwsPixelType type)
Definition: ops.c:77
cpu.h
SwsPass::width
int width
Definition: graph.h:86
ff_sws_op_list_subpass
int ff_sws_op_list_subpass(SwsOpList *ops, SwsOpList **out_rest)
Eliminate SWS_OP_FILTER_* operations by merging them with prior SWS_OP_READ operations.
Definition: ops_optimizer.c:959
SwsOpList::plane_dst
uint8_t plane_dst[4]
Definition: ops.h:289
ff_sws_op_list_print
void ff_sws_op_list_print(void *log, int lev, int lev_extra, const SwsOpList *ops)
Print out the contents of an operation list.
Definition: ops.c:983
ff_sws_op_backends
const SwsOpBackend *const ff_sws_op_backends[]
Definition: ops.c:45
SwsFrame::data
uint8_t * data[4]
Definition: format.h:223
SwsOpExec::block_size_in
int32_t block_size_in[4]
Definition: ops_dispatch.h:57
SwsOpBackend::hw_format
enum AVPixelFormat hw_format
If NONE, backend only supports software frames.
Definition: ops_dispatch.h:150
SwsOpPass::memcpy_last
bool memcpy_last
Definition: ops_dispatch.c:51
refstruct.h
get_row_data
static void get_row_data(const SwsOpPass *p, const int y_dst, const uint8_t *in[4], uint8_t *out[4])
Definition: ops_dispatch.c:143
safe_blocks_offset
static size_t safe_blocks_offset(size_t num_blocks, unsigned block_size, ptrdiff_t safe_offset, const int32_t *offset_bytes)
Definition: ops_dispatch.c:190
SWS_RW_PACKED
@ SWS_RW_PACKED
Definition: ops.h:98
SwsFrame
Represents a view into a single field of frame data.
Definition: format.h:221
SwsBackend
SwsBackend
Definition: swscale.h:110
first
trying all byte sequences megabyte in length and selecting the best looking sequence will yield cases to try But first
Definition: rate_distortion.txt:12
avassert.h
AV_LOG_TRACE
#define AV_LOG_TRACE
Extremely verbose debugging, useful for libav* development.
Definition: log.h:236
AV_LOG_ERROR
#define AV_LOG_ERROR
Something went wrong and cannot losslessly be recovered.
Definition: log.h:210
SwsFrame::format
enum AVPixelFormat format
Definition: format.h:230
SwsPass::priv
void * priv
Definition: graph.h:111
AV_CEIL_RSHIFT
#define AV_CEIL_RSHIFT(a, b)
Definition: common.h:60
op
static int op(uint8_t **dst, const uint8_t *dst_end, GetByteContext *gb, int pixel, int count, int *x, int width, int linesize)
Perform decode operation.
Definition: anm.c:76
bits
uint8_t bits
Definition: vp3data.h:128
av_assert0
#define av_assert0(cond)
assert() equivalent, that is always enabled.
Definition: avassert.h:42
AV_LOG_DEBUG
#define AV_LOG_DEBUG
Stuff which is only useful for libav* developers.
Definition: log.h:231
SwsGraph::num_passes
int num_passes
Definition: graph.h:134
ctx
static AVFormatContext * ctx
Definition: movenc.c:49
AVPixFmtDescriptor::log2_chroma_w
uint8_t log2_chroma_w
Amount to shift the luma width right to find the chroma width.
Definition: pixdesc.h:80
ff_sws_op_list_output
const SwsOp * ff_sws_op_list_output(const SwsOpList *ops)
Returns the output operation for a given op list, or NULL if there is none.
Definition: ops.c:697
SWS_OP_FILTER_H
@ SWS_OP_FILTER_H
Definition: ops.h:61
av_mallocz
#define av_mallocz(s)
Definition: tableprint_vlc.h:31
SwsOpPass::comp
SwsCompiledOp comp
Definition: ops_dispatch.c:34
SwsOpBackend
Definition: ops_dispatch.h:133
FFABS
#define FFABS(a)
Absolute value, Note, INT_MIN / INT64_MIN result in undefined behavior as they are not representable ...
Definition: common.h:74
if
if(ret)
Definition: filter_design.txt:179
SwsOpExec
Copyright (C) 2026 Niklas Haas.
Definition: ops_dispatch.h:35
fail
#define fail
Definition: test.h:478
ff_sws_op_list_is_noop
bool ff_sws_op_list_is_noop(const SwsOpList *ops)
Returns whether an op list represents a true no-op operation, i.e.
Definition: ops.c:736
op_pass_free
static void op_pass_free(void *ptr)
Definition: ops_dispatch.c:129
NULL
#define NULL
Definition: coverity.c:32
ff_sws_compiled_op_unref
void ff_sws_compiled_op_unref(SwsCompiledOp *comp)
Definition: ops_dispatch.c:121
av_unreachable
#define av_unreachable(msg)
Asserts that are used as compiler optimization hints depending upon ASSERT_LEVEL and NBDEBUG.
Definition: avassert.h:116
av_fast_mallocz
void av_fast_mallocz(void *ptr, unsigned int *size, size_t min_size)
Allocate and clear a buffer, reusing the given one if large enough.
Definition: mem.c:562
SWS_OP_FILTER_V
@ SWS_OP_FILTER_V
Definition: ops.h:62
rw_pixel_bits
static int rw_pixel_bits(const SwsOp *op)
Definition: ops_dispatch.c:465
compile
static int compile(SwsGraph *graph, const SwsOpBackend *backend, const SwsOpList *ops, SwsPass *input, SwsPass **output)
Definition: ops_dispatch.c:497
AVPixFmtDescriptor::flags
uint64_t flags
Combination of AV_PIX_FMT_FLAG_...
Definition: pixdesc.h:94
c
Undefined Behavior In the C some operations are like signed integer dereferencing freed accessing outside allocated Undefined Behavior must not occur in a C it is not safe even if the output of undefined operations is unused The unsafety may seem nit picking but Optimizing compilers have in fact optimized code on the assumption that no undefined Behavior occurs Optimizing code based on wrong assumptions can and has in some cases lead to effects beyond the output of computations The signed integer overflow problem in speed critical code Code which is highly optimized and works with signed integers sometimes has the problem that often the output of the computation does not c
Definition: undefined.txt:32
SwsOpPass::filter_size_h
int filter_size_h
Definition: ops_dispatch.c:49
SwsOpBackend::compile
int(* compile)(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out)
Compile an operation list to an implementation chain.
Definition: ops_dispatch.h:143
AV_ROUND_DOWN
@ AV_ROUND_DOWN
Round toward -infinity.
Definition: mathematics.h:133
SwsPass::height
int height
Definition: graph.h:86
copy
static void copy(const float *p1, float *p2, const int length)
Definition: vf_vaguedenoiser.c:186
ff_sws_enabled_backends
SwsBackend ff_sws_enabled_backends(const SwsContext *ctx)
Definition: utils.c:71
SwsFrame::height
int height
Definition: format.h:229
dst
uint8_t ptrdiff_t const uint8_t ptrdiff_t int intptr_t intptr_t int int16_t * dst
Definition: dsp.h:87
SwsOpExec::in_sub_x
uint8_t in_sub_x[4]
Definition: ops_dispatch.h:62
i
#define i(width, name, range_min, range_max)
Definition: cbs_h264.c:63
av_err2str
#define av_err2str(errnum)
Convenience macro, the return value should be used only directly in function arguments but never stan...
Definition: error.h:122
for
for(k=2;k<=8;++k)
Definition: h264pred_template.c:424
size
int size
Definition: twinvq_data.h:10344
op_pass_setup
static int op_pass_setup(const SwsFrame *out, const SwsFrame *in, const SwsPass *pass)
Definition: ops_dispatch.c:200
SwsOpPass::offsets_y
int * offsets_y
Definition: ops_dispatch.c:48
SwsOpList::src
SwsFormat src
Definition: ops.h:286
ff_sws_op_list_update_comps
void ff_sws_op_list_update_comps(SwsOpList *ops)
Infer + propagate known information about components.
Definition: ops.c:353
compile_backend
static int compile_backend(SwsContext *ctx, const SwsOpBackend *backend, const SwsOpList *ops, SwsCompiledOp *out)
Definition: ops_dispatch.c:58
SwsFormat
Definition: format.h:77
SwsCompiledOp::backend
const struct SwsOpBackend * backend
Definition: ops_dispatch.h:115
align
static const uint8_t *BS_FUNC() align(BSCTX *bc)
Skip bits to a byte boundary.
Definition: bitstream_template.h:419
av_refstruct_ref
void * av_refstruct_ref(void *obj)
Create a new reference to an object managed via this API, i.e.
Definition: refstruct.c:140
SwsPass::output
SwsPassBuffer * output
Filter output buffer.
Definition: graph.h:99
offset
it s the only field you need to keep assuming you have a context There is some magic you don t need to care about around this just let it vf offset
Definition: writing_filters.txt:86
line
Definition: graph2dot.c:48
align_pass
static void align_pass(SwsPass *pass, int block_size, const int *over_rw, int pixel_bits)
Definition: ops_dispatch.c:479
SWS_OP_FLAG_OPTIMIZE
@ SWS_OP_FLAG_OPTIMIZE
Definition: ops.h:365
input
and forward the test the status of outputs and forward it to the corresponding return FFERROR_NOT_READY If the filters stores internally one or a few frame for some input
Definition: filter_design.txt:172
SwsOpPass::planes_in
int planes_in
Definition: ops_dispatch.c:42
av_refstruct_unref
void av_refstruct_unref(void *objp)
Decrement the reference count of the underlying object and automatically free the object if there are...
Definition: refstruct.c:120
SwsOpExec::out
uint8_t * out[4]
Definition: ops_dispatch.h:38
get_lines_in
static int get_lines_in(const SwsOpPass *p, const int y, const int h, const int plane)
Definition: ops_dispatch.c:154
ff_sws_op_list_optimize
int ff_sws_op_list_optimize(SwsOpList *ops)
Fuse compatible and eliminate redundant operations, as well as replacing some operations with more ef...
Definition: ops_optimizer.c:350
SwsPassBuffer::width_align
int width_align
Definition: graph.h:66
SwsOpPass::pixel_bits_out
int pixel_bits_out
Definition: ops_dispatch.c:45
SwsOpExec::in_offset_x
int32_t * in_offset_x
Pixel offset map; for horizontal scaling, in bytes.
Definition: ops_dispatch.h:80
SwsOpPass::planes_out
int planes_out
Definition: ops_dispatch.c:43
AV_ROUND_INF
@ AV_ROUND_INF
Round away from zero.
Definition: mathematics.h:132
av_malloc_array
#define av_malloc_array(a, b)
Definition: tableprint_vlc.h:32
SwsOpPass::tail_size_in
int tail_size_in
Definition: ops_dispatch.c:40
av_cpu_max_align
size_t av_cpu_max_align(void)
Get the maximum data alignment that may be required by FFmpeg.
Definition: cpu.c:287
av_assert1
#define av_assert1(cond)
assert() equivalent, that does not lie in speed critical code.
Definition: avassert.h:58
swscale_internal.h
DECLARE_ALIGNED_32
#define DECLARE_ALIGNED_32(t, v)
Definition: mem_internal.h:113
FFMIN
#define FFMIN(a, b)
Definition: macros.h:49
ops_internal.h
SwsOpPass
Copyright (C) 2025 Niklas Haas.
Definition: ops_dispatch.c:33
pixel_bytes
static size_t pixel_bytes(size_t pixels, int pixel_bits, enum AVRounding rounding)
Definition: ops_dispatch.c:166
SwsOp
Definition: ops.h:226
SwsOpPass::memcpy_first
bool memcpy_first
Definition: ops_dispatch.c:50
ff_sws_graph_add_pass
int ff_sws_graph_add_pass(SwsGraph *graph, enum AVPixelFormat fmt, int width, int height, SwsPass *input, int align, SwsPassFunc run, SwsPassSetup setup, void *priv, void(*free_cb)(void *priv), SwsPass **out_pass)
Allocate and add a new pass to the filter graph.
Definition: graph.c:175
ret
ret
Definition: filter_design.txt:187
SwsOpList::dst
SwsFormat dst
Definition: ops.h:286
SwsCompiledOp
Definition: ops_dispatch.h:100
SwsPassBuffer::width_pad
int width_pad
Definition: graph.h:67
SwsFormat::hw_format
enum AVPixelFormat hw_format
Definition: format.h:81
Windows::Graphics::DirectX::Direct3D11::p
IDirect3DDxgiInterfaceAccess _COM_Outptr_ void ** p
Definition: vsrc_gfxcapture_winrt.hpp:53
SwsOpPass::num_blocks
size_t num_blocks
Definition: ops_dispatch.c:37
safe_bytes_pad
static size_t safe_bytes_pad(int linesize, int plane_pad)
Definition: ops_dispatch.c:183
SwsOpPass::exec_base
SwsOpExec exec_base
Definition: ops_dispatch.c:35
ff_sws_compile_pass
int ff_sws_compile_pass(SwsGraph *graph, const SwsOpBackend *backend, SwsOpList **pops, int flags, SwsPass *input, SwsPass **output)
Resolves an operation list to a graph pass.
Definition: ops_dispatch.c:634
SwsOpPass::pixel_bits_in
int pixel_bits_in
Definition: ops_dispatch.c:44
SwsOpPass::tail_off_in
int tail_off_in
Definition: ops_dispatch.c:38
SwsOpPass::memcpy_out
bool memcpy_out
Definition: ops_dispatch.c:52
mem.h
SwsGraph
Filter graph, which represents a 'baked' pixel format conversion.
Definition: graph.h:122
AVPixFmtDescriptor
Descriptor that unambiguously describes how the bits of a pixel are stored in the up to 4 data planes...
Definition: pixdesc.h:69
av_free
#define av_free(p)
Definition: tableprint_vlc.h:34
FFALIGN
#define FFALIGN(x, a)
Definition: macros.h:78
op_pass_run
static void op_pass_run(const SwsFrame *out, const SwsFrame *in, const int y, const int h, const SwsPass *pass)
Definition: ops_dispatch.c:365
int32_t
int32_t
Definition: audioconvert.c:56
av_log
#define av_log(a,...)
Definition: tableprint_vlc.h:27
SwsPassBuffer
Represents an output buffer for a filter pass.
Definition: graph.h:59
h
h
Definition: vp9dsp_template.c:2070
width
#define width
Definition: dsp.h:89
SwsOpList::plane_src
uint8_t plane_src[4]
Definition: ops.h:289
SwsOpList
Helper struct for representing a list of operations.
Definition: ops.h:281
SwsContext
Main external API structure.
Definition: swscale.h:229
SwsOpPass::tail_off_out
int tail_off_out
Definition: ops_dispatch.c:39
SwsFrame::linesize
int linesize[4]
Definition: format.h:224
AVPixFmtDescriptor::log2_chroma_h
uint8_t log2_chroma_h
Amount to shift the luma height right to find the chroma height.
Definition: pixdesc.h:89
src
#define src
Definition: vp8dsp.c:248
SwsOpExec::out_bump
ptrdiff_t out_bump[4]
Definition: ops_dispatch.h:52
read
static uint32_t BS_FUNC() read(BSCTX *bc, unsigned int n)
Return n bits from the buffer, n has to be in the 0-32 range.
Definition: bitstream_template.h:239
ff_sws_ops_compile
int ff_sws_ops_compile(SwsContext *ctx, const SwsOpBackend *backend, const SwsOpList *ops, SwsCompiledOp *out)
Attempt to compile a list of operations using a specific backend, or the best available backend if ba...
Definition: ops_dispatch.c:99
ff_sws_graph_rollback
void ff_sws_graph_rollback(SwsGraph *graph, int since_idx)
Remove all passes added since the given index.
Definition: graph.c:909