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]

Reply via email to