Files
Lynne 5a81cd4f2b 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.
2026-10-03 12:15:47 +09:00

349 lines
9.8 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
*/
#ifndef VULKAN_FFV1_COMMON_H
#define VULKAN_FFV1_COMMON_H
#ifdef GOLOMB
#include "ffv1_vlc.glsl"
#endif
#define MAX_QUANT_TABLES 8
#define MAX_CONTEXT_INPUTS 5
#define MAX_QUANT_TABLE_SIZE 256
#define MAX_QUANT_TABLE_MASK (MAX_QUANT_TABLE_SIZE - 1)
layout (constant_id = 0) const int rgb_linecache = 2;
layout (constant_id = 1) const bool has_crc = false;
layout (constant_id = 2) const int version = 0;
layout (constant_id = 3) const int quant_table_count = 0;
layout (constant_id = 4) const bool has_extend_lookup = false;
layout (constant_id = 5) const int rct_offset = 0;
layout (constant_id = 6) const int colorspace = 0;
layout (constant_id = 7) const bool transparency = false;
layout (constant_id = 8) const bool planar_rgb = false;
layout (constant_id = 9) const int codec_planes = 0;
layout (constant_id = 10) const int color_planes = 0;
layout (constant_id = 11) const int planes = 0;
layout (constant_id = 12) const int c_bits = 0;
layout (constant_id = 13) const int chroma_shift_x = 0;
layout (constant_id = 14) const int chroma_shift_y = 0;
const ivec2 chroma_shift = ivec2(chroma_shift_x, chroma_shift_y);
/* Encoder-only */
layout (constant_id = 15) const bool force_pcm = false;
layout (constant_id = 16) const bool rct_search = false;
layout (constant_id = 17) const uint context_model = 0;
layout (constant_id = 18) const uint remap_mode = 0;
layout (push_constant, scalar) uniform pushConstants {
u8buf slice_data;
bool extend_lookup[MAX_QUANT_TABLES];
uint16_t context_count[MAX_QUANT_TABLES];
ivec4 fmt_lut;
u16vec2 img_size;
uint plane_state_size;
bool key_frame;
uint32_t crcref;
int micro_version;
/* Encoder-only */
ivec2 sar;
int pic_mode;
uint slice_size_max;
uint max_pixels_per_slice;
/* Decoder-only */
bool remap_allowed;
};
#ifdef RC_SUBGROUP
#include "rangecoder_subgroup.glsl"
#else
#include "rangecoder.glsl"
#endif
#if !defined(RGB)
#define TYPE int16_t
#define VTYPE2 i16vec2
#define VTYPE3 i16vec3
#else
#define TYPE int32_t
#define VTYPE2 i32vec2
#define VTYPE3 i32vec3
#endif
struct SliceContext {
RangeCoder c;
ivec2 slice_dim;
ivec2 slice_pos;
ivec2 slice_rct_coef;
u8vec3 quant_table_idx;
uint slice_coding_mode;
bool slice_reset_contexts;
i32vec4 remap_count;
/* Decoder-only */
uint remap;
};
#if !defined(SB_QUALI)
#if (defined(ENCODE) || defined(DECODE))
#define SB_QUALI readonly
#else
#define SB_QUALI
#endif
#endif
layout (set = 1, binding = 0, scalar) SB_QUALI buffer slice_ctx_buf {
SliceContext slice_ctx[];
};
uint slice_coord(uint width, uint sx, uint num_h_slices, uint chroma_shift)
{
uint mpw = 1 << chroma_shift;
if (colorspace == 2)
mpw = max(mpw, 2u);
uint awidth = align(width, mpw);
if ((version < 4) || ((version == 4) && (micro_version < 3)))
return width * sx / num_h_slices;
sx = (2 * awidth * sx + num_h_slices * mpw) / (2 * num_h_slices * mpw) * mpw;
if (sx == awidth)
sx = width;
return sx;
}
#if defined(ENCODE) || defined(DECODE)
#define ceil_log2(x) (findMSB(uint(((uint(x) - 1u) << 1u) | 1u)))
u16vec4 get_slice_bits(in SliceContext sc)
{
#ifndef FLOAT
u16vec4 bits = u16vec4(c_bits, c_bits, c_bits, c_bits);
#ifdef RGB
/* 4.8 stopped coding the extra bit on the primary and alpha planes */
if (sc.slice_coding_mode == 0 &&
(version > 4 || (version == 4 && micro_version >= 8))) {
bits[0] = uint16_t(c_bits - 1);
bits[3] = uint16_t(c_bits - 1);
}
#endif
return bits;
#else
u32vec4 cnt = sc.remap_count;
#if defined(ENCODE)
if (remap_mode == 0)
#elif defined(DECODE)
if (sc.remap == 0)
#endif
cnt = u32vec4(uint(rct_offset), uint(rct_offset),
uint(rct_offset), uint(rct_offset));
u16vec4 bits = u16vec4(cnt);
if (sc.slice_coding_mode == 0) {
uint max3 = max(cnt[0], max(cnt[1], cnt[2]));
bits = u16vec4(ceil_log2(max3),
ceil_log2(cnt[0] + cnt[1]),
ceil_log2(cnt[0] + cnt[2]),
ceil_log2(cnt[3]));
}
return bits;
#endif
}
layout (set = 0, binding = 1, scalar) readonly uniform quant_buf {
int32_t quant_table[MAX_QUANT_TABLES]
[MAX_CONTEXT_INPUTS]
[MAX_QUANT_TABLE_SIZE];
ivec2 quant_thresh[MAX_QUANT_TABLES][32];
ivec2 quant_scale_off[MAX_QUANT_TABLES];
};
/* -1, { -1, 0 } */
int predict(int L, ivec2 top)
{
return mid_pred(L, L + top[1] - top[0], top[1]);
}
const uint32_t log2_run[41] = {
0, 0, 0, 0, 1, 1, 1, 1,
2, 2, 2, 2, 3, 3, 3, 3,
4, 4, 5, 5, 6, 6, 7, 7,
8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23,
24,
};
#ifdef DECODE
#define IMG_QUALI coherent readonly
VTYPE2 linecache;
#else
#define IMG_QUALI readonly
shared VTYPE2 linecache;
#endif
#ifdef RGB
#define RGB_LBUF (rgb_linecache - 1)
#define LADDR(p) (ivec2((p).x, ((p).y & RGB_LBUF)))
ivec4 get_top(IMG_QUALI uimage2D pred, ivec2 sp, ivec2 off,
uint comp, int sw, bool extend_lookup)
{
ivec2 yoff_border1 = expectEXT(off.x == 0, false) ? off + ivec2(1, -1) : off;
/* Thanks to the same coincidence as below, we can skip checking if off == 0, 1 */
VTYPE3 top = VTYPE3(TYPE(imageLoad(pred, sp + LADDR(yoff_border1 + ivec2(-1, -1)))[comp]),
TYPE(imageLoad(pred, sp + LADDR(off + ivec2(0, -1)))[comp]),
TYPE(imageLoad(pred, sp + LADDR(off + ivec2(min(1, sw - off.x - 1), -1)))[comp]));
TYPE top2 = TYPE(0);
if (has_extend_lookup && extend_lookup) {
/* top-2 became current upon swap when rgb_linecache == 2 */
ivec2 top2_off = off;
if (rgb_linecache != 2)
top2_off += ivec2(0, -2);
top2 = TYPE(imageLoad(pred, sp + LADDR(top2_off))[comp]);
}
return ivec4(top, top2);
}
#else
#define LADDR(p) (p)
ivec4 get_top(IMG_QUALI uimage2D pred, ivec2 sp, ivec2 off,
uint comp, int sw, bool extend_lookup)
{
ivec2 yoff_border1 = off.x == 0 ? ivec2(1, -1) : ivec2(0, 0);
sp += off;
VTYPE3 top = VTYPE3(TYPE(0),
TYPE(0),
TYPE(0));
if (off.y > 0 && off != ivec2(0, 1))
top[0] = TYPE(imageLoad(pred, sp + ivec2(-1, -1) + yoff_border1)[comp]);
if (off.y > 0) {
top[1] = TYPE(imageLoad(pred, sp + ivec2(0, -1))[comp]);
top[2] = TYPE(imageLoad(pred, sp + ivec2(min(1, sw - off.x - 1), -1))[comp]);
}
TYPE top2 = TYPE(0);
if (has_extend_lookup && extend_lookup && off.y > 1)
top2 = TYPE(imageLoad(pred, sp + ivec2(0, -2))[comp]);
return ivec4(top, top2);
}
#endif /* RGB */
ivec3 get_pred_top_quant(ivec4 top, uint8_t quant_table_idx, bool extend_lookup)
{
int base = quant_table[quant_table_idx][1][(top[0] - top[1]) & MAX_QUANT_TABLE_MASK] +
quant_table[quant_table_idx][2][(top[1] - top[2]) & MAX_QUANT_TABLE_MASK];
if (has_extend_lookup && extend_lookup)
base += quant_table[quant_table_idx][4][(top[3] - top[1]) & MAX_QUANT_TABLE_MASK];
return ivec3(top[0], top[1], base);
}
ivec3 get_pred_top(IMG_QUALI uimage2D pred, ivec2 sp, ivec2 off,
uint comp, int sw, uint8_t quant_table_idx, bool extend_lookup)
{
return get_pred_top_quant(get_top(pred, sp, off, comp, sw, extend_lookup),
quant_table_idx, extend_lookup);
}
ivec2 get_pred_left(ivec3 top, uint8_t quant_table_idx, bool extend_lookup)
{
/* Normally, we'd need to check if off != ivec2(0, 0) here, since otherwise, we must
* return zero. However, ivec2(-1, 0) + ivec2(1, -1) == ivec2(0, -1), e.g. previous
* row, 0 offset, same slice, which is zero since we zero out the buffer for RGB */
TYPE cur = linecache[1];
int base = top[2] + quant_table[quant_table_idx][0][(cur - top[0]) & MAX_QUANT_TABLE_MASK];
if (has_extend_lookup && extend_lookup) {
TYPE cur2 = linecache[0];
base += quant_table[quant_table_idx][3][(cur2 - cur) & MAX_QUANT_TABLE_MASK];
}
/* context, prediction */
return ivec2(base, predict(cur, top.xy));
}
ivec2 get_pred(IMG_QUALI uimage2D pred, ivec2 sp, ivec2 off,
uint comp, int sw, uint8_t quant_table_idx, bool extend_lookup)
{
return get_pred_left(get_pred_top(pred, sp, off, comp, sw, quant_table_idx, extend_lookup),
quant_table_idx, extend_lookup);
}
void linecache_load(IMG_QUALI uimage2D src, ivec2 sp, int y, uint comp)
{
#ifdef DECODE
linecache = VTYPE2(TYPE(0), TYPE(0));
if (y > 0)
linecache[1] = TYPE(imageLoad(src, sp + LADDR(ivec2(0, y - 1)))[comp]);
#else
if (gl_LocalInvocationID.x == 0)
linecache[0] = TYPE(0);
#ifndef GOLOMB
if (gl_LocalInvocationID.x == 1)
#endif
{
TYPE c = TYPE(0);
if (y > 0)
c = TYPE(imageLoad(src, sp + LADDR(ivec2(0, y - 1)))[comp]);
linecache[1] = c;
}
barrier();
#endif
}
void linecache_next(TYPE cur)
{
linecache[0] = linecache[1];
linecache[1] = cur;
}
#endif /* ENCODE || DECODE */
#endif /* VULKAN_FFV1_COMMON_H */