PR #23707 opened by charlymp
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23707
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/23707.patch

Two small optimizations of the HEVC residual decoding path, independent of each 
other.

The first patch precomputes the composition of the significance context map 
with the intra coefficient-group scan.
The hottest decoding loop then indexes a single table by scan position instead 
of going through scan_x_off/scan_y_off and the map for every bin.
The SCAN_HORIZ table is the original one, horizontal scan order being raster 
order, and the two others are permutations of it.

The second patch skips the coefficient clear for DC-only blocks, where idct_dc 
reads coeffs[0] only and writes the whole block, and moves the clear after the 
last significant coefficient position is known.

About 2.5% faster single threaded decoding on 4K high bitrate inter streams, 
almost all of it from the first patch.


>From d11af35dce8703849cf9c329e8e7d0d3ca00a9af Mon Sep 17 00:00:00 2001
From: Charly Morgand-Poyac <[email protected]>
Date: Sun, 5 Jul 2026 14:09:47 +0200
Subject: [PATCH 1/2] lavc/hevcdec: precompute the significance context tables
 per scan

The significance flag loop looked up scan_x_off/scan_y_off and then
ctx_idx_map for every decoded bin. The composition of the two is
constant per scan type, so precompute the three permuted tables and
index them by scan position directly. The SCAN_HORIZ table is the
original one, horizontal scan order being raster order.

About 2.5% faster single threaded decoding on high bitrate streams.

Signed-off-by: Charly Morgand-Poyac <[email protected]>
---
 libavcodec/hevc/cabac.c | 43 +++++++++++++++++++++++++++--------------
 1 file changed, 29 insertions(+), 14 deletions(-)

diff --git a/libavcodec/hevc/cabac.c b/libavcodec/hevc/cabac.c
index f6bde3028f..bb632ef990 100644
--- a/libavcodec/hevc/cabac.c
+++ b/libavcodec/hevc/cabac.c
@@ -909,10 +909,10 @@ static av_always_inline int 
significant_coeff_group_flag_decode(HEVCLocalContext
 
     return GET_CABAC(SIGNIFICANT_COEFF_GROUP_FLAG_OFFSET + inc);
 }
-static av_always_inline int significant_coeff_flag_decode(HEVCLocalContext 
*lc, int x_c, int y_c,
+static av_always_inline int significant_coeff_flag_decode(HEVCLocalContext 
*lc, int n,
                                            int offset, const uint8_t 
*ctx_idx_map)
 {
-    int inc = ctx_idx_map[(y_c << 2) + x_c] + offset;
+    int inc = ctx_idx_map[n] + offset;
     return GET_CABAC(SIGNIFICANT_COEFF_FLAG_OFFSET + inc);
 }
 
@@ -1223,18 +1223,35 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
             prev_sig += (!!significant_coeff_group_flag[x_cg][y_cg + 1] << 1);
 
         if (significant_coeff_group_flag[x_cg][y_cg] && n_end >= 0) {
-            static const uint8_t ctx_idx_map[] = {
-                0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8, // 
log2_trafo_size == 2
-                1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, // prev_sig == 0
-                2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, // prev_sig == 
1
-                2, 1, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0, // prev_sig == 
2
-                2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2  // default
+            // ctx_idx_map composed with the intra-CG scan, indexed by scan 
position
+            static const uint8_t ctx_idx_map[3][5 * 16] = {
+                { // SCAN_DIAG
+                    0, 2, 1, 6, 3, 4, 7, 6, 4, 5, 7, 8, 5, 8, 8, 8, // 
log2_trafo_size == 2
+                    1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 
prev_sig == 0
+                    2, 1, 2, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 0, 0, 0, // 
prev_sig == 1
+                    2, 2, 1, 2, 1, 0, 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, // 
prev_sig == 2
+                    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // default
+                },
+                { // SCAN_HORIZ
+                    0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8,
+                    1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+                    2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+                    2, 1, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0,
+                    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+                },
+                { // SCAN_VERT
+                    0, 2, 6, 7, 1, 3, 6, 7, 4, 4, 8, 8, 5, 5, 8, 8,
+                    1, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0,
+                    2, 1, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0, 2, 1, 0, 0,
+                    2, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0,
+                    2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+                },
             };
             const uint8_t *ctx_idx_map_p;
             int scf_offset = 0;
             if (sps->transform_skip_context_enabled &&
                 (transform_skip_flag || lc->cu.cu_transquant_bypass_flag)) {
-                ctx_idx_map_p = &ctx_idx_map[4 * 16];
+                ctx_idx_map_p = &ctx_idx_map[scan_idx][4 * 16];
                 if (c_idx == 0) {
                     scf_offset = 40;
                 } else {
@@ -1244,9 +1261,9 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
                 if (c_idx != 0)
                     scf_offset = 27;
                 if (log2_trafo_size == 2) {
-                    ctx_idx_map_p = &ctx_idx_map[0];
+                    ctx_idx_map_p = &ctx_idx_map[scan_idx][0];
                 } else {
-                    ctx_idx_map_p = &ctx_idx_map[(prev_sig + 1) << 4];
+                    ctx_idx_map_p = &ctx_idx_map[scan_idx][(prev_sig + 1) << 
4];
                     if (c_idx == 0) {
                         if ((x_cg > 0 || y_cg > 0))
                             scf_offset += 3;
@@ -1264,9 +1281,7 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
                 }
             }
             for (n = n_end; n > 0; n--) {
-                x_c = scan_x_off[n];
-                y_c = scan_y_off[n];
-                if (significant_coeff_flag_decode(lc, x_c, y_c, scf_offset, 
ctx_idx_map_p)) {
+                if (significant_coeff_flag_decode(lc, n, scf_offset, 
ctx_idx_map_p)) {
                     significant_coeff_flag_idx[nb_significant_coeff_flag] = n;
                     nb_significant_coeff_flag++;
                     implicit_non_zero_coeff = 0;
-- 
2.52.0


>From b5dfb89e569138acac360f970872a47f7aa530e0 Mon Sep 17 00:00:00 2001
From: Charly Morgand-Poyac <[email protected]>
Date: Sun, 5 Jul 2026 14:09:47 +0200
Subject: [PATCH 2/2] lavc/hevcdec: skip the coefficient clear for DC-only
 blocks

idct_dc reads coeffs[0] only and writes the whole block, so the
clear before residual decoding is not needed on that path. Move it
after the last significant coefficient position is known and compute
col_limit once instead of at the transform call.

Signed-off-by: Charly Morgand-Poyac <[email protected]>
---
 libavcodec/hevc/cabac.c | 32 +++++++++++++++++++++-----------
 1 file changed, 21 insertions(+), 11 deletions(-)

diff --git a/libavcodec/hevc/cabac.c b/libavcodec/hevc/cabac.c
index bb632ef990..5d0f950663 100644
--- a/libavcodec/hevc/cabac.c
+++ b/libavcodec/hevc/cabac.c
@@ -999,6 +999,7 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
     int greater1_ctx = 1;
 
     int num_last_subset;
+    int max_xy, col_limit, clear_rows;
     int x_cg_last_sig, y_cg_last_sig;
 
     const uint8_t *scan_x_cg, *scan_y_cg, *scan_x_off, *scan_y_off;
@@ -1022,7 +1023,6 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
     int pred_mode_intra = (c_idx == 0) ? lc->tu.intra_pred_mode :
                                          lc->tu.intra_pred_mode_c;
 
-    memset(coeffs, 0, trafo_size * trafo_size * sizeof(int16_t));
 
     // Derive QP for dequant
     if (!lc->cu.cu_transquant_bypass_flag) {
@@ -1176,6 +1176,25 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
     num_coeff++;
     num_last_subset = (num_coeff - 1) >> 4;
 
+    max_xy = FFMAX(last_significant_coeff_x, last_significant_coeff_y);
+    col_limit = last_significant_coeff_x + last_significant_coeff_y + 4;
+    if (max_xy < 4)
+        col_limit = FFMIN(4, col_limit);
+    else if (max_xy < 8)
+        col_limit = FFMIN(8, col_limit);
+    else if (max_xy < 12)
+        col_limit = FFMIN(24, col_limit);
+
+    // idct_dc reads coeffs[0] only and writes the whole block: no clear needed
+    clear_rows = trafo_size;
+    if (!lc->cu.cu_transquant_bypass_flag && !transform_skip_flag &&
+        !(lc->cu.pred_mode == MODE_INTRA && c_idx == 0 && log2_trafo_size == 
2)) {
+        if (max_xy == 0)
+            clear_rows = 0;
+    }
+    if (clear_rows)
+        memset(coeffs, 0, clear_rows * trafo_size * sizeof(int16_t));
+
     for (i = num_last_subset; i >= 0; i--) {
         int n, m;
         int x_cg, y_cg, x_c, y_c, pos;
@@ -1480,19 +1499,10 @@ void ff_hevc_hls_residual_coding(HEVCLocalContext *lc, 
const HEVCPPS *pps,
         } else if (lc->cu.pred_mode == MODE_INTRA && c_idx == 0 && 
log2_trafo_size == 2) {
             s->hevcdsp.transform_4x4_luma(coeffs);
         } else {
-            int max_xy = FFMAX(last_significant_coeff_x, 
last_significant_coeff_y);
             if (max_xy == 0)
                 s->hevcdsp.idct_dc[log2_trafo_size - 2](coeffs);
-            else {
-                int col_limit = last_significant_coeff_x + 
last_significant_coeff_y + 4;
-                if (max_xy < 4)
-                    col_limit = FFMIN(4, col_limit);
-                else if (max_xy < 8)
-                    col_limit = FFMIN(8, col_limit);
-                else if (max_xy < 12)
-                    col_limit = FFMIN(24, col_limit);
+            else
                 s->hevcdsp.idct[log2_trafo_size - 2](coeffs, col_limit);
-            }
         }
     }
     if (lc->tu.cross_pf) {
-- 
2.52.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to