This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit 4bdb4d564f677d2f7f7f03b3b9a902a06cd916ee Author: Niklas Haas <[email protected]> AuthorDate: Thu Jul 23 21:26:52 2026 +0200 Commit: Niklas Haas <[email protected]> CommitDate: Mon Aug 3 09:32:30 2026 +0000 swscale/uops: add SWS_UOP_LUT_3D reference implementation The structure for the tetrahedral interpolation deviates slightly from the naive formulation in lut3d.c; instead of branching into every separate case, we sort the weights and offsets using a series of conditional swaps. This actually performs identically on my end, but results in code that is much closer to what SIMD will be doing. That should hopefully serve as a better reference for future SIMD implementors. (Myself included) I also reordered the dynamic tone-mapping code a bit to better indicate the sources of live register pressure that will manifest in the real SIMD kernel. Sponsored-by: Sovereign Tech Fund Signed-off-by: Niklas Haas <[email protected]> --- libswscale/csputils.h | 4 ++ libswscale/uops.c | 11 ++++ libswscale/uops.h | 8 +++ libswscale/uops_backend.c | 1 + libswscale/uops_list.h | 1 + libswscale/uops_macros.h | 8 +++ libswscale/uops_tmpl.c | 159 +++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 191 insertions(+), 1 deletion(-) diff --git a/libswscale/csputils.h b/libswscale/csputils.h index 82eb0b68f4..5437276ead 100644 --- a/libswscale/csputils.h +++ b/libswscale/csputils.h @@ -73,6 +73,10 @@ typedef struct v3u16_t { uint16_t x, y, z; } v3u16_t; +typedef struct v3f32_t { + float x, y, z; +} v3f32_t; + /* Fast perceptual quantizer */ static const float PQ_M1 = 2610./4096 * 1./4, PQ_M2 = 2523./4096 * 128, diff --git a/libswscale/uops.c b/libswscale/uops.c index fc9d609636..95ce6c719d 100644 --- a/libswscale/uops.c +++ b/libswscale/uops.c @@ -159,6 +159,9 @@ void ff_sws_uop_name(const SwsUOp *op, char buf[SWS_UOP_NAME_MAX]) const unsigned size = 1u << par->dither.size_log2; av_bprintf(&bp, "_%ux%u", size, size); break; + case SWS_UOP_LUT_3D: + av_bprintf(&bp, "_%s", par->lut3d.dynamic ? "dynamic" : "static"); + break; } av_assert0(av_bprint_is_complete(&bp)); @@ -175,6 +178,9 @@ static void uop_uninit(SwsUOp *uop) case SWS_UOP_READ_PLANAR_FV_FMA: av_refstruct_unref(&uop->data.kernel); break; + case SWS_UOP_LUT_3D: + av_refstruct_unref(&uop->data.lut3d); + break; } *uop = (SwsUOp) {0}; @@ -655,6 +661,11 @@ static int translate_op(SwsContext *ctx, SwsUOpList *uops, SwsUOpFlags flags, uop.uop = SWS_UOP_SWAP_BYTES; uop.type = pixel_type_to_int(op->type); break; + case SWS_OP_LUT_3D: + uop.uop = SWS_UOP_LUT_3D; + uop.par.lut3d.dynamic = op->lut3d.dynamic; + uop.data.lut3d = av_refstruct_ref_c(op->lut3d.lut); + break; default: return AVERROR(ENOTSUP); } diff --git a/libswscale/uops.h b/libswscale/uops.h index 198e6caea9..feb8744865 100644 --- a/libswscale/uops.h +++ b/libswscale/uops.h @@ -33,6 +33,7 @@ typedef struct SwsContext SwsContext; typedef struct SwsFilterWeights SwsFilterWeights; +typedef struct SwsLut3D SwsLut3D; typedef struct SwsOpList SwsOpList; typedef enum SwsPixelType { @@ -175,6 +176,7 @@ typedef enum SwsUOpType { SWS_UOP_LINEAR, /* mask = non-trivial output rows */ SWS_UOP_LINEAR_FMA, /* with SWS_UOP_FLAG_FMA */ SWS_UOP_DITHER, /* mask = components to dither */ + SWS_UOP_LUT_3D, /* mask = needed output components */ /* Platform-specific uops would go here */ SWS_UOP_TYPE_NB, @@ -237,6 +239,10 @@ typedef struct SwsDitherUOp { uint8_t size_log2; } SwsDitherUOp; +typedef struct SwsLut3DUOp { + int dynamic; +} SwsLut3DUOp; + /** * Computes (1 << size_log2) + MAX(y_offset). The dither matrix attached to * the SwsUOp is always pre-padded to this number of lines. @@ -252,6 +258,7 @@ typedef union SwsUOpParams { SwsClearUOp clear; SwsLinearUOp lin; SwsDitherUOp dither; + SwsLut3DUOp lut3d; } SwsUOpParams; typedef struct SwsUOp { @@ -269,6 +276,7 @@ typedef struct SwsUOp { SwsPixel vec4[4]; SwsPixel mat4[4][5]; /* row major */ SwsShuffleMask shuffle; /* for SWS_UOP_RW_SHUFFLE */ + const SwsLut3D *lut3d; /* for SWS_UOP_LUT_3D; refstruct */ void *opaque; /* reserved for internal use */ } data; } SwsUOp; diff --git a/libswscale/uops_backend.c b/libswscale/uops_backend.c index d0d05ee42f..5b35c1b585 100644 --- a/libswscale/uops_backend.c +++ b/libswscale/uops_backend.c @@ -92,6 +92,7 @@ SWS_FOR(TYPE, CLEAR, REF_ENTRY) \ SWS_FOR(TYPE, LINEAR, REF_ENTRY) \ SWS_FOR(TYPE, DITHER, REF_ENTRY) \ + SWS_FOR(TYPE, LUT_3D, REF_ENTRY) \ /* end of macro */ static const SwsUOpTable uop_table = { diff --git a/libswscale/uops_list.h b/libswscale/uops_list.h index a35af90360..31632dfcc0 100644 --- a/libswscale/uops_list.h +++ b/libswscale/uops_list.h @@ -57,5 +57,6 @@ ENTRY(SWS_UOP_RSHIFT, "rshift") \ ENTRY(SWS_UOP_CLEAR, "clear") \ ENTRY(SWS_UOP_DITHER, "dither") \ + ENTRY(SWS_UOP_LUT_3D, "lut_3d") \ #endif /* SWSCALE_UOPS_LIST_H */ diff --git a/libswscale/uops_macros.h b/libswscale/uops_macros.h index 6d5c8facf9..3a21efe206 100644 --- a/libswscale/uops_macros.h +++ b/libswscale/uops_macros.h @@ -343,6 +343,8 @@ #define SWS_FOR_STRUCT_U8_LINEAR_FMA(MACRO, ...) #define SWS_FOR_U8_DITHER(MACRO, ...) #define SWS_FOR_STRUCT_U8_DITHER(MACRO, ...) +#define SWS_FOR_U8_LUT_3D(MACRO, ...) +#define SWS_FOR_STRUCT_U8_LUT_3D(MACRO, ...) #define SWS_FOR_U16_READ_PLANAR(MACRO, ...) \ MACRO(__VA_ARGS__, u16_read_planar_x , SWS_PIXEL_U16, SWS_UOP_READ_PLANAR , 0x1) \ MACRO(__VA_ARGS__, u16_read_planar_xy , SWS_PIXEL_U16, SWS_UOP_READ_PLANAR , 0x3) \ @@ -625,6 +627,8 @@ #define SWS_FOR_STRUCT_U16_LINEAR_FMA(MACRO, ...) #define SWS_FOR_U16_DITHER(MACRO, ...) #define SWS_FOR_STRUCT_U16_DITHER(MACRO, ...) +#define SWS_FOR_U16_LUT_3D(MACRO, ...) +#define SWS_FOR_STRUCT_U16_LUT_3D(MACRO, ...) #define SWS_FOR_U32_READ_PLANAR(MACRO, ...) \ MACRO(__VA_ARGS__, u32_read_planar_x , SWS_PIXEL_U32, SWS_UOP_READ_PLANAR , 0x1) \ MACRO(__VA_ARGS__, u32_read_planar_xyz , SWS_PIXEL_U32, SWS_UOP_READ_PLANAR , 0x7) \ @@ -845,6 +849,8 @@ #define SWS_FOR_STRUCT_U32_LINEAR_FMA(MACRO, ...) #define SWS_FOR_U32_DITHER(MACRO, ...) #define SWS_FOR_STRUCT_U32_DITHER(MACRO, ...) +#define SWS_FOR_U32_LUT_3D(MACRO, ...) +#define SWS_FOR_STRUCT_U32_LUT_3D(MACRO, ...) #define SWS_FOR_F32_READ_PLANAR(MACRO, ...) #define SWS_FOR_STRUCT_F32_READ_PLANAR(MACRO, ...) #define SWS_FOR_F32_READ_PLANAR_FH(MACRO, ...) \ @@ -1157,5 +1163,7 @@ MACRO(__VA_ARGS__, f32_dither_xyzw_3_2_0_5_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0xf, .par.dither = { .y_offset = {3, 2, 0, 5}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xyzw_5_0_3_2_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0xf, .par.dither = { .y_offset = {5, 0, 3, 2}, .size_log2 = 4 }) \ MACRO(__VA_ARGS__, f32_dither_xyzw_5_2_3_0_16x16 , .type = SWS_PIXEL_F32, .uop = SWS_UOP_DITHER , .mask = 0xf, .par.dither = { .y_offset = {5, 2, 3, 0}, .size_log2 = 4 }) +#define SWS_FOR_F32_LUT_3D(MACRO, ...) +#define SWS_FOR_STRUCT_F32_LUT_3D(MACRO, ...) #endif /* SWSCALE_UOPS_MACROS_H */ diff --git a/libswscale/uops_tmpl.c b/libswscale/uops_tmpl.c index 172b7fb06b..52152bcdd5 100644 --- a/libswscale/uops_tmpl.c +++ b/libswscale/uops_tmpl.c @@ -30,6 +30,7 @@ # define PIXEL_TYPE SWS_PIXEL_F32 # define pixel_t float # define inter_t float +# define vec3_t v3f32_t # define PX F32 # define px f32 #elif BIT_DEPTH == 32 @@ -839,10 +840,166 @@ DECL_FUNC(linear, const SwsCompMask mask, const uint32_t one, const uint32_t zer SWS_FOR(PX, LINEAR, DECL_IMPL, linear) SWS_FOR_STRUCT(PX, LINEAR, DECL_ENTRY, .setup = fn(setup_linear) ) +/****************** + * Look-up tables * + ******************/ + +DECL_SETUP(setup_lut3d, params, out) +{ + const SwsLut3D *lut = params->uop->data.lut3d; + out->priv.ptr = (void *) av_refstruct_ref_c(lut); + out->free = ff_op_priv_unref; + return 0; +} + +#if IS_FLOAT +av_always_inline static vec3_t fn(vec3)(v3u16_t v) +{ + return (vec3_t) { v.x, v.y, v.z }; +} + +#define lerp(a, b, w) ((a) + (w) * ((pixel_t) (b) - (a))) + +av_always_inline static +vec3_t fn(lerp3)(vec3_t a, vec3_t b, pixel_t w) +{ + return (vec3_t) { + lerp(a.x, b.x, w), + lerp(a.y, b.y, w), + lerp(a.z, b.z, w), + }; +} + +av_always_inline static +vec3_t fn(lut3d_static)(const SwsLut3D *restrict lut3d, vec3_t rgb) +{ + const int r_base = (int) rgb.x; + const int g_base = (int) rgb.y; + const int b_base = (int) rgb.z; + + int off0 = (r_base < INPUT_LUT_SIZE - 1); + int off1 = (g_base < INPUT_LUT_SIZE - 1) * INPUT_LUT_SIZE; + int off2 = (b_base < INPUT_LUT_SIZE - 1) * INPUT_LUT_SIZE * INPUT_LUT_SIZE; + pixel_t f0 = rgb.x - r_base; + pixel_t f1 = rgb.y - g_base; + pixel_t f2 = rgb.z - b_base; + + /* Sort offsets descending by relative weight */ + if (f0 < f1) { + FFSWAP(pixel_t, f0, f1); + FFSWAP(int, off0, off1); + } + if (f0 < f2) { + FFSWAP(pixel_t, f0, f2); + FFSWAP(int, off0, off2); + } + if (f1 < f2) { + FFSWAP(pixel_t, f1, f2); + FFSWAP(int, off1, off2); + } + + /* Tetrahedral interpolation */ + const pixel_t w0 = 1 - f0; + const pixel_t w1 = f0 - f1; + const pixel_t w2 = f1 - f2; + const pixel_t w3 = f2; + + const v3u16_t *restrict base = &lut3d->input[b_base][g_base][r_base]; + const vec3_t v0 = fn(vec3)(base[0]); + const vec3_t v1 = fn(vec3)(base[off0]); + const vec3_t v2 = fn(vec3)(base[off0 + off1]); + const vec3_t v3 = fn(vec3)(base[off0 + off1 + off2]); + + return (vec3_t) { + w0 * v0.x + w1 * v1.x + w2 * v2.x + w3 * v3.x, + w0 * v0.y + w1 * v1.y + w2 * v2.y + w3 * v3.y, + w0 * v0.z + w1 * v1.z + w2 * v2.z + w3 * v3.z, + }; +} + +av_always_inline static +vec3_t fn(lut3d_dynamic)(const SwsLut3D *restrict lut3d, vec3_t rgb) +{ + rgb.x *= (TONE_LUT_SIZE - 1) / (pixel_t) UINT16_MAX; + + /* Linear interpolation */ + const int Ix = (int) rgb.x; + const pixel_t If = rgb.x - Ix; + + const v2u16_t a = lut3d->tone_map[Ix]; + const v2u16_t b = lut3d->tone_map[Ix + 1]; + + const pixel_t k = lerp(a.y, b.y, If); + const pixel_t bias = (1 << 15) - k; + const pixel_t scale = k / (pixel_t) (1 << 15); + + rgb.x = lerp(a.x, b.x, If); + rgb.y = bias + scale * rgb.y; + rgb.z = bias + scale * rgb.z; + + /* Re-scale to output LUT size */ + rgb.x *= (OUTPUT_LUT_SIZE_I - 1) / (pixel_t) UINT16_MAX; + rgb.y *= (OUTPUT_LUT_SIZE_PT - 1) / (pixel_t) UINT16_MAX; + rgb.z *= (OUTPUT_LUT_SIZE_PT - 1) / (pixel_t) UINT16_MAX; + + /* Trilinear interpolation */ + const int lo0 = (int) rgb.x; + const int lo1 = (int) rgb.y; + const int lo2 = (int) rgb.z; + + const int hi0 = FFMIN(lo0 + 1, OUTPUT_LUT_SIZE_I - 1); + const int hi1 = FFMIN(lo1 + 1, OUTPUT_LUT_SIZE_PT - 1); + const int hi2 = FFMIN(lo2 + 1, OUTPUT_LUT_SIZE_PT - 1); + + const pixel_t w0 = rgb.x - lo0; + const vec3_t c000 = fn(vec3)(lut3d->output[lo2][lo1][lo0]); + const vec3_t c001 = fn(vec3)(lut3d->output[lo2][lo1][hi0]); + const vec3_t c00 = fn(lerp3)(c000, c001, w0); + const vec3_t c010 = fn(vec3)(lut3d->output[lo2][hi1][lo0]); + const vec3_t c011 = fn(vec3)(lut3d->output[lo2][hi1][hi0]); + const vec3_t c01 = fn(lerp3)(c010, c011, w0); + const vec3_t c100 = fn(vec3)(lut3d->output[hi2][lo1][lo0]); + const vec3_t c101 = fn(vec3)(lut3d->output[hi2][lo1][hi0]); + const vec3_t c10 = fn(lerp3)(c100, c101, w0); + const vec3_t c110 = fn(vec3)(lut3d->output[hi2][hi1][lo0]); + const vec3_t c111 = fn(vec3)(lut3d->output[hi2][hi1][hi0]); + const vec3_t c11 = fn(lerp3)(c110, c111, w0); + + const pixel_t w1 = rgb.y - lo1; + const vec3_t c0 = fn(lerp3)(c00, c01, w1); + const vec3_t c1 = fn(lerp3)(c10, c11, w1); + + const pixel_t w2 = rgb.z - lo2; + return fn(lerp3)(c0, c1, w2); +} + +DECL_FUNC(lut3d, const SwsCompMask mask, const int dynamic) +{ + const SwsLut3D *restrict lut3d = impl->priv.ptr; + + SWS_LOOP + for (int i = 0; i < SWS_BLOCK_SIZE; i++) { + vec3_t c = { x[i], y[i], z[i] }; + c = fn(lut3d_static)(lut3d, c); + if (dynamic) + c = fn(lut3d_dynamic)(lut3d, c); + + x[i] = c.x; + y[i] = c.y; + z[i] = c.z; + } + + CONTINUE(x, y, z, w); +} +#endif /* IS_FLOAT */ + +SWS_FOR(PX, LUT_3D, DECL_IMPL, lut3d) +SWS_FOR_STRUCT(PX, LUT_3D, DECL_ENTRY, .setup = fn(setup_lut3d) ) + #undef PIXEL_MAX #undef PIXEL_SWAP #undef pixel_t #undef inter_t -#undef block_t +#undef vec3_t #undef PX #undef px _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
