Commit 2997641b63 for ffmpeg
commit 2997641b638e3bed0b5a008ea5ee2d171d7f082b
Author: Lynne <dev@lynne.ee>
Date: Sat Nov 7 15:13:30 2026 +0800
avcodec/vulkan_apv: use the APV integer inverse transform
The iDCT shader used the generic floating-point DCT, which cannot
reproduce the integer transform APV specifies, nor its rounding and
clipping. Its output differed from the software decoder and OpenAPV, by
one in the 4:0:0 FATE sample and by up to 26 at 12 bits.
Implement the specified dequantization, transform and rounding in 32-bit
integers. The transform is split into its even and odd halves like the
HEVC one, which takes 20 multiplies per 8-point pass rather than 64.
Scaled coefficients beyond the 16-bit range are clamped before the shift,
so the dequantization needs no 64-bit arithmetic either.
On an RX 6900 XT, the transform takes 15us for a 1080p 4:2:2 frame,
against 12us for the floating-point one.
Fixes: #25431
diff --git a/libavcodec/vulkan/apv_idct.comp.glsl b/libavcodec/vulkan/apv_idct.comp.glsl
index c66df279af..99e32c5c62 100644
--- a/libavcodec/vulkan/apv_idct.comp.glsl
+++ b/libavcodec/vulkan/apv_idct.comp.glsl
@@ -23,7 +23,6 @@
#extension GL_GOOGLE_include_directive : require
#include "common.glsl"
-#include "dct.glsl"
#define APV_MAX_NUM_COMP 4
#define APV_MAX_TILE_COLS 20
@@ -54,6 +53,45 @@ layout (push_constant, scalar) uniform pushConstants {
const int apv_level_scale[6] = { 40, 45, 51, 57, 64, 71 };
+/* Rows are padded to avoid bank conflicts */
+shared int blocks[APV_BLOCKS_PER_WG][8*(8 + 1)];
+
+/* Inverse of the APV integer transform, split into its even and odd halves
+ * like the HEVC one, then rounded and shifted down. */
+void apv_idct8(uint block, uint offset, uint stride, int shift)
+{
+ int s0 = blocks[block][0*stride + offset];
+ int s1 = blocks[block][1*stride + offset];
+ int s2 = blocks[block][2*stride + offset];
+ int s3 = blocks[block][3*stride + offset];
+ int s4 = blocks[block][4*stride + offset];
+ int s5 = blocks[block][5*stride + offset];
+ int s6 = blocks[block][6*stride + offset];
+ int s7 = blocks[block][7*stride + offset];
+
+ /* Even half, with the rounding offset */
+ int ee0 = 64*(s0 + s4) + (1 << (shift - 1));
+ int ee1 = 64*(s0 - s4) + (1 << (shift - 1));
+ int eo0 = 84*s2 + 35*s6;
+ int eo1 = 35*s2 - 84*s6;
+ int e0 = ee0 + eo0, e1 = ee1 + eo1, e2 = ee1 - eo1, e3 = ee0 - eo0;
+
+ /* Odd half */
+ int o0 = 89*s1 + 75*s3 + 50*s5 + 18*s7;
+ int o1 = 75*s1 - 18*s3 - 89*s5 - 50*s7;
+ int o2 = 50*s1 - 89*s3 + 18*s5 + 75*s7;
+ int o3 = 18*s1 - 50*s3 + 75*s5 - 89*s7;
+
+ blocks[block][0*stride + offset] = (e0 + o0) >> shift;
+ blocks[block][1*stride + offset] = (e1 + o1) >> shift;
+ blocks[block][2*stride + offset] = (e2 + o2) >> shift;
+ blocks[block][3*stride + offset] = (e3 + o3) >> shift;
+ blocks[block][4*stride + offset] = (e3 - o3) >> shift;
+ blocks[block][5*stride + offset] = (e2 - o2) >> shift;
+ blocks[block][6*stride + offset] = (e1 - o1) >> shift;
+ blocks[block][7*stride + offset] = (e0 - o0) >> shift;
+}
+
void main(void)
{
const uvec3 wgid = gl_WorkGroupID;
@@ -89,8 +127,10 @@ void main(void)
const int half_range = 1 << (bit_depth - 1);
const int max_val = (1 << bit_depth) - 1;
- const float fact = float(half_range);
- const float norm = 1.0f / (1024.0f * fact); /* DCT normalization const */
+ const int dequant_shift = bit_depth - 2;
+ /* Scaled coefficients beyond this saturate either way, clamping to it
+ * first keeps the dequantization within 32 bits */
+ const int dequant_max = 1 << (16 + dequant_shift - qp_shift);
/* This component's plane inside the flat coefficient buffer */
const int cw0 = int(tile_col[tile_count.x]);
@@ -106,35 +146,30 @@ void main(void)
/* blocks fully outside the coded area have nothing stored for them */
const bool oob = pos.x >= cstride || pos.y >= cheight;
- /* Loop-invariant column scale */
- const float col_scale = norm * idct_scale[col];
-
[[unroll]]
for (uint y = 0u; y < 8u; y++) {
/* load */
int coeff = oob ? 0
: int(coeffs_in[cbase + uint((pos.y + int(y)) * cstride +
pos.x + int(col))]);
- /* dequant + norm */
- int qs = level_scale * int(q_matrix[comp][col][y]) * (1 << qp_shift);
- float v = float(coeff * qs) * col_scale;
- /* scale */
- blocks[block][y * 9u + col] = v * idct_scale[y];
+ /* dequant */
+ int qs = level_scale * int(q_matrix[comp][col][y]);
+ int v = clamp(coeff * qs, -dequant_max, dequant_max) << qp_shift;
+ v = (v + (1 << (dequant_shift - 1))) >> dequant_shift;
+ blocks[block][y * 9u + col] = clamp(v, -32768, 32767);
}
barrier();
- idct8(block, col, 9);
+ apv_idct8(block, col, 9, 7);
barrier();
- blocks[block][col * 9u] += 1.0f;
-
- idct8(block, col * 9u, 1);
+ apv_idct8(block, col * 9u, 1, 20 - bit_depth);
barrier();
[[unroll]]
for (int y = 0; y < 8; y++) {
- float v = round(blocks[block][y * 9u + col] * fact);
+ int v = blocks[block][y * 9u + col] + half_range;
imageStore(dst[comp], pos + ivec2(col, y),
- uvec4(uint(clamp(int(v), 0, max_val))));
+ uvec4(uint(clamp(v, 0, max_val))));
}
}
diff --git a/libavcodec/vulkan_apv.c b/libavcodec/vulkan_apv.c
index e9c469e6ad..19fa3eb776 100644
--- a/libavcodec/vulkan_apv.c
+++ b/libavcodec/vulkan_apv.c
@@ -406,19 +406,7 @@ static int init_idct_shader(AVCodecContext *avctx, FFVulkanContext *s,
AVHWFramesContext *dec_frames_ctx;
dec_frames_ctx = (AVHWFramesContext *)avctx->hw_frames_ctx->data;
- SPEC_LIST_CREATE(sl, 1 + 8, (1 + 8)*sizeof(uint32_t))
- SPEC_LIST_ADD(sl, 16, 32, 8); /* nb_blocks per workgroup */
-
- const double idct_8_scales[8] = {
- cos(4.0*M_PI/16.0) / 2.0, cos(1.0*M_PI/16.0) / 2.0,
- cos(2.0*M_PI/16.0) / 2.0, cos(3.0*M_PI/16.0) / 2.0,
- cos(4.0*M_PI/16.0) / 2.0, cos(5.0*M_PI/16.0) / 2.0,
- cos(6.0*M_PI/16.0) / 2.0, cos(7.0*M_PI/16.0) / 2.0,
- };
- for (int i = 0; i < 8; i++)
- SPEC_LIST_ADD(sl, 18 + i, 32, av_float2int(idct_8_scales[i]));
-
- ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
+ ff_vk_shader_load(shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
(uint32_t []) { 32, 2, 1 }, 0);
ff_vk_shader_add_push_const(shd, 0, sizeof(DecodePushData),
VK_SHADER_STAGE_COMPUTE_BIT);