Commit a9054d015e for ffmpeg
commit a9054d015e107a7f56dbc5e350cd93c0ca91a701
Author: Lynne <dev@lynne.ee>
Date: Sat Sep 26 15:36:17 2026 +0900
vulkan_ffv1: overlap the state load of the next sample with the current one
The context of the next sample, and with it the load of its states, are
issued as soon as the value is known, before the states of the current
sample are adapted and written back, and the states are forwarded when
both samples use the same context. The renormalisation left over by the
sign decision moves behind the load.
The parts of the context and of the predictor that only depend on the
row above are computed 32 samples at a time, one per invocation, with
the row of the next chunk loaded during the current one, and decoded
samples are written once per chunk. The median predictor becomes a
clamp of the sample above between bounds computed per chunk.
Decoding a 6464x4852 16-bit RGB frame with 1024 slices on an RX 6900
XT, with the bitstream in VRAM, goes from 99.1/72.4/69.8 ms to
53.5/45.2/44.7 ms with context model 1/0/2.
diff --git a/libavcodec/vulkan/ffv1_dec.comp.glsl b/libavcodec/vulkan/ffv1_dec.comp.glsl
index c5b276b6b8..6ad7f3846b 100644
--- a/libavcodec/vulkan/ffv1_dec.comp.glsl
+++ b/libavcodec/vulkan/ffv1_dec.comp.glsl
@@ -83,44 +83,85 @@ void decode_line(ivec2 sp, int w,
}
#endif
+ bool ext = extend_lookup[quant_table_idx];
linecache_load(dec[p], sp, y, 0);
- bool ext = extend_lookup[quant_table_idx];
+ ivec3 top = subgroupBroadcast(get_pred_top(dec[p], sp, ivec2(0, y), 0, w,
+ quant_table_idx, ext), 0u);
+ ivec2 pr = get_pred_left(top, quant_table_idx, ext);
+ int c = pr[0];
+ int pred = pr[1];
+ int sgn = c < 0 ? -1 : 1;
+ int tl = top.y;
+ int l = linecache[1];
+ uint ctx = abs(c);
+ uint sbase = state_off + gl_LocalInvocationID.x;
+ uint soff = sbase + CONTEXT_SIZE*ctx;
+ uint ld = slice_rc_state[soff];
+ uint8_t adapted = uint8_t(0);
+ bool same = false;
+ uint row = 0;
ivec2 qthr = quant_ballot ? quant_thresh[quant_table_idx][gl_LocalInvocationID.x] : ivec2(0);
ivec2 qso = quant_ballot ? quant_scale_off[quant_table_idx] : ivec2(0);
- uint sbase = state_off + gl_LocalInvocationID.x;
-
- for (int x = 0; x < w; x++) {
- ivec2 pr;
- if (quant_ballot) {
- ivec3 top = get_pred_top(dec[p], sp, ivec2(x, y), 0, w, quant_table_idx, ext);
- TYPE cur = linecache[1];
- uvec4 q0 = subgroupBallot(int(int8_t(cur - top[0])) >= qthr.x);
- uvec4 q3 = subgroupBallot(ext && int(int8_t(linecache[0] - cur)) >= qthr.y);
- pr = ivec2(top[2] + qso.y + int(subgroupBallotBitCount(q0)) +
- qso.x*int(subgroupBallotBitCount(q3)),
- predict(cur, top.xy));
- } else {
- pr = get_pred(dec[p], sp, ivec2(x, y), 0, w, quant_table_idx, ext);
- }
- int sgn = pr[0] < 0 ? -1 : 1;
- uint soff = sbase + CONTEXT_SIZE*abs(pr[0]);
- uint st = slice_rc_state[soff];
-
- uint used, used_bits;
- int v = get_isymbol(st, pr[1], sgn, used, used_bits);
- rac_renorm();
- uint vz = zero_extend(v, bits);
- rac_check_window();
-
- if (subgroupInverseBallot(uvec4(used, 0, 0, 0)))
- slice_rc_state[soff] =
- zero_one_state[st + (subgroupInverseBallot(uvec4(used_bits, 0, 0, 0)) ? 256 : 0)];
+ ivec4 tr = get_top(dec[p], sp, ivec2(min(1 + int(gl_LocalInvocationID.x), w - 1), y),
+ 0, w, ext);
+ for (int x = 0; x < w; x += 32) {
+ ivec3 tn = get_pred_top_quant(tr, quant_table_idx, ext);
+ tn.z += qso.y;
+ tr = get_top(dec[p], sp, ivec2(min(x + 33 + int(gl_LocalInvocationID.x), w - 1), y),
+ 0, w, ext);
+ int gmin = min(tn.y - tn.x, 0);
+ int gmax = max(tn.y - tn.x, 0);
+ int n = min(w - x, 32);
+
+ int j = 0;
+ do {
+ uint st = same ? uint(adapted) : ld;
+ int base = subgroupBroadcast(tn.z, j);
+ int t = subgroupBroadcast(tn.y, j);
+
+ uint used, used_bits;
+ int v = get_isymbol(st, pred, sgn, used, used_bits);
+ uint vz = zero_extend(v, bits);
+#ifdef FLOAT
+ v = int(vz);
+#endif
- if (gl_LocalInvocationID.x == 0)
- imageStore(dec[p], sp + LADDR(ivec2(x, y)), uvec4(vz));
- linecache_next(TYPE(vz));
+ if (quant_ballot) {
+ uvec4 q0 = subgroupBallot(int(int8_t(v - tl)) >= qthr.x);
+ uvec4 q3 = subgroupBallot(ext && int(int8_t(l - v)) >= qthr.y);
+ c = base + int(subgroupBallotBitCount(q0)) + qso.x*int(subgroupBallotBitCount(q3));
+ } else {
+ c = base + quant_table[quant_table_idx][0][(v - tl) & MAX_QUANT_TABLE_MASK];
+ if (ext)
+ c += quant_table[quant_table_idx][3][(l - v) & MAX_QUANT_TABLE_MASK];
+ }
+ uint ctx_prev = ctx;
+ uint soff_prev = soff;
+ ctx = abs(c);
+ soff = sbase + CONTEXT_SIZE*ctx;
+ same = ctx == ctx_prev;
+ if (!same)
+ ld = slice_rc_state[soff];
+ rac_renorm();
+ uint nst = zero_one_state[st + (subgroupInverseBallot(uvec4(used_bits, 0, 0, 0)) ? 256 : 0)];
+
+ adapted = uint8_t(subgroupInverseBallot(uvec4(used, 0, 0, 0)) ? nst : st);
+ slice_rc_state[soff_prev] = adapted;
+ sgn = c < 0 ? -1 : 1;
+
+ int vm = int(TYPE(vz));
+ pred = subgroupBroadcast(clamp(tn.y, vm + gmin, vm + gmax), j);
+ row = gl_LocalInvocationID.x == j ? vz : row;
+ rac_check_window();
+
+ l = v;
+ tl = t;
+ } while (++j < n);
+
+ if (gl_LocalInvocationID.x < n)
+ imageStore(dec[p], sp + LADDR(ivec2(x + int(gl_LocalInvocationID.x), y)), uvec4(row));
}
memoryBarrierImage();