Commit a387d21c32 for ffmpeg

commit a387d21c3292dcacfc066e64295f9603c6c61aa9
Author: Lynne <dev@lynne.ee>
Date:   Sun Sep 27 01:15:56 2026 +0900

    ffv1enc_vulkan: encode range coded slices as a single subgroup

    A range coded slice is encoded by a workgroup of 32 invocations, but
    only the first one ran the range coder, on a coder state and context
    states kept in shared memory, with two barriers per sample.

    The slice is now encoded by a single subgroup. Each invocation holds
    one of the 32 states of the current context, and the range coder runs
    on uniform values, in a new encoder section of rangecoder_subgroup.glsl.
    A decision reads its state from the invocation that holds it and
    updates it there, so no barriers are needed. The coder keeps the top of
    its interval instead of the bottom, which coding a one leaves unchanged.
    The line cache of the range coded encoder is kept per invocation, as in
    the decoder, and subgroups of 32 invocations are required.

    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
    202.5/178.5 ms to 179.0/154.9 ms with context model 1/0.

diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c
index 1f0d839ded..b358a2a0df 100644
--- a/libavcodec/ffv1enc_vulkan.c
+++ b/libavcodec/ffv1enc_vulkan.c
@@ -1065,7 +1065,8 @@ static int init_encode_shader(AVCodecContext *avctx, VkSpecializationInfo *sl)

     uint32_t wg_x = fv->ctx.ac != AC_GOLOMB_RICE ? CONTEXT_SIZE : 1;
     ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
-                      (uint32_t []) { wg_x, 1, 1 }, 0);
+                      (uint32_t []) { wg_x, 1, 1 },
+                      fv->ctx.ac != AC_GOLOMB_RICE ? CONTEXT_SIZE : 0);

     ff_vk_shader_add_push_const(shd, 0, sizeof(FFv1ShaderParams),
                                 VK_SHADER_STAGE_COMPUTE_BIT);
@@ -1271,6 +1272,17 @@ static av_cold int vulkan_encode_ffv1_init(AVCodecContext *avctx)
         return err;
     }

