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]

Reply via email to