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

Pictures split into several independent slices, without tiles or WPP, are 
decoded one slice at a time even under slice threading.  Certain encoders like 
the Haivision Makito X4 can emit N independent NAL slices per frame.

This PR supersedes my previous #23873 and reuses the slice deferral added for 
one slice per tile ("lavc/hevcdec: decode the single-tile slices of a picture 
together"). Under slice threading, independent slices of an untiled, non-WPP 
picture are collected the same way. Once the picture is complete, each slice is 
decoded in its own execute2() job, and a job stops at the first CTB of the next 
slice. Deblocking boundary strengths on slice edges read the neighboring slice, 
so the workers skip them. They are computed afterwards, together with 
deblocking and SAO, in a serial pass that makes the same calls in the same 
order as the serial path. The output is bit-exact.

Constrained intra prediction and a PPS SCC extension continue to use the 
current serial decoding path.

Commits:

1. avcodec/hevc/mvs: test xB0 against the width before reading B0. This is 
#25692, included because the parallel slice path needs it. Under tile-parallel 
threading, the read it moves is already a data race. Will drop it once #25692 
is merged.
2. avcodec/hevc: decode tiles serially with constrained intra prediction. This 
fixes an existing race in the tile-parallel path and is independent of the 
rest. CIP sample substitution reads the PUs of neighboring tiles while other 
threads write them. TSan fails on PPS_A_qualcomm_7.
3. lavc/hevcdec: decode the slices of a non-tiled picture in parallel.
4. fate/hevc: add a slice threading test for untiled multi-slice pictures. It 
reuses the existing SLICES_A_Rovi_3 conformance reference.

Performance on an 8-core machine, -threads 8 -thread_type slice:

On an 8-core machine with -threads 8 -thread_type slice, decoding a 1080p 
stream with 8 slices per picture drops from 1.95 s to 0.96 s, about 2x faster. 
A 1080p stream with one slice per picture decodes in 2.01 s before and after, 
at both 1 and 8 threads.  No speed regression for streams that can't use the 
new path.


The single-slice stream is also unchanged at -threads 1.

Testing:

- framecrc is identical to master at -threads 1 for 4:2:0 and 4:2:2, 8-bit and 
10-bit Makito X4 captures, plus SLICES_A, SLIST_A–D, DELTAQP_A–C, RPLM_B, 
CIP_C, TILES_A, WPP_A, PPS_A, SLPPLP_A and STRUCT_B. Checked at threads 1/4/8, 
with both slice and frame threading.
- fate-hevc passes at THREADS=1/4/8 with THREAD_TYPE=slice, and at THREADS=4 
with frame threading.
- TSan is clean at -threads 8 -thread_type slice on the streams above.
- ASan and UBSan are clean at 4 and 8 threads.
- valgrind reports no leaks, including across a WPP to non-WPP stream 
transition.



From ce3b65c13ecd6361367e3b3d4f81c81e288ee135 Mon Sep 17 00:00:00 2001
From: Kieran Kunhya <[email protected]>
Date: Mon, 2 Nov 2026 23:04:26 +0100
Subject: [PATCH 1/4] avcodec/hevc/mvs: test xB0 against the width before
 reading B0

AVAILABLE(cand_up_right, B0) reads the motion field at B0 before the
xB0 < width test. cand_up_right_sap is not limited to the picture
width, so at the right edge of a picture whose width is not a multiple
of the CTB size this reads the first entry of the next row, and the
result is then discarded. The read stays inside tab_mvf (PU offsets are
multiples of the minimum PU size, so the row is never the last one),
but there is no reason to make it.

Test the width first in the merge and the AMVP derivation. The result
of the condition is unchanged.
---
 libavcodec/hevc/mvs.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/libavcodec/hevc/mvs.c b/libavcodec/hevc/mvs.c
index b22b3e7ac6d..854c6f8badc 100644
--- a/libavcodec/hevc/mvs.c
+++ b/libavcodec/hevc/mvs.c
@@ -371,8 +371,8 @@ static void 
derive_spatial_merge_candidates(HEVCLocalContext *lc, const HEVCCont
     }
 
     // above right spatial merge candidate
-    is_available_b0 = AVAILABLE(cand_up_right, B0) &&
-                      xB0 < sps->width &&
+    is_available_b0 = xB0 < sps->width &&
+                      AVAILABLE(cand_up_right, B0) &&
                       PRED_BLOCK_AVAILABLE(B0) &&
                       !is_diff_mer(pps, xB0, yB0, x0, y0);
 
@@ -690,8 +690,8 @@ b_candidates:
     xB0    = x0 + nPbW;
     yB0    = y0 - 1;
 
-    is_available_b0 =  AVAILABLE(cand_up_right, B0) &&
-                       xB0 < sps->width &&
+    is_available_b0 =  xB0 < sps->width &&
+                       AVAILABLE(cand_up_right, B0) &&
                        PRED_BLOCK_AVAILABLE(B0);
 
     // above spatial merge candidate
-- 
2.54.0


From d5451a3fbc9e6e93f24d6f9f97f5de841031dedd Mon Sep 17 00:00:00 2001
From: Phillip Blucas <[email protected]>
Date: Thu, 5 Nov 2026 20:10:16 -0600
Subject: [PATCH 2/4] avcodec/hevc: decode tiles serially with constrained
 intra prediction

With constrained_intra_pred_flag set, intra sample substitution checks
whether neighbouring PUs are intra coded, for example the up-left one,
gated only on the block not being at the picture edge. At a tile edge
that PU belongs to another tile, which under tile-parallel slice
threading is being decoded by another thread at the same time, so the
read is a data race; TSan reports it on PPS_A_qualcomm_7.

Keep such pictures on the serial path.

Signed-off-by: Phillip Blucas <[email protected]>
---
 libavcodec/hevc/hevcdec.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c
index faee53c5094..bdcb1b0f856 100644
--- a/libavcodec/hevc/hevcdec.c
+++ b/libavcodec/hevc/hevcdec.c
@@ -3696,6 +3696,7 @@ static int decode_slice_data(HEVCContext *s, const 
HEVCLayerContext *l,
         s->sh.num_entry_point_offsets > 0                &&
         pps->tiles_enabled_flag                          &&
         !pps->entropy_coding_sync_enabled_flag           &&
+        !pps->constrained_intra_pred_flag                &&
         s->sh.first_slice_in_pic_flag                    &&
         s->sh.num_entry_point_offsets + 1 == pps->num_tile_rows * 
pps->num_tile_columns)
         return hls_slice_data_tiles(s, nal);
-- 
2.54.0


From f6d0e62f708a20871c8ba1d90e7b152b06100501 Mon Sep 17 00:00:00 2001
From: Phillip Blucas <[email protected]>
Date: Thu, 5 Nov 2026 19:38:04 -0600
Subject: [PATCH 3/4] lavc/hevcdec: decode the slices of a non-tiled picture in
 parallel

Pictures split into several independent slices without tiles or WPP are
decoded one slice after another even under slice threading, although the
slices share no entropy coding state and, apart from the loop filters,
read nothing of each other. Broadcast encoders commonly emit such
streams, eight slices per 1080p picture for example.

Under slice threading, collect these slices with the deferral added for
one slice per tile, and once the picture is complete decode each in its
own job. A job stops at the start of the next slice, so damaged input
cannot run into the CTBs of another job. Deblocking boundary strengths
on slice edges read the neighbouring slice, so the workers skip them;
they are computed afterwards together with deblocking and SAO in a
serial pass that repeats the calls of the serial path in the same
order, so the output is bit-exact.

Constrained intra prediction substitutes samples based on the prediction
modes of neighbouring slices, and an SCC extension is parsed regardless
of the profile; both keep the serial path.

Around 1.9x faster at 8 threads on 8-slice 1080p streams on an 8-core
machine.

Signed-off-by: Phillip Blucas <[email protected]>
---
 libavcodec/hevc/filter.c  |  12 +--
 libavcodec/hevc/hevcdec.c | 191 ++++++++++++++++++++++++++++++++++++--
 libavcodec/hevc/hevcdec.h |   9 +-
 3 files changed, 195 insertions(+), 17 deletions(-)

diff --git a/libavcodec/hevc/filter.c b/libavcodec/hevc/filter.c
index bd18ca594ca..7e95b5ba460 100644
--- a/libavcodec/hevc/filter.c
+++ b/libavcodec/hevc/filter.c
@@ -778,7 +778,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext 
*lc, const HEVCLayer
 
     boundary_upper = y0 > 0 && !(y0 & 7);
     if (boundary_upper &&
-        ((!lc->sh->slice_loop_filter_across_slices_enabled_flag &&
+        (((!lc->sh->slice_loop_filter_across_slices_enabled_flag || 
lc->slice_bs_defer) &&
           lc->boundary_flags & BOUNDARY_UPPER_SLICE &&
           (y0 % (1 << sps->log2_ctb_size)) == 0) ||
          ((!pps->loop_filter_across_tiles_enabled_flag || lc->tile_bs_defer) &&
@@ -819,7 +819,7 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext 
*lc, const HEVCLayer
     // bs for vertical TU boundaries
     boundary_left = x0 > 0 && !(x0 & 7);
     if (boundary_left &&
-        ((!lc->sh->slice_loop_filter_across_slices_enabled_flag &&
+        (((!lc->sh->slice_loop_filter_across_slices_enabled_flag || 
lc->slice_bs_defer) &&
           lc->boundary_flags & BOUNDARY_LEFT_SLICE &&
           (x0 % (1 << sps->log2_ctb_size)) == 0) ||
          ((!pps->loop_filter_across_tiles_enabled_flag || lc->tile_bs_defer) &&
@@ -930,8 +930,8 @@ void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext 
*lc, const HEVCLayer
     }
 }
 
-void ff_hevc_tile_boundary_bs(HEVCLocalContext *lc, const HEVCLayerContext *l,
-                              const HEVCPPS *pps, int x0, int y0)
+void ff_hevc_ctb_edge_bs(HEVCLocalContext *lc, const HEVCLayerContext *l,
+                         const HEVCPPS *pps, int x0, int y0, unsigned edges)
 {
     const HEVCSPS *const sps = pps->sps;
     const HEVCContext *s = lc->parent;
@@ -943,7 +943,7 @@ void ff_hevc_tile_boundary_bs(HEVCLocalContext *lc, const 
HEVCLayerContext *l,
     const int ctb_size         = 1 << sps->log2_ctb_size;
     int i, bs;
 
-    if ((lc->boundary_flags & BOUNDARY_UPPER_TILE) && y0 > 0) {
+    if ((lc->boundary_flags & edges & (BOUNDARY_UPPER_TILE | 
BOUNDARY_UPPER_SLICE)) && y0 > 0) {
         const RefPicList *rpl_top = (lc->boundary_flags & 
BOUNDARY_UPPER_SLICE) ?
                                     ff_hevc_get_ref_list(s->cur_frame, x0, y0 
- 1) :
                                     lc->rpl;
@@ -967,7 +967,7 @@ void ff_hevc_tile_boundary_bs(HEVCLocalContext *lc, const 
HEVCLayerContext *l,
         }
     }
 
-    if ((lc->boundary_flags & BOUNDARY_LEFT_TILE) && x0 > 0) {
+    if ((lc->boundary_flags & edges & (BOUNDARY_LEFT_TILE | 
BOUNDARY_LEFT_SLICE)) && x0 > 0) {
         const RefPicList *rpl_left = (lc->boundary_flags & 
BOUNDARY_LEFT_SLICE) ?
                                      ff_hevc_get_ref_list(s->cur_frame, x0 - 
1, y0) :
                                      lc->rpl;
diff --git a/libavcodec/hevc/hevcdec.c b/libavcodec/hevc/hevcdec.c
index bdcb1b0f856..83fbdef9ba4 100644
--- a/libavcodec/hevc/hevcdec.c
+++ b/libavcodec/hevc/hevcdec.c
@@ -2776,12 +2776,11 @@ static void hls_decode_neighbour(HEVCLocalContext *lc,
 }
 
 /* Decode one CTB from the substream in lc->sub. */
-static int decode_ctb(HEVCContext *s, const HEVCLayerContext *l,
-                      HEVCLocalContext *lc, int ctb_addr_rs)
+static int decode_ctb_data(const HEVCContext *s, const HEVCLayerContext *l,
+                           HEVCLocalContext *lc, int ctb_addr_rs)
 {
     const HEVCPPS   *const pps = s->pps;
     const HEVCSPS   *const sps = pps->sps;
-    const int ctb_size    = 1 << sps->log2_ctb_size;
     const int ctb_addr_ts = pps->ctb_addr_rs_to_ts[ctb_addr_rs];
     const int x_ctb       = (ctb_addr_rs % sps->ctb_width) << 
sps->log2_ctb_size;
     const int y_ctb       = (ctb_addr_rs / sps->ctb_width) << 
sps->log2_ctb_size;
@@ -2805,7 +2804,6 @@ static int decode_ctb(HEVCContext *s, const 
HEVCLayerContext *l,
         goto fail;
 
     ff_hevc_save_states(lc, pps, ctb_addr_ts + 1);
-    ff_hevc_hls_filters(lc, l, pps, x_ctb, y_ctb, ctb_size);
 
     return ret;
 
@@ -2814,6 +2812,22 @@ fail:
     return ret;
 }
 
+static int decode_ctb(HEVCContext *s, const HEVCLayerContext *l,
+                      HEVCLocalContext *lc, int ctb_addr_rs)
+{
+    const HEVCPPS   *const pps = s->pps;
+    const HEVCSPS   *const sps = pps->sps;
+    const int ctb_size = 1 << sps->log2_ctb_size;
+    const int x_ctb    = (ctb_addr_rs % sps->ctb_width) << sps->log2_ctb_size;
+    const int y_ctb    = (ctb_addr_rs / sps->ctb_width) << sps->log2_ctb_size;
+    int ret = decode_ctb_data(s, l, lc, ctb_addr_rs);
+
+    if (ret >= 0)
+        ff_hevc_hls_filters(lc, l, pps, x_ctb, y_ctb, ctb_size);
+
+    return ret;
+}
+
 static int hls_decode_entry(HEVCContext *s, GetBitContext *gb)
 {
     HEVCLocalContext *const lc = &s->local_ctx[0];
@@ -3109,6 +3123,16 @@ static int tile_interleave_wanted(const HEVCContext *s, 
const HEVCPPS *pps)
     return pps->num_tile_columns * pps->num_tile_rows > 1;
 }
 
+static int slice_threads_wanted(const HEVCContext *s, const HEVCPPS *pps)
+{
+    return s->avctx->active_thread_type == FF_THREAD_SLICE &&
+           pps                                         &&
+           !pps->tiles_enabled_flag                    &&
+           !pps->entropy_coding_sync_enabled_flag      &&
+           !pps->constrained_intra_pred_flag           &&
+           !pps->pps_scc_extension_flag;
+}
+
 static int alloc_tile_streams(HEVCContext *s, unsigned n)
 {
     if (n > s->tile_streams_alloc) {
@@ -3258,14 +3282,14 @@ static int tile_slice_payload(const HEVCTileSlice *sl,
     return 0;
 }
 
-/* Only an independent slice covering exactly one tile may be deferred. */
+/* Only an independent slice covering exactly one tile, or any independent
+ * slice of an untiled picture under slice threading, may be deferred. */
 static int slice_is_deferrable(const HEVCContext *s)
 {
     const HEVCPPS *const pps = s->pps;
     int start_ts;
 
-    if (!tile_interleave_wanted(s, pps)         ||
-        s->sh.num_entry_point_offsets != 0      ||
+    if (s->sh.num_entry_point_offsets != 0      ||
         s->sh.dependent_slice_segment_flag      ||
         s->cur_layer != 0                       ||
         !s->cur_frame)
@@ -3275,6 +3299,12 @@ static int slice_is_deferrable(const HEVCContext *s)
     if (s->nb_tile_slices && s->cur_frame != s->tile_slices_frame)
         return 0;
 
+    if (slice_threads_wanted(s, pps))
+        return 1;
+
+    if (!tile_interleave_wanted(s, pps))
+        return 0;
+
     start_ts = pps->ctb_addr_rs_to_ts[s->sh.slice_ctb_addr_rs];
 
     if (start_ts < 0 || start_ts >= pps->sps->ctb_size ||
@@ -3392,6 +3422,144 @@ static int build_tile_streams(HEVCContext *s, unsigned 
nb)
     return 1;
 }
 
+static int hls_decode_entry_slice(AVCodecContext *avctx, void *hevc_lclist,
+                                  int job, int thread)
+{
+    HEVCLocalContext *lc = &((HEVCLocalContext *)hevc_lclist)[thread];
+    const HEVCContext *const s = lc->parent;
+    const HEVCLayerContext *const l = &s->layers[s->tile_slices_layer];
+    const HEVCPPS *const pps = s->pps;
+    const HEVCTileSlice *const sl = &s->tile_slices[job];
+    const uint8_t *data;
+    size_t size;
+    int more_data = 1, ts = sl->start_ts;
+
+    tile_slice_payload(sl, &data, &size);
+
+    bind_slice_ctx(lc, &sl->sh, sl->rpl, sl->collocated_ref);
+    substream_load(&lc->sub, data, size, sl->qp_y_seed);
+    lc->slice_bs_defer = 1;
+
+    while (more_data && ts < sl->end_ts) {
+        more_data = decode_ctb_data(s, l, lc, pps->ctb_addr_ts_to_rs[ts]);
+        if (more_data < 0)
+            return more_data;
+        ts++;
+    }
+
+    return ts;
+}
+
+/* Returns 1 if decoded, 0 to decode the slices serially, <0 on error. */
+static int decode_slices_parallel(HEVCContext *s, const HEVCLayerContext *l,
+                                  unsigned nb)
+{
+    const HEVCPPS *const pps = s->pps;
+    const HEVCSPS *const sps = pps->sps;
+    const int ctb_size = 1 << sps->log2_ctb_size;
+    HEVCLocalContext *lc;
+    int *end_ts;
+    int ret;
+
+    for (unsigned i = 0; i < nb; i++) {
+        HEVCTileSlice *const sl = &s->tile_slices[i];
+        const uint8_t *data;
+        size_t size;
+
+        if (sl->start_ts < 0 || sl->start_ts >= sps->ctb_size ||
+            tile_slice_payload(sl, &data, &size) < 0)
+            return 0;
+
+        if (i) {
+            if (sl->start_ts <= s->tile_slices[i - 1].start_ts)
+                return 0;
+            s->tile_slices[i - 1].end_ts = sl->start_ts;
+        }
+    }
+    s->tile_slices[nb - 1].end_ts = sps->ctb_size;
+
+    ret = alloc_local_ctxs(s);
+    if (ret < 0)
+        return ret;
+    lc = &s->local_ctx[0];
+
+    end_ts = av_calloc(nb, sizeof(*end_ts));
+    if (!end_ts)
+        return AVERROR(ENOMEM);
+
+    s->avctx->execute2(s->avctx, hls_decode_entry_slice, s->local_ctx, end_ts, 
nb);
+
+    for (unsigned i = 0; i < s->nb_local_ctx; i++)
+        s->local_ctx[i].slice_bs_defer = 0;
+
+    for (unsigned i = 0; i < nb; i++) {
+        const HEVCTileSlice *const sl = &s->tile_slices[i];
+
+        if (end_ts[i] < 0) {
+            if (end_ts[i] != AVERROR_INVALIDDATA ||
+                (s->avctx->err_recognition & AV_EF_EXPLODE)) {
+                ret = end_ts[i];
+                goto end;
+            }
+            av_log(s->avctx, AV_LOG_WARNING,
+                   "Skipping invalid undecodable slice %u\n", i);
+            end_ts[i] = sl->start_ts;
+        }
+
+        for (int ts = end_ts[i]; ts < sl->end_ts; ts++)
+            l->tab_slice_address[pps->ctb_addr_ts_to_rs[ts]] = -1;
+    }
+
+    for (unsigned i = 0; i < nb; i++) {
+        const HEVCTileSlice *const sl = &s->tile_slices[i];
+
+        if (sl->sh.disable_deblocking_filter_flag ||
+            !sl->sh.slice_loop_filter_across_slices_enabled_flag)
+            continue;
+
+        bind_slice_ctx(lc, &sl->sh, sl->rpl, sl->collocated_ref);
+
+        for (int ts = sl->start_ts; ts < end_ts[i]; ts++) {
+            const int rs    = pps->ctb_addr_ts_to_rs[ts];
+            const int x_ctb = (rs % sps->ctb_width) << sps->log2_ctb_size;
+            const int y_ctb = (rs / sps->ctb_width) << sps->log2_ctb_size;
+            unsigned edges = 0;
+
+            if (y_ctb > 0 && l->tab_slice_address[rs - sps->ctb_width] >= 0)
+                edges |= BOUNDARY_UPPER_SLICE;
+            if (x_ctb > 0 && l->tab_slice_address[rs - 1] >= 0)
+                edges |= BOUNDARY_LEFT_SLICE;
+
+            hls_decode_neighbour(lc, l, pps, sps, x_ctb, y_ctb, ts);
+            ff_hevc_ctb_edge_bs(lc, l, pps, x_ctb, y_ctb, edges);
+        }
+    }
+
+    for (unsigned i = 0; i < nb; i++) {
+        const HEVCTileSlice *const sl = &s->tile_slices[i];
+
+        bind_slice_ctx(lc, &sl->sh, sl->rpl, sl->collocated_ref);
+
+        for (int ts = sl->start_ts; ts < end_ts[i]; ts++) {
+            const int rs    = pps->ctb_addr_ts_to_rs[ts];
+            const int x_ctb = (rs % sps->ctb_width) << sps->log2_ctb_size;
+            const int y_ctb = (rs / sps->ctb_width) << sps->log2_ctb_size;
+
+            ff_hevc_hls_filters(lc, l, pps, x_ctb, y_ctb, ctb_size);
+        }
+    }
+
+    if (end_ts[nb - 1] == sps->ctb_size)
+        finish_last_ctb_filter(s, l, lc,
+                               (sps->ctb_width  - 1) << sps->log2_ctb_size,
+                               (sps->ctb_height - 1) << sps->log2_ctb_size);
+
+    ret = 1;
+end:
+    av_free(end_ts);
+    return ret;
+}
+
 /* If picture_complete, the slices cover the picture and are decoded in row
  * major order. Otherwise more slices follow, so decode them in their
  * original order. */
@@ -3415,6 +3583,12 @@ static int decode_deferred_tile_slices(HEVCContext *s, 
int picture_complete)
         return 0;
     }
 
+    if (picture_complete && nb > 1 && slice_threads_wanted(s, pps)) {
+        ret = decode_slices_parallel(s, l, nb);
+        if (ret)
+            return ret < 0 ? ret : 0;
+    }
+
     if (picture_complete) {
         interleave = build_tile_streams(s, nb);
         if (interleave < 0)
@@ -3610,7 +3784,8 @@ static int hls_slice_data_tiles(HEVCContext *s, const 
H2645NAL *nal)
             y_ctb = (ctb_addr_rs / sps->ctb_width) << sps->log2_ctb_size;
             hls_decode_neighbour(&s->local_ctx[0], l, pps, sps, x_ctb, y_ctb, 
ctb_addr_ts);
             if (s->local_ctx[0].boundary_flags & (BOUNDARY_LEFT_TILE | 
BOUNDARY_UPPER_TILE))
-                ff_hevc_tile_boundary_bs(&s->local_ctx[0], l, pps, x_ctb, 
y_ctb);
+                ff_hevc_ctb_edge_bs(&s->local_ctx[0], l, pps, x_ctb, y_ctb,
+                                    BOUNDARY_LEFT_TILE | BOUNDARY_UPPER_TILE);
         }
     }
 
diff --git a/libavcodec/hevc/hevcdec.h b/libavcodec/hevc/hevcdec.h
index bafc218d32b..83c9cf80e2e 100644
--- a/libavcodec/hevc/hevcdec.h
+++ b/libavcodec/hevc/hevcdec.h
@@ -459,6 +459,7 @@ typedef struct HEVCLocalContext {
 
     /* decoding tiles in parallel: defer tile-boundary BS to a serial pass */
     int tile_bs_defer;
+    int slice_bs_defer;
 
     // an array of these structs is used for per-thread state - pad its size
     // to avoid false sharing
@@ -510,6 +511,7 @@ typedef struct HEVCTileSlice {
     const uint8_t *data;
     int size;
     int start_ts;
+    int end_ts;
     int qp_y_seed;      ///< lc[0].qp_y before deferring, the initial QP 
predictor
     const RefPicList *rpl;              ///< reference lists of this slice
     const HEVCFrame *collocated_ref;    ///< temporal MVP target of this slice
@@ -627,7 +629,8 @@ typedef struct HEVCContext {
     AVBufferRef *rpu_buf;       ///< 0 or 1 Dolby Vision RPUs.
     DOVIContext dovi_ctx;       ///< Dolby Vision decoding context
 
-    /* Slices deferred when every tile carries its own slice. Frame and layer
+    /* Slices deferred when every tile carries its own slice, or when the
+     * slices of an untiled picture are decoded in parallel. Frame and layer
      * are kept so that leftovers are dropped, not decoded into another
      * frame. */
     HEVCTileSlice *tile_slices;
@@ -769,8 +772,8 @@ void ff_hevc_set_qPy(HEVCLocalContext *lc,
 void ff_hevc_deblocking_boundary_strengths(HEVCLocalContext *lc, const 
HEVCLayerContext *l,
                                            const HEVCPPS *pps,
                                            int x0, int y0, int 
log2_trafo_size);
-void ff_hevc_tile_boundary_bs(HEVCLocalContext *lc, const HEVCLayerContext *l,
-                              const HEVCPPS *pps, int x0, int y0);
+void ff_hevc_ctb_edge_bs(HEVCLocalContext *lc, const HEVCLayerContext *l,
+                         const HEVCPPS *pps, int x0, int y0, unsigned edges);
 int ff_hevc_cu_qp_delta_sign_flag(HEVCLocalContext *lc);
 int ff_hevc_cu_qp_delta_abs(HEVCLocalContext *lc);
 int ff_hevc_cu_chroma_qp_offset_flag(HEVCLocalContext *lc);
-- 
2.54.0


From c49d51b87711e8307e1092d711fb9e20072e0a38 Mon Sep 17 00:00:00 2001
From: Phillip Blucas <[email protected]>
Date: Thu, 5 Nov 2026 20:06:41 -0600
Subject: [PATCH 4/4] fate/hevc: add a slice threading test for untiled
 multi-slice pictures

Decode SLICES_A_Rovi_3 with four slice threads. Its pictures carry many
independent slices without tiles, so they go through the parallel slice
path; the output matches the existing conformance reference.

Signed-off-by: Phillip Blucas <[email protected]>
---
 tests/fate/hevc.mak | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/tests/fate/hevc.mak b/tests/fate/hevc.mak
index e8692b67295..9ae21af24ab 100644
--- a/tests/fate/hevc.mak
+++ b/tests/fate/hevc.mak
@@ -308,6 +308,11 @@ fate-hevc-tiles-slice-threads: CMD = thread_type=slice 
threads=4 framecrc -i $(T
 fate-hevc-tiles-slice-threads: REF = 
$(SRC_PATH)/tests/ref/fate/hevc-conformance-TILES_A_Cisco_2
 FATE_HEVC-$(call FRAMECRC, HEVC, HEVC, HEVC_PARSER) += 
fate-hevc-tiles-slice-threads
 
+# SLICES_A conformance pictures through the parallel slice path, same output
+fate-hevc-slice-threads: CMD = thread_type=slice threads=4 framecrc -i 
$(TARGET_SAMPLES)/hevc-conformance/SLICES_A_Rovi_3.bit -pix_fmt yuv420p
+fate-hevc-slice-threads: REF = 
$(SRC_PATH)/tests/ref/fate/hevc-conformance-SLICES_A_Rovi_3
+FATE_HEVC-$(call FRAMECRC, HEVC, HEVC, HEVC_PARSER) += fate-hevc-slice-threads
+
 # deblocking disabled + loop_filter_across_tiles: tile edges must stay 
unfiltered
 fate-hevc-tiles-nodeblock-slice-threads: CMD = thread_type=slice threads=4 
framecrc -i $(TARGET_SAMPLES)/hevc/tiles_nodeblock.hevc
 FATE_HEVC-$(call FRAMECRC, HEVC, HEVC, HEVC_PARSER) += 
fate-hevc-tiles-nodeblock-slice-threads
-- 
2.54.0

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

Reply via email to