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]
