Files
Lynne 6120b70d66 ffv1_vulkan: range code with subgroups smaller than 32 invocations
Range coded slices were decoded and encoded by a subgroup of exactly 32
invocations, one for each of the states of a context, and devices which
could not provide one were refused. The number of invocations now
follows the subgroup size: 32 if the subgroup size can be set to it,
and the largest subgroup size below it otherwise. Each invocation holds
32 divided by that number of the states of a context and of the bytes
of the decoder's window, and lines are coded in chunks of as many
samples. The encoder keeps its queue of 64 output bytes, and computes
the slice CRC over as many segments. Devices whose subgroups are all
wider than 32 invocations run the 32 invocations as part of one, and
are no longer refused either. The ballot quantizers need 32
invocations, and are only used with them.

With 32 invocations, the instructions executed per sample and the speed
of both the decoder and the encoder are unchanged. Decoding and encoding
were checked with 1 to 16 invocations on an RX 6900 XT, running the
workgroups as partial subgroups, and decoding with the subgroups of 8
invocations of lavapipe.
2026-10-03 12:15:47 +09:00

530 lines
16 KiB
GLSL

/*
* FFv1 codec
*
* Copyright (c) 2024 Lynne <dev@lynne.ee>
*
* This file is part of FFmpeg.
*
* FFmpeg is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* FFmpeg is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#pragma shader_stage(compute)
#extension GL_GOOGLE_include_directive : require
#define DECODE
#ifndef GOLOMB
#define RC_SUBGROUP
#endif
#include "common.glsl"
#include "ffv1_common.glsl"
layout (set = 1, binding = 1, scalar) readonly buffer slice_order_buf {
uint32_t slice_order[];
};
layout (set = 1, binding = 2, scalar) writeonly buffer slice_status_buf {
uint32_t slice_status[];
};
layout (set = 1, binding = 4) coherent uniform uimage2D dec[];
#ifdef FLOAT
layout(set = 1, binding = 6) readonly buffer fltmap_buf {
uint fltmap[][4][65536];
};
#endif
#ifndef GOLOMB
layout (set = 1, binding = 3, scalar) buffer slice_state_buf {
uint8_t slice_rc_state[];
};
layout (constant_id = 20) const bool quant_ballot = false;
void decode_line_pcm(ivec2 sp, int w, int y, int p)
{
#ifndef RGB
if (p > 0 && p < 3) {
w = ceil_rshift(w, chroma_shift.x);
sp >>= chroma_shift;
}
#endif
for (int x = 0; x < w; x++) {
uint v = 0;
for (uint i = (rct_offset >> 1); i > 0; i >>= 1)
v |= get_rac_equi() ? i : 0;
if (gl_LocalInvocationID.x == 0)
imageStore(dec[p], sp + LADDR(ivec2(x, y)), uvec4(v));
}
}
shared int16_t quant_top[2][3][MAX_QUANT_TABLE_SIZE];
void decode_line(ivec2 sp, int w,
int y, int p, int bits, uint state_off,
uint8_t quant_table_idx, int run_index, bool ext)
{
#ifndef RGB
if (p > 0 && p < 3) {
w = ceil_rshift(w, chroma_shift.x);
sp >>= chroma_shift;
}
#endif
linecache_load(dec[p], sp, y, 0);
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[RC_K];
uint8_t adapted[RC_K];
[[unroll]] for (uint k = 0; k < RC_K; k++) {
ld[k] = slice_rc_state[soff + k*RC_LANES];
adapted[k] = 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);
#ifdef BAYER
int slot = p == 0 ? 0 : p > 1 ? 1 : -1;
#else
int slot = p == 0 ? 0 : p < 3 ? 1 : -1;
#endif
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 += int(RC_LANES)) {
ivec3 tn;
if (slot >= 0) {
int tb = quant_top[slot][0][(tr[0] - tr[1]) & MAX_QUANT_TABLE_MASK] +
quant_top[slot][1][(tr[1] - tr[2]) & MAX_QUANT_TABLE_MASK];
if (ext)
tb += quant_top[slot][2][(tr[3] - tr[1]) & MAX_QUANT_TABLE_MASK];
tn = ivec3(tr[0], tr[1], tb);
} else {
tn = get_pred_top_quant(tr, quant_table_idx, ext);
}
tn.z += qso.y;
tr = get_top(dec[p], sp,
ivec2(min(x + int(RC_LANES) + 1 + 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, int(RC_LANES));
int j = 0;
do {
RCStates st;
[[unroll]] for (uint k = 0; k < RC_K; k++)
st.v[k] = same ? uint(adapted[k]) : ld[k];
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 (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)
[[unroll]] for (uint k = 0; k < RC_K; k++)
ld[k] = slice_rc_state[soff + k*RC_LANES];
rac_renorm();
[[unroll]] for (uint k = 0; k < RC_K; k++) {
bool one = subgroupInverseBallot(uvec4(used_bits >> (k*RC_LANES), 0, 0, 0));
uint nst = zero_one_state[st.v[k] + (one ? 256 : 0)];
bool read = subgroupInverseBallot(uvec4(used >> (k*RC_LANES), 0, 0, 0));
adapted[k] = uint8_t(read ? nst : st.v[k]);
slice_rc_state[soff_prev + k*RC_LANES] = adapted[k];
}
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();
barrier();
}
#else /* GOLOMB */
layout (set = 1, binding = 3, scalar) buffer slice_state_buf {
VlcState slice_vlc_state[];
};
GetBitContext gb;
void golomb_init(void)
{
if (version == 3 && micro_version > 1 || version > 3)
get_rac_internal((rc.range * 129) >> 8);
uint64_t ac_byte_count = rc.bs_off - rc.bs_start - 1;
init_get_bits(gb, u8buf(slice_data + rc.bs_start + ac_byte_count),
int(rc.bs_end - rc.bs_start - ac_byte_count));
}
void decode_line(ivec2 sp, int w,
int y, int p, int bits, uint state_off,
uint8_t quant_table_idx, inout int run_index, bool ext)
{
#ifndef RGB
if (p > 0 && p < 3) {
w = ceil_rshift(w, chroma_shift.x);
sp >>= chroma_shift;
}
#endif
linecache_load(dec[p], sp, y, 0);
int run_count = 0;
int run_mode = 0;
for (int x = 0; x < w; x++) {
ivec2 pos = sp + ivec2(x, y);
int diff;
ivec2 pr = get_pred(dec[p], sp, ivec2(x, y), 0, w,
quant_table_idx, ext);
uint vlc_off = state_off + abs(pr[0]);
if (pr[0] == 0 && run_mode == 0)
run_mode = 1;
if (run_mode != 0) {
if (run_count == 0 && run_mode == 1) {
int tmp_idx = int(log2_run[run_index]);
if (get_bit(gb)) {
run_count = 1 << tmp_idx;
if (x + run_count <= w)
run_index++;
} else {
if (tmp_idx != 0) {
run_count = int(get_bits(gb, tmp_idx));
} else
run_count = 0;
if (run_index != 0)
run_index--;
run_mode = 2;
}
}
run_count--;
if (run_count < 0) {
run_mode = 0;
run_count = 0;
diff = read_vlc_symbol(gb, slice_vlc_state[vlc_off], bits);
if (diff >= 0)
diff++;
} else {
diff = 0;
}
} else {
diff = read_vlc_symbol(gb, slice_vlc_state[vlc_off], bits);
}
if (pr[0] < 0)
diff = -diff;
uint v = zero_extend(pr[1] + diff, bits);
imageStore(dec[p], sp + LADDR(ivec2(x, y)), uvec4(v));
linecache_next(TYPE(v));
}
}
#endif
void decode_plane_line(ivec2 sp, int w, int y, int p, int bits, uint state_off,
uint8_t quant_table_idx, inout int run_index)
{
if (has_extend_lookup && extend_lookup[quant_table_idx])
decode_line(sp, w, y, p, bits, state_off, quant_table_idx, run_index, true);
else
decode_line(sp, w, y, p, bits, state_off, quant_table_idx, run_index, false);
}
#ifdef BAYER
void writeout_bayer(uint slice_idx, in SliceContext sc, ivec2 sp, int w, int y)
{
memoryBarrierImage();
barrier();
int offset = rct_offset;
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(int(x) << 1, y << 1);
int g_r = int(imageLoad(dec[0], lpos)[0]);
int g_b = int(imageLoad(dec[1], lpos)[0]);
int b = int(imageLoad(dec[2], lpos)[0]);
int r = int(imageLoad(dec[3], lpos)[0]);
if (sc.slice_coding_mode != 1) {
b -= offset;
r -= offset;
g_r -= (b*sc.slice_rct_coef.g + r*sc.slice_rct_coef.r) >> 2;
b += g_r;
r += g_r;
int gd = g_b - offset;
g_b = g_r - (gd >> 1);
g_r = g_b + gd;
}
imageStore(dst[0], pos + ivec2(0, 0), uvec4(r));
imageStore(dst[0], pos + ivec2(1, 0), uvec4(g_r));
imageStore(dst[0], pos + ivec2(0, 1), uvec4(g_b));
imageStore(dst[0], pos + ivec2(1, 1), uvec4(b));
}
}
#endif
#ifdef RGB
ivec4 transform_sample(ivec4 pix, ivec2 rct_coef, int offset)
{
pix.b -= offset;
pix.r -= offset;
pix.g -= (pix.b*rct_coef.g + pix.r*rct_coef.r) >> 2;
pix.b += pix.g;
pix.r += pix.g;
return pix;
}
void writeout_rgb(uint slice_idx, in SliceContext sc, ivec2 sp, int w, int y,
bool apply_rct)
{
memoryBarrierImage();
barrier();
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;
pix.r = int(imageLoad(dec[2], lpos)[0]);
pix.g = int(imageLoad(dec[0], lpos)[0]);
pix.b = int(imageLoad(dec[1], lpos)[0]);
if (transparency)
pix.a = int(imageLoad(dec[3], lpos)[0]);
if (apply_rct)
#ifdef FLOAT
pix = transform_sample(pix, sc.slice_rct_coef, sc.remap_count[0]);
#else
pix = transform_sample(pix, sc.slice_rct_coef, rct_offset);
#endif
#ifdef FLOAT
pix = pix.gbra;
vec4 pd;
for (int i = 0; i < color_planes; i++) {
uint mask = (1u << ceil_log2(sc.remap_count[i])) - 1u;
uint v = fltmap[slice_idx][i][uint(pix[i]) & mask];
if (c_bits >= 32)
pd[i] = uintBitsToFloat(v);
else
pd[i] = float(uint16BitsToFloat16(uint16_t(v)));
}
pd = pd.brga;
pd = vec4(pd[fmt_lut[0]], pd[fmt_lut[1]],
pd[fmt_lut[2]], pd[fmt_lut[3]]);
#define CAST(x) vec4(x)
#else
#define CAST(x) ivec4(x)
ivec4 pd = ivec4(pix[fmt_lut[0]], pix[fmt_lut[1]],
pix[fmt_lut[2]], pix[fmt_lut[3]]);
#endif
imageStore(dst[0], pos, pd);
if (planar_rgb) {
for (int i = 1; i < color_planes; i++)
imageStore(dst[i], pos, CAST(pd[i]));
}
}
}
#endif
void decode_slice(in SliceContext sc, uint slice_idx)
{
int w = sc.slice_dim.x;
ivec2 sp = sc.slice_pos;
u16vec4 bits = get_slice_bits(sc);
#ifdef BAYER
/* Bayer logical dims: 2x2 blocks at half resolution */
w >>= 1;
int bayer_h = sc.slice_dim.y >> 1;
sp.x >>= 1;
sp.y = int(slice_idx / gl_NumWorkGroups.x)*rgb_linecache;
/* c_bits = bps + 1 (the +1 is for is_rgb). For PCM mode, all planes use
* raw bps. For non-PCM, gm uses bps (bps+1 before 4.8, which coded an
* extra bit); gd/b-gm/r-gm use bps+1. */
if (sc.slice_coding_mode == 0) {
bits = u16vec4(c_bits - 1, c_bits, c_bits, c_bits);
if (version == 4 && micro_version < 8)
bits[0] = uint16_t(c_bits);
} else
bits = u16vec4(c_bits - 1, c_bits - 1, c_bits - 1, c_bits - 1);
#elif defined(RGB)
sp.y = int(slice_idx / gl_NumWorkGroups.x)*rgb_linecache;
#endif
#ifndef GOLOMB
/* PCM coding */
if (sc.slice_coding_mode == 1) {
#ifdef BAYER
for (int y = 0; y < bayer_h; y++) {
for (int p = 0; p < 4; p++)
decode_line_pcm(sp, w, y, p);
writeout_bayer(slice_idx, sc, sp, w, y);
}
#elif defined(RGB)
for (int y = 0; y < sc.slice_dim.y; y++) {
for (int p = 0; p < color_planes; p++)
decode_line_pcm(sp, w, y, p);
writeout_rgb(slice_idx, sc, sp, w, y, false);
}
#else
for (int p = 0; p < planes; p++) {
int h = sc.slice_dim.y;
if (p > 0 && p < 3)
h = ceil_rshift(h, chroma_shift.y);
for (int y = 0; y < h; y++)
decode_line_pcm(sp, w, y, p);
}
#endif
return;
}
#endif
#ifdef BAYER
u8vec4 quant_table_idx = sc.quant_table_idx.xzyy;
u32vec4 slice_state_off = (slice_idx*codec_planes +
uvec4(0, 2, 1, 1))*plane_state_size;
#else
u8vec4 quant_table_idx = sc.quant_table_idx.xyyz;
u32vec4 slice_state_off = (slice_idx*codec_planes +
uvec4(0, 1, 1, 2))*plane_state_size;
#endif
#ifdef GOLOMB
slice_state_off >>= 3; // division by VLC_STATE_SIZE
golomb_init();
#else
for (uint i = gl_LocalInvocationID.x; i < 2*3*MAX_QUANT_TABLE_SIZE; i += gl_WorkGroupSize.x) {
uint t = i / (3*MAX_QUANT_TABLE_SIZE);
uint k = (i / MAX_QUANT_TABLE_SIZE) % 3;
uint e = i % MAX_QUANT_TABLE_SIZE;
quant_top[t][k][e] = int16_t(quant_table[sc.quant_table_idx[t]][k == 2 ? 4 : k + 1][e]);
}
barrier();
#endif
#ifdef BAYER
int run_index = 0;
for (int y = 0; y < bayer_h; y++) {
for (int p = 0; p < 4; p++)
decode_plane_line(sp, w, y, p, bits[p], slice_state_off[p],
quant_table_idx[p], run_index);
writeout_bayer(slice_idx, sc, sp, w, y);
}
#elif defined(RGB)
int run_index = 0;
for (int y = 0; y < sc.slice_dim.y; y++) {
for (int p = 0; p < color_planes; p++)
decode_plane_line(sp, w, y, p, bits[p], slice_state_off[p],
quant_table_idx[p], run_index);
writeout_rgb(slice_idx, sc, sp, w, y, true);
}
#else
for (int p = 0; p < planes; p++) {
int h = sc.slice_dim.y;
if (p > 0 && p < 3)
h = ceil_rshift(h, chroma_shift.y);
int run_index = 0;
for (int y = 0; y < h; y++)
decode_plane_line(sp, w, y, p, bits[p], slice_state_off[p],
quant_table_idx[p], run_index);
}
#endif
}
void main(void)
{
uint slice_idx = slice_order[gl_WorkGroupID.y*gl_NumWorkGroups.x + gl_WorkGroupID.x];
#ifdef GOLOMB
rc = slice_ctx[slice_idx].c;
#else
rac_init_dec(slice_ctx[slice_idx].c);
#endif
decode_slice(slice_ctx[slice_idx], slice_idx);
if (gl_LocalInvocationID.x == 0) {
#ifndef GOLOMB
rc.bs_off += rc_pos;
#endif
uint overread = 0;
if (rc.bs_off >= (rc.bs_end + MAX_OVERREAD))
overread = rc.bs_off - rc.bs_end;
slice_status[2*slice_idx + 1] = overread;
}
}