Commit 5a81cd4f2b for ffmpeg

commit 5a81cd4f2b167dbc30c0884a49cd626b4eb55c9e
Author: Lynne <dev@lynne.ee>
Date:   Sun Sep 27 01:32:33 2026 +0900

    ffv1enc_vulkan: compute contexts and residuals 32 samples at a time

    Every invocation computed the context, the prediction and the residual
    of each sample in turn, from neighbours kept in the line cache and
    loaded from the image, and the states of a sample were only loaded
    once it was reached.

    The invocations now compute them for 32 samples at once, one sample
    each, together with the masks of the states each sample will use, and
    the image loads of the next 32 samples are issued before the current
    ones are coded. The states of the next sample are loaded while the
    current one is coded, and forwarded when both samples use the same
    context. The byte queue is checked in the loop condition, the extended
    lookup becomes a specialisation constant, as the encoder uses a single
    quant table, and RGB rows are preloaded four samples per invocation at
    a time.

    The output is unchanged. Encoding a 6464x4852 16-bit RGB frame with
    1024 slices on an RX 6900 XT, with the frame in VRAM, goes from
    126.6/101.6 ms to 53.2/51.2 ms with context model 1/0.

    Large slices gain the most: with 24 slices, the same frame now takes
    1.5 s to encode, where the previous code ran into the GPU's job
    timeout and lost the device.

diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c
index b358a2a0df..265ae9bf35 100644
--- a/libavcodec/ffv1enc_vulkan.c
+++ b/libavcodec/ffv1enc_vulkan.c
@@ -1331,7 +1331,7 @@ static av_cold int vulkan_encode_ffv1_init(AVCodecContext *avctx)
                        !fv->force_pcm && fv->optimize_rct && !f->bayer;

     /* Init shader specialization consts */
-    SPEC_LIST_CREATE(sl, 19, 19*sizeof(uint32_t))
+    SPEC_LIST_CREATE(sl, 20, 20*sizeof(uint32_t))
     SPEC_LIST_ADD(sl,  0, 32, RGB_LINECACHE);
     SPEC_LIST_ADD(sl,  1, 32, f->ec);
     ff_ffv1_vk_set_common_sl(avctx, f, sl, fv->s.frames->sw_format);
@@ -1340,6 +1340,11 @@ static av_cold int vulkan_encode_ffv1_init(AVCodecContext *avctx)
     SPEC_LIST_ADD(sl, 17, 32, f->context_model);
     SPEC_LIST_ADD(sl, 18, 32, f->remap_mode);

+    if (f->ac != AC_GOLOMB_RICE) {
+        const int16_t (*qt)[MAX_QUANT_TABLE_SIZE] = f->quant_tables[f->context_model];
+        SPEC_LIST_ADD(sl, 19, 32, qt[3][127] || qt[4][127]);
+    }
+
     if (fv->optimize_rct) {
         err = init_rct_search_shader(avctx, sl);
         if (err < 0)
diff --git a/libavcodec/vulkan/ffv1_common.glsl b/libavcodec/vulkan/ffv1_common.glsl
index b23fd6f396..61e8446e9c 100644
--- a/libavcodec/vulkan/ffv1_common.glsl
+++ b/libavcodec/vulkan/ffv1_common.glsl
@@ -208,13 +208,9 @@ const uint32_t log2_run[41] = {

 #ifdef DECODE
 #define IMG_QUALI coherent readonly
-#else
-#define IMG_QUALI readonly
-#endif
-
-#if defined(DECODE) || defined(RC_SUBGROUP)
 VTYPE2 linecache;
 #else
+#define IMG_QUALI readonly
 shared VTYPE2 linecache;
 #endif

@@ -319,7 +315,7 @@ ivec2 get_pred(IMG_QUALI uimage2D pred, ivec2 sp, ivec2 off,

 void linecache_load(IMG_QUALI uimage2D src, ivec2 sp, int y, uint comp)
 {
-#if defined(DECODE) || defined(RC_SUBGROUP)
+#ifdef DECODE
     linecache = VTYPE2(TYPE(0), TYPE(0));
     if (y > 0)
         linecache[1] = TYPE(imageLoad(src, sp + LADDR(ivec2(0, y - 1)))[comp]);
diff --git a/libavcodec/vulkan/ffv1_enc.comp.glsl b/libavcodec/vulkan/ffv1_enc.comp.glsl
index 3e46a7f838..bb311532dd 100644
--- a/libavcodec/vulkan/ffv1_enc.comp.glsl
+++ b/libavcodec/vulkan/ffv1_enc.comp.glsl
@@ -57,6 +57,8 @@ layout (set = 1, binding = 2, scalar) buffer slice_state_buf {
     uint8_t slice_rc_state[];
 };

+layout (constant_id = 19) const bool enc_ext = false;
+
 void encode_line_pcm(in SliceContext sc, readonly uimage2D img,
                      ivec2 sp, int y, uint p, uint comp)
 {
@@ -101,42 +103,145 @@ void encode_line(in SliceContext sc, readonly uimage2D img, uint state_off,
         return;
 #endif

-    linecache_load(img, sp, y, comp);
+    int lane = int(gl_SubgroupInvocationID);
+    uint last_off = ~0u;
+    uint adapted = 0;

-    for (int x = 0; x < w; x++) {
-        ivec2 d = get_pred(img, sp, ivec2(x, y), comp, w,
-                           quant_table_idx, extend_lookup[quant_table_idx]);
-        TYPE cur = TYPE(imageLoad(img, sp + LADDR(ivec2(x, y)))[comp]);
-        d[1] = int(cur) - d[1];
+    ivec2 pc = ivec2(min(lane, w - 1), y);
+    uvec4 rcur, rT, rTR, rL, rTL, rLL = uvec4(0), rTT = uvec4(0);
+#ifdef RGB
+    rcur = imageLoad(img, sp + LADDR(pc));
+    rT   = imageLoad(img, sp + LADDR(pc + ivec2(0, -1)));
+    rTR  = imageLoad(img, sp + LADDR(ivec2(min(pc.x + 1, w - 1), y - 1)));
+    rL   = imageLoad(img, sp + LADDR(pc.x > 0 ? pc + ivec2(-1, 0) : ivec2(0, y - 1)));
+    rTL  = imageLoad(img, sp + LADDR(pc.x > 0 ? pc + ivec2(-1, -1) : ivec2(0, y - 2)));
+    if (enc_ext) {
+        rLL = pc.x > 0 ? imageLoad(img, sp + LADDR(pc.x > 1 ? pc + ivec2(-2, 0) : ivec2(0, y - 1))) : uvec4(0);
+        rTT = imageLoad(img, sp + LADDR(pc + ivec2(0, -2)));
+    }
+#else
+    int y1 = max(y - 1, 0);
+    int y2 = max(y - 2, 0);
+    rcur = imageLoad(img, sp + pc);
+    rT   = imageLoad(img, sp + ivec2(pc.x, y1));
+    rTR  = imageLoad(img, sp + ivec2(min(pc.x + 1, w - 1), y1));
+    rL   = imageLoad(img, sp + (pc.x > 0 ? pc + ivec2(-1, 0) : ivec2(0, y1)));
+    rTL  = imageLoad(img, sp + (pc.x > 0 ? ivec2(pc.x - 1, y1) : ivec2(0, y2)));
+    if (enc_ext) {
+        rLL = pc.x > 0 && (pc.x > 1 || y > 0) ?
+              imageLoad(img, sp + (pc.x > 1 ? pc + ivec2(-2, 0) : ivec2(0, y1))) : uvec4(0);
+        rTT = y > 1 ? imageLoad(img, sp + ivec2(pc.x, y2)) : uvec4(0);
+    }
+#endif

-        if (d[0] < 0)
-            d = -d;
+    for (int x = 0; x < w; x += 32) {
+        int px = min(x + lane, w - 1);
+        int cur = int(TYPE(rcur[comp]));
+        int T   = int(TYPE(rT[comp]));
+        int TR  = int(TYPE(rTR[comp]));
+        int L   = int(TYPE(rL[comp]));
+        int TL  = int(TYPE(rTL[comp]));
+        int LL  = int(TYPE(rLL[comp]));
+        int TT  = int(TYPE(rTT[comp]));
+#ifndef RGB
+        if (y < 1) {
+            T = 0;
+            TR = 0;
+            TL = 0;
+            L = px > 0 ? L : 0;
+        } else if (y < 2 && px == 0) {
+            TL = 0;
+        }
+#endif

-        d[1] = fold(d[1], bits);
+        int c = quant_table[quant_table_idx][0][(L - TL) & MAX_QUANT_TABLE_MASK] +
+                quant_table[quant_table_idx][1][(TL - T) & MAX_QUANT_TABLE_MASK] +
+                quant_table[quant_table_idx][2][(T - TR) & MAX_QUANT_TABLE_MASK];
+        if (enc_ext)
+            c += quant_table[quant_table_idx][3][(LL - L) & MAX_QUANT_TABLE_MASK] +
+                 quant_table[quant_table_idx][4][(TT - T) & MAX_QUANT_TABLE_MASK];

-        uint lane = gl_SubgroupInvocationID;
-        uint rc_off = state_off + CONTEXT_SIZE*d[0] + lane;
-        uint st = slice_rc_state[rc_off];
+        int d = cur - predict(L, ivec2(TL, T));
+        if (c < 0) {
+            c = -c;
+            d = -d;
+        }
+        d = fold(d, bits);
+        uint soff = state_off + CONTEXT_SIZE*uint(c);

-        uint ad = abs(d[1]);
+        uint ad = abs(d);
         int e = findMSB(ad);
         int ec = clamp(e, 0, 9);
         int es = 11 + min(e, 10);
-        uint used = d[1] == 0 ? 1u : 1u | ((4u << ec) - 2u) | (((1u << ec) - 1u) << 22) | (1u << es);
-        uint ones = d[1] == 0 ? 1u : ((2u << ec) - 2u) | ((ad & ((1u << ec) - 1u)) << 22) |
-                                     (uint(d[1] < 0) << es);
-        uint nst = zero_one_state[(bitfieldExtract(ones, int(lane), 1) << 8) + st];
-
-        uint s10, s31;
-        put_isymbol(st, d[1], s10, s31);
-
-        uint adapted = bitfieldExtract(used, int(lane), 1) != 0 ? nst : st;
-        if (ad >= 1024)
-            adapted = lane == 10 ? s10 : lane == 31 ? s31 : adapted;
-        slice_rc_state[rc_off] = uint8_t(adapted);
-        linecache_next(cur);
-        if (rc_nev > 42)
-            rac_emit();
+        uint used = d == 0 ? 1u : 1u | ((4u << ec) - 2u) | (((1u << ec) - 1u) << 22) | (1u << es);
+        uint ones = d == 0 ? 1u : ((2u << ec) - 2u) | ((ad & ((1u << ec) - 1u)) << 22) |
+                                  (uint(d < 0) << es);
+
+        int n = min(w - x, 32);
+        uint so_n = subgroupBroadcast(soff, 0);
+        int v_n = subgroupBroadcast(d, 0);
+        uint used_n = subgroupBroadcast(used, 0);
+        uint ones_n = subgroupBroadcast(ones, 0);
+        uint st = uint(slice_rc_state[so_n + lane]);
+        st = so_n == last_off ? adapted : st;
+
+        if (x + 32 < w) {
+            pc = ivec2(min(x + 32 + lane, w - 1), y);
+#ifdef RGB
+            rcur = imageLoad(img, sp + LADDR(pc));
+            rT   = imageLoad(img, sp + LADDR(pc + ivec2(0, -1)));
+            rTR  = imageLoad(img, sp + LADDR(ivec2(min(pc.x + 1, w - 1), y - 1)));
+            rL   = imageLoad(img, sp + LADDR(pc + ivec2(-1, 0)));
+            rTL  = imageLoad(img, sp + LADDR(pc + ivec2(-1, -1)));
+            if (enc_ext) {
+                rLL = imageLoad(img, sp + LADDR(pc + ivec2(-2, 0)));
+                rTT = imageLoad(img, sp + LADDR(pc + ivec2(0, -2)));
+            }
+#else
+            rcur = imageLoad(img, sp + pc);
+            rT   = imageLoad(img, sp + ivec2(pc.x, y1));
+            rTR  = imageLoad(img, sp + ivec2(min(pc.x + 1, w - 1), y1));
+            rL   = imageLoad(img, sp + pc + ivec2(-1, 0));
+            rTL  = imageLoad(img, sp + ivec2(pc.x - 1, y1));
+            if (enc_ext) {
+                rLL = imageLoad(img, sp + pc + ivec2(-2, 0));
+                rTT = y > 1 ? imageLoad(img, sp + ivec2(pc.x, y2)) : uvec4(0);
+            }
+#endif
+        }
+
+        int j = 0;
+        while (true) {
+            do {
+                uint so = so_n;
+                int v = v_n;
+                uint used_j = used_n;
+                uint ones_j = ones_n;
+                uint nst = zero_one_state[(bitfieldExtract(ones_j, lane, 1) << 8) + st];
+
+                int jn = min(j + 1, n - 1);
+                so_n = subgroupBroadcast(soff, jn);
+                v_n = subgroupBroadcast(d, jn);
+                used_n = subgroupBroadcast(used, jn);
+                ones_n = subgroupBroadcast(ones, jn);
+                uint ld = uint(slice_rc_state[so_n + lane]);
+
+                uint s10, s31;
+                put_isymbol(st, v, s10, s31);
+
+                adapted = bitfieldExtract(used_j, lane, 1) != 0 ? nst : st;
+                if (abs(v) >= 1024)
+                    adapted = lane == 10 ? s10 : lane == 31 ? s31 : adapted;
+
+                slice_rc_state[so + lane] = uint8_t(adapted);
+                last_off = so;
+                st = so_n == so ? adapted : ld;
+            } while (++j < n && rc_nev <= 42);
+            if (rc_nev > 42)
+                rac_emit();
+            if (j >= n)
+                break;
+        }
     }
 }

@@ -282,21 +387,26 @@ void transform_sample(inout ivec4 pix, ivec2 rct_coef, int offset)
 void preload_rgb(uint slice_idx, in SliceContext sc, ivec2 sp, int w, int y,
                  bool apply_rct)
 {
-    for (uint x = gl_LocalInvocationID.x; x < w; x += gl_WorkGroupSize.x) {
-        ivec2 lpos = sp + LADDR(ivec2(x, y));
-        ivec2 pos = sc.slice_pos + ivec2(x, y);
-
-        ivec4 pix = load_components(slice_idx, sc, pos);
+    for (uint x0 = 0; x0 < w; x0 += 4*gl_WorkGroupSize.x) {
+        ivec4 pix[4];
+        [[unroll]] for (uint k = 0; k < 4; k++) {
+            uint x = min(x0 + k*gl_WorkGroupSize.x + gl_LocalInvocationID.x, w - 1);
+            pix[k] = load_components(slice_idx, sc, sc.slice_pos + ivec2(x, y));
+        }

+        [[unroll]] for (uint k = 0; k < 4; k++) {
+            uint x = x0 + k*gl_WorkGroupSize.x + gl_LocalInvocationID.x;
+            if (x < w) {
 #ifdef FLOAT
-        if (apply_rct)
-            transform_sample(pix, sc.slice_rct_coef, sc.remap_count[0]);
+                if (apply_rct)
+                    transform_sample(pix[k], sc.slice_rct_coef, sc.remap_count[0]);
 #else
-        if (apply_rct)
-            transform_sample(pix, sc.slice_rct_coef, rct_offset);
+                if (apply_rct)
+                    transform_sample(pix[k], sc.slice_rct_coef, rct_offset);
 #endif
-
-        imageStore(tmp, lpos, pix);
+                imageStore(tmp, sp + LADDR(ivec2(x, y)), pix[k]);
+            }
+        }
     }

     memoryBarrierImage();