PR #25500 opened by Forgejo_Fairy URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25500 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25500.patch
APV Vulkan decoding currently produces different samples from the software and OpenAPV decoders because its generic floating-point IDCT does not implement APV’s integer reconstruction. For `apv/profile_400-10.apv`, the unpatched output differs in 43,302 samples on an RTX 5090. Implement the APV integer transform, dequantization rounding and clipping, and both prescribed transform rounding shifts. Keep the dequantization product in 64 bits until clipping and remove the obsolete floating-point specialization constants. The shared generic DCT remains unchanged. Validation on NVIDIA GeForce RTX 5090, driver 595.99.02: - Both existing APV FATE tests pass with `make fate-apv SAMPLES=/opt/fate-suite HWACCEL=vulkan` and with software decoding. - Both FATE samples match OpenAPV byte for byte. The reported monochrome sample now produces SHA-256 `6b273995b9fde15cc6cf1219ba6f680d71f5fb3d35bcc22fbdcdcf3d17eca0d3`. - 28 generated streams covering all seven standard profiles at QPs 0, 19, 37 and 63 match OpenAPV, with Vulkan frames explicitly required to exclude software fallback. - The validation script detects incorrect output from the initial Vulkan implementation as a negative control. OpenAPV reference revision: b4ff69856b4c. Reusable validation tooling and detailed results are preserved separately on `fairy/issue25431-tooling`, under `tools/issue25431/`. AMD/Intel execution and performance benchmarking have not been performed locally. Release/9.0 is also affected; this PR targets master. Fixes #25431. From 9d7c6507c0cbcb45dbb4d4f50d8a344bf39744bc Mon Sep 17 00:00:00 2001 From: Michael Niedermayer <[email protected]> Date: Mon, 26 Oct 2026 23:25:45 +0000 Subject: [PATCH] avcodec/vulkan_apv: use the specified integer inverse transform The generic floating-point IDCT does not implement APV's integer transform and produces different samples from the software and reference decoders. It also omits dequantization rounding and clipping, and the rounding between the vertical and horizontal transform passes. Use the APV integer matrix with both prescribed rounding shifts, round and clip dequantized coefficients, and remove the floating-point IDCT specialization constants. Keep the dequantization product in 64 bits until clipping. Both existing APV FATE tests pass with HWACCEL=vulkan on an RTX 5090. The two FATE samples and 28 generated streams covering all seven standard profiles match OpenAPV byte for byte. Fixes: #25431 Assisted-by: Fairy --- libavcodec/vulkan/apv_idct.comp.glsl | 61 ++++++++++++++++++++-------- libavcodec/vulkan_apv.c | 14 +------ 2 files changed, 45 insertions(+), 30 deletions(-) diff --git a/libavcodec/vulkan/apv_idct.comp.glsl b/libavcodec/vulkan/apv_idct.comp.glsl index c66df279afa..422877d7a08 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,39 @@ layout (push_constant, scalar) uniform pushConstants { const int apv_level_scale[6] = { 40, 45, 51, 57, 64, 71 }; +/* RFC 9924 section 6.3.2: integer transform with rounding between passes. */ +const int trans_matrix[8][8] = { + { 64, 64, 64, 64, 64, 64, 64, 64 }, + { 89, 75, 50, 18, -18, -50, -75, -89 }, + { 84, 35, -35, -84, -84, -35, 35, 84 }, + { 75, -18, -89, -50, 50, 89, 18, -75 }, + { 64, -64, -64, 64, 64, -64, -64, 64 }, + { 50, -89, 18, 75, -75, -18, 89, -50 }, + { 35, -84, 84, -35, -35, 84, -84, 35 }, + { 18, -50, 75, -89, 89, -75, 50, -18 }, +}; + +/* Pad each row to avoid bank conflicts. */ +shared int blocks[APV_BLOCKS_PER_WG][8 * 9]; + +void apv_idct8(uint block, uint offset, uint stride, int shift) +{ + int result[8]; + + [[unroll]] + for (int i = 0; i < 8; i++) { + int sum = 0; + [[unroll]] + for (int j = 0; j < 8; j++) + sum += trans_matrix[j][i] * blocks[block][j * stride + offset]; + result[i] = (sum + (1 << (shift - 1))) >> shift; + } + + [[unroll]] + for (int i = 0; i < 8; i++) + blocks[block][i * stride + offset] = result[i]; +} + void main(void) { const uvec3 wgid = gl_WorkGroupID; @@ -89,8 +121,7 @@ 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; /* This component's plane inside the flat coefficient buffer */ const int cw0 = int(tile_col[tile_count.x]); @@ -106,35 +137,31 @@ 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]; + /* Round and clip the scaled coefficient before transforming it. */ + int qs = level_scale * int(q_matrix[comp][col][y]); + /* Keep the product wide until the scaled coefficient is clipped. */ + int64_t v = (int64_t(coeff) * qs * (1 << qp_shift) + + (1 << (dequant_shift - 1))) >> dequant_shift; + blocks[block][y * 9u + col] = int(clamp(v, int64_t(-32768), int64_t(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 e9c469e6ad4..19fa3eb776f 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); -- 2.54.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