+    /* Range coded slices are encoded by one subgroup of CONTEXT_SIZE
+     * invocations, each holding one of the states of a context */
+    if (f->ac != AC_GOLOMB_RICE &&
+        !(fv->s.subgroup_props.minSubgroupSize <= CONTEXT_SIZE &&
+          fv->s.subgroup_props.maxSubgroupSize >= CONTEXT_SIZE &&
+          (fv->s.subgroup_props.requiredSubgroupSizeStages & VK_SHADER_STAGE_COMPUTE_BIT))) {
+        av_log(avctx, AV_LOG_ERROR, "Range coded FFv1 encoding needs subgroups "
+               "of %i invocations\n", CONTEXT_SIZE);
+        return AVERROR(ENOTSUP);
+    }
+
     maxsize = ffv1_vk_buffer_size(avctx);
     if (maxsize > fv->s.props_11.maxMemoryAllocationSize) {
         av_log(avctx, AV_LOG_WARNING, "Encoding buffer size (%zu) larger "
diff --git a/libavcodec/vulkan/ffv1_common.glsl b/libavcodec/vulkan/ffv1_common.glsl
index 61e8446e9c..b23fd6f396 100644
--- a/libavcodec/vulkan/ffv1_common.glsl
+++ b/libavcodec/vulkan/ffv1_common.glsl
@@ -208,9 +208,13 @@ const uint32_t log2_run[41] = {

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

@@ -315,7 +319,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)
 {
-#ifdef DECODE
+#if defined(DECODE) || defined(RC_SUBGROUP)
     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 c59ad5527f..daa94b996e 100644
--- a/libavcodec/vulkan/ffv1_enc.comp.glsl
+++ b/libavcodec/vulkan/ffv1_enc.comp.glsl
@@ -24,6 +24,9 @@
 #extension GL_GOOGLE_include_directive : require

 #define ENCODE
+#ifndef GOLOMB
+#define RC_SUBGROUP
+#endif
 /* Golomb slices start writing at rac_terminate()'s byte count, with no
  * alignment guarantee */
 #define PB_UNALIGNED
@@ -54,33 +57,9 @@ layout (set = 1, binding = 2, scalar) buffer slice_state_buf {
     uint8_t slice_rc_state[];
 };

-#define WRITE(idx, val) put_rac(rc_state[idx], val)
-void put_symbol(int v)
-{
-    bool is_nil = (v == 0);
-    WRITE(0, is_nil);
-    if (is_nil)
-        return;
-
-    int a = abs(v);
-    int e = findMSB(a);
-
-    for (int i = 0; i < e; i++)
-        WRITE(1 + min(i, 9), true);
-    WRITE(1 + min(e, 9), false);
-
-    for (int i = e - 1; i >= 0; i--)
-        WRITE(22 + min(i, 9), bool(bitfieldExtract(a, i, 1)));
-
-    WRITE(22 - 11 + min(e, 10), v < 0);
-}
-
 void encode_line_pcm(in SliceContext sc, readonly uimage2D img,
                      ivec2 sp, int y, uint p, uint comp)
 {
-    if (gl_LocalInvocationID.x > 0)
-        return;
-
     int w = sc.slice_dim.x;
 #ifdef BAYER
     w >>= 1;
@@ -94,7 +73,7 @@ void encode_line_pcm(in SliceContext sc, readonly uimage2D img,
 #endif

     for (int x = 0; x < w; x++) {
-        uint v = imageLoad(img, sp + LADDR(ivec2(x, y)))[comp];
+        uint v = subgroupBroadcastFirst(imageLoad(img, sp + LADDR(ivec2(x, y)))[comp]);

         for (uint i = (rct_offset >> 1); i > 0; i >>= 1)
             put_rac_equi(bool(v & i));
@@ -135,16 +114,10 @@ void encode_line(in SliceContext sc, readonly uimage2D img, uint state_off,

         uint rc_off = state_off + CONTEXT_SIZE*d[0] + gl_LocalInvocationID.x;

-        rc_state[gl_LocalInvocationID.x] = slice_rc_state[rc_off];
-        barrier();
-
-        if (gl_LocalInvocationID.x == 0) {
-            put_symbol(d[1]);
-            linecache_next(cur);
-        }
-
-        barrier();
-        slice_rc_state[rc_off] = rc_state[gl_LocalInvocationID.x];
+        uint st = slice_rc_state[rc_off];
+        put_isymbol(st, d[1]);
+        slice_rc_state[rc_off] = uint8_t(st);
+        linecache_next(cur);
     }
 }

@@ -460,6 +433,8 @@ void finalize_slice(in uint slice_idx)
     uint32_t enc_len = hdr_len + flush_put_bits(pb);
 #else
     uint32_t enc_len = rac_terminate();
+    if (gl_LocalInvocationID.x > 0)
+        return;
 #endif

     u8buf bs = u8buf(slice_data + rc.bs_start);
@@ -498,12 +473,18 @@ void main(void)
 {
     uint slice_idx = gl_WorkGroupID.y*gl_NumWorkGroups.x + gl_WorkGroupID.x;

+#ifdef GOLOMB
     if (gl_LocalInvocationID.x == 0)
         rc = slice_ctx[slice_idx].c;
     barrier();
+#else
+    rac_init_enc(slice_ctx[slice_idx].c);
+#endif

     encode_slice(slice_ctx[slice_idx], slice_idx);

+#ifdef GOLOMB
     if (gl_LocalInvocationID.x == 0)
+#endif
         finalize_slice(slice_idx);
 }
diff --git a/libavcodec/vulkan/rangecoder_subgroup.glsl b/libavcodec/vulkan/rangecoder_subgroup.glsl
index da787dc99b..3a2e98d403 100644
--- a/libavcodec/vulkan/rangecoder_subgroup.glsl
+++ b/libavcodec/vulkan/rangecoder_subgroup.glsl
@@ -60,6 +60,114 @@ uint rac_range1(uint range, uint state24)
     return hi;
 }

+#ifdef ENCODE
+uint rc_top;
+uint rc_oc;
+int rc_ob;
+
+void rac_init_enc(in RangeCoder c)
+{
+    for (uint i = gl_SubgroupInvocationID; i < 512; i += gl_SubgroupSize)
+        zero_one_state[i] = rangecoder_state[i];
+    barrier();
+
+    rc = c;
+    rc_top = rc.low + rc.range;
+    rc_oc = uint(rc.outstanding_count);
+    rc_ob = int(rc.outstanding_byte);
+}
+
+void rac_renorm_enc(void)
+{
+    uint low = rc_top - rc.range;
+    rc.range <<= 8;
+    rc_top = ((low & 0xFFu) << 8) + rc.range;
+
+    if (rc_ob < 0) {
+        rc_ob = int(low >> 8);
+    } else if (low - 0xFF01u < 0xFFu) {
+        rc_oc++;
+    } else {
+        uint lane = gl_SubgroupInvocationID;
+        uint carry = low >> 16;
+        if (lane == 0)
+            slice_data[rc.bs_off].v = uint8_t(uint(rc_ob) + carry);
+        for (uint k = 0; k < rc_oc; k += 32)
+            if (lane < rc_oc - k)
+                slice_data[rc.bs_off + 1 + k + lane].v = uint8_t(carry - 1u);
+        rc.bs_off += 1 + rc_oc;
+        rc_oc = 0;
+        rc_ob = int((low >> 8) & 0xFFu);
+    }
+}
+
+void put_rac_range1(uint range1, bool bit)
+{
+    rc_top = bit ? rc_top : rc_top - range1;
+    rc.range = bit ? range1 : rc.range - range1;
+    if (rc.range < 0x100)
+        rac_renorm_enc();
+}
+
+void put_rac(uint state24, bool bit)
+{
+    put_rac_range1(rac_range1(rc.range, state24), bit);
+}
+
+void put_rac_equi(bool bit)
+{
+    put_rac_range1(rc.range >> 1, bit);
+}
+
+uint rac_terminate(void)
+{
+    uint range1 = (rc.range * 129) >> 8;
+    rc.range -= range1;
+    rc_top -= range1;
+    if (rc.range < 0x100)
+        rac_renorm_enc();
+
+    rc_top = rc_top - rc.range + 0x1FEu;
+    rc.range = 0xFFu;
+    rac_renorm_enc();
+    rc_top = rc_top - rc.range + 0xFFu;
+    rc.range = 0xFFu;
+    rac_renorm_enc();
+
+    rc.low = rc_top - rc.range;
+    rc.outstanding_count = uint16_t(rc_oc);
+    rc.outstanding_byte = int16_t(rc_ob);
+    return rc.bs_off - rc.bs_start;
+}
+
+void put_rac_state(inout uint st, uint i, bool bit)
+{
+    uint s = subgroupBroadcast(st, i);
+    put_rac(s << 24, bit);
+    uint ns = uint(zero_one_state[(uint(bit) << 8) + s]);
+    st = gl_SubgroupInvocationID == i ? ns : st;
+}
+
+void put_isymbol(inout uint st, int v)
+{
+    put_rac_state(st, 0, v == 0);
+    if (v == 0)
+        return;
+
+    uint a = abs(v);
+    int e = findMSB(a);
+
+    for (int i = 0; i < e; i++)
+        put_rac_state(st, 1 + min(i, 9), true);
+    put_rac_state(st, 1 + min(e, 9), false);
+
+    for (int i = e - 1; i >= 0; i--)
+        put_rac_state(st, 22 + min(i, 9), bitfieldExtract(a, i, 1) != 0);
+
+    put_rac_state(st, 11 + min(e, 10), v < 0);
+}
+#endif
+
 #ifdef DECODE
 uint rc_win;
 uint rc_dist;