This is an automated email from the git hooks/post-receive script. Git pushed a commit to branch master in repository ffmpeg.
commit d04c5385ce721358da8a72139914ae8366686a0a Author: Lynne <[email protected]> AuthorDate: Sat Sep 26 15:08:11 2026 +0900 Commit: Lynne <[email protected]> CommitDate: Sat Oct 3 12:15:46 2026 +0900 vulkan_ffv1: evaluate quant table inputs 0 and 3 with a subgroup ballot The two context inputs that depend on the sample to the left are the only ones on the dependency chain between decoding a sample and loading the states of the next one. When a quant table is a staircase over the 8-bit difference, with at most 32 steps of the same size, its value is the number of thresholds the difference passes: one threshold per invocation, a comparison, a ballot and a popcount, with no memory access. The host checks the tables and stores the thresholds next to them in the constants buffer, and the decoder requests a subgroup of exactly CONTEXT_SIZE invocations when it uses them. get_pred is split into the part that depends on the row above and the part that depends on the sample to the left. Decoding a 6464x4852 16-bit RGB frame with 1024 slices on an RX 6900 XT, with the bitstream in VRAM, goes from 171.6/143.7/139.6 ms to 168.9/144.1/140.2 ms with context model 1/0/2: only context model 1 has an extended lookup, and the current decoder is bound by shared memory and barriers. The rewrite of the range coded decoder relies on it. --- libavcodec/ffv1_vulkan.c | 41 ++++++++++++++++++- libavcodec/ffv1_vulkan.h | 15 +++++++ libavcodec/ffv1enc_vulkan.c | 3 +- libavcodec/vulkan/ffv1_common.glsl | 79 ++++++++++++++++++++++-------------- libavcodec/vulkan/ffv1_dec.comp.glsl | 21 +++++++++- libavcodec/vulkan_ffv1.c | 23 ++++++++--- 6 files changed, 143 insertions(+), 39 deletions(-) diff --git a/libavcodec/ffv1_vulkan.c b/libavcodec/ffv1_vulkan.c index 9c34a479d3..9d61f35946 100644 --- a/libavcodec/ffv1_vulkan.c +++ b/libavcodec/ffv1_vulkan.c @@ -82,6 +82,42 @@ static void set_rc_state_tab(FFV1Context *f, uint8_t *buf) } } +int ff_ffv1_vk_quant_ballot(const FFV1Context *f, FFv1QuantBallot *qb) +{ + int ok = 1; + + for (int i = 0; i < MAX_QUANT_TABLES; i++) + for (int k = 0; k < 32; k++) + qb->thresh[i][k][0] = qb->thresh[i][k][1] = 128; + memset(qb->scale_off, 0, sizeof(qb->scale_off)); + + for (int i = 0; i < f->quant_table_count; i++) { + for (int j = 0; j < 2; j++) { + const int16_t *qt = f->quant_tables[i][3*j]; + int n = 0, scale = 0; + + for (int d = -127; d < 128; d++) { + int step = qt[d & 255] - qt[(d - 1) & 255]; + if (!step) + continue; + if (!scale) + scale = step; + if (step != scale || n == 32 || (!j && step != 1)) { + ok = 0; + break; + } + qb->thresh[i][n++][j] = d; + } + + if (j) + qb->scale_off[i][0] = scale; + qb->scale_off[i][1] += qt[128]; + } + } + + return ok; +} + int ff_ffv1_vk_init_consts(FFVulkanContext *s, FFVkBuffer *vkb, FFV1Context *f) { int err; @@ -92,7 +128,8 @@ int ff_ffv1_vk_init_consts(FFVulkanContext *s, FFVkBuffer *vkb, FFV1Context *f) 512*sizeof(uint8_t) + /* Rangecoder */ MAX_QUANT_TABLES* MAX_CONTEXT_INPUTS* - MAX_QUANT_TABLE_SIZE*sizeof(int32_t); + MAX_QUANT_TABLE_SIZE*sizeof(int32_t) + + sizeof(FFv1QuantBallot); RET(ff_vk_create_buf(s, vkb, buf_len, @@ -113,6 +150,8 @@ int ff_ffv1_vk_init_consts(FFVulkanContext *s, FFVkBuffer *vkb, FFV1Context *f) for (int k = 0; k < MAX_QUANT_TABLE_SIZE; k++) quant_tables[i][j][k] = f->quant_tables[i][j][k]; + ff_ffv1_vk_quant_ballot(f, (FFv1QuantBallot *)(quant_tables + MAX_QUANT_TABLES)); + RET(ff_vk_unmap_buffer(s, vkb, 1)); fail: diff --git a/libavcodec/ffv1_vulkan.h b/libavcodec/ffv1_vulkan.h index fd69acf0a1..f4b6f167be 100644 --- a/libavcodec/ffv1_vulkan.h +++ b/libavcodec/ffv1_vulkan.h @@ -30,6 +30,21 @@ void ff_ffv1_vk_set_common_sl(AVCodecContext *avctx, FFV1Context *f, int ff_ffv1_vk_init_consts(FFVulkanContext *s, FFVkBuffer *vkb, FFV1Context *f); +/* Context inputs 0 and 3 of the quant tables as a function of the signed + * 8-bit difference d, with one threshold per subgroup invocation: + * q(d) = q(-128) + scale*#{k : thresh[k] <= d}, where scale is 1 for input 0. + * scale_off holds the scale of input 3 and the sum of both q(-128). */ +typedef struct FFv1QuantBallot { + int32_t thresh[MAX_QUANT_TABLES][32][2]; + int32_t scale_off[MAX_QUANT_TABLES][2]; +} FFv1QuantBallot; + +/** + * Fill in the ballot quantizers of all quant tables. + * Returns 1 if all of them can be evaluated with a ballot. + */ +int ff_ffv1_vk_quant_ballot(const FFV1Context *f, FFv1QuantBallot *qb); + typedef struct FFv1ShaderParams { VkDeviceAddress slice_data; diff --git a/libavcodec/ffv1enc_vulkan.c b/libavcodec/ffv1enc_vulkan.c index 5de2c15087..1f0d839ded 100644 --- a/libavcodec/ffv1enc_vulkan.c +++ b/libavcodec/ffv1enc_vulkan.c @@ -1390,7 +1390,8 @@ static av_cold int vulkan_encode_ffv1_init(AVCodecContext *avctx) &fv->consts_buf, 256*sizeof(uint32_t) + 512*sizeof(uint8_t), MAX_QUANT_TABLES*MAX_CONTEXT_INPUTS* - MAX_QUANT_TABLE_SIZE*sizeof(int32_t), + MAX_QUANT_TABLE_SIZE*sizeof(int32_t) + + sizeof(FFv1QuantBallot), VK_FORMAT_UNDEFINED)); RET(ff_vk_shader_update_desc_buffer(&fv->s, &fv->exec_pool.contexts[0], &fv->enc, 0, 2, 0, diff --git a/libavcodec/vulkan/ffv1_common.glsl b/libavcodec/vulkan/ffv1_common.glsl index a3177b0d75..22fef6c969 100644 --- a/libavcodec/vulkan/ffv1_common.glsl +++ b/libavcodec/vulkan/ffv1_common.glsl @@ -183,6 +183,8 @@ 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 } */ @@ -206,8 +208,8 @@ shared VTYPE2 linecache; #define RGB_LBUF (rgb_linecache - 1) #define LADDR(p) (ivec2((p).x, ((p).y & RGB_LBUF))) -ivec2 get_pred(readonly uimage2D pred, ivec2 sp, ivec2 off, - uint comp, int sw, uint8_t quant_table_idx, bool extend_lookup) +ivec4 get_top(readonly 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; @@ -216,38 +218,25 @@ ivec2 get_pred(readonly uimage2D pred, ivec2 sp, ivec2 off, 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])); - /* 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 = quant_table[quant_table_idx][0][(cur - top[0]) & MAX_QUANT_TABLE_MASK] + - 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]; - + TYPE top2 = TYPE(0); if (has_extend_lookup && extend_lookup) { - TYPE cur2 = linecache[0]; - base += quant_table[quant_table_idx][3][(cur2 - cur) & MAX_QUANT_TABLE_MASK]; - /* top-2 became current upon swap when rgb_linecache == 2 */ ivec2 top2_off = off; if (rgb_linecache != 2) top2_off += ivec2(0, -2); - TYPE top2 = TYPE(imageLoad(pred, sp + LADDR(top2_off))[comp]); - base += quant_table[quant_table_idx][4][(top2 - top[1]) & MAX_QUANT_TABLE_MASK]; + top2 = TYPE(imageLoad(pred, sp + LADDR(top2_off))[comp]); } - /* context, prediction */ - return ivec2(base, predict(cur, VTYPE2(top))); + return ivec4(top, top2); } #else #define LADDR(p) (p) -ivec2 get_pred(readonly uimage2D pred, ivec2 sp, ivec2 off, - uint comp, int sw, uint8_t quant_table_idx, bool extend_lookup) +ivec4 get_top(readonly 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; @@ -262,27 +251,57 @@ ivec2 get_pred(readonly uimage2D pred, ivec2 sp, ivec2 off, top[2] = TYPE(imageLoad(pred, sp + ivec2(min(1, sw - off.x - 1), -1))[comp]); } - TYPE cur = linecache[1]; + 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 */ - int base = quant_table[quant_table_idx][0][(cur - top[0]) & MAX_QUANT_TABLE_MASK] + - quant_table[quant_table_idx][1][(top[0] - top[1]) & MAX_QUANT_TABLE_MASK] + +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(readonly 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]; - - TYPE top2 = TYPE(0); - if (off.y > 1) - top2 = TYPE(imageLoad(pred, sp + ivec2(0, -2))[comp]); - base += quant_table[quant_table_idx][4][(top2 - top[1]) & MAX_QUANT_TABLE_MASK]; } /* context, prediction */ - return ivec2(base, predict(cur, VTYPE2(top))); + return ivec2(base, predict(cur, top.xy)); } -#endif /* RGB */ +ivec2 get_pred(readonly 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(readonly uimage2D src, ivec2 sp, int y, uint comp) { diff --git a/libavcodec/vulkan/ffv1_dec.comp.glsl b/libavcodec/vulkan/ffv1_dec.comp.glsl index 8e52ac2aeb..270d513b48 100644 --- a/libavcodec/vulkan/ffv1_dec.comp.glsl +++ b/libavcodec/vulkan/ffv1_dec.comp.glsl @@ -22,6 +22,7 @@ #pragma shader_stage(compute) #extension GL_GOOGLE_include_directive : require +#extension GL_KHR_shader_subgroup_ballot : require #define DECODE #include "common.glsl" @@ -47,6 +48,8 @@ layout (set = 1, binding = 3, scalar) buffer slice_state_buf { uint8_t slice_rc_state[]; }; +layout (constant_id = 20) const bool quant_ballot = false; + #define READ(idx) get_rac_state(idx) shared int sym_e; shared bool rc_dec[CONTEXT_SIZE]; @@ -130,9 +133,23 @@ void decode_line(ivec2 sp, int w, linecache_load(dec[p], sp, y, 0); + bool ext = extend_lookup[quant_table_idx]; + 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); + for (int x = 0; x < w; x++) { - ivec2 pr = get_pred(dec[p], sp, ivec2(x, y), 0, w, - quant_table_idx, extend_lookup[quant_table_idx]); + 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); + } uint rc_off = state_off + CONTEXT_SIZE*abs(pr[0]) + gl_LocalInvocationID.x; diff --git a/libavcodec/vulkan_ffv1.c b/libavcodec/vulkan_ffv1.c index 161f21e7cb..fd272e5293 100644 --- a/libavcodec/vulkan_ffv1.c +++ b/libavcodec/vulkan_ffv1.c @@ -687,13 +687,13 @@ static int init_decode_shader(FFV1Context *f, FFVulkanContext *s, AVHWFramesContext *dec_frames_ctx, AVHWFramesContext *out_frames_ctx, VkSpecializationInfo *sl, int ac, int rgb, - int bayer) + int bayer, int ballot) { int err; uint32_t wg_x = 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 }, ballot ? CONTEXT_SIZE : 0); ff_vk_shader_add_push_const(shd, 0, sizeof(FFv1ShaderParams), VK_SHADER_STAGE_COMPUTE_BIT); @@ -856,6 +856,8 @@ static void vk_decode_ffv1_uninit(FFVulkanDecodeShared *ctx) static int vk_decode_ffv1_init(AVCodecContext *avctx) { int err; + int ballot = 0; + FFv1QuantBallot qb; FFV1Context *f = avctx->priv_data; FFVulkanDecodeContext *dec = avctx->internal->hwaccel_priv_data; FFVulkanDecodeShared *ctx = NULL; @@ -897,7 +899,7 @@ static int vk_decode_ffv1_init(AVCodecContext *avctx) dctx = (AVHWFramesContext *)fv->intermediate_frames_ref->data; } - SPEC_LIST_CREATE(sl, 15, 15*sizeof(uint32_t)) + SPEC_LIST_CREATE(sl, 16, 16*sizeof(uint32_t)) ff_ffv1_vk_set_common_sl(avctx, f, sl, sw_format); if (RGB_LINECACHE != 2) @@ -906,6 +908,16 @@ static int vk_decode_ffv1_init(AVCodecContext *avctx) if (f->ec && !!(avctx->err_recognition & AV_EF_CRCCHECK)) SPEC_LIST_ADD(sl, 1, 32, 1); + /* The ballot quantizer holds one threshold per invocation, so the + * workgroup must be a single subgroup of CONTEXT_SIZE invocations */ + if (f->ac != AC_GOLOMB_RICE && + ctx->s.subgroup_props.minSubgroupSize <= CONTEXT_SIZE && + ctx->s.subgroup_props.maxSubgroupSize >= CONTEXT_SIZE && + (ctx->s.subgroup_props.requiredSubgroupSizeStages & VK_SHADER_STAGE_COMPUTE_BIT)) + ballot = ff_ffv1_vk_quant_ballot(f, &qb); + if (ballot) + SPEC_LIST_ADD(sl, 20, 32, 1); + /* Setup shader */ RET(init_setup_shader(f, &ctx->s, &ctx->exec_pool, &fv->setup, sl)); @@ -914,7 +926,7 @@ static int vk_decode_ffv1_init(AVCodecContext *avctx) /* Decode shaders */ RET(init_decode_shader(f, &ctx->s, &ctx->exec_pool, &fv->decode, - dctx, hwfc, sl, f->ac, is_rgb, f->bayer)); + dctx, hwfc, sl, f->ac, is_rgb, f->bayer, ballot)); /* Init static data */ RET(ff_ffv1_vk_init_consts(&ctx->s, &fv->consts_buf, f)); @@ -942,7 +954,8 @@ static int vk_decode_ffv1_init(AVCodecContext *avctx) &fv->consts_buf, 256*sizeof(uint32_t) + 512*sizeof(uint8_t), MAX_QUANT_TABLES*MAX_CONTEXT_INPUTS* - MAX_QUANT_TABLE_SIZE*sizeof(int32_t), + MAX_QUANT_TABLE_SIZE*sizeof(int32_t) + + sizeof(FFv1QuantBallot), VK_FORMAT_UNDEFINED)); fail: -- To stop receiving notification emails like this one, please contact [email protected]. _______________________________________________ ffmpeg-cvslog mailing list -- [email protected] To unsubscribe send an email to [email protected]
