This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 63140bff5e4808a9a6fdb53899c4f0a3de471527 Author: Niklas Haas <[email protected]> AuthorDate: Sat Mar 28 12:28:23 2026 +0100 Commit: Niklas Haas <[email protected]> CommitDate: Sat Mar 28 18:50:13 2026 +0100 swscale/ops: define SWS_OP_FILTER_H/V This commit merely adds the definitions. The implementations will follow. It may seem a bit impractical to have these filter ops given that they break the usual 1:1 association between operation inputs and outputs, but the design path I chose will have these filter "pseudo-ops" end up migrating towards the read/write for CPU implementations. (Which don't benefit from any ability to hide the intermediate memory internally the way e.g. a fused Vulkan compute shader might). What we gain from this design, on the other hand, is considerably cleaner high-level code, which doesn't need to concern itself with low-level execution details at all, and can just freely insert these ops wherever it needs to. The dispatch layer will take care of actually executing these by implicitly splitting apart subpasses. To handle out-of-range values and so on, the filters by necessity have to also convert the pixel range. I have settled on using floating point types as the canonical intermediate format - not only does this save us from having to define e.g. I32 as a new intermediate format, but it also allows these operations to chain naturally into SWS_OP_DITHER, which will basically always be needed after a filter pass anyways. The one exception here is for point sampling, which would rather preserve the input type. I'll worry about this optimization at a later point in time. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/ops.c | 50 ++++++++++++++++++++++++++++++++++++++++++++++ libswscale/ops.h | 10 ++++++++++ libswscale/ops_chain.c | 3 +++ libswscale/ops_optimizer.c | 4 ++++ 4 files changed, 67 insertions(+) diff --git a/libswscale/ops.c b/libswscale/ops.c index 5d2e9628b5..2ff33dc3b7 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -112,6 +112,8 @@ const char *ff_sws_op_type_name(SwsOpType op) case SWS_OP_SCALE: return "SWS_OP_SCALE"; case SWS_OP_LINEAR: return "SWS_OP_LINEAR"; case SWS_OP_DITHER: return "SWS_OP_DITHER"; + case SWS_OP_FILTER_H: return "SWS_OP_FILTER_H"; + case SWS_OP_FILTER_V: return "SWS_OP_FILTER_V"; case SWS_OP_INVALID: return "SWS_OP_INVALID"; case SWS_OP_TYPE_NB: break; } @@ -235,6 +237,11 @@ void ff_sws_apply_op_q(const SwsOp *op, AVRational x[4]) for (int i = 0; i < 4; i++) x[i] = x[i].den ? av_mul_q(x[i], op->c.q) : x[i]; return; + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: + /* Filters have normalized energy by definition, so they don't + * conceptually modify individual components */ + return; } av_unreachable("Invalid operation type!"); @@ -265,6 +272,24 @@ static void clear_undefined_values(AVRational dst[4], const AVRational src[4]) } } +static void apply_filter_weights(SwsComps *comps, const SwsComps *prev, + const SwsFilterWeights *weights) +{ + const AVRational posw = { weights->sum_positive, SWS_FILTER_SCALE }; + const AVRational negw = { weights->sum_negative, SWS_FILTER_SCALE }; + for (int i = 0; i < 4; i++) { + comps->flags[i] = prev->flags[i]; + /* Only point sampling preserves exactness */ + if (weights->filter_size != 1) + comps->flags[i] &= ~SWS_COMP_EXACT; + /* Update min/max assuming extremes */ + comps->min[i] = av_add_q(av_mul_q(prev->min[i], posw), + av_mul_q(prev->max[i], negw)); + comps->max[i] = av_add_q(av_mul_q(prev->min[i], negw), + av_mul_q(prev->max[i], posw)); + } +} + /* Infer + propagate known information about components */ void ff_sws_op_list_update_comps(SwsOpList *ops) { @@ -282,6 +307,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_LINEAR: case SWS_OP_SWAP_BYTES: case SWS_OP_UNPACK: + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: break; /* special cases, handled below */ default: memcpy(op->comps.min, prev.min, sizeof(prev.min)); @@ -421,6 +448,11 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) FFSWAP(AVRational, op->comps.min[i], op->comps.max[i]); } break; + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: { + apply_filter_weights(&op->comps, &prev, op->filter.kernel); + break; + } case SWS_OP_INVALID: case SWS_OP_TYPE_NB: @@ -450,6 +482,8 @@ void ff_sws_op_list_update_comps(SwsOpList *ops) case SWS_OP_MIN: case SWS_OP_MAX: case SWS_OP_SCALE: + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: for (int i = 0; i < 4; i++) op->comps.unused[i] = next.unused[i]; break; @@ -509,6 +543,10 @@ static void op_uninit(SwsOp *op) case SWS_OP_DITHER: av_refstruct_unref(&op->dither.matrix); break; + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: + av_refstruct_unref(&op->filter.kernel); + break; } *op = (SwsOp) {0}; @@ -563,6 +601,10 @@ SwsOpList *ff_sws_op_list_duplicate(const SwsOpList *ops) case SWS_OP_DITHER: av_refstruct_ref(op->dither.matrix); break; + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: + av_refstruct_ref(op->filter.kernel); + break; } } @@ -826,6 +868,14 @@ void ff_sws_op_desc(AVBPrint *bp, const SwsOp *op, const bool unused[4]) case SWS_OP_SCALE: av_bprintf(bp, "%-20s: * %d/%d", name, op->c.q.num, op->c.q.den); break; + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: { + const SwsFilterWeights *kernel = op->filter.kernel; + av_bprintf(bp, "%-20s: %d -> %d %s (%d taps)", name, + kernel->src_size, kernel->dst_size, + kernel->name, kernel->filter_size); + break; + } case SWS_OP_TYPE_NB: break; } diff --git a/libswscale/ops.h b/libswscale/ops.h index ed72f7acfa..651f1e8ecd 100644 --- a/libswscale/ops.h +++ b/libswscale/ops.h @@ -28,6 +28,7 @@ #include "libavutil/bprint.h" #include "graph.h" +#include "filters.h" typedef enum SwsPixelType { SWS_PIXEL_NONE = 0, @@ -68,6 +69,10 @@ typedef enum SwsOpType { SWS_OP_LINEAR, /* generalized linear affine transform */ SWS_OP_DITHER, /* add dithering noise */ + /* Filtering operations. Always output floating point. */ + SWS_OP_FILTER_H, /* horizontal filtering */ + SWS_OP_FILTER_V, /* vertical filtering */ + SWS_OP_TYPE_NB, } SwsOpType; @@ -187,6 +192,10 @@ enum { /* Helper function to compute the correct mask */ uint32_t ff_sws_linear_mask(SwsLinearOp); +typedef struct SwsFilterOp { + SwsFilterWeights *kernel; /* filter kernel (refstruct) */ +} SwsFilterOp; + typedef struct SwsOp { SwsOpType op; /* operation to perform */ SwsPixelType type; /* pixel type to operate on */ @@ -197,6 +206,7 @@ typedef struct SwsOp { SwsSwizzleOp swizzle; SwsConvertOp convert; SwsDitherOp dither; + SwsFilterOp filter; SwsConst c; }; diff --git a/libswscale/ops_chain.c b/libswscale/ops_chain.c index d159acb119..598e51a7db 100644 --- a/libswscale/ops_chain.c +++ b/libswscale/ops_chain.c @@ -185,6 +185,9 @@ static int op_match(const SwsOp *op, const SwsOpEntry *entry, const SwsComps nex return score; case SWS_OP_SCALE: return av_cmp_q(op->c.q, entry->scale) ? 0 : score; + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: + return score; case SWS_OP_TYPE_NB: break; } diff --git a/libswscale/ops_optimizer.c b/libswscale/ops_optimizer.c index 520069f859..3f8f5b1319 100644 --- a/libswscale/ops_optimizer.c +++ b/libswscale/ops_optimizer.c @@ -54,6 +54,8 @@ static bool op_commute_clear(SwsOp *op, SwsOp *next) case SWS_OP_SCALE: case SWS_OP_READ: case SWS_OP_SWIZZLE: + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: ff_sws_apply_op_q(next, op->c.q4); return true; case SWS_OP_SWAP_BYTES: @@ -107,6 +109,8 @@ static bool op_commute_swizzle(SwsOp *op, SwsOp *next) case SWS_OP_LSHIFT: case SWS_OP_RSHIFT: case SWS_OP_SCALE: + case SWS_OP_FILTER_H: + case SWS_OP_FILTER_V: return true; /** _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
