From d3ca0e300d86d8c49fd4f0a093126e153ecffe0c Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Sun, 18 May 2025 16:50:13 +0200 Subject: [PATCH] swscale/ops_internal: add internal ops backend API This adds an internal API for ops backends, which are responsible for compiling op lists into executable functions. --- libswscale/ops.c | 64 +++++++++++++++++++++++++++++++ libswscale/ops_internal.h | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/libswscale/ops.c b/libswscale/ops.c index 5ee4b0fbfc..3b39dc69bd 100644 --- a/libswscale/ops.c +++ b/libswscale/ops.c @@ -27,6 +27,16 @@ #include "ops.h" #include "ops_internal.h" +const SwsOpBackend * const ff_sws_op_backends[] = { + NULL +}; + +#define RET(x) \ + do { \ + if ((ret = (x)) < 0) \ + return ret; \ + } while (0) + const char *ff_sws_pixel_type_name(SwsPixelType type) { switch (type) { @@ -508,3 +518,57 @@ void ff_sws_op_list_print(void *log, int lev, const SwsOpList *ops) av_log(log, lev, " (X = unused, + = exact, 0 = zero)\n"); } + +int ff_sws_ops_compile_backend(SwsContext *ctx, const SwsOpBackend *backend, + const SwsOpList *ops, SwsCompiledOp *out) +{ + SwsOpList *copy, rest; + SwsCompiledOp compiled = {0}; + int ret = 0; + + copy = ff_sws_op_list_duplicate(ops); + if (!copy) + return AVERROR(ENOMEM); + + /* Ensure these are always set during compilation */ + ff_sws_op_list_update_comps(copy); + + /* Make an on-stack copy of `ops` to ensure we can still properly clean up + * the copy afterwards */ + rest = *copy; + + ret = backend->compile(ctx, &rest, &compiled); + if (ret < 0) { + int msg_lev = ret == AVERROR(ENOTSUP) ? AV_LOG_TRACE : AV_LOG_ERROR; + av_log(ctx, msg_lev, "Backend '%s' failed to compile operations: %s\n", + backend->name, av_err2str(ret)); + if (rest.num_ops != ops->num_ops) { + av_log(ctx, msg_lev, "Uncompiled remainder:\n"); + ff_sws_op_list_print(ctx, msg_lev, &rest); + } + } else { + *out = compiled; + } + + ff_sws_op_list_free(©); + return ret; +} + +int ff_sws_ops_compile(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out) +{ + for (int n = 0; ff_sws_op_backends[n]; n++) { + const SwsOpBackend *backend = ff_sws_op_backends[n]; + if (ff_sws_ops_compile_backend(ctx, backend, ops, out) < 0) + continue; + + av_log(ctx, AV_LOG_VERBOSE, "Compiled using backend '%s': " + "block size = %d, over-read = %d, over-write = %d, cpu flags = 0x%x\n", + backend->name, out->block_size, out->over_read, out->over_write, + out->cpu_flags); + return 0; + } + + av_log(ctx, AV_LOG_WARNING, "No backend found for operations:\n"); + ff_sws_op_list_print(ctx, AV_LOG_WARNING, ops); + return AVERROR(ENOTSUP); +} diff --git a/libswscale/ops_internal.h b/libswscale/ops_internal.h index 78dc5d7d0a..7c3ae5b6c1 100644 --- a/libswscale/ops_internal.h +++ b/libswscale/ops_internal.h @@ -21,6 +21,8 @@ #ifndef SWSCALE_OPS_INTERNAL_H #define SWSCALE_OPS_INTERNAL_H +#include "libavutil/mem_internal.h" + #include "ops.h" #define Q(N) ((AVRational) { N, 1 }) @@ -45,4 +47,83 @@ static inline void ff_sws_pack_op_decode(const SwsOp *op, uint64_t mask[4], int } } +/** + * Global execution context for all compiled functions. + * + * Note: This struct is hard-coded in assembly, so do not change the layout + * without updating the corresponding assembly definitions. + */ +typedef struct SwsOpExec { + /* The data pointers point to the first pixel to process */ + DECLARE_ALIGNED_32(const uint8_t, *in[4]); + DECLARE_ALIGNED_32(uint8_t, *out[4]); + + /* Separation between lines in bytes */ + DECLARE_ALIGNED_32(ptrdiff_t, in_stride[4]); + DECLARE_ALIGNED_32(ptrdiff_t, out_stride[4]); + + /* Extra metadata, may or may not be useful */ + int32_t width, height; /* Overall image dimensions */ + int32_t slice_y, slice_h; /* Start and height of current slice */ + int32_t pixel_bits_in; /* Bits per input pixel */ + int32_t pixel_bits_out; /* Bits per output pixel */ +} SwsOpExec; + +static_assert(sizeof(SwsOpExec) == 16 * sizeof(void *) + 8 * sizeof(int32_t), + "SwsOpExec layout mismatch"); + +/** + * Process a given range of pixel blocks. + * + * Note: `bx_start` and `bx_end` are in units of `SwsCompiledOp.block_size`. + */ +typedef void (*SwsOpFunc)(const SwsOpExec *exec, const void *priv, + int bx_start, int y_start, int bx_end, int y_end); + +#define SWS_DECL_FUNC(NAME) \ + void NAME(const SwsOpExec *, const void *, int, int, int, int) + +typedef struct SwsCompiledOp { + SwsOpFunc func; + + int block_size; /* number of pixels processed per iteration */ + int over_read; /* implementation over-reads input by this many bytes */ + int over_write; /* implementation over-writes output by this many bytes */ + int cpu_flags; /* active set of CPU flags (informative) */ + + /* Arbitrary private data */ + void *priv; + void (*free)(void *priv); +} SwsCompiledOp; + +typedef struct SwsOpBackend { + const char *name; /* Descriptive name for this backend */ + + /** + * Compile an operation list to an implementation chain. May modify `ops` + * freely; the original list will be freed automatically by the caller. + * + * Returns 0 or a negative error code. + */ + int (*compile)(SwsContext *ctx, SwsOpList *ops, SwsCompiledOp *out); +} SwsOpBackend; + +/* List of all backends, terminated by NULL */ +extern const SwsOpBackend *const ff_sws_op_backends[]; + +/** + * Attempt to compile a list of operations using a specific backend. + * + * Returns 0 on success, or a negative error code on failure. + */ +int ff_sws_ops_compile_backend(SwsContext *ctx, const SwsOpBackend *backend, + const SwsOpList *ops, SwsCompiledOp *out); + +/** + * Compile a list of operations using the best available backend. + * + * Returns 0 on success, or a negative error code on failure. + */ +int ff_sws_ops_compile(SwsContext *ctx, const SwsOpList *ops, SwsCompiledOp *out); + #endif /* SWSCALE_OPS_INTERNAL_H */