rusty_h264_decoder/mb16.rs
1//! I_16x16 macroblock decoding — the mirror of the encoder's `mb16`.
2//!
3//! Parses each macroblock's residuals and reconstructs it with the exact same
4//! prediction + inverse-transform helpers the encoder uses, so decoder output
5//! matches encoder reconstruction bit-for-bit.
6#![allow(clippy::needless_range_loop)]
7
8#[allow(unused_imports)]
9use alloc::borrow::ToOwned;
10#[allow(unused_imports)]
11use alloc::boxed::Box;
12#[allow(unused_imports)]
13use alloc::format;
14#[allow(unused_imports)]
15use alloc::string::{String, ToString};
16#[allow(unused_imports)]
17use alloc::vec;
18#[allow(unused_imports)]
19use alloc::vec::Vec;
20use rusty_h264_common::bit_reader::OutOfData;
21use rusty_h264_common::cavlc::{decode_residual_block_into, read_cbp_inter, read_cbp_intra};
22use rusty_h264_common::inter::{
23 inter_partitions, mc_chroma_padded, mc_luma_padded, predict_mv, predict_partition_mv,
24 MvNeighbor,
25};
26use rusty_h264_common::predict::{
27 add_residual_8x8, chroma8x8_pred, chroma_qp, intra4x4_pred, intra8x8_pred, luma16x16_pred,
28 nnz_raster_from_z, reconstruct_4x4_dc_into, reconstruct_4x4_scan_into, I16Mode,
29 CHROMA_4X4_SCAN_XY, LUMA_4X4_SCAN_XY,
30};
31use rusty_h264_common::transform::{
32 inverse_quant_8x8_dq, inverse_quant_chroma_dc, inverse_quant_chroma_dc_weighted,
33 inverse_quant_luma_dc_scan, Dequant8Qp, DequantQp, DQ8_FLAT, DQ_FLAT,
34};
35use rusty_h264_common::{BitReader, YuvFrame};
36
37/// One frame's motion field, in 4x4-block raster (`mb_w*4` wide).
38///
39/// Captured from any conformant stream this decoder parses — including x264's —
40/// so a harness can compare motion fields between encoders without depending on
41/// external MV-export tooling.
42pub struct MvField {
43 pub mb_w: usize,
44 pub mb_h: usize,
45 pub mv: Vec<(i32, i32)>,
46 pub ref_idx: Vec<i32>,
47 pub inter: Vec<bool>,
48}
49
50/// Frames captured in decode order when `RFF_MV_DUMP=1`. Diagnostic only.
51#[cfg(feature = "std")]
52pub static MV_DUMP: crate::sync::Mutex<Vec<MvField>> = crate::sync::Mutex::new(Vec::new());
53
54/// `RH264_DUMP_MB` -- the per-frame/per-slice conformance-bisection dump.
55///
56/// ROUTED AT BUILD TIME, like the rest of the knob inventory. It was a bare
57/// `knob("RH264_DUMP_MB").is_some()` at four call sites, which is
58/// `std::env::var` PLUS a String allocation on EVERY slice and every DPB add --
59/// and it kept each dump body, with its `eprintln!` formatting machinery and
60/// String building, compiled into the shipping decoder. `deblock` alone carried
61/// four `Stderr::write_fmt`, four `panic_fmt` and thirteen alloc/free calls that
62/// exist only for a dump nobody enables.
63/// `RS_H264_ROUTE_DUMP` -- the per-picture content-route dump. Build-time routed
64/// for the same reason as `dump_mb_on`.
65pub(crate) fn route_dump_on() -> bool {
66 #[cfg(not(feature = "knobs"))]
67 {
68 return false;
69 }
70 #[cfg(feature = "knobs")]
71 {
72 static ON: rusty_h264_common::once::OnceLock<bool> =
73 rusty_h264_common::once::OnceLock::new();
74 *ON.get_or_init(|| rusty_h264_common::knob("RS_H264_ROUTE_DUMP").is_some())
75 }
76}
77
78pub(crate) fn dump_mb_on() -> bool {
79 #[cfg(not(feature = "knobs"))]
80 {
81 return false;
82 }
83 #[cfg(feature = "knobs")]
84 {
85 static ON: rusty_h264_common::once::OnceLock<bool> =
86 rusty_h264_common::once::OnceLock::new();
87 *ON.get_or_init(|| rusty_h264_common::knob("RH264_DUMP_MB").is_some())
88 }
89}
90
91pub fn mv_dump_on() -> bool {
92 #[cfg(not(feature = "knobs"))]
93 {
94 return false;
95 }
96 #[cfg(feature = "knobs")]
97 {
98 static ON: rusty_h264_common::once::OnceLock<bool> =
99 rusty_h264_common::once::OnceLock::new();
100 *ON.get_or_init(|| rusty_h264_common::knob("RFF_MV_DUMP").map_or(false, |v| v != "0"))
101 }
102}
103
104/// Copy filtered MB rows `[prev..mb_rows)` from coded-size planes into a
105/// Frame-MT progress slot's live padded planes, then bump `ready_rows`.
106/// Coarser publish: only advance the watermark every 2 MB rows (and always
107/// on the last row) to cut lock/notify traffic.
108fn publish_filtered_rows_to_slot(
109 slot: &crate::RefFrame,
110 rec_y: &[u8],
111 rec_u: &[u8],
112 rec_v: &[u8],
113 cw: usize,
114 ccw: usize,
115 ch: usize,
116 mb_rows: usize,
117) {
118 if mb_rows == 0 || slot.frozen.get().is_some() {
119 return;
120 }
121 if !crate::row_publish_on() {
122 return;
123 }
124 let mb_h = (ch + 15) / 16;
125 // Batch: defer watermark until even MB rows (or picture end).
126 if mb_rows < mb_h && (mb_rows & 1) != 0 {
127 return;
128 }
129 let Some(live) = &slot.live else {
130 slot.publish_ready_rows(mb_rows * 16);
131 return;
132 };
133 let cch = ch / 2;
134 let prev = slot.ready_rows.load(core::sync::atomic::Ordering::Acquire) / 16;
135 if mb_rows <= prev {
136 return;
137 }
138 let mut py = live.py.write().unwrap();
139 let mut pu = live.pu.write().unwrap();
140 let mut pv = live.pv.write().unwrap();
141 let ls = cw + 2 * crate::LPAD;
142 let cs = ccw + 2 * crate::CPAD;
143 for mr in prev..mb_rows {
144 for dy in 0..16 {
145 let y = mr * 16 + dy;
146 if y >= ch {
147 break;
148 }
149 let src = &rec_y[y * cw..(y + 1) * cw];
150 let dst_y = y + crate::LPAD;
151 // ONE row segment covering pad+picture+pad, then `split_at_mut` and
152 // two fills — the edge reads and the 2*LPAD writes were separate
153 // whole-plane indexes. Same shape as `expand_plane`.
154 let seg = &mut py[dst_y * ls..][..2 * crate::LPAD + cw];
155 let (lpad, rest) = seg.split_at_mut(crate::LPAD);
156 let (mid, rpad) = rest.split_at_mut(cw);
157 mid.copy_from_slice(src);
158 let (Some(&left), Some(&right)) = (mid.first(), mid.last()) else {
159 continue;
160 };
161 lpad.fill(left);
162 rpad[..crate::LPAD].fill(right);
163 }
164 for dy in 0..8 {
165 let cy = mr * 8 + dy;
166 if cy >= cch {
167 break;
168 }
169 for (rec, plane) in [(rec_u, &mut *pu), (rec_v, &mut *pv)] {
170 let src = &rec[cy * ccw..(cy + 1) * ccw];
171 let dst_y = cy + crate::CPAD;
172 let seg = &mut plane[dst_y * cs..][..2 * crate::CPAD + ccw];
173 let (lpad, rest) = seg.split_at_mut(crate::CPAD);
174 let (mid, rpad) = rest.split_at_mut(ccw);
175 mid.copy_from_slice(src);
176 let (Some(&left), Some(&right)) = (mid.first(), mid.last()) else {
177 continue;
178 };
179 lpad.fill(left);
180 rpad[..crate::CPAD].fill(right);
181 }
182 }
183 }
184 if prev == 0 {
185 // Copy the whole first picture ROW upward instead of walking columns:
186 // `split_at_mut` at the pad boundary proves both sides at once.
187 let (pad, rest) = py.split_at_mut(crate::LPAD * ls);
188 if let Some(first) = rest.get(..ls) {
189 for row in pad.chunks_mut(ls) {
190 row.copy_from_slice(first);
191 }
192 }
193 for plane in [&mut *pu, &mut *pv] {
194 let (pad, rest) = plane.split_at_mut(crate::CPAD * cs);
195 if let Some(first) = rest.get(..cs) {
196 for row in pad.chunks_mut(cs) {
197 row.copy_from_slice(first);
198 }
199 }
200 }
201 }
202 slot.publish_ready_rows(mb_rows * 16);
203}
204
205/// Reconstructed coded-size planes plus CAVLC `nnz` context grids.
206pub struct FrameDecoder {
207 mb_w: usize,
208 mb_h: usize,
209 /// Slice QP (`SliceQPy`) — the deblock filter's frame-level QP.
210 qp: u8,
211 /// Running luma QP (`QPy`), carried across macroblocks and stepped by each
212 /// `mb_qp_delta` (spec §7.4.5). Equals `qp` on constant-QP streams.
213 cur_qp: u8,
214 /// `chroma_qp_index_offset` from the active PPS (§8.5.8).
215 chroma_qp_offset: i32,
216 cw: usize,
217 ch: usize,
218 ccw: usize,
219 cch: usize,
220 rec_y: Vec<u8>,
221 rec_u: Vec<u8>,
222 rec_v: Vec<u8>,
223 /// Per-macroblock luma QP (`QPy`), for per-edge deblock strength.
224 mb_qp: Vec<u8>,
225 /// First macroblock address of the slice currently being decoded. Neighbors
226 /// with a lower address belong to an earlier slice and are "not available"
227 /// for prediction (spec §8.3/§8.4). Slices are contiguous raster ranges (we
228 /// reject FMO/slice-groups), so address ≥ this ⇔ same slice.
229 slice_first_mb: usize,
230 nnz_y: Vec<u8>,
231 nnz_c: [Vec<u8>; 2],
232 modes_y: Vec<u8>,
233 coded_y: Vec<bool>,
234 /// Per-4×4-block List-0 motion (mv + ref index, `-1` = no L0). For P slices
235 /// this is the only motion; B slices add the List-1 grids below.
236 mv_y: Vec<(i32, i32)>,
237 inter_y: Vec<bool>,
238 ref_idx_y: Vec<i32>,
239 /// Per-4×4-block List-1 motion for B slices (`ref_idx1 = -1` = no L1).
240 mv1: Vec<(i32, i32)>,
241 ref_idx1: Vec<i32>,
242 /// `RefPicList1` and B-slice flags (unused outside B slices).
243 refs1: Vec<crate::Ref>,
244 num_ref_active1: usize,
245 is_b: bool,
246 /// True if the stream's profile permits B-slices (`profile_idc != 66`). When
247 /// false (Baseline / Constrained Baseline), `as_reference_pooled` skips the per-block
248 /// motion (mv/ref_idx/ref_poc) that only B temporal/spatial direct ever reads.
249 b_possible: bool,
250 direct_spatial: bool,
251 nnz_l_cache: [u8; 25],
252 nnz_c_cache: [[u8; 9]; 2],
253 /// Decoded-picture buffer (most-recent first); empty in I-slices. `ref_idx`
254 /// indexes into this list.
255 refs: Vec<crate::Ref>,
256 /// `num_ref_idx_l0_active` for the current slice — drives whether `ref_idx`
257 /// is coded (active > 1) and its te(v)/ue(v) form, independently of how many
258 /// reference pictures actually exist (spec §7.4.5.1, §9.1).
259 num_ref_active: usize,
260 /// `constrained_intra_pred_flag`: when set, intra prediction may only use
261 /// samples from intra-coded neighbors (inter neighbors are "not available").
262 constrained_intra: bool,
263 /// High-profile 4×4 scaling matrices in **raster** order, indexed by
264 /// `[Y-intra, Cb-intra, Cr-intra, Y-inter, Cb-inter, Cr-inter]`. `None` = flat.
265 scaling: Option<[[i32; 16]; 6]>,
266 /// High-profile 8×8 luma scaling matrices in raster order `[Y-intra, Y-inter]`
267 /// (4:2:0 has only these two). `None` = flat.
268 scaling8: Option<[[i32; 64]; 2]>,
269 /// `transform_8x8_mode_flag` from the PPS: enables `transform_size_8x8_flag`.
270 transform_8x8_mode: bool,
271 /// SPS `qpprime_y_zero_transform_bypass_flag` — refusal gate in `step_qp`.
272 transform_bypass: bool,
273 /// GATE 1 router counters (big-oppy-decoder §2): skip MBs and
274 /// residual-coded MBs this picture. Two integer adds per MB, no atomics.
275 route_skip_mbs: u32,
276 route_coded_mbs: u32,
277 /// Per-slice `(first_mb, idc == 2)` in decode order — drives the
278 /// `disable_deblocking_filter_idc == 2` cross-slice-edge suppression.
279 slice_bounds: Vec<(usize, bool)>,
280 /// The CURRENT slice's `disable_deblocking_filter_idc == 2`, latched by
281 /// `set_deblock_params` and recorded per slice by the decode loops.
282 cur_idc2: bool,
283 /// Per-macroblock `transform_size_8x8_flag` (for deblocking: internal 4×4
284 /// luma edges of 8×8-transform MBs are not filtered).
285 mb_t8x8: Vec<bool>,
286 // ---- Row-interleaved deblocking state (docs/row-interleave-plan.md) ----
287 /// Per-MB boundary strengths, filled row-by-row as decode completes rows.
288 bs_frame: Vec<rusty_h264_common::deblock::MbBs>,
289 /// Rows whose bS is derived (watermark).
290 bs_rows: usize,
291 /// Rows already deblock-FILTERED (watermark; R3).
292 flt_rows: usize,
293 /// Two-row rolling window of packed records (prev = row r-1, cur = row r).
294 pk_prev: Vec<rusty_h264_common::deblock::MbPack>,
295 pk_cur: Vec<rusty_h264_common::deblock::MbPack>,
296 /// Transform-block coded mask (nnz with the 8x8 OR applied), filled per row.
297 nnz_dbr: Vec<u8>,
298 /// Unfiltered bottom rows of the last-filtered MB row (intra reads these).
299 bak_y: Vec<u8>,
300 bak_u: Vec<u8>,
301 bak_v: Vec<u8>,
302 /// Entropy-decouple: deferred pixel jobs + the per-slice activation flag
303 /// (CABAC slices only — the CAVLC loop has no flush hooks).
304 edc_jobs: Vec<EdcJob>,
305 /// Recycled job boxes (see `take_pinter_job`): a coded inter macroblock
306 /// used to malloc, fill, memcpy and free a 2.8 KB box every time.
307 edc_job_pool: Vec<Box<PInterJob>>,
308 edc_nores_pool: Vec<Box<PInterNoResJob>>,
309 /// Coefficient planes for the B and intra arms (which reconstruct inline,
310 /// so no job carries them): taken for a macroblock, zeroed per CODED block
311 /// by the parse, put back after recon. `None` only while in use.
312 scratch_luma: Option<Box<[[i32; 16]; 16]>>,
313 scratch_luma8: Option<Box<[[i32; 64]; 4]>>,
314 scratch_cac: Option<Box<[[[i32; 16]; 4]; 2]>>,
315 edc_active: bool,
316 // ---- E2: the worker-thread plumbing (all None outside a threaded slice) ----
317 edc_tx: Option<crate::sync::mpsc::SyncSender<EdcMsg>>,
318 edc_ctx_rx: Option<crate::sync::mpsc::Receiver<PixelCtx>>,
319 edc_back_tx: Option<crate::sync::mpsc::Sender<PixelCtx>>,
320 /// While the parse thread holds the pixel context for an intra macroblock
321 /// (planes moved into `self`), the rest of the context parks here.
322 edc_parked: Option<PixelCtx>,
323 /// E3: while parsing a B macroblock in threaded mode, its MC regions
324 /// accumulate here instead of executing (the pixel side is the worker's).
325 edc_regions: Option<Vec<BRegion>>,
326 /// Measurement only: previous full-MB single-rect direct (x, y, refi0,
327 /// refi1, m0, m1) for run-adjacency counting. No decode behavior.
328 bsk_last: Option<(usize, usize, i32, i32, (i32, i32), (i32, i32))>,
329 /// D10: jobs accumulated for the current row, sent as ONE message.
330 edc_batch: Vec<EdcJob>,
331 /// D12: bits/MB carried in from previous slices (0 = not yet known).
332 bits_per_mb: f64,
333 /// Current slice's deblock parameters (set per slice by the caller).
334 db_ena: bool,
335 db_oa: i32,
336 db_ob: i32,
337 /// Per-macroblock deblock derivation CLASS (`MB_KIND_*`), so the loop filter
338 /// can skip the 24-block neighbourhood gather on macroblocks whose strengths
339 /// are determined by syntax alone. Starts UNSET; anything left UNSET simply
340 /// takes the blind path, so a missed producer site costs speed, not
341 /// correctness. Only classes that are uniform BY SYNTAX are written — notably
342 /// NOT `B_Skip`/`B_Direct`, whose direct-derived motion varies per 4×4.
343 mb_kind: Vec<u8>,
344 /// Explicit weighted-prediction tables, when active for this slice.
345 weights: Option<WeightTable>,
346 /// `frame_mt::row_progress_on()` cached at construction (see
347 /// wait_refs_for_mb).
348 row_wait: bool,
349 /// Colocated-picture fast-probe cache (set_b_context): true when refs1[0]
350 /// is frozen (1T), short-term, with motion grids — col_zero then skips
351 /// the Option + live + long_term + w4 branch chain per probe.
352 col_ok: bool,
353 col_w4: usize,
354 /// Measurement knobs cached at construction — these were OnceLock derefs
355 /// on EVERY skip macroblock (no_bskipfast per B skip, no_runmv per P
356 /// skip); one field test now.
357 /// rowdb_on()/rowhook_eager() cached at construction — these were atomic
358 /// loads inside row_hook_at on EVERY macroblock.
359 /// MEASUREMENT KNOB, cached. `kind_loads()` is a `OnceLock` deref and was
360 /// evaluated as a MATCH GUARD on every Skip/InterUniform macroblock in
361 /// `derive_bs_row` — the dominant class. The census (DBSDERIVE kindarm=0 on
362 /// 6/6 corpus streams) shows the arm it guards never fires by default, so
363 /// every one of those derefs was pure tax. Same treatment `k_rowdb` /
364 /// `k_roweager` already had.
365 /// True once ANY macroblock in this picture used the 8x8 transform. The
366 /// `nnz_dbr` row copy + the per-row t8 scan in `derive_bs_row` exist ONLY
367 /// to apply the 8x8 coded-status OR; with no t8 macroblock `nnz_dbr` is a
368 /// byte-for-byte copy of `nnz_y`, so both are skipped and the derivation
369 /// reads `nnz_y` directly. Census: t8mb=0 on every cavlc/main stream.
370 any_t8: bool,
371 /// True once any slice in this picture carries
372 /// `disable_deblocking_filter_idc == 2`. Replaces a per-ROW
373 /// `slice_bounds.iter().any(..)` scan (census: idc2row=0 on the corpus).
374 any_idc2: bool,
375 /// Raster address whose P_Skip MV is FORCED (0,0): the previous
376 /// decode_p_skip was at addr-1 and committed (0,0), so this MB's left
377 /// neighbor either fires §8.4.1.1's zero-MV rule (in-slice: ref 0,
378 /// (0,0)) or is unavailable (out-of-slice — same rule). Interior MBs of
379 /// a skip run skip the 3-neighbor gather entirely.
380 skip_zero_next: usize,
381 /// Pending B_Skip grid+recon span: (mb row, x0, n, kind) of CONSECUTIVE
382 /// fast B_Skips with IDENTICAL committed values, deferred and range-
383 /// filled/band-reconned at flush. Flushed before ANY grid or pixel
384 /// reader runs — see span_flush callers.
385 bzspan: Option<(usize, usize, usize, BzKind)>,
386 /// Pending P_Skip (0,0) grid-commit span — the P mirror of `bzspan`
387 /// (values: ref 0 list-0, mv (0,0), inter, coded, DC mode, kind SKIP).
388 /// The bool records whether the RECON is deferred too (1T, identity/no
389 /// weights) — decided at push time, not re-derived at flush (begin_slice
390 /// may have changed the fields in between).
391 pzspan: Option<(usize, usize, usize, bool)>,
392 /// Per-picture bitmap: MB decoded as a ref0/(0,0)-both-lists zero-bi fast
393 /// B_Skip. Never cleared mid-picture — false only ever means "derive
394 /// normally" — so no MB path carries a clearing duty.
395 bzero: Vec<bool>,
396 /// Pooled CABAC slice scratch — refilled (not reallocated) at slice entry;
397 /// carried across pictures via `GridPool`. Taken out of `self` at
398 /// `decode_slice_cabac_inner` entry and put back at its normal exit (an
399 /// error exit simply forfeits the pooled allocation).
400 sc_cat: Vec<u8>,
401 sc_cbp: Vec<u8>,
402 sc_cmode: Vec<i32>,
403 sc_nzc: Vec<[u8; 24]>,
404 sc_cbfdc: Vec<u16>,
405 sc_skip: Vec<bool>,
406 sc_ref: Vec<[i8; 16]>,
407 sc_mvd: Vec<[[i16; 2]; 16]>,
408 sc_ref1: Vec<[i8; 16]>,
409 sc_mvd1: Vec<[[i16; 2]; 16]>,
410 sc_direct: Vec<bool>,
411 /// Lazily cached `implicit_weights(0, 0)` — slice-constant (POCs of
412 /// refs[0]/refs1[0] and cur don't change within a slice). Reset in
413 /// set_b_context.
414 iw00: Option<Option<(i32, i32)>>,
415 /// Cached `weights.list_identity(0)` — all list-0 refs identity; gates the
416 /// whole weight_partition pass for P inter (one choke point, all sites).
417 weights_l0id: bool,
418 /// Cached `weights.ref0_identity()` — identity tables (the x264 weightp=2
419 /// common case) make every ref-0 weighting pass an exact no-op, and the
420 /// skip fast paths check this per MB, so it is computed once per slice.
421 weights_id0: bool,
422 /// Current picture's `PicOrderCnt` (for temporal direct + implicit weighting).
423 cur_poc: i32,
424 /// `weighted_bipred_idc` (0 = none/average, 1 = explicit, 2 = implicit).
425 weighted_bipred_idc: u8,
426 /// `direct_8x8_inference_flag` (B direct co-located sub-block selection).
427 direct_8x8_inference: bool,
428 /// Frame-MT Phase B: shared progress Arc for the picture being decoded.
429 progress: Option<crate::Ref>,
430 /// Slice-stable ref→POC tables for bS (≤16 entries). `derive_bs_row` used
431 /// to `collect()` these every row — same values for the whole slice.
432 ref_poc0: Vec<i32>,
433 ref_poc1: Vec<i32>,
434}
435
436/// Explicit weighted-prediction tables (spec §7.4.3.2 / §8.4.2.3.2). Per
437/// reference list, per ref index: a luma `(weight, offset)` and two chroma
438/// `(weight, offset)` (Cb, Cr). `log2` denominators are shared.
439#[derive(Clone, Default)]
440pub struct WeightTable {
441 pub luma_log2_denom: i32,
442 pub chroma_log2_denom: i32,
443 /// `[list][ref_idx] = (weight, offset)`.
444 pub luma: [Vec<(i32, i32)>; 2],
445 /// `[list][ref_idx][cb=0/cr=1] = (weight, offset)`.
446 pub chroma: [Vec<[(i32, i32); 2]>; 2],
447}
448
449impl WeightTable {
450 /// True when list-0 ref-0 carries identity weights (w == 1<<denom, offset 0)
451 /// for luma and both chroma planes. x264's weightp=2 puts a pred_weight_table
452 /// in EVERY P slice, but outside fades the entries are identity — and the
453 /// identity weight is an EXACT no-op ((s*2^d + 2^(d-1))>>d + 0 == s), so a
454 /// P_Skip recon may bypass the weighting pass byte-identically.
455 fn ref0_identity(&self) -> bool {
456 self.luma[0]
457 .first()
458 .is_some_and(|&(w, o)| w == 1 << self.luma_log2_denom && o == 0)
459 && self.chroma[0].first().is_some_and(|c| {
460 c.iter()
461 .all(|&(w, o)| w == 1 << self.chroma_log2_denom && o == 0)
462 })
463 }
464
465 /// True when EVERY entry of `list` (all refs, luma + both chroma) is the
466 /// identity weight — the x264 weightp=2 shape outside fades. Identity is
467 /// an exact per-pixel no-op (see ref0_identity), so the whole per-ref
468 /// weighting pass may be skipped for this list.
469 fn list_identity(&self, list: usize) -> bool {
470 self.luma[list]
471 .iter()
472 .all(|&(w, o)| w == 1 << self.luma_log2_denom && o == 0)
473 && self.chroma[list].iter().all(|c| {
474 c.iter()
475 .all(|&(w, o)| w == 1 << self.chroma_log2_denom && o == 0)
476 })
477 }
478
479 /// Applies a single-list (uni-prediction) luma weight (spec §8.4.2.3.2).
480 /// Resolves a list/ref slot's (weight, offset) ONCE. `list` is 0..1 and the
481 /// fallback is the IDENTITY weight, which is what an absent entry means.
482 #[inline]
483 fn luma_wo(&self, list: usize, refi: usize) -> (i32, i32) {
484 self.luma[list & 1]
485 .get(refi)
486 .copied()
487 .unwrap_or((1 << self.luma_log2_denom, 0))
488 }
489
490 /// Ditto for chroma component `cc`.
491 #[inline]
492 fn chroma_wo(&self, list: usize, refi: usize, cc: usize) -> (i32, i32) {
493 self.chroma[list & 1]
494 .get(refi)
495 .map(|c| c[cc & 1])
496 .unwrap_or((1 << self.chroma_log2_denom, 0))
497 }
498
499 fn apply_luma(&self, sample: u8, list: usize, refi: usize) -> u8 {
500 let (w, o) = self.luma_wo(list, refi);
501 let lwd = self.luma_log2_denom;
502 let v = if lwd >= 1 {
503 ((sample as i32 * w + (1 << (lwd - 1))) >> lwd) + o
504 } else {
505 sample as i32 * w + o
506 };
507 v.clamp(0, 255) as u8
508 }
509
510 /// Applies a single-list (uni-prediction) chroma weight for component `cc`.
511 fn apply_chroma(&self, sample: u8, list: usize, refi: usize, cc: usize) -> u8 {
512 let (w, o) = self.chroma_wo(list, refi, cc);
513 let cwd = self.chroma_log2_denom;
514 let v = if cwd >= 1 {
515 ((sample as i32 * w + (1 << (cwd - 1))) >> cwd) + o
516 } else {
517 sample as i32 * w + o
518 };
519 v.clamp(0, 255) as u8
520 }
521}
522
523/// Why a macroblock could not be decoded.
524#[derive(Debug, Clone, PartialEq, Eq)]
525pub enum MbError {
526 Truncated,
527 Unsupported(&'static str),
528}
529
530impl From<OutOfData> for MbError {
531 fn from(_: OutOfData) -> Self {
532 MbError::Truncated
533 }
534}
535
536/// Recycled per-picture scratch grids.
537///
538/// `FrameDecoder::new` used to allocate ~1.65 MB of frame-wide grids for EVERY
539/// coded picture and drop them when the picture finished. The sampled profiler
540/// prices that (stage `dec-setup`) at 6.7% of decode — larger than dequant,
541/// reconstruct and intra prediction combined, and none of it is codec work.
542///
543/// Two costs are being paid, and the allocation is the bigger one. A ~460 KB
544/// `Vec` goes straight to the OS, so every page is a fresh zero page and the
545/// decoder takes a soft page fault on FIRST TOUCH of each 4 KB — a cost charged
546/// to whatever per-macroblock stage happens to touch it first, not to the
547/// allocation. Handing the same buffers back keeps the pages mapped and warm.
548///
549/// The initialising fill is NOT skipped: these grids are read as neighbour
550/// context (`modes_y` must read 2/DC, `ref_idx_y` must read -1) before every
551/// block that writes them, so a stale value from the previous picture is a
552/// correctness bug, not a performance trade. `clear()` + `resize()` keeps the
553/// fill and drops only the allocation.
554///
555/// The reconstruction planes are deliberately NOT pooled: `into_frame` MOVES
556/// them out as the caller's output frame, so there is nothing to hand back.
557#[derive(Default)]
558pub struct GridPool {
559 /// Deferred-job boxes carried across pictures (see `take_pinter_job`).
560 job_pool: Vec<Box<PInterJob>>,
561 nores_pool: Vec<Box<PInterNoResJob>>,
562 /// D12: running bits-per-macroblock of decoded slices, the E2 dispatch's
563 /// density signal. Lives here because `GridPool` is the only state that
564 /// survives a picture (`FrameDecoder` is rebuilt per picture).
565 bits_per_mb: f64,
566 mb_qp: Vec<u8>,
567 bs_frame: Vec<rusty_h264_common::deblock::MbBs>,
568 pk_prev: Vec<rusty_h264_common::deblock::MbPack>,
569 pk_cur: Vec<rusty_h264_common::deblock::MbPack>,
570 nnz_dbr: Vec<u8>,
571 bak_y: Vec<u8>,
572 bak_u: Vec<u8>,
573 bak_v: Vec<u8>,
574 nnz_y: Vec<u8>,
575 nnz_c0: Vec<u8>,
576 nnz_c1: Vec<u8>,
577 modes_y: Vec<u8>,
578 coded_y: Vec<bool>,
579 mv_y: Vec<(i32, i32)>,
580 inter_y: Vec<bool>,
581 ref_idx_y: Vec<i32>,
582 mv1: Vec<(i32, i32)>,
583 ref_idx1: Vec<i32>,
584 mb_t8x8: Vec<bool>,
585 mb_kind: Vec<u8>,
586 bzero: Vec<bool>,
587 // CABAC slice scratch (D13 follow-through): these were fresh `vec![..]`
588 // allocations at EVERY slice entry — ~407 KB per P slice, ~700 KB per B
589 // slice at 720p, the same fresh-page class GridPool exists to kill.
590 sc_cat: Vec<u8>,
591 sc_cbp: Vec<u8>,
592 sc_cmode: Vec<i32>,
593 sc_nzc: Vec<[u8; 24]>,
594 sc_cbfdc: Vec<u16>,
595 sc_skip: Vec<bool>,
596 sc_ref: Vec<[i8; 16]>,
597 sc_mvd: Vec<[[i16; 2]; 16]>,
598 sc_ref1: Vec<[i8; 16]>,
599 sc_mvd1: Vec<[[i16; 2]; 16]>,
600 sc_direct: Vec<bool>,
601 // D24 (inline-execution.md 11.10): the ref-POC mirrors were the only
602 // per-picture Vecs NOT recycled - a fresh alloc + collect per picture each.
603 ref_poc0: Vec<i32>,
604 ref_poc1: Vec<i32>,
605}
606
607/// Reuse `v`'s allocation for `n` copies of `val`. Identical OBSERVABLE result to
608/// `vec![val; n]`; differs only in that it reuses the existing allocation when the
609/// capacity already suffices.
610#[inline]
611fn refill<T: Clone>(mut v: Vec<T>, n: usize, val: T) -> Vec<T> {
612 v.clear();
613 v.resize(n, val);
614 v
615}
616
617impl FrameDecoder {
618 /// Non-pooled ctor — tests only; production always enters via `with_pool`
619 /// (`GridPool` recycling, see lib.rs).
620 #[cfg(test)]
621 pub fn new(
622 mb_w: usize,
623 mb_h: usize,
624 qp: u8,
625 chroma_qp_offset: i32,
626 refs: Vec<crate::Ref>,
627 num_ref_active: usize,
628 constrained_intra: bool,
629 transform_8x8_mode: bool,
630 b_possible: bool,
631 ) -> Self {
632 Self::with_pool(
633 mb_w,
634 mb_h,
635 qp,
636 chroma_qp_offset,
637 refs,
638 num_ref_active,
639 constrained_intra,
640 transform_8x8_mode,
641 b_possible,
642 GridPool::default(),
643 )
644 }
645
646 /// As `new`, but reusing a previous picture's grid allocations. See `GridPool`.
647 #[allow(clippy::too_many_arguments)]
648 pub fn with_pool(
649 mb_w: usize,
650 mb_h: usize,
651 qp: u8,
652 chroma_qp_offset: i32,
653 refs: Vec<crate::Ref>,
654 num_ref_active: usize,
655 constrained_intra: bool,
656 transform_8x8_mode: bool,
657 b_possible: bool,
658 pool: GridPool,
659 ) -> Self {
660 let (cw, ch) = (mb_w * 16, mb_h * 16);
661 let (ccw, cch) = (cw / 2, ch / 2);
662 let bits_per_mb = pool.bits_per_mb;
663 let ref_poc0 = {
664 let mut v = pool.ref_poc0;
665 v.clear();
666 v.extend(refs.iter().map(|f| f.pic_poc()));
667 v
668 };
669 Self {
670 mb_w,
671 mb_h,
672 qp,
673 cur_qp: qp,
674 chroma_qp_offset,
675 cw,
676 ch,
677 ccw,
678 cch,
679 rec_y: vec![0; cw * ch],
680 rec_u: vec![0; ccw * cch],
681 rec_v: vec![0; ccw * cch],
682 mb_qp: refill(pool.mb_qp, mb_w * mb_h, qp),
683 slice_first_mb: 0,
684 nnz_y: refill(pool.nnz_y, (mb_w * 4) * (mb_h * 4), 0),
685 nnz_c: [
686 refill(pool.nnz_c0, (mb_w * 2) * (mb_h * 2), 0),
687 refill(pool.nnz_c1, (mb_w * 2) * (mb_h * 2), 0),
688 ],
689 modes_y: refill(pool.modes_y, (mb_w * 4) * (mb_h * 4), 2),
690 coded_y: refill(pool.coded_y, (mb_w * 4) * (mb_h * 4), false),
691 mv_y: refill(pool.mv_y, (mb_w * 4) * (mb_h * 4), (0, 0)),
692 inter_y: refill(pool.inter_y, (mb_w * 4) * (mb_h * 4), false),
693 ref_idx_y: refill(pool.ref_idx_y, (mb_w * 4) * (mb_h * 4), -1),
694 mv1: refill(pool.mv1, (mb_w * 4) * (mb_h * 4), (0, 0)),
695 ref_idx1: refill(pool.ref_idx1, (mb_w * 4) * (mb_h * 4), -1),
696 refs1: Vec::new(),
697 num_ref_active1: 0,
698 is_b: false,
699 b_possible,
700 direct_spatial: true,
701 nnz_l_cache: [0x80; 25],
702 nnz_c_cache: [[0x80; 9]; 2],
703 refs,
704 num_ref_active,
705 constrained_intra,
706 scaling: None,
707 scaling8: None,
708 transform_8x8_mode,
709 transform_bypass: false,
710 route_skip_mbs: 0,
711 route_coded_mbs: 0,
712 slice_bounds: Vec::new(),
713 cur_idc2: false,
714 mb_t8x8: refill(pool.mb_t8x8, mb_w * mb_h, false),
715 bs_frame: refill(pool.bs_frame, mb_w * mb_h, Default::default()),
716 bs_rows: 0,
717 flt_rows: 0,
718 pk_prev: {
719 let mut v = pool.pk_prev;
720 v.clear();
721 v
722 },
723 pk_cur: {
724 let mut v = pool.pk_cur;
725 v.clear();
726 v
727 },
728 nnz_dbr: refill(pool.nnz_dbr, (mb_w * 4) * (mb_h * 4), 0),
729 bak_y: refill(pool.bak_y, cw, 0),
730 bak_u: refill(pool.bak_u, ccw, 0),
731 bak_v: refill(pool.bak_v, ccw, 0),
732 edc_jobs: Vec::new(),
733 edc_job_pool: pool.job_pool,
734 edc_nores_pool: pool.nores_pool,
735 scratch_luma: None,
736 scratch_luma8: None,
737 scratch_cac: None,
738 edc_active: false,
739 edc_tx: None,
740 edc_ctx_rx: None,
741 edc_back_tx: None,
742 edc_parked: None,
743 edc_regions: None,
744 bsk_last: None,
745 edc_batch: Vec::new(),
746 bits_per_mb,
747 db_ena: false,
748 db_oa: 0,
749 db_ob: 0,
750 mb_kind: refill(
751 pool.mb_kind,
752 mb_w * mb_h,
753 rusty_h264_common::deblock::MB_KIND_UNSET,
754 ),
755 weights: None,
756 row_wait: crate::row_progress_on(),
757 col_ok: false,
758 col_w4: 0,
759 any_t8: false,
760 any_idc2: false,
761 skip_zero_next: usize::MAX,
762 bzero: refill(pool.bzero, mb_w * mb_h, false),
763 sc_cat: pool.sc_cat,
764 sc_cbp: pool.sc_cbp,
765 sc_cmode: pool.sc_cmode,
766 sc_nzc: pool.sc_nzc,
767 sc_cbfdc: pool.sc_cbfdc,
768 sc_skip: pool.sc_skip,
769 sc_ref: pool.sc_ref,
770 sc_mvd: pool.sc_mvd,
771 sc_ref1: pool.sc_ref1,
772 sc_mvd1: pool.sc_mvd1,
773 sc_direct: pool.sc_direct,
774 bzspan: None,
775 pzspan: None,
776 iw00: None,
777 weights_id0: false,
778 weights_l0id: false,
779 cur_poc: 0,
780 weighted_bipred_idc: 0,
781 direct_8x8_inference: false,
782 progress: None,
783 ref_poc0,
784 ref_poc1: {
785 let mut v = pool.ref_poc1;
786 v.clear();
787 v
788 },
789 }
790 }
791
792 fn refresh_ref_pocs(&mut self) {
793 self.ref_poc0.clear();
794 self.ref_poc0.extend(self.refs.iter().map(|f| f.pic_poc()));
795 self.ref_poc1.clear();
796 self.ref_poc1.extend(self.refs1.iter().map(|f| f.pic_poc()));
797 }
798
799 /// Frame-MT Phase B: attach the shared progress Arc for row watermarks.
800 pub fn set_progress_slot(&mut self, slot: crate::Ref) {
801 self.progress = Some(slot);
802 }
803
804 /// Wait until every L0/L1 ref has enough ready luma rows for MB row `mb_y`
805 /// (pad conservatively for MV overshoot).
806 #[inline(always)]
807 fn wait_refs_for_mb(&self, mb_y: usize) {
808 // Cached at construction: in 1T (row-progress off, the default) this
809 // is one field test instead of a fn call + OnceLock read per MB.
810 if !self.row_wait {
811 return;
812 }
813 let need = crate::RefFrame::rows_needed_for_mb(mb_y, self.ch);
814 crate::RefFrame::set_mc_row_need(mb_y, self.ch);
815 for r in self.refs.iter().chain(self.refs1.iter()) {
816 // Only pay the wait when the ref may still be in-flight (Phase B).
817 if r.live.is_some() && r.frozen.get().is_none() {
818 r.wait_ready_rows(need);
819 }
820 }
821 }
822
823 fn publish_progress(&mut self) {
824 let Some(slot) = &self.progress else {
825 return;
826 };
827 // Under EDC the worker publishes (PixelCtx::publish_progress_rows).
828 if self.edc_tx.is_some() {
829 return;
830 }
831 // Cached field, not the knob function: this runs per ROW.
832 if !rowdb_on() || !self.db_ena || self.flt_rows == 0 {
833 return;
834 }
835 publish_filtered_rows_to_slot(
836 slot,
837 &self.rec_y,
838 &self.rec_u,
839 &self.rec_v,
840 self.cw,
841 self.ccw,
842 self.ch,
843 self.flt_rows,
844 );
845 }
846
847 /// Sets the explicit weighted-prediction tables for this slice.
848 pub fn set_weights(&mut self, weights: WeightTable) {
849 self.weights_id0 = weights.ref0_identity();
850 self.weights_l0id = weights.list_identity(0);
851 self.weights = Some(weights);
852 }
853
854 /// Applies explicit uni-prediction weighting to a motion-compensated partition
855 /// (luma `pred_y` region + the two chroma planes), if weighting is active.
856 /// `list` is the reference list and `refi` the partition's reference index.
857 fn weight_partition(
858 &self,
859 pred_y: &mut [u8; 256],
860 c_pred: &mut [[u8; 64]; 2],
861 list: usize,
862 refi: usize,
863 rx: usize,
864 ry: usize,
865 rw: usize,
866 rh: usize,
867 ) {
868 let Some(wt) = &self.weights else { return };
869 // Identity list-0 table (x264 weightp=2 outside fades): the whole pass
870 // is an exact per-pixel no-op — skip it. RS_H264_NO_SKIPFP restores it
871 // for paired A/B (same knob family as the P_Skip weight skip).
872 if list == 0 && self.weights_l0id && !no_skipfp() {
873 edcstat::bump(&edcstat::WP_SKIPPED, 1);
874 return;
875 }
876 // REFUTED, reverted: row-slicing these loops measured +28% instructions
877 // on `weight_partition` (the extents are runtime values, so the slice
878 // bounds cost more than the per-sample checks they replaced). The real
879 // win for this function was hoisting the identity test to its CALLERS,
880 // which stands.
881 // RESOLVE ONCE, APPLY MANY. `apply_luma` re-read `self.luma[list][refi]`
882 // — an array index, a Vec deref and an element index, two of them bounds
883 // checked — on EVERY sample, up to 256 luma plus 128 chroma per call.
884 // The weight is a property of the partition, not of the pixel. (The loop
885 // SHAPE is untouched: row-slicing it is refuted above.)
886 // MASKED, not row-sliced. `pred_y` is a fixed `[u8; 256]` and `c_pred` a
887 // `[[u8; 64]; 2]`, so `& 255` / `& 63` are no-ops that prove the index
888 // outright — where SLICING these loops cost +28% (the extents are runtime
889 // values, so the slice bounds outweigh the checks). Same lesson as
890 // `luma_centre`: the refutation was of one SHAPE, not of the goal.
891 let (lw, lo) = wt.luma_wo(list, refi);
892 weight_block(pred_y, 16, rx, ry, rw, rh, lw, lo, wt.luma_log2_denom);
893 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
894 for cc in 0..2 {
895 let (cw, co) = wt.chroma_wo(list, refi, cc);
896 weight_block(
897 &mut c_pred[cc & 1],
898 8,
899 crx,
900 cry,
901 crw,
902 crh,
903 cw,
904 co,
905 wt.chroma_log2_denom,
906 );
907 }
908 }
909
910 /// Sets the High-profile scaling matrices (raster order: six 4×4 lists, two
911 /// 8×8 luma lists). The caller un-zig-zags the SPS lists. Flat is the default.
912 /// GATE 1 router counters: (skip MBs, residual-coded MBs) this picture.
913 pub fn route_counters(&self) -> (u32, u32) {
914 (self.route_skip_mbs, self.route_coded_mbs)
915 }
916
917 /// SPS `qpprime_y_zero_transform_bypass_flag` — see [`Self::step_qp`].
918 pub fn set_transform_bypass(&mut self, on: bool) {
919 self.transform_bypass = on;
920 }
921
922 pub fn set_scaling(&mut self, scaling: [[i32; 16]; 6], scaling8: [[i32; 64]; 2]) {
923 self.scaling = Some(scaling);
924 self.scaling8 = Some(scaling8);
925 }
926
927 /// Dequantizes a 4×4 AC block with scaling list `list` (flat if none active).
928 /// Per-(qp, list) dequant constants for the fused scan-order kernel: the flat
929 /// table at zero cost, or the scaling-list build once per macroblock.
930 #[inline]
931 fn dq_const(&self, qp: u8, list: usize) -> DequantQp {
932 match &self.scaling {
933 Some(s) => DequantQp::weighted(qp, &s[list]),
934 None => DQ_FLAT[(qp as usize).min(51)],
935 }
936 }
937
938 /// Single-coefficient twin of `dequant` for position 0 (DC-only fast path).
939 fn dequant_dc4(&self, level: i32, qp: u8, list: usize) -> i32 {
940 rusty_h264_common::transform::dequantize_dc4(
941 level,
942 qp,
943 self.scaling.as_ref().map(|s| s[list][0]),
944 )
945 }
946
947 /// Inverse-quantizes a chroma DC block with scaling list `list`'s DC weight.
948 fn dequant_chroma_dc(&self, levels: &[i32; 4], qp: u8, list: usize) -> [i32; 4] {
949 match &self.scaling {
950 Some(s) => inverse_quant_chroma_dc_weighted(levels, qp, s[list][0]),
951 None => inverse_quant_chroma_dc(levels, qp),
952 }
953 }
954
955 /// Sets the B-slice context for the slice about to be decoded: `RefPicList1`,
956 /// its active count, and the direct-mode flag.
957 #[allow(clippy::too_many_arguments)]
958 pub fn set_b_context(
959 &mut self,
960 refs1: Vec<crate::Ref>,
961 num_ref_active1: usize,
962 direct_spatial: bool,
963 cur_poc: i32,
964 weighted_bipred_idc: u8,
965 direct_8x8_inference: bool,
966 ) {
967 self.is_b = true;
968 self.refs1 = refs1;
969 self.num_ref_active1 = num_ref_active1;
970 self.direct_spatial = direct_spatial;
971 self.cur_poc = cur_poc;
972 self.weighted_bipred_idc = weighted_bipred_idc;
973 self.direct_8x8_inference = direct_8x8_inference;
974 self.iw00 = None; // slice-scoped cache: lists/POC may have changed
975 // Colocated fast-probe cache (see col_zero / col_zero_fast).
976 self.col_ok = self
977 .refs1
978 .first()
979 .is_some_and(|c| c.live.is_none() && !c.long_term && c.w4 != 0);
980 self.col_w4 = self.refs1.first().map_or(0, |c| c.w4);
981 self.refresh_ref_pocs();
982 }
983
984 /// Cached `implicit_weights(0, 0)` — constant within a slice.
985 fn iw00(&mut self) -> Option<(i32, i32)> {
986 // Read the discriminant ONCE. The `is_none()` + `unwrap()` pair tested it
987 // twice and carried an `unwrap_failed` path for a value this very function
988 // had just written. `get_or_insert_with` cannot be used here: the closure
989 // would need `&mut self` while the `Option` is already borrowed.
990 match self.iw00 {
991 Some(v) => v,
992 None => {
993 let v = if self.refs.is_empty() || self.refs1.is_empty() {
994 None
995 } else {
996 self.implicit_weights(0, 0)
997 };
998 self.iw00 = Some(v);
999 v
1000 }
1001 }
1002 }
1003
1004 /// Steps the running luma QP by a `mb_qp_delta` (spec §7.4.5, 8-bit depth):
1005 /// `QPy = (QPy_prev + delta + 52) % 52`.
1006 /// Steps QPy by `mb_qp_delta` (spec §7.4.5 modulo). Called exactly for
1007 /// residual-coded macroblocks (mb_qp_delta presence ⇔ cbp != 0 or I_16x16),
1008 /// which makes it the one chokepoint for the transform-bypass refusal:
1009 /// with `qpprime_y_zero_transform_bypass_flag` set and QP'Y == 0 the
1010 /// residual is LOSSLESS-bypassed (no transform, no quant — and DPCM intra
1011 /// forms), which this decoder does not implement. Refusing here is loud
1012 /// and exact: all-PCM lossless streams (no mb_qp_delta) still decode.
1013 fn step_qp(&mut self, delta: i32) -> Result<(), MbError> {
1014 // Router counter: step_qp fires exactly once per residual-coded MB.
1015 self.route_coded_mbs += 1;
1016 self.cur_qp = (self.cur_qp as i32 + delta + 52).rem_euclid(52) as u8;
1017 if self.transform_bypass && self.cur_qp == 0 {
1018 return Err(MbError::Unsupported(
1019 "transform-bypass (lossless) macroblock",
1020 ));
1021 }
1022 Ok(())
1023 }
1024
1025 /// Maps a luma QP to its chroma QP, applying `chroma_qp_index_offset`
1026 /// (spec §8.5.8): `QPc = qpc_table(Clip3(0, 51, QPy + offset))`.
1027 fn chroma_qp_for(&self, qp_y: u8) -> u8 {
1028 let qpi = (qp_y as i32 + self.chroma_qp_offset).clamp(0, 51) as u8;
1029 chroma_qp(qpi)
1030 }
1031
1032 /// Resets per-slice state before decoding a continuation slice of the same
1033 /// picture: the running QP (each slice carries its own `slice_qp`) and the
1034 /// reference list (each slice may reorder it).
1035 pub fn begin_slice(&mut self, slice_qp: u8, refs: Vec<crate::Ref>, num_ref_active: usize) {
1036 self.cur_qp = slice_qp;
1037 self.qp = slice_qp;
1038 self.refs = refs;
1039 self.num_ref_active = num_ref_active;
1040 self.weights = None; // re-set per slice if a pred_weight_table is present
1041 self.weights_id0 = false;
1042 self.weights_l0id = false;
1043 self.refresh_ref_pocs();
1044 }
1045
1046 /// Whether the neighbor macroblock at `(nbx, nby)` is in the slice currently
1047 /// being decoded (address ≥ the slice's first MB). For single-slice pictures
1048 /// `slice_first_mb == 0`, so this is always true and prediction is unchanged.
1049 #[inline]
1050 fn nbr_in_slice(&self, nbx: usize, nby: usize) -> bool {
1051 nby * self.mb_w + nbx >= self.slice_first_mb
1052 }
1053
1054 /// Whether the neighbor 4×4 block at `(nbx, nby)` may contribute to intra
1055 /// prediction. With `constrained_intra_pred`, an inter-coded neighbor is
1056 /// treated as unavailable (spec §8.3.1.2.{1,2}); otherwise always usable.
1057 #[inline]
1058 fn intra_nbr_ok(&self, nbx: usize, nby: usize) -> bool {
1059 // Fallible read: this is inlined at eleven sites across the intra paths,
1060 // and `constrained_intra` short-circuits it on nearly every stream, so
1061 // the check was pure tax on a value that is usually never loaded.
1062 // `unwrap_or(true)` is the conservative direction — an out-of-range
1063 // neighbour reads as inter, i.e. UNAVAILABLE, which is what the
1064 // constrained-intra rule means by a neighbour it cannot use.
1065 !self.constrained_intra
1066 || !self
1067 .inter_y
1068 .get(nby * (self.mb_w * 4) + nbx)
1069 .copied()
1070 .unwrap_or(true)
1071 }
1072
1073 fn mv_neighbors(&self, mb_x: usize, mb_y: usize) -> [MvNeighbor; 3] {
1074 let w4 = self.mb_w * 4;
1075 let get = |avail: bool, bx: isize, by: isize| {
1076 if avail {
1077 let idx = by as usize * w4 + bx as usize;
1078 match (self.mv_y.get(idx), self.ref_idx_y.get(idx)) {
1079 (Some(&m), Some(&r)) => MvNeighbor {
1080 available: true,
1081 mv: m,
1082 ref_idx: r,
1083 },
1084 _ => MvNeighbor::NONE,
1085 }
1086 } else {
1087 MvNeighbor::NONE
1088 }
1089 };
1090 let (bx, by) = (mb_x as isize * 4, mb_y as isize * 4);
1091 let a = get(mb_x > 0 && self.nbr_in_slice(mb_x - 1, mb_y), bx - 1, by);
1092 let b = get(mb_y > 0 && self.nbr_in_slice(mb_x, mb_y - 1), bx, by - 1);
1093 let c = if mb_y > 0 && mb_x + 1 < self.mb_w && self.nbr_in_slice(mb_x + 1, mb_y - 1) {
1094 get(true, bx + 4, by - 1)
1095 } else {
1096 get(
1097 mb_x > 0 && mb_y > 0 && self.nbr_in_slice(mb_x - 1, mb_y - 1),
1098 bx - 1,
1099 by - 1,
1100 )
1101 };
1102 [a, b, c]
1103 }
1104
1105 fn mv_neighbors_block(&self, pbx: isize, pby: isize, pwb: isize) -> [MvNeighbor; 3] {
1106 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Neighbors);
1107 self.mv_neighbors_block_grid(pbx, pby, pwb, 0)
1108 }
1109
1110 fn skip_mv(&self, mb_x: usize, mb_y: usize) -> (i32, i32) {
1111 let [a, b, c] = self.mv_neighbors(mb_x, mb_y);
1112 if !a.available
1113 || !b.available
1114 || (a.ref_idx == 0 && a.mv == (0, 0))
1115 || (b.ref_idx == 0 && b.mv == (0, 0))
1116 {
1117 (0, 0)
1118 } else {
1119 predict_mv(a, b, c, 0)
1120 }
1121 }
1122
1123 fn set_mb_mv(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32), inter: bool, refi: i32) {
1124 // ROW FILLS. This wrote all sixteen cells of three grids one indexed
1125 // store at a time - FORTY-EIGHT separate bounds checks per call - and
1126 // re-evaluated `if inter` inside the inner loop. Each macroblock row is
1127 // four CONTIGUOUS cells, so twelve `fill`s cover it.
1128 let w4 = self.mb_w * 4;
1129 let r = if inter { refi } else { -1 };
1130 for dy in 0..4 {
1131 let a = (mb_y * 4 + dy) * w4 + mb_x * 4;
1132 self.mv_y[a..a + 4].fill(mv);
1133 self.inter_y[a..a + 4].fill(inter);
1134 self.ref_idx_y[a..a + 4].fill(r);
1135 }
1136 }
1137
1138 /// Commit one inter partition's motion into the 4×4 grid (ref 0, 1-ref P).
1139 /// `(rx,ry,rw,rh)` are MB-relative luma pixels; committing before the next
1140 /// partition's prediction is what lets a later partition predict from it.
1141 fn commit_inter_grid(
1142 &mut self,
1143 mb_x: usize,
1144 mb_y: usize,
1145 rx: usize,
1146 ry: usize,
1147 rw: usize,
1148 rh: usize,
1149 mv: (i32, i32),
1150 refi: i8,
1151 ) {
1152 // Row-range fills instead of the per-4x4 scatter: partitions are
1153 // rectangles, so each grid row is one contiguous run (same values).
1154 let w4 = self.mb_w * 4;
1155 let (bx0, bw) = (mb_x * 4 + rx / 4, rw / 4);
1156 for by in ry / 4..ry / 4 + rh / 4 {
1157 let a = (mb_y * 4 + by) * w4 + bx0;
1158 self.mv_y[a..a + bw].fill(mv);
1159 self.inter_y[a..a + bw].fill(true);
1160 self.ref_idx_y[a..a + bw].fill(refi as i32);
1161 self.coded_y[a..a + bw].fill(true);
1162 }
1163 }
1164
1165 /// Per-slice deblock parameters, needed DURING decode by the row-interleave
1166 /// path. `ena` is already resolved against `RFF_ABL_DEBLOCK` by the caller.
1167 pub fn set_deblock_params(&mut self, ena: bool, oa: i32, ob: i32, idc2: bool) {
1168 // Latch: the FIRST disabling slice turns row filtering off for the rest
1169 // of the picture (see `row_hook`); rows already filtered stay counted
1170 // in `flt_rows` and the picture-end tail handles the remainder.
1171 self.db_ena = ena && (self.flt_rows == 0 || self.db_ena);
1172 self.db_oa = oa;
1173 self.db_ob = ob;
1174 self.cur_idc2 = idc2;
1175 }
1176
1177 /// Slice index owning `addr` (slices are raster-contiguous, bounds ascending).
1178 fn slice_of(&self, addr: usize) -> usize {
1179 match self.slice_bounds.binary_search_by(|&(f, _)| f.cmp(&addr)) {
1180 Ok(i) => i,
1181 Err(i) => i - 1,
1182 }
1183 }
1184
1185 /// Derives bS for macroblock row `r` from the just-decoded (hot) grids into
1186 /// `bs_frame`, maintaining the two-row rolling record window (R2 of
1187 /// docs/row-interleave-plan.md).
1188 fn derive_bs_row(&mut self, r: usize) {
1189 // bS derivation reads the motion/nnz grids — flush deferred spans.
1190 self.span_flush();
1191 // Same stage label the in-filter derivation used, so profiles keep
1192 // pricing bS derivation wherever it lives.
1193 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DebDerive);
1194 use rusty_h264_common::deblock::{
1195 derive_mb_kind, derive_mb_records, pack_mb, BlockInfo, MbBs, MbKind,
1196 };
1197 let (mb_w, w4) = (self.mb_w, self.mb_w * 4);
1198 edcstat::bump(&edcstat::DBS_ROWS, 1);
1199 // Transform-block coded mask for this row: raw nnz, then the 8x8 OR for
1200 // t8 macroblocks (spec §8.7: the 8x8 transform's coded status is per 8x8).
1201 //
1202 // T8 GATE. `nnz_dbr` exists ONLY to carry that OR. With no 8x8
1203 // macroblock anywhere in the picture it is a byte-for-byte copy of
1204 // `nnz_y`, so both the 4-row copy and the per-row scan below are pure
1205 // work: the derivation reads `nnz_y` directly instead. Census
1206 // (DBSDERIVE) measured t8mb=0 on EVERY cavlc and main stream while
1207 // nnz_rowcopy ran 10800-16320 sub-rows per decode.
1208 if self.any_t8 {
1209 for br in r * 4..r * 4 + 4 {
1210 let a = br * w4;
1211 self.nnz_dbr[a..a + w4].copy_from_slice(&self.nnz_y[a..a + w4]);
1212 }
1213 edcstat::bump(&edcstat::DBS_NNZ_ROWCOPY, 4);
1214 // WHOLE-MACROBLOCK ROW SLICES. The 2x2-of-2x2 nest did sixteen
1215 // bounds-checked reads and sixteen bounds-checked writes per t8
1216 // macroblock; the same cells are four contiguous runs of four, so
1217 // four slice reads and four slice writes cover them. The OR is over
1218 // bytes, so `a | b | c | d > 0` is exactly the old per-cell `> 0`.
1219 let t8_row_pre = &self.mb_t8x8[r * mb_w..][..mb_w];
1220 for mb_x in 0..mb_w {
1221 if !t8_row_pre[mb_x] {
1222 continue;
1223 }
1224 edcstat::bump(&edcstat::DBS_T8MB, 1);
1225 let base = r * 4 * w4 + mb_x * 4;
1226 let mut src = [[0u8; 4]; 4];
1227 for (k, row) in src.iter_mut().enumerate() {
1228 row.copy_from_slice(&self.nnz_y[base + k * w4..][..4]);
1229 }
1230 let mut out = [[0u8; 4]; 4];
1231 for b8 in 0..4usize {
1232 let (cx, cy) = ((b8 % 2) * 2, (b8 / 2) * 2);
1233 let any =
1234 (src[cy][cx] | src[cy][cx + 1] | src[cy + 1][cx] | src[cy + 1][cx + 1]) > 0;
1235 for sy in 0..2 {
1236 for sx in 0..2 {
1237 out[cy + sy][cx + sx] = any as u8;
1238 }
1239 }
1240 }
1241 for (k, row) in out.iter().enumerate() {
1242 self.nnz_dbr[base + k * w4..][..4].copy_from_slice(row);
1243 }
1244 }
1245 }
1246 let info = BlockInfo {
1247 inter: &self.inter_y,
1248 nnz: if self.any_t8 {
1249 &self.nnz_dbr
1250 } else {
1251 &self.nnz_y
1252 },
1253 mv: &self.mv_y,
1254 ref_id: &self.ref_idx_y,
1255 mv1: &self.mv1,
1256 ref_id1: if self.ref_poc1.is_empty() {
1257 &[]
1258 } else {
1259 &self.ref_idx1
1260 },
1261 w4,
1262 t8x8: &self.mb_t8x8,
1263 bs: &[],
1264 poc0: &self.ref_poc0,
1265 poc1: &self.ref_poc1,
1266 kind: &self.mb_kind,
1267 };
1268 let has1 = !info.ref_id1.is_empty();
1269 core::mem::swap(&mut self.pk_prev, &mut self.pk_cur);
1270 self.pk_cur.clear();
1271 // WIN: read the knob at the USE SITE, not through the struct field.
1272 // is under cfg(not(knobs)), so LLVM folds
1273 // this to a constant and the kind-loads arm below becomes dead -- which
1274 // is what finally lets (1543 instrs of Skip/InterUniform/
1275 // Inter gathering the decoder never runs) leave the decode binary.
1276 // Storing a build-time-constant knob in a FIELD erases the constant:
1277 // the field is a runtime value and every arm it guards stays live.
1278 let kl = kind_loads();
1279 // Census gate resolved ONCE per row: `edcstat::on()` is a relaxed atomic
1280 // load and LLVM will not CSE atomic loads, so a per-MB bump costs a load
1281 // each. Same rule as hoisting an A/B arm selector out of the loop under
1282 // test (codec-measurement 15).
1283 let stats = edcstat::on();
1284 // ROW SLICES for the three per-macroblock grids. Each was indexed
1285 // `r * mb_w + mb_x` against a whole-FRAME Vec, which the compiler
1286 // cannot prove in range; sliced to THIS row, `[mb_x]` is provably
1287 // `< mb_w`. These are disjoint FIELDS of `self`, so the `&mut` on
1288 // `bs_frame` coexists with the `&` borrows `info` holds.
1289 let row0 = r * mb_w;
1290 let bs_row = &mut self.bs_frame[row0..][..mb_w];
1291 let t8_row = &self.mb_t8x8[row0..][..mb_w];
1292 let kind_row: &[u8] = if self.mb_kind.is_empty() {
1293 &[]
1294 } else {
1295 &self.mb_kind[row0..][..mb_w]
1296 };
1297 for mb_x in 0..mb_w {
1298 // Always pack: UNSET / Inter neighbours in this row and the next
1299 // read left/top MbPack. Kind stores MbBs directly (no i32 hop).
1300 self.pk_cur.push(pack_mb(&info, has1, mb_x, r));
1301 if stats {
1302 edcstat::bump(&edcstat::DBS_MB, 1);
1303 }
1304 // KIND ECONOMICS ON THE PACKED PATH: derive_mb_kind(Skip) does 9
1305 // fresh strided Blk::loads per MB, but pack_mb already ran for this
1306 // MB (the line above) — the packed `_` arm derives from those
1307 // records with no further gathers. A B_Skip kind experiment
1308 // measured kind-arm 1.8% SLOWER (z=-2.69) with +1.07M loads, so
1309 // Skip/InterUniform route to the packed arm; Intra keeps the kind
1310 // arm (pure constants, no loads). `RS_H264_KIND_LOADS=1` restores
1311 // the old routing for paired A/B.
1312 match kind_row.get(mb_x).copied().and_then(MbKind::from_u8) {
1313 Some(MbKind::Intra) => {
1314 if stats {
1315 edcstat::bump(&edcstat::DBS_INTRA, 1);
1316 }
1317 // WIN: intra strengths are a constant table keyed on availability;
1318 // asking derive_mb_kind for them dragged its whole gathering body
1319 // (Skip/InterUniform/Inter, 1654 instrs) into the decode binary,
1320 // even though the only other arm that calls it is guarded by a
1321 // build-time false knob.
1322 bs_row[mb_x] = rusty_h264_common::deblock::intra_mb_bs(mb_x, r);
1323 }
1324 Some(k @ (MbKind::Skip | MbKind::InterUniform)) if kl => {
1325 if stats {
1326 edcstat::bump(&edcstat::DBS_KINDARM, 1);
1327 }
1328 bs_row[mb_x] = derive_mb_kind(&info, mb_x, r, k);
1329 }
1330 _ => {
1331 let Some(cur) = self.pk_cur.get(mb_x) else {
1332 continue;
1333 };
1334 let left = if mb_x > 0 {
1335 self.pk_cur.get(mb_x - 1)
1336 } else {
1337 None
1338 };
1339 let top = if r > 0 { self.pk_prev.get(mb_x) } else { None };
1340 let mb_t8 = t8_row[mb_x];
1341 let (mut bv, mut bh) = ([[0i32; 4]; 4], [[0i32; 4]; 4]);
1342 rusty_h264_common::deblock::census_note_packed();
1343 let flat = derive_mb_records(cur, left, top, mb_t8, &mut bv, &mut bh);
1344 if stats {
1345 edcstat::bump(&edcstat::DBS_PACKED, 1);
1346 }
1347 // MEASUREMENT ONLY: sizes the `kind_loads()` match guard that
1348 // used to be a OnceLock deref here (now `k_kindloads`).
1349 if stats
1350 && matches!(
1351 // WIN: kind_row is already this row of mb_kind, proven in range
1352 // above, so [mb_x] needs no whole-frame bounds check. Identical
1353 // when mb_kind is empty: kind_row is &[] and both answer None.
1354 kind_row.get(mb_x).copied().and_then(MbKind::from_u8),
1355 Some(MbKind::Skip | MbKind::InterUniform)
1356 )
1357 {
1358 edcstat::bump(&edcstat::DBS_KINDGUARD, 1);
1359 }
1360 // FLAT-AWARE NARROWING. `derive_mb_records` returns early on a
1361 // flat inter macroblock having written ONLY edge 0 of each
1362 // orientation; edges 1..4 are still the caller's zero-init, so
1363 // widening all 32 entries copies 24 known zeros onto 24 known
1364 // zeros - after a `MbBs::default()` that zeroed them a third
1365 // time. Census DBSDERIVE flat: 96.8% screen_text, 90.2%
1366 // FourPeople, 82.1% akiyo - the dominant class.
1367 let m = if flat {
1368 if stats {
1369 edcstat::bump(&edcstat::DBS_FLAT, 1);
1370 }
1371 debug_assert!(bv[1..] == [[0i32; 4]; 3] && bh[1..] == [[0i32; 4]; 3]);
1372 let mut m = MbBs::default();
1373 m.v[0] = core::array::from_fn(|sg| bv[0][sg] as u8);
1374 m.h[0] = core::array::from_fn(|sg| bh[0][sg] as u8);
1375 m
1376 } else {
1377 // Written once, not zeroed by `default()` and then written.
1378 MbBs {
1379 v: core::array::from_fn(|e| core::array::from_fn(|sg| bv[e][sg] as u8)),
1380 h: core::array::from_fn(|e| core::array::from_fn(|sg| bh[e][sg] as u8)),
1381 }
1382 };
1383 // MEASUREMENT ONLY: `on()` first, so the 32-byte compare
1384 // never runs in a shipped decode (it is not otherwise needed
1385 // here — the consumer in filter_frame_rows makes it).
1386 if stats && m.v == [[0u8; 4]; 4] && m.h == [[0u8; 4]; 4] {
1387 edcstat::bump(&edcstat::DBS_ALLZERO, 1);
1388 }
1389 bs_row[mb_x] = m;
1390 }
1391 }
1392 }
1393 // disable_deblocking_filter_idc == 2 (spec §7.4.3): a slice may forbid
1394 // filtering ITS macroblocks' edges against OTHER slices. bS = 0 on the
1395 // crossing MB edges kills exactly those filters; interior edges keep
1396 // their derived strengths. Guarded so single-slice / idc 0-1 pictures
1397 // pay one branch per row.
1398 if self.any_idc2 && self.slice_bounds.len() > 1 {
1399 edcstat::bump(&edcstat::DBS_IDC2ROW, 1);
1400 for mb_x in 0..mb_w {
1401 let slot = r * mb_w + mb_x;
1402 let si = self.slice_of(slot);
1403 if !self.slice_bounds.get(si).is_some_and(|b| b.1) {
1404 continue;
1405 }
1406 if mb_x > 0 && self.slice_of(slot - 1) != si {
1407 if let Some(b) = self.bs_frame.get_mut(slot) {
1408 b.v[0] = [0; 4];
1409 }
1410 }
1411 if r > 0 && self.slice_of(slot - mb_w) != si {
1412 if let Some(b) = self.bs_frame.get_mut(slot) {
1413 b.h[0] = [0; 4];
1414 }
1415 }
1416 }
1417 }
1418 }
1419
1420 /// Decode-loop hook: called at each MB-loop head with the NEXT address to be
1421 /// decoded; derives AND FILTERS (R3) every fully-decoded row. Filtering a
1422 /// row here preserves the spec's raster per-MB filter order exactly (every
1423 /// MB the row's edges touch is already decoded; bottom-adjacent edges
1424 /// belong to the NEXT row's MBs, which filter later).
1425 ///
1426 /// Fast path: mid-row heads (`done <= bs_rows`) do no row work — return
1427 /// before the profiler scope (this hook is entered once per MB; scoping
1428 /// every call was measuring the timer, not the filter).
1429 /// row_hook with the caller's carried row — avoids the per-MB division.
1430 #[inline(always)]
1431 fn row_hook_at(&mut self, addr: usize, mby: usize) {
1432 if rowdb_on() {
1433 // ~44/45 of calls are mid-row: one compare, no atomic loads.
1434 if !rowhook_eager() && mby <= self.bs_rows {
1435 return;
1436 }
1437 }
1438 self.row_hook(addr, mby);
1439 }
1440
1441 /// `mby` is the row `addr` sits in. It is a PARAMETER rather than
1442 /// `addr / self.mb_w` because both callers already carry it: the slice loops
1443 /// track `(mbx, mby)` with a compare-and-wrap precisely so the per-macroblock
1444 /// div+mod never runs, and this hook -- entered PER MACROBLOCK on the
1445 /// non-rowdb arm -- was reconstructing it with an integer divide anyway.
1446 fn row_hook(&mut self, addr: usize, mby: usize) {
1447 debug_assert_eq!(mby, addr / self.mb_w, "row_hook: mby must be addr's row");
1448 // `RS_H264_ROWHOOK_EAGER=1` restores per-MB profiler scoping (A/B oracle).
1449 // READ THE KNOB FUNCTIONS, NOT THE CACHED FIELDS. The old note here said
1450 // the functions cost a OnceLock deref plus a relaxed atomic load per
1451 // entry; that was true BEFORE the routing round, and is not true now --
1452 // they are `return false` under cfg(not(knobs)), i.e. a constant. Going
1453 // through a struct FIELD erases that constant, because the field is a
1454 // runtime value, and every arm the knob guards then stays live in the
1455 // binary. That is what kept `derive_mb_kind` (1543 instrs the decoder
1456 // never runs) linked in until the same change was made for kind_loads.
1457 let eager = rowhook_eager();
1458 if !rowdb_on() {
1459 edcstat::bump(&edcstat::MBS, 1);
1460 let _rh = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecRowHook);
1461 self.edc_flush();
1462 let done = mby;
1463 if done > self.bs_rows {
1464 self.bs_rows = done;
1465 self.publish_progress();
1466 }
1467 return;
1468 }
1469 let done = mby;
1470 // ~44/45 of calls are mid-row: no derive/filter/handoff yet.
1471 if !eager && done <= self.bs_rows {
1472 return;
1473 }
1474 edcstat::bump(&edcstat::MBS, 1);
1475 let _rh = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecRowHook);
1476 if done <= self.bs_rows {
1477 return;
1478 }
1479 if self.edc_tx.is_some() {
1480 // E2: derivation stays here (it reads the syntax grids); filtering
1481 // is the worker's, fed the row's bs/qp/t8 snapshot. `flt_rows`
1482 // advances on the worker and comes home with the context.
1483 self.edc_giveback();
1484 while self.bs_rows < done {
1485 let r = self.bs_rows;
1486 self.derive_bs_row(r);
1487 self.bs_rows += 1;
1488 let base = r * self.mb_w;
1489 // ORDER: this row's pixel jobs must reach the worker BEFORE the
1490 // filter message for the same row.
1491 self.edc_flush_batch();
1492 edcstat::bump(&edcstat::ROWS, 1);
1493 edcstat::bump(
1494 &edcstat::ROWBYTES,
1495 (self.mb_w * (core::mem::size_of::<rusty_h264_common::deblock::MbBs>() + 2))
1496 as u64,
1497 );
1498 let msg = EdcMsg::Row {
1499 r,
1500 bs: self.bs_frame[base..base + self.mb_w].to_vec(),
1501 qp: self.mb_qp[base..base + self.mb_w].to_vec(),
1502 t8: self.mb_t8x8[base..base + self.mb_w].to_vec(),
1503 };
1504 self.edc_tx
1505 .as_ref()
1506 .unwrap()
1507 .send(msg)
1508 .expect("worker alive");
1509 }
1510 return;
1511 }
1512 self.edc_flush();
1513 while self.bs_rows < done {
1514 let r = self.bs_rows;
1515 self.derive_bs_row(r);
1516 self.bs_rows += 1;
1517 // Row filtering requires deblock enabled on EVERY slice so far
1518 // (`db_ena` latches false once any slice disables it): a mixed
1519 // picture falls back to the picture-end tail so "latest slice
1520 // wins" semantics are preserved.
1521 if self.db_ena {
1522 self.save_bak(r);
1523 self.filter_row(r);
1524 self.flt_rows = r + 1;
1525 }
1526 self.publish_progress();
1527 }
1528 }
1529
1530 /// Saves the UNFILTERED bottom pixel rows of MB row `r` before filtering
1531 /// modifies them: the next row's intra prediction must read pre-deblock
1532 /// samples (spec §8.3), and filtering touches the bottom three rows while
1533 /// intra reads exactly the bottom ONE (+ the corner) — so one backup row
1534 /// per plane suffices, overwritten per row.
1535 fn save_bak(&mut self, r: usize) {
1536 let y0 = (r * 16 + 15) * self.cw;
1537 self.bak_y.copy_from_slice(&self.rec_y[y0..y0 + self.cw]);
1538 let c0 = (r * 8 + 7) * self.ccw;
1539 self.bak_u.copy_from_slice(&self.rec_u[c0..c0 + self.ccw]);
1540 self.bak_v.copy_from_slice(&self.rec_v[c0..c0 + self.ccw]);
1541 }
1542
1543 /// Filters one MB row against the stored strengths, using the CURRENT
1544 /// slice's alpha/beta offsets (single-offset streams — the whole corpus —
1545 /// are bit-identical to the picture-end call; the plan's risk register
1546 /// documents the multi-offset divergence).
1547 fn filter_row(&mut self, r: usize) {
1548 let info = rusty_h264_common::deblock::BlockInfo {
1549 inter: &self.inter_y,
1550 // Same source `derive_bs_row` used (see the T8 GATE there). This
1551 // path passes a non-empty `bs`, so it takes the PRECOMPUTED arm and
1552 // never reads `nnz` at all; kept in step so the two cannot diverge.
1553 nnz: if self.any_t8 {
1554 &self.nnz_dbr
1555 } else {
1556 &self.nnz_y
1557 },
1558 mv: &self.mv_y,
1559 ref_id: &self.ref_idx_y,
1560 mv1: &self.mv1,
1561 ref_id1: &self.ref_idx1,
1562 w4: self.mb_w * 4,
1563 t8x8: &self.mb_t8x8,
1564 bs: &self.bs_frame,
1565 poc0: &[],
1566 poc1: &[],
1567 kind: &self.mb_kind,
1568 };
1569 rusty_h264_common::deblock::filter_frame_rows_pre(
1570 &mut self.rec_y,
1571 &mut self.rec_u,
1572 &mut self.rec_v,
1573 self.mb_w,
1574 self.mb_h,
1575 r..r + 1,
1576 &self.mb_qp,
1577 self.chroma_qp_offset,
1578 self.db_oa,
1579 self.db_ob,
1580 &info,
1581 );
1582 }
1583
1584 /// Top-neighbour LUMA pixel for intra prediction: reads the unfiltered
1585 /// backup row when the row above has already been deblock-filtered by the
1586 /// row-interleave (flt_rows gates it; 0 when the interleave is off, so
1587 /// this compiles to the plain read on the fallback path).
1588 #[inline]
1589 fn top_y_px(&self, py: usize, x: usize) -> u8 {
1590 // 128 is the spec's value for an unavailable sample (1 << (BitDepth - 1),
1591 // §8.3.1.2), so the fallback is the one the prediction rules already use
1592 // — and unreachable anyway, since callers gate on availability first.
1593 if py % 16 == 0 && self.flt_rows * 16 >= py {
1594 self.bak_y.get(x).copied().unwrap_or(128)
1595 } else {
1596 self.rec_y
1597 .get((py - 1) * self.cw + x)
1598 .copied()
1599 .unwrap_or(128)
1600 }
1601 }
1602
1603 /// Slice form of [`Self::top_y_px`] for the contiguous 16-wide I16 gather.
1604 #[inline]
1605 fn top_y_row(&self, py: usize, x: usize, n: usize) -> &[u8] {
1606 if py % 16 == 0 && self.flt_rows * 16 >= py {
1607 &self.bak_y[x..x + n]
1608 } else {
1609 &self.rec_y[(py - 1) * self.cw + x..][..n]
1610 }
1611 }
1612
1613 /// Top-neighbour CHROMA pixel (plane `c`: 0 = U, 1 = V).
1614 #[inline]
1615 fn top_c_px(&self, c: usize, cy: usize, x: usize) -> u8 {
1616 // 128 = the spec's unavailable sample, as in `top_y_px`.
1617 if cy % 8 == 0 && self.flt_rows * 8 >= cy {
1618 let bak = if c == 0 { &self.bak_u } else { &self.bak_v };
1619 bak.get(x).copied().unwrap_or(128)
1620 } else {
1621 let rec = if c == 0 { &self.rec_u } else { &self.rec_v };
1622 rec.get((cy - 1) * self.ccw + x).copied().unwrap_or(128)
1623 }
1624 }
1625
1626 /// Slice form of [`Self::top_c_px`] for the 8-wide chroma gather.
1627 #[inline]
1628 fn top_c_row(&self, c: usize, cy: usize, x: usize, n: usize) -> &[u8] {
1629 if cy % 8 == 0 && self.flt_rows * 8 >= cy {
1630 if c == 0 {
1631 &self.bak_u[x..x + n]
1632 } else {
1633 &self.bak_v[x..x + n]
1634 }
1635 } else {
1636 let rec = if c == 0 { &self.rec_u } else { &self.rec_v };
1637 &rec[(cy - 1) * self.ccw + x..][..n]
1638 }
1639 }
1640
1641 /// Snapshots the (deblocked) reconstruction as a reference picture, drawing
1642 /// its padded-plane allocations from `pool` (recycled
1643 /// planes of evicted DPB frames — see `Decoder::reclaim_retired`). ~1.9 MB of
1644 /// fresh allocation per reference picture otherwise (`dpb-clone` stage, 3-4%
1645 /// of decode, mostly first-touch page faults).
1646 pub fn as_reference_pooled(&self, pool: &mut Vec<Vec<u8>>) -> crate::RefFrame {
1647 // MV CAPTURE (`RFF_MV_DUMP=1`) — lets a harness read the motion field any
1648 // conformant H.264 stream carries, including x264's, using this decoder as
1649 // the parser. Diagnostic only; inert unless the env var is set.
1650 #[cfg(feature = "std")]
1651 if mv_dump_on() {
1652 MV_DUMP.lock().unwrap().push(MvField {
1653 mb_w: self.mb_w,
1654 mb_h: self.mb_h,
1655 mv: self.mv_y.clone(),
1656 ref_idx: self.ref_idx_y.clone(),
1657 inter: self.inter_y.clone(),
1658 });
1659 }
1660
1661 // The per-block motion (mv/ref_idx/ref_poc) is read ONLY by B temporal/spatial
1662 // direct (`col.mv/ref_idx/ref_poc`, guarded on `w4 != 0` + `idx < len`). On
1663 // Baseline/Constrained-Baseline streams (no B) it's pure waste — skip the two
1664 // grid clones + the per-block ref_poc resolve/alloc. `w4 = 0` makes the B
1665 // readers no-op even on malformed input.
1666 let (mv, ref_idx, mv1, ref_idx1, ref_poc, w4) = if self.b_possible {
1667 (
1668 self.mv_y.clone(),
1669 self.ref_idx_y.clone(),
1670 self.mv1.clone(),
1671 self.ref_idx1.clone(),
1672 // Resolve each block's List-0 ref index to the referenced picture's
1673 // POC, so temporal direct can map it into the current list.
1674 // Via a tiny per-ref LUT: the per-block bounds + Option chain +
1675 // Ref pointer chase (57k blocks at 720p, once per reference
1676 // frame) becomes one table index. Identical output: LUT slots
1677 // past refs.len() hold MIN, exactly what .get() returned.
1678 {
1679 let mut poc_lut = [i32::MIN; 32];
1680 for (i, f) in self.refs.iter().take(32).enumerate() {
1681 poc_lut[i & 31] = f.pic_poc();
1682 }
1683 self.ref_idx_y
1684 .iter()
1685 .map(|&r| {
1686 if (0..32).contains(&r) {
1687 poc_lut[r as usize]
1688 } else {
1689 i32::MIN
1690 }
1691 })
1692 .collect()
1693 },
1694 self.mb_w * 4,
1695 )
1696 } else {
1697 (
1698 Vec::new(),
1699 Vec::new(),
1700 Vec::new(),
1701 Vec::new(),
1702 Vec::new(),
1703 0,
1704 )
1705 };
1706 // Pop an exact-size recycled buffer per plane; a miss falls back to a
1707 // fresh allocation inside `pad_plane_into`.
1708 let mut take = |len: usize| -> Vec<u8> {
1709 match pool.iter().position(|v| v.len() == len) {
1710 Some(i) => pool.swap_remove(i),
1711 None => Vec::new(),
1712 }
1713 };
1714 let (lpw, lph) = (self.cw + 2 * crate::LPAD, self.ch + 2 * crate::LPAD);
1715 let (cpw, cph) = (self.ccw + 2 * crate::CPAD, self.ch / 2 + 2 * crate::CPAD);
1716 crate::RefFrame {
1717 // Pad once here (ExpandPicture) instead of extracting a clamped tile
1718 // on every MC call — same copy class as the old plane clone.
1719 py: rusty_h264_common::inter::pad_plane_into(
1720 take(lpw * lph),
1721 &self.rec_y,
1722 self.cw,
1723 self.ch,
1724 crate::LPAD,
1725 ),
1726 pu: rusty_h264_common::inter::pad_plane_into(
1727 take(cpw * cph),
1728 &self.rec_u,
1729 self.ccw,
1730 self.ch / 2,
1731 crate::CPAD,
1732 ),
1733 pv: rusty_h264_common::inter::pad_plane_into(
1734 take(cpw * cph),
1735 &self.rec_v,
1736 self.ccw,
1737 self.ch / 2,
1738 crate::CPAD,
1739 ),
1740 cw: self.cw,
1741 ch: self.ch,
1742 ready_rows: core::sync::atomic::AtomicUsize::new(0),
1743 live: None,
1744 frozen: crate::sync::Once::new(),
1745 frame_num: 0, // set by the caller (decode_slice knows frame_num)
1746 poc: 0, // set by the caller
1747 mv,
1748 ref_idx,
1749 mv1,
1750 ref_idx1,
1751 ref_poc,
1752 w4,
1753 long_term: false,
1754 long_term_idx: 0,
1755 }
1756 }
1757
1758 fn nnz_cache_load(&mut self, mb_x: usize, mb_y: usize) {
1759 let w4 = self.mb_w * 4;
1760 let top_unavail = mb_y == 0 || !self.nbr_in_slice(mb_x, mb_y - 1);
1761 let left_unavail = mb_x == 0 || !self.nbr_in_slice(mb_x - 1, mb_y);
1762 // The four TOP neighbours are one contiguous run of the nnz grid; only
1763 // the left column is strided. Hoisting the uniform `top_unavail` test out
1764 // of the loop turns four checked grid loads into one slice copy.
1765 if top_unavail {
1766 self.nnz_l_cache[1..5].fill(0x80);
1767 } else {
1768 let src = &self.nnz_y[(mb_y * 4 - 1) * w4 + mb_x * 4..][..4];
1769 self.nnz_l_cache[1..5].copy_from_slice(src);
1770 }
1771 for lby in 0..4 {
1772 self.nnz_l_cache[(lby + 1) * 5] = if left_unavail {
1773 0x80
1774 } else {
1775 self.nnz_y
1776 .get((mb_y * 4 + lby) * w4 + (mb_x * 4 - 1))
1777 .copied()
1778 .unwrap_or(0)
1779 };
1780 }
1781 }
1782 #[inline]
1783 fn nc_pred(&self, lbx: usize, lby: usize) -> i32 {
1784 // MASKED, and it pays twice over: this helper and `nnz_cache_set` are
1785 // inlined into the intra path, the inter path AND both slice loops, so
1786 // the two unprovable indexes were replicated at every call site. The
1787 // cache is a 5x5 grid in a `[u8; 25]` and every caller passes a 4x4 block
1788 // coordinate (`LUMA_4X4_SCAN_XY`, `b8*2 + s`, or a literal), so `& 3` is
1789 // a no-op that puts the worst case at 4 * 5 + 4 = 24.
1790 let (lbx, lby) = (lbx & 3, lby & 3);
1791 let left = self.nnz_l_cache[(lby + 1) * 5 + lbx] as i32;
1792 let top = self.nnz_l_cache[lby * 5 + (lbx + 1)] as i32;
1793 let r = left + top;
1794 if r < 0x80 {
1795 (r + 1) >> 1
1796 } else {
1797 r & 0x7f
1798 }
1799 }
1800 #[inline]
1801 fn nnz_cache_set(&mut self, lbx: usize, lby: usize, total: u8) {
1802 self.nnz_l_cache[((lby & 3) + 1) * 5 + ((lbx & 3) + 1)] = total;
1803 }
1804 fn chroma_cache_load(&mut self, mb_x: usize, mb_y: usize) {
1805 let w2 = self.mb_w * 2;
1806 let top_unavail = mb_y == 0 || !self.nbr_in_slice(mb_x, mb_y - 1);
1807 let left_unavail = mb_x == 0 || !self.nbr_in_slice(mb_x - 1, mb_y);
1808 for c in 0..2 {
1809 if top_unavail {
1810 self.nnz_c_cache[c][1..3].fill(0x80);
1811 } else {
1812 let src = &self.nnz_c[c][(mb_y * 2 - 1) * w2 + mb_x * 2..][..2];
1813 self.nnz_c_cache[c][1..3].copy_from_slice(src);
1814 }
1815 for by in 0..2 {
1816 self.nnz_c_cache[c][(by + 1) * 3] = if left_unavail {
1817 0x80
1818 } else {
1819 self.nnz_c[c & 1]
1820 .get((mb_y * 2 + by) * w2 + (mb_x * 2 - 1))
1821 .copied()
1822 .unwrap_or(0)
1823 };
1824 }
1825 }
1826 }
1827 #[inline]
1828 fn chroma_nc_pred(&self, c: usize, bx: usize, by: usize) -> i32 {
1829 // Same shape as `nc_pred`, one size down: a 3x3 grid in `[u8; 9]`, two
1830 // planes, and every caller passes 0..1 for all three coordinates.
1831 let (c, bx, by) = (c & 1, bx & 1, by & 1);
1832 let left = self.nnz_c_cache[c][(by + 1) * 3 + bx] as i32;
1833 let top = self.nnz_c_cache[c][by * 3 + (bx + 1)] as i32;
1834 let r = left + top;
1835 if r < 0x80 {
1836 (r + 1) >> 1
1837 } else {
1838 r & 0x7f
1839 }
1840 }
1841 #[inline]
1842 fn chroma_nnz_cache_set(&mut self, c: usize, bx: usize, by: usize, total: u8) {
1843 self.nnz_c_cache[c & 1][((by & 1) + 1) * 3 + ((bx & 1) + 1)] = total;
1844 }
1845
1846 /// Decodes one slice's macroblocks (raster order) starting at `first_mb`,
1847 /// until `more_rbsp_data()` is exhausted or the picture is full. Returns the
1848 /// next macroblock address (= total when the picture is complete). In a
1849 /// P-slice each macroblock is preceded by `mb_skip_run`.
1850 /// CABAC slice-data decode (docs/cabac-decode-plan.md), brought up brick by brick
1851 /// against the instrumented openh264 oracle. Phase 1: verify engine init; the
1852 /// syntax layer (Phase 2+) is WIP.
1853 #[allow(clippy::too_many_arguments)]
1854 pub fn decode_slice_data_cabac(
1855 &mut self,
1856 rbsp: &[u8],
1857 start_byte: usize,
1858 slice_qp: u8,
1859 cabac_init_idc: u32,
1860 is_i: bool,
1861 is_p: bool,
1862 first_mb: usize,
1863 ) -> Result<usize, MbError> {
1864 // E2: overlap parse (this thread) with pixel reconstruction (a scoped
1865 // worker owning the planes) for P slices. I slices and B slices keep
1866 // the inline path (their pixel coupling is per-MB); the ownership
1867 // ping-pong around intra-in-P macroblocks is `edc_intra_sync`.
1868 let eligible = edc_on() && rowdb_on() && !is_i && (is_p || self.is_b);
1869 let threaded = eligible && edc_spawn_worker(self.mb_w, self.mb_h, self.bits_per_mb, true);
1870 edcstat::bump(&edcstat::DISPATCH_ON, threaded as u64);
1871 edcstat::bump(&edcstat::DISPATCH_SEEN, eligible as u64);
1872 if !threaded {
1873 let r = self.decode_slice_cabac_inner(
1874 rbsp,
1875 start_byte,
1876 slice_qp,
1877 cabac_init_idc,
1878 is_i,
1879 is_p,
1880 first_mb,
1881 );
1882 self.note_slice_density(rbsp.len().saturating_sub(start_byte), first_mb, &r);
1883 return r;
1884 }
1885 #[cfg(feature = "std")]
1886 {
1887 let ctx = self.edc_take_ctx();
1888 // D7 PROBE: is the CPU overhead PAYLOAD (alloc/copy per job) or
1889 // SYNCHRONISATION (blocking on a full queue, park/unpark)? The bound
1890 // separates them: raising it removes send-blocking without changing a
1891 // single byte copied. `RS_H264_EDC_BOUND` sweeps it.
1892 let (tx, rx) = crate::sync::mpsc::sync_channel::<EdcMsg>(edc_bound());
1893 let (ctx_tx, ctx_rx) = crate::sync::mpsc::channel::<PixelCtx>();
1894 let (back_tx, back_rx) = crate::sync::mpsc::channel::<PixelCtx>();
1895 let (res, ctx, panicked) = std::thread::scope(|sc| {
1896 let h = sc.spawn(move || edc_worker(ctx, rx, ctx_tx, back_rx));
1897 self.edc_tx = Some(tx);
1898 self.edc_ctx_rx = Some(ctx_rx);
1899 self.edc_back_tx = Some(back_tx);
1900 // UNWIND SAFETY (found by the fuzzer as a HANG, not a failure): a
1901 // panic inside the parse loop would skip the cleanup below — but
1902 // the sender lives in `self`, which outlives the unwind, so the
1903 // channel would never close, the worker would never exit, and the
1904 // scope's join would block forever, converting a diagnosable panic
1905 // into a silent deadlock under `catch_unwind` harnesses. Catch,
1906 // clean up, join, restore the planes, THEN resume the panic.
1907 let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1908 self.decode_slice_cabac_inner(
1909 rbsp,
1910 start_byte,
1911 slice_qp,
1912 cabac_init_idc,
1913 is_i,
1914 is_p,
1915 first_mb,
1916 )
1917 }));
1918 self.edc_flush_batch(); // ORDER: no job may outlive the channel
1919 self.edc_giveback(); // if an intra macroblock left us holding
1920 self.edc_tx = None; // closes the channel -> worker drains + returns
1921 self.edc_ctx_rx = None;
1922 self.edc_back_tx = None;
1923 match (r, h.join()) {
1924 (Ok(res), Ok(ctx)) => (res, Some(ctx), None),
1925 (Err(p), Ok(ctx)) => (Err(MbError::Truncated), Some(ctx), Some(p)),
1926 (Ok(_), Err(p)) | (Err(_), Err(p)) => (Err(MbError::Truncated), None, Some(p)),
1927 }
1928 });
1929 if let Some(ctx) = ctx {
1930 self.edc_restore_ctx(ctx);
1931 }
1932 if let Some(p) = panicked {
1933 std::panic::resume_unwind(p);
1934 }
1935 self.note_slice_density(rbsp.len().saturating_sub(start_byte), first_mb, &res);
1936 return res;
1937 }
1938 #[cfg(not(feature = "std"))]
1939 {
1940 unreachable!("the EDC worker needs std; edc_spawn_worker is false without it")
1941 }
1942 }
1943
1944 /// Feed the D12 dispatch its density signal from a slice just decoded.
1945 /// Exponentially smoothed so one atypical slice cannot flip the arm, and
1946 /// only ever read on the NEXT slice — the current one is already committed.
1947 fn note_slice_density(&mut self, bytes: usize, first_mb: usize, r: &Result<usize, MbError>) {
1948 let Ok(end) = r else { return };
1949 let mbs = end.saturating_sub(first_mb);
1950 if mbs == 0 {
1951 return;
1952 }
1953 let bpm = (bytes * 8) as f64 / mbs as f64;
1954 self.bits_per_mb = if self.bits_per_mb == 0.0 {
1955 bpm
1956 } else {
1957 0.75 * self.bits_per_mb + 0.25 * bpm
1958 };
1959 }
1960
1961 fn decode_slice_cabac_inner(
1962 &mut self,
1963 rbsp: &[u8],
1964 start_byte: usize,
1965 slice_qp: u8,
1966 cabac_init_idc: u32,
1967 is_i: bool,
1968 is_p: bool,
1969 first_mb: usize,
1970 ) -> Result<usize, MbError> {
1971 self.edc_active = edc_on();
1972 let mut cab =
1973 crate::cabac::Cabac::new(rbsp, start_byte, slice_qp as i32, cabac_init_idc, is_i);
1974 let (range, _offset) = cab.dbg_state();
1975 // The crate already HAS a `cabac-trace` feature for this; the bare knob()
1976 // was env::var plus a String allocation per SLICE, inside the hottest
1977 // function in the decoder, and it kept every trace arm compiled in.
1978 #[cfg(not(feature = "cabac-trace"))]
1979 let trace = false;
1980 #[cfg(feature = "cabac-trace")]
1981 let trace = rusty_h264_common::knob("RH_CABAC_TRACE").is_some();
1982 debug_assert_eq!(range, 510, "CABAC init range must be 510");
1983
1984 let mbw = self.mb_w;
1985 let total = self.mb_w * self.mb_h;
1986 // Per-MB neighbour state (single-slice assumption: avail == in-bounds).
1987 // SCOPED: zero-initialised allocations sized by MB count, once per slice.
1988 // D13: B-only grids (List-1 ref/mvd + direct flags) are ~292 KB at 720p and
1989 // are ONLY written on the B branch below — allocating them on every P/I
1990 // slice was the same fresh-page class GridPool fixed for the frame grids.
1991 // `RS_H264_FAT_SLICE=1` restores the always-alloc path for A/B.
1992 let _alloc_g =
1993 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecSliceAlloc);
1994 // POOLED (was fresh `vec![..]` per slice): refill reuses the pooled
1995 // allocation with contents identical to a fresh vec, so the body below
1996 // is untouched. Taken out of `self` here; put back at the normal exit.
1997 let mut cat = refill(core::mem::take(&mut self.sc_cat), total, 255u8); // 0=I4x4, 2=I16, 255=unavailable
1998 let mut mb_cbp = refill(core::mem::take(&mut self.sc_cbp), total, 0u8);
1999 let mut cmode = refill(core::mem::take(&mut self.sc_cmode), total, -1i32); // chroma pred mode
2000 let mut mb_nzc = refill(core::mem::take(&mut self.sc_nzc), total, [0u8; 24]); // 16 luma raster + 8 chroma
2001 let mut cbf_dc = refill(core::mem::take(&mut self.sc_cbfdc), total, 0u16);
2002 let mut mb_skip = refill(core::mem::take(&mut self.sc_skip), total, false);
2003 let mut mb_ref = refill(core::mem::take(&mut self.sc_ref), total, [-1i8; 16]); // per-4×4-block List-0 ref (-1 = intra)
2004 let mut mb_mvd = refill(core::mem::take(&mut self.sc_mvd), total, [[0i16; 2]; 16]); // per-block mvd (for mvd ctxInc)
2005 // D13: B-only grids (~292 KB @720p) only on B slices (or FAT_SLICE A/B).
2006 let want_b_grids = self.is_b || fat_slice_on();
2007 let mut mb_ref1 = if want_b_grids {
2008 refill(core::mem::take(&mut self.sc_ref1), total, [-1i8; 16])
2009 } else {
2010 Vec::new()
2011 };
2012 let mut mb_mvd1 = if want_b_grids {
2013 refill(core::mem::take(&mut self.sc_mvd1), total, [[0i16; 2]; 16])
2014 } else {
2015 Vec::new()
2016 };
2017 let mut mb_direct = if want_b_grids {
2018 refill(core::mem::take(&mut self.sc_direct), total, false)
2019 } else {
2020 Vec::new()
2021 };
2022 drop(_alloc_g);
2023 // Multi-slice availability (spec §6.4.x): a macroblock before this
2024 // slice's first_mb is NOT available — for pixel-domain prediction
2025 // (`nbr_in_slice`, like the CAVLC twin sets) AND for every CABAC ctx
2026 // neighbour below. The per-slice ctx arrays' defaults are not the
2027 // "unavailable" value (cat 255 reads as available-I16, mb_cbp 0 as
2028 // all-zero-cbp, mb_t8x8 is a frame grid…), so the `left`/`top`
2029 // options themselves are gated on slice membership — one gate that
2030 // every downstream ctxIdxInc read inherits. Confirmed against ffmpeg
2031 // on an x264 `--slices 4` stream, which desynced without this.
2032 self.slice_first_mb = first_mb;
2033 self.slice_bounds.push((first_mb, self.cur_idc2));
2034 self.any_idc2 |= self.cur_idc2;
2035 let mut last_delta_qp = 0i32;
2036 let mut addr = first_mb;
2037
2038 let _mbloop_g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbLoop);
2039 // A new slice's first MBs may read grids a previous slice's deferred
2040 // spans still owe (P-after-B in one picture included).
2041 self.span_flush();
2042 // `mbw` is `pic_width_in_mbs_minus1 + 1` from the SPS. It cannot be 0 in
2043 // a conformant stream, but it is a runtime value, so this div+rem pair
2044 // carried a divide-by-zero panic reachable from a MALFORMED header --
2045 // on the untrusted-input path, at slice entry. `.max(1)` retires both
2046 // checks and cannot change a conformant decode.
2047 let mbw_nz = mbw.max(1);
2048 let (mut mbx, mut mby) = (addr % mbw_nz, addr / mbw_nz);
2049 // Slice-invariant B properties, formerly re-derived per macroblock.
2050 if self.is_b {
2051 self.edc_flush(); // drain anything a preceding P slice queued
2052 }
2053 let b_records = self.edc_tx.is_some();
2054 let b_refs_ok = !self.refs.is_empty() && !self.refs1.is_empty();
2055 // Loop-invariant: the lowest `addr` whose top neighbour is in this slice.
2056 let top_lim = first_mb + mbw;
2057 // Reused across macroblocks: MC overwrites every byte it reads back (all
2058 // B layouts cover the full macroblock), so the per-MB 384-byte zero-init
2059 // these used to pay was dead. Byte-identity is the guard.
2060 let mut pred_y = [0u8; 256];
2061 let mut c_pred = [[0u8; 64]; 2];
2062 // The loop wraps mbx at entry; bias so the first iteration is exact.
2063 // BOUND the entropy-coded loop (fuzzer-surfaced): a mutated stream can
2064 // never produce decode_terminate and the engine zero-fills forever. The
2065 // bound needs checking ONCE at entry — every in-loop path that advances
2066 // `addr` already breaks on `addr >= total` before continuing.
2067 if addr >= total {
2068 return Err(MbError::Truncated);
2069 }
2070 // BIND THE LENGTH FOR THE LOOP. Every grid below is `refill(.., total, ..)`
2071 // so its length is EXACTLY `total`, and the loop is entered only with
2072 // `addr < total` (checked above, re-asserted each turn) — but nothing
2073 // related the two, so every `[addr]` carried a check. Twenty-eight of
2074 // those became `get_mut` in the previous pass and cost ~2% on CABAC
2075 // content (crowd_run-main 1.021x, z=+2.11): a branch per write on the
2076 // decoder's hottest loop. Reborrowing at the literal `total` makes
2077 // `addr < total` and `addr < len` the SAME fact — no check, no branch.
2078 // The borrows end with this block, before the grids go back to the pool.
2079 // `mb_direct`/`mb_ref1`/`mb_mvd1` are deliberately NOT bound here: they
2080 // are `Vec::new()` on non-B slices, so slicing them to `total` would be
2081 // the very panic this campaign is removing.
2082 {
2083 let cat = &mut cat[..total];
2084 let mb_cbp = &mut mb_cbp[..total];
2085 let cmode = &mut cmode[..total];
2086 let mb_nzc = &mut mb_nzc[..total];
2087 let cbf_dc = &mut cbf_dc[..total];
2088 let mb_skip = &mut mb_skip[..total];
2089 let mb_ref = &mut mb_ref[..total];
2090 let mb_mvd = &mut mb_mvd[..total];
2091 loop {
2092 // A REAL check, not `debug_assert` — which compiles OUT in release,
2093 // so nothing carried `addr < total` across the loop's back-edge and
2094 // the reborrows above folded nothing on their own. This cannot fire
2095 // (every path that advances `addr` already breaks on `addr >= total`,
2096 // and entry is guarded above), but stating it once per macroblock
2097 // replaces FIFTEEN per-write branches with one that never taken.
2098 if addr >= total {
2099 break;
2100 }
2101 // Carried coordinates: one compare-and-wrap replaces the per-MB
2102 // div+mod pair (and row_hook's own division).
2103 if mbx == mbw {
2104 mbx = 0;
2105 mby += 1;
2106 }
2107 self.row_hook_at(addr, mby);
2108 self.wait_refs_for_mb(mby);
2109 let left = (mbx > 0 && addr > first_mb).then(|| addr - 1);
2110 let top = (mby > 0 && addr >= top_lim).then(|| addr - mbw);
2111
2112 // Brick 3.1/3.2: P-slice mb_skip_flag, then mb_type (P mb_type is neighbour-
2113 // independent; intra sub-types map to the I dispatch below).
2114 let mb_type;
2115 if is_p {
2116 // Direct bool arithmetic — no Option chain on the hot path.
2117 let sctx = 11
2118 + (left.is_some() && !mb_skip.get(addr - 1).copied().unwrap_or(true))
2119 as usize
2120 + (top.is_some()
2121 && !mb_skip.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true))
2122 as usize;
2123 // ONE engine view carries mb_skip_flag, the terminate bin of a skipped
2124 // macroblock, and mb_type of a coded one (was 2-3 view/commit round
2125 // trips of 8 memory ops each per macroblock).
2126 let (data, mut e, ctx) = cab.view();
2127 if e.decode_decision(data, ctx, sctx) != 0 {
2128 if let Some(p) = mb_skip.get_mut(addr) {
2129 *p = true;
2130 }
2131 last_delta_qp = 0; // skip codes no mb_qp_delta → delta ctxInc resets
2132 // P_Skip recon reuses the entropy-free CAVLC primitive verbatim: it
2133 // takes no bit-reader (skip has no coded syntax past the flag), just
2134 // predicts the skip MV, motion-compensates, and commits the grid.
2135 self.decode_p_skip(mbx, mby)?;
2136 if let Some(p) = self.mb_qp.get_mut(addr) {
2137 *p = self.cur_qp;
2138 } // skip inherits QPy
2139 let eos = e.decode_terminate(data);
2140 cab.commit(e);
2141 addr += 1;
2142 mbx += 1;
2143 if eos || addr >= total {
2144 break;
2145 }
2146 continue;
2147 }
2148 let mbt = mb_type_p_eng(&mut e, data, ctx);
2149 cab.commit(e);
2150 // NON-SKIP P MB: the inter arms below predict MVs from the
2151 // grids (mv_neighbors_block) and the intra arm gathers — flush
2152 // the deferred P span (B span cannot be pending in a P slice,
2153 // but span_flush is one Option check each).
2154 self.span_flush();
2155 if mbt <= 3 {
2156 let _gb =
2157 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbP);
2158 // noSubMbPartSizeLessThan8x8Flag (spec 7.3.5): P_8x8 permits the
2159 // 8x8 transform only when every sub-partition is itself 8x8.
2160 let mut allow8 = true;
2161 // Inter MB (Bricks 3.3/3.4/3.5). 1-ref stream → ref_idx not coded (ref=0).
2162 // Build the 30-entry mvd/ref neighbour cache (openh264 WelsFillCacheInterCabac).
2163 let mut mvdc = [[0i16; 2]; 30];
2164 let mut refc = [-1i8; 30];
2165 // Index the neighbour's RECORD once, then read within it.
2166 // `mb_ref[l][bi]` is two bounds checks - one on the Vec, one
2167 // on the array - repeated for each of the four entries and
2168 // again for `mb_mvd`, i.e. sixteen checks per macroblock for
2169 // four neighbours. Binding the record hoists the Vec check
2170 // out; the inner index is a literal and folds.
2171 if let Some(l) = left {
2172 let (Some(lr), Some(lm)) = (mb_ref.get(l), mb_mvd.get(l)) else {
2173 return Err(MbError::Truncated);
2174 };
2175 for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
2176 refc[ci] = lr[bi];
2177 mvdc[ci] = lm[bi];
2178 }
2179 }
2180 if let Some(t) = top {
2181 let (Some(tr), Some(tm)) = (mb_ref.get(t), mb_mvd.get(t)) else {
2182 return Err(MbError::Truncated);
2183 };
2184 for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
2185 refc[ci] = tr[bi];
2186 mvdc[ci] = tm[bi];
2187 }
2188 }
2189 if mbx > 0 && mby > 0 {
2190 let a = addr - mbw - 1;
2191 if let (Some(r), Some(m)) = (mb_ref.get(a), mb_mvd.get(a)) {
2192 (refc[0], mvdc[0]) = (r[15], m[15]);
2193 }
2194 }
2195 if mby > 0 && mbx + 1 < mbw {
2196 let a = addr - mbw + 1;
2197 if let (Some(r), Some(m)) = (mb_ref.get(a), mb_mvd.get(a)) {
2198 (refc[5], mvdc[5]) = (r[12], m[12]);
2199 }
2200 }
2201 let mut mmvd = [[0i16; 2]; 16];
2202 let mut mref = [0i8; 16];
2203 // mb_pred (spec 7.3.5.1): all ref_idx_l0 FIRST (only when >1 active
2204 // ref), then all mvd + ref-aware predict + commit. `refidx!` parses one
2205 // partition's ref_idx (ctxIdxOffset 54, ctx from neighbour refc) and
2206 // seeds refc so a later partition's ref/mvd context sees it — mirror
2207 // of the encoder's two-phase emit_mb_cabac_p_inter.
2208 macro_rules! refidx {
2209 ($pi:expr, $zb:expr) => {{
2210 if self.num_ref_active > 1 {
2211 let s = CACHE30[$pi & 15].clamp(6, 29);
2212 let c0 =
2213 (refc[s - 1] > 0) as usize + 2 * (refc[s - 6] > 0) as usize;
2214 let r = parse_ref_idx_cabac(&mut cab, c0);
2215 for &zb in $zb.iter() {
2216 refc[CACHE30[zb & 15]] = r;
2217 }
2218 r
2219 } else {
2220 0i8
2221 }
2222 }};
2223 }
2224 macro_rules! part {
2225 ($pi:expr, $zb:expr, $pred:expr, $rx:expr, $ry:expr, $rw:expr, $rh:expr, $refi:expr) => {{
2226 let (mvx, mvy) = parse_mvd_partition(
2227 &mut cab, $pi, $zb, &mut mvdc, &mut refc, &mut mmvd, &mut mref,
2228 $refi,
2229 );
2230 let [na, nb, nc] = self.mv_neighbors_block(
2231 (mbx * 4 + $rx / 4) as isize,
2232 (mby * 4 + $ry / 4) as isize,
2233 ($rw / 4) as isize,
2234 );
2235 let pmv = $pred(na, nb, nc);
2236 self.commit_inter_grid(
2237 mbx,
2238 mby,
2239 $rx,
2240 $ry,
2241 $rw,
2242 $rh,
2243 (pmv.0 + mvx, pmv.1 + mvy),
2244 $refi,
2245 );
2246 }};
2247 }
2248 match mbt {
2249 0 => {
2250 // Sibling of CAVLC P_16x16: one (ref, mv) for all 16
2251 // blocks → every internal edge is strength 0 (§8.7.2.1).
2252 // Without this, CABAC Main/High P_16x16 stayed UNSET and
2253 // paid the blind 24-block bS gather.
2254 if let Some(k) = self.mb_kind.get_mut(mby * self.mb_w + mbx) {
2255 *k = rusty_h264_common::deblock::MB_KIND_INTER_UNIFORM;
2256 }
2257 let r0 = refidx!(
2258 0,
2259 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
2260 );
2261 part!(
2262 0,
2263 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
2264 |a, b, c| predict_partition_mv(0, 0, a, b, c, r0 as i32),
2265 0,
2266 0,
2267 16,
2268 16,
2269 r0
2270 );
2271 }
2272 1 => {
2273 let r0 = refidx!(0, &[0, 1, 2, 3, 4, 5, 6, 7]);
2274 let r1 = refidx!(8, &[8, 9, 10, 11, 12, 13, 14, 15]);
2275 part!(
2276 0,
2277 &[0, 1, 2, 3, 4, 5, 6, 7],
2278 |a, b, c| predict_partition_mv(1, 0, a, b, c, r0 as i32),
2279 0,
2280 0,
2281 16,
2282 8,
2283 r0
2284 );
2285 part!(
2286 8,
2287 &[8, 9, 10, 11, 12, 13, 14, 15],
2288 |a, b, c| predict_partition_mv(1, 1, a, b, c, r1 as i32),
2289 0,
2290 8,
2291 16,
2292 8,
2293 r1
2294 );
2295 }
2296 2 => {
2297 let r0 = refidx!(0, &[0, 1, 2, 3, 8, 9, 10, 11]);
2298 let r1 = refidx!(4, &[4, 5, 6, 7, 12, 13, 14, 15]);
2299 part!(
2300 0,
2301 &[0, 1, 2, 3, 8, 9, 10, 11],
2302 |a, b, c| predict_partition_mv(2, 0, a, b, c, r0 as i32),
2303 0,
2304 0,
2305 8,
2306 16,
2307 r0
2308 );
2309 part!(
2310 4,
2311 &[4, 5, 6, 7, 12, 13, 14, 15],
2312 |a, b, c| predict_partition_mv(2, 1, a, b, c, r1 as i32),
2313 8,
2314 0,
2315 8,
2316 16,
2317 r1
2318 );
2319 }
2320 _ => {
2321 // P_8x8: 4 sub_mb_types, then 4 ref_idx (one per 8×8), then mvd.
2322 let mut subt = [0u32; 4];
2323 for st in &mut subt {
2324 *st = parse_sub_mb_type_p_cabac(&mut cab);
2325 }
2326 allow8 = subt.iter().all(|&t| t == 0);
2327 let mut pr = [0i8; 4];
2328 for (i, r) in pr.iter_mut().enumerate() {
2329 let b = i * 4;
2330 *r = refidx!(b, &[b, b + 1, b + 2, b + 3]);
2331 }
2332 for i in 0..4usize {
2333 let b = i * 4;
2334 let (ox, oy) = ((i % 2) * 8, (i / 2) * 8); // 8×8 pixel origin in MB
2335 let ri = pr[i];
2336 match subt[i] {
2337 0 => part!(
2338 b,
2339 &[b, b + 1, b + 2, b + 3],
2340 |a, b, c| predict_mv(a, b, c, ri as i32),
2341 ox,
2342 oy,
2343 8,
2344 8,
2345 ri
2346 ),
2347 1 => {
2348 part!(
2349 b,
2350 &[b, b + 1],
2351 |a, b, c| predict_mv(a, b, c, ri as i32),
2352 ox,
2353 oy,
2354 8,
2355 4,
2356 ri
2357 );
2358 part!(
2359 b + 2,
2360 &[b + 2, b + 3],
2361 |a, b, c| predict_mv(a, b, c, ri as i32),
2362 ox,
2363 oy + 4,
2364 8,
2365 4,
2366 ri
2367 );
2368 }
2369 2 => {
2370 part!(
2371 b,
2372 &[b, b + 2],
2373 |a, b, c| predict_mv(a, b, c, ri as i32),
2374 ox,
2375 oy,
2376 4,
2377 8,
2378 ri
2379 );
2380 part!(
2381 b + 1,
2382 &[b + 1, b + 3],
2383 |a, b, c| predict_mv(a, b, c, ri as i32),
2384 ox + 4,
2385 oy,
2386 4,
2387 8,
2388 ri
2389 );
2390 }
2391 _ => {
2392 for j in 0..4usize {
2393 let (sx, sy) = ((j % 2) * 4, (j / 2) * 4);
2394 part!(
2395 b + j,
2396 &[b + j],
2397 |a, b, c| predict_mv(a, b, c, ri as i32),
2398 ox + sx,
2399 oy + sy,
2400 4,
2401 4,
2402 ri
2403 );
2404 }
2405 }
2406 }
2407 }
2408 }
2409 }
2410 if let Some(p) = mb_ref.get_mut(addr) {
2411 *p = mref;
2412 }
2413 if let Some(p) = mb_mvd.get_mut(addr) {
2414 *p = mmvd;
2415 }
2416
2417 // Inter cbp + residual (is_intra = false → cbf default nA=nB=0).
2418 let cbp = parse_cbp_cabac(
2419 &mut cab,
2420 top.and_then(|a| mb_cbp.get(a).copied()),
2421 left.and_then(|a| mb_cbp.get(a).copied()),
2422 );
2423 if let Some(p) = mb_cbp.get_mut(addr) {
2424 *p = cbp as u8;
2425 }
2426 // H-49: an INTER macroblock carries transform_size_8x8_flag AFTER cbp
2427 // (spec 7.3.5), present only when CodedBlockPatternLuma > 0 and
2428 // noSubMbPartSizeLessThan8x8Flag. Same context as the intra read.
2429 let t8 = self.transform_8x8_mode && (cbp & 15) != 0 && allow8 && {
2430 let a = left
2431 .map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2432 let b =
2433 top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2434 cab.decode_decision(399 + a + b) != 0
2435 };
2436 if let Some(p) = self.mb_t8x8.get_mut(addr) {
2437 *p = t8;
2438 }
2439 self.any_t8 |= t8;
2440 // D9c: cbp==0 never parses residuals — skip the 2.5 KB coeff
2441 // zero-init + PInterJob entirely when NORES is on (default).
2442 // Current-MB nzc slots stay unset under cbp==0 and export as 0
2443 // (same as the 0xff→0 scrub below), so mb_nzc = [0;24] is exact.
2444 if cbp == 0 && nores_on() {
2445 last_delta_qp = 0;
2446 if let Some(p) = self.mb_qp.get_mut(addr) {
2447 *p = self.cur_qp;
2448 }
2449 if let Some(p) = cbf_dc.get_mut(addr) {
2450 *p = 0;
2451 }
2452 if let Some(p) = mb_nzc.get_mut(addr) {
2453 *p = [0u8; 24];
2454 }
2455 if self.refs.is_empty() {
2456 return Err(MbError::Unsupported("inter without reference"));
2457 }
2458 let (mut jgmv, mut jgref) = ([(0i32, 0i32); 16], [0u8; 16]);
2459 {
2460 let w4r = self.mb_w * 4;
2461 for by in 0..4usize {
2462 // Row-contiguous — see the coded-inter gather.
2463 let row = (mby * 4 + by) * w4r + mbx * 4;
2464 jgmv[by * 4..by * 4 + 4]
2465 .copy_from_slice(&self.mv_y[row..row + 4]);
2466 // Row-slice the ref grid too - it was the only
2467 // one still indexed per block.
2468 let ridx = &self.ref_idx_y[row..row + 4];
2469 for bx in 0..4usize {
2470 jgref[by * 4 + bx] = ridx[bx].clamp(0, 15) as u8;
2471 }
2472 }
2473 }
2474 let pj = PInterNoResJob {
2475 mbx,
2476 mby,
2477 t8,
2478 gmv: jgmv,
2479 gref: jgref,
2480 };
2481 if self.edc_tx.is_some() {
2482 self.edc_giveback();
2483 self.edc_commit_nnz(mbx, mby, t8, &[0u8; 24], 0);
2484 if edcstat::on() {
2485 edcstat::bump(&edcstat::J_INTER, 1);
2486 edcstat::bump(&edcstat::J_INTER_NORES, 1);
2487 }
2488 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2489 self.edc_send_job(EdcJob::InterNoRes(Box::new(pj)));
2490 } else if self.edc_active {
2491 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2492 self.edc_jobs.push(EdcJob::InterNoRes(Box::new(pj)));
2493 } else {
2494 self.recon_p_inter_nores(&pj);
2495 if double_recon() {
2496 self.recon_p_inter_nores(&pj);
2497 }
2498 }
2499 let eos = cab.decode_terminate();
2500 addr += 1;
2501 mbx += 1;
2502 if eos || addr >= total {
2503 break;
2504 }
2505 continue;
2506 }
2507 // POOLED JOB, BUILT IN PLACE: the residual parse writes straight into
2508 // the boxed job (no 2.8 KB stack-then-heap copy, no malloc/free per
2509 // macroblock), and only the blocks the cbp says will be parsed are
2510 // zeroed -- the consumer reads a block only when its nnz is nonzero.
2511 let mut job = self.take_pinter_job();
2512 job.t8 = t8;
2513 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
2514 let mut nzc = [0xffu8; 48];
2515 if let Some(t) = top {
2516 let tnz = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
2517 nzc[1..5].copy_from_slice(&tnz[12..16]);
2518 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
2519 (nzc[6], nzc[7], nzc[30], nzc[31]) =
2520 (tnz[20], tnz[21], tnz[22], tnz[23]);
2521 }
2522 if let Some(l) = left {
2523 let lnz = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
2524 (nzc[8], nzc[16], nzc[24], nzc[32]) =
2525 (lnz[3], lnz[7], lnz[11], lnz[15]);
2526 (nzc[13], nzc[21], nzc[37], nzc[45]) =
2527 (lnz[17], lnz[21], lnz[19], lnz[23]);
2528 }
2529 let mut cbfdc = 0u16;
2530 job.nnzs = [0u8; 24]; // parsed totalCoeff per block (see add_inter_residual)
2531 // A cbp==0 MB codes no mb_qp_delta → the next MB's delta ctxInc sees 0.
2532 if cbp == 0 {
2533 last_delta_qp = 0;
2534 }
2535 if cbp != 0 {
2536 let ndc = (
2537 top.and_then(|a| cbf_dc.get(a).copied()),
2538 left.and_then(|a| cbf_dc.get(a).copied()),
2539 );
2540 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
2541 self.step_qp(qpd)?;
2542 parse_mb_residual_cabac::<false>(
2543 &mut cab,
2544 &mut nzc,
2545 &mut cbfdc,
2546 ndc,
2547 cbp_luma,
2548 cbp_chroma,
2549 t8,
2550 ResidualOut {
2551 luma: &mut job.luma_scan,
2552 luma8: &mut job.luma8,
2553 cdc: &mut job.cdc,
2554 cac: &mut job.cac,
2555 nnzs: &mut job.nnzs,
2556 },
2557 );
2558 }
2559 if let Some(p) = self.mb_qp.get_mut(addr) {
2560 *p = self.cur_qp;
2561 }
2562 if let Some(p) = cbf_dc.get_mut(addr) {
2563 *p = cbfdc;
2564 }
2565 let _sc = rusty_h264_common::prof::scope(
2566 rusty_h264_common::prof::Stage::DecStateCache,
2567 );
2568 let mn = nnz_raster_from_z(&job.nnzs);
2569 if let Some(p) = mb_nzc.get_mut(addr) {
2570 *p = mn;
2571 }
2572 drop(_sc);
2573
2574 if self.refs.is_empty() {
2575 return Err(MbError::Unsupported("inter without reference"));
2576 }
2577 // NOTE (measured, do not "optimise" this away): EDC is
2578 // DEFAULT ON (`edc_on()`, opt out with RS_H264_EDC=0), so
2579 // even single-threaded this job is built and deferred —
2580 // that batching is the E1 loop-fission win, not overhead.
2581 // A "skip the job in 1T" bypass added here could never fire
2582 // and was removed.
2583 let (mut jgmv, mut jgref) = ([(0i32, 0i32); 16], [0u8; 16]);
2584 {
2585 let w4r = self.mb_w * 4;
2586 for by in 0..4usize {
2587 // Row-contiguous: one slice copy for the MVs
2588 // instead of four bounds-checked strided loads.
2589 let row = (mby * 4 + by) * w4r + mbx * 4;
2590 jgmv[by * 4..by * 4 + 4].copy_from_slice(&self.mv_y[row..row + 4]);
2591 // Row-slice the ref grid too - it was the only one
2592 // still indexed per block.
2593 let ridx = &self.ref_idx_y[row..row + 4];
2594 for bx in 0..4usize {
2595 jgref[by * 4 + bx] = ridx[bx].clamp(0, 15) as u8;
2596 }
2597 }
2598 }
2599 // D9c: when `cbp == 0`, never materialise the 2.5 KB coeff
2600 // arrays into a `PInterJob` — ship `InterNoRes` (or call
2601 // `recon_p_inter_nores` inline). `RS_H264_NORES=0` keeps the
2602 // old full-job path for A/B.
2603 let nores = cbp == 0 && nores_on();
2604 if nores {
2605 let pj = PInterNoResJob {
2606 mbx,
2607 mby,
2608 t8,
2609 gmv: jgmv,
2610 gref: jgref,
2611 };
2612 if self.edc_tx.is_some() {
2613 self.edc_giveback();
2614 self.edc_commit_nnz(mbx, mby, t8, &[0u8; 24], 0);
2615 if edcstat::on() {
2616 edcstat::bump(&edcstat::J_INTER, 1);
2617 edcstat::bump(&edcstat::J_INTER_NORES, 1);
2618 }
2619 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2620 let b = self.take_nores_job(pj);
2621 self.edc_send_job(EdcJob::InterNoRes(b));
2622 } else if self.edc_active {
2623 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2624 let b = self.take_nores_job(pj);
2625 self.edc_jobs.push(EdcJob::InterNoRes(b));
2626 } else {
2627 self.recon_p_inter_nores(&pj);
2628 if double_recon() {
2629 self.recon_p_inter_nores(&pj);
2630 }
2631 }
2632 let eos = cab.decode_terminate();
2633 addr += 1;
2634 mbx += 1;
2635 if eos || addr >= total {
2636 break;
2637 }
2638 continue;
2639 }
2640 job.mbx = mbx;
2641 job.mby = mby;
2642 job.qp = self.cur_qp;
2643 job.cbp_chroma = cbp_chroma;
2644 job.gmv = jgmv;
2645 job.gref = jgref;
2646 if self.edc_tx.is_some() {
2647 self.edc_giveback();
2648 self.edc_commit_nnz(mbx, mby, t8, &job.nnzs, cbp_chroma);
2649 if edcstat::on() {
2650 edcstat::bump(&edcstat::J_INTER, 1);
2651 }
2652 self.edc_send_job(EdcJob::Inter(job));
2653 } else if self.edc_active {
2654 self.edc_jobs.push(EdcJob::Inter(job));
2655 } else {
2656 self.recon_p_inter(&job);
2657 if double_recon() {
2658 self.recon_p_inter(&job);
2659 }
2660 self.edc_job_pool.push(job);
2661 }
2662
2663 let eos = cab.decode_terminate();
2664 addr += 1;
2665 mbx += 1;
2666 if eos || addr >= total {
2667 break;
2668 }
2669 continue;
2670 }
2671 mb_type = mbt - 5; // 5→0 (I_4x4), 6..29→1..24 (I_16x16)
2672 } else if self.is_b {
2673 // NOTE: the per-macroblock edc_flush() that used to sit here is
2674 // hoisted to slice entry (see below) — nothing inside a B slice
2675 // pushes to edc_jobs.
2676 if b_records {
2677 // E3: this B macroblock's MC regions record instead of executing.
2678 self.edc_regions = Some(Vec::with_capacity(8));
2679 }
2680 let _gb =
2681 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbB);
2682 // noSubMbPartSizeLessThan8x8Flag for B: direct MBs qualify only under
2683 // direct_8x8_inference_flag; B_8x8 needs every sub-partition 8x8.
2684 let mut allow8 = true;
2685 // B-slice: mb_skip_flag (ctx 24 + neighbour-not-skip), then B mb_type.
2686 let (hl, ht) = (left.is_some(), top.is_some());
2687 let sctx = 24
2688 + (hl && !mb_skip.get(addr - 1).copied().unwrap_or(true)) as usize
2689 + (ht && !mb_skip.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true))
2690 as usize;
2691 // ONE engine view carries mb_skip_flag, the terminate bin of a skipped
2692 // macroblock, and mb_type of a coded one (was 2-3 view/commit round
2693 // trips of 8 memory ops each per macroblock).
2694 let (data, mut e, ctx) = cab.view();
2695 if e.decode_decision(data, ctx, sctx) != 0 {
2696 if let (Some(sk), Some(di)) =
2697 (mb_skip.get_mut(addr), mb_direct.get_mut(addr))
2698 {
2699 (*sk, *di) = (true, true);
2700 }
2701 last_delta_qp = 0; // skip codes no mb_qp_delta → delta ctxInc resets
2702 // B_Skip recon reuses the entropy-free CAVLC primitive (spatial/temporal
2703 // direct with no residual), which also commits the motion grid.
2704 // Hot prefix inline: forced run continuations skip the call.
2705 if !self.b_skip_hot(mbx, mby) {
2706 self.decode_b_skip(mbx, mby)?;
2707 }
2708 if let Some(p) = self.mb_qp.get_mut(addr) {
2709 *p = self.cur_qp;
2710 }
2711 // Skip/direct blocks contribute mvd 0 to a later MB's mvd ctxInc; the
2712 // ref stays in-list so |mvd|=0 is summed (same result either way).
2713 // mb_ref/mb_ref1 stay at their -1 init: every reader is
2714 // either a `> 0` context test (-1 and 0 both false) or the
2715 // mvd-sum's `>= 0` gate — and a skip's mvd is (0,0), so
2716 // exclusion (-1) and inclusion-of-zero (0) give the same
2717 // sum. The 64-byte per-skip zero-fill was pure waste.
2718 let eos = e.decode_terminate(data);
2719 cab.commit(e);
2720 addr += 1;
2721 mbx += 1;
2722 if eos || addr >= total {
2723 break;
2724 }
2725 continue;
2726 }
2727 // NON-SKIP B MB: every arm below reads the grids INLINE
2728 // (H-48: CABAC never routes through decode_b_mb) — flush
2729 // the deferred spans. Caught by the tempete ARM-DIFF.
2730 self.span_flush();
2731 let bci = (hl && !mb_direct.get(addr - 1).copied().unwrap_or(true)) as usize
2732 + (ht
2733 && !mb_direct
2734 .get(addr.wrapping_sub(mbw))
2735 .copied()
2736 .unwrap_or(true)) as usize;
2737 let bmt = {
2738 let _s = rusty_h264_common::prof::scope(
2739 rusty_h264_common::prof::Stage::BTypeParse,
2740 );
2741 mb_type_b_eng(&mut e, data, ctx, 27, bci)
2742 };
2743 cab.commit(e);
2744 if bmt < 23 {
2745 // ---- B inter: parse motion (mvd L0/L1; ref not coded on this 1-ref
2746 // stream) + residual. Recon (b_mc/direct) deferred to B.3. ----
2747 let mut mvdc0 = [[0i16; 2]; 30];
2748 let mut refc0 = [-1i8; 30];
2749 let mut mvdc1 = [[0i16; 2]; 30];
2750 let mut refc1 = [-1i8; 30];
2751 // WelsFillCacheInterCabac, per list (L0 = mb_ref/mb_mvd, L1 = mb_ref1/mb_mvd1).
2752 // The corner addresses and their guards are macroblock
2753 // properties, resolved ONCE here rather than rebuilt inside
2754 // each list expansion; each neighbour grid row is borrowed
2755 // once instead of re-indexed per entry.
2756 let tl = (mbx > 0 && mby > 0).then(|| addr - mbw - 1);
2757 let tr = (mby > 0 && mbx + 1 < mbw).then(|| addr - mbw + 1);
2758 macro_rules! fill {
2759 ($mrf:expr, $mmv:expr, $rc:expr, $mc:expr) => {{
2760 // `.get` PER PARALLEL GRID. The ref and mvd grids are
2761 // separate Vecs indexed by the same macroblock address,
2762 // so `$mrf[l]` proved nothing about `$mmv[l]` and each
2763 // of the four neighbour slots carried two panic paths —
2764 // doubled again because this macro expands once per
2765 // list. A `None` here degrades to exactly what the
2766 // cache already means by "neighbour unavailable" (the
2767 // `-1` ref it is initialised to), so the fallible form
2768 // is the honest one as well as the cheap one.
2769 if let Some(l) = left {
2770 if let (Some(rr), Some(mm)) = ($mrf.get(l), $mmv.get(l)) {
2771 for (ci, bi) in
2772 [(6usize, 3usize), (12, 7), (18, 11), (24, 15)]
2773 {
2774 $rc[ci] = rr[bi];
2775 $mc[ci] = mm[bi];
2776 }
2777 }
2778 }
2779 if let Some(t) = top {
2780 if let (Some(rr), Some(mm)) = ($mrf.get(t), $mmv.get(t)) {
2781 for (ci, bi) in
2782 [(1usize, 12usize), (2, 13), (3, 14), (4, 15)]
2783 {
2784 $rc[ci] = rr[bi];
2785 $mc[ci] = mm[bi];
2786 }
2787 }
2788 }
2789 if let Some(a) = tl {
2790 if let (Some(rr), Some(mm)) = ($mrf.get(a), $mmv.get(a)) {
2791 ($rc[0], $mc[0]) = (rr[15], mm[15]);
2792 }
2793 }
2794 if let Some(a) = tr {
2795 if let (Some(rr), Some(mm)) = ($mrf.get(a), $mmv.get(a)) {
2796 ($rc[5], $mc[5]) = (rr[12], mm[12]);
2797 }
2798 }
2799 }};
2800 }
2801 {
2802 let _s = rusty_h264_common::prof::scope(
2803 rusty_h264_common::prof::Stage::BFillCache,
2804 );
2805 fill!(mb_ref, mb_mvd, refc0, mvdc0);
2806 fill!(mb_ref1, mb_mvd1, refc1, mvdc1);
2807 }
2808 let mut _smv = Some(rusty_h264_common::prof::scope(
2809 rusty_h264_common::prof::Stage::BMvdParse,
2810 ));
2811 let mut mmvd0 = [[0i16; 2]; 16];
2812 let mut mref0 = [-1i8; 16];
2813 let mut mmvd1 = [[0i16; 2]; 16];
2814 let mut mref1 = [-1i8; 16];
2815 if !b_refs_ok {
2816 return Err(MbError::Unsupported("B without references"));
2817 }
2818 // Recon (mirrors CAVLC decode_b_mb / decode_b_8x8): predict each list's
2819 // MV off the committed grid + the CABAC-parsed mvd, commit, MC (bi-pred
2820 // blend), then add the residual. Prediction reads mmvd0/mmvd1 (the mvd
2821 // per raster block, splatted during the parse above).
2822 // pred_y / c_pred are slice-scope scratch (see the loop head).
2823 // Recording is a per-macroblock property — ask once, not per
2824 // partition, so the 1T path calls b_mc straight through.
2825 let rec_mode = self.edc_regions.is_some();
2826
2827 if bmt == 0 {
2828 // B_Direct_16x16: no coded motion. A direct block contributes mvd 0
2829 // to a later MB's mvd ctxInc with its ref in-list (|0| summed).
2830 if let Some(p) = mb_direct.get_mut(addr) {
2831 *p = true;
2832 }
2833 allow8 = self.direct_8x8_inference;
2834 (mref0, mref1) = ([0i8; 16], [0i8; 16]);
2835 // FORCED derivation via the zero-bi bitmap (same triple
2836 // as b_skip_hot): a direct-16 whose left/top/topright are
2837 // recorded ref0/(0,0) committers derives (0,0)/(0,0) bi —
2838 // skip the gather + derivation, run the region half
2839 // directly. Its own commit is zero-bi too, so it EXTENDS
2840 // forcing chains through coded direct MBs.
2841 // `mbw` is already carried by the loop, and the row-above
2842 // address is one subtraction, not three.
2843 let up = addr.wrapping_sub(mbw);
2844 let forced = !b_records
2845 && mbx > 0
2846 && mby > 0
2847 && mbx + 1 < mbw
2848 && up >= self.slice_first_mb
2849 && match self.bzero.get(..=addr) {
2850 // A slice ENDING at `addr` proves all three
2851 // neighbour reads at once — the guards above put
2852 // every one of them below `addr`. Same shape as
2853 // `b_skip_hot`.
2854 Some(bz) => {
2855 bz.get(addr - 1).copied().unwrap_or(false)
2856 && bz.get(up).copied().unwrap_or(false)
2857 && bz.get(up + 1).copied().unwrap_or(false)
2858 }
2859 None => false,
2860 };
2861 if forced {
2862 self.b_direct_region(
2863 mbx,
2864 mby,
2865 0,
2866 0,
2867 16,
2868 16,
2869 &mut pred_y,
2870 &mut c_pred,
2871 (0, 0, (0, 0), (0, 0), false),
2872 );
2873 if let Some(b) = self.bzero.get_mut(addr) {
2874 *b = true;
2875 }
2876 } else {
2877 self.decode_b_direct(
2878 mbx,
2879 mby,
2880 0,
2881 0,
2882 16,
2883 16,
2884 &mut pred_y,
2885 &mut c_pred,
2886 );
2887 }
2888 } else if bmt == 22 {
2889 // B_8x8: 4 sub_mb_types, (ref not coded on 1-ref), then mvd
2890 // list-major → sub-MB → sub-partition (openh264 order).
2891 let mut subt = [0u32; 4];
2892 for s in &mut subt {
2893 *s = parse_sub_mb_type_b_cabac(&mut cab);
2894 }
2895 let d8i = self.direct_8x8_inference;
2896 allow8 =
2897 subt.iter()
2898 .all(|&t| if t == 0 { d8i } else { (1..=3).contains(&t) });
2899 // uses(list) depends only on the sub_mb_type — resolve the
2900 // whole 4x2 table once here; the ref_idx, mvd and recon
2901 // loops below all read it instead of re-asking.
2902 let su = [
2903 [b_sub_uses(subt[0], 0), b_sub_uses(subt[0], 1)],
2904 [b_sub_uses(subt[1], 0), b_sub_uses(subt[1], 1)],
2905 [b_sub_uses(subt[2], 0), b_sub_uses(subt[2], 1)],
2906 [b_sub_uses(subt[3], 0), b_sub_uses(subt[3], 1)],
2907 ];
2908 // A direct sub-partition contributes mvd 0 / ref in-list to the
2909 // ctxInc — both the per-MB export and the within-MB 30-cache that a
2910 // later (non-direct) sub in this MB reads.
2911 for i in 0..4usize {
2912 if subt[i] == 0 {
2913 let b = i * 4;
2914 for zb in b..b + 4 {
2915 // each table read once, not twice
2916 let (g, c) = (G_SCAN4[zb & 15], CACHE30[zb & 15]);
2917 (mref0[g], mref1[g]) = (0, 0);
2918 (refc0[c], refc1[c]) = (0, 0);
2919 }
2920 }
2921 }
2922 // ref_idx_l0 for all four 8x8s, then ref_idx_l1, then the mvds
2923 // (spec 7.3.5.2 sub_mb_pred). ONE ref per 8x8 -- never per
2924 // sub-partition -- and B_Direct_8x8 codes none.
2925 let mut sref = [[0i8; 2]; 4]; // [sub-MB][list]
2926 for list in 0..2usize {
2927 // Single-reference lists code no ref_idx at all.
2928 if (if list == 0 {
2929 self.num_ref_active
2930 } else {
2931 self.num_ref_active1
2932 }) <= 1
2933 {
2934 continue;
2935 }
2936 let rc = if list == 0 { &mut refc0 } else { &mut refc1 };
2937 for i in 0..4usize {
2938 let st = subt[i];
2939 if st == 0 || !su[i][list] {
2940 continue;
2941 }
2942 let b = i * 4;
2943 let s = CACHE30[b & 15].clamp(6, 29);
2944 let c0 =
2945 (rc[s - 1] > 0) as usize + 2 * (rc[s - 6] > 0) as usize;
2946 let r = parse_ref_idx_cabac(&mut cab, c0);
2947 for &zb in &[b, b + 1, b + 2, b + 3] {
2948 rc[CACHE30[zb]] = r;
2949 }
2950 sref[i][list] = r;
2951 }
2952 }
2953 for list in 0..2usize {
2954 let (mmv, mrf, mc, rc) = if list == 0 {
2955 (&mut mmvd0, &mut mref0, &mut mvdc0, &mut refc0)
2956 } else {
2957 (&mut mmvd1, &mut mref1, &mut mvdc1, &mut refc1)
2958 };
2959 for i in 0..4usize {
2960 let st = subt[i];
2961 if st == 0 || !su[i][list] {
2962 continue;
2963 }
2964 let b = i * 4;
2965 for &(sx, sy, sw, sh) in b_sub_parts(st) {
2966 // Shapes are 8x8 / 8x4 / 4x8 / 4x4, so the block
2967 // list is closed-form: `step` is 1 when the part
2968 // spans both columns, 2 when it spans both rows.
2969 let (w4b, h4b) = (sw / 4, sh / 4);
2970 let base = b + (sy / 4) * 2 + sx / 4;
2971 let step = if w4b == 2 { 1 } else { 2 };
2972 let zb = [base, base + step, base + 2, base + 3];
2973 let n = w4b * h4b;
2974 parse_mvd_partition(
2975 &mut cab,
2976 zb[0],
2977 &zb[..n],
2978 mc,
2979 rc,
2980 mmv,
2981 mrf,
2982 sref[i][list],
2983 );
2984 }
2985 }
2986 }
2987 // Recon each 8×8: direct sub → decode_b_direct; else per sub-part
2988 // predict (median) + commit + MC.
2989 // Spatial-direct A/B/C are MB-level — walk once if any sub is
2990 // direct. `dmemo=0` rewalks every direct 8×8 (A/B oracle).
2991 // Derivation hoist: A/B/C AND the rid/median result are
2992 // MB-level — derive once, run only the per-8x8 region half
2993 // (czg differs per sub) for every direct sub.
2994 let hoisted = if self.direct_spatial
2995 && direct_memo_on()
2996 && subt.iter().any(|&t| t == 0)
2997 {
2998 let (n0, n1) = self.b_direct_nbrs(mbx, mby);
2999 Some(Self::b_direct_refs_mvs(&n0, &n1))
3000 } else {
3001 None
3002 };
3003 for (p, &st) in subt.iter().enumerate() {
3004 let (b8x, b8y) = ((p % 2) * 8, (p / 2) * 8);
3005 if st == 0 {
3006 match hoisted {
3007 Some(derived) => self.b_direct_region(
3008 mbx,
3009 mby,
3010 b8x,
3011 b8y,
3012 8,
3013 8,
3014 &mut pred_y,
3015 &mut c_pred,
3016 derived,
3017 ),
3018 None => self.decode_b_direct(
3019 mbx,
3020 mby,
3021 b8x,
3022 b8y,
3023 8,
3024 8,
3025 &mut pred_y,
3026 &mut c_pred,
3027 ),
3028 }
3029 continue;
3030 }
3031 // From the table built at the top of this arm.
3032 let (u0, u1) = (su[p & 3][0], su[p & 3][1]);
3033 for &(sx, sy, sw, sh) in b_sub_parts(st) {
3034 let (px, py) = (b8x + sx, b8y + sy);
3035 let mut mv = [(0i32, 0i32); 2];
3036 let (bx4, by4) =
3037 ((mbx * 4 + px / 4) as isize, (mby * 4 + py / 4) as isize);
3038 let didx = (py / 4) * 4 + px / 4;
3039 // A BI sub-part gathers both lists at ONE position, so the
3040 // availability work (bounds + coded + slice) is shared —
3041 // the fusion the 16x16 layout path already had.
3042 if u0 && u1 {
3043 let (n0, n1) =
3044 self.mv_neighbors_both(bx4, by4, (sw / 4) as isize);
3045 for (list, nb) in [(0usize, &n0), (1, &n1)] {
3046 let d = (if list == 0 { &mmvd0 } else { &mmvd1 })
3047 [didx & 15];
3048 let pmv = predict_mv(
3049 nb[0],
3050 nb[1],
3051 nb[2],
3052 sref[p & 3][list & 1] as i32,
3053 );
3054 mv[list & 1] =
3055 (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3056 }
3057 } else {
3058 for list in 0..2usize {
3059 if if list == 0 { u0 } else { u1 } {
3060 let d = (if list == 0 { &mmvd0 } else { &mmvd1 })
3061 [didx & 15];
3062 let nb = self.mv_neighbors_list(
3063 bx4,
3064 by4,
3065 (sw / 4) as isize,
3066 list,
3067 );
3068 let pmv = predict_mv(
3069 nb[0],
3070 nb[1],
3071 nb[2],
3072 sref[p & 3][list & 1] as i32,
3073 );
3074 mv[list & 1] =
3075 (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3076 }
3077 }
3078 }
3079 let refi0 = if u0 { sref[p & 3][0] as i32 } else { -1 };
3080 let refi1 = if u1 { sref[p & 3][1] as i32 } else { -1 };
3081 self.b_set_motion(
3082 mbx, mby, px, py, sw, sh, refi0, mv[0], refi1, mv[1],
3083 );
3084 if rec_mode {
3085 self.b_mc_or_record(
3086 mbx,
3087 mby,
3088 px,
3089 py,
3090 sw,
3091 sh,
3092 refi0,
3093 mv[0],
3094 refi1,
3095 mv[1],
3096 &mut pred_y,
3097 &mut c_pred,
3098 );
3099 } else {
3100 self.b_mc(
3101 mbx,
3102 mby,
3103 px,
3104 py,
3105 sw,
3106 sh,
3107 refi0,
3108 mv[0],
3109 refi1,
3110 mv[1],
3111 &mut pred_y,
3112 &mut c_pred,
3113 );
3114 }
3115 }
3116 }
3117 } else {
3118 let (layout, mvmode, preds) = b_inter_layout(bmt);
3119 let parts: &[(usize, &[usize])] = match mvmode {
3120 0 => {
3121 &[(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])]
3122 }
3123 1 => &[
3124 (0, &[0, 1, 2, 3, 4, 5, 6, 7]),
3125 (8, &[8, 9, 10, 11, 12, 13, 14, 15]),
3126 ],
3127 _ => &[
3128 (0, &[0, 1, 2, 3, 8, 9, 10, 11]),
3129 (4, &[4, 5, 6, 7, 12, 13, 14, 15]),
3130 ],
3131 };
3132 // ref_idx_l0 for EVERY partition, then ref_idx_l1, then the mvds
3133 // (spec 7.3.5.1 macroblock_prediction). This was missing entirely
3134 // -- the B path assumed a single reference -- so any B slice with
3135 // more than one active reference in either list desynced the
3136 // arithmetic decoder at the first partition that codes a ref_idx,
3137 // and the slice ended early at a phantom end_of_slice_flag.
3138 let mut pref = [[0i8; 2]; 2]; // [partition][list]
3139 for list in 0..2usize {
3140 let active = if list == 0 {
3141 self.num_ref_active
3142 } else {
3143 self.num_ref_active1
3144 };
3145 if active <= 1 {
3146 continue;
3147 }
3148 let rc = if list == 0 { &mut refc0 } else { &mut refc1 };
3149 for (p, &(pidx, zb)) in parts.iter().enumerate() {
3150 if !preds[p & 1].uses(list) {
3151 continue;
3152 }
3153 let s = CACHE30[pidx & 15].clamp(6, 29);
3154 let c0 =
3155 (rc[s - 1] > 0) as usize + 2 * (rc[s - 6] > 0) as usize;
3156 let r = parse_ref_idx_cabac(&mut cab, c0);
3157 // Seed the cache so a later partition's ref/mvd ctxInc sees it.
3158 for &zbi in zb.iter() {
3159 rc[CACHE30[zbi & 15]] = r;
3160 }
3161 pref[p & 1][list & 1] = r;
3162 }
3163 }
3164 // mvd parse order: list-major, partition-minor (openh264
3165 // ParseInterBMotionInfoCabac); the ctxInc reads the same-list cache.
3166 for list in 0..2usize {
3167 let (mmv, mrf, mc, rc) = if list == 0 {
3168 (&mut mmvd0, &mut mref0, &mut mvdc0, &mut refc0)
3169 } else {
3170 (&mut mmvd1, &mut mref1, &mut mvdc1, &mut refc1)
3171 };
3172 for (p, &(pidx, zb)) in parts.iter().enumerate() {
3173 if preds[p & 1].uses(list) {
3174 parse_mvd_partition(
3175 &mut cab,
3176 pidx,
3177 zb,
3178 mc,
3179 rc,
3180 mmv,
3181 mrf,
3182 pref[p & 1][list & 1],
3183 );
3184 }
3185 }
3186 }
3187 // Per-partition recon: predict each list's MV, commit, MC.
3188 let (mbb_x, mbb_y) = (mbx * 4, mby * 4);
3189 for (p, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
3190 let mut mv = [(0i32, 0i32); 2];
3191 // uses(list) is a property of the layout — resolve both
3192 // once instead of five times per partition, and build
3193 // the neighbour coordinates once instead of per list.
3194 let (u0, u1) = (preds[p & 1].uses(0), preds[p & 1].uses(1));
3195 let (nx, ny) =
3196 ((mbb_x + rx / 4) as isize, (mbb_y + ry / 4) as isize);
3197 let (nw, didx) = ((rw / 4) as isize, (ry / 4) * 4 + rx / 4);
3198 // Bi partitions gather both lists at ONE position —
3199 // fuse the availability work (same trick as
3200 // b_direct_nbrs); uni partitions keep the single
3201 // gather.
3202 if u0 && u1 {
3203 let (n0, n1) = self.mv_neighbors_both(nx, ny, nw);
3204 for (list, n) in [(0usize, &n0), (1, &n1)] {
3205 let d =
3206 (if list == 0 { &mmvd0 } else { &mmvd1 })[didx & 15];
3207 let pmv = predict_partition_mv(
3208 mvmode,
3209 p,
3210 n[0],
3211 n[1],
3212 n[2],
3213 pref[p & 1][list & 1] as i32,
3214 );
3215 mv[list & 1] = (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3216 }
3217 } else {
3218 for list in 0..2usize {
3219 if if list == 0 { u0 } else { u1 } {
3220 let d = (if list == 0 { &mmvd0 } else { &mmvd1 })
3221 [didx & 15];
3222 let n = self.mv_neighbors_list(nx, ny, nw, list);
3223 let pmv = predict_partition_mv(
3224 mvmode,
3225 p,
3226 n[0],
3227 n[1],
3228 n[2],
3229 pref[p & 1][list & 1] as i32,
3230 );
3231 mv[list & 1] =
3232 (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3233 }
3234 }
3235 }
3236 let refi0 = if u0 { pref[p & 1][0] as i32 } else { -1 };
3237 let refi1 = if u1 { pref[p & 1][1] as i32 } else { -1 };
3238 self.b_set_motion(
3239 mbx, mby, rx, ry, rw, rh, refi0, mv[0], refi1, mv[1],
3240 );
3241 // Proper spec bi-prediction (average of L0+L1). NOTE: the CAVLC
3242 // decode_b_mb replicates an openh264 bug here for a Bi 16×8/8×16
3243 // partition; our pixel gate is ffmpeg (spec-correct), so we do NOT.
3244 if rec_mode {
3245 self.b_mc_or_record(
3246 mbx,
3247 mby,
3248 rx,
3249 ry,
3250 rw,
3251 rh,
3252 refi0,
3253 mv[0],
3254 refi1,
3255 mv[1],
3256 &mut pred_y,
3257 &mut c_pred,
3258 );
3259 } else {
3260 self.b_mc(
3261 mbx,
3262 mby,
3263 rx,
3264 ry,
3265 rw,
3266 rh,
3267 refi0,
3268 mv[0],
3269 refi1,
3270 mv[1],
3271 &mut pred_y,
3272 &mut c_pred,
3273 );
3274 }
3275 }
3276 }
3277 // Paired stores: one index expression per grid pair.
3278 // Four per-macroblock grids at one address; each is its own
3279 // Vec, so each assignment carried its own check.
3280 if let (Some(r0), Some(d0)) = (mb_ref.get_mut(addr), mb_mvd.get_mut(addr)) {
3281 (*r0, *d0) = (mref0, mmvd0);
3282 }
3283 if let (Some(r1), Some(d1)) = (mb_ref1.get_mut(addr), mb_mvd1.get_mut(addr))
3284 {
3285 (*r1, *d1) = (mref1, mmvd1);
3286 }
3287 _smv = None; // close b:mvd-parse; the residual half follows
3288 let _sres =
3289 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BResid);
3290
3291 // Inter cbp + residual (identical to the P path).
3292 let cbp = parse_cbp_cabac(
3293 &mut cab,
3294 top.and_then(|a| mb_cbp.get(a).copied()),
3295 left.and_then(|a| mb_cbp.get(a).copied()),
3296 );
3297 if let Some(p) = mb_cbp.get_mut(addr) {
3298 *p = cbp as u8;
3299 }
3300 // H-49: an INTER macroblock carries transform_size_8x8_flag AFTER cbp
3301 // (spec 7.3.5), present only when CodedBlockPatternLuma > 0 and
3302 // noSubMbPartSizeLessThan8x8Flag. Same context as the intra read.
3303 let t8 = self.transform_8x8_mode && (cbp & 15) != 0 && allow8 && {
3304 let a = left
3305 .map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3306 let b =
3307 top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3308 cab.decode_decision(399 + a + b) != 0
3309 };
3310 if let Some(p) = self.mb_t8x8.get_mut(addr) {
3311 *p = t8;
3312 }
3313 self.any_t8 |= t8;
3314 // D9c-B: coded B with cbp==0 never parses residuals — skip the
3315 // ~2.5 KB coeff zero-init + fat BJob when NORES is on (default).
3316 // MC already filled pred_y / edc_regions; recon == pred (same as
3317 // B_Skip / P InterNoRes). t8 is always false here ((cbp&15)==0).
3318 if cbp == 0 && nores_on() {
3319 last_delta_qp = 0;
3320 if let Some(p) = self.mb_qp.get_mut(addr) {
3321 *p = self.cur_qp;
3322 }
3323 if let Some(p) = cbf_dc.get_mut(addr) {
3324 *p = 0;
3325 }
3326 if let Some(p) = mb_nzc.get_mut(addr) {
3327 *p = [0u8; 24];
3328 }
3329 if let Some(regions) = self.edc_regions.take() {
3330 self.edc_giveback();
3331 self.edc_commit_nnz(mbx, mby, false, &[0u8; 24], 0);
3332 edcstat::bump(&edcstat::J_NORES_SENT, 1);
3333 self.edc_send_job(EdcJob::BSkip { mbx, mby, regions });
3334 } else {
3335 // Inline twin of decode_b_skip's plane copy (MC already done).
3336 for dy in 0..16 {
3337 let d = (mby * 16 + dy) * self.cw + mbx * 16;
3338 self.rec_y[d..d + 16]
3339 .copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
3340 }
3341 for c in 0..2 {
3342 let plane = if c == 0 {
3343 &mut self.rec_u
3344 } else {
3345 &mut self.rec_v
3346 };
3347 for dy in 0..8 {
3348 let d = (mby * 8 + dy) * self.ccw + mbx * 8;
3349 plane[d..d + 8]
3350 .copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
3351 }
3352 }
3353 let w4 = self.mb_w * 4;
3354 for dy in 0..4 {
3355 self.nnz_y[(mby * 4 + dy) * w4 + mbx * 4..][..4].fill(0);
3356 }
3357 }
3358 let eos = cab.decode_terminate();
3359 addr += 1;
3360 mbx += 1;
3361 if eos || addr >= total {
3362 break;
3363 }
3364 continue;
3365 }
3366 // Scratch planes (see `scratch_luma`): zeroed per coded block by the parse.
3367 let mut luma8 = self
3368 .scratch_luma8
3369 .take()
3370 .unwrap_or_else(|| Box::new([[0i32; 64]; 4]));
3371 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
3372 let mut nzc = [0xffu8; 48];
3373 if let Some(t) = top {
3374 let tnz = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3375 nzc[1..5].copy_from_slice(&tnz[12..16]);
3376 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3377 (nzc[6], nzc[7], nzc[30], nzc[31]) =
3378 (tnz[20], tnz[21], tnz[22], tnz[23]);
3379 }
3380 if let Some(l) = left {
3381 let lnz = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3382 (nzc[8], nzc[16], nzc[24], nzc[32]) =
3383 (lnz[3], lnz[7], lnz[11], lnz[15]);
3384 (nzc[13], nzc[21], nzc[37], nzc[45]) =
3385 (lnz[17], lnz[21], lnz[19], lnz[23]);
3386 }
3387 let mut cbfdc = 0u16;
3388 let mut nnzs = [0u8; 24]; // parsed totalCoeff per block
3389 let mut luma_scan = self
3390 .scratch_luma
3391 .take()
3392 .unwrap_or_else(|| Box::new([[0i32; 16]; 16]));
3393 let mut cdc = [[0i32; 4]; 2];
3394 let mut cac = self
3395 .scratch_cac
3396 .take()
3397 .unwrap_or_else(|| Box::new([[[0i32; 16]; 4]; 2]));
3398 if cbp == 0 {
3399 last_delta_qp = 0;
3400 }
3401 if cbp != 0 {
3402 let ndc = (
3403 top.and_then(|a| cbf_dc.get(a).copied()),
3404 left.and_then(|a| cbf_dc.get(a).copied()),
3405 );
3406 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3407 self.step_qp(qpd)?;
3408 parse_mb_residual_cabac::<false>(
3409 &mut cab,
3410 &mut nzc,
3411 &mut cbfdc,
3412 ndc,
3413 cbp_luma,
3414 cbp_chroma,
3415 t8,
3416 ResidualOut {
3417 luma: &mut luma_scan,
3418 luma8: &mut luma8,
3419 cdc: &mut cdc,
3420 cac: &mut cac,
3421 nnzs: &mut nnzs,
3422 },
3423 );
3424 }
3425 if let Some(p) = self.mb_qp.get_mut(addr) {
3426 *p = self.cur_qp;
3427 }
3428 if let Some(p) = cbf_dc.get_mut(addr) {
3429 *p = cbfdc;
3430 }
3431 let _sc = rusty_h264_common::prof::scope(
3432 rusty_h264_common::prof::Stage::DecStateCache,
3433 );
3434 let mn = nnz_raster_from_z(&nnzs);
3435 if let Some(p) = mb_nzc.get_mut(addr) {
3436 *p = mn;
3437 }
3438 drop(_sc);
3439 if let Some(regions) = self.edc_regions.take() {
3440 self.edc_giveback();
3441 self.edc_commit_nnz(mbx, mby, t8, &nnzs, cbp_chroma);
3442 let job = BJob {
3443 mbx,
3444 mby,
3445 qp: self.cur_qp,
3446 cbp_chroma,
3447 skip: false,
3448 regions,
3449 // MT-only copies out of the scratch planes (the worker owns its job).
3450 luma_scan: (!t8 && cbp_luma != 0).then(|| *luma_scan),
3451 luma8: t8.then(|| *luma8),
3452 cdc,
3453 cac: (cbp_chroma == 2).then(|| *cac),
3454 nnzs,
3455 };
3456 self.edc_send_job(EdcJob::B(Box::new(job)));
3457 } else {
3458 self.add_inter_residual(
3459 mbx,
3460 mby,
3461 &pred_y,
3462 &c_pred,
3463 Some(&*luma_scan),
3464 t8.then_some(&*luma8),
3465 &cdc,
3466 (cbp_chroma == 2).then_some(&*cac),
3467 cbp_chroma,
3468 &nnzs,
3469 );
3470 }
3471 self.scratch_luma = Some(luma_scan);
3472 self.scratch_luma8 = Some(luma8);
3473 self.scratch_cac = Some(cac);
3474
3475 let eos = cab.decode_terminate();
3476 addr += 1;
3477 mbx += 1;
3478 if eos || addr >= total {
3479 break;
3480 }
3481 continue;
3482 }
3483 mb_type = bmt - 23; // 23→0 (I_4x4), 24..=47→1..24 (I_16x16), 48→25 (PCM)
3484 } else {
3485 let li = left.map_or(0, |a| cat.get(a).is_some_and(|&c| c >= 2) as usize);
3486 let ti = top.map_or(0, |a| cat.get(a).is_some_and(|&c| c >= 2) as usize);
3487 mb_type = parse_mb_type_i_cabac(&mut cab, li + ti);
3488 }
3489 // H-48: the CABAC intra path is INLINED in this loop, not routed through
3490 // `decode_intra_mb` (which only the CAVLC readers call) — wiring the scope
3491 // there reported ZERO calls against 480,510 intra-pred calls. All three
3492 // intra entries (I-slice, P-slice mb_type>3, B-slice bmt>=23) converge
3493 // here, so this is the one point that sees every intra MB.
3494 self.edc_intra_sync(); // intra reconstruction reads neighbour PIXELS
3495 // Intra prediction ALSO reads the coded_y/modes_y/inter_y grids a
3496 // deferred span still owes (this arm is inline — it never passes
3497 // decode_b_mb's flush). Caught by the tempete ARM-DIFF.
3498 self.span_flush();
3499 let _gi = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbI);
3500 // Intra bS is a constant pattern (4 on MB edges, 3 internal) — written
3501 // here because CABAC inlines I recon and never calls decode_intra_mb.
3502 if let Some(p) = self.mb_kind.get_mut(mby * self.mb_w + mbx) {
3503 *p = rusty_h264_common::deblock::MB_KIND_INTRA;
3504 }
3505 if mb_type == 25 {
3506 // ---- I_PCM (spec §7.3.5): 384 raw byte-aligned sample bytes inside
3507 // the CABAC stream. The PCM marker was a terminate bin, so the engine
3508 // has stopped; DecodeFlush + pcm_alignment_zero_bit put the samples
3509 // at `pcm_start_byte()`, and the engine re-initialises after them
3510 // with its CONTEXTS KEPT (§9.3.1). All three slice types (I, P via
3511 // mbt 30, B via bmt 48) reach here as mb_type 25.
3512 let pcm = cab.pcm_start_byte();
3513 let end = pcm + 384;
3514 if end > rbsp.len() {
3515 return Err(MbError::Truncated);
3516 }
3517 let mut pr = BitReader::new(&rbsp[pcm..end]);
3518 self.decode_ipcm(&mut pr, mbx, mby)?;
3519 cab.reinit_at(end);
3520 // Neighbour context (§7.4.5 + §9.3.3.1.1.x inferences): intra, QPy
3521 // unchanged (no mb_qp_delta — its ctxInc resets), CodedBlockPattern
3522 // luma/chroma inferred 15/2, every coded_block_flag (incl. DC) 1,
3523 // nnz 16, chroma pred mode 0.
3524 if let Some(p) = self.mb_qp.get_mut(addr) {
3525 *p = self.cur_qp;
3526 }
3527 if let Some(p) = cat.get_mut(addr) {
3528 *p = 25;
3529 }
3530 if let Some(p) = cmode.get_mut(addr) {
3531 *p = 0;
3532 }
3533 if let Some(p) = mb_cbp.get_mut(addr) {
3534 *p = 0x2f;
3535 }
3536 if let Some(p) = cbf_dc.get_mut(addr) {
3537 *p = (1 << RP_I16_DC) | (1 << RP_CHROMA_DC) | (1 << (RP_CHROMA_DC + 1));
3538 }
3539 if let Some(p) = mb_nzc.get_mut(addr) {
3540 *p = [16u8; 24];
3541 }
3542 last_delta_qp = 0;
3543 let eos = cab.decode_terminate();
3544 addr += 1;
3545 mbx += 1;
3546 if eos || addr >= total {
3547 break;
3548 }
3549 continue;
3550 }
3551 // chroma-pred-mode ctxInc from neighbour chroma modes (1..=3).
3552 let cci = left.map_or(0, |a| {
3553 cmode.get(a).is_some_and(|c| (1..=3).contains(c)) as usize
3554 }) + top.map_or(0, |a| {
3555 cmode.get(a).is_some_and(|c| (1..=3).contains(c)) as usize
3556 });
3557
3558 if mb_type != 0 {
3559 // ---- I_16x16 (mb_type 1..=24): pred mode & cbp DERIVED from mb_type;
3560 // luma DC always coded. Syntax order: intra_chroma_pred_mode, mb_qp_delta,
3561 // luma DC (Hadamard), luma AC (if cbp_luma), chroma DC/AC. Mirrors the CAVLC
3562 // decode_i16, driven by the CABAC residual. ----
3563 let mt = mb_type - 1;
3564 let pred_mode = I16Mode::from_id(mt % 4);
3565 let cbp_chroma = (mt % 12) / 4;
3566 let cbp_luma_15 = mt / 12 == 1;
3567 let chroma_mode = parse_intra_chroma_pred_mode_cabac(&mut cab, cci) as u8;
3568 if let Some(p) = cmode.get_mut(addr) {
3569 *p = chroma_mode as i32;
3570 }
3571 if let Some(p) = cat.get_mut(addr) {
3572 *p = 2;
3573 }
3574 if let Some(p) = mb_cbp.get_mut(addr) {
3575 *p = ((cbp_chroma as u8) << 4) | if cbp_luma_15 { 15 } else { 0 };
3576 }
3577 let w4 = self.mb_w * 4;
3578
3579 let mut nzc = [0xffu8; 48];
3580 if let Some(t) = top {
3581 let tn = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3582 nzc[1..5].copy_from_slice(&tn[12..16]);
3583 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3584 (nzc[6], nzc[7]) = (tn[20], tn[21]);
3585 (nzc[30], nzc[31]) = (tn[22], tn[23]);
3586 }
3587 if let Some(l) = left {
3588 let ln = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3589 (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
3590 (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
3591 }
3592
3593 let ndc = (
3594 top.and_then(|a| cbf_dc.get(a).copied()),
3595 left.and_then(|a| cbf_dc.get(a).copied()),
3596 );
3597 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3598 self.step_qp(qpd)?;
3599 let qp = self.cur_qp;
3600 let mut cbfdc = 0u16;
3601 let nd = resolve_ndc(ndc, true);
3602 let (data, mut e, ctx) = cab.view();
3603
3604 // Luma DC (iz=0, category I16_LUMA_DC, 16 coeffs) → Hadamard dequant.
3605 let mut dc_scan = [0i32; 16];
3606 residual_block_eng::<RP_I16_DC, 16>(
3607 &mut e,
3608 data,
3609 ctx,
3610 &mut nzc,
3611 &mut cbfdc,
3612 0,
3613 0,
3614 true,
3615 nd,
3616 &mut dc_scan,
3617 );
3618 // FUSED scan-order DC: un-scan + Hadamard + scale in one kernel (was 32 moves + 253 instrs).
3619 let recon_dc = inverse_quant_luma_dc_scan(
3620 &dc_scan,
3621 qp,
3622 self.scaling.as_ref().map(|s| s[0][0]),
3623 );
3624
3625 // Luma AC (iz 0..15, category I16_LUMA_AC, 15 coeffs) when cbp_luma set.
3626 // Materialised only when AC is actually coded — a DC-only
3627 // I_16x16 zeroed 1 KB of stack for nothing.
3628 let mut q_blocks: Option<[[i32; 16]; 16]> = None;
3629 let mut coded16 = 0u16;
3630 for (iz, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3631 let total = if cbp_luma_15 {
3632 let mut ac = [0i32; 16];
3633 let t = residual_block_eng::<RP_I16_AC, 16>(
3634 &mut e, data, ctx, &mut nzc, &mut cbfdc, iz, 0, true, nd, &mut ac,
3635 );
3636 q_blocks.get_or_insert_with(|| [[0i32; 16]; 16])
3637 [(lby & 3) * 4 + (lbx & 3)] = ac; // SCAN order (fused kernel)
3638 t as u8
3639 } else {
3640 nzc[NZC_CACHE[iz.min(23)].min(47)] = 0;
3641 0
3642 };
3643 coded16 |= ((total != 0) as u16) << ((lby & 3) * 4 + (lbx & 3));
3644 if let Some(p) = self.nnz_y.get_mut((mby * 4 + lby) * w4 + (mbx * 4 + lbx))
3645 {
3646 *p = total;
3647 }
3648 }
3649
3650 let mut cdc = [[0i32; 4]; 2];
3651 let mut cac: Option<[[[i32; 16]; 4]; 2]> = None;
3652 let mut cnnz = [0u8; 8];
3653 if cbp_chroma >= 1 {
3654 for i in 0..2usize {
3655 residual_block_eng::<RP_CHROMA_DC, 4>(
3656 &mut e,
3657 data,
3658 ctx,
3659 &mut nzc,
3660 &mut cbfdc,
3661 16 + i * 4,
3662 i,
3663 true,
3664 nd,
3665 &mut cdc[i],
3666 );
3667 }
3668 }
3669 if cbp_chroma == 2 {
3670 let cacm = cac.get_or_insert_with(|| [[[0i32; 16]; 4]; 2]);
3671 for i in 0..2usize {
3672 for id4 in 0..4usize {
3673 cnnz[(i * 4 + id4) & 7] = residual_block_eng::<RP_CHROMA_AC, 16>(
3674 &mut e,
3675 data,
3676 ctx,
3677 &mut nzc,
3678 &mut cbfdc,
3679 16 + i * 4 + id4,
3680 i,
3681 true,
3682 nd,
3683 &mut cacm[i][id4],
3684 ) as u8;
3685 }
3686 }
3687 }
3688
3689 cab.commit(e);
3690 // Luma recon: 16×16 intra prediction, then per-4×4 (dequant AC + injected DC).
3691 let top_ok = mby > 0
3692 && self.nbr_in_slice(mbx, mby - 1)
3693 && self.intra_nbr_ok(mbx * 4, mby * 4 - 1);
3694 let left_ok = mbx > 0
3695 && self.nbr_in_slice(mbx - 1, mby)
3696 && self.intra_nbr_ok(mbx * 4 - 1, mby * 4);
3697 self.recon_i16_luma(
3698 mbx,
3699 mby,
3700 pred_mode,
3701 top_ok,
3702 left_ok,
3703 q_blocks.as_ref(),
3704 coded16,
3705 &recon_dc,
3706 qp,
3707 );
3708 self.recon_chroma_cabac(
3709 mbx,
3710 mby,
3711 chroma_mode,
3712 &cdc,
3713 cac.as_ref(),
3714 &cnnz,
3715 cbp_chroma,
3716 top_ok,
3717 left_ok,
3718 );
3719
3720 if let Some(p) = self.mb_qp.get_mut(addr) {
3721 *p = self.cur_qp;
3722 }
3723 if let Some(p) = cbf_dc.get_mut(addr) {
3724 *p = cbfdc;
3725 }
3726 let mut mn = [0u8; 24];
3727 for k in 0..4 {
3728 mn[k] = nzc[9 + k];
3729 mn[4 + k] = nzc[17 + k];
3730 mn[8 + k] = nzc[25 + k];
3731 mn[12 + k] = nzc[33 + k];
3732 }
3733 (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
3734 (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
3735 for v in mn.iter_mut() {
3736 if *v == 0xff {
3737 *v = 0;
3738 }
3739 }
3740 if let Some(p) = mb_nzc.get_mut(addr) {
3741 *p = mn;
3742 }
3743
3744 let eos = cab.decode_terminate();
3745 addr += 1;
3746 mbx += 1;
3747 if eos || addr >= total {
3748 break;
3749 }
3750 continue;
3751 }
3752 if let Some(p) = cat.get_mut(addr) {
3753 *p = 0;
3754 }
3755 let w4 = self.mb_w * 4;
3756 // H-49: transform_size_8x8_flag. For I_NxN it precedes the intra pred
3757 // modes (spec §7.3.5); ctxIdx = 399 + condTermFlagA + condTermFlagB,
3758 // each 1 when that neighbour MB carries the flag. Omitting this read is
3759 // what desynced every High-profile stream.
3760 let t8 = self.transform_8x8_mode && {
3761 let a = left.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3762 let b = top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3763 cab.decode_decision(399 + a + b) != 0
3764 };
3765 if let Some(p) = self.mb_t8x8.get_mut(addr) {
3766 *p = t8;
3767 }
3768 self.any_t8 |= t8;
3769 // Hoisted ABOVE the mode loop (it was computed after it) so the
3770 // per-block mode prediction can use it - see `predict_i4_mode_fast`.
3771 let top_ok = mby > 0
3772 && self.nbr_in_slice(mbx, mby - 1)
3773 && self.intra_nbr_ok(mbx * 4, mby * 4 - 1);
3774 let left_ok = mbx > 0
3775 && self.nbr_in_slice(mbx - 1, mby)
3776 && self.intra_nbr_ok(mbx * 4 - 1, mby * 4);
3777 // Brick 2.4 + recon: derive & store each intra mode (prev-flag → the
3778 // neighbour-predicted mode, else rem), exactly as the CAVLC path.
3779 let mut modes = [2u8; 16]; // raster [lby*4+lbx]
3780 let mut modes8 = [2u8; 4]; // one per 8×8 when t8
3781 // ONE engine view for all 16 (or 4) pred-mode reads of the macroblock.
3782 let (data, mut e, ctx) = cab.view();
3783 if t8 {
3784 // One mode per 8×8, broadcast to its four 4×4 cells so neighbour
3785 // mode prediction keeps working unchanged.
3786 for b8 in 0..4usize {
3787 let (b8x, b8y) = (b8 % 2, b8 / 2);
3788 let (bx, by) = (mbx * 4 + b8x * 2, mby * 4 + b8y * 2);
3789 let predicted =
3790 self.predict_i4_mode_fast(bx, by, b8x * 2, b8y * 2, top_ok, left_ok);
3791 let rr = intra4x4_pred_mode_eng(&mut e, data, ctx);
3792 let actual = if rr < 0 {
3793 predicted
3794 } else {
3795 let rem = rr as u8;
3796 if rem < predicted {
3797 rem
3798 } else {
3799 rem + 1
3800 }
3801 };
3802 modes8[b8] = actual;
3803 for dy in 0..2 {
3804 for dx in 0..2 {
3805 if let Some(p) = self.modes_y.get_mut((by + dy) * w4 + (bx + dx)) {
3806 *p = actual;
3807 }
3808 modes[(b8y * 2 + dy) * 4 + (b8x * 2 + dx)] = actual;
3809 }
3810 }
3811 }
3812 } else {
3813 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
3814 let (bx, by) = (mbx * 4 + lbx, mby * 4 + lby);
3815 let predicted =
3816 self.predict_i4_mode_fast(bx, by, lbx, lby, top_ok, left_ok);
3817 let rr = intra4x4_pred_mode_eng(&mut e, data, ctx);
3818 let actual = if rr < 0 {
3819 predicted
3820 } else {
3821 let rem = rr as u8;
3822 if rem < predicted {
3823 rem
3824 } else {
3825 rem + 1
3826 }
3827 };
3828 if let Some(m) = self.modes_y.get_mut(by * w4 + bx) {
3829 *m = actual;
3830 }
3831 modes[(lby & 3) * 4 + (lbx & 3)] = actual;
3832 }
3833 }
3834 cab.commit(e);
3835 let chroma_mode = parse_intra_chroma_pred_mode_cabac(&mut cab, cci) as u8;
3836 if let Some(p) = cmode.get_mut(addr) {
3837 *p = chroma_mode as i32;
3838 }
3839 let cbp = parse_cbp_cabac(
3840 &mut cab,
3841 top.and_then(|a| mb_cbp.get(a).copied()),
3842 left.and_then(|a| mb_cbp.get(a).copied()),
3843 );
3844 if let Some(p) = mb_cbp.get_mut(addr) {
3845 *p = cbp as u8;
3846 }
3847 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
3848
3849 // Build the padded nzc cache from neighbours (openh264 WelsFillCacheNonZeroCount).
3850 let mut nzc = [0xffu8; 48];
3851 if let Some(t) = top {
3852 let tn = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3853 nzc[1..5].copy_from_slice(&tn[12..16]);
3854 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3855 (nzc[6], nzc[7]) = (tn[20], tn[21]);
3856 (nzc[30], nzc[31]) = (tn[22], tn[23]);
3857 }
3858 if let Some(l) = left {
3859 let ln = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3860 (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
3861 (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
3862 }
3863
3864 // Bricks 2.6 + 2.7: mb_qp_delta + residual (I_4x4 luma 4×4 + chroma DC/AC),
3865 // storing scan-order coefficients for recon.
3866 let mut cbfdc = 0u16;
3867 // Both materialised only when the parse writes them: an I_8x8
3868 // macroblock never touches luma_scan (it carries luma8), and
3869 // cbp_chroma < 2 never touches cac.
3870 // Scratch planes (see `scratch_luma`): zeroed per coded block by the parse.
3871 let mut luma_scan = self
3872 .scratch_luma
3873 .take()
3874 .unwrap_or_else(|| Box::new([[0i32; 16]; 16]));
3875 let mut luma8 = self
3876 .scratch_luma8
3877 .take()
3878 .unwrap_or_else(|| Box::new([[0i32; 64]; 4]));
3879 let mut cdc = [[0i32; 4]; 2];
3880 let mut cac = self
3881 .scratch_cac
3882 .take()
3883 .unwrap_or_else(|| Box::new([[[0i32; 16]; 4]; 2]));
3884 let mut nnzs = [0u8; 24]; // parse-side per-block coeff counts
3885 if cbp == 0 {
3886 last_delta_qp = 0;
3887 }
3888 if cbp != 0 {
3889 let ndc = (
3890 top.and_then(|a| cbf_dc.get(a).copied()),
3891 left.and_then(|a| cbf_dc.get(a).copied()),
3892 );
3893 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3894 self.step_qp(qpd)?;
3895 parse_mb_residual_cabac::<true>(
3896 &mut cab,
3897 &mut nzc,
3898 &mut cbfdc,
3899 ndc,
3900 cbp_luma,
3901 cbp_chroma,
3902 t8,
3903 ResidualOut {
3904 luma: &mut luma_scan,
3905 luma8: &mut luma8,
3906 cdc: &mut cdc,
3907 cac: &mut cac,
3908 nnzs: &mut nnzs,
3909 },
3910 );
3911 if t8 {
3912 // Per-cell nnz for the deblock/neighbour readers: the 8x8 total
3913 // (0 for an uncoded 8x8) in each of its four 4x4 cells.
3914 for id8 in 0..4usize {
3915 let n = nnzs[id8 * 4];
3916 let (b8x, b8y) = (id8 % 2, id8 / 2);
3917 for sy in 0..2 {
3918 for sx in 0..2 {
3919 if let Some(p) = self.nnz_y.get_mut(
3920 (mby * 4 + b8y * 2 + sy) * w4 + (mbx * 4 + b8x * 2 + sx),
3921 ) {
3922 *p = n;
3923 }
3924 }
3925 }
3926 }
3927 }
3928 }
3929 if let Some(p) = self.mb_qp.get_mut(addr) {
3930 *p = self.cur_qp;
3931 }
3932 if let Some(p) = cbf_dc.get_mut(addr) {
3933 *p = cbfdc;
3934 }
3935 // Extract the MB's nzc (raster luma + chroma) for future neighbours.
3936 let mn = nnz_raster_from_z(&nnzs);
3937 if let Some(p) = mb_nzc.get_mut(addr) {
3938 *p = mn;
3939 }
3940
3941 // ---- Brick 4.3a: recon (I_4x4 luma + chroma) via the CAVLC-proven primitives.
3942 let qp = self.cur_qp;
3943
3944 if t8 {
3945 // I_8x8 recon, reusing the CAVLC-proven primitives verbatim
3946 // (un_scan_8x8 / inv_quant8 / gather_i8 / intra8x8_pred /
3947 // add_residual_8x8). Only the ENTROPY half differed.
3948 for b8 in 0..4usize {
3949 let (b8x, b8y) = (b8 % 2, b8 / 2);
3950 let (bx, by) = (mbx * 4 + b8x * 2, mby * 4 + b8y * 2);
3951 let coded = cbp_luma & (1 << b8) != 0;
3952 let avail_top = b8y > 0 || top_ok;
3953 let avail_left = b8x > 0 || left_ok;
3954 self.recon_i8_block(
3955 bx,
3956 by,
3957 modes8[b8],
3958 avail_top,
3959 avail_left,
3960 coded.then_some(&luma8[b8]),
3961 qp,
3962 );
3963 for sy in 0..2 {
3964 // Row fill: the 2x2 cell block is two contiguous PAIRS.
3965 self.coded_y[(by + sy) * w4 + bx..][..2].fill(true);
3966 }
3967 }
3968 }
3969 // The `t8` guard was INSIDE the loop (breaking on the first
3970 // iteration) and the `luma_scan` Option was re-resolved on EVERY one
3971 // of the sixteen blocks. Both are macroblock-invariant.
3972 if !t8 {
3973 let scans = &*luma_scan;
3974 // Residual ladders decided up front (`i4_prepare`), then the serial
3975 // predict+add walk with the SIMD IDCT+add kernel per coded block.
3976 let lnnz: [u8; 16] = core::array::from_fn(|i| nnzs[i]);
3977 let kinds = self.i4_prepare(scans, &lnnz, qp);
3978 let dq0 = self.dq_const(qp, 0);
3979 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3980 let (bx, by) = (mbx * 4 + lbx, mby * 4 + lby);
3981 let at = lby > 0 || top_ok;
3982 let al = lbx > 0 || left_ok;
3983 self.recon_i4_block_res(
3984 bx,
3985 by,
3986 modes[(lby & 3) * 4 + (lbx & 3)],
3987 at,
3988 al,
3989 kinds[blk & 15],
3990 scans,
3991 &dq0,
3992 );
3993 }
3994 // Per-MB ROW COPIES for nnz_y (from the raster `mn` already built) and
3995 // coded_y (routing round): sixteen + sixteen checked scattered stores
3996 // per macroblock -> four + four row operations. Nothing inside this
3997 // macroblock reads either grid for its own blocks (interior top-right
3998 // availability is the z-order constant now).
3999 for ry in 0..4usize {
4000 let a = (mby * 4 + ry) * w4 + mbx * 4;
4001 self.nnz_y[a..a + 4].copy_from_slice(&mn[ry * 4..ry * 4 + 4]);
4002 self.coded_y[a..a + 4].fill(true);
4003 }
4004 }
4005 self.recon_chroma_cabac(
4006 mbx,
4007 mby,
4008 chroma_mode,
4009 &cdc,
4010 (cbp_chroma == 2).then_some(&*cac),
4011 nnzs[16..24].try_into().expect("8 chroma counts"),
4012 cbp_chroma,
4013 top_ok,
4014 left_ok,
4015 );
4016 self.scratch_luma = Some(luma_scan);
4017 self.scratch_luma8 = Some(luma8);
4018 self.scratch_cac = Some(cac);
4019
4020 // Brick 2.1: end_of_slice_flag.
4021 let eos = cab.decode_terminate();
4022 addr += 1;
4023 mbx += 1;
4024 if eos || addr >= total {
4025 break;
4026 }
4027 }
4028 }
4029 if trace {
4030 eprintln!("# CABAC decoded {} MBs (of {total})", addr - first_mb);
4031 }
4032 self.edc_flush(); // slice end: no job crosses a slice boundary
4033 // Return the pooled slice scratch (an error exit forfeits it — the
4034 // next slice then refills fresh allocations, correctness unchanged).
4035 self.sc_cat = cat;
4036 self.sc_cbp = mb_cbp;
4037 self.sc_cmode = cmode;
4038 self.sc_nzc = mb_nzc;
4039 self.sc_cbfdc = cbf_dc;
4040 self.sc_skip = mb_skip;
4041 self.sc_ref = mb_ref;
4042 self.sc_mvd = mb_mvd;
4043 if want_b_grids {
4044 self.sc_ref1 = mb_ref1;
4045 self.sc_mvd1 = mb_mvd1;
4046 self.sc_direct = mb_direct;
4047 }
4048 Ok(addr)
4049 }
4050
4051 /// CABAC chroma recon (mirrors `decode_chroma`'s reconstruction, driven by the
4052 /// CABAC-parsed DC/AC coefficients). `cdc[c]` = 2×2 DC (scan order); `cac[c][blk]`
4053 /// = 15 AC per 4×4 block (scan order).
4054 #[allow(clippy::too_many_arguments)]
4055 /// Add a CABAC-parsed inter residual to an already-built motion-comp prediction
4056 /// (`pred_y`/`c_pred`), writing the reconstruction. Shared by the P and B inter
4057 /// paths — same `reconstruct_4x4` as intra, MC output as the prediction, inter
4058 /// scaling lists (luma 3 / chroma 4+c). `luma_scan[z]`/`cdc`/`cac` are the
4059 /// scan-order coefficients; uncoded blocks are zero so recon == prediction.
4060 #[allow(clippy::too_many_arguments)]
4061 fn add_inter_residual(
4062 &mut self,
4063 mb_x: usize,
4064 mb_y: usize,
4065 pred_y: &[u8; 256],
4066 c_pred: &[[u8; 64]; 2],
4067 luma_scan: Option<&[[i32; 16]; 16]>,
4068 // `Some` when the macroblock carries transform_size_8x8_flag: four 8x8
4069 // blocks in 8x8 scan order, replacing the sixteen 4x4 luma blocks.
4070 luma8: Option<&[[i32; 64]; 4]>,
4071 cdc: &[[i32; 4]; 2],
4072 cac: Option<&[[[i32; 16]; 4]; 2]>,
4073 cbp_chroma: u32,
4074 // Parsed totalCoeff per block, indexed exactly as the parse's `iz`:
4075 // [0..16] luma 4x4 z-order (for t8, the 8x8 count sits at `id8*4`),
4076 // [16..24] chroma AC as `16 + c*4 + id4`. The parser already counted
4077 // every significant coefficient; re-deriving the counts here scanned
4078 // 16-64 array elements per block (~400 loads/MB) for information the
4079 // caller was holding — the diagnosis's stage-boundary re-derivation tax.
4080 nnzs: &[u8; 24],
4081 ) {
4082 // A `None` field means the parse wrote nothing there; every read below
4083 // is guarded by a zero-count test, so the shared zero plane is
4084 // read-equivalent to the per-macroblock zeroed stack array it replaces.
4085 let luma_scan = luma_scan.unwrap_or(&ZERO_LUMA_SCAN);
4086 let cac = cac.unwrap_or(&ZERO_CAC);
4087 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecResidAdd);
4088 let qp = self.cur_qp;
4089 let qpc = self.chroma_qp_for(qp);
4090 let (w4r, w2r) = (self.mb_w * 4, self.mb_w * 2);
4091 if let Some(l8) = luma8 {
4092 // INTER 8x8 luma: same primitives the I_8x8 and CAVLC paths use.
4093 for b8 in 0..4usize {
4094 let (b8x, b8y) = (b8 % 2, b8 / 2);
4095 // PER-CELL, not one aggregate broadcast over all four cells. CAVLC
4096 // codes an 8x8 block as four 4x4 sub-blocks and its nC predictor
4097 // reads these per-4x4 counts from `nnz_y`, so the broadcast
4098 // corrupted the NEXT macroblock's nC and desynced the parse -- which
4099 // is why CAVLC 8x8 streams ffmpeg accepts would not decode here. The
4100 // worker copy of this function never wrote `nnz_y` at all, so the
4101 // threaded path was unaffected and hid the defect. CABAC has no
4102 // per-4x4 counts, so its callers put the 8x8 total in all four slots.
4103 let nnz: u32 = (0..4).map(|k| nnzs[b8 * 4 + k] as u32).sum();
4104 // Two row slices, not four whole-grid indexed stores. The pair
4105 // written per row is contiguous and `nnzs` is a fixed 24-entry
4106 // array, so both sides become provable.
4107 for sy in 0..2 {
4108 let base = (mb_y * 4 + b8y * 2 + sy) * w4r + mb_x * 4 + b8x * 2;
4109 self.nnz_y[base..][..2].copy_from_slice(&nnzs[b8 * 4 + sy * 2..][..2]);
4110 }
4111 // The 4x4 inter path marks coded_y per block; the 8x8 branch must too,
4112 // or a later intra macroblock's neighbour availability is wrong.
4113 for sy in 0..2 {
4114 let base = (mb_y * 4 + b8y * 2 + sy) * w4r + mb_x * 4 + b8x * 2;
4115 self.coded_y[base..][..2].fill(true);
4116 }
4117 let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
4118 if nnz == 0 {
4119 // Zero residual: recon == pred — the 4x4 arm zero shortcut,
4120 // ported to the 8x8 branch.
4121 edcstat::bump(&edcstat::T8_ZERO, 1);
4122 for dy in 0..8 {
4123 let d = (py + dy) * self.cw + px;
4124 let po = (b8y * 8 + dy) * 16 + b8x * 8;
4125 self.rec_y[d..d + 8].copy_from_slice(&pred_y[po..po + 8]);
4126 }
4127 } else {
4128 let raster = un_scan_8x8(&l8[b8]);
4129 // list 1 = INTER 8x8 luma scaling list (0 is the intra one).
4130 let res8 = self.inv_quant8(&raster, qp, 1);
4131 let predb: [i32; 64] = core::array::from_fn(|i| {
4132 pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
4133 });
4134 let recon = add_residual_8x8(&res8, &predb);
4135 // Eight row copies. This wrote SIXTY-FOUR individually
4136 // bounds-checked samples into the luma plane per coded 8x8
4137 // block — the same shape whose fix carried `recon_i8_block`.
4138 // Present in BOTH the main path and the EDC worker twin.
4139 for dy in 0..8 {
4140 let d = (py + dy) * self.cw + px;
4141 self.rec_y[d..][..8].copy_from_slice(&recon[dy * 8..][..8]);
4142 }
4143 }
4144 }
4145 }
4146 // RESIDUAL LADDER, then the SIMD IDCT+add kernel per parked block (SIMD census
4147 // 2026-09-05). The zero and DC-only ladders write the plane in the loop;
4148 // every other coded block parks its dequantised coefficients here and is
4149 // reconstructed by `reconstruct_4x4_into` = accel `idct4x4_add` (in-register
4150 // transposes, exact on i32). A lane-per-block BATCHED form was tried first
4151 // and lost on all-intra content: its scalar gathers cost more than the
4152 // butterflies they vectorised.
4153 let dq3 = self.dq_const(qp, 3);
4154 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4155 if luma8.is_some() {
4156 break;
4157 }
4158 let nnz = nnzs[blk];
4159 if let Some(p) = self
4160 .nnz_y
4161 .get_mut((mb_y * 4 + lby) * w4r + (mb_x * 4 + lbx))
4162 {
4163 *p = nnz;
4164 }
4165 let cw = self.cw;
4166 let p_off = (lby * 4) * 16 + lbx * 4;
4167 let r_off = (mb_y * 4 + lby) * 4 * cw + (mb_x * 4 + lbx) * 4;
4168 if nnz == 0 {
4169 // Zero residual → recon == prediction EXACTLY (the integer IDCT is
4170 // linear so zeros map to zeros, and pred is already 0..=255) — copy
4171 // the pred rows straight into the plane. On real (sparse-cbp)
4172 // streams this is MOST of the 4×4 blocks.
4173 for r in 0..4 {
4174 self.rec_y[r_off + r * cw..r_off + r * cw + 4]
4175 .copy_from_slice(&pred_y[p_off + r * 16..p_off + r * 16 + 4]);
4176 }
4177 continue;
4178 }
4179 // DC-ONLY: the sole significant coefficient is scan position 0 (the
4180 // zig-zag starts at DC, and un_scan keeps it at raster 0), so the
4181 // whole dequant + IDCT collapses to one multiply and a flat add.
4182 if nnz == 1 && luma_scan[blk][0] != 0 {
4183 let f = self.dequant_dc4(luma_scan[blk][0], qp, 3);
4184 reconstruct_4x4_dc_into(
4185 (f + 32) >> 6,
4186 pred_y,
4187 p_off,
4188 16,
4189 &mut self.rec_y,
4190 r_off,
4191 cw,
4192 );
4193 } else {
4194 // Fused un-scan + dequant over ONLY the significant coefficients,
4195 // then IDCT + add + clip straight into the plane — no `qb`, no
4196 // `deq`-from-dense, no `predb` gather, no `s`, no `store` call.
4197 //
4198 // HYBRID: the scatter walks scan positions with a data-dependent
4199 // branch per slot, which beats the branchless dense 16-multiply
4200 // loop only while the block is SPARSE. The DC/zero fast paths
4201 // already removed the sparsest blocks, so the population here
4202 // skews denser — above ~6 coefficients the dense loop wins.
4203 // FUSED: un-scan + dequant + IDCT + add in one kernel call (dense-over-scatter round).
4204 reconstruct_4x4_scan_into::<false>(
4205 &luma_scan[blk],
4206 &dq3,
4207 0,
4208 pred_y,
4209 p_off,
4210 16,
4211 &mut self.rec_y,
4212 r_off,
4213 cw,
4214 );
4215 }
4216 }
4217 let mut c_dc = [[0i32; 4]; 2];
4218 if cbp_chroma != 0 {
4219 for c in 0..2 {
4220 c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 4 + c);
4221 }
4222 }
4223 let dqc = [self.dq_const(qpc, 4), self.dq_const(qpc, 5)];
4224 let ccw = self.ccw;
4225 for c in 0..2 {
4226 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4227 let mut ac_nz = false;
4228 if cbp_chroma == 2 {
4229 let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
4230 if let Some(p) =
4231 self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2r + (mb_x * 2 + bx))
4232 {
4233 *p = n;
4234 }
4235 ac_nz = n != 0;
4236 }
4237 let dc = c_dc[c & 1][(by * 2 + bx) & 3];
4238 let p_off = (by * 4) * 8 + bx * 4;
4239 let r_off = (mb_y * 2 + by) * 4 * ccw + (mb_x * 2 + bx) * 4;
4240 let plane = if c == 0 {
4241 &mut self.rec_u
4242 } else {
4243 &mut self.rec_v
4244 };
4245 if dc == 0 && !ac_nz {
4246 // Zero residual (no AC, zero DC) → recon == prediction exactly.
4247 for r in 0..4 {
4248 plane[r_off + r * ccw..r_off + r * ccw + 4]
4249 .copy_from_slice(&c_pred[c][p_off + r * 8..p_off + r * 8 + 4]);
4250 }
4251 continue;
4252 }
4253 // DC-ONLY (no coded AC — covers every cbp_chroma==1 block and the
4254 // AC-empty blocks of cbp_chroma==2): the chroma DC arrives ALREADY
4255 // dequantized, so the residual is `(dc + 32) >> 6` flat.
4256 if !ac_nz {
4257 reconstruct_4x4_dc_into(
4258 (dc + 32) >> 6,
4259 &c_pred[c],
4260 p_off,
4261 8,
4262 plane,
4263 r_off,
4264 ccw,
4265 );
4266 continue;
4267 }
4268 // AC-only scan: index i is overall scan position i+1 (ac_shift=1).
4269 // Same sparse/dense hybrid as luma.
4270 reconstruct_4x4_scan_into::<true>(
4271 &cac[c & 1][(by * 2 + bx) & 3],
4272 &dqc[c & 1],
4273 dc,
4274 &c_pred[c],
4275 p_off,
4276 8,
4277 plane,
4278 r_off,
4279 ccw,
4280 );
4281 }
4282 }
4283 }
4284
4285 // ── SHARED INTRA RECON PRIMITIVES ──────────────────────────────────
4286 // ONE implementation per block family, called by BOTH entropy arms.
4287 // The per-coder recon twins are where routing rot lived: the residual
4288 // ladder and the DC-only collapse each had to be found and ported
4289 // twin-by-twin. The parse halves stay per-coder (that is the real
4290 // difference between the coders); the pixel halves live here — the
4291 // same convergence D14 gave the inter path via add_inter_residual.
4292
4293 /// The residual ladder of an intra 4x4 block, decided BEFORE the serial
4294 /// predict+add walk (SIMD census 2026-09-05); each coded block is then
4295 /// reconstructed by the SIMD IDCT+add kernel.
4296 /// Zero -> recon == pred; Flat -> DC-only, one value; Idx -> a slot in the
4297 /// batched residual array.
4298 fn i4_prepare(&self, scans: &[[i32; 16]; 16], nnzs: &[u8; 16], qp: u8) -> [I4Res; 16] {
4299 let mut kinds = [I4Res::Zero; 16];
4300 for blk in 0..16 {
4301 let (nnz, scan) = (nnzs[blk], &scans[blk]);
4302 kinds[blk] = if nnz == 0 {
4303 edcstat::bump(&edcstat::I4_ZERO, 1);
4304 I4Res::Zero
4305 } else if nnz == 1 && scan[0] != 0 {
4306 edcstat::bump(&edcstat::I4_DC, 1);
4307 let f = self.dequant_dc4(scan[0], qp, 0);
4308 I4Res::Flat((f + 32) >> 6)
4309 } else {
4310 edcstat::bump(&edcstat::I4_DENSE, 1);
4311 I4Res::Idx(blk as u8) // dense: the fused kernel takes the scan block
4312 };
4313 }
4314 kinds
4315 }
4316
4317 /// Intra 4x4 luma block with its residual PRE-TRANSFORMED by [`Self::i4_prepare`]:
4318 /// gather + predict (serial, depends on the previous block's recon) + add.
4319 #[allow(clippy::too_many_arguments)]
4320 fn recon_i4_block_res(
4321 &mut self,
4322 bx: usize,
4323 by: usize,
4324 mode: u8,
4325 at: bool,
4326 al: bool,
4327 kind: I4Res,
4328 scans: &[[i32; 16]; 16],
4329 dq: &DequantQp,
4330 ) {
4331 let (px, py) = (bx * 4, by * 4);
4332 let (t, l, corner) = self.gather_i4(px, py, at, al, bx, by);
4333 let pred = intra4x4_pred(mode, at, al, &t, &l, corner);
4334 let r_off = py * self.cw + px;
4335 let cw = self.cw;
4336 match kind {
4337 I4Res::Zero => {
4338 let win = &mut self.rec_y[r_off..r_off + 3 * cw + 4];
4339 for r in 0..4 {
4340 win[r * cw..r * cw + 4].copy_from_slice(&pred[r * 4..r * 4 + 4]);
4341 }
4342 }
4343 I4Res::Flat(v) => reconstruct_4x4_dc_into(v, &pred, 0, 4, &mut self.rec_y, r_off, cw),
4344 I4Res::Idx(i) => reconstruct_4x4_scan_into::<false>(
4345 &scans[i as usize & 15],
4346 dq,
4347 0,
4348 &pred,
4349 0,
4350 4,
4351 &mut self.rec_y,
4352 r_off,
4353 cw,
4354 ),
4355 }
4356 // coded_y is filled per macroblock row by the callers (routing round).
4357 }
4358
4359 /// Intra 8x8 luma block: predict + zero-arm or un-scan/quant/add.
4360 /// `coeffs` = SCAN-order 8x8 coefficients, `None` when the cbp bit is
4361 /// unset. `coded_y` marking stays with the callers (their orders differ).
4362 fn recon_i8_block(
4363 &mut self,
4364 bx: usize,
4365 by: usize,
4366 mode: u8,
4367 avail_top: bool,
4368 avail_left: bool,
4369 coeffs: Option<&[i32; 64]>,
4370 qp: u8,
4371 ) {
4372 let (px, py) = (bx * 4, by * 4);
4373 let (t, l, corner, avail_corner) = self.gather_i8(px, py, avail_top, avail_left, bx, by);
4374 let pred = intra8x8_pred(mode, avail_top, avail_left, avail_corner, &t, &l, corner);
4375 match coeffs {
4376 None => {
4377 // Zero residual: recon == pred exactly.
4378 edcstat::bump(&edcstat::T8_ZERO, 1);
4379 let (cw, base) = (self.cw, py * self.cw + px);
4380 let win = &mut self.rec_y[base..base + 7 * cw + 8];
4381 for dy in 0..8 {
4382 win[dy * cw..dy * cw + 8].copy_from_slice(&pred[dy * 8..dy * 8 + 8]);
4383 }
4384 }
4385 Some(scan8) => {
4386 let raster = un_scan_8x8(scan8);
4387 let res8 = self.inv_quant8(&raster, qp, 0);
4388 // Written once rather than zeroed-then-filled.
4389 let predb: [i32; 64] = core::array::from_fn(|i| pred[i] as i32);
4390 let recon = add_residual_8x8(&res8, &predb);
4391 // ROW COPIES, NOT PER-PIXEL STORES. This wrote all 64 samples
4392 // one at a time, each separately bounds-checked, where the
4393 // destination is eight contiguous 8-byte runs `cw` apart.
4394 // REFUTED, do not retry: replacing this WINDOW + sub-slice with
4395 // eight direct `rec_y[d..][..8]` slices left the panic count
4396 // EXACTLY unchanged (12) and cost +3.0% instructions. Neither
4397 // form folds the check, but the window amortises the base
4398 // address computation across the eight rows. (Contrast
4399 // `gather_tile`, where a window cost +57.6% — a window pays only
4400 // when the rows it spans are written in one tight loop.)
4401 let (cw, base) = (self.cw, py * self.cw + px);
4402 let win = &mut self.rec_y[base..base + 7 * cw + 8];
4403 for dy in 0..8 {
4404 win[dy * cw..dy * cw + 8].copy_from_slice(&recon[dy * 8..dy * 8 + 8]);
4405 }
4406 }
4407 }
4408 }
4409
4410 /// I_16x16 luma: neighbor gather + prediction + the 16 4x4 AC blocks
4411 /// with the DC-only collapse. `q_blocks` = RASTER AC (slot 0 unused),
4412 /// `recon_dc` = the Hadamard-dequantized DC per block. Marks modes_y
4413 /// (I_16x16 predicts as DC for neighbors) + coded_y.
4414 #[allow(clippy::too_many_arguments)]
4415 fn recon_i16_luma(
4416 &mut self,
4417 mbx: usize,
4418 mby: usize,
4419 pred_mode: rusty_h264_common::predict::I16Mode,
4420 top_ok: bool,
4421 left_ok: bool,
4422 q_blocks: Option<&[[i32; 16]; 16]>,
4423 coded: u16,
4424 recon_dc: &[i32; 16],
4425 qp: u8,
4426 ) {
4427 // `None` on a DC-only I_16x16 macroblock (CodedBlockPatternLuma == 0):
4428 // no AC was parsed, so the shared zero plane is read-equivalent to the
4429 // 1 KB of stack this used to zero per macroblock.
4430 let q_blocks = q_blocks.unwrap_or(&ZERO_LUMA_SCAN);
4431 let w4 = self.mb_w * 4;
4432 let (lx, ly) = (mbx * 16, mby * 16);
4433 let mut t16 = [0u8; 16];
4434 let mut l16 = [0u8; 16];
4435 if top_ok {
4436 t16.copy_from_slice(self.top_y_row(ly, lx, 16));
4437 }
4438 if left_ok {
4439 // ONE check for the 16-sample column (see `gather_i4`).
4440 // `step_by` walk rather than a span + `col[i * cw]` (see `gather_i8`):
4441 // the span form's inner index stayed checked on all sixteen samples.
4442 let (cw, base) = (self.cw, ly * self.cw + lx - 1);
4443 for (s, &v) in l16.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
4444 *s = v;
4445 }
4446 }
4447 let corner = if top_ok && left_ok {
4448 self.top_y_px(ly, lx - 1)
4449 } else {
4450 0
4451 };
4452 let pred_l = luma16x16_pred(pred_mode, top_ok, left_ok, &t16, &l16, corner);
4453 // AC blocks parked, then the SIMD IDCT+add kernel per block (the pred is whole).
4454 let dq0 = self.dq_const(qp, 0);
4455 for by in 0..4 {
4456 for bx in 0..4 {
4457 let p_off = (by * 4) * 16 + bx * 4;
4458 let r_off = (ly + by * 4) * self.cw + lx + bx * 4;
4459 // ROUTED ON THE PARSE`S CODED MASK (routing round): was a 16-word compare.
4460 if coded & (1u16 << ((by & 3) * 4 + (bx & 3))) == 0 {
4461 // Zero AC: the residual is the Hadamard DC alone.
4462 edcstat::bump(&edcstat::I16_DCONLY, 1);
4463 reconstruct_4x4_dc_into(
4464 (recon_dc[by * 4 + bx] + 32) >> 6,
4465 &pred_l,
4466 p_off,
4467 16,
4468 &mut self.rec_y,
4469 r_off,
4470 self.cw,
4471 );
4472 } else {
4473 // q_blocks holds the SCAN-order AC; the fused kernel un-scans, dequantises
4474 // and inserts the Hadamard DC.
4475 reconstruct_4x4_scan_into::<true>(
4476 &q_blocks[(by & 3) * 4 + (bx & 3)],
4477 &dq0,
4478 recon_dc[by * 4 + bx],
4479 &pred_l,
4480 p_off,
4481 16,
4482 &mut self.rec_y,
4483 r_off,
4484 self.cw,
4485 );
4486 }
4487 }
4488 }
4489 // ROW FILLS. The mode/coded grids were written one 4x4 cell at a time
4490 // inside the recon loop; each macroblock row is four CONTIGUOUS cells,
4491 // so four fills replace sixteen indexed stores.
4492 for by in 0..4 {
4493 let r = (mby * 4 + by) * w4 + mbx * 4;
4494 self.modes_y[r..r + 4].fill(2);
4495 self.coded_y[r..r + 4].fill(true);
4496 }
4497 }
4498
4499 /// Chroma 8x8 recon, both planes: prediction + four 4x4 blocks per
4500 /// plane with the DC-only collapse. `qac` = RASTER AC per plane/block
4501 /// (all-zero when uncoded), `dc` = the 2x2-Hadamard-dequantized DC.
4502 #[allow(clippy::too_many_arguments)]
4503 fn recon_chroma_blocks(
4504 &mut self,
4505 mb_x: usize,
4506 mb_y: usize,
4507 chroma_mode: u8,
4508 avail_top: bool,
4509 avail_left: bool,
4510 qac: &[[[i32; 16]; 4]; 2],
4511 coded: [u8; 2],
4512 dc: &[[i32; 4]; 2],
4513 qpc: u8,
4514 ) {
4515 let (cx, cy) = (mb_x * 8, mb_y * 8);
4516 let dqc = [self.dq_const(qpc, 1), self.dq_const(qpc, 2)];
4517 for c in 0..2 {
4518 let mut ctop = [0u8; 8];
4519 let mut cleft = [0u8; 8];
4520 let mut ccorner = 0u8;
4521 {
4522 let rec_c = if c == 0 { &self.rec_u } else { &self.rec_v };
4523 if avail_top {
4524 ctop.copy_from_slice(self.top_c_row(c, cy, cx, 8));
4525 }
4526 if avail_left {
4527 // Strided WALK, as in `gather_i4`/`gather_i8`.
4528 let cbase = cy * self.ccw + cx - 1;
4529 for (o, &v) in cleft
4530 .iter_mut()
4531 .zip(rec_c[cbase..].iter().step_by(self.ccw))
4532 {
4533 *o = v;
4534 }
4535 }
4536 if avail_top && avail_left {
4537 ccorner = self.top_c_px(c, cy, cx - 1);
4538 }
4539 }
4540 let pred8 = chroma8x8_pred(chroma_mode, avail_top, avail_left, &ctop, &cleft, ccorner);
4541 // Coded AC blocks parked (up to 4), then the SIMD IDCT+add kernel per block.
4542 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4543 let p_off = (by * 4) * 8 + bx * 4;
4544 let ccw = self.ccw;
4545 let r_off = (cy + by * 4) * ccw + cx + bx * 4;
4546 if coded[c & 1] & (1u8 << ((by * 2 + bx) & 3)) == 0 {
4547 // Zero AC (cbp_chroma <= 1, the common case): DC-alone flat add.
4548 edcstat::bump(&edcstat::I16_DCONLY, 1);
4549 let plane = if c == 0 {
4550 &mut self.rec_u
4551 } else {
4552 &mut self.rec_v
4553 };
4554 reconstruct_4x4_dc_into(
4555 (dc[c & 1][(by * 2 + bx) & 3] + 32) >> 6,
4556 &pred8,
4557 p_off,
4558 8,
4559 plane,
4560 r_off,
4561 ccw,
4562 );
4563 } else {
4564 let plane = if c == 0 {
4565 &mut self.rec_u
4566 } else {
4567 &mut self.rec_v
4568 };
4569 reconstruct_4x4_scan_into::<true>(
4570 &qac[c & 1][(by * 2 + bx) & 3],
4571 &dqc[c & 1],
4572 dc[c & 1][(by * 2 + bx) & 3],
4573 &pred8,
4574 p_off,
4575 8,
4576 plane,
4577 r_off,
4578 ccw,
4579 );
4580 }
4581 }
4582 }
4583 }
4584
4585 fn recon_chroma_cabac(
4586 &mut self,
4587 mb_x: usize,
4588 mb_y: usize,
4589 chroma_mode: u8,
4590 cdc: &[[i32; 4]; 2],
4591 cac: Option<&[[[i32; 16]; 4]; 2]>,
4592 cnnz: &[u8; 8], // parse-side AC counts, c * 4 + by * 2 + bx (routing round: was a 128-word rescan)
4593 cbp_chroma: u32,
4594 avail_top: bool,
4595 avail_left: bool,
4596 ) {
4597 // `None` = no chroma AC was parsed; every read below is already gated
4598 // on cbp_chroma == 2, so the shared zero plane is read-equivalent.
4599 let cac = cac.unwrap_or(&ZERO_CAC);
4600 let qpc = self.chroma_qp_for(self.cur_qp);
4601 let mut c_dc = [[0i32; 4]; 2];
4602 if cbp_chroma != 0 {
4603 for c in 0..2 {
4604 c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 1 + c);
4605 }
4606 }
4607 // ENTROPY-SIDE half: un-scan the AC into raster + commit nnz_c; the
4608 // pixel half is the SHARED recon_chroma_blocks.
4609 let w2 = self.mb_w * 2;
4610 let mut ccoded = [0u8; 2];
4611 if cbp_chroma == 2 {
4612 for c in 0..2 {
4613 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4614 let cnt = cnnz[(c * 4 + by * 2 + bx) & 7];
4615 ccoded[c & 1] |= ((cnt != 0) as u8) << ((by * 2 + bx) & 3);
4616 if let Some(p) =
4617 self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2 + (mb_x * 2 + bx))
4618 {
4619 *p = cnt;
4620 }
4621 }
4622 }
4623 }
4624 self.recon_chroma_blocks(
4625 mb_x,
4626 mb_y,
4627 chroma_mode,
4628 avail_top,
4629 avail_left,
4630 cac,
4631 ccoded,
4632 &c_dc,
4633 qpc,
4634 );
4635 }
4636
4637 /// D14 — the CAVLC E-seam (P3 item 5). Mirrors `decode_slice_data_cabac`:
4638 /// overlap parse (this thread) with pixel reconstruction (a scoped worker
4639 /// owning the planes). Now possible because the CAVLC inter recon was
4640 /// converged onto `add_inter_residual`, so both entropy coders emit the SAME
4641 /// `PInterJob` and share one worker recon.
4642 pub fn decode_slice_data(
4643 &mut self,
4644 r: &mut BitReader,
4645 is_p: bool,
4646 first_mb: usize,
4647 ) -> Result<usize, MbError> {
4648 // Same cross-slice guard as the CABAC loop head.
4649 self.span_flush();
4650 let eligible = edc_on() && rowdb_on() && (is_p || self.is_b);
4651 let threaded = eligible && edc_spawn_worker(self.mb_w, self.mb_h, self.bits_per_mb, false);
4652 edcstat::bump(&edcstat::DISPATCH_ON, threaded as u64);
4653 edcstat::bump(&edcstat::DISPATCH_SEEN, eligible as u64);
4654 if !threaded {
4655 return self.decode_slice_cavlc_inner(r, is_p, first_mb);
4656 }
4657 #[cfg(feature = "std")]
4658 {
4659 let ctx = self.edc_take_ctx();
4660 let (tx, rx) = crate::sync::mpsc::sync_channel::<EdcMsg>(edc_bound());
4661 let (ctx_tx, ctx_rx) = crate::sync::mpsc::channel::<PixelCtx>();
4662 let (back_tx, back_rx) = crate::sync::mpsc::channel::<PixelCtx>();
4663 let (res, ctx, panicked) = std::thread::scope(|sc| {
4664 let h = sc.spawn(move || edc_worker(ctx, rx, ctx_tx, back_rx));
4665 self.edc_tx = Some(tx);
4666 self.edc_ctx_rx = Some(ctx_rx);
4667 self.edc_back_tx = Some(back_tx);
4668 // UNWIND SAFETY (same trap the CABAC wrapper documents): the sender
4669 // lives in `self`, which outlives an unwind, so a panic in the parse
4670 // loop would leave the channel open, the worker alive and the scope
4671 // join blocking forever — turning a diagnosable panic into a silent
4672 // deadlock. Catch, clean up, join, restore, THEN resume.
4673 let r2 = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4674 self.decode_slice_cavlc_inner(r, is_p, first_mb)
4675 }));
4676 self.edc_flush_batch();
4677 self.edc_giveback();
4678 self.edc_tx = None;
4679 self.edc_ctx_rx = None;
4680 self.edc_back_tx = None;
4681 match (r2, h.join()) {
4682 (Ok(res), Ok(ctx)) => (res, Some(ctx), None),
4683 (Err(pn), Ok(ctx)) => (Err(MbError::Truncated), Some(ctx), Some(pn)),
4684 (Ok(_), Err(pn)) | (Err(_), Err(pn)) => {
4685 (Err(MbError::Truncated), None, Some(pn))
4686 }
4687 }
4688 });
4689 if let Some(ctx) = ctx {
4690 self.edc_restore_ctx(ctx);
4691 }
4692 if let Some(pn) = panicked {
4693 std::panic::resume_unwind(pn);
4694 }
4695 return res;
4696 }
4697 #[cfg(not(feature = "std"))]
4698 {
4699 unreachable!("the EDC worker needs std; edc_spawn_worker is false without it")
4700 }
4701 }
4702
4703 fn decode_slice_cavlc_inner(
4704 &mut self,
4705 r: &mut BitReader,
4706 is_p: bool,
4707 first_mb: usize,
4708 ) -> Result<usize, MbError> {
4709 let total = self.mb_w * self.mb_h;
4710 self.slice_first_mb = first_mb;
4711 self.slice_bounds.push((first_mb, self.cur_idc2));
4712 self.any_idc2 |= self.cur_idc2;
4713 self.edc_active = edc_on();
4714 let mut addr = first_mb;
4715 // Same malformed-header divide-by-zero as the sibling slice loop above.
4716 let mbw_nz = self.mb_w.max(1);
4717 let (mut mbx, mut mby) = (addr % mbw_nz, addr / mbw_nz);
4718 let mbw_c = self.mb_w;
4719 while addr < total {
4720 if mbx == mbw_c {
4721 mbx = 0;
4722 mby += 1;
4723 }
4724 self.row_hook_at(addr, mby);
4725 if is_p || self.is_b {
4726 let skip_run = {
4727 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
4728 r.read_ue()?
4729 } as usize;
4730 // A run past the picture end is a corrupt stream (ffmpeg errors
4731 // here too); a run TO the end is legal. Silently clamping used
4732 // to fill the remainder with skip MBs.
4733 if skip_run > total - addr {
4734 return Err(MbError::Truncated);
4735 }
4736 // The run length is KNOWN here (CAVLC codes it as one syntax
4737 // element). P runs: after the first skip commits (0,0), every
4738 // later run MB is FORCED (0,0) by the zero-MV rule — process
4739 // the remainder SEGMENT-WISE: one span extension + one mb_qp
4740 // fill per row segment instead of a per-MB call + span match +
4741 // store. Non-(0,0) runs and B runs keep the per-MB loop. The
4742 // per-MB `addr >= total` check is gone: the run was validated
4743 // against `total - addr` above.
4744 let mut remaining = skip_run;
4745 while remaining > 0 {
4746 debug_assert!(addr < total);
4747 if mbx == mbw_c {
4748 mbx = 0;
4749 mby += 1;
4750 }
4751 if self.is_b {
4752 if !self.b_skip_hot(mbx, mby) {
4753 self.decode_b_skip(mbx, mby)?;
4754 }
4755 if let Some(p) = self.mb_qp.get_mut(addr) {
4756 *p = self.cur_qp;
4757 } // skip inherits QPy
4758 addr += 1;
4759 mbx += 1;
4760 remaining -= 1;
4761 continue;
4762 }
4763 self.decode_p_skip(mbx, mby)?;
4764 if let Some(p) = self.mb_qp.get_mut(addr) {
4765 *p = self.cur_qp;
4766 }
4767 addr += 1;
4768 mbx += 1;
4769 remaining -= 1;
4770 // Segment-wise remainder: forced (0,0) continuation holds
4771 // exactly while skip_zero_next tracks addr (decode_p_skip
4772 // set it iff this skip committed (0,0)).
4773 // Worker mode (edc_tx) must route per-MB jobs through the
4774 // channel — the parse thread no longer owns the planes.
4775 while remaining > 0
4776 && self.skip_zero_next == addr
4777 && !no_runmv()
4778 && self.edc_tx.is_none()
4779 {
4780 if mbx == mbw_c {
4781 mbx = 0;
4782 mby += 1;
4783 }
4784 // Row segment: run to row end or run end.
4785 let seg = remaining.min(mbw_c - mbx);
4786 let recon =
4787 self.edc_tx.is_none() && (self.weights.is_none() || self.weights_id0);
4788 for k in 0..seg {
4789 self.pz_push(mbx + k, mby, recon);
4790 }
4791 edcstat::bump(&edcstat::SKIPMV_FORCED, seg as u64);
4792 self.route_skip_mbs += seg as u32;
4793 self.mb_qp[addr..addr + seg].fill(self.cur_qp);
4794 if !recon {
4795 for k in 0..seg {
4796 self.edc_jobs.push(EdcJob::Skip {
4797 mbx: mbx + k,
4798 mby,
4799 mv: (0, 0),
4800 });
4801 }
4802 }
4803 addr += seg;
4804 mbx += seg;
4805 remaining -= seg;
4806 self.skip_zero_next = addr;
4807 }
4808 }
4809 if addr >= total {
4810 break;
4811 }
4812 // A trailing skip run with no following macroblock ends the slice.
4813 if skip_run > 0 && !r.more_rbsp_data() {
4814 break;
4815 }
4816 }
4817 // The skip run above may have crossed a row boundary within this
4818 // iteration — wrap before the non-skip MB decodes.
4819 if mbx == mbw_c {
4820 mbx = 0;
4821 mby += 1;
4822 }
4823 if self.is_b {
4824 // ORDER: B reconstructs inline (not seam-ready).
4825 self.edc_intra_sync();
4826 self.decode_b_mb(r, mbx, mby)?;
4827 } else {
4828 // Non-skip CAVLC MB: inter MV prediction + intra gathers
4829 // read the grids — flush deferred spans.
4830 self.span_flush();
4831 self.decode_mb(r, mbx, mby, is_p)?;
4832 }
4833 if let Some(p) = self.mb_qp.get_mut(addr) {
4834 *p = self.cur_qp;
4835 }
4836 addr += 1;
4837 mbx += 1;
4838 // CAVLC slice end: no more data after this macroblock.
4839 if !r.more_rbsp_data() {
4840 break;
4841 }
4842 }
4843 self.edc_flush(); // slice end: no job crosses a slice boundary
4844 Ok(addr)
4845 }
4846
4847 fn decode_mb(
4848 &mut self,
4849 r: &mut BitReader,
4850 mb_x: usize,
4851 mb_y: usize,
4852 is_p: bool,
4853 ) -> Result<(), MbError> {
4854 self.wait_refs_for_mb(mb_y);
4855 let mut mb_type = {
4856 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
4857 r.read_ue()?
4858 };
4859 if is_p {
4860 // In P-slices, mb_type 0/1/2 are inter (16×16, 16×8, 8×16),
4861 // 3 = P_8x8, 4 = P_8x8ref0 (ref_idx forced 0), 5+ intra.
4862 if mb_type <= 2 {
4863 return self.decode_inter(r, mb_x, mb_y, mb_type as u8);
4864 }
4865 if mb_type == 3 || mb_type == 4 {
4866 return self.decode_p8x8(r, mb_x, mb_y, mb_type == 4);
4867 }
4868 mb_type -= 5;
4869 }
4870 // ORDER: intra reconstruction reads neighbour PIXELS, so the worker
4871 // must have applied every deferred job before this point.
4872 self.edc_intra_sync();
4873 self.decode_intra_mb(r, mb_x, mb_y, mb_type)
4874 }
4875
4876 /// Decodes an intra macroblock given its intra `mb_type` (0 = I_4x4,
4877 /// 1..=24 = I_16x16, 25 = I_PCM) — shared by I-, P- and B-slice paths.
4878 fn decode_intra_mb(
4879 &mut self,
4880 r: &mut BitReader,
4881 mb_x: usize,
4882 mb_y: usize,
4883 mb_type: u32,
4884 ) -> Result<(), MbError> {
4885 // H-48: this scope was DECLARED and never wired, which is precisely why the
4886 // stage table left 19.8% unaccounted — 66,120 of 475,200 macroblocks on the
4887 // reference stream are I-type and had no scope at all.
4888 let _gi = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbI);
4889 if let Some(p) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
4890 *p = rusty_h264_common::deblock::MB_KIND_INTRA;
4891 }
4892 if mb_type == 0 {
4893 // I_NxN: transform_size_8x8_flag (when enabled) selects I_8x8 vs I_4x4.
4894 if self.transform_8x8_mode && r.read_bit()? {
4895 self.decode_i8x8(r, mb_x, mb_y)?;
4896 } else {
4897 self.decode_i4x4(r, mb_x, mb_y)?;
4898 }
4899 } else if (1..=24).contains(&mb_type) {
4900 self.decode_i16(r, mb_x, mb_y, mb_type - 1)?;
4901 } else if mb_type == 25 {
4902 // ORDER: I_PCM writes pixels directly.
4903 self.edc_intra_sync();
4904 self.decode_ipcm(r, mb_x, mb_y)?;
4905 } else {
4906 return Err(MbError::Unsupported(
4907 "only I_4x4 / I_16x16 / I_PCM macroblocks",
4908 ));
4909 }
4910 // Mark all luma blocks coded for the next macroblock's top-right.
4911 // Four contiguous cells per macroblock row - `fill`, not sixteen
4912 // separately bounds-checked indexed stores.
4913 let w4 = self.mb_w * 4;
4914 for lby in 0..4usize {
4915 let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
4916 self.coded_y[a..a + 4].fill(true);
4917 }
4918 Ok(())
4919 }
4920
4921 /// Reconstructs an inter macroblock (`mode` 0 = P_L0_16x16, 1 = P_16x8,
4922 /// 2 = P_8x16): parse the per-partition motion vectors and residual,
4923 /// motion-compensate each partition, and add the residual.
4924 fn decode_inter(
4925 &mut self,
4926 r: &mut BitReader,
4927 mb_x: usize,
4928 mb_y: usize,
4929 mode: u8,
4930 ) -> Result<(), MbError> {
4931 if self.refs.is_empty() {
4932 return Err(MbError::Unsupported("inter without reference"));
4933 }
4934 // DEBLOCK CLASS: mode 0 is P_L0_16x16 — ONE partition, so all 16 blocks
4935 // share a reference and motion vector and no internal edge can reach
4936 // strength 1. Internal strengths then follow from coefficients alone, i.e.
4937 // 16 nnz bytes instead of a 24-block gather across 5-7 grids. Modes 1/2
4938 // (P_16x8 / P_8x16) have two partitions with independent motion and stay
4939 // UNSET (blind path).
4940 if mode == 0 {
4941 if let Some(k) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
4942 *k = rusty_h264_common::deblock::MB_KIND_INTER_UNIFORM;
4943 }
4944 }
4945 // QP (qp/qpc) is bound after mb_qp_delta is read below.
4946 let w4 = self.mb_w * 4;
4947 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
4948 let num_refs = self.refs.len();
4949 let layout = inter_partitions(mode);
4950
4951 // mb_pred order (spec 7.3.5.1): all ref_idx_l0 first (only when more than
4952 // one reference is active), then all mvd_l0.
4953 let nparts = layout.len();
4954 let mut ref_idxs = [0i32; 4];
4955 if self.num_ref_active > 1 {
4956 for ri in ref_idxs[..nparts].iter_mut() {
4957 *ri = read_ref_idx(r, self.num_ref_active)?;
4958 if *ri as usize >= num_refs {
4959 return Err(MbError::Truncated); // references a non-existent picture
4960 }
4961 }
4962 }
4963
4964 // Phase 1: per partition, ref-aware MV prediction + mvd, committing the
4965 // motion grid so a later partition predicts from an earlier one.
4966 let mut part_mv = [(0i32, (0i32, 0i32)); 4];
4967 {
4968 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
4969 for (part, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
4970 let refi = ref_idxs[part];
4971 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
4972 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
4973 let pmv = predict_partition_mv(mode, part, a, b, c, refi);
4974 let mvd_x = read_mvd(r)?;
4975 let mvd_y = read_mvd(r)?;
4976 let mv = (pmv.0 + mvd_x, pmv.1 + mvd_y);
4977 part_mv[part] = (refi, mv);
4978 for by in ry / 4..ry / 4 + rh / 4 {
4979 for bx in rx / 4..rx / 4 + rw / 4 {
4980 let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
4981 if let (Some(m), Some(it), Some(rf), Some(cd)) = (
4982 self.mv_y.get_mut(idx),
4983 self.inter_y.get_mut(idx),
4984 self.ref_idx_y.get_mut(idx),
4985 self.coded_y.get_mut(idx),
4986 ) {
4987 (*m, *it, *rf, *cd) = (mv, true, refi, true);
4988 }
4989 }
4990 }
4991 }
4992 }
4993
4994 // Phase 2: motion-compensate each partition from its reference.
4995 let mut pred_y = [0u8; 256];
4996 let mut c_pred = [[0u8; 64]; 2];
4997 // D8-CAVLC: the double-stage ablation, extended to the CAVLC loop. The
4998 // CABAC measurement could not run here at all (`edc_active` is false on
4999 // this path, so nothing replays through `edc_flush` and `doubled` read
5000 // 0) — yet CAVLC is exactly the population P3 item 5 targets, and its
5001 // cheaper parse should make the PIXEL share larger. Doubling the whole
5002 // partition loop is idempotent: pass 2 overwrites `pred_y` with fresh MC
5003 // BEFORE `weight_partition` runs, so weighting cannot apply twice.
5004 // This doubles MC only (not the residual add inside `inter_finish`), so
5005 // it is a LOWER BOUND on the CAVLC pixel share.
5006 // D14 (CAVLC E-seam): when the seam is live the WORKER motion-compensates
5007 // from the committed MV grids, so skip MC here rather than computing a
5008 // prediction that would be discarded.
5009 let defer = self.edc_tx.is_some() || self.edc_active;
5010 let mc_passes = if defer {
5011 0
5012 } else if double_recon() {
5013 2
5014 } else {
5015 1
5016 };
5017 for _pass in 0..mc_passes {
5018 if _pass > 0 {
5019 edcstat::bump(&edcstat::DOUBLED, 1);
5020 }
5021 for (part, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
5022 let (refi, mv) = part_mv[part & 3];
5023 let Some(reference) = self.refs.get(refi as usize) else {
5024 continue;
5025 };
5026 let mut tmp = [0u8; 256];
5027 mc_luma_padded(
5028 &*reference.luma_guard(reference.ch),
5029 reference.lstride(),
5030 crate::LPAD,
5031 self.cw,
5032 ch,
5033 mb_x * 16 + rx,
5034 mb_y * 16 + ry,
5035 rw,
5036 rh,
5037 mv.0,
5038 mv.1,
5039 &mut tmp,
5040 );
5041 {
5042 let _g =
5043 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
5044 restride(&mut pred_y, 16, rx, ry, &tmp, rw, rh);
5045 }
5046 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
5047 for cc in 0..2 {
5048 let rc = if cc == 0 {
5049 &*reference.chroma_guard(0, reference.ch)
5050 } else {
5051 &*reference.chroma_guard(1, reference.ch)
5052 };
5053 let mut tc = [0u8; 64];
5054 mc_chroma_padded(
5055 rc,
5056 reference.cstride(),
5057 crate::CPAD,
5058 self.ccw,
5059 cch,
5060 mb_x * 8 + crx,
5061 mb_y * 8 + cry,
5062 crw,
5063 crh,
5064 mv.0,
5065 mv.1,
5066 &mut tc,
5067 );
5068 {
5069 let _g =
5070 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
5071 restride(&mut c_pred[cc], 8, crx, cry, &tc, crw, crh);
5072 }
5073 }
5074 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi as usize, rx, ry, rw, rh);
5075 }
5076 }
5077
5078 // 16×16/16×8/8×16 partitions are all ≥ 8×8, so the 8×8 transform is allowed.
5079 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, true, defer)
5080 }
5081
5082 /// Shared inter tail: parse `coded_block_pattern` + `mb_qp_delta`, decode the
5083 /// luma/chroma residual, and add it to the already-built motion-compensated
5084 /// prediction. Used by both the 16×16/16×8/8×16 path and `P_8x8`.
5085 fn inter_finish(
5086 &mut self,
5087 r: &mut BitReader,
5088 mb_x: usize,
5089 mb_y: usize,
5090 pred_y: &[u8; 256],
5091 c_pred: &[[u8; 64]; 2],
5092 allow_8x8: bool,
5093 // D14: emit a worker job instead of reconstructing. Only the CAVLC
5094 // 16x16/16x8/8x16 path sets this; B and P_8x8 stay inline.
5095 defer: bool,
5096 ) -> Result<(), MbError> {
5097 let w4 = self.mb_w * 4;
5098 let cbp = {
5099 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
5100 read_cbp_inter(r)?
5101 };
5102 let cbp_luma = cbp & 15;
5103 let cbp_chroma = cbp >> 4;
5104 // transform_size_8x8_flag follows cbp (before mb_qp_delta) when luma has
5105 // coefficients, the 8×8 transform is enabled, and every partition ≥ 8×8.
5106 let t8x8 = cbp_luma > 0 && self.transform_8x8_mode && allow_8x8 && r.read_bit()?;
5107 if t8x8 {
5108 if let Some(f) = self.mb_t8x8.get_mut(mb_y * self.mb_w + mb_x) {
5109 *f = true;
5110 }
5111 self.any_t8 = true;
5112 }
5113 if cbp != 0 {
5114 self.step_qp(r.read_se()?)?;
5115 }
5116 let (qp, _qpc) = (self.cur_qp, self.chroma_qp_for(self.cur_qp));
5117
5118 // ---- luma residual ----
5119 self.nnz_cache_load(mb_x, mb_y);
5120 let mut nnzs = [0u8; 24];
5121 // PARSE STRAIGHT INTO THE DESTINATION. The deferred arm used to parse into
5122 // 1.5 KB of zeroed locals and then copy them into the pooled job (another
5123 // 1.5 KB, +1 KB each for t8x8); the inline arm zeroed the same locals.
5124 // Both now decode into a pre-owned plane set -- the pooled `PInterJob`
5125 // when deferring, the decoder's scratch boxes otherwise -- and zero only
5126 // the blocks the cbp codes. Uncoded slots stay dirty on purpose: the
5127 // consumer (`add_inter_residual` / the worker) skips every block whose
5128 // `nnzs` count is 0 and every chroma AC plane unless cbp_chroma == 2,
5129 // the contract the CABAC P arm has shipped on since round 4.
5130 let nores = cbp == 0 && nores_on();
5131 let mut job = (defer && !nores).then(|| self.take_pinter_job());
5132 let mut s_luma = if job.is_none() {
5133 self.scratch_luma
5134 .take()
5135 .or_else(|| Some(Box::new([[0i32; 16]; 16])))
5136 } else {
5137 None
5138 };
5139 let mut s_luma8 = if job.is_none() {
5140 self.scratch_luma8
5141 .take()
5142 .or_else(|| Some(Box::new([[0i32; 64]; 4])))
5143 } else {
5144 None
5145 };
5146 let mut s_cac = if job.is_none() {
5147 self.scratch_cac
5148 .take()
5149 .or_else(|| Some(Box::new([[[0i32; 16]; 4]; 2])))
5150 } else {
5151 None
5152 };
5153 let (luma_scan, luma8, cac): (
5154 &mut [[i32; 16]; 16],
5155 &mut [[i32; 64]; 4],
5156 &mut [[[i32; 16]; 4]; 2],
5157 ) = match job.as_deref_mut() {
5158 Some(j) => (&mut j.luma_scan, &mut j.luma8, &mut j.cac),
5159 None => (
5160 s_luma.as_deref_mut().expect("scratch"),
5161 s_luma8.as_deref_mut().expect("scratch"),
5162 s_cac.as_deref_mut().expect("scratch"),
5163 ),
5164 };
5165 // BUILD THE MACROBLOCK'S nnz RASTER ON THE STACK, COPY IT ROW-WISE ONCE.
5166 // Both arms below scattered sixteen individually bounds-checked stores
5167 // into the frame grid while the entropy parse ran. Nothing reads
5168 // `nnz_y` for THIS macroblock before the function returns - `nc_pred`
5169 // predicts from the separate `nnz_cache` - so the writes can be
5170 // deferred. The zero arm then costs NOTHING: the raster already holds
5171 // zeros.
5172 let mut nnz_raster = [0u8; 16];
5173 if t8x8 {
5174 for b8 in 0..4 {
5175 let (b8x, b8y) = (b8 % 2, b8 / 2);
5176 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
5177 let _ = (bx, by);
5178 if cbp_luma & (1 << b8) != 0 {
5179 // Zero this 8x8 slot only (256 B) -- was a 1 KB `[[0;64];4]` insert + a 256 B copy.
5180 luma8[b8 & 3] = [0i32; 64];
5181 for sub in 0..4 {
5182 let (sx, sy) = (sub % 2, sub / 2);
5183 let (cx, cy) = (b8x * 2 + sx, b8y * 2 + sy);
5184 let nc = self.nc_pred(cx, cy);
5185 let mut blk = [0i32; 16];
5186 let total = decode_residual_block_into::<16, 16>(r, nc, &mut blk)?;
5187 self.nnz_cache_set(cx, cy, total);
5188 nnz_raster[cy * 4 + cx] = total;
5189 // The PER-SUB-BLOCK count the next macroblock's nC prediction
5190 // depends on -- summing these into one slot and letting the
5191 // recon helper broadcast it back is what broke CAVLC 8x8.
5192 // WIN: b8 < 4 and sub < 4, so the index is 0..=15 and the
5193 // mask is a no-op that PROVES it -- the same idiom the
5194 // neighbouring luma8[b8 & 3] / luma_scan[blk & 15] already use.
5195 // Folds a panic_bounds_check out of inter_finish.
5196 nnzs[(b8 * 4 + sub) & 15] = total;
5197 // RAW 8x8 scan: coeff k of sub-block s at 4k + s (spec 7.3.5.3.2);
5198 // `add_inter_residual` un-scans + dequantises, as for CABAC.
5199 for k in 0..16 {
5200 luma8[b8 & 3][(4 * k + sub) & 63] = blk[k];
5201 }
5202 }
5203 } else {
5204 for sub in 0..4 {
5205 let (sx, sy) = (sub % 2, sub / 2);
5206 self.nnz_cache_set(b8x * 2 + sx, b8y * 2 + sy, 0);
5207 // `nnz_raster` is already zero here - no store needed.
5208 }
5209 }
5210 }
5211 } else {
5212 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
5213 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
5214 let total = if cbp_luma & (1 << (blk / 4)) != 0 {
5215 let nc = self.nc_pred(lbx, lby);
5216 // Decoded STRAIGHT into the destination plane (RAW scan order, like
5217 // CABAC); zero only this coded block first (the slot is pooled/dirty).
5218 luma_scan[blk & 15] = [0i32; 16];
5219 decode_residual_block_into::<16, 16>(r, nc, &mut luma_scan[blk & 15])?
5220 } else {
5221 0
5222 };
5223 self.nnz_cache_set(lbx, lby, total);
5224 let _ = (bx, by);
5225 nnz_raster[(lby & 3) * 4 + (lbx & 3)] = total;
5226 // WIN: luma 4x4 index, 0..=15 (luma_scan[blk & 15] above relies on the
5227 // same range); the mask folds the second bounds check.
5228 nnzs[blk & 15] = total;
5229 }
5230 }
5231 // ONE contiguous copy per macroblock row.
5232 for by in 0..4usize {
5233 let a = (mb_y * 4 + by) * w4 + mb_x * 4;
5234 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
5235 }
5236
5237 // ---- chroma residual ----
5238 let mut c_recon_dc = [[0i32; 4]; 2];
5239 if cbp_chroma != 0 {
5240 for slot in c_recon_dc.iter_mut() {
5241 // RAW, straight into the 4-word slot (dequantised in the helper).
5242 decode_residual_block_into::<4, 4>(r, -1, slot)?;
5243 }
5244 }
5245 if cbp_chroma == 2 {
5246 self.chroma_cache_load(mb_x, mb_y);
5247 let w2 = self.mb_w * 2;
5248 let mut cnnz = [[0u8; 4]; 2];
5249 for c in 0..2 {
5250 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
5251 let nc = self.chroma_nc_pred(c, bx, by);
5252 let slot = &mut cac[c & 1][(by * 2 + bx) & 3];
5253 *slot = [0i32; 16];
5254 let total = decode_residual_block_into::<15, 16>(r, nc, slot)?; // RAW scan order
5255 self.chroma_nnz_cache_set(c, bx, by, total);
5256 // WIN: by < 2 and bx < 2, so 0..=3 -- the mask proves it against
5257 // the [[u8; 4]; 2], exactly as the cac[c & 1][(by * 2 + bx) & 3]
5258 // slot two lines above already does. Folds the last
5259 // panic_bounds_check out of inter_finish.
5260 cnnz[c & 1][(by * 2 + bx) & 3] = total;
5261 // Masks are the proof: max index 16 + 4 + 2 + 1 = 23 < 24 (was a `.min(23)`).
5262 nnzs[16 + (c & 1) * 4 + (by & 1) * 2 + (bx & 1)] = total;
5263 }
5264 for by in 0..2usize {
5265 let a = (mb_y * 2 + by) * w2 + mb_x * 2;
5266 self.nnz_c[c][a..a + 2].copy_from_slice(&cnnz[c][by * 2..by * 2 + 2]);
5267 }
5268 }
5269 }
5270
5271 // ---- reconstruction ----
5272 //
5273 // D13: this used to be a 109-line hand-rolled copy of the residual add.
5274 // It now calls the SAME `add_inter_residual` the CABAC path uses, which
5275 // is what makes the CAVLC E-seam possible at all: the two paths had
5276 // different residual representations (CAVLC pre-applied un_scan and
5277 // inv_quant at PARSE time; CABAC carries raw scan-order coefficients and
5278 // dequantises inside the helper), so no job could be shared. Carrying the
5279 // raw forms — which CAVLC already had in hand — converges them, deletes
5280 // a duplicate implementation, and lets a deferred job reuse the existing
5281 // worker recon instead of needing a second copy that could drift.
5282 if defer {
5283 // The residual representation now matches CABAC exactly (the
5284 // convergence commit), so the SAME `PInterJob` and the SAME worker
5285 // `recon_p_inter` serve both entropy coders — no second recon
5286 // implementation exists that could drift.
5287 let (mut gmv, mut gref) = ([(0i32, 0i32); 16], [0u8; 16]);
5288 let w4r = self.mb_w * 4;
5289 // ROW SLICES over BOTH grids. This was the single densest panic site
5290 // left in the decoder crate: sixteen iterations reading two PARALLEL
5291 // frame grids at the same index, and `mv_y`'s bound proved nothing
5292 // about `ref_idx_y`, so all thirty-two loads carried their own check.
5293 // Each macroblock row is four contiguous cells in both.
5294 for by in 0..4usize {
5295 let base = (mb_y * 4 + by) * w4r + mb_x * 4;
5296 let mvr = &self.mv_y[base..][..4];
5297 let rfr = &self.ref_idx_y[base..][..4];
5298 gmv[by * 4..][..4].copy_from_slice(mvr);
5299 for bx in 0..4usize {
5300 gref[by * 4 + bx] = rfr[bx].clamp(0, 15) as u8;
5301 }
5302 }
5303 // D9 applies here too: `cbp == 0` means all 2,592 coefficient bytes
5304 // of the 2,784-byte job are ZERO, so ship the 176-byte motion-only
5305 // form. Discovered on the CABAC path; it transfers for free because
5306 // the CAVLC arm now emits the SAME job type.
5307 let ej = if nores {
5308 edcstat::bump(&edcstat::J_NORES_SENT, 1);
5309 let b = self.take_nores_job(PInterNoResJob {
5310 mbx: mb_x,
5311 mby: mb_y,
5312 t8: t8x8,
5313 gmv,
5314 gref,
5315 });
5316 EdcJob::InterNoRes(b)
5317 } else {
5318 // Pooled box (see `take_pinter_job`); the CAVLC arm still parses into
5319 // locals, so this is a copy, not an allocation.
5320 let mut job = job.take().expect("pooled job taken above");
5321 job.mbx = mb_x;
5322 job.mby = mb_y;
5323 job.qp = qp;
5324 job.cbp_chroma = cbp_chroma;
5325 job.t8 = t8x8;
5326 job.gmv = gmv;
5327 job.gref = gref;
5328 // luma_scan / luma8 / cac were parsed in place.
5329 job.cdc = c_recon_dc;
5330 job.nnzs = nnzs;
5331 EdcJob::Inter(job)
5332 };
5333 if self.edc_tx.is_some() {
5334 self.edc_giveback();
5335 self.edc_send_job(ej);
5336 } else {
5337 self.edc_jobs.push(ej);
5338 }
5339 } else {
5340 self.add_inter_residual(
5341 mb_x,
5342 mb_y,
5343 pred_y,
5344 c_pred,
5345 Some(&*luma_scan),
5346 t8x8.then_some(&*luma8),
5347 &c_recon_dc,
5348 Some(&*cac),
5349 cbp_chroma,
5350 &nnzs,
5351 );
5352 // Give the scratch planes back (taken only on this arm).
5353 self.scratch_luma = s_luma;
5354 self.scratch_luma8 = s_luma8;
5355 self.scratch_cac = s_cac;
5356 }
5357
5358 // MV grid + coded flags were set per partition; mark modes as DC.
5359 // Four contiguous cells per row - `fill`, not sixteen indexed stores.
5360 for lby in 0..4usize {
5361 let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
5362 self.modes_y[a..a + 4].fill(2);
5363 }
5364 Ok(())
5365 }
5366
5367 // ---------------------------------------------------------------------
5368 // B-slice macroblock decoding
5369 // ---------------------------------------------------------------------
5370
5371 /// Per-list (`list` 0 or 1) MV-prediction neighbors for the block region at
5372 /// `(pbx, pby)` of width `pwb` blocks — the L0/L1 analogue of
5373 /// `mv_neighbors_block`.
5374 fn mv_neighbors_list(
5375 &self,
5376 pbx: isize,
5377 pby: isize,
5378 pwb: isize,
5379 list: usize,
5380 ) -> [MvNeighbor; 3] {
5381 self.mv_neighbors_block_grid(pbx, pby, pwb, list)
5382 }
5383
5384 /// Spatial-direct A/B/C at the MB origin — same for every 8×8 in the MB
5385 /// (spec §8.4.1.2.2). B_8x8 callers hoist this once; 16×16 skip/direct walk
5386 /// once inside `decode_b_direct`.
5387 #[inline]
5388 fn b_direct_nbrs(&self, mb_x: usize, mb_y: usize) -> ([MvNeighbor; 3], [MvNeighbor; 3]) {
5389 self.mv_neighbors_both((mb_x * 4) as isize, (mb_y * 4) as isize, 4)
5390 }
5391
5392 /// FUSED dual-list gather at arbitrary partition geometry: A/B/C positions
5393 /// and their availability (bounds + coded + slice) are list-independent —
5394 /// compute once, load both lists' grids from the same resolved index.
5395 fn mv_neighbors_both(
5396 &self,
5397 pbx: isize,
5398 pby: isize,
5399 pwb: isize,
5400 ) -> ([MvNeighbor; 3], [MvNeighbor; 3]) {
5401 let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
5402 let get2 = |bx: isize, by: isize| -> (MvNeighbor, MvNeighbor) {
5403 if bx < 0
5404 || by < 0
5405 || bx >= w4
5406 || by >= h4
5407 || !self
5408 .coded_y
5409 .get((by * w4 + bx) as usize)
5410 .copied()
5411 .unwrap_or(false)
5412 || !self.nbr_in_slice(bx as usize / 4, by as usize / 4)
5413 {
5414 (MvNeighbor::NONE, MvNeighbor::NONE)
5415 } else {
5416 // FOUR PARALLEL GRIDS at one index. The `coded_y` guard above
5417 // bounds none of them — they are separate Vecs — so each carried
5418 // its own panic path. `MvNeighbor::NONE` is the established
5419 // "no such neighbour" value, so the fallible form degrades into
5420 // the branch directly above it.
5421 let idx = (by * w4 + bx) as usize;
5422 match (
5423 self.mv_y.get(idx),
5424 self.ref_idx_y.get(idx),
5425 self.mv1.get(idx),
5426 self.ref_idx1.get(idx),
5427 ) {
5428 (Some(&m0), Some(&r0), Some(&m1), Some(&r1)) => (
5429 MvNeighbor {
5430 available: true,
5431 mv: m0,
5432 ref_idx: r0,
5433 },
5434 MvNeighbor {
5435 available: true,
5436 mv: m1,
5437 ref_idx: r1,
5438 },
5439 ),
5440 _ => (MvNeighbor::NONE, MvNeighbor::NONE),
5441 }
5442 }
5443 };
5444 let (a0, a1) = get2(pbx - 1, pby);
5445 let (b0, b1) = get2(pbx, pby - 1);
5446 let (mut c0, mut c1) = get2(pbx + pwb, pby - 1);
5447 if !c0.available {
5448 // The C fallback is position-driven (topright unavailable ⇒
5449 // topleft), so both lists fall back together — same decision the
5450 // two per-list gathers made independently.
5451 let t = get2(pbx - 1, pby - 1);
5452 c0 = t.0;
5453 c1 = t.1;
5454 }
5455 ([a0, b0, c0], [a1, b1, c1])
5456 }
5457
5458 fn mv_neighbors_block_grid(
5459 &self,
5460 pbx: isize,
5461 pby: isize,
5462 pwb: isize,
5463 list: usize,
5464 ) -> [MvNeighbor; 3] {
5465 let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
5466 let (mvg, refg) = if list == 0 {
5467 (&self.mv_y, &self.ref_idx_y)
5468 } else {
5469 (&self.mv1, &self.ref_idx1)
5470 };
5471 let get = |bx: isize, by: isize| -> MvNeighbor {
5472 if bx < 0
5473 || by < 0
5474 || bx >= w4
5475 || by >= h4
5476 || !self
5477 .coded_y
5478 .get((by * w4 + bx) as usize)
5479 .copied()
5480 .unwrap_or(false)
5481 || !self.nbr_in_slice(bx as usize / 4, by as usize / 4)
5482 {
5483 MvNeighbor::NONE
5484 } else {
5485 let idx = (by * w4 + bx) as usize;
5486 match (mvg.get(idx), refg.get(idx)) {
5487 (Some(&m), Some(&r)) => MvNeighbor {
5488 available: true,
5489 mv: m,
5490 ref_idx: r,
5491 },
5492 _ => MvNeighbor::NONE,
5493 }
5494 }
5495 };
5496 let a = get(pbx - 1, pby);
5497 let b = get(pbx, pby - 1);
5498 let mut c = get(pbx + pwb, pby - 1);
5499 if !c.available {
5500 c = get(pbx - 1, pby - 1);
5501 }
5502 [a, b, c]
5503 }
5504
5505 /// `colZeroFlag` for the 4×4 block at absolute block coords `(bx, by)`: true
5506 /// when `RefPicList1[0]` is a short-term picture whose co-located block uses
5507 /// reference 0 with a near-zero motion vector (spec §8.4.1.2.2).
5508 /// Co-located 4x4 block coords for the current block's `(bx4, by4)` within the
5509 /// macroblock, per spec 8.4.1.2.1. Under `direct_8x8_inference_flag` every 4x4
5510 /// in an 8x8 takes that 8x8's OUTER CORNER (`luma4x4BlkIdx = 5 * mbPartIdx`,
5511 /// i.e. (0,0) (3,0) (0,3) (3,3)); otherwise motion is genuinely per-4x4.
5512 ///
5513 /// 8.4.1.2.1 is SHARED by both direct modes, so spatial and temporal must map
5514 /// identically. They did not: temporal mapped the corner and spatial read the
5515 /// block's own coords, which is invisible while every 4x4 in the co-located 8x8
5516 /// carries the same motion -- true of every stream until sub-8x8 P partitions
5517 /// (x264 `--partitions p4x4`) make them differ. Hence one function.
5518 #[inline]
5519 fn col_block(&self, bx4: usize, by4: usize) -> (usize, usize) {
5520 if self.direct_8x8_inference {
5521 ((bx4 / 2) * 3, (by4 / 2) * 3)
5522 } else {
5523 (bx4, by4)
5524 }
5525 }
5526
5527 fn col_zero(&self, bx: usize, by: usize) -> bool {
5528 // Fast path (1T frozen colocated, the default): the invariant checks
5529 // were hoisted to set_b_context; two grid loads + the threshold test.
5530 if self.col_ok {
5531 let Some(col) = self.refs1.first() else {
5532 return false;
5533 };
5534 let idx = by * self.col_w4 + bx;
5535 // `.get` on EVERY parallel array, not a length test on one of them.
5536 // The `idx >= ref_idx.len()` guard proved nothing about `mv`,
5537 // `ref_idx1` or `mv1` — separate Vecs with independent lengths — so
5538 // each of those reads carried its own panic path. They are built
5539 // together and always agree; the fallible form states that instead of
5540 // asserting it, and keeps the "no panic on malformed input" contract.
5541 let (cref, cmv) = match col.ref_idx.get(idx) {
5542 Some(&r0) if r0 >= 0 => match col.mv.get(idx) {
5543 Some(&m) => (r0, m),
5544 None => return false,
5545 },
5546 Some(_) => match (col.ref_idx1.get(idx), col.mv1.get(idx)) {
5547 (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
5548 _ => return false,
5549 },
5550 None => return false,
5551 };
5552 return cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1;
5553 }
5554 let Some(col) = self.refs1.first() else {
5555 return false;
5556 };
5557 if let Some(live) = col.live.as_ref() {
5558 col.wait_motion_ready();
5559 let meta = live.meta.read().unwrap();
5560 if meta.long_term || meta.w4 == 0 {
5561 return false;
5562 }
5563 let idx = by * meta.w4 + bx;
5564 // Same `.get`-per-array shape as the frozen-colocated path above.
5565 let (cref, cmv) = match meta.ref_idx.get(idx) {
5566 Some(&r0) if r0 >= 0 => match meta.mv.get(idx) {
5567 Some(&m) => (r0, m),
5568 None => return false,
5569 },
5570 Some(_) => match (meta.ref_idx1.get(idx), meta.mv1.get(idx)) {
5571 (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
5572 _ => return false,
5573 },
5574 None => return false,
5575 };
5576 return cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1;
5577 }
5578 if col.long_term || col.w4 == 0 {
5579 return false;
5580 }
5581 let idx = by * col.w4 + bx;
5582 if idx >= col.ref_idx.len() {
5583 return false;
5584 }
5585 let (cref, cmv) = match col.ref_idx.get(idx) {
5586 Some(&r0) if r0 >= 0 => match col.mv.get(idx) {
5587 Some(&m) => (r0, m),
5588 None => return false,
5589 },
5590 Some(_) => match (col.ref_idx1.get(idx), col.mv1.get(idx)) {
5591 (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
5592 _ => return false,
5593 },
5594 None => return false,
5595 };
5596 cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1
5597 }
5598
5599 /// Implicit bi-prediction weights `(w0, w1)` from POC distances (spec
5600 /// §8.4.2.3.2), or `None` for the plain average (idc≠2, uni-pred, or the
5601 /// equidistant / out-of-range fall-back to 32:32 which equals the average).
5602 fn implicit_weights(&self, refi0: i32, refi1: i32) -> Option<(i32, i32)> {
5603 if self.weighted_bipred_idc != 2 || refi0 < 0 || refi1 < 0 {
5604 return None;
5605 }
5606 let (Some(r0), Some(r1)) = (
5607 self.refs.get(refi0 as usize),
5608 self.refs1.get(refi1 as usize),
5609 ) else {
5610 return None;
5611 };
5612 let td = (r1.pic_poc() - r0.pic_poc()).clamp(-128, 127);
5613 let tb = (self.cur_poc - r0.pic_poc()).clamp(-128, 127);
5614 if td == 0 || r0.long_term || r1.long_term {
5615 return None; // 32:32 → identical to the average
5616 }
5617 let tx = tx_for_td(td);
5618 let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
5619 let w1 = dsf >> 2;
5620 if !(-64..=128).contains(&w1) {
5621 return None; // out of range → 32:32 average
5622 }
5623 Some((64 - w1, w1))
5624 }
5625
5626 /// Motion-compensates a region with the given per-list refs/MVs. Bi-prediction
5627 /// is the simple `(a+b+1)>>1` average, or POC-weighted when implicit weighting
5628 /// (idc 2) is active. Writes into `pred_y`/`c_pred`.
5629 #[allow(clippy::too_many_arguments)]
5630 fn b_mc(
5631 &self,
5632 mb_x: usize,
5633 mb_y: usize,
5634 px: usize,
5635 py: usize,
5636 rw: usize,
5637 rh: usize,
5638 refi0: i32,
5639 mv0: (i32, i32),
5640 refi1: i32,
5641 mv1: (i32, i32),
5642 pred_y: &mut [u8; 256],
5643 c_pred: &mut [[u8; 64]; 2],
5644 ) {
5645 let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBMc);
5646 // Malformed-stream armor, mirroring the P path: now that B slices actually
5647 // PARSE ref_idx (they used to be hardcoded to 0), a mutated stream can hand
5648 // us an index past the end of either list. Clamp rather than panic — the
5649 // crate is `forbid(unsafe_code)` and fuzz-gated to never panic, and a
5650 // wrong picture on garbage input carries no conformance duty.
5651 let refi0 = if refi0 >= 0 {
5652 (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32
5653 } else {
5654 -1
5655 };
5656 let refi1 = if refi1 >= 0 {
5657 (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32
5658 } else {
5659 -1
5660 };
5661 if (refi0 >= 0 && self.refs.is_empty()) || (refi1 >= 0 && self.refs1.is_empty()) {
5662 return;
5663 }
5664 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
5665 let weights = {
5666 let _gw = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBWeights);
5667 self.implicit_weights(refi0, refi1)
5668 };
5669 // Bi-prediction blend: the weights decision is LOOP-INVARIANT, so every
5670 // blend site below matches on `weights` ONCE and runs a branch-free
5671 // pixel loop — the unweighted `(p+q+1)>>1` average then autovectorizes
5672 // (the per-pixel closure this replaces hid the invariant behind a
5673 // capture, and its chroma form was a &dyn call PER PIXEL).
5674 // FULL-WIDTH regions (px == 0, rw == 16 — every 16×16/16×8 partition and
5675 // most direct regions) occupy contiguous rows of `pred_y`, so MC writes
5676 // the destination DIRECTLY: uni-pred needs no staging at all, bi-pred
5677 // stages only the second list and blends in place. The staging arrays
5678 // (512 B zeroed per call before this) now exist only on the branches
5679 // that read them. Same fusion as the P path's mc_rect (WHYS Part 8).
5680 let full = px == 0 && rw == 16;
5681 if refi0 >= 0
5682 && refi1 >= 0
5683 && mv0.0 % 4 == 0
5684 && mv0.1 % 4 == 0
5685 && mv1.0 % 4 == 0
5686 && mv1.1 % 4 == 0
5687 {
5688 edcstat::bump(&edcstat::BMC_BI_FP, 1);
5689 }
5690 let _gl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBLuma);
5691 // One scratch borrow for the whole region — both bi-pred passes included.
5692 // The closure yields whether the arm already ran the chroma half (the
5693 // bi-pred full-width arm does, to keep its staging alive) — a plain
5694 // `return` inside would exit the CLOSURE only and chroma would run twice.
5695 let chroma_done =
5696 rusty_h264_common::inter::with_mc_scratch(|scr| match (refi0 >= 0, refi1 >= 0, full) {
5697 (true, false, true) => {
5698 let Some(rf) = self.refs.get(refi0 as usize) else {
5699 return false;
5700 };
5701 rusty_h264_common::inter::mc_luma_padded_pre(
5702 scr,
5703 &*rf.luma_guard(rf.ch),
5704 rf.lstride(),
5705 crate::LPAD,
5706 self.cw,
5707 ch,
5708 mb_x * 16,
5709 mb_y * 16 + py,
5710 rw,
5711 rh,
5712 mv0.0,
5713 mv0.1,
5714 &mut pred_y[py * 16..py * 16 + rw * rh],
5715 );
5716 false
5717 }
5718 (false, true, true) => {
5719 let Some(rf) = self.refs1.get(refi1 as usize) else {
5720 return false;
5721 };
5722 rusty_h264_common::inter::mc_luma_padded_pre(
5723 scr,
5724 &*rf.luma_guard(rf.ch),
5725 rf.lstride(),
5726 crate::LPAD,
5727 self.cw,
5728 ch,
5729 mb_x * 16,
5730 mb_y * 16 + py,
5731 rw,
5732 rh,
5733 mv1.0,
5734 mv1.1,
5735 &mut pred_y[py * 16..py * 16 + rw * rh],
5736 );
5737 false
5738 }
5739 (true, true, true) => {
5740 let Some(rf) = self.refs.get(refi0 as usize) else {
5741 return false;
5742 };
5743 rusty_h264_common::inter::mc_luma_padded_pre(
5744 scr,
5745 &*rf.luma_guard(rf.ch),
5746 rf.lstride(),
5747 crate::LPAD,
5748 self.cw,
5749 ch,
5750 mb_x * 16,
5751 mb_y * 16 + py,
5752 rw,
5753 rh,
5754 mv0.0,
5755 mv0.1,
5756 &mut pred_y[py * 16..py * 16 + rw * rh],
5757 );
5758 let mut b = [0u8; 256];
5759 let Some(rf) = self.refs1.get(refi1 as usize) else {
5760 return false;
5761 };
5762 rusty_h264_common::inter::mc_luma_padded_pre(
5763 scr,
5764 &*rf.luma_guard(rf.ch),
5765 rf.lstride(),
5766 crate::LPAD,
5767 self.cw,
5768 ch,
5769 mb_x * 16,
5770 mb_y * 16 + py,
5771 rw,
5772 rh,
5773 mv1.0,
5774 mv1.1,
5775 &mut b[..rw * rh],
5776 );
5777 drop(_gl);
5778 let _gbl =
5779 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
5780 // SLICE-then-zip: proving the bounds ONCE lets rustc emit the whole
5781 // 256-byte average as 8 straight-line vpavgb ops (verified in
5782 // isolation, x86-64-v3); the indexed form kept a per-iteration
5783 // bounds check and a loop. A hand AVX2 kernel is refuted — the
5784 // compiler already emits the ideal instruction.
5785 let dst = &mut pred_y[py * 16..py * 16 + rw * rh];
5786 match weights {
5787 None => {
5788 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
5789 *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
5790 }
5791 }
5792 Some((w0, w1)) => {
5793 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
5794 *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255)
5795 as u8;
5796 }
5797 }
5798 }
5799 let _gc =
5800 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
5801 self.b_mc_chroma(
5802 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
5803 );
5804 true
5805 }
5806 _ => {
5807 // Narrow region — rows are strided in `pred_y`; stage and copy.
5808 let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
5809 if refi0 >= 0 {
5810 let Some(rf) = self.refs.get(refi0 as usize) else {
5811 return false;
5812 };
5813 rusty_h264_common::inter::mc_luma_padded_pre(
5814 scr,
5815 &*rf.luma_guard(rf.ch),
5816 rf.lstride(),
5817 crate::LPAD,
5818 self.cw,
5819 ch,
5820 mb_x * 16 + px,
5821 mb_y * 16 + py,
5822 rw,
5823 rh,
5824 mv0.0,
5825 mv0.1,
5826 &mut a[..rw * rh],
5827 );
5828 }
5829 if refi1 >= 0 {
5830 let Some(rf) = self.refs1.get(refi1 as usize) else {
5831 return false;
5832 };
5833 rusty_h264_common::inter::mc_luma_padded_pre(
5834 scr,
5835 &*rf.luma_guard(rf.ch),
5836 rf.lstride(),
5837 crate::LPAD,
5838 self.cw,
5839 ch,
5840 mb_x * 16 + px,
5841 mb_y * 16 + py,
5842 rw,
5843 rh,
5844 mv1.0,
5845 mv1.1,
5846 &mut b[..rw * rh],
5847 );
5848 }
5849 drop(_gl);
5850 let _gbl =
5851 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
5852 match (refi0 >= 0, refi1 >= 0) {
5853 (true, true) => {
5854 for dy in 0..rh {
5855 let (ar, br) =
5856 (&a[dy * rw..dy * rw + rw], &b[dy * rw..dy * rw + rw]);
5857 let base = (py + dy) * 16 + px;
5858 let dst = &mut pred_y[base..base + rw];
5859 match weights {
5860 None => {
5861 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
5862 *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
5863 }
5864 }
5865 Some((w0, w1)) => {
5866 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
5867 *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6)
5868 .clamp(0, 255)
5869 as u8;
5870 }
5871 }
5872 }
5873 }
5874 }
5875 (true, false) => {
5876 for dy in 0..rh {
5877 let d = (py + dy) * 16 + px;
5878 pred_y[d..d + rw].copy_from_slice(&a[dy * rw..dy * rw + rw]);
5879 }
5880 }
5881 _ => {
5882 for dy in 0..rh {
5883 let d = (py + dy) * 16 + px;
5884 pred_y[d..d + rw].copy_from_slice(&b[dy * rw..dy * rw + rw]);
5885 }
5886 }
5887 }
5888 false
5889 }
5890 });
5891 if chroma_done {
5892 return;
5893 }
5894 let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
5895 self.b_mc_chroma(
5896 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
5897 );
5898 }
5899
5900 /// Chroma half of `b_mc`, with the same full-width direct-write fusion
5901 /// (crw == 8 rows are contiguous in the 8-wide `c_pred` planes).
5902 #[allow(clippy::too_many_arguments)]
5903 /// Chroma half of `b_mc`. U and V share every piece of MC geometry, so
5904 /// each list is ONE `mc_chroma_padded_pair` call (setup + range check paid
5905 /// once, kernels unchanged) instead of two per-plane calls — the per-plane
5906 /// pixel math is byte-identical to the old per-plane flow.
5907 #[allow(clippy::too_many_arguments)]
5908 fn b_mc_chroma(
5909 &self,
5910 mb_x: usize,
5911 mb_y: usize,
5912 px: usize,
5913 py: usize,
5914 rw: usize,
5915 rh: usize,
5916 refi0: i32,
5917 mv0: (i32, i32),
5918 refi1: i32,
5919 mv1: (i32, i32),
5920 c_pred: &mut [[u8; 64]; 2],
5921 weights: Option<(i32, i32)>,
5922 cch: usize,
5923 ) {
5924 use rusty_h264_common::inter::mc_chroma_padded_pair;
5925 let (crx, cry, crw, crh) = (px / 2, py / 2, rw / 2, rh / 2);
5926 let full = crx == 0 && crw == 8;
5927 let n = crw * crh;
5928 // RESOLVE BOTH SLOTS ONCE, then let the match patterns bind them. Every
5929 // arm below re-indexed `refs`/`refs1` by a raw `refi` — six checked Vec
5930 // indexes across the four arms — although `a0`/`a1` already meant
5931 // exactly "this slot is present". Carrying `Option<&RefPic>` instead of
5932 // a bool makes "present" and "in range" the same fact.
5933 let rf0 = (refi0 >= 0)
5934 .then(|| self.refs.get(refi0 as usize))
5935 .flatten();
5936 let rf1 = (refi1 >= 0)
5937 .then(|| self.refs1.get(refi1 as usize))
5938 .flatten();
5939 let (a0, a1) = (rf0.is_some(), rf1.is_some());
5940 let [cu, cv] = c_pred;
5941 match (a0, a1, full) {
5942 (true, false, true) | (false, true, true) => {
5943 let (Some(rf), mv) = (if a0 { rf0 } else { rf1 }, if a0 { mv0 } else { mv1 })
5944 else {
5945 return;
5946 };
5947 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
5948 mc_chroma_padded_pair(
5949 &gu,
5950 &gv,
5951 rf.cstride(),
5952 crate::CPAD,
5953 self.ccw,
5954 cch,
5955 mb_x * 8,
5956 mb_y * 8 + cry,
5957 crw,
5958 crh,
5959 mv.0,
5960 mv.1,
5961 &mut cu[cry * 8..cry * 8 + n],
5962 &mut cv[cry * 8..cry * 8 + n],
5963 );
5964 }
5965 (true, true, true) => {
5966 let (Some(rfa), Some(rfb)) = (rf0, rf1) else {
5967 return;
5968 };
5969 {
5970 let rf = rfa;
5971 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
5972 mc_chroma_padded_pair(
5973 &gu,
5974 &gv,
5975 rf.cstride(),
5976 crate::CPAD,
5977 self.ccw,
5978 cch,
5979 mb_x * 8,
5980 mb_y * 8 + cry,
5981 crw,
5982 crh,
5983 mv0.0,
5984 mv0.1,
5985 &mut cu[cry * 8..cry * 8 + n],
5986 &mut cv[cry * 8..cry * 8 + n],
5987 );
5988 }
5989 let (mut bu, mut bv) = ([0u8; 64], [0u8; 64]);
5990 {
5991 let rf = rfb;
5992 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
5993 mc_chroma_padded_pair(
5994 &gu,
5995 &gv,
5996 rf.cstride(),
5997 crate::CPAD,
5998 self.ccw,
5999 cch,
6000 mb_x * 8,
6001 mb_y * 8 + cry,
6002 crw,
6003 crh,
6004 mv1.0,
6005 mv1.1,
6006 &mut bu[..n],
6007 &mut bv[..n],
6008 );
6009 }
6010 for (dst, stage) in [(&mut *cu, &bu), (&mut *cv, &bv)] {
6011 let d = &mut dst[cry * 8..cry * 8 + n];
6012 match weights {
6013 None => {
6014 for (o, q) in d.iter_mut().zip(&stage[..n]) {
6015 *o = ((*o as u16 + *q as u16 + 1) >> 1) as u8;
6016 }
6017 }
6018 Some((w0, w1)) => {
6019 for (o, q) in d.iter_mut().zip(&stage[..n]) {
6020 *o = ((*o as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255)
6021 as u8;
6022 }
6023 }
6024 }
6025 }
6026 }
6027 _ => {
6028 // Narrow region — rows are strided in the 8-wide pred planes;
6029 // stage per list (paired), then copy/blend strided per plane.
6030 let (mut au, mut av, mut bu, mut bv) = ([0u8; 64], [0u8; 64], [0u8; 64], [0u8; 64]);
6031 if let Some(rf) = rf0 {
6032 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6033 mc_chroma_padded_pair(
6034 &gu,
6035 &gv,
6036 rf.cstride(),
6037 crate::CPAD,
6038 self.ccw,
6039 cch,
6040 mb_x * 8 + crx,
6041 mb_y * 8 + cry,
6042 crw,
6043 crh,
6044 mv0.0,
6045 mv0.1,
6046 &mut au[..n],
6047 &mut av[..n],
6048 );
6049 }
6050 if let Some(rf) = rf1 {
6051 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6052 mc_chroma_padded_pair(
6053 &gu,
6054 &gv,
6055 rf.cstride(),
6056 crate::CPAD,
6057 self.ccw,
6058 cch,
6059 mb_x * 8 + crx,
6060 mb_y * 8 + cry,
6061 crw,
6062 crh,
6063 mv1.0,
6064 mv1.1,
6065 &mut bu[..n],
6066 &mut bv[..n],
6067 );
6068 }
6069 for (dst, sa, sb) in [(&mut *cu, &au, &bu), (&mut *cv, &av, &bv)] {
6070 for dy in 0..crh {
6071 let base = (cry + dy) * 8 + crx;
6072 let d = &mut dst[base..base + crw];
6073 match (a0, a1) {
6074 (true, true) => {
6075 let (pr, qr) =
6076 (&sa[dy * crw..dy * crw + crw], &sb[dy * crw..dy * crw + crw]);
6077 match weights {
6078 None => {
6079 for ((o, pp), q) in d.iter_mut().zip(pr).zip(qr) {
6080 *o = ((*pp as u16 + *q as u16 + 1) >> 1) as u8;
6081 }
6082 }
6083 Some((w0, w1)) => {
6084 for ((o, pp), q) in d.iter_mut().zip(pr).zip(qr) {
6085 *o = ((*pp as i32 * w0 + *q as i32 * w1 + 32) >> 6)
6086 .clamp(0, 255)
6087 as u8;
6088 }
6089 }
6090 }
6091 }
6092 (true, false) => d.copy_from_slice(&sa[dy * crw..dy * crw + crw]),
6093 _ => d.copy_from_slice(&sb[dy * crw..dy * crw + crw]),
6094 }
6095 }
6096 }
6097 }
6098 }
6099 }
6100
6101 /// Commits a region's per-list motion to the 4×4 grids (and marks coded).
6102 #[allow(clippy::too_many_arguments)]
6103 fn b_set_motion(
6104 &mut self,
6105 mb_x: usize,
6106 mb_y: usize,
6107 px: usize,
6108 py: usize,
6109 rw: usize,
6110 rh: usize,
6111 refi0: i32,
6112 mv0: (i32, i32),
6113 refi1: i32,
6114 mv1: (i32, i32),
6115 ) {
6116 let _gs = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBSet);
6117 let w4 = self.mb_w * 4;
6118 let mv0w = if refi0 >= 0 { mv0 } else { (0, 0) };
6119 let mv1w = if refi1 >= 0 { mv1 } else { (0, 0) };
6120 let by0 = mb_y * 4 + py / 4;
6121 let bx0 = mb_x * 4 + px / 4;
6122 let (bw, bh) = (rw / 4, rh / 4);
6123 // Contiguous per-row slices: fill instead of per-4x4 stores (same values).
6124 // SEVEN parallel grids, seven separate range checks — they are disjoint
6125 // fields of `self`, so one `get_mut` apiece resolves them all together
6126 // and a short row simply writes nothing rather than panicking.
6127 for by in by0..by0 + bh {
6128 let row = by * w4 + bx0;
6129 let end = row + bw;
6130 let (Some(r0), Some(m0), Some(r1), Some(m1)) = (
6131 self.ref_idx_y.get_mut(row..end),
6132 self.mv_y.get_mut(row..end),
6133 self.ref_idx1.get_mut(row..end),
6134 self.mv1.get_mut(row..end),
6135 ) else {
6136 continue;
6137 };
6138 r0.fill(refi0);
6139 m0.fill(mv0w);
6140 r1.fill(refi1);
6141 m1.fill(mv1w);
6142 let (Some(it), Some(cd), Some(md)) = (
6143 self.inter_y.get_mut(row..end),
6144 self.coded_y.get_mut(row..end),
6145 self.modes_y.get_mut(row..end),
6146 ) else {
6147 continue;
6148 };
6149 it.fill(true);
6150 cd.fill(true);
6151 md.fill(2);
6152 }
6153 }
6154
6155 /// Spatial direct prediction for a region (whole MB or an 8×8): derives the
6156 /// per-list reference indices and base MVs, then motion-compensates each 4×4
6157 /// sub-block (applying `colZeroFlag`) and commits the motion (spec §8.4.1.2.2).
6158 #[allow(clippy::too_many_arguments)]
6159 /// Splits a `w`×`h` block region (4×4-block units) into the fewest rectangles
6160 /// whose contents are `uniform`, preferring partition-shaped cuts (whole →
6161 /// horizontal halves → vertical halves → quadrants). Emits at most w·h rects
6162 /// (the all-different worst case degenerates to per-block, i.e. the old loop).
6163 fn coalesce_region<
6164 U: Fn(usize, usize, usize, usize) -> bool,
6165 E: FnMut(usize, usize, usize, usize),
6166 >(
6167 x: usize,
6168 y: usize,
6169 w: usize,
6170 h: usize,
6171 uniform: &U,
6172 emit: &mut E,
6173 ) {
6174 if uniform(x, y, w, h) {
6175 emit(x, y, w, h);
6176 return;
6177 }
6178 if h > 1 && uniform(x, y, w, h / 2) && uniform(x, y + h / 2, w, h / 2) {
6179 emit(x, y, w, h / 2);
6180 emit(x, y + h / 2, w, h / 2);
6181 return;
6182 }
6183 if w > 1 && uniform(x, y, w / 2, h) && uniform(x + w / 2, y, w / 2, h) {
6184 emit(x, y, w / 2, h);
6185 emit(x + w / 2, y, w / 2, h);
6186 return;
6187 }
6188 match (w > 1, h > 1) {
6189 (true, true) => {
6190 for q in 0..4usize {
6191 Self::coalesce_region(
6192 x + (q % 2) * (w / 2),
6193 y + (q / 2) * (h / 2),
6194 w / 2,
6195 h / 2,
6196 uniform,
6197 emit,
6198 );
6199 }
6200 }
6201 (true, false) => {
6202 Self::coalesce_region(x, y, w / 2, h, uniform, emit);
6203 Self::coalesce_region(x + w / 2, y, w / 2, h, uniform, emit);
6204 }
6205 (false, true) => {
6206 Self::coalesce_region(x, y, w, h / 2, uniform, emit);
6207 Self::coalesce_region(x, y + h / 2, w, h / 2, uniform, emit);
6208 }
6209 (false, false) => emit(x, y, 1, 1),
6210 }
6211 }
6212
6213 fn decode_b_direct(
6214 &mut self,
6215 mb_x: usize,
6216 mb_y: usize,
6217 px: usize,
6218 py: usize,
6219 rw: usize,
6220 rh: usize,
6221 pred_y: &mut [u8; 256],
6222 c_pred: &mut [[u8; 64]; 2],
6223 ) {
6224 if !self.direct_spatial {
6225 return self.decode_b_direct_temporal(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred);
6226 }
6227 let (n0, n1) = self.b_direct_nbrs(mb_x, mb_y);
6228 self.decode_b_direct_n(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred, n0, n1);
6229 }
6230
6231 /// Spec §8.4.1.2.2: the spatial-direct reference indices (min positive over
6232 /// the three neighbors, per list) and the predicted MVs. `direct_zero` is
6233 /// the both-lists-unavailable case, which forces ref 0 / mv (0,0). Shared
6234 /// by `decode_b_direct_n` and the B_Skip zero-bi fast path — ONE derivation,
6235 /// no drift.
6236 #[inline]
6237 fn b_direct_refs_mvs(
6238 n0: &[MvNeighbor; 3],
6239 n1: &[MvNeighbor; 3],
6240 ) -> (i32, i32, (i32, i32), (i32, i32), bool) {
6241 let min_pos = |a: i32, b: i32| {
6242 if a < 0 {
6243 b
6244 } else if b < 0 {
6245 a
6246 } else {
6247 a.min(b)
6248 }
6249 };
6250 let rid = |n: &[MvNeighbor; 3]| min_pos(min_pos(n[0].ref_idx, n[1].ref_idx), n[2].ref_idx);
6251 let (mut refi0, mut refi1) = (rid(n0), rid(n1));
6252 let direct_zero = refi0 < 0 && refi1 < 0;
6253 if direct_zero {
6254 refi0 = 0;
6255 refi1 = 0;
6256 }
6257 let mv0 = if refi0 >= 0 && !direct_zero {
6258 predict_mv(n0[0], n0[1], n0[2], refi0)
6259 } else {
6260 (0, 0)
6261 };
6262 let mv1 = if refi1 >= 0 && !direct_zero {
6263 predict_mv(n1[0], n1[1], n1[2], refi1)
6264 } else {
6265 (0, 0)
6266 };
6267 (refi0, refi1, mv0, mv1, direct_zero)
6268 }
6269
6270 fn decode_b_direct_n(
6271 &mut self,
6272 mb_x: usize,
6273 mb_y: usize,
6274 px: usize,
6275 py: usize,
6276 rw: usize,
6277 rh: usize,
6278 pred_y: &mut [u8; 256],
6279 c_pred: &mut [[u8; 64]; 2],
6280 n0: [MvNeighbor; 3],
6281 n1: [MvNeighbor; 3],
6282 ) {
6283 let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBDirect);
6284 // H-48: DERIVATION-ONLY scope, dropped before the MC loop below. DecBDirect
6285 // wraps this function whole and therefore INCLUDES the `b_mc` calls it makes,
6286 // so its 1460 ns/call was never "MV derivation is slow" — that read was wrong.
6287 // This guard is what separates the two.
6288 let gd = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBDeriv);
6289 let derived = Self::b_direct_refs_mvs(&n0, &n1);
6290 drop(gd);
6291 self.b_direct_region(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred, derived);
6292 }
6293
6294 /// The post-derivation half of spatial direct: czg probing (gated), region
6295 /// coalescing, MC + motion commit. Split out so B_8x8 direct subs derive
6296 /// ONCE per MB (the A/B/C neighbours and the rid/median result are
6297 /// MB-level; only czg varies per 8x8).
6298 #[allow(clippy::too_many_arguments)]
6299 fn b_direct_region(
6300 &mut self,
6301 mb_x: usize,
6302 mb_y: usize,
6303 px: usize,
6304 py: usize,
6305 rw: usize,
6306 rh: usize,
6307 pred_y: &mut [u8; 256],
6308 c_pred: &mut [[u8; 64]; 2],
6309 derived: (i32, i32, (i32, i32), (i32, i32), bool),
6310 ) {
6311 let (refi0, refi1, mv0, mv1, direct_zero) = derived;
6312 // Per 4×4 sub-block: colZeroFlag zeroes the ref-0 motion vector. cz is the
6313 // ONLY per-block variable (two possible (m0,m1) values for the region), and
6314 // the MC filters + bi-blend are per-output-pixel — so sub-blocks with equal
6315 // cz coalesce into one wider `b_mc`, BIT-IDENTICAL. A 16×16 direct MB paid
6316 // 16 bi-pred b_mc calls (~96 MC kernel entries) before this; typically 1 now.
6317 let (bx0, by0, bw, bh) = (px / 4, py / 4, rw / 4, rh / 4);
6318 // MASKED at every use below. The loop bounds are the region's `bw`/`bh` in
6319 // 4x4 units, which are always <= 4 for a macroblock but are RUNTIME values,
6320 // so none of the eight indexes into this fixed 4x4 grid was provable.
6321 let mut czg = [[false; 4]; 4]; // region-local, [dy][dx]
6322 // colZeroFlag can only change a list whose ref is 0 AND whose predicted
6323 // MV is nonzero (it zeroes MVs; zeroing (0,0) is a no-op). When neither
6324 // list qualifies, skip the probing entirely: czg stays false, the
6325 // region is uniform, and the m values are identical — the only change
6326 // is FEWER b_mc calls where the old czg would have split rects with
6327 // equal values (tiles of the same math, byte-identical).
6328 let cz_matters = (refi0 == 0 && mv0 != (0, 0)) || (refi1 == 0 && mv1 != (0, 0));
6329 // Uniformity is KNOWN after the probe fill — when every probe agreed,
6330 // the 16-bool coalesce scan and its recursion are skipped outright.
6331 let mut cz_mixed = false;
6332 if cz_matters && !direct_zero && self.direct_8x8_inference {
6333 // Under direct_8x8_inference every 4×4 in an 8×8 shares one colZeroFlag
6334 // (col_block collapses to the MB-corner). Probe once per 8×8 — same
6335 // czg, fewer wait_motion_ready / meta locks on the live-ref path.
6336 let mut oy = 0usize;
6337 while oy < bh {
6338 let h = (bh - oy).min(2);
6339 let mut ox = 0usize;
6340 while ox < bw {
6341 let w = (bw - ox).min(2);
6342 let (colx, coly) = self.col_block(bx0 + ox, by0 + oy);
6343 let cz = self.col_zero(mb_x * 4 + colx, mb_y * 4 + coly);
6344 cz_mixed |= cz != czg[0][0] && !(ox == 0 && oy == 0);
6345 for dy in oy..oy + h {
6346 for dx in ox..ox + w {
6347 czg[dy & 3][dx & 3] = cz;
6348 }
6349 }
6350 ox += w;
6351 }
6352 oy += h;
6353 }
6354 } else if cz_matters && !direct_zero {
6355 for dy in 0..bh {
6356 for dx in 0..bw {
6357 let (colx, coly) = self.col_block(bx0 + dx, by0 + dy);
6358 let cz = self.col_zero(mb_x * 4 + colx, mb_y * 4 + coly);
6359 cz_mixed |= cz != czg[0][0] && !(dx == 0 && dy == 0);
6360 czg[dy & 3][dx & 3] = cz;
6361 }
6362 }
6363 }
6364 let mut rects: [(usize, usize, usize, usize); 16] = [(0, 0, 0, 0); 16];
6365 let mut n = 0usize;
6366 if !cz_mixed {
6367 rects[0] = (0, 0, bw, bh);
6368 n = 1;
6369 } else {
6370 let uniform = |x: usize, y: usize, w: usize, h: usize| -> bool {
6371 let t = czg[y & 3][x & 3];
6372 (y..y + h).all(|dy| (x..x + w).all(|dx| czg[dy & 3][dx & 3] == t))
6373 };
6374 Self::coalesce_region(0, 0, bw, bh, &uniform, &mut |x, y, w, h| {
6375 rects[n & 15] = (x, y, w, h);
6376 n += 1;
6377 });
6378 }
6379 let recording = self.edc_regions.is_some();
6380 if rw == 16 && rh == 16 {
6381 edcstat::bump(&edcstat::BSK_FULLMB, 1);
6382 if n == 1 {
6383 edcstat::bump(&edcstat::BSK_1RECT, 1);
6384 let cz = czg[0][0];
6385 let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
6386 let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
6387 let (a0, a1) = (refi0 >= 0, refi1 >= 0);
6388 if a0 && a1 && m0 == (0, 0) && m1 == (0, 0) {
6389 edcstat::bump(&edcstat::BSK_ZBI, 1);
6390 } else if (a0 != a1) && (if a0 { m0 } else { m1 }) == (0, 0) {
6391 edcstat::bump(&edcstat::BSK_ZUNI, 1);
6392 } else if (!a0 || (m0.0 % 4 == 0 && m0.1 % 4 == 0))
6393 && (!a1 || (m1.0 % 4 == 0 && m1.1 % 4 == 0))
6394 {
6395 edcstat::bump(&edcstat::BSK_FP, 1);
6396 }
6397 if self.bsk_last == Some((mb_x.wrapping_sub(1), mb_y, refi0, refi1, m0, m1)) {
6398 edcstat::bump(&edcstat::BSK_RUNCONT, 1);
6399 }
6400 self.bsk_last = Some((mb_x, mb_y, refi0, refi1, m0, m1));
6401 } else {
6402 self.bsk_last = None;
6403 }
6404 }
6405 for &(x, y, w, h) in &rects[..n] {
6406 let cz = czg[y & 3][x & 3];
6407 let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
6408 let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
6409 let (lx, ly, lw, lh) = ((bx0 + x) * 4, (by0 + y) * 4, w * 4, h * 4);
6410 if recording {
6411 self.b_mc_or_record(
6412 mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1, pred_y, c_pred,
6413 );
6414 } else {
6415 self.b_mc(
6416 mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1, pred_y, c_pred,
6417 );
6418 }
6419 self.b_set_motion(mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1);
6420 }
6421 }
6422
6423 /// Temporal direct prediction for a region (spec §8.4.1.2.3): for each 4×4
6424 /// (or per-8×8 corner under `direct_8x8_inference`), take the co-located
6425 /// List-0 motion from `RefPicList1[0]`, map its reference into the current
6426 /// List-0 by POC, and scale the motion vector by the POC distances.
6427 #[allow(clippy::too_many_arguments)]
6428 fn decode_b_direct_temporal(
6429 &mut self,
6430 mb_x: usize,
6431 mb_y: usize,
6432 px: usize,
6433 py: usize,
6434 rw: usize,
6435 rh: usize,
6436 pred_y: &mut [u8; 256],
6437 c_pred: &mut [[u8; 64]; 2],
6438 ) {
6439 let poc1 = self.refs1.first().map_or(0, |f| f.pic_poc());
6440 let infer = self.direct_8x8_inference;
6441 // Under direct_8x8_inference every 4×4 in an 8×8 takes the same MB-corner
6442 // co-located motion, so motion-compensate the whole 8×8 in one call — this
6443 // hits the width-8 MC asm and pays the per-call tile/blend setup 4× less.
6444 // Without inference, motion is genuinely per-4×4. Bit-identical either way
6445 // (MC of an 8×8 with one MV == four 4×4 MCs with that same MV).
6446 let step = if infer { 8 } else { 4 };
6447 let mut sy = py;
6448 while sy < py + rh {
6449 let mut sx = px;
6450 while sx < px + rw {
6451 // Co-located 4×4 (the 8×8's MB-corner under inference) — shared with
6452 // the spatial path's colZeroFlag, which must map identically.
6453 let (colx, coly) = self.col_block(sx / 4, sy / 4);
6454 let (mvcol, refpoc) = {
6455 let Some(col) = self.refs1.first() else {
6456 return;
6457 };
6458 if let Some(live) = col.live.as_ref() {
6459 col.wait_motion_ready();
6460 let meta = live.meta.read().unwrap();
6461 let idx = (mb_y * 4 + coly) * meta.w4 + (mb_x * 4 + colx);
6462 // `mv` and `ref_poc` are PARALLEL Vecs: the `mv.len()`
6463 // guard said nothing about `ref_poc`.
6464 match (meta.mv.get(idx), meta.ref_poc.get(idx)) {
6465 (Some(&m), Some(&pc)) if meta.w4 != 0 && pc != i32::MIN => (m, pc),
6466 _ => ((0, 0), i32::MIN),
6467 }
6468 } else {
6469 let idx = (mb_y * 4 + coly) * col.w4 + (mb_x * 4 + colx);
6470 // intra co-located → zero motion, refIdxL0 = 0
6471 match (col.mv.get(idx), col.ref_poc.get(idx)) {
6472 (Some(&m), Some(&pc)) if col.w4 != 0 && pc != i32::MIN => (m, pc),
6473 _ => ((0, 0), i32::MIN),
6474 }
6475 }
6476 };
6477 // MapColToList0: the current-list index of the co-located reference.
6478 let (refi0, mvc) = if refpoc == i32::MIN {
6479 (0, (0, 0))
6480 } else {
6481 let r = self
6482 .refs
6483 .iter()
6484 .position(|f| f.pic_poc() == refpoc)
6485 .unwrap_or(0) as i32;
6486 (r, mvcol)
6487 };
6488 let Some(r0) = self.refs.get(refi0 as usize) else {
6489 return;
6490 };
6491 let poc0 = r0.pic_poc();
6492 let td = (poc1 - poc0).clamp(-128, 127);
6493 let tb = (self.cur_poc - poc0).clamp(-128, 127);
6494 let (mv0, mv1) = if td == 0 || r0.long_term {
6495 (mvc, (0, 0))
6496 } else {
6497 let tx = tx_for_td(td);
6498 let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
6499 let m0 = ((dsf * mvc.0 + 128) >> 8, (dsf * mvc.1 + 128) >> 8);
6500 (m0, (m0.0 - mvc.0, m0.1 - mvc.1))
6501 };
6502 self.b_mc_or_record(
6503 mb_x, mb_y, sx, sy, step, step, refi0, mv0, 0, mv1, pred_y, c_pred,
6504 );
6505 self.b_set_motion(mb_x, mb_y, sx, sy, step, step, refi0, mv0, 0, mv1);
6506 sx += step;
6507 }
6508 sy += step;
6509 }
6510 }
6511
6512 /// Reads `ref_idx_lX` for a B partition (te(v)/ue(v) by the list's active
6513 /// count), bounds-checked against the available reference count.
6514 fn read_b_ref(&self, r: &mut BitReader, list: usize) -> Result<i32, MbError> {
6515 let (active, avail) = if list == 0 {
6516 (self.num_ref_active, self.refs.len())
6517 } else {
6518 (self.num_ref_active1, self.refs1.len())
6519 };
6520 let v = if active > 1 {
6521 read_ref_idx(r, active)?
6522 } else {
6523 0
6524 };
6525 if v as usize >= avail {
6526 return Err(MbError::Truncated);
6527 }
6528 Ok(v)
6529 }
6530
6531 /// Reconstructs a `B_Skip` macroblock: spatial-direct prediction, no residual.
6532 /// Defer one fast B_Skip's grid commit + recon into the pending row span.
6533 /// Continuation requires position adjacency AND kind equality.
6534 #[inline]
6535 fn bz_push(&mut self, mb_x: usize, mb_y: usize, kind: BzKind) {
6536 match self.bzspan {
6537 Some((row, x0, ref mut n, k)) if row == mb_y && x0 + *n == mb_x && k == kind => *n += 1,
6538 _ => {
6539 self.bz_flush();
6540 self.bzspan = Some((mb_y, mb_x, 1, kind));
6541 }
6542 }
6543 }
6544
6545 /// Range-fill the pending span's grids. MUST run before anything reads
6546 /// the motion/coded/mode/nnz grids of a deferred MB: decode_b_mb entry,
6547 /// decode_b_skip's b_direct_nbrs, bS derivation (derive_bs_row callers
6548 /// via row_hook + deblock), and slice end (edc_flush covers it).
6549 #[inline(always)]
6550 fn bz_flush(&mut self) {
6551 if self.bzspan.is_some() {
6552 self.bz_flush_slow();
6553 }
6554 }
6555
6556 fn bz_flush_slow(&mut self) {
6557 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSpanRecon);
6558 let Some((row, x0, n, kind)) = self.bzspan.take() else {
6559 return;
6560 };
6561 edcstat::bump(&edcstat::BZ_SPANS, 1);
6562 edcstat::bump(&edcstat::BZ_SPAN_MBS, n as u64);
6563 // Grid values per kind (mirrors b_set_motion's inactive-list zeroing).
6564 let (g_r0, g_r1, g_m0, g_m1): (i32, i32, (i32, i32), (i32, i32)) = match kind {
6565 BzKind::ZeroBi => (0, 0, (0, 0), (0, 0)),
6566 BzKind::ZeroUni(0, ri) => (ri as i32, -1, (0, 0), (0, 0)),
6567 BzKind::ZeroUni(_, ri) => (-1, ri as i32, (0, 0), (0, 0)),
6568 BzKind::Fp { r0, r1, m0, m1 } => {
6569 let mm0 = if r0 >= 0 {
6570 (m0.0 as i32, m0.1 as i32)
6571 } else {
6572 (0, 0)
6573 };
6574 let mm1 = if r1 >= 0 {
6575 (m1.0 as i32, m1.1 as i32)
6576 } else {
6577 (0, 0)
6578 };
6579 (r0 as i32, r1 as i32, mm0, mm1)
6580 }
6581 };
6582 let w4 = self.mb_w * 4;
6583 let (b0, len) = (x0 * 4, n * 4);
6584 for dy in 0..4 {
6585 let a = (row * 4 + dy) * w4 + b0;
6586 self.ref_idx_y[a..a + len].fill(g_r0);
6587 self.mv_y[a..a + len].fill(g_m0);
6588 self.ref_idx1[a..a + len].fill(g_r1);
6589 self.mv1[a..a + len].fill(g_m1);
6590 self.inter_y[a..a + len].fill(true);
6591 self.coded_y[a..a + len].fill(true);
6592 self.modes_y[a..a + len].fill(2);
6593 self.nnz_y[a..a + len].fill(0);
6594 }
6595 match kind {
6596 BzKind::ZeroBi => self.bz_recon_band_bi(row, x0, n),
6597 BzKind::ZeroUni(list, ri) => {
6598 self.bz_recon_band_copy(row, x0, n, list as usize, ri as usize, (0, 0))
6599 }
6600 BzKind::Fp { r0, r1, m0, m1 } => {
6601 let mv0 = (m0.0 as i32, m0.1 as i32);
6602 let mv1 = (m1.0 as i32, m1.1 as i32);
6603 match (r0 >= 0, r1 >= 0) {
6604 (true, true) => {
6605 self.bz_recon_band_fp_bi(row, x0, n, r0 as usize, r1 as usize, mv0, mv1)
6606 }
6607 (true, false) => self.bz_recon_band_copy(row, x0, n, 0, r0 as usize, mv0),
6608 _ => self.bz_recon_band_copy(row, x0, n, 1, r1 as usize, mv1),
6609 }
6610 }
6611 }
6612 }
6613
6614 /// ZeroBi band: rows of 16n averaged from both padded refs (index 0).
6615 fn bz_recon_band_bi(&mut self, row: usize, x0: usize, n: usize) {
6616 // Byte-identical to n recon_b_skip_zero_bi(_, _, 0, 0) calls: each
6617 // output byte is the same (a + b + 1) >> 1 of the same source bytes.
6618 let w = n * 16;
6619 let Some(rf0) = self.refs.first() else { return };
6620 let Some(rf1) = self.refs1.first() else {
6621 return;
6622 };
6623 let (l0st, l1st) = (rf0.lstride(), rf1.lstride());
6624 {
6625 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6626 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
6627 for dy in 0..16 {
6628 let y = row * 16 + dy;
6629 let s0 = (y + crate::LPAD) * l0st + crate::LPAD + x0 * 16;
6630 let s1 = (y + crate::LPAD) * l1st + crate::LPAD + x0 * 16;
6631 let d = y * self.cw + x0 * 16;
6632 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
6633 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
6634 // span in kernel-legal widths.
6635 rusty_h264_common::inter::avg_row_into(
6636 &ly0[s0..s0 + w],
6637 &ly1[s1..s1 + w],
6638 w,
6639 &mut self.rec_y[d..d + w],
6640 );
6641 }
6642 }
6643 let (c0st, c1st) = (rf0.cstride(), rf1.cstride());
6644 let wc = n * 8;
6645 for c in 0..2 {
6646 let rc0 = rf0.chroma_guard(c, rf0.ch);
6647 let rc1 = rf1.chroma_guard(c, rf1.ch);
6648 let plane = if c == 0 {
6649 &mut self.rec_u
6650 } else {
6651 &mut self.rec_v
6652 };
6653 for dy in 0..8 {
6654 let y = row * 8 + dy;
6655 let s0 = (y + crate::CPAD) * c0st + crate::CPAD + x0 * 8;
6656 let s1 = (y + crate::CPAD) * c1st + crate::CPAD + x0 * 8;
6657 let d = y * self.ccw + x0 * 8;
6658 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
6659 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
6660 // span in kernel-legal widths.
6661 rusty_h264_common::inter::avg_row_into(
6662 &rc0[s0..s0 + wc],
6663 &rc1[s1..s1 + wc],
6664 wc,
6665 &mut plane[d..d + wc],
6666 );
6667 }
6668 }
6669 }
6670
6671 /// Full-pel offset copy band from ONE list's padded planes. The caller
6672 /// prevalidated (per MB, at push): mv%8 == 0 both components and luma +
6673 /// chroma windows inside the padded planes — contiguous MB windows tile,
6674 /// so the span's union window is valid by induction.
6675 #[allow(clippy::too_many_arguments)]
6676 fn bz_recon_band_copy(
6677 &mut self,
6678 row: usize,
6679 x0: usize,
6680 n: usize,
6681 list: usize,
6682 ri: usize,
6683 mv: (i32, i32),
6684 ) {
6685 let Some(rf) = (if list == 0 {
6686 self.refs.get(ri)
6687 } else {
6688 self.refs1.get(ri)
6689 }) else {
6690 return;
6691 };
6692 let lst = rf.lstride();
6693 let (dx, dy) = ((mv.0 / 4) as isize, (mv.1 / 4) as isize);
6694 let w = n * 16;
6695 {
6696 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6697 let ly = rf.luma_guard(rf.ch);
6698 for r in 0..16 {
6699 let y = (row * 16 + r) as isize + dy;
6700 let src = ((y + crate::LPAD as isize) * lst as isize
6701 + crate::LPAD as isize
6702 + x0 as isize * 16
6703 + dx) as usize;
6704 let d = (row * 16 + r) * self.cw + x0 * 16;
6705 self.rec_y[d..d + w].copy_from_slice(&ly[src..src + w]);
6706 }
6707 }
6708 let cst = rf.cstride();
6709 let (cdx, cdy) = ((mv.0 / 8) as isize, (mv.1 / 8) as isize);
6710 let wc = n * 8;
6711 for c in 0..2 {
6712 let rc = rf.chroma_guard(c, rf.ch);
6713 let plane = if c == 0 {
6714 &mut self.rec_u
6715 } else {
6716 &mut self.rec_v
6717 };
6718 for r in 0..8 {
6719 let y = (row * 8 + r) as isize + cdy;
6720 let src = ((y + crate::CPAD as isize) * cst as isize
6721 + crate::CPAD as isize
6722 + x0 as isize * 8
6723 + cdx) as usize;
6724 let d = (row * 8 + r) * self.ccw + x0 * 8;
6725 plane[d..d + wc].copy_from_slice(&rc[src..src + wc]);
6726 }
6727 }
6728 }
6729
6730 /// Full-pel bi band: rows of 16n averaged from two offset windows
6731 /// (prevalidated as above; implicit weights None/(32,32) guaranteed by
6732 /// the pushing arm).
6733 #[allow(clippy::too_many_arguments)]
6734 fn bz_recon_band_fp_bi(
6735 &mut self,
6736 row: usize,
6737 x0: usize,
6738 n: usize,
6739 r0: usize,
6740 r1: usize,
6741 mv0: (i32, i32),
6742 mv1: (i32, i32),
6743 ) {
6744 let (Some(rf0), Some(rf1)) = (self.refs.get(r0), self.refs1.get(r1)) else {
6745 return;
6746 };
6747 let (l0, l1) = (rf0.lstride(), rf1.lstride());
6748 let off = |st: usize, pad: isize, yy: isize, xx: isize| {
6749 (((yy + pad) * st as isize) + pad + xx) as usize
6750 };
6751 let w = n * 16;
6752 {
6753 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6754 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
6755 for r in 0..16 {
6756 let y = (row * 16 + r) as isize;
6757 let s0 = off(
6758 l0,
6759 crate::LPAD as isize,
6760 y + (mv0.1 / 4) as isize,
6761 x0 as isize * 16 + (mv0.0 / 4) as isize,
6762 );
6763 let s1 = off(
6764 l1,
6765 crate::LPAD as isize,
6766 y + (mv1.1 / 4) as isize,
6767 x0 as isize * 16 + (mv1.0 / 4) as isize,
6768 );
6769 let d = (row * 16 + r) * self.cw + x0 * 16;
6770 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
6771 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
6772 // span in kernel-legal widths.
6773 rusty_h264_common::inter::avg_row_into(
6774 &ly0[s0..s0 + w],
6775 &ly1[s1..s1 + w],
6776 w,
6777 &mut self.rec_y[d..d + w],
6778 );
6779 }
6780 }
6781 let (c0, c1) = (rf0.cstride(), rf1.cstride());
6782 let wc = n * 8;
6783 for c in 0..2 {
6784 let rc0 = rf0.chroma_guard(c, rf0.ch);
6785 let rc1 = rf1.chroma_guard(c, rf1.ch);
6786 let plane = if c == 0 {
6787 &mut self.rec_u
6788 } else {
6789 &mut self.rec_v
6790 };
6791 for r in 0..8 {
6792 let y = (row * 8 + r) as isize;
6793 let s0 = off(
6794 c0,
6795 crate::CPAD as isize,
6796 y + (mv0.1 / 8) as isize,
6797 x0 as isize * 8 + (mv0.0 / 8) as isize,
6798 );
6799 let s1 = off(
6800 c1,
6801 crate::CPAD as isize,
6802 y + (mv1.1 / 8) as isize,
6803 x0 as isize * 8 + (mv1.0 / 8) as isize,
6804 );
6805 let d = (row * 8 + r) * self.ccw + x0 * 8;
6806 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
6807 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
6808 // span in kernel-legal widths.
6809 rusty_h264_common::inter::avg_row_into(
6810 &rc0[s0..s0 + wc],
6811 &rc1[s1..s1 + wc],
6812 wc,
6813 &mut plane[d..d + wc],
6814 );
6815 }
6816 }
6817 }
6818
6819 /// Per-MB prevalidation for an Fp span push: mv%8 both components (chroma
6820 /// copies at mv/8) and luma + chroma windows inside the padded planes for
6821 /// every active list. False ⇒ the arm recons immediately instead.
6822 fn bz_fp_valid(
6823 &self,
6824 mbx: usize,
6825 mby: usize,
6826 r0: Option<usize>,
6827 r1: Option<usize>,
6828 mv0: (i32, i32),
6829 mv1: (i32, i32),
6830 ) -> bool {
6831 let chk = |rf: &crate::Ref, mv: (i32, i32)| -> bool {
6832 if mv.0 % 8 != 0 || mv.1 % 8 != 0 {
6833 return false;
6834 }
6835 let lst = rf.lstride();
6836 let lpad = crate::LPAD as isize;
6837 let cx = mbx as isize * 16 + (mv.0 / 4) as isize + lpad;
6838 let cy = mby as isize * 16 + (mv.1 / 4) as isize + lpad;
6839 let lrows = rf.lrows() as isize;
6840 if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
6841 return false;
6842 }
6843 let cst = rf.cstride();
6844 let cpad = crate::CPAD as isize;
6845 let ccx = mbx as isize * 8 + (mv.0 / 8) as isize + cpad;
6846 let ccy = mby as isize * 8 + (mv.1 / 8) as isize + cpad;
6847 let crows = rf.crows() as isize;
6848 ccx >= 0 && ccx + 8 <= cst as isize && ccy >= 0 && ccy + 8 <= crows
6849 };
6850 r0.is_none_or(|i| self.refs.get(i).is_some_and(|r| chk(r, mv0)))
6851 && r1.is_none_or(|i| self.refs1.get(i).is_some_and(|r| chk(r, mv1)))
6852 }
6853
6854 /// Defer one (0,0) P_Skip's grid commit into the pending P span.
6855 #[inline]
6856 fn pz_push(&mut self, mb_x: usize, mb_y: usize, recon: bool) {
6857 match self.pzspan {
6858 Some((row, x0, ref mut n, r)) if row == mb_y && x0 + *n == mb_x && r == recon => {
6859 *n += 1
6860 }
6861 _ => {
6862 self.pz_flush();
6863 self.pzspan = Some((mb_y, mb_x, 1, recon));
6864 }
6865 }
6866 }
6867
6868 /// Range-fill the pending P span's grids (the deferral constants of
6869 /// decode_p_skip's commit: list-0 ref 0, mv (0,0), inter, coded, DC mode,
6870 /// deblock kind SKIP). Same flush points as the B span (span_flush).
6871 #[inline(always)]
6872 fn pz_flush(&mut self) {
6873 if self.pzspan.is_some() {
6874 self.pz_flush_slow();
6875 }
6876 }
6877
6878 fn pz_flush_slow(&mut self) {
6879 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSpanRecon);
6880 let Some((row, x0, n, recon)) = self.pzspan.take() else {
6881 return;
6882 };
6883 edcstat::bump(&edcstat::PZ_SPANS, 1);
6884 edcstat::bump(&edcstat::PZ_SPAN_MBS, n as u64);
6885 let w4 = self.mb_w * 4;
6886 let (b0, len) = (x0 * 4, n * 4);
6887 for dy in 0..4 {
6888 let a = (row * 4 + dy) * w4 + b0;
6889 self.mv_y[a..a + len].fill((0, 0));
6890 self.inter_y[a..a + len].fill(true);
6891 self.ref_idx_y[a..a + len].fill(0);
6892 self.coded_y[a..a + len].fill(true);
6893 self.modes_y[a..a + len].fill(2);
6894 }
6895 self.mb_kind[row * self.mb_w + x0..row * self.mb_w + x0 + n]
6896 .fill(rusty_h264_common::deblock::MB_KIND_SKIP);
6897 if recon {
6898 // The recon deferred with the commit: one band copy from ref[0]
6899 // replaces n queued EdcJob::Skip pushes AND the flush-time
6900 // run-coalescing scan that used to rediscover this very run.
6901 self.recon_p_skip_band(x0, row, n);
6902 }
6903 }
6904
6905 /// Flush BOTH pending grid spans — the one call every grid reader makes.
6906 #[inline]
6907 fn span_flush(&mut self) {
6908 self.bz_flush();
6909 self.pz_flush();
6910 }
6911
6912 /// Hot prefix of decode_b_skip: the FORCED zero-bi run continuation,
6913 /// small enough to inline into the slice loops. Returns true when the MB
6914 /// was fully handled (deferred into the span); false falls to the cold
6915 /// body. Exactly the forced arm's conditions — no behavior change.
6916 #[inline(always)]
6917 fn b_skip_hot(&mut self, mb_x: usize, mb_y: usize) -> bool {
6918 if !self.direct_spatial || self.edc_tx.is_some() || no_bskipfast() {
6919 return false;
6920 }
6921 let mbw = self.mb_w;
6922 let addr = mb_y * mbw + mb_x;
6923 // ONE slice that ENDS at `addr` proves all three neighbour reads. Each
6924 // was a separate check against the whole `bzero` grid even though every
6925 // index is strictly below `addr` (the guards above establish that), and
6926 // the trailing `bzero[addr] = true` a fourth. `get(..=addr)` states the
6927 // bound once, in the form the neighbour indexes are already inside.
6928 // The `get(..=addr)` slice did NOT fold these: it bounds the reads from
6929 // above, but LLVM still has to chain `mb_x > 0 => addr >= 1` and
6930 // `mb_y > 0 => addr >= mbw` through the `&&` sequence, and it will not.
6931 // Fallible reads state each one where it is made; a missing neighbour is
6932 // "not zero", which is the conservative answer this guard wants.
6933 let up = addr.wrapping_sub(mbw);
6934 if !(mb_x > 0
6935 && mb_y > 0
6936 && mb_x + 1 < mbw
6937 && up >= self.slice_first_mb
6938 && self.bzero.get(addr - 1).copied().unwrap_or(false)
6939 && self.bzero.get(up).copied().unwrap_or(false)
6940 && self.bzero.get(up + 1).copied().unwrap_or(false))
6941 {
6942 return false;
6943 }
6944 let wgt = self.iw00();
6945 if !(wgt.is_none() || wgt == Some((32, 32))) {
6946 return false;
6947 }
6948 self.route_skip_mbs += 1;
6949 edcstat::bump(&edcstat::BSKB_FORCED, 1);
6950 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSkipHot);
6951 edcstat::bump(&edcstat::BSKB_FAST, 1);
6952 self.bz_push(mb_x, mb_y, BzKind::ZeroBi);
6953 if let Some(b) = self.bzero.get_mut(addr) {
6954 *b = true;
6955 }
6956 true
6957 }
6958
6959 /// Zero this macroblock's 16 luma nnz entries, walking rows by adding `w4`
6960 /// instead of recomputing `(mb_y * 4 + dy) * w4 + mb_x * 4` four times.
6961 /// Shared by the three sites in decode_b_skip that had it open-coded.
6962 #[inline]
6963 fn clear_mb_nnz(&mut self, mb_x: usize, mb_y: usize, w4: usize) {
6964 let mut a = (mb_y * 4) * w4 + mb_x * 4;
6965 for _ in 0..4 {
6966 self.nnz_y[a..a + 4].fill(0);
6967 a += w4;
6968 }
6969 }
6970
6971 /// The B_Skip SLOW half: full spatial-direct recon plus the zero-residual
6972 /// plane copy. Split out so `pred_y`/`c_pred` (384 B) are built ONLY when a
6973 /// macroblock actually reaches it — the zero-bi / zero-uni / full-pel fast
6974 /// paths in decode_b_skip all return before this, and on LIGHT streams they
6975 /// take 90%+ of the calls.
6976 fn b_skip_slow(
6977 &mut self,
6978 mb_x: usize,
6979 mb_y: usize,
6980 nbrs: Option<([MvNeighbor; 3], [MvNeighbor; 3])>,
6981 w4: usize,
6982 ) -> Result<(), MbError> {
6983 let mut pred_y = [0u8; 256];
6984 let mut c_pred = [[0u8; 64]; 2];
6985 match nbrs {
6986 // Reuses the already-probed neighbours — the grid walk is the
6987 // expensive half of the derivation and paying it twice leaned on
6988 // fall-through-heavy streams (stockholm).
6989 Some((n0, n1)) => {
6990 self.decode_b_direct_n(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred, n0, n1)
6991 }
6992 None => self.decode_b_direct(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred),
6993 }
6994 if let Some(regions) = self.edc_regions.take() {
6995 // nnz clears are PARSE state; the pixel copy is the worker's.
6996 self.clear_mb_nnz(mb_x, mb_y, w4);
6997 self.edc_giveback();
6998 self.edc_send_job(EdcJob::BSkip {
6999 mbx: mb_x,
7000 mby: mb_y,
7001 regions,
7002 });
7003 return Ok(());
7004 }
7005 // Zero residual: the prediction IS the reconstruction — copy it row-wise,
7006 // stepping the destination by the stride rather than multiplying per row.
7007 let mut d = (mb_y * 16) * self.cw + mb_x * 16;
7008 for dy in 0..16 {
7009 self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
7010 d += self.cw;
7011 }
7012 let (ccw, cx0) = (self.ccw, mb_x * 8);
7013 for c in 0..2 {
7014 let plane = if c == 0 {
7015 &mut self.rec_u
7016 } else {
7017 &mut self.rec_v
7018 };
7019 let mut d = (mb_y * 8) * ccw + cx0;
7020 for dy in 0..8 {
7021 plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
7022 d += ccw;
7023 }
7024 }
7025 // nnz stays 0 (no residual) — clear the grids for neighbour context.
7026 self.clear_mb_nnz(mb_x, mb_y, w4);
7027 Ok(())
7028 }
7029
7030 fn decode_b_skip(&mut self, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7031 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSkipCold);
7032 self.route_skip_mbs += 1;
7033 self.wait_refs_for_mb(mb_y);
7034 // Each list length read ONCE: the emptiness test and every later
7035 // `len() - 1` clamp (six reads in all) come from these two.
7036 let (nref0, nref1) = (self.refs.len(), self.refs1.len());
7037 if nref0 == 0 || nref1 == 0 {
7038 return Err(MbError::Unsupported("B without references"));
7039 }
7040 let (lref0, lref1) = (nref0 - 1, nref1 - 1);
7041 let mbw = self.mb_w;
7042 let addr = mb_y * mbw + mb_x;
7043 let w4 = mbw * 4;
7044 // ZERO-BI FAST PATH: when spatial direct derives (0,0)/(0,0) bi motion,
7045 // colZeroFlag is IRRELEVANT (it only zeroes MVs that are already zero),
7046 // so the colocated probing, region split, b_mc staging and the pred
7047 // round-trip all collapse into one fused row-average from the two
7048 // padded refs straight into the recon planes. b_mc's bi blend applies
7049 // only IMPLICIT weights, so `None` or (32,32) (both exactly
7050 // (a+b+1)>>1) is the whole identity condition. FourPeople-class LIGHT
7051 // streams put 90%+ of their B_Skips here (BSK_ZBI counter).
7052 // pred_y / c_pred now live in `b_skip_slow` — see there.
7053 if self.direct_spatial && self.edc_tx.is_none() && !no_bskipfast() {
7054 // (The FORCED zero-bi run continuation lives in b_skip_hot, inlined
7055 // at the loop call sites — this cold body only sees non-forced MBs.)
7056 // A pending span can only occupy the LEFT gather position, and the
7057 // left member's committed grid values are fully determined by the
7058 // span KIND — synthesize them instead of flushing, for EVERY kind
7059 // (the same mapping bz_flush writes).
7060 let patch = match self.bzspan {
7061 Some((r, x0, n, k)) if r == mb_y && x0 + n == mb_x => Some(k),
7062 _ => None,
7063 };
7064 if patch.is_none() {
7065 self.bz_flush();
7066 }
7067 let (mut n0, mut n1) = self.b_direct_nbrs(mb_x, mb_y);
7068 if let Some(k) = patch {
7069 let (r0, r1, m0, m1): (i32, i32, (i32, i32), (i32, i32)) = match k {
7070 BzKind::ZeroBi => (0, 0, (0, 0), (0, 0)),
7071 BzKind::ZeroUni(0, ri) => (ri as i32, -1, (0, 0), (0, 0)),
7072 BzKind::ZeroUni(_, ri) => (-1, ri as i32, (0, 0), (0, 0)),
7073 BzKind::Fp { r0, r1, m0, m1 } => {
7074 let mm0 = if r0 >= 0 {
7075 (m0.0 as i32, m0.1 as i32)
7076 } else {
7077 (0, 0)
7078 };
7079 let mm1 = if r1 >= 0 {
7080 (m1.0 as i32, m1.1 as i32)
7081 } else {
7082 (0, 0)
7083 };
7084 (r0 as i32, r1 as i32, mm0, mm1)
7085 }
7086 };
7087 n0[0] = MvNeighbor {
7088 available: true,
7089 mv: m0,
7090 ref_idx: r0,
7091 };
7092 n1[0] = MvNeighbor {
7093 available: true,
7094 mv: m1,
7095 ref_idx: r1,
7096 };
7097 }
7098 let (refi0, refi1, mv0, mv1, _dz) = Self::b_direct_refs_mvs(&n0, &n1);
7099 let (a0, a1) = (refi0 >= 0, refi1 >= 0);
7100 let fast = if a0 && a1 && mv0 == (0, 0) && mv1 == (0, 0) {
7101 // Same malformed-stream ref clamp as b_mc.
7102 let r0 = (refi0 as usize).min(lref0);
7103 let r1 = (refi1 as usize).min(lref1);
7104 // (0,0) dominates here and is already cached slice-wide by iw00.
7105 let wgt = if r0 == 0 && r1 == 0 {
7106 self.iw00()
7107 } else {
7108 self.implicit_weights(r0 as i32, r1 as i32)
7109 };
7110 if wgt.is_none() || wgt == Some((32, 32)) {
7111 if refi0 == 0 && refi1 == 0 {
7112 if let Some(b) = self.bzero.get_mut(addr) {
7113 *b = true;
7114 }
7115 // Same constants as the forced arm: recon AND commit
7116 // both defer into the span.
7117 self.bz_push(mb_x, mb_y, BzKind::ZeroBi);
7118 edcstat::bump(&edcstat::BSKB_FAST, 1);
7119 return Ok(());
7120 }
7121 self.recon_b_skip_zero_bi(mb_x, mb_y, r0, r1);
7122 true
7123 } else {
7124 false
7125 }
7126 } else if a0 != a1 && (if a0 { mv0 } else { mv1 }) == (0, 0) {
7127 // ZERO-UNI: one active list at (0,0) — b_mc's uni arms are plain
7128 // unweighted copies. Defer as a memcpy-band span.
7129 let (list, ri) = if a0 {
7130 (0u8, (refi0 as usize).min(lref0) as u8)
7131 } else {
7132 (1u8, (refi1 as usize).min(lref1) as u8)
7133 };
7134 self.bz_push(mb_x, mb_y, BzKind::ZeroUni(list, ri));
7135 edcstat::bump(&edcstat::BSKB_FAST, 1);
7136 return Ok(());
7137 } else if (!a0 || (mv0.0 % 4 == 0 && mv0.1 % 4 == 0))
7138 && (!a1 || (mv1.0 % 4 == 0 && mv1.1 % 4 == 0))
7139 && self.direct_8x8_inference
7140 {
7141 // FULL-PEL arm (the pan case — shields): nonzero MVs make
7142 // colZeroFlag matter, so probe the four 8x8 corners; a UNIFORM
7143 // czg keeps the MB one region and the MC is an offset read.
7144 // Non-uniform czg or an out-of-pad window falls through.
7145 let mut cz_ok = true;
7146 let mut czv = false;
7147 // Same cz-relevance gate as decode_b_direct_n: only a ref-0
7148 // list with a nonzero MV can be changed by colZeroFlag (fp-arm
7149 // MVs are nonzero by construction, so refs alone decide).
7150 let cz_need = refi0 == 0 || refi1 == 0;
7151 // LOOP-INVARIANT: this gated a `break` INSIDE the probe loop, so
7152 // a fact known before the loop was re-tested on every probe.
7153 if cz_need {
7154 // Absolute block base, hoisted out of the four probes.
7155 let (bx4, by4) = (mb_x * 4, mb_y * 4);
7156 for (k, &(ox, oy)) in [(0usize, 0usize), (2, 0), (0, 2), (2, 2)]
7157 .iter()
7158 .enumerate()
7159 {
7160 // col_block takes MB-LOCAL block coords (the whole MB is
7161 // the region here); col_zero takes absolute — same
7162 // convention as decode_b_direct_n's probe loop.
7163 let (colx, coly) = self.col_block(ox, oy);
7164 let cz = self.col_zero(bx4 + colx, by4 + coly);
7165 if k == 0 {
7166 czv = cz;
7167 } else if cz != czv {
7168 cz_ok = false;
7169 break;
7170 }
7171 }
7172 }
7173 if cz_ok {
7174 let m0 = if refi0 == 0 && czv { (0, 0) } else { mv0 };
7175 let m1 = if refi1 == 0 && czv { (0, 0) } else { mv1 };
7176 let r0c = (refi0 as usize).min(lref0);
7177 let r1c = (refi1 as usize).min(lref1);
7178 let (or0, or1) = (a0.then_some(r0c), a1.then_some(r1c));
7179 let wgt_ok = !(a0 && a1) || {
7180 let wgt = self.implicit_weights(r0c as i32, r1c as i32);
7181 wgt.is_none() || wgt == Some((32, 32))
7182 };
7183 // SPAN path: prevalidated windows + %8 chroma ⇒ defer as
7184 // an offset copy/avg band; otherwise the immediate per-MB
7185 // recon below (which handles interpolating chroma).
7186 if wgt_ok && self.bz_fp_valid(mb_x, mb_y, or0, or1, m0, m1) {
7187 let kind = BzKind::Fp {
7188 r0: if a0 { r0c as i8 } else { -1 },
7189 r1: if a1 { r1c as i8 } else { -1 },
7190 m0: (m0.0 as i16, m0.1 as i16),
7191 m1: (m1.0 as i16, m1.1 as i16),
7192 };
7193 self.bz_push(mb_x, mb_y, kind);
7194 edcstat::bump(&edcstat::BSKB_FP, 1);
7195 return Ok(());
7196 }
7197 let ok = wgt_ok && self.recon_b_skip_fp(mb_x, mb_y, or0, or1, m0, m1);
7198 if ok {
7199 edcstat::bump(&edcstat::BSKB_FP, 1);
7200 self.b_set_motion(mb_x, mb_y, 0, 0, 16, 16, refi0, m0, refi1, m1);
7201 self.clear_mb_nnz(mb_x, mb_y, w4);
7202 return Ok(());
7203 }
7204 }
7205 false
7206 } else {
7207 false
7208 };
7209 if fast {
7210 edcstat::bump(&edcstat::BSKB_FAST, 1);
7211 self.b_set_motion(mb_x, mb_y, 0, 0, 16, 16, refi0, (0, 0), refi1, (0, 0));
7212 self.clear_mb_nnz(mb_x, mb_y, w4);
7213 return Ok(());
7214 }
7215 // Fall-through hands the ALREADY-probed neighbours to the slow
7216 // half so the grid walk is not paid twice.
7217 return self.b_skip_slow(mb_x, mb_y, Some((n0, n1)), w4);
7218 } else {
7219 if self.edc_tx.is_some() {
7220 self.edc_regions = Some(Vec::with_capacity(4));
7221 }
7222 return self.b_skip_slow(mb_x, mb_y, None, w4);
7223 }
7224 }
7225
7226 /// Reconstructs a B macroblock (spec Table 7-14): direct, L0/L1/Bi partitions,
7227 /// `B_8x8`, or intra.
7228 fn decode_b_mb(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7229 // Non-skip B MBs gather neighbor motion/modes — flush any deferred
7230 // spans before those grids are read.
7231 self.span_flush();
7232 self.wait_refs_for_mb(mb_y);
7233 let mb_type = r.read_ue()?;
7234 if mb_type >= 23 {
7235 return self.decode_intra_mb(r, mb_x, mb_y, mb_type - 23);
7236 }
7237 if self.refs.is_empty() || self.refs1.is_empty() {
7238 return Err(MbError::Unsupported("B without references"));
7239 }
7240 let mut pred_y = [0u8; 256];
7241 let mut c_pred = [[0u8; 64]; 2];
7242
7243 if mb_type == 0 {
7244 // B_Direct_16x16 — 8×8 transform allowed only with direct_8x8_inference.
7245 // FORCED derivation via the zero-bi bitmap (CAVLC parity with the
7246 // CABAC direct arm): gather + rid/median skipped, chains extended.
7247 let addr_f = mb_y * self.mb_w + mb_x;
7248 let forced = self.direct_spatial
7249 && self.edc_tx.is_none()
7250 && mb_x > 0
7251 && mb_y > 0
7252 && mb_x + 1 < self.mb_w
7253 && addr_f - self.mb_w >= self.slice_first_mb
7254 && self.bzero.get(addr_f - 1).copied().unwrap_or(false)
7255 && self
7256 .bzero
7257 .get(addr_f.wrapping_sub(self.mb_w))
7258 .copied()
7259 .unwrap_or(false)
7260 && self
7261 .bzero
7262 .get(addr_f.wrapping_sub(self.mb_w) + 1)
7263 .copied()
7264 .unwrap_or(false);
7265 if forced {
7266 self.b_direct_region(
7267 mb_x,
7268 mb_y,
7269 0,
7270 0,
7271 16,
7272 16,
7273 &mut pred_y,
7274 &mut c_pred,
7275 (0, 0, (0, 0), (0, 0), false),
7276 );
7277 if let Some(b) = self.bzero.get_mut(addr_f) {
7278 *b = true;
7279 }
7280 } else {
7281 self.decode_b_direct(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred);
7282 }
7283 return self.inter_finish(
7284 r,
7285 mb_x,
7286 mb_y,
7287 &pred_y,
7288 &c_pred,
7289 self.direct_8x8_inference,
7290 false,
7291 );
7292 }
7293 if mb_type == 22 {
7294 return self.decode_b_8x8(r, mb_x, mb_y);
7295 }
7296
7297 // 16x16 / 16x8 / 8x16 partitions with per-partition L0/L1/Bi.
7298 let (layout, mvmode, preds) = b_inter_layout(mb_type);
7299 // mb_pred order: ref_idx_l0 (all L0 parts), ref_idx_l1, mvd_l0, mvd_l1.
7300 let mut refi = [[-1i32; 2]; 2]; // [part][list]
7301 for (p, &(_, _, _, _)) in layout.iter().enumerate() {
7302 if preds[p & 1].uses(0) {
7303 refi[p][0] = self.read_b_ref(r, 0)?;
7304 }
7305 }
7306 for (p, _) in layout.iter().enumerate() {
7307 if preds[p & 1].uses(1) {
7308 refi[p][1] = self.read_b_ref(r, 1)?;
7309 }
7310 }
7311 let mut mvd = [[(0i32, 0i32); 2]; 2];
7312 for (p, _) in layout.iter().enumerate() {
7313 if preds[p & 1].uses(0) {
7314 mvd[p][0] = (read_mvd(r)?, read_mvd(r)?);
7315 }
7316 }
7317 for (p, _) in layout.iter().enumerate() {
7318 if preds[p & 1].uses(1) {
7319 mvd[p][1] = (read_mvd(r)?, read_mvd(r)?);
7320 }
7321 }
7322 // Per partition: predict + commit each list's MV, then motion-compensate.
7323 for (p, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
7324 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
7325 let pwb = (rw / 4) as isize;
7326 let mut mv = [(0i32, 0i32); 2];
7327 for list in 0..2 {
7328 if refi[p][list] >= 0 {
7329 let n = self.mv_neighbors_list(pbx, pby, pwb, list);
7330 let pmv = predict_partition_mv(mvmode, p, n[0], n[1], n[2], refi[p][list]);
7331 mv[list] = (pmv.0 + mvd[p][list].0, pmv.1 + mvd[p][list].1);
7332 }
7333 }
7334 self.b_set_motion(
7335 mb_x,
7336 mb_y,
7337 rx,
7338 ry,
7339 rw,
7340 rh,
7341 refi[p & 3][0],
7342 mv[0],
7343 refi[p & 3][1],
7344 mv[1],
7345 );
7346 // Spec-correct bi-prediction (average of L0 and L1), matching the CABAC
7347 // path. This used to replicate an openh264 bug for a Bi 16x8/8x16
7348 // partition -- openh264 mis-handles the destination buffer there, so
7349 // partition 0 came out List-1-only and partition 1 List-0-only. That was
7350 // deliberate when openh264's h264dec WAS the conformance oracle, but the
7351 // gate is ffmpeg now and the CABAC path already went spec-correct; the
7352 // CAVLC path was simply left behind. Measured: mb_type 12..21 (every B
7353 // 16x8/8x16 with at least one Bi partition) were 100% wrong vs ffmpeg,
7354 // while 1..11 (no Bi partition) were only collaterally damaged.
7355 self.b_mc_or_record(
7356 mb_x,
7357 mb_y,
7358 rx,
7359 ry,
7360 rw,
7361 rh,
7362 refi[p & 3][0],
7363 mv[0],
7364 refi[p & 3][1],
7365 mv[1],
7366 &mut pred_y,
7367 &mut c_pred,
7368 );
7369 }
7370 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, true, false)
7371 }
7372
7373 /// Reconstructs a `B_8x8` macroblock: four 8×8 sub-macroblock partitions, each
7374 /// direct or L0/L1/Bi with its own sub-partitioning (spec Table 7-18).
7375 fn decode_b_8x8(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7376 let mut sub = [0u32; 4];
7377 for s in sub.iter_mut() {
7378 let v = r.read_ue()?;
7379 if v > 12 {
7380 return Err(MbError::Unsupported("invalid B sub_mb_type"));
7381 }
7382 *s = v;
7383 }
7384 let mut pred_y = [0u8; 256];
7385 let mut c_pred = [[0u8; 64]; 2];
7386 // ref_idx for all 8×8 partitions (L0 batch, then L1 batch), for the
7387 // non-direct sub-partitions.
7388 let mut refi = [[-1i32; 2]; 4];
7389 for (p, &st) in sub.iter().enumerate() {
7390 if st != 0 && b_sub_uses(st, 0) {
7391 refi[p][0] = self.read_b_ref(r, 0)?;
7392 }
7393 }
7394 for (p, &st) in sub.iter().enumerate() {
7395 if st != 0 && b_sub_uses(st, 1) {
7396 refi[p][1] = self.read_b_ref(r, 1)?;
7397 }
7398 }
7399 // mvd: all mvd_l0 (partition-major, sub-partition order), then all mvd_l1.
7400 //
7401 // FIXED ARRAYS, NOT `Vec::new()` + push. These are per-MACROBLOCK on every
7402 // B_8x8, and a growing Vec allocated (and reallocated) twice per MB — on a
7403 // B-heavy stream that is thousands of allocations per frame for data whose
7404 // maximum size is a compile-time constant: 4 partitions x at most 4
7405 // sub-partitions = 16 entries. Indexing a fixed array cannot exceed that, and
7406 // an out-of-range index would panic rather than misbehave, so the bound is
7407 // enforced either way and this crate stays forbid(unsafe).
7408 const MAX_MVD: usize = 16;
7409 let mut mvd0 = [(0i32, 0i32); MAX_MVD];
7410 let mut mvd1 = [(0i32, 0i32); MAX_MVD];
7411 let (mut n0, mut n1) = (0usize, 0usize);
7412 for &st in &sub {
7413 if st != 0 && b_sub_uses(st, 0) {
7414 for _ in b_sub_parts(st) {
7415 mvd0[n0] = (read_mvd(r)?, read_mvd(r)?);
7416 n0 += 1;
7417 }
7418 }
7419 }
7420 for &st in &sub {
7421 if st != 0 && b_sub_uses(st, 1) {
7422 for _ in b_sub_parts(st) {
7423 mvd1[n1] = (read_mvd(r)?, read_mvd(r)?);
7424 n1 += 1;
7425 }
7426 }
7427 }
7428 // Decode each 8×8 partition.
7429 // Spatial-direct A/B/C are MB-level — walk once if any sub is direct.
7430 // `dmemo=0` rewalks every direct 8×8 (A/B oracle).
7431 let hoisted = if self.direct_spatial && direct_memo_on() && sub.iter().any(|&t| t == 0) {
7432 Some(self.b_direct_nbrs(mb_x, mb_y))
7433 } else {
7434 None
7435 };
7436 let (mut i0, mut i1) = (0usize, 0usize);
7437 for (p, &st) in sub.iter().enumerate() {
7438 let (b8x, b8y) = ((p % 2) * 8, (p / 2) * 8);
7439 if st == 0 {
7440 match hoisted {
7441 Some((n0, n1)) => self.decode_b_direct_n(
7442 mb_x,
7443 mb_y,
7444 b8x,
7445 b8y,
7446 8,
7447 8,
7448 &mut pred_y,
7449 &mut c_pred,
7450 n0,
7451 n1,
7452 ),
7453 None => {
7454 self.decode_b_direct(mb_x, mb_y, b8x, b8y, 8, 8, &mut pred_y, &mut c_pred)
7455 }
7456 }
7457 continue;
7458 }
7459 for &(sx, sy, sw, sh) in b_sub_parts(st) {
7460 let (px, py) = (b8x + sx, b8y + sy);
7461 let (pbx, pby) = ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
7462 let pwb = (sw / 4) as isize;
7463 let mut mv = [(0i32, 0i32); 2];
7464 if b_sub_uses(st, 0) {
7465 let n = self.mv_neighbors_list(pbx, pby, pwb, 0);
7466 let pmv = predict_mv(n[0], n[1], n[2], refi[p & 3][0]);
7467 let d = mvd0[i0 & 15];
7468 i0 += 1;
7469 mv[0] = (pmv.0 + d.0, pmv.1 + d.1);
7470 }
7471 if b_sub_uses(st, 1) {
7472 let n = self.mv_neighbors_list(pbx, pby, pwb, 1);
7473 let pmv = predict_mv(n[0], n[1], n[2], refi[p & 3][1]);
7474 let d = mvd1[i1 & 15];
7475 i1 += 1;
7476 mv[1] = (pmv.0 + d.0, pmv.1 + d.1);
7477 }
7478 self.b_set_motion(
7479 mb_x,
7480 mb_y,
7481 px,
7482 py,
7483 sw,
7484 sh,
7485 refi[p & 3][0],
7486 mv[0],
7487 refi[p & 3][1],
7488 mv[1],
7489 );
7490 self.b_mc_or_record(
7491 mb_x,
7492 mb_y,
7493 px,
7494 py,
7495 sw,
7496 sh,
7497 refi[p & 3][0],
7498 mv[0],
7499 refi[p & 3][1],
7500 mv[1],
7501 &mut pred_y,
7502 &mut c_pred,
7503 );
7504 }
7505 }
7506 // noSubMbPartSizeLessThan8x8: each sub-partition must be ≥ 8×8 (direct
7507 // counts only with the 8×8 inference flag).
7508 let allow_8x8 = sub.iter().all(|&st| {
7509 if st == 0 {
7510 self.direct_8x8_inference
7511 } else {
7512 st <= 3
7513 }
7514 });
7515 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, allow_8x8, false)
7516 }
7517
7518 /// Reconstructs a `P_8x8` macroblock: four 8×8 sub-macroblock partitions,
7519 /// each independently split (8×8 / 8×4 / 4×8 / 4×4) with its own motion
7520 /// vector(s). `ref0` is `P_8x8ref0` (every `ref_idx` forced to 0, not coded).
7521 fn decode_p8x8(
7522 &mut self,
7523 r: &mut BitReader,
7524 mb_x: usize,
7525 mb_y: usize,
7526 ref0: bool,
7527 ) -> Result<(), MbError> {
7528 if self.refs.is_empty() {
7529 return Err(MbError::Unsupported("inter without reference"));
7530 }
7531 let w4 = self.mb_w * 4;
7532 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
7533 let num_refs = self.refs.len();
7534
7535 // mb_pred order (spec §7.3.5.2): all sub_mb_type, then all ref_idx_l0,
7536 // then all mvd_l0 (partition-major, sub-partition order within each).
7537 let mut sub_types = [0u32; 4];
7538 for st in sub_types.iter_mut() {
7539 let v = r.read_ue()?;
7540 if v > 3 {
7541 return Err(MbError::Unsupported("B-slice / invalid sub_mb_type"));
7542 }
7543 *st = v;
7544 }
7545 let mut ref_idxs = [0i32; 4];
7546 if self.num_ref_active > 1 && !ref0 {
7547 for ri in ref_idxs.iter_mut() {
7548 *ri = read_ref_idx(r, self.num_ref_active)?;
7549 if *ri as usize >= num_refs {
7550 return Err(MbError::Truncated); // references a non-existent picture
7551 }
7552 }
7553 }
7554
7555 // Per sub-partition (in decoding order): median MV prediction from the
7556 // committed neighbor grid, mvd, commit, then motion-compensate. Committing
7557 // before the next prediction is what lets sub-partitions chain correctly.
7558 // D14b: P_8x8 defers too. Syncing before it instead cost 9,772 pipeline
7559 // drains per stream (45 per 1000 MBs, vs CABAC's 3.3) because P_8x8 is
7560 // common in CAVLC P slices — and the seam measured 1.65-1.97x SLOWER
7561 // for it. Deferring is byte-identical for sub-partitions: the worker
7562 // motion-compensates per 4x4 from the committed grids, which is exactly
7563 // how the CABAC path already handles P_8x8, and a 6-tap filter applied
7564 // per-4x4 with the same MV gives the same pixels as one 8x8 call.
7565 let defer = self.edc_tx.is_some() || self.edc_active;
7566
7567 // ── PHASE 1: PARSE + COMMIT. Must always run to completion. ──────────
7568 //
7569 // These two are interleaved BY NECESSITY: each sub-partition's MV
7570 // prediction reads the grids the previous one committed, so they cannot
7571 // be separated from each other. But they consume BITSTREAM, so nothing
7572 // here may ever be skipped conditionally.
7573 //
7574 // This phase split is a STRUCTURAL guard, not a tidy-up. When MC lived
7575 // inside this loop, deferring it was written as a `break` — which also
7576 // skipped the `read_se` mvd reads, desynced the bitstream, and
7577 // mis-parsed as a B-slice sub_mb_type on a Baseline stream. It happened
7578 // to crash; a desync that stayed IN RANGE would have produced plausible
7579 // garbage instead. With MC in its own pass below, skipping pixel work
7580 // cannot reach the bitstream at all — the mistake is unavailable.
7581 let mut regions: [(usize, usize, usize, usize, i32, (i32, i32)); 16] =
7582 [(0, 0, 0, 0, 0, (0, 0)); 16];
7583 let mut nreg = 0usize;
7584 for part in 0..4usize {
7585 let refi = ref_idxs[part];
7586 let (b8x, b8y) = ((part % 2) * 8, (part / 2) * 8);
7587 for &(srx, sry, srw, srh) in sub_mb_partitions(sub_types[part]) {
7588 let (px, py) = (b8x + srx, b8y + sry);
7589 let (pbx, pby) = ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
7590 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (srw / 4) as isize);
7591 let pmv = predict_mv(a, b, c, refi);
7592 let mvd_x = read_mvd(r)?;
7593 let mvd_y = read_mvd(r)?;
7594 let mv = (pmv.0 + mvd_x, pmv.1 + mvd_y);
7595 for by in py / 4..py / 4 + srh / 4 {
7596 for bx in px / 4..px / 4 + srw / 4 {
7597 let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
7598 if let (Some(m), Some(it), Some(rf), Some(cd)) = (
7599 self.mv_y.get_mut(idx),
7600 self.inter_y.get_mut(idx),
7601 self.ref_idx_y.get_mut(idx),
7602 self.coded_y.get_mut(idx),
7603 ) {
7604 (*m, *it, *rf, *cd) = (mv, true, refi, true);
7605 }
7606 }
7607 }
7608 regions[nreg & 15] = (px, py, srw, srh, refi, mv);
7609 nreg += 1;
7610 }
7611 }
7612
7613 // ── PHASE 2: PIXEL WORK ONLY. Reads no bitstream; safe to skip. ──────
7614 let mut pred_y = [0u8; 256];
7615 let mut c_pred = [[0u8; 64]; 2];
7616 if !defer {
7617 for &(px, py, srw, srh, refi, mv) in ®ions[..nreg] {
7618 let Some(reference) = self.refs.get(refi as usize) else {
7619 continue;
7620 };
7621 let mut tmp = [0u8; 256];
7622 mc_luma_padded(
7623 &*reference.luma_guard(reference.ch),
7624 reference.lstride(),
7625 crate::LPAD,
7626 self.cw,
7627 ch,
7628 mb_x * 16 + px,
7629 mb_y * 16 + py,
7630 srw,
7631 srh,
7632 mv.0,
7633 mv.1,
7634 &mut tmp,
7635 );
7636 restride(&mut pred_y, 16, px, py, &tmp, srw, srh);
7637 let (crx, cry, crw, crh) = (px / 2, py / 2, srw / 2, srh / 2);
7638 for cc in 0..2 {
7639 let rc = if cc == 0 {
7640 &*reference.chroma_guard(0, reference.ch)
7641 } else {
7642 &*reference.chroma_guard(1, reference.ch)
7643 };
7644 let mut tc = [0u8; 64];
7645 mc_chroma_padded(
7646 rc,
7647 reference.cstride(),
7648 crate::CPAD,
7649 self.ccw,
7650 cch,
7651 mb_x * 8 + crx,
7652 mb_y * 8 + cry,
7653 crw,
7654 crh,
7655 mv.0,
7656 mv.1,
7657 &mut tc,
7658 );
7659 restride(&mut c_pred[cc], 8, crx, cry, &tc, crw, crh);
7660 }
7661 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi as usize, px, py, srw, srh);
7662 }
7663 }
7664
7665 // P_8x8 allows the 8×8 transform only when every sub-partition is 8×8.
7666 let allow_8x8 = sub_types.iter().all(|&t| t == 0);
7667 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, allow_8x8, defer)
7668 }
7669
7670 /// Reconstructs a `P_Skip` macroblock: motion-compensate from the reference
7671 /// at the skip MV, with no residual.
7672 /// Records a B MC region (threaded mode) or executes it inline — the SAME
7673 /// arguments as `b_mc`; the recorded arm resolves the implicit weights at
7674 /// parse time (identical function, parse-side data).
7675 #[allow(clippy::too_many_arguments)]
7676 fn b_mc_or_record(
7677 &mut self,
7678 mb_x: usize,
7679 mb_y: usize,
7680 px: usize,
7681 py: usize,
7682 rw: usize,
7683 rh: usize,
7684 refi0: i32,
7685 mv0: (i32, i32),
7686 refi1: i32,
7687 mv1: (i32, i32),
7688 pred_y: &mut [u8; 256],
7689 c_pred: &mut [[u8; 64]; 2],
7690 ) {
7691 if self.edc_regions.is_some() {
7692 // Mirror `b_mc`'s malformed-stream armor EXACTLY before touching the
7693 // ref lists: the inline path clamps the indices and bails on empty
7694 // lists BEFORE computing weights; calling `implicit_weights` with the
7695 // raw indices re-introduced the panic the armor exists to prevent
7696 // (found by the fuzzer, via the unwind-guard that turned the
7697 // resulting worker deadlock back into a diagnosable failure).
7698 let cr0 = if refi0 >= 0 {
7699 (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32
7700 } else {
7701 -1
7702 };
7703 let cr1 = if refi1 >= 0 {
7704 (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32
7705 } else {
7706 -1
7707 };
7708 let w = if (cr0 >= 0 && self.refs.is_empty()) || (cr1 >= 0 && self.refs1.is_empty()) {
7709 None // the worker's port returns before reading the weights
7710 } else {
7711 self.implicit_weights(cr0, cr1)
7712 };
7713 // Cannot bind above: `implicit_weights` needs `&mut self` between the
7714 // guard and here. The guard already proved this is `Some`, so the
7715 // `else` is unreachable -- but an unreachable no-op is the correct
7716 // shape for armor code, not an `unwrap` that can abort a decode.
7717 if let Some(regions) = self.edc_regions.as_mut() {
7718 regions.push(BRegion {
7719 px,
7720 py,
7721 rw,
7722 rh,
7723 refi0,
7724 refi1,
7725 mv0,
7726 mv1,
7727 w,
7728 });
7729 }
7730 return;
7731 }
7732 self.b_mc(
7733 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, pred_y, c_pred,
7734 );
7735 }
7736
7737 /// Builds the worker's owned pixel context from `self` (planes MOVED out,
7738 /// shared read-only state cloned, filter inputs snapshotted).
7739 fn edc_take_ctx(&mut self) -> PixelCtx {
7740 PixelCtx {
7741 rec_y: core::mem::take(&mut self.rec_y),
7742 rec_u: core::mem::take(&mut self.rec_u),
7743 rec_v: core::mem::take(&mut self.rec_v),
7744 bak_y: core::mem::take(&mut self.bak_y),
7745 bak_u: core::mem::take(&mut self.bak_u),
7746 bak_v: core::mem::take(&mut self.bak_v),
7747 refs: self.refs.clone(),
7748 refs1: self.refs1.clone(),
7749 weights: self.weights.clone(),
7750 weights_l0id: self.weights_l0id,
7751 scaling: self.scaling,
7752 scaling8: self.scaling8,
7753 cw: self.cw,
7754 ccw: self.ccw,
7755 mb_w: self.mb_w,
7756 mb_h: self.mb_h,
7757 chroma_qp_offset: self.chroma_qp_offset,
7758 flt_rows: self.flt_rows,
7759 db_ena: self.db_ena,
7760 db_oa: self.db_oa,
7761 db_ob: self.db_ob,
7762 cur_qp: self.cur_qp,
7763 qp_grid: self.mb_qp.clone(),
7764 t8_grid: self.mb_t8x8.clone(),
7765 bs_store: self.bs_frame.clone(),
7766 progress: self.progress.clone(),
7767 }
7768 }
7769
7770 /// Restores the planes (and the filter watermark) from a returned context.
7771 fn edc_restore_ctx(&mut self, ctx: PixelCtx) {
7772 self.rec_y = ctx.rec_y;
7773 self.rec_u = ctx.rec_u;
7774 self.rec_v = ctx.rec_v;
7775 self.bak_y = ctx.bak_y;
7776 self.bak_u = ctx.bak_u;
7777 self.bak_v = ctx.bak_v;
7778 self.flt_rows = ctx.flt_rows;
7779 }
7780
7781 /// Intra macroblocks read neighbour PIXELS: fetch the context from the
7782 /// worker (which drains all prior jobs first — the channel is FIFO) and
7783 /// install the planes so the inline intra path runs unchanged. The context
7784 /// is given back lazily at the next job/row/slice-end (`edc_giveback`), so
7785 /// consecutive intra macroblocks pay ONE round-trip.
7786 /// Queue a pixel job for the worker (D10). Batched per row; see `EdcMsg::Batch`.
7787 #[inline]
7788 fn edc_send_job(&mut self, job: EdcJob) {
7789 edcstat::bump(&edcstat::JOBS, 1);
7790 if !batch_on() {
7791 self.edc_tx
7792 .as_ref()
7793 .unwrap()
7794 .send(EdcMsg::Job(job))
7795 .expect("worker alive");
7796 return;
7797 }
7798 self.edc_batch.push(job);
7799 }
7800
7801 /// Ship the accumulated row batch. MUST be called before anything that
7802 /// depends on those jobs having been applied: the row's `Row` filter
7803 /// message, a `NeedCtx` handover, and slice end.
7804 fn edc_flush_batch(&mut self) {
7805 if self.edc_batch.is_empty() {
7806 return;
7807 }
7808 // Replace with a PRE-RESERVED buffer rather than the empty Vec
7809 // `mem::take` would leave: otherwise each row reallocs and regrows from
7810 // zero, trading 208k channel sends for ~7 reallocs x 3k rows.
7811 let cap = self.edc_batch.capacity().max(self.mb_w);
7812 let jobs = core::mem::replace(&mut self.edc_batch, Vec::with_capacity(cap));
7813 edcstat::bump(&edcstat::BATCHES, 1);
7814 self.edc_tx
7815 .as_ref()
7816 .unwrap()
7817 .send(EdcMsg::Batch(jobs))
7818 .expect("worker alive");
7819 }
7820
7821 fn edc_intra_sync(&mut self) {
7822 if self.edc_tx.is_none() {
7823 self.edc_flush();
7824 return;
7825 }
7826 if self.edc_parked.is_some() {
7827 return; // already holding
7828 }
7829 // ORDER: drain the batch before taking the planes, or those jobs would
7830 // be applied to a context the parse thread is concurrently holding.
7831 self.edc_flush_batch();
7832 edcstat::bump(&edcstat::NEEDCTX, 1);
7833 self.edc_tx
7834 .as_ref()
7835 .unwrap()
7836 .send(EdcMsg::NeedCtx)
7837 .expect("worker alive");
7838 let mut ctx = self
7839 .edc_ctx_rx
7840 .as_ref()
7841 .unwrap()
7842 .recv()
7843 .expect("worker ctx");
7844 self.rec_y = core::mem::take(&mut ctx.rec_y);
7845 self.rec_u = core::mem::take(&mut ctx.rec_u);
7846 self.rec_v = core::mem::take(&mut ctx.rec_v);
7847 self.bak_y = core::mem::take(&mut ctx.bak_y);
7848 self.bak_u = core::mem::take(&mut ctx.bak_u);
7849 self.bak_v = core::mem::take(&mut ctx.bak_v);
7850 self.flt_rows = ctx.flt_rows;
7851 self.edc_parked = Some(ctx);
7852 }
7853
7854 /// Returns a held context to the worker (inverse of `edc_intra_sync`).
7855 /// NOTE (D10): this is called per-macroblock on the job paths, so it must
7856 /// NOT flush the batch — that would undo the batching. It is safe: the
7857 /// parked state is only ever entered through `edc_intra_sync`, which
7858 /// flushes before taking the planes, so nothing can be queued-but-unsent
7859 /// while the parse thread holds them.
7860 fn edc_giveback(&mut self) {
7861 if let Some(mut parked) = self.edc_parked.take() {
7862 parked.rec_y = core::mem::take(&mut self.rec_y);
7863 parked.rec_u = core::mem::take(&mut self.rec_u);
7864 parked.rec_v = core::mem::take(&mut self.rec_v);
7865 parked.bak_y = core::mem::take(&mut self.bak_y);
7866 parked.bak_u = core::mem::take(&mut self.bak_u);
7867 parked.bak_v = core::mem::take(&mut self.bak_v);
7868 parked.flt_rows = self.flt_rows;
7869 // Fail-soft: on the unwind path the worker may already be gone;
7870 // dropping the parked context is acceptable there (the planes are
7871 // lost, but the panic is being propagated anyway).
7872 let _ = self.edc_back_tx.as_ref().unwrap().send(parked);
7873 }
7874 }
7875
7876 /// Parse-side twin of the nnz/coded grid writes `add_inter_residual` does
7877 /// inline — the worker's port omits them (they are PARSE state: deblock
7878 /// derivation and the CAVLC nC contexts read them), so the threaded path
7879 /// commits them here, from the same parsed counts (their equality with the
7880 /// recon-side recount is the Part 8 nnz-threading brick's own invariant).
7881 fn edc_commit_nnz(
7882 &mut self,
7883 mbx: usize,
7884 mby: usize,
7885 t8: bool,
7886 nnzs: &[u8; 24],
7887 cbp_chroma: u32,
7888 ) {
7889 // BUILD THE RASTER LOCALLY, THEN COPY ROW-WISE. Both luma arms scattered
7890 // sixteen individually bounds-checked stores into the frame grid (the t8
7891 // arm through a 2x2-of-2x2 nest, the other through the z-order scan
7892 // table); chroma scattered eight more. Assembling the macroblock's own
7893 // 4x4 raster on the stack first turns that into four contiguous row
7894 // copies per plane.
7895 let (w4r, w2r) = (self.mb_w * 4, self.mb_w * 2);
7896 let mut raster = [0u8; 16];
7897 if t8 {
7898 for b8 in 0..4usize {
7899 let (b8x, b8y) = (b8 % 2, b8 / 2);
7900 let n = nnzs[b8 * 4];
7901 for sy in 0..2 {
7902 for sx in 0..2 {
7903 raster[(b8y * 2 + sy) * 4 + b8x * 2 + sx] = n;
7904 }
7905 }
7906 }
7907 } else {
7908 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
7909 raster[lby * 4 + lbx] = nnzs[blk];
7910 }
7911 }
7912 for by in 0..4usize {
7913 let a = (mby * 4 + by) * w4r + mbx * 4;
7914 self.nnz_y[a..a + 4].copy_from_slice(&raster[by * 4..by * 4 + 4]);
7915 }
7916 if cbp_chroma == 2 {
7917 for c in 0..2usize {
7918 let mut cr = [0u8; 4];
7919 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
7920 cr[by * 2 + bx] = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
7921 }
7922 for by in 0..2usize {
7923 let a = (mby * 2 + by) * w2r + mbx * 2;
7924 self.nnz_c[c][a..a + 2].copy_from_slice(&cr[by * 2..by * 2 + 2]);
7925 }
7926 }
7927 }
7928 }
7929
7930 /// Flush the entropy-decouple job queue: replay every deferred pixel job
7931 /// in parse order. Called before any intra macroblock (its reconstruction
7932 /// reads neighbour PIXELS), before row filtering, at B-branch entry, at
7933 /// slice end, and at `deblock()` as a backstop.
7934 /// Per-macroblock guard: the B arm calls this once per B MB — the empty
7935 /// test inlines, the drain body stays outlined.
7936 /// A job box from the pool (or a fresh one when the pool is empty -- the
7937 /// first row of a picture, or MT mode where boxes cross to the worker).
7938 #[inline]
7939 fn take_pinter_job(&mut self) -> Box<PInterJob> {
7940 self.edc_job_pool
7941 .pop()
7942 .unwrap_or_else(|| Box::new(PInterJob::blank()))
7943 }
7944
7945 #[inline]
7946 fn take_nores_job(&mut self, pj: PInterNoResJob) -> Box<PInterNoResJob> {
7947 match self.edc_nores_pool.pop() {
7948 Some(mut b) => {
7949 *b = pj;
7950 b
7951 }
7952 None => Box::new(pj),
7953 }
7954 }
7955
7956 #[inline(always)]
7957 fn edc_flush(&mut self) {
7958 if self.edc_jobs.is_empty() {
7959 return;
7960 }
7961 self.edc_flush_slow();
7962 }
7963
7964 fn edc_flush_slow(&mut self) {
7965 let mut jobs = core::mem::take(&mut self.edc_jobs);
7966 // Band identity holds unweighted OR under identity weights (the x264
7967 // weightp=2 common case: a table in every P slice, identity outside fades).
7968 let band_ok = self.weights.is_none() || self.weights_id0;
7969 // Measurement: nonzero-MV skips sitting in same-row runs of >=2 EQUAL
7970 // MVs (the next band candidate). counted_until stops double-counting.
7971 let mut counted_until = 0usize;
7972 let mut i = 0usize;
7973 while i < jobs.len() {
7974 let j = &jobs[i];
7975 match j {
7976 EdcJob::Skip { mbx, mby, mv } => {
7977 // mb_skip_run band coalescing: gather the maximal run of
7978 // consecutive same-row (0,0)-skips and recon it as ONE
7979 // band copy (weighted-P falls back — the copy identity
7980 // only holds unweighted).
7981 if *mv == (0, 0) && band_ok && !no_skipband() {
7982 let (x0, y0) = (*mbx, *mby);
7983 let mut n = 1usize;
7984 while let Some(EdcJob::Skip {
7985 mbx: nx,
7986 mby: ny,
7987 mv: nmv,
7988 }) = jobs.get(i + n)
7989 {
7990 if *ny == y0 && *nx == x0 + n && *nmv == (0, 0) {
7991 n += 1;
7992 } else {
7993 break;
7994 }
7995 }
7996 self.recon_p_skip_band(x0, y0, n);
7997 if double_recon() {
7998 edcstat::bump(&edcstat::DOUBLED, n as u64);
7999 self.recon_p_skip_band(x0, y0, n);
8000 }
8001 i += n;
8002 continue;
8003 }
8004 edcstat::bump(&edcstat::SKIP_SINGLES, 1);
8005 if *mv != (0, 0) && i >= counted_until && edcstat::on() {
8006 let (x0, y0) = (*mbx, *mby);
8007 let mut n2 = 1usize;
8008 while let Some(EdcJob::Skip {
8009 mbx: nx,
8010 mby: ny,
8011 mv: nmv,
8012 }) = jobs.get(i + n2)
8013 {
8014 if *ny == y0 && *nx == x0 + n2 && nmv == mv {
8015 n2 += 1;
8016 } else {
8017 break;
8018 }
8019 }
8020 if n2 >= 2 {
8021 let fp = mv.0 % 4 == 0 && mv.1 % 4 == 0;
8022 edcstat::bump(
8023 if fp {
8024 &edcstat::PEQ_FP
8025 } else {
8026 &edcstat::PEQ_FRAC
8027 },
8028 n2 as u64,
8029 );
8030 }
8031 counted_until = i + n2;
8032 }
8033 self.recon_p_skip(*mbx, *mby, *mv);
8034 if double_recon() {
8035 edcstat::bump(&edcstat::DOUBLED, 1);
8036 self.recon_p_skip(*mbx, *mby, *mv);
8037 }
8038 }
8039 EdcJob::Inter(job) => {
8040 self.recon_p_inter(job);
8041 if double_recon() {
8042 edcstat::bump(&edcstat::DOUBLED, 1);
8043 self.recon_p_inter(job);
8044 }
8045 }
8046 EdcJob::InterNoRes(job) => {
8047 // D9b: do NOT to_full() — that memset ~2.5 KB of zeros per MB
8048 // then walked add_inter_residual's all-zero path. MC + plane copy.
8049 self.recon_p_inter_nores(job);
8050 if double_recon() {
8051 edcstat::bump(&edcstat::DOUBLED, 1);
8052 self.recon_p_inter_nores(job);
8053 }
8054 }
8055 // B jobs exist only in worker (MT) mode and are never queued
8056 // here — the single-thread seam keeps B inline. Not reachable
8057 // from any input (the push sites are gated on `edc_tx`).
8058 EdcJob::B(_) | EdcJob::BSkip { .. } => unreachable!("B jobs are worker-only"),
8059 }
8060 i += 1;
8061 }
8062 // RECYCLE the job boxes (was: drop = one `free` per coded inter MB).
8063 for j in jobs.drain(..) {
8064 match j {
8065 EdcJob::Inter(b) => self.edc_job_pool.push(b),
8066 EdcJob::InterNoRes(b) => self.edc_nores_pool.push(b),
8067 _ => {}
8068 }
8069 }
8070 // Hand the (now empty) Vec back so its allocation is reused.
8071 self.edc_jobs = jobs;
8072 }
8073
8074 /// Reconstructs one CABAC P inter macroblock from its parse job — the
8075 /// pixel half of the entropy-decouple seam (docs/entropy-decouple-plan.md
8076 /// E1). Reads NOTHING from parse state except the frame grids this MB's
8077 /// parse already committed (its own block MVs/refs, re-gathered below —
8078 /// stable after commit) and the immutable DPB; called either inline
8079 /// (seam off / flush disabled) or in-order at a flush point. Byte-
8080 /// identical to the former inline block by construction: replay order
8081 /// equals inline order at every pixel-observable point (intra reads, row
8082 /// filtering) because flushes precede both.
8083 /// The P-inter reconstruction body, taking its inputs directly rather than
8084 /// through a `PInterJob`. Split out so the residual planes can be passed as
8085 /// `Option`s (absent == nothing was parsed) instead of forcing every caller
8086 /// to own a zeroed copy. NOTE: it re-gathers `gmv`/`gref` from the frame
8087 /// grids rather than reading the job's copies — those are for the worker,
8088 /// which must not touch the parse thread's grids.
8089 #[allow(clippy::too_many_arguments)]
8090 fn recon_p_inter_parts(
8091 &mut self,
8092 mbx: usize,
8093 mby: usize,
8094 qp: u8,
8095 cbp_chroma: u32,
8096 luma_scan: Option<&[[i32; 16]; 16]>,
8097 luma8: Option<&[[i32; 64]; 4]>,
8098 cdc: &[[i32; 4]; 2],
8099 cac: Option<&[[[i32; 16]; 4]; 2]>,
8100 nnzs: &[u8; 24],
8101 ) {
8102 let mbw = self.mb_w;
8103 // `add_inter_residual` (and anything under it) reads `self.cur_qp`,
8104 // which at FLUSH time belongs to a later macroblock — replay must
8105 // restore this MB's qp. The x264 corpus (near-constant QP) could not
8106 // see this; the encoder's delta-QP roundtrip stream caught it.
8107 let saved_qp = self.cur_qp;
8108 self.cur_qp = qp;
8109 // ---- Recon: motion-comp (per 4×4 luma / co-located 2×2 chroma using the
8110 // committed grid MV — the 6-tap/bilinear filter is per-output-pixel, so
8111 // per-block MC is bit-identical to per-partition MC) + residual add via the
8112 // SAME reconstruct_4x4 as intra, with the MC output as the prediction.
8113 let w4r = mbw * 4;
8114 let mut pred_y = [0u8; 256];
8115 let mut c_pred = [[0u8; 64]; 2];
8116 {
8117 // MC-CALL COALESCING (side-by-side descent, dec target #2): the old
8118 // loop paid 16 mc_luma(4×4) + 32 mc_chroma(2×2) per MB regardless of
8119 // partitioning — 48 calls even for a single-MV 16×16 MB, and the
8120 // per-call glue around 2.4M calls was ~40% of decoding real-world
8121 // (x264) streams. The 6-tap/bilinear filters are per-output-pixel,
8122 // so merging blocks with equal (mv, ref) into one wider MC call is
8123 // BIT-IDENTICAL; the rect ladder mirrors the partition shapes.
8124 let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
8125 let (rh16, cch) = (self.mb_h * 16, self.mb_h * 8);
8126 let mut gmv = [(0i32, 0i32); 16];
8127 let mut gref = [0usize; 16];
8128 let _gg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
8129 let nrefs = self.refs.len() - 1;
8130 for by in 0..4usize {
8131 // Row-contiguous: one slice copy for the MVs.
8132 let row = (mby * 4 + by) * w4r + mbx * 4;
8133 gmv[by * 4..by * 4 + 4].copy_from_slice(&self.mv_y[row..row + 4]);
8134 let ridx = &self.ref_idx_y[row..row + 4];
8135 for bx in 0..4usize {
8136 // Per-block reference (multi-ref P): ref_idx_l0 committed to the
8137 // grid. Clamp — a corrupt stream can over-range it (never panic).
8138 gref[by * 4 + bx] = (ridx[bx].max(0) as usize).min(nrefs);
8139 }
8140 }
8141 drop(_gg);
8142 // All blocks of the rect (in 4×4-block units) match its top-left?
8143 let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
8144 let t = y4 * 4 + x4;
8145 (0..h4).all(|dy| {
8146 (0..w4).all(|dx| {
8147 let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
8148 gmv[b] == gmv[t] && gref[b] == gref[t]
8149 })
8150 })
8151 };
8152 let refs = &self.refs;
8153 let (cw, ccw) = (self.cw, self.ccw);
8154 let mc_rect = |x4: usize,
8155 y4: usize,
8156 w4: usize,
8157 h4: usize,
8158 pred_y: &mut [u8; 256],
8159 c_pred: &mut [[u8; 64]; 2]| {
8160 let b = y4 * 4 + x4;
8161 let (mv, gr) = (gmv[b & 15], gref[b & 15]);
8162 // `gref` holds a reference-list slot; a slot the list
8163 // does not have means there is nothing to predict from,
8164 // so the rect is left as it stands.
8165 let Some(reference) = refs.get(gr) else {
8166 return;
8167 };
8168 let (w, h) = (w4 * 4, h4 * 4);
8169 // A FULL-WIDTH rect (w == 16, so x4 == 0) occupies contiguous
8170 // whole rows of `pred_y` — the MC output layout and the
8171 // destination layout coincide, so MC writes the prediction
8172 // buffer DIRECTLY. The staging copy exists only for narrow
8173 // rects, whose rows really are strided in `pred_y`. This is
8174 // the diagnosis's "stage-boundary materialization" tax paid
8175 // by the dominant 16×16/16×8 shapes: 256 B of `t` zeroing
8176 // plus a 256 B copy per rect, for nothing.
8177 if w == 16 {
8178 rusty_h264_common::inter::with_mc_scratch(|scr| {
8179 rusty_h264_common::inter::mc_luma_padded_pre(
8180 scr,
8181 &*reference.luma_guard(reference.ch),
8182 reference.lstride(),
8183 crate::LPAD,
8184 cw,
8185 rh16,
8186 mbx * 16,
8187 mby * 16 + y4 * 4,
8188 w,
8189 h,
8190 mv.0,
8191 mv.1,
8192 &mut pred_y[y4 * 64..y4 * 64 + w * h],
8193 )
8194 });
8195 } else {
8196 let mut t = [0u8; 256];
8197 rusty_h264_common::inter::with_mc_scratch(|scr| {
8198 rusty_h264_common::inter::mc_luma_padded_pre(
8199 scr,
8200 &*reference.luma_guard(reference.ch),
8201 reference.lstride(),
8202 crate::LPAD,
8203 cw,
8204 rh16,
8205 mbx * 16 + x4 * 4,
8206 mby * 16 + y4 * 4,
8207 w,
8208 h,
8209 mv.0,
8210 mv.1,
8211 &mut t[..w * h],
8212 )
8213 });
8214 let _pb =
8215 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
8216 for dy in 0..h {
8217 pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
8218 .copy_from_slice(&t[dy * w..dy * w + w]);
8219 }
8220 }
8221 let (cw4, ch4) = (w4 * 2, h4 * 2);
8222 let nc = cw4 * ch4;
8223 let (gu, gv) = (
8224 reference.chroma_guard(0, reference.ch),
8225 reference.chroma_guard(1, reference.ch),
8226 );
8227 // U+V paired: one setup serves both planes (see
8228 // mc_chroma_padded_pair). Full-width coincidence:
8229 // cw4 == 8 rows are contiguous in the 8-wide plane.
8230 if cw4 == 8 {
8231 let [cu, cv] = &mut *c_pred;
8232 rusty_h264_common::inter::mc_chroma_padded_pair(
8233 &gu,
8234 &gv,
8235 reference.cstride(),
8236 crate::CPAD,
8237 ccw,
8238 cch,
8239 mbx * 8,
8240 mby * 8 + y4 * 2,
8241 cw4,
8242 ch4,
8243 mv.0,
8244 mv.1,
8245 &mut cu[y4 * 16..y4 * 16 + nc],
8246 &mut cv[y4 * 16..y4 * 16 + nc],
8247 );
8248 } else {
8249 let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
8250 rusty_h264_common::inter::mc_chroma_padded_pair(
8251 &gu,
8252 &gv,
8253 reference.cstride(),
8254 crate::CPAD,
8255 ccw,
8256 cch,
8257 mbx * 8 + x4 * 2,
8258 mby * 8 + y4 * 2,
8259 cw4,
8260 ch4,
8261 mv.0,
8262 mv.1,
8263 &mut tu[..nc],
8264 &mut tv[..nc],
8265 );
8266 let _pb =
8267 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
8268 for (cc, tc) in [(0usize, &tu), (1, &tv)] {
8269 for dy in 0..ch4 {
8270 c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
8271 .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
8272 }
8273 }
8274 }
8275 };
8276 if rect_eq(0, 0, 4, 4) {
8277 mc_rect(0, 0, 4, 4, &mut pred_y, &mut c_pred);
8278 } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
8279 mc_rect(0, 0, 4, 2, &mut pred_y, &mut c_pred);
8280 mc_rect(0, 2, 4, 2, &mut pred_y, &mut c_pred);
8281 } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
8282 mc_rect(0, 0, 2, 4, &mut pred_y, &mut c_pred);
8283 mc_rect(2, 0, 2, 4, &mut pred_y, &mut c_pred);
8284 } else {
8285 for q in 0..4usize {
8286 let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
8287 if rect_eq(qx, qy, 2, 2) {
8288 mc_rect(qx, qy, 2, 2, &mut pred_y, &mut c_pred);
8289 } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
8290 mc_rect(qx, qy, 2, 1, &mut pred_y, &mut c_pred);
8291 mc_rect(qx, qy + 1, 2, 1, &mut pred_y, &mut c_pred);
8292 } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
8293 mc_rect(qx, qy, 1, 2, &mut pred_y, &mut c_pred);
8294 mc_rect(qx + 1, qy, 1, 2, &mut pred_y, &mut c_pred);
8295 } else {
8296 for j in 0..4usize {
8297 mc_rect(qx + (j % 2), qy + (j / 2), 1, 1, &mut pred_y, &mut c_pred);
8298 }
8299 }
8300 }
8301 }
8302 // EXPLICIT WEIGHTED PREDICTION (spec 8.4.2.3). The CAVLC inter
8303 // path weights each partition after MC; the MC-call-coalescing
8304 // rewrite of this CABAC path lost it, and nothing caught that
8305 // because the effect is invisible unless a stream actually
8306 // carries non-default weights. x264's `weightp` DUPLICATES a
8307 // reference and distinguishes the copy ONLY by its weights, so
8308 // every macroblock picking the weighted index decoded unweighted
8309 // -- a silent, accumulating luma drift.
8310 //
8311 // Applied per 4x4 block rather than per partition: the weight
8312 // depends solely on the block's reference index, so the two are
8313 // equivalent, and `gref` already holds it for every block
8314 // regardless of which rect ladder rung ran.
8315 if self.weights.is_some() {
8316 for by in 0..4usize {
8317 for bx in 0..4usize {
8318 let refi = gref[by * 4 + bx];
8319 self.weight_partition(
8320 &mut pred_y,
8321 &mut c_pred,
8322 0,
8323 refi,
8324 bx * 4,
8325 by * 4,
8326 4,
8327 4,
8328 );
8329 }
8330 }
8331 }
8332 }
8333 // Residual add — the SAME helper the B path uses (this inline
8334 // copy was a duplicate; deduped when the zero-block fast path
8335 // landed so both paths share it).
8336 self.add_inter_residual(
8337 mbx, mby, &pred_y, &c_pred, luma_scan, luma8, cdc, cac, cbp_chroma, nnzs,
8338 );
8339 self.cur_qp = saved_qp;
8340 }
8341
8342 /// Job-shaped entry point (EDC replay + the `double_recon` A/B knob).
8343 fn recon_p_inter(&mut self, j: &PInterJob) {
8344 self.recon_p_inter_parts(
8345 j.mbx,
8346 j.mby,
8347 j.qp,
8348 j.cbp_chroma,
8349 Some(&j.luma_scan),
8350 j.t8.then_some(&j.luma8),
8351 &j.cdc,
8352 Some(&j.cac),
8353 &j.nnzs,
8354 );
8355 }
8356
8357 /// D9b: P inter with `cbp == 0` — MC + plane copy, no coeff memset / residual walk.
8358 /// Byte-identical to `recon_p_inter` on a zero-residual job: prediction is the recon.
8359 fn recon_p_inter_nores(&mut self, j: &PInterNoResJob) {
8360 let w4r = self.mb_w * 4;
8361 // Parse-side nnz for single-thread EDC flush (MT commits earlier via edc_commit_nnz).
8362 // ROW FILLS. Both arms touch the same sixteen cells - four contiguous
8363 // per macroblock row - and wrote them one indexed store at a time (the
8364 // t8 arm through a 2x2-of-2x2 nest, the other through the z-order scan).
8365 for by in 0..4usize {
8366 let r = (j.mby * 4 + by) * w4r + j.mbx * 4;
8367 self.nnz_y[r..r + 4].fill(0);
8368 if j.t8 {
8369 self.coded_y[r..r + 4].fill(true);
8370 }
8371 }
8372 let mut pred_y = [0u8; 256];
8373 let mut c_pred = [[0u8; 64]; 2];
8374 // `self.refs.len() - 1` was re-loaded on every one of the sixteen
8375 // iterations; it is a per-macroblock invariant. Written once, not
8376 // zeroed-then-filled.
8377 let nrefs = self.refs.len() - 1;
8378 let gref: [usize; 16] = core::array::from_fn(|k| (j.gref[k] as usize).min(nrefs));
8379 coalesce_p_inter_mc(
8380 &self.refs,
8381 self.cw,
8382 self.ccw,
8383 self.mb_h,
8384 j.mbx,
8385 j.mby,
8386 &j.gmv,
8387 &gref,
8388 &mut pred_y,
8389 &mut c_pred,
8390 );
8391 // The identity early-out lives INSIDE `weight_partition`, so an x264
8392 // stream - which carries a pred_weight_table in EVERY P slice, identity
8393 // outside fades - still paid sixteen calls per macroblock to be told
8394 // there was nothing to do. Hoisted to one test.
8395 if self.weights.is_some() && !self.weights_l0id {
8396 for by in 0..4usize {
8397 for bx in 0..4usize {
8398 let refi = gref[by * 4 + bx];
8399 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4);
8400 }
8401 }
8402 }
8403 // ONE span per plane: the destination is a stack of contiguous runs a
8404 // fixed stride apart, so the rows share a single bounds check instead of
8405 // one each (16 + 8 + 8 of them).
8406 let (cw, ccw) = (self.cw, self.ccw);
8407 let ybase = (j.mby * 16) * cw + j.mbx * 16;
8408 let ywin = &mut self.rec_y[ybase..ybase + 15 * cw + 16];
8409 for dy in 0..16 {
8410 ywin[dy * cw..dy * cw + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
8411 }
8412 let cbase = (j.mby * 8) * ccw + j.mbx * 8;
8413 for c in 0..2 {
8414 let plane = if c == 0 {
8415 &mut self.rec_u
8416 } else {
8417 &mut self.rec_v
8418 };
8419 let cwin = &mut plane[cbase..cbase + 7 * ccw + 8];
8420 for dy in 0..8 {
8421 cwin[dy * ccw..dy * ccw + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
8422 }
8423 }
8424 }
8425
8426 fn decode_p_skip(&mut self, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
8427 self.route_skip_mbs += 1;
8428 self.wait_refs_for_mb(mb_y);
8429 // DEBLOCK CLASS: a P_Skip macroblock carries no coefficients and one
8430 // (ref, mv) for all 16 blocks, so every internal boundary strength is 0 by
8431 // §8.7.2.1 and the loop filter needs 9 block loads instead of 24. This is
8432 // the single highest-value classification: the MB-kind census measures Skip
8433 // at 36.4% (CAVLC) / 65.0% (main) / 57.8% (high) of real x264 corpora.
8434 // Written HERE because both the CAVLC and the CABAC slice loops funnel
8435 // through this one function.
8436 //
8437 // Deliberately NOT done for `B_Skip` — its motion is direct-derived and can
8438 // differ per 4×4 sub-block, so its internal edges can legally reach
8439 // strength 1. B_Skip stays UNSET and takes the blind path.
8440 // (kind commit moved into the span/immediate arms below.)
8441 // P_Skip always references index 0 (the most recent picture). Borrow it —
8442 // a full-frame `.cloned()` here was ~86% of total decode time (one ~3 MB
8443 // plane copy per skip MB, thousands per frame).
8444 if self.refs.is_empty() {
8445 return Err(MbError::Unsupported("P_Skip without reference"));
8446 }
8447 let addr = mb_y * self.mb_w + mb_x;
8448 // mb_x == 0: the left neighbor is off-frame, so §8.4.1.1's
8449 // unavailability rule forces (0,0) with no gather at all.
8450 let mv = if (mb_x == 0 || self.skip_zero_next == addr) && !no_runmv() {
8451 edcstat::bump(&edcstat::SKIPMV_FORCED, 1);
8452 (0, 0)
8453 } else {
8454 // The gather reads the grids; the pending spans cannot contain
8455 // this MB's left (a non-forced skip means the previous MB was not
8456 // a (0,0) committer) — flush both before deriving.
8457 self.span_flush();
8458 edcstat::bump(&edcstat::SKIPMV_DERIVED, 1);
8459 self.skip_mv(mb_x, mb_y)
8460 };
8461 self.skip_zero_next = if mv == (0, 0) { addr + 1 } else { usize::MAX };
8462 // Grid commits are PARSE state — (0,0) skips defer them into the P
8463 // span (the deferral constants); nonzero-MV skips commit immediately.
8464 if mv == (0, 0) {
8465 // Recon defers too when the band identity holds (1T, no/identity
8466 // weights) — the span flush band-copies instead of queueing jobs.
8467 let recon = self.edc_tx.is_none() && (self.weights.is_none() || self.weights_id0);
8468 self.pz_push(mb_x, mb_y, recon);
8469 if recon {
8470 return Ok(());
8471 }
8472 } else {
8473 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8474 if let Some(p) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
8475 *p = rusty_h264_common::deblock::MB_KIND_SKIP;
8476 }
8477 self.set_mb_mv(mb_x, mb_y, mv, true, 0);
8478 let w4 = self.mb_w * 4;
8479 for dy in 0..4 {
8480 let a = (mb_y * 4 + dy) * w4 + mb_x * 4;
8481 self.coded_y[a..a + 4].fill(true);
8482 self.modes_y[a..a + 4].fill(2);
8483 }
8484 }
8485 if self.edc_tx.is_some() {
8486 self.edc_giveback();
8487 self.edc_send_job(EdcJob::Skip {
8488 mbx: mb_x,
8489 mby: mb_y,
8490 mv,
8491 });
8492 return Ok(());
8493 }
8494 if self.edc_active {
8495 self.edc_jobs.push(EdcJob::Skip {
8496 mbx: mb_x,
8497 mby: mb_y,
8498 mv,
8499 });
8500 return Ok(());
8501 }
8502 self.recon_p_skip(mb_x, mb_y, mv);
8503 if double_recon() {
8504 self.recon_p_skip(mb_x, mb_y, mv);
8505 }
8506 Ok(())
8507 }
8508
8509 /// Pixel half of P_Skip (see the E1 seam note on `recon_p_inter`).
8510 /// Run-coalesced P_Skip reconstruction: `n` consecutive skip MBs on one
8511 /// row, all with `mv == (0,0)` and NO weighted prediction. At full-pel
8512 /// zero motion, MC is the identity read of `refs[0]` — so the recon is a
8513 /// straight band copy from the padded reference plane, no staging, no MC
8514 /// calls. Byte-identical to `n` calls of `recon_p_skip` by construction
8515 /// (mc_luma_padded/mc_chroma_padded at mv 0 return exactly these bytes).
8516 ///
8517 /// The (0,0) run theorem that makes ONE derivation cover the run: once a
8518 /// skip MB commits (ref 0, mv (0,0)), every later MB of the run derives
8519 /// (0,0) too — its left neighbour (or a row-start's missing neighbour)
8520 /// triggers §8.4.1.1's zero-MV rule in `skip_mv`.
8521 fn recon_p_skip_band(&mut self, mbx0: usize, mby: usize, n: usize) {
8522 let w = n * 16;
8523 let Some(rf0) = self.refs.first() else { return };
8524 let lst = rf0.lstride();
8525 {
8526 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8527 let ly = rf0.luma_guard(rf0.ch);
8528 for dy in 0..16 {
8529 let y = mby * 16 + dy;
8530 let src = (y + crate::LPAD) * lst + crate::LPAD + mbx0 * 16;
8531 let dst = y * self.cw + mbx0 * 16;
8532 // ARMOR (fuzz find, inline-execution.md 11.11): on a MALFORMED
8533 // stream a reference can carry geometry that does not match the
8534 // open picture (mutated parameter sets / truncated ref planes),
8535 // so the in-frame band's ref window can leave the guard slice.
8536 // The band REFUSES rather than panics; a conformant stream
8537 // cannot take this arm (repro: scratchpad repro.264, plane 6400
8538 // vs index 6416). Output on malformed input is unspecified —
8539 // absence of panic is the contract the fuzz gate enforces.
8540 let (Some(d), Some(sr)) = (self.rec_y.get_mut(dst..dst + w), ly.get(src..src + w))
8541 else {
8542 return;
8543 };
8544 d.copy_from_slice(sr);
8545 }
8546 }
8547 let cst = rf0.cstride();
8548 let wc = n * 8;
8549 for c in 0..2 {
8550 let rc = rf0.chroma_guard(c, rf0.ch);
8551 let plane = if c == 0 {
8552 &mut self.rec_u
8553 } else {
8554 &mut self.rec_v
8555 };
8556 for dy in 0..8 {
8557 let y = mby * 8 + dy;
8558 let src = (y + crate::CPAD) * cst + crate::CPAD + mbx0 * 8;
8559 let dst = y * self.ccw + mbx0 * 8;
8560 // Same armor as the luma band above.
8561 let (Some(d), Some(sr)) = (plane.get_mut(dst..dst + wc), rc.get(src..src + wc))
8562 else {
8563 return;
8564 };
8565 d.copy_from_slice(sr);
8566 }
8567 }
8568 edcstat::bump(&edcstat::SKIPBAND_MBS, n as u64);
8569 edcstat::bump(&edcstat::SKIPBAND_RUNS, 1);
8570 }
8571
8572 /// B_Skip zero-bi recon: rec = avg(ref0, ref1') row-wise, straight from the
8573 /// padded planes. Byte-identical to b_mc at mv (0,0)/(0,0): full-pel MC is
8574 /// an identity read and the unweighted bi blend is exactly (a+b+1)>>1 —
8575 /// which the compiler emits as pavgb over the sliced zips, same as b_mc's
8576 /// blend site.
8577 fn recon_b_skip_zero_bi(&mut self, mbx: usize, mby: usize, r0i: usize, r1i: usize) {
8578 let (Some(rf0), Some(rf1)) = (self.refs.get(r0i), self.refs1.get(r1i)) else {
8579 return;
8580 };
8581 let (l0st, l1st) = (rf0.lstride(), rf1.lstride());
8582 {
8583 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8584 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
8585 for dy in 0..16 {
8586 let y = mby * 16 + dy;
8587 let s0 = (y + crate::LPAD) * l0st + crate::LPAD + mbx * 16;
8588 let s1 = (y + crate::LPAD) * l1st + crate::LPAD + mbx * 16;
8589 let d = y * self.cw + mbx * 16;
8590 // WIN: STRIDED destination, so rustc cannot do what it does for the
8591 // contiguous pred_y averages (which are already ideal vpavgb and are
8592 // left alone). Route the row through the pixel_avg kernel instead.
8593 rusty_h264_common::inter::avg_row_into(
8594 &ly0[s0..s0 + 16],
8595 &ly1[s1..s1 + 16],
8596 16,
8597 &mut self.rec_y[d..d + 16],
8598 );
8599 }
8600 }
8601 let (c0st, c1st) = (rf0.cstride(), rf1.cstride());
8602 for c in 0..2 {
8603 let rc0 = rf0.chroma_guard(c, rf0.ch);
8604 let rc1 = rf1.chroma_guard(c, rf1.ch);
8605 let plane = if c == 0 {
8606 &mut self.rec_u
8607 } else {
8608 &mut self.rec_v
8609 };
8610 for dy in 0..8 {
8611 let y = mby * 8 + dy;
8612 let s0 = (y + crate::CPAD) * c0st + crate::CPAD + mbx * 8;
8613 let s1 = (y + crate::CPAD) * c1st + crate::CPAD + mbx * 8;
8614 let d = y * self.ccw + mbx * 8;
8615 // WIN: STRIDED destination, so rustc cannot do what it does for the
8616 // contiguous pred_y averages (which are already ideal vpavgb and are
8617 // left alone). Route the row through the pixel_avg kernel instead.
8618 rusty_h264_common::inter::avg_row_into(
8619 &rc0[s0..s0 + 8],
8620 &rc1[s1..s1 + 8],
8621 8,
8622 &mut plane[d..d + 8],
8623 );
8624 }
8625 }
8626 }
8627
8628 /// Full-pel P_Skip single: rec window = ref\[0\] window at an integer offset.
8629 /// Returns false (touching nothing) when any window leaves the padded
8630 /// plane — mc's edge clamping takes over there. Luma needs mv%4==0;
8631 /// chroma needs mv%8==0 (its frac is mv&7), so odd full-pel MVs copy luma
8632 /// and interpolate chroma.
8633 fn recon_p_skip_fullpel(&mut self, mbx: usize, mby: usize, mv: (i32, i32)) -> bool {
8634 let (dx, dy) = ((mv.0 / 4) as isize, (mv.1 / 4) as isize);
8635 let Some(rf0) = self.refs.first() else {
8636 return false;
8637 };
8638 let lst = rf0.lstride();
8639 let lpad = crate::LPAD as isize;
8640 let cx = mbx as isize * 16 + dx + lpad;
8641 let cy = mby as isize * 16 + dy + lpad;
8642 {
8643 let ly = rf0.luma_guard(rf0.ch);
8644 let lrows = rf0.lrows() as isize;
8645 if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
8646 return false;
8647 }
8648 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8649 // ONE span each side: sixteen rows a fixed stride apart, so the
8650 // source and destination checks are paid once instead of per row.
8651 let (cw, sb) = (self.cw, cy as usize * lst + cx as usize);
8652 let db = (mby * 16) * cw + mbx * 16;
8653 let sw = &ly[sb..sb + 15 * lst + 16];
8654 let dw = &mut self.rec_y[db..db + 15 * cw + 16];
8655 for r in 0..16 {
8656 dw[r * cw..r * cw + 16].copy_from_slice(&sw[r * lst..r * lst + 16]);
8657 }
8658 }
8659 let cst = rf0.cstride();
8660 let cpad = crate::CPAD as isize;
8661 if mv.0 % 8 == 0 && mv.1 % 8 == 0 {
8662 let ccx = mbx as isize * 8 + (mv.0 / 8) as isize + cpad;
8663 let ccy = mby as isize * 8 + (mv.1 / 8) as isize + cpad;
8664 let crows = rf0.crows() as isize;
8665 if ccx >= 0 && ccx + 8 <= cst as isize && ccy >= 0 && ccy + 8 <= crows {
8666 for c in 0..2 {
8667 let rc = rf0.chroma_guard(c, rf0.ch);
8668 let plane = if c == 0 {
8669 &mut self.rec_u
8670 } else {
8671 &mut self.rec_v
8672 };
8673 let (ccw, sb) = (self.ccw, ccy as usize * cst + ccx as usize);
8674 let db = (mby * 8) * ccw + mbx * 8;
8675 let sw = &rc[sb..sb + 7 * cst + 8];
8676 let dw = &mut plane[db..db + 7 * ccw + 8];
8677 for r in 0..8 {
8678 dw[r * ccw..r * ccw + 8].copy_from_slice(&sw[r * cst..r * cst + 8]);
8679 }
8680 }
8681 edcstat::bump(&edcstat::SKIP_FP_FULL, 1);
8682 return true;
8683 }
8684 }
8685 // Odd full-pel (or chroma window out of pad): luma is already copied,
8686 // chroma still interpolates — identical to the mc path's chroma half.
8687 let cch = self.mb_h * 8;
8688 for c in 0..2 {
8689 let mut pc = [0u8; 64];
8690 let rc = if c == 0 {
8691 &*rf0.chroma_guard(0, rf0.ch)
8692 } else {
8693 &*rf0.chroma_guard(1, rf0.ch)
8694 };
8695 mc_chroma_padded(
8696 rc,
8697 cst,
8698 crate::CPAD,
8699 self.ccw,
8700 cch,
8701 mbx * 8,
8702 mby * 8,
8703 8,
8704 8,
8705 mv.0,
8706 mv.1,
8707 &mut pc,
8708 );
8709 let plane = if c == 0 {
8710 &mut self.rec_u
8711 } else {
8712 &mut self.rec_v
8713 };
8714 for r in 0..8 {
8715 let d = (mby * 8 + r) * self.ccw + mbx * 8;
8716 plane[d..d + 8].copy_from_slice(&pc[r * 8..r * 8 + 8]);
8717 }
8718 }
8719 edcstat::bump(&edcstat::SKIP_FP_LUMA, 1);
8720 true
8721 }
8722
8723 /// B_Skip full-pel recon with per-list integer offsets: uni = offset row
8724 /// copy, bi = offset row average ((a+b+1)>>1). Luma always copies here
8725 /// (caller guarantees mv%4==0); chroma copies at mv%8==0 and otherwise
8726 /// interpolates via b_mc_chroma — identical to the b_mc path's chroma
8727 /// half with no implicit weights. Returns false untouched when a luma
8728 /// window leaves the padded plane.
8729 fn recon_b_skip_fp(
8730 &mut self,
8731 mbx: usize,
8732 mby: usize,
8733 r0: Option<usize>,
8734 r1: Option<usize>,
8735 mv0: (i32, i32),
8736 mv1: (i32, i32),
8737 ) -> bool {
8738 let lpad = crate::LPAD as isize;
8739 // (start-row, start-col, stride, rows) of each active list's luma window.
8740 let win = |rf: &crate::RefFrame, mv: (i32, i32)| -> Option<(usize, usize)> {
8741 let lst = rf.lstride();
8742 let cx = mbx as isize * 16 + (mv.0 / 4) as isize + lpad;
8743 let cy = mby as isize * 16 + (mv.1 / 4) as isize + lpad;
8744 let lrows = rf.lrows() as isize;
8745 if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
8746 None
8747 } else {
8748 Some((cy as usize * lst + cx as usize, lst))
8749 }
8750 };
8751 let w0 = match r0 {
8752 Some(i) => match self.refs.get(i).and_then(|rr| win(rr, mv0)) {
8753 Some(w) => Some(w),
8754 None => return false,
8755 },
8756 None => None,
8757 };
8758 let w1 = match r1 {
8759 Some(i) => match self.refs1.get(i).and_then(|rr| win(rr, mv1)) {
8760 Some(w) => Some(w),
8761 None => return false,
8762 },
8763 None => None,
8764 };
8765 {
8766 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8767 match (w0, w1) {
8768 (Some((s0, l0)), Some((s1, l1))) => {
8769 let Some(rf0) = r0.and_then(|i| self.refs.get(i)) else {
8770 return false;
8771 };
8772 // Same shape as the `rf0` line above: `w1` is `Some` only when
8773 // `r1` is `Some(i)` AND `refs1.get(i)` returned a frame, so the
8774 // `else` is unreachable -- but it is a REFUSAL, not a panic, and
8775 // `recon_b_skip_fp` already answers `false` for every case it
8776 // cannot fast-path. Indexing a `Vec` with an unwrapped `Option`
8777 // spent a discriminant test, an `unwrap_failed` call and a bounds
8778 // check to restate an invariant the caller had already proven.
8779 let Some(rf1) = r1.and_then(|i| self.refs1.get(i)) else {
8780 return false;
8781 };
8782 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
8783 // WIN: this was 256 scalar (p + q + 1) >> 1 per macroblock, three
8784 // modules from the kernel that computes exactly that
8785 // with and already takes both source strides. One kernel
8786 // call over the 16x16, then sixteen row copies out to the strided
8787 // recon plane.
8788 for r in 0..16 {
8789 let d = (mby * 16 + r) * self.cw + mbx * 16;
8790 rusty_h264_common::inter::avg_row_into(
8791 &ly0[s0 + r * l0..],
8792 &ly1[s1 + r * l1..],
8793 16,
8794 &mut self.rec_y[d..d + 16],
8795 );
8796 }
8797 }
8798 (Some((s0, l0)), None) => {
8799 let Some(rf0) = r0.and_then(|i| self.refs.get(i)) else {
8800 return false;
8801 };
8802 let ly = rf0.luma_guard(rf0.ch);
8803 for r in 0..16 {
8804 let d = (mby * 16 + r) * self.cw + mbx * 16;
8805 self.rec_y[d..d + 16].copy_from_slice(&ly[s0 + r * l0..s0 + r * l0 + 16]);
8806 }
8807 }
8808 (None, Some((s1, l1))) => {
8809 // Same shape as the `rf0` line above: `w1` is `Some` only when
8810 // `r1` is `Some(i)` AND `refs1.get(i)` returned a frame, so the
8811 // `else` is unreachable -- but it is a REFUSAL, not a panic, and
8812 // `recon_b_skip_fp` already answers `false` for every case it
8813 // cannot fast-path. Indexing a `Vec` with an unwrapped `Option`
8814 // spent a discriminant test, an `unwrap_failed` call and a bounds
8815 // check to restate an invariant the caller had already proven.
8816 let Some(rf1) = r1.and_then(|i| self.refs1.get(i)) else {
8817 return false;
8818 };
8819 let ly = rf1.luma_guard(rf1.ch);
8820 for r in 0..16 {
8821 let d = (mby * 16 + r) * self.cw + mbx * 16;
8822 self.rec_y[d..d + 16].copy_from_slice(&ly[s1 + r * l1..s1 + r * l1 + 16]);
8823 }
8824 }
8825 (None, None) => unreachable!("caller guarantees an active list"),
8826 }
8827 }
8828 // Chroma: interpolating half — route through b_mc_chroma into a stage
8829 // (weights None: the caller only enters at implicit None/(32,32), and
8830 // (32,32) IS the plain average b_mc_chroma computes for None).
8831 let cch = self.mb_h * 8;
8832 let mut c_pred = [[0u8; 64]; 2];
8833 let (ri0, ri1) = (r0.map_or(-1, |i| i as i32), r1.map_or(-1, |i| i as i32));
8834 self.b_mc_chroma(
8835 mbx,
8836 mby,
8837 0,
8838 0,
8839 16,
8840 16,
8841 ri0,
8842 mv0,
8843 ri1,
8844 mv1,
8845 &mut c_pred,
8846 None,
8847 cch,
8848 );
8849 for c in 0..2 {
8850 let plane = if c == 0 {
8851 &mut self.rec_u
8852 } else {
8853 &mut self.rec_v
8854 };
8855 for r in 0..8 {
8856 let d = (mby * 8 + r) * self.ccw + mbx * 8;
8857 plane[d..d + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
8858 }
8859 }
8860 true
8861 }
8862
8863 fn recon_p_skip(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32)) {
8864 // Full-pel + identity/no weights: MC is a pure offset read, so copy the
8865 // ref window straight into rec — no pred staging, no weight pass. The
8866 // window must sit inside the PADDED plane (outside it, mc's clamping
8867 // differs from a raw offset read, so fall through).
8868 if (self.weights.is_none() || self.weights_id0)
8869 && mv.0 % 4 == 0
8870 && mv.1 % 4 == 0
8871 && !no_skipfp()
8872 && self.recon_p_skip_fullpel(mb_x, mb_y, mv)
8873 {
8874 return;
8875 }
8876 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
8877
8878 let mut pred = [0u8; 256];
8879 let Some(rf0) = self.refs.first() else { return };
8880 mc_luma_padded(
8881 &*rf0.luma_guard(rf0.ch),
8882 rf0.lstride(),
8883 crate::LPAD,
8884 self.cw,
8885 ch,
8886 mb_x * 16,
8887 mb_y * 16,
8888 16,
8889 16,
8890 mv.0,
8891 mv.1,
8892 &mut pred,
8893 );
8894 if let Some(wt) = &self.weights {
8895 if !self.weights_id0 || no_skipfp() {
8896 for p in pred.iter_mut() {
8897 *p = wt.apply_luma(*p, 0, 0);
8898 }
8899 }
8900 }
8901 {
8902 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8903 // ONE span for the sixteen rows (see `recon_p_inter_nores`).
8904 let (cw, base) = (self.cw, (mb_y * 16) * self.cw + mb_x * 16);
8905 let win = &mut self.rec_y[base..base + 15 * cw + 16];
8906 for dy in 0..16 {
8907 win[dy * cw..dy * cw + 16].copy_from_slice(&pred[dy * 16..dy * 16 + 16]);
8908 }
8909 }
8910 let (mut pu, mut pv) = ([0u8; 64], [0u8; 64]);
8911 {
8912 let Some(rf0) = self.refs.first() else { return };
8913 let (gu, gv) = (rf0.chroma_guard(0, rf0.ch), rf0.chroma_guard(1, rf0.ch));
8914 rusty_h264_common::inter::mc_chroma_padded_pair(
8915 &gu,
8916 &gv,
8917 rf0.cstride(),
8918 crate::CPAD,
8919 self.ccw,
8920 cch,
8921 mb_x * 8,
8922 mb_y * 8,
8923 8,
8924 8,
8925 mv.0,
8926 mv.1,
8927 &mut pu,
8928 &mut pv,
8929 );
8930 }
8931 if let Some(wt) = &self.weights {
8932 if !self.weights_id0 || no_skipfp() {
8933 for p in pu.iter_mut() {
8934 *p = wt.apply_chroma(*p, 0, 0, 0);
8935 }
8936 for p in pv.iter_mut() {
8937 *p = wt.apply_chroma(*p, 0, 0, 1);
8938 }
8939 }
8940 }
8941 for (pc, plane) in [(&pu, &mut self.rec_u), (&pv, &mut self.rec_v)] {
8942 for dy in 0..8 {
8943 let d = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
8944 plane[d..d + 8].copy_from_slice(&pc[dy * 8..dy * 8 + 8]);
8945 }
8946 }
8947 }
8948
8949 /// Predicted `Intra_4x4` mode for the block at absolute coords `(bx, by)`.
8950 /// If either the left or top neighbor is outside the frame or in another
8951 /// slice, the prediction is DC (mode 2) (spec §8.3.1.1).
8952 /// `predict_i4_mode` with the MACROBLOCK-level availability already
8953 /// resolved by the caller (`top_ok` / `left_ok`).
8954 ///
8955 /// Twelve of a macroblock's sixteen blocks have BOTH neighbours inside the
8956 /// same macroblock, so their slice test is a foregone conclusion - yet the
8957 /// general form recomputed two `nbr_in_slice` products and two
8958 /// `intra_nbr_ok` tests for every one of them, ~1M times on all-intra
8959 /// content.
8960 ///
8961 /// `constrained_intra` is excluded deliberately: there the general form
8962 /// tests THIS macroblock's own `inter_y` cells for interior blocks, which
8963 /// the macroblock-level flags do not model, so that case defers unchanged.
8964 #[inline]
8965 fn predict_i4_mode_fast(
8966 &self,
8967 bx: usize,
8968 by: usize,
8969 lbx: usize,
8970 lby: usize,
8971 top_ok: bool,
8972 left_ok: bool,
8973 ) -> u8 {
8974 if self.constrained_intra {
8975 return self.predict_i4_mode(bx, by);
8976 }
8977 if !((lbx > 0 || left_ok) && (lby > 0 || top_ok)) {
8978 return 2;
8979 }
8980 let w4 = self.mb_w * 4;
8981 // Fallible reads: two checks on the same grid at two indexes. `2` is DC,
8982 // which is exactly what every unavailable-neighbour path above already
8983 // returns, so the fallback is the function's own existing semantics.
8984 let l = self.modes_y.get(by * w4 + (bx - 1)).copied().unwrap_or(2);
8985 let t = self.modes_y.get((by - 1) * w4 + bx).copied().unwrap_or(2);
8986 l.min(t)
8987 }
8988
8989 fn predict_i4_mode(&self, bx: usize, by: usize) -> u8 {
8990 if bx == 0 || by == 0 {
8991 return 2;
8992 }
8993 // Left neighbor block (bx-1,by); top neighbor block (bx,by-1). A neighbor
8994 // in another slice — or, under constrained_intra, an inter neighbor — is
8995 // unavailable, forcing the predicted mode to DC.
8996 if !self.nbr_in_slice((bx - 1) / 4, by / 4)
8997 || !self.nbr_in_slice(bx / 4, (by - 1) / 4)
8998 || !self.intra_nbr_ok(bx - 1, by)
8999 || !self.intra_nbr_ok(bx, by - 1)
9000 {
9001 return 2;
9002 }
9003 let w4 = self.mb_w * 4;
9004 // Fallible reads: two checks on the same grid at two indexes. `2` is DC,
9005 // which is exactly what every unavailable-neighbour path above already
9006 // returns, so the fallback is the function's own existing semantics.
9007 let l = self.modes_y.get(by * w4 + (bx - 1)).copied().unwrap_or(2);
9008 let t = self.modes_y.get((by - 1) * w4 + bx).copied().unwrap_or(2);
9009 l.min(t)
9010 }
9011
9012 /// Gathers 4×4 luma intra neighbors at pixel `(px, py)` from `rec_y`.
9013 fn gather_i4(
9014 &self,
9015 px: usize,
9016 py: usize,
9017 avail_top: bool,
9018 avail_left: bool,
9019 bx: usize,
9020 by: usize,
9021 ) -> ([u8; 8], [u8; 4], u8) {
9022 let (cw, w4) = (self.cw, self.mb_w * 4);
9023 let mut top = [0u8; 8];
9024 let mut left = [0u8; 4];
9025 let mut corner = 0;
9026 let (lbx, lby) = (bx & 3, by & 3);
9027 if avail_top {
9028 // Row-slice loads: the bak-vs-rec source branch runs once per row
9029 // segment instead of once per PIXEL (top_y_px paid it 8 times).
9030 top[..4].copy_from_slice(self.top_y_row(py, px, 4));
9031 // ROUTED BY POSITION (routing round): below the macroblock's top row the
9032 // top-right neighbour is inside this macroblock (or the undecoded right
9033 // neighbour), so its availability is the z-order constant I4_TR_IN_MB --
9034 // no grid load, no slice test, no constrained-intra call.
9035 let tr_avail = if lby > 0 {
9036 lbx < 3 && (I4_TR_IN_MB >> I4_Z_OF_XY[lby][lbx]) & 1 == 1
9037 } else {
9038 bx + 1 < w4
9039 && self
9040 .coded_y
9041 .get((by - 1) * w4 + (bx + 1))
9042 .copied()
9043 .unwrap_or(false)
9044 && self.nbr_in_slice((bx + 1) / 4, (by - 1) / 4)
9045 && self.intra_nbr_ok(bx + 1, by - 1)
9046 };
9047 if tr_avail {
9048 top[4..8].copy_from_slice(self.top_y_row(py, px + 4, 4));
9049 } else {
9050 let t3 = top[3];
9051 top[4..8].fill(t3);
9052 }
9053 }
9054 if avail_left {
9055 // STRIDED WALK. The span form this replaces claimed `i * cw <= 3 * cw`
9056 // was provable against the span's length; it is not — the same shape
9057 // left five live checks on `recon_i16_luma`'s sixteen-sample column.
9058 // `step_by` does the stride with no index, and `zip` bounds the count.
9059 let base = py * cw + px - 1;
9060 for (s, &v) in left.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
9061 *s = v;
9062 }
9063 }
9064 // The above-left corner has its own availability (block D); under
9065 // constrained_intra it is gone if that block is inter.
9066 // Interior corner is this macroblock's own (intra) sample: no constrained-intra call.
9067 if avail_top && avail_left && ((lbx > 0 && lby > 0) || self.intra_nbr_ok(bx - 1, by - 1)) {
9068 corner = self.top_y_px(py, px - 1);
9069 }
9070 (top, left, corner)
9071 }
9072
9073 /// Reconstructs an `I_PCM` macroblock: byte-aligned raw 8-bit samples, no
9074 /// prediction/transform/quant (spec §7.3.5, §8.3.5).
9075 fn decode_ipcm(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
9076 r.align_to_byte()?;
9077 // ROW SLICES throughout. A PCM macroblock wrote 384 samples one at a
9078 // time, each a separately bounds-checked index into a whole plane, and
9079 // then 104 more scattered context stores below. Every extent here is a
9080 // literal (16, 8, 4, 2), so one slice per row makes the inner index
9081 // provable and the stores become a straight walk.
9082 let (lx, ly) = (mb_x * 16, mb_y * 16);
9083 for dy in 0..16 {
9084 let row = &mut self.rec_y[(ly + dy) * self.cw + lx..][..16];
9085 for px in row.iter_mut() {
9086 *px = r.read_bits(8)? as u8;
9087 }
9088 }
9089 let (cx, cy) = (mb_x * 8, mb_y * 8);
9090 let ccw = self.ccw;
9091 for plane in [&mut self.rec_u, &mut self.rec_v] {
9092 for dy in 0..8 {
9093 let row = &mut plane[(cy + dy) * ccw + cx..][..8];
9094 for px in row.iter_mut() {
9095 *px = r.read_bits(8)? as u8;
9096 }
9097 }
9098 }
9099 // Neighbor context: an I_PCM block contributes TotalCoeff = 16, counts as
9100 // intra with DC mode for prediction, and has no motion (§9.2.1, §8.3.1.2.2).
9101 let (w4, w2) = (self.mb_w * 4, self.mb_w * 2);
9102 // The scan order is irrelevant when every written value is a constant:
9103 // the sixteen `LUMA_4X4_SCAN_XY` positions are exactly the macroblock's
9104 // 4x4 rectangle, so four row fills per array cover them.
9105 for ry in 0..4 {
9106 let base = (mb_y * 4 + ry) * w4 + mb_x * 4;
9107 self.nnz_y[base..][..4].fill(16);
9108 self.modes_y[base..][..4].fill(2);
9109 self.coded_y[base..][..4].fill(true);
9110 self.inter_y[base..][..4].fill(false);
9111 self.ref_idx_y[base..][..4].fill(-1);
9112 self.mv_y[base..][..4].fill((0, 0));
9113 }
9114 for c in 0..2 {
9115 for by in 0..2 {
9116 self.nnz_c[c][(mb_y * 2 + by) * w2 + mb_x * 2..][..2].fill(16);
9117 }
9118 }
9119 Ok(())
9120 }
9121
9122 fn decode_i4x4(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
9123 let w4 = self.mb_w * 4;
9124
9125 // intra4x4 mode signalling
9126 let mut modes = [2u8; 16]; // raster [lby*4+lbx]
9127 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
9128 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
9129 let predicted = self.predict_i4_mode(bx, by);
9130 let actual = if r.read_bit()? {
9131 predicted
9132 } else {
9133 let rem = r.read_bits(3)? as u8;
9134 if rem < predicted {
9135 rem
9136 } else {
9137 rem + 1
9138 }
9139 };
9140 if let Some(p) = self.modes_y.get_mut(by * w4 + bx) {
9141 *p = actual;
9142 }
9143 modes[(lby & 3) * 4 + (lbx & 3)] = actual;
9144 }
9145
9146 let chroma_mode = r.read_ue()? as u8;
9147 let cbp = read_cbp_intra(r)?;
9148 let cbp_luma = cbp & 15;
9149 let cbp_chroma = cbp >> 4;
9150 if cbp != 0 {
9151 self.step_qp(r.read_se()?)?;
9152 }
9153 let qp = self.cur_qp;
9154
9155 // luma residuals + serial reconstruction. Cross-MB neighbors are only
9156 // available when the adjacent macroblock is in this slice (and, under
9157 // constrained_intra_pred, is itself intra-coded).
9158 let top_mb_avail = mb_y > 0
9159 && self.nbr_in_slice(mb_x, mb_y - 1)
9160 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9161 let left_mb_avail = mb_x > 0
9162 && self.nbr_in_slice(mb_x - 1, mb_y)
9163 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9164 self.nnz_cache_load(mb_x, mb_y);
9165 let mut nnz_raster = [0u8; 16];
9166 // PARSE all sixteen blocks first (nC prediction needs only the counts), so
9167 // the residual transforms run as ONE batched IDCT before the serial
9168 // predict+add walk. Parse and recon were interleaved per block before.
9169 let mut scans = [[0i32; 16]; 16];
9170 let mut totals = [0u8; 16];
9171 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
9172 let total = if cbp_luma & (1 << (blk / 4)) != 0 {
9173 let nc = self.nc_pred(lbx, lby);
9174 decode_residual_block_into::<16, 16>(r, nc, &mut scans[blk & 15])?
9175 } else {
9176 0
9177 };
9178 self.nnz_cache_set(lbx, lby, total);
9179 nnz_raster[(lby & 3) * 4 + (lbx & 3)] = total;
9180 totals[blk & 15] = total;
9181 }
9182 let kinds = self.i4_prepare(&scans, &totals, qp);
9183 let dq0 = self.dq_const(qp, 0);
9184 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
9185 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
9186 let avail_top = lby > 0 || top_mb_avail;
9187 let avail_left = lbx > 0 || left_mb_avail;
9188 self.recon_i4_block_res(
9189 bx,
9190 by,
9191 modes[(lby & 3) * 4 + (lbx & 3)],
9192 avail_top,
9193 avail_left,
9194 kinds[blk & 15],
9195 &scans,
9196 &dq0,
9197 );
9198 }
9199 for ry in 0..4usize {
9200 self.coded_y[(mb_y * 4 + ry) * w4 + mb_x * 4..][..4].fill(true);
9201 }
9202 // Deferred: `recon_i4_block` gathers from `coded_y` / `rec_y`, never
9203 // from `nnz_y`, so one contiguous copy per row replaces sixteen stores.
9204 for by in 0..4usize {
9205 let a = (mb_y * 4 + by) * w4 + mb_x * 4;
9206 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
9207 }
9208
9209 self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
9210 }
9211
9212 /// Decodes an `I_8x8` macroblock (High profile): four 8×8 luma blocks, each
9213 /// with its own intra mode, 8×8 transform residual (CAVLC = four interleaved
9214 /// 4×4 blocks), and 8×8 intra prediction.
9215 fn decode_i8x8(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
9216 let w4 = self.mb_w * 4;
9217 if let Some(p) = self.mb_t8x8.get_mut(mb_y * self.mb_w + mb_x) {
9218 *p = true;
9219 }
9220 self.any_t8 = true;
9221
9222 // intra8x8 mode signalling — one mode per 8×8 block (raster 0..3),
9223 // stored into all four of its 4×4 cells so neighbors can read it.
9224 let mut modes8 = [2u8; 4];
9225 for (b8, mode) in modes8.iter_mut().enumerate() {
9226 let (b8x, b8y) = (b8 % 2, b8 / 2);
9227 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
9228 let predicted = self.predict_i4_mode(bx, by);
9229 let actual = if r.read_bit()? {
9230 predicted
9231 } else {
9232 let rem = r.read_bits(3)? as u8;
9233 if rem < predicted {
9234 rem
9235 } else {
9236 rem + 1
9237 }
9238 };
9239 *mode = actual;
9240 for sy in 0..2 {
9241 // Row fill: the 2x2 cell block is two contiguous PAIRS.
9242 self.modes_y[(by + sy) * w4 + bx..][..2].fill(actual);
9243 }
9244 }
9245
9246 let chroma_mode = r.read_ue()? as u8;
9247 let cbp = read_cbp_intra(r)?;
9248 let cbp_luma = cbp & 15;
9249 let cbp_chroma = cbp >> 4;
9250 if cbp != 0 {
9251 self.step_qp(r.read_se()?)?;
9252 }
9253 let qp = self.cur_qp;
9254
9255 let top_mb_avail = mb_y > 0
9256 && self.nbr_in_slice(mb_x, mb_y - 1)
9257 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9258 let left_mb_avail = mb_x > 0
9259 && self.nbr_in_slice(mb_x - 1, mb_y)
9260 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9261 self.nnz_cache_load(mb_x, mb_y);
9262
9263 let mut nnz_raster = [0u8; 16];
9264 for b8 in 0..4 {
9265 let (b8x, b8y) = (b8 % 2, b8 / 2);
9266 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
9267
9268 // residual: 8×8 CAVLC = four 4×4 sub-blocks, coeff k of sub-block s
9269 // mapping to 8×8 scan position 4·k + s (spec §7.3.5.3.2).
9270 let mut scan8 = [0i32; 64];
9271 let coded8 = cbp_luma & (1 << b8) != 0;
9272 if coded8 {
9273 for sub in 0..4 {
9274 let (sx, sy) = (sub % 2, sub / 2);
9275 let (cx, cy) = (b8x * 2 + sx, b8y * 2 + sy);
9276 let nc = self.nc_pred(cx, cy);
9277 let mut blk = [0i32; 16];
9278 let total = decode_residual_block_into::<16, 16>(r, nc, &mut blk)?;
9279 self.nnz_cache_set(cx, cy, total);
9280 // Deferred to one row copy per MB row after the loop (`recon_i8_block`
9281 // gathers from `coded_y` / `rec_y`, never `nnz_y`) -- was a checked store per sub.
9282 nnz_raster[(cy & 3) * 4 + (cx & 3)] = total;
9283 for k in 0..16 {
9284 scan8[4 * k + sub] = blk[k];
9285 }
9286 }
9287 } else {
9288 for sub in 0..4 {
9289 let (sx, sy) = (sub % 2, sub / 2);
9290 self.nnz_cache_set(b8x * 2 + sx, b8y * 2 + sy, 0);
9291 // `nnz_raster` is already zero here.
9292 }
9293 }
9294
9295 let avail_top = b8y > 0 || top_mb_avail;
9296 let avail_left = b8x > 0 || left_mb_avail;
9297 self.recon_i8_block(
9298 bx,
9299 by,
9300 modes8[b8],
9301 avail_top,
9302 avail_left,
9303 coded8.then_some(&scan8),
9304 qp,
9305 );
9306 for sy in 0..2 {
9307 // Row fill: the 2x2 cell block is two contiguous PAIRS.
9308 self.coded_y[(by + sy) * w4 + bx..][..2].fill(true);
9309 }
9310 }
9311
9312 // ONE contiguous copy per macroblock row (see the I4x4 arm).
9313 for ry in 0..4usize {
9314 let a = (mb_y * 4 + ry) * w4 + mb_x * 4;
9315 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[ry * 4..ry * 4 + 4]);
9316 }
9317
9318 self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
9319 }
9320
9321 /// Dequantizes + inverse-transforms an 8×8 luma block, applying the scaling
9322 /// matrix `list` (0 = intra, 1 = inter) or flat weights.
9323 fn inv_quant8(&self, raster: &[i32; 64], qp: u8, list: usize) -> [i32; 64] {
9324 // Prebuilt per-(qp, list) constants: the flat table at zero cost, or one
9325 // 64-entry build per macroblock for scaling lists (was 64 x two lookups +
9326 // a multiply per coefficient on every block).
9327 match &self.scaling8 {
9328 Some(s) => inverse_quant_8x8_dq(raster, &Dequant8Qp::build(qp, &s[list])),
9329 None => inverse_quant_8x8_dq(raster, &DQ8_FLAT[(qp as usize).min(51)]),
9330 }
9331 }
9332
9333 /// Gathers the 8×8 luma intra reference samples at pixel `(px, py)`: the 16
9334 /// top samples (8..15 substituted from the last when no top-right), 8 left
9335 /// samples, the above-left corner, and whether the corner is available.
9336 #[allow(clippy::too_many_arguments)]
9337 fn gather_i8(
9338 &self,
9339 px: usize,
9340 py: usize,
9341 avail_top: bool,
9342 avail_left: bool,
9343 bx: usize,
9344 by: usize,
9345 ) -> ([u8; 16], [u8; 8], u8, bool) {
9346 let (cw, w4) = (self.cw, self.mb_w * 4);
9347 let mut top = [0u8; 16];
9348 let mut left = [0u8; 8];
9349 let mut corner = 0;
9350 if avail_top {
9351 // Row-slice loads: one source branch per segment, not per pixel.
9352 top[..8].copy_from_slice(self.top_y_row(py, px, 8));
9353 let tr_avail = bx + 2 < w4
9354 && self
9355 .coded_y
9356 .get((by - 1) * w4 + (bx + 2))
9357 .copied()
9358 .unwrap_or(false)
9359 && self.nbr_in_slice((bx + 2) / 4, (by - 1) / 4)
9360 && self.intra_nbr_ok(bx + 2, by - 1);
9361 if tr_avail {
9362 top[8..16].copy_from_slice(self.top_y_row(py, px + 8, 8));
9363 } else {
9364 let t7 = top[7];
9365 top[8..16].fill(t7);
9366 }
9367 }
9368 if avail_left {
9369 // STRIDED WALK, NOT STRIDED INDEXING. A span plus `col[i * cw]` still
9370 // checks, because `i * cw <= 7 * cw` is not something LLVM derives
9371 // against the span's own length; `step_by` on the iterator does the
9372 // stride with no index at all, and `zip` bounds the count.
9373 let base = py * cw + px - 1;
9374 for (s, &v) in left.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
9375 *s = v;
9376 }
9377 }
9378 let avail_corner = avail_top && avail_left && self.intra_nbr_ok(bx - 1, by - 1);
9379 if avail_corner {
9380 corner = self.top_y_px(py, px - 1);
9381 }
9382 (top, left, corner, avail_corner)
9383 }
9384
9385 fn decode_i16(
9386 &mut self,
9387 r: &mut BitReader,
9388 mb_x: usize,
9389 mb_y: usize,
9390 mt: u32,
9391 ) -> Result<(), MbError> {
9392 let pred_mode = I16Mode::from_id(mt % 4);
9393 let cbp_chroma = (mt % 12) / 4;
9394 let cbp_luma_15 = mt / 12 == 1;
9395 let chroma_mode = r.read_ue()? as u8;
9396 self.step_qp(r.read_se()?)?;
9397 let qp = self.cur_qp;
9398 let w4 = self.mb_w * 4;
9399
9400 // luma DC
9401 self.nnz_cache_load(mb_x, mb_y);
9402 let nc_dc = self.nc_pred(0, 0);
9403 let mut dc_scan = [0i32; 16];
9404 decode_residual_block_into::<16, 16>(r, nc_dc, &mut dc_scan)?;
9405 let recon_dc =
9406 inverse_quant_luma_dc_scan(&dc_scan, qp, self.scaling.as_ref().map(|s| s[0][0]));
9407
9408 // luma AC (nnz set for all 16 blocks: 0 when DC-only, matching the encoder)
9409 let mut nnz_raster = [0u8; 16];
9410 let mut q_blocks = [[0i32; 16]; 16];
9411 for &(bx, by) in &LUMA_4X4_SCAN_XY {
9412 let total = if cbp_luma_15 {
9413 let nc = self.nc_pred(bx, by);
9414 let mut ac = [0i32; 16];
9415 let t = decode_residual_block_into::<15, 16>(r, nc, &mut ac)?;
9416 // Zero-skip: an empty AC block leaves the fresh-zero raster
9417 // block untouched (un-scanning 16 zeros wrote zeros on zeros).
9418 if t != 0 {
9419 q_blocks[(by & 3) * 4 + (bx & 3)] = ac; // SCAN order (fused kernel)
9420 }
9421 t
9422 } else {
9423 0
9424 };
9425 self.nnz_cache_set(bx, by, total);
9426 nnz_raster[(by & 3) * 4 + (bx & 3)] = total;
9427 }
9428 let coded16 = nnz_raster
9429 .iter()
9430 .enumerate()
9431 .fold(0u16, |m, (i, &n)| m | (((n != 0) as u16) << i));
9432 // ONE contiguous copy per row (nothing reads `nnz_y` for this
9433 // macroblock in between - `nc_pred` predicts from `nnz_cache`).
9434 for by in 0..4usize {
9435 let a = (mb_y * 4 + by) * w4 + mb_x * 4;
9436 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
9437 }
9438
9439 // prediction + reconstruction
9440 let avail_top = mb_y > 0
9441 && self.nbr_in_slice(mb_x, mb_y - 1)
9442 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9443 let avail_left = mb_x > 0
9444 && self.nbr_in_slice(mb_x - 1, mb_y)
9445 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9446 self.recon_i16_luma(
9447 mb_x,
9448 mb_y,
9449 pred_mode,
9450 avail_top,
9451 avail_left,
9452 Some(&q_blocks),
9453 coded16,
9454 &recon_dc,
9455 qp,
9456 );
9457 // I_16x16 blocks are treated as DC for neighbor mode prediction.
9458 for lby in 0..4usize {
9459 let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
9460 self.modes_y[a..a + 4].fill(2);
9461 }
9462
9463 self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
9464 }
9465
9466 /// Reads and reconstructs the chroma residual (shared by both luma types).
9467 fn decode_chroma(
9468 &mut self,
9469 r: &mut BitReader,
9470 mb_x: usize,
9471 mb_y: usize,
9472 cbp_chroma: u32,
9473 chroma_mode: u8,
9474 ) -> Result<(), MbError> {
9475 let qpc = self.chroma_qp_for(self.cur_qp);
9476 let avail_top = mb_y > 0
9477 && self.nbr_in_slice(mb_x, mb_y - 1)
9478 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9479 let avail_left = mb_x > 0
9480 && self.nbr_in_slice(mb_x - 1, mb_y)
9481 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9482
9483 let mut c_recon_dc = [[0i32; 4]; 2];
9484 if cbp_chroma != 0 {
9485 for (c, slot) in c_recon_dc.iter_mut().enumerate() {
9486 let mut dc = [0i32; 4];
9487 decode_residual_block_into::<4, 4>(r, -1, &mut dc)?;
9488 *slot = self.dequant_chroma_dc(&dc, qpc, 1 + c);
9489 }
9490 }
9491 let mut c_q_blocks = [[[0i32; 16]; 4]; 2];
9492 let mut ccoded = [0u8; 2];
9493 if cbp_chroma == 2 {
9494 self.chroma_cache_load(mb_x, mb_y);
9495 let w2 = self.mb_w * 2;
9496 for c in 0..2 {
9497 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
9498 let nc = self.chroma_nc_pred(c, bx, by);
9499 let mut ac = [0i32; 16];
9500 let total = decode_residual_block_into::<15, 16>(r, nc, &mut ac)?;
9501 self.chroma_nnz_cache_set(c, bx, by, total);
9502 if let Some(n) =
9503 self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2 + (mb_x * 2 + bx))
9504 {
9505 *n = total;
9506 }
9507 // Zero-skip: empty AC leaves the fresh-zero raster block.
9508 if total != 0 {
9509 ccoded[c & 1] |= 1u8 << ((by * 2 + bx) & 3);
9510 // WIN: c < 2, by < 2, bx < 2, so both indexes are in range and
9511 // the masks PROVE it -- the ccoded[c & 1] |= 1 << ((by*2+bx) & 3)
9512 // on the line above already uses exactly these. This was the last
9513 // panic_bounds_check on the decoder hot path.
9514 c_q_blocks[c & 1][(by * 2 + bx) & 3] = ac; // SCAN order (fused kernel)
9515 }
9516 }
9517 }
9518 }
9519 self.recon_chroma_blocks(
9520 mb_x,
9521 mb_y,
9522 chroma_mode,
9523 avail_top,
9524 avail_left,
9525 &c_q_blocks,
9526 ccoded,
9527 &c_recon_dc,
9528 qpc,
9529 );
9530 Ok(())
9531 }
9532
9533 /// Applies the in-loop deblocking filter to the reconstructed frame, with
9534 /// the slice's `FilterOffsetA`/`FilterOffsetB` (each = the coded `*_div2`
9535 /// value × 2).
9536 /// Per-frame per-MB dump for conformance bisection, keyed on `RH264_DUMP_MB`.
9537 /// Prints one char per macroblock: `i` = intra, otherwise the List-0 reference
9538 /// index of the MB's top-left 4x4 block. Directly comparable with ffmpeg's
9539 /// `-debug mb_type` map, which is the only per-MB ground truth we can get out
9540 /// of the reference decoder.
9541 fn dump_mb_map(&self) {
9542 if !dump_mb_on() {
9543 return;
9544 }
9545 let w4 = self.mb_w * 4;
9546 let mut hist = [0usize; 4];
9547 eprintln!("--- frame poc {} ---", self.cur_poc);
9548 for mb_y in 0..self.mb_h {
9549 let mut row = String::new();
9550 for mb_x in 0..self.mb_w {
9551 let b = (mb_y * 4) * w4 + mb_x * 4;
9552 let Some(&r) = self.ref_idx_y.get(b) else {
9553 continue;
9554 };
9555 if r < 0 {
9556 row.push('i');
9557 } else {
9558 if (r as usize) < 4 {
9559 hist[r as usize] += 1;
9560 }
9561 row.push((b'0' + (r as u8).min(9)) as char);
9562 }
9563 }
9564 eprintln!("{row}");
9565 }
9566 eprintln!(
9567 "ref histogram: {hist:?} num_ref_active={} refs.len()={} OUT-OF-RANGE={}",
9568 self.num_ref_active,
9569 self.refs.len(),
9570 hist.iter().skip(self.refs.len()).sum::<usize>()
9571 );
9572 let list: Vec<String> = self
9573 .refs
9574 .iter()
9575 .enumerate()
9576 .map(|(i, f)| {
9577 // A synthesized frame_num-gap frame is uniform grey with w4 == 0;
9578 // flag it, because it silently displaces real pictures in the list.
9579 let synth = if f.w4 == 0 { " SYNTH-GREY" } else { "" };
9580 format!("[{i}] poc={} fn={}{synth}", f.poc, f.frame_num)
9581 })
9582 .collect();
9583 eprintln!(" RefPicList0: {}", list.join(" "));
9584 }
9585
9586 pub fn deblock(&mut self, offset_a: i32, offset_b: i32) {
9587 self.span_flush(); // deferred grid spans feed bS derivation below
9588 self.edc_flush(); // backstop: no pixel job may survive to filtering
9589 self.dump_mb_map();
9590 // ROW MODE: finish any rows not derived during decode (mid-row slice
9591 // ends, error paths) FIRST, while `self` is still mutably borrowable.
9592 if rowdb_on() {
9593 while self.bs_rows < self.mb_h {
9594 let r = self.bs_rows;
9595 self.derive_bs_row(r);
9596 self.bs_rows += 1;
9597 }
9598 }
9599 // Deblock boundary strength uses the *transform block's* coded status. For
9600 // an 8×8-transform macroblock the unit is the whole 8×8, so every 4×4 cell
9601 // shares the 8×8's coefficient presence (OR of its four sub-block counts)
9602 // — distinct from the per-sub-block `nnz_y` used for the CAVLC nC context.
9603 // Only differs from `nnz_y` when some MB uses the 8×8 transform (High
9604 // profile). On Baseline (no 8×8) it's identical — skip the clone + rewrite.
9605 // Row-interleave already derived bS into `bs_frame`; the filter then
9606 // reads only `bs`+`t8x8`+qp — do not clone nnz or rebuild POC maps.
9607 let rowdb = rowdb_on();
9608 let nnz_db_storage;
9609 let nnz_db: &[u8] = if rowdb {
9610 &[]
9611 } else if self.mb_t8x8.iter().any(|&t| t) {
9612 let mut n = self.nnz_y.clone();
9613 let w4 = self.mb_w * 4;
9614 for mb_y in 0..self.mb_h {
9615 for mb_x in 0..self.mb_w {
9616 if !self
9617 .mb_t8x8
9618 .get(mb_y * self.mb_w + mb_x)
9619 .copied()
9620 .unwrap_or(false)
9621 {
9622 continue;
9623 }
9624 for b8 in 0..4 {
9625 let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
9626 let any = (0..2).any(|sy| {
9627 self.nnz_y[(by + sy) * w4 + bx..][..2]
9628 .iter()
9629 .any(|&v| v > 0)
9630 });
9631 for sy in 0..2 {
9632 // Row fill: the 2x2 cell block is two contiguous PAIRS.
9633 n[(by + sy) * w4 + bx..][..2].fill(u8::from(any));
9634 }
9635 }
9636 }
9637 }
9638 nnz_db_storage = n;
9639 &nnz_db_storage
9640 } else {
9641 &self.nnz_y
9642 };
9643 let mut info = rusty_h264_common::deblock::BlockInfo {
9644 inter: if rowdb { &[] } else { &self.inter_y },
9645 nnz: nnz_db,
9646 mv: if rowdb { &[] } else { &self.mv_y },
9647 ref_id: if rowdb { &[] } else { &self.ref_idx_y },
9648 mv1: if rowdb { &[] } else { &self.mv1 },
9649 ref_id1: if rowdb || self.ref_poc1.is_empty() {
9650 &[]
9651 } else {
9652 &self.ref_idx1
9653 },
9654 w4: self.mb_w * 4,
9655 t8x8: &self.mb_t8x8,
9656 bs: &[],
9657 poc0: if rowdb { &[] } else { &self.ref_poc0 },
9658 poc1: if rowdb { &[] } else { &self.ref_poc1 },
9659 kind: &self.mb_kind,
9660 };
9661 // ROW MODE (R2): rows were derived during decode; the remainder was
9662 // finished above (before `info` borrowed the grids). Fallback: the
9663 // Part 16/17 picture-end precompute; `RS_H264_BS_PRE=0` further falls
9664 // back to the pack-then-derive-in-loop pipeline.
9665 let bs_store;
9666 if rowdb {
9667 bs_store = core::mem::take(&mut self.bs_frame);
9668 info.bs = &bs_store;
9669 } else if bs_pre_on() {
9670 let mut buf = Vec::new();
9671 rusty_h264_common::deblock::precompute_bs_frame(&info, self.mb_w, self.mb_h, &mut buf);
9672 bs_store = buf;
9673 info.bs = &bs_store;
9674 } else {
9675 bs_store = Vec::new();
9676 }
9677 let first_row = if rowdb { self.flt_rows } else { 0 };
9678 rusty_h264_common::deblock::filter_frame_rows_pre(
9679 &mut self.rec_y,
9680 &mut self.rec_u,
9681 &mut self.rec_v,
9682 self.mb_w,
9683 self.mb_h,
9684 first_row..self.mb_h,
9685 &self.mb_qp,
9686 self.chroma_qp_offset,
9687 offset_a,
9688 offset_b,
9689 &info,
9690 );
9691 drop(info);
9692 if rowdb {
9693 self.bs_frame = bs_store;
9694 }
9695 }
9696
9697 /// Crops the reconstructed coded-size planes to the display window.
9698 /// `into_frame`, additionally handing the per-picture grids back for reuse by
9699 /// the next picture. See `GridPool` for why this is worth doing.
9700 pub fn into_frame_recycle(mut self, crop_r: usize, crop_b: usize) -> (YuvFrame, GridPool) {
9701 let [c0, c1] = core::mem::take(&mut self.nnz_c);
9702 let pool = GridPool {
9703 job_pool: core::mem::take(&mut self.edc_job_pool),
9704 nores_pool: core::mem::take(&mut self.edc_nores_pool),
9705 bits_per_mb: self.bits_per_mb,
9706 mb_qp: core::mem::take(&mut self.mb_qp),
9707 bs_frame: core::mem::take(&mut self.bs_frame),
9708 pk_prev: core::mem::take(&mut self.pk_prev),
9709 pk_cur: core::mem::take(&mut self.pk_cur),
9710 nnz_dbr: core::mem::take(&mut self.nnz_dbr),
9711 bak_y: core::mem::take(&mut self.bak_y),
9712 bak_u: core::mem::take(&mut self.bak_u),
9713 bak_v: core::mem::take(&mut self.bak_v),
9714 nnz_y: core::mem::take(&mut self.nnz_y),
9715 nnz_c0: c0,
9716 nnz_c1: c1,
9717 modes_y: core::mem::take(&mut self.modes_y),
9718 coded_y: core::mem::take(&mut self.coded_y),
9719 mv_y: core::mem::take(&mut self.mv_y),
9720 inter_y: core::mem::take(&mut self.inter_y),
9721 ref_idx_y: core::mem::take(&mut self.ref_idx_y),
9722 mv1: core::mem::take(&mut self.mv1),
9723 ref_idx1: core::mem::take(&mut self.ref_idx1),
9724 mb_t8x8: core::mem::take(&mut self.mb_t8x8),
9725 mb_kind: core::mem::take(&mut self.mb_kind),
9726 bzero: core::mem::take(&mut self.bzero),
9727 sc_cat: core::mem::take(&mut self.sc_cat),
9728 sc_cbp: core::mem::take(&mut self.sc_cbp),
9729 sc_cmode: core::mem::take(&mut self.sc_cmode),
9730 sc_nzc: core::mem::take(&mut self.sc_nzc),
9731 sc_cbfdc: core::mem::take(&mut self.sc_cbfdc),
9732 sc_skip: core::mem::take(&mut self.sc_skip),
9733 sc_ref: core::mem::take(&mut self.sc_ref),
9734 sc_mvd: core::mem::take(&mut self.sc_mvd),
9735 sc_ref1: core::mem::take(&mut self.sc_ref1),
9736 sc_mvd1: core::mem::take(&mut self.sc_mvd1),
9737 sc_direct: core::mem::take(&mut self.sc_direct),
9738 ref_poc0: core::mem::take(&mut self.ref_poc0),
9739 ref_poc1: core::mem::take(&mut self.ref_poc1),
9740 };
9741 (self.into_frame(crop_r, crop_b), pool)
9742 }
9743
9744 pub fn into_frame(self, crop_r: usize, crop_b: usize) -> YuvFrame {
9745 // No cropping (the common case): the reconstruction planes ARE the output —
9746 // move them out instead of allocating + copying three full planes per frame.
9747 if crop_r == 0 && crop_b == 0 {
9748 return YuvFrame {
9749 width: self.cw,
9750 height: self.ch,
9751 y: self.rec_y,
9752 u: self.rec_u,
9753 v: self.rec_v,
9754 };
9755 }
9756 let dw = self.cw - 2 * crop_r;
9757 let dh = self.ch - 2 * crop_b;
9758 let mut y = vec![0u8; dw * dh];
9759 for row in 0..dh {
9760 y[row * dw..row * dw + dw]
9761 .copy_from_slice(&self.rec_y[row * self.cw..row * self.cw + dw]);
9762 }
9763 let (cdw, cdh) = (dw / 2, dh / 2);
9764 let mut u = vec![0u8; cdw * cdh];
9765 let mut v = vec![0u8; cdw * cdh];
9766 for row in 0..cdh {
9767 u[row * cdw..row * cdw + cdw]
9768 .copy_from_slice(&self.rec_u[row * self.ccw..row * self.ccw + cdw]);
9769 v[row * cdw..row * cdw + cdw]
9770 .copy_from_slice(&self.rec_v[row * self.ccw..row * self.ccw + cdw]);
9771 }
9772 let _ = self.cch;
9773 YuvFrame {
9774 width: dw,
9775 height: dh,
9776 y,
9777 u,
9778 v,
9779 }
9780 }
9781}
9782
9783/// Reads `ref_idx_l0` as `te(v)` with range `num_ref_active - 1`: a single flag
9784/// when exactly two references are active (cMax == 1), else `ue(v)`.
9785// ---- CABAC binarization engine helpers (openh264 cabac_decoder.cpp) ----
9786
9787/// Unary bin (`DecodeUnaryBinCabac`): bin0 at `ctx`; if 1, count bins at `ctx+off`
9788/// (including the terminating 0) until a 0.
9789/// CAVLC `mvd_lX` with a sanity bound. `se(v)` can legally code ±2^31-1, but
9790/// every profile/level caps |MV| far below ±2^17 quarter-pel units; beyond
9791/// that is a corrupt stream, and the unchecked `pmv + mvd` addition would
9792/// overflow i32 (debug panic / release wrap into an absurd vector).
9793fn read_mvd(r: &mut BitReader) -> Result<i32, MbError> {
9794 let v = r.read_se()?;
9795 if v.unsigned_abs() > (1 << 17) {
9796 return Err(MbError::Truncated);
9797 }
9798 Ok(v)
9799}
9800
9801type Eng = crate::cabac::Engine;
9802type Ctx = crate::cabac::Ctx;
9803
9804fn cabac_unary(e: &mut Eng, data: &[u8], ctx: &mut Ctx, c: usize, off: usize) -> u32 {
9805 if e.decode_decision(data, ctx, c) == 0 {
9806 return 0;
9807 }
9808 let mut sym = 0;
9809 loop {
9810 let bin = e.decode_decision(data, ctx, c + off);
9811 sym += 1;
9812 // Cap the unary run: no valid H.264 element coded through this helper
9813 // (mb_qp_delta) exceeds a few dozen bins, but on malformed / buffer-exhausted
9814 // input the arithmetic engine keeps yielding 1s (it zero-fills past the end),
9815 // which would loop forever. 512 is far beyond any legal value.
9816 if bin == 0 || sym >= 512 {
9817 break;
9818 }
9819 }
9820 sym
9821}
9822
9823/// k-th order Exp-Golomb in bypass (`DecodeExpBypassCabac`). Out of line (it is the
9824/// rare escape arm of levels and mvds), so it takes the engine BY VALUE and hands
9825/// it back: a `&mut Engine` here would be the one address escape in the hot
9826/// callers and LLVM would then home their engine copy on the stack for EVERY bin.
9827#[inline(never)]
9828fn cabac_exp_bypass(mut e: Eng, data: &[u8], mut count: i32) -> (u32, Eng) {
9829 let mut sym = 0u32;
9830 loop {
9831 let c = e.decode_bypass(data);
9832 if c == 1 {
9833 sym += 1 << count;
9834 count += 1;
9835 }
9836 if c == 0 || count == 16 {
9837 break;
9838 }
9839 }
9840 let mut sym2 = 0u32;
9841 while count > 0 {
9842 count -= 1;
9843 if e.decode_bypass(data) != 0 {
9844 sym2 |= 1 << count;
9845 }
9846 }
9847 (sym + sym2, e)
9848}
9849
9850/// UEG0 coeff-level suffix (`DecodeUEGLevelCabac`): TU prefix at `c` (<=13) then an
9851/// EG0 bypass suffix. INLINED ALWAYS into the four residual-parser instantiations:
9852/// out of line it took `&mut Engine`, which put the engine back in memory for
9853/// every level >= 2 and spilled the caller around the call.
9854#[inline(always)]
9855fn cabac_ueg_level(e: &mut Eng, data: &[u8], ctx: &mut Ctx, c: usize) -> u32 {
9856 if e.decode_decision(data, ctx, c) == 0 {
9857 return 0;
9858 }
9859 let mut code = 0u32;
9860 let mut tmp;
9861 loop {
9862 tmp = e.decode_decision(data, ctx, c);
9863 code += 1;
9864 if tmp == 0 || code == 12 {
9865 break;
9866 }
9867 }
9868 if tmp != 0 {
9869 let (v, e2) = cabac_exp_bypass(*e, data, 0);
9870 *e = e2;
9871 code += v + 1;
9872 }
9873 code
9874}
9875
9876/// `mb_qp_delta` CABAC (`ParseDeltaQpCabac`): ctxIdxOffset 60, ctxInc = (prev delta != 0).
9877pub fn parse_mb_qp_delta_cabac(cab: &mut crate::cabac::Cabac, last_delta_qp: &mut i32) -> i32 {
9878 const O: usize = 60;
9879 let ctx_inc = (*last_delta_qp != 0) as usize;
9880 let mut qp_delta = 0;
9881 let (data, mut e, ctx) = cab.view();
9882 if e.decode_decision(data, ctx, O + ctx_inc) != 0 {
9883 let code = cabac_unary(&mut e, data, ctx, O + 2, 1) + 1;
9884 qp_delta = ((code + 1) >> 1) as i32;
9885 if code & 1 == 0 {
9886 qp_delta = -qp_delta;
9887 }
9888 }
9889 cab.commit(e);
9890 *last_delta_qp = qp_delta;
9891 qp_delta
9892}
9893
9894// Shared CABAC residual glue tables (NZC_CACHE, RES_*) now live in
9895// `cabac_tables` — both coders read the ONE copy.
9896use rusty_h264_common::cabac_tables::{
9897 NZC_CACHE, RES_CBF, RES_MAP, RES_MAXC2, RES_MAXPOS, RES_ONE,
9898};
9899// res-property values (post GetMbResProperty, CABAC): the ctx-table index.
9900const RP_I16_DC: usize = 1;
9901const RP_I16_AC: usize = 2;
9902const RP_LUMA_4X4: usize = 3;
9903const RP_CHROMA_DC: usize = 7; // U (V=8, same offsets)
9904const RP_CHROMA_AC: usize = 9; // U (V=10, same offsets)
9905/// Luma 8×8 (ctxBlockCat 5). Its RES_MAP/RES_CBF entries stay 0: cat 5 does NOT
9906/// share the `105 + off` / `166 + off` context bases the 4×4 categories use — it
9907/// has its own absolute bases (402 sig, 417 last) and its own per-position
9908/// ctxIdxInc maps below. RES_ONE[6] = 199 IS used, because 227 + 199 = 426 and
9909/// 232 + 199 = 431 reproduce the spec's coeff_abs_level_minus1 base exactly, so
9910/// the level loop needs no special case at all.
9911const RP_LUMA_8X8: usize = 6;
9912
9913// SIG8X8 / LAST8X8 moved to `rusty_h264_common::cabac_tables` (R6-1) so the encoder's
9914// ctxBlockCat 5 writer shares the exact spec data this reader is validated against.
9915use rusty_h264_common::cabac_tables::{LAST8X8, SIG8X8};
9916
9917/// One residual block (openh264 `ParseResidualBlockCabac`), generic over the 5 CABAC
9918/// block categories. `rp` selects the context offsets. DC categories (I16 luma DC,
9919/// chroma DC) take the cbf context from the per-MB `cbf_dc` bitmask + neighbour MB DC
9920/// cbf; AC categories from the padded nzc cache. Returns totalCoeffNum.
9921#[allow(clippy::too_many_arguments)]
9922#[inline(always)]
9923fn residual_block_eng<const RP: usize, const N: usize>(
9924 e: &mut Eng,
9925 data: &[u8],
9926 ctx: &mut Ctx,
9927 nzc: &mut [u8; 48],
9928 cbf_dc: &mut u16,
9929 iz: usize,
9930 plane: usize,
9931 is_intra: bool,
9932 ndc: (u16, u16), // resolved neighbour DC cbf words (see `resolve_ndc`)
9933 out: &mut [i32; N], // scan-order coefficients written here (fresh-zero on entry)
9934) -> u32 {
9935 // CONST-GENERIC over the block category (`RP`, one of the RP_* literals every
9936 // call site already passed) and the block length (`N` = 4 / 16 / 64): the
9937 // category tests, the five `RES_*` table reads, the 8x8 map selects and the
9938 // output bound all fold at compile time. Chroma U/V share every table
9939 // entry (RES_*[7] == RES_*[8], RES_*[9] == RES_*[10]); only the cbf_dc bit
9940 // differs, so the plane rides in `plane` (0 for luma categories).
9941 const { assert!(N == 4 || N == 16 || N == 64) };
9942 // The CABAC residual parse IS the entropy stage of the decoder on Main-profile
9943 // streams -- it was invisible (a ~47% residue) until this scope named it.
9944 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Entropy);
9945 // ---- coded_block_flag ----
9946 // ctxBlockCat 5 is the ONLY category with no coded_block_flag: its presence is
9947 // inferred from CodedBlockPatternLuma, so parsing one here would desync.
9948 let is8 = RP == RP_LUMA_8X8;
9949 let is_dc = RP == RP_I16_DC || RP == RP_CHROMA_DC;
9950 let bit = RP + plane;
9951 let (mut na, mut nb) = (is_intra as u8, is_intra as u8);
9952 // `nzc` is [u8; 48] and every `NZC_CACHE` entry lies in [9, 47], so this
9953 // clamp is a semantic no-op that hands LLVM BOTH bounds - which is what
9954 // makes `nzc[scan]`, `nzc[scan - 8]` and `nzc[scan - 1]` below provable
9955 // instead of three bounds checks. Same trick as `parse_mvd_partition`.
9956 let scan = NZC_CACHE[iz.min(23)].clamp(8, 47);
9957 if is_dc {
9958 nb = ((ndc.0 >> bit) & 1) as u8;
9959 na = ((ndc.1 >> bit) & 1) as u8;
9960 } else {
9961 let (nbc, nac) = (nzc[scan - 8], nzc[scan - 1]);
9962 if nbc != 0xff {
9963 nb = (nbc != 0) as u8;
9964 }
9965 if nac != 0xff {
9966 na = (nac != 0) as u8;
9967 }
9968 }
9969 if !is8 {
9970 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntCbf);
9971 let cbf = e.decode_decision(data, ctx, 85 + RES_CBF[RP] + (na + (nb << 1)) as usize);
9972 if cbf == 0 {
9973 if !is_dc {
9974 nzc[scan] = 0;
9975 }
9976 return 0;
9977 }
9978 if is_dc {
9979 *cbf_dc |= 1 << bit;
9980 }
9981 }
9982 // ---- significance map ----
9983 let maxpos = RES_MAXPOS[RP] as usize;
9984 debug_assert!(maxpos < N);
9985 // cat 5 uses its own absolute bases; the 4x4 categories share 105/166 + offset.
9986 let (map, last) = if is8 {
9987 (402, 417)
9988 } else {
9989 let m = RES_MAP[RP];
9990 (105 + m, 166 + m)
9991 };
9992 // SPARSE significance map: record each significant POSITION in `pos[..n]`.
9993 // Bin ORDER is unchanged: levels are decoded at descending significant
9994 // positions, which is exactly `pos[..n]` reversed. `pos` is sized to the
9995 // block (N), not 64: a 4x4 block zeroes 16 bytes, not 64.
9996 //
9997 // CONTRACT with the callers (all 10 sites): `out` is freshly zeroed, so
9998 // writing only the significant entries leaves the same contents a dense
9999 // copy would produce. A reused non-zero `out` would be a correctness bug.
10000 // Significant positions as a BITMASK (bit i = scan position i): one OR per
10001 // significant coefficient instead of a store + count increment into a
10002 // zero-initialised array; the level loop walks the set bits from the top
10003 // (descending positions = the spec order).
10004 let mut sig = 0u64;
10005 let mut last_hit = false;
10006 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntSig);
10007 // 4x4: ctxIdxInc IS the scan position. 8x8: it comes from the folded maps.
10008 // NOTE (4:2:2 landmine): `(i, i)` is correct for every 4:2:0 category
10009 // only because chroma-DC (cat 3) has NumC8x8 == 1 here; spec 9.3.3.1.3
10010 // wants `Min(i / NumC8x8, 2)` for its sig/last ctxIdxInc, which
10011 // diverges the day 4:2:2 (NumC8x8 == 2) is admitted.
10012 for i in 0..maxpos {
10013 // Both maps are [u8; 64] and `i < maxpos <= 63`: `& 63` folds the checks.
10014 let (mi, li) = if is8 {
10015 (
10016 (SIG8X8[i & 63] & 15) as usize,
10017 (LAST8X8[i & 63] & 15) as usize,
10018 )
10019 } else {
10020 (i, i)
10021 };
10022 if e.decode_decision(data, ctx, map + mi) != 0 {
10023 // `n <= maxpos < N` at every write: `& (N - 1)` is the own bound of `pos`.
10024 sig |= 1u64 << i;
10025 if e.decode_decision(data, ctx, last + li) != 0 {
10026 last_hit = true;
10027 break;
10028 }
10029 }
10030 }
10031 if !last_hit {
10032 sig |= 1u64 << maxpos;
10033 }
10034 let coeff_num = sig.count_ones();
10035 // ---- levels ----
10036 let one = 227 + RES_ONE[RP];
10037 let abs = one + 5;
10038 // `usize` with visible `min` bounds: as i32 the casts hid the ranges and every
10039 // `one + c1` / `abs + c2` context index kept its `& 511` (one op per bin).
10040 let maxc2 = RES_MAXC2[RP] as usize;
10041 let (mut c1, mut c2) = (1usize, 0usize);
10042 drop(_sg);
10043 let _lg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntLvl);
10044 let mut m = sig;
10045 while m != 0 {
10046 let i = 63 - m.leading_zeros() as usize;
10047 m &= !(1u64 << i);
10048 let mut level = 1 + e.decode_decision(data, ctx, one + c1) as i32;
10049 if level == 2 {
10050 level += cabac_ueg_level(e, data, ctx, abs + c2) as i32;
10051 c2 = (c2 + 1).min(maxc2);
10052 c1 = 0;
10053 } else if c1 != 0 {
10054 c1 = (c1 + 1).min(4);
10055 }
10056 if e.decode_bypass_sign() != 0 {
10057 level = -level;
10058 }
10059 // `out` is `[i32; N]` and every recorded position is <= maxpos < N, so
10060 // `& (N - 1)` is the own bound of the array -- a proof, not a relocation
10061 // (the runtime-length-slice form this replaces needed `.get_mut`).
10062 out[i & (N - 1)] = level;
10063 }
10064 if is8 {
10065 // One 8x8 covers four consecutive z-order 4x4 cells. Every later
10066 // coded_block_flag ctxIdxInc reads this cache, so all four must carry the
10067 // count -- writing only `scan` would corrupt the contexts of the NEXT
10068 // macroblock.
10069 let cn = coeff_num as u8;
10070 for k in 0..4 {
10071 nzc[NZC_CACHE[(iz + k).min(23)].min(47)] = cn;
10072 }
10073 } else if !is_dc {
10074 nzc[scan] = coeff_num as u8;
10075 }
10076 coeff_num
10077}
10078
10079/// Where a macroblock residual parse writes: plain arrays whose UNCODED blocks
10080/// are stale by contract (every consumer reads a block only when its `nnzs`
10081/// entry is nonzero, or copies the prediction). The parse zeroes exactly the
10082/// blocks it is about to write.
10083struct ResidualOut<'a> {
10084 luma: &'a mut [[i32; 16]; 16],
10085 luma8: &'a mut [[i32; 64]; 4],
10086 cdc: &'a mut [[i32; 4]; 2],
10087 cac: &'a mut [[[i32; 16]; 4]; 2],
10088 /// Per-block totalCoeff, `iz`-indexed; must be zero on entry.
10089 nnzs: &'a mut [u8; 24],
10090}
10091
10092/// The neighbour-record form of a macroblock per-block coefficient counts:
10093/// luma in RASTER order (from the z-order `nnzs`, via G_SCAN4, which is its own
10094/// inverse) and chroma Cb/Cr in the interleaved slot order the readers expect.
10095/// Uncoded blocks are already 0 in `nnzs`, so the 0xff->0 scrub of the old
10096/// cache-based export is gone with the 24 scattered cache reads.
10097/// Explicit-weighted-prediction sample: `((s*w + round) >> denom) + o`, clipped.
10098#[inline(always)]
10099fn weight_sample(sample: u8, w: i32, o: i32, denom: i32) -> u8 {
10100 let v = if denom >= 1 {
10101 ((sample as i32 * w + (1 << (denom - 1))) >> denom) + o
10102 } else {
10103 sample as i32 * w + o
10104 };
10105 v.clamp(0, 255) as u8
10106}
10107
10108/// CONST-WIDTH rows of the weighted-prediction loop (SIMD census 2026-09-05,
10109/// finding #9). The two recon twins carried the IDENTICAL runtime-width loop
10110/// (`for dx in 0..rw` over a masked index) and LLVM vectorised it to 500 packed
10111/// ops in one inlining context and 53 in the other. A partition width is one
10112/// of 16/8/4 (chroma 8/4/2); handing LLVM the trip count makes both contexts
10113/// vectorise the same way. One row slice per row instead of a masked index per
10114/// sample; the sample formula is unchanged.
10115#[inline(always)]
10116fn weight_rows<const W: usize>(
10117 plane: &mut [u8],
10118 stride: usize,
10119 rx: usize,
10120 ry: usize,
10121 rh: usize,
10122 w: i32,
10123 o: i32,
10124 denom: i32,
10125) {
10126 for dy in 0..rh {
10127 let row = &mut plane[(ry + dy) * stride + rx..][..W];
10128 for s in row.iter_mut() {
10129 *s = weight_sample(*s, w, o, denom);
10130 }
10131 }
10132}
10133
10134#[allow(clippy::too_many_arguments)]
10135#[inline]
10136fn weight_block(
10137 plane: &mut [u8],
10138 stride: usize,
10139 rx: usize,
10140 ry: usize,
10141 rw: usize,
10142 rh: usize,
10143 w: i32,
10144 o: i32,
10145 denom: i32,
10146) {
10147 match rw {
10148 16 => weight_rows::<16>(plane, stride, rx, ry, rh, w, o, denom),
10149 8 => weight_rows::<8>(plane, stride, rx, ry, rh, w, o, denom),
10150 4 => weight_rows::<4>(plane, stride, rx, ry, rh, w, o, denom),
10151 2 => weight_rows::<2>(plane, stride, rx, ry, rh, w, o, denom),
10152 _ => {
10153 for dy in 0..rh {
10154 for dx in 0..rw {
10155 let i = (ry + dy) * stride + rx + dx;
10156 if let Some(s) = plane.get_mut(i) {
10157 *s = weight_sample(*s, w, o, denom);
10158 }
10159 }
10160 }
10161 }
10162 }
10163}
10164
10165/// Residual class of an intra 4x4 block (see `FrameDecoder::i4_prepare`).
10166#[derive(Clone, Copy)]
10167enum I4Res {
10168 Zero,
10169 Flat(i32),
10170 Idx(u8),
10171}
10172
10173/// z-order index of the 4x4 block at raster (x, y) -- inverse of `LUMA_4X4_SCAN_XY`.
10174const I4_Z_OF_XY: [[usize; 4]; 4] = {
10175 let mut t = [[0usize; 4]; 4];
10176 let mut i = 0;
10177 while i < 16 {
10178 let (x, y) = LUMA_4X4_SCAN_XY[i];
10179 t[y][x] = i;
10180 i += 1;
10181 }
10182 t
10183};
10184/// Bit i set iff the top-right 4x4 neighbour of z-order block i lies INSIDE the
10185/// macroblock and is already reconstructed when block i is (spec 6.4.11.4 via the
10186/// decode order): the classic i4x4 top-right availability table, here derived from
10187/// the scan table at compile time instead of hand-typed.
10188const I4_TR_IN_MB: u16 = {
10189 let mut m = 0u16;
10190 let mut i = 0;
10191 while i < 16 {
10192 let (x, y) = LUMA_4X4_SCAN_XY[i];
10193 if y > 0 && x < 3 && I4_Z_OF_XY[y - 1][x + 1] < i {
10194 m |= 1 << i;
10195 }
10196 i += 1;
10197 }
10198 m
10199};
10200
10201// SPARSE-vs-DENSE DEQUANT ROUTE (routing round C, 2026-09-05): the DQROUTE census
10202// (scatter = 37 + 6L + 9nnz vs dense-AVX2 ~60; crowd 134.8M -> 80.3M instrs / 60 f)
10203// retired the scan-walking scatter; every coded block now takes the FUSED
10204// scan-order kernel (`reconstruct_4x4_scan_into`: un-scan + dequant + IDCT + add).
10205// The (nnz, L) histogram tap `edcstat::dq_note` stays for the next route question.
10206
10207/// Neighbour DC coded_block_flags as plain bit words: an unavailable neighbour
10208/// contributes the intra default for EVERY category, so the per-block
10209/// `Option` test becomes one shift. Resolved once per macroblock.
10210#[inline(always)]
10211fn resolve_ndc(ndc: (Option<u16>, Option<u16>), is_intra: bool) -> (u16, u16) {
10212 let d = if is_intra { 0xFFFF } else { 0 };
10213 (ndc.0.unwrap_or(d), ndc.1.unwrap_or(d))
10214}
10215
10216/// The WHOLE residual of one non-I16 macroblock (luma 4x4 or 8x8 by cbp bit,
10217/// chroma DC, chroma AC) on ONE engine view. The block body is inlined here,
10218/// so a macroblock costs one call and one engine load/store, where each block
10219/// used to pay a call, eight arguments (five on the stack), an eight-register
10220/// prologue/epilogue and its own view/commit round trip -- ~40 instructions
10221/// per block beside the bins, on 7-24 blocks per coded macroblock.
10222#[allow(clippy::too_many_arguments)]
10223fn parse_mb_residual_cabac<const INTRA: bool>(
10224 cab: &mut crate::cabac::Cabac,
10225 nzc: &mut [u8; 48],
10226 cbf_dc: &mut u16,
10227 ndc: (Option<u16>, Option<u16>),
10228 cbp_luma: u32,
10229 cbp_chroma: u32,
10230 t8: bool,
10231 o: ResidualOut,
10232) {
10233 // INTRA is a const generic: the cbf defaults and the neighbour-word resolve
10234 // fold per instantiation (every call site passes a literal).
10235 let is_intra = INTRA;
10236 let nd = resolve_ndc(ndc, INTRA);
10237 let (data, mut e, ctx) = cab.view();
10238 for id8 in 0..4usize {
10239 if cbp_luma & (1 << id8) != 0 {
10240 if t8 {
10241 // ctxBlockCat 5: one 64-coefficient block per 8x8, no cbf. All
10242 // four nnzs slots carry the 8x8 total (the recon reads one per cell).
10243 o.luma8[id8] = [0i32; 64];
10244 let n = residual_block_eng::<RP_LUMA_8X8, 64>(
10245 &mut e,
10246 data,
10247 ctx,
10248 nzc,
10249 cbf_dc,
10250 id8 * 4,
10251 0,
10252 is_intra,
10253 nd,
10254 &mut o.luma8[id8],
10255 ) as u8;
10256 o.nnzs[id8 * 4..id8 * 4 + 4].fill(n);
10257 } else {
10258 o.luma[id8 * 4..id8 * 4 + 4].fill([0i32; 16]);
10259 for id4 in 0..4usize {
10260 let iz = id8 * 4 + id4;
10261 o.nnzs[iz] = residual_block_eng::<RP_LUMA_4X4, 16>(
10262 &mut e,
10263 data,
10264 ctx,
10265 nzc,
10266 cbf_dc,
10267 iz,
10268 0,
10269 is_intra,
10270 nd,
10271 &mut o.luma[iz],
10272 ) as u8;
10273 }
10274 }
10275 } else {
10276 for k in 0..4 {
10277 nzc[NZC_CACHE[(id8 * 4 + k).min(23)].min(47)] = 0;
10278 }
10279 }
10280 }
10281 if cbp_chroma >= 1 {
10282 *o.cdc = [[0i32; 4]; 2];
10283 for i in 0..2usize {
10284 residual_block_eng::<RP_CHROMA_DC, 4>(
10285 &mut e,
10286 data,
10287 ctx,
10288 nzc,
10289 cbf_dc,
10290 16 + i * 4,
10291 i,
10292 is_intra,
10293 nd,
10294 &mut o.cdc[i],
10295 );
10296 }
10297 }
10298 if cbp_chroma == 2 {
10299 *o.cac = [[[0i32; 16]; 4]; 2];
10300 for i in 0..2usize {
10301 for id4 in 0..4usize {
10302 o.nnzs[16 + i * 4 + id4] = residual_block_eng::<RP_CHROMA_AC, 16>(
10303 &mut e,
10304 data,
10305 ctx,
10306 nzc,
10307 cbf_dc,
10308 16 + i * 4 + id4,
10309 i,
10310 is_intra,
10311 nd,
10312 &mut o.cac[i][id4],
10313 ) as u8;
10314 }
10315 }
10316 }
10317 cab.commit(e);
10318}
10319
10320// ============================================================================
10321// Entropy-decouple E2: the OWNED pixel context that crosses the thread
10322// boundary (docs/entropy-decouple-plan.md). The worker owns the planes, the
10323// backup rows, the DPB Arcs and its own qp/t8/bs grids (fed by Row messages);
10324// the parse thread keeps every syntax grid. The methods below are ports of
10325// the FrameDecoder pixel halves — grid writes removed (parse commits its own
10326// grids), motion carried in the job instead of re-gathered.
10327// ============================================================================
10328
10329/// The committed-value class of a deferred B_Skip span. Continuation
10330/// requires kind EQUALITY: the flush range-fills grids and recons the band
10331/// from these values alone.
10332#[derive(Clone, Copy, PartialEq, Eq)]
10333enum BzKind {
10334 /// ref 0 both lists, (0,0)/(0,0) — band = avg of both padded refs.
10335 ZeroBi,
10336 /// One active list at (0,0): (list, clamped ref index) — band = memcpy.
10337 ZeroUni(u8, u8),
10338 /// Uniform full-pel direct: clamped refs (-1 = inactive) + adjusted MVs,
10339 /// windows PREVALIDATED per MB at push (contiguous tiles ⇒ the union
10340 /// window is valid) — band = offset copy/avg. Chroma requires mv%8==0
10341 /// (also prevalidated).
10342 Fp {
10343 r0: i8,
10344 r1: i8,
10345 m0: (i16, i16),
10346 m1: (i16, i16),
10347 },
10348}
10349
10350/// Messages from the parse thread to the pixel worker.
10351enum EdcMsg {
10352 Job(EdcJob),
10353 /// A ROW's worth of pixel jobs in one message (D10).
10354 ///
10355 /// The seam sent ONE message per macroblock: 208k sends per 60-frame pass
10356 /// against 2,596 rows. The overhead is per-JOB (channel lock, park/unpark)
10357 /// while the prize is proportional to pixel WORK, so the per-job send was
10358 /// dividing the payoff by ~80 for nothing. Batching per row cuts the
10359 /// synchronisation events by that factor and moves not one byte of pixel
10360 /// work off the worker.
10361 ///
10362 /// ORDER IS THE CORRECTNESS CONDITION: the worker must see a row's jobs
10363 /// before that row's `Row` filter message, so the batch is flushed at every
10364 /// row boundary, before `NeedCtx`, and at slice end.
10365 Batch(Vec<EdcJob>),
10366 /// A macroblock row finished parsing: install its qp/t8/bs and filter it.
10367 Row {
10368 r: usize,
10369 bs: Vec<rusty_h264_common::deblock::MbBs>,
10370 qp: Vec<u8>,
10371 t8: Vec<bool>,
10372 },
10373 /// An intra macroblock needs the planes on the parse thread: send the
10374 /// context over and wait for it to come back.
10375 NeedCtx,
10376}
10377
10378pub(crate) struct PixelCtx {
10379 rec_y: Vec<u8>,
10380 rec_u: Vec<u8>,
10381 rec_v: Vec<u8>,
10382 bak_y: Vec<u8>,
10383 bak_u: Vec<u8>,
10384 bak_v: Vec<u8>,
10385 refs: Vec<crate::Ref>,
10386 refs1: Vec<crate::Ref>,
10387 weights: Option<WeightTable>,
10388 /// `weights.list_identity(0)`, cached - mirrors `FrameDecoder::weights_l0id`
10389 /// so the worker twin does not have to walk the table per macroblock.
10390 weights_l0id: bool,
10391 scaling: Option<[[i32; 16]; 6]>,
10392 scaling8: Option<[[i32; 64]; 2]>,
10393 cw: usize,
10394 ccw: usize,
10395 mb_w: usize,
10396 mb_h: usize,
10397 chroma_qp_offset: i32,
10398 flt_rows: usize,
10399 db_ena: bool,
10400 db_oa: i32,
10401 db_ob: i32,
10402 cur_qp: u8,
10403 qp_grid: Vec<u8>,
10404 t8_grid: Vec<bool>,
10405 bs_store: Vec<rusty_h264_common::deblock::MbBs>,
10406 /// Frame-MT Phase B progress Arc (row publish from the EDC worker).
10407 progress: Option<crate::Ref>,
10408}
10409
10410impl PixelCtx {
10411 /// Frame-MT Phase B: copy filtered MB rows into the shared progress Arc.
10412 fn publish_progress_rows(&self) {
10413 let Some(slot) = &self.progress else {
10414 return;
10415 };
10416 if !self.db_ena || self.flt_rows == 0 {
10417 return;
10418 }
10419 publish_filtered_rows_to_slot(
10420 slot,
10421 &self.rec_y,
10422 &self.rec_u,
10423 &self.rec_v,
10424 self.cw,
10425 self.ccw,
10426 self.mb_h * 16,
10427 self.flt_rows,
10428 );
10429 }
10430
10431 fn chroma_qp_for(&self, qp: u8) -> u8 {
10432 rusty_h264_common::predict::chroma_qp(
10433 ((qp as i32 + self.chroma_qp_offset).clamp(0, 51)) as u8,
10434 )
10435 }
10436
10437 /// Per-(qp, list) dequant constants for the fused scan-order kernel (worker twin).
10438 #[inline]
10439 fn dq_const(&self, qp: u8, list: usize) -> DequantQp {
10440 match &self.scaling {
10441 Some(s) => DequantQp::weighted(qp, &s[list]),
10442 None => DQ_FLAT[(qp as usize).min(51)],
10443 }
10444 }
10445
10446 fn dequant_dc4(&self, level: i32, qp: u8, list: usize) -> i32 {
10447 rusty_h264_common::transform::dequantize_dc4(
10448 level,
10449 qp,
10450 self.scaling.as_ref().map(|sc| sc[list][0]),
10451 )
10452 }
10453
10454 fn inv_quant8(&self, raster: &[i32; 64], qp: u8, list: usize) -> [i32; 64] {
10455 // Prebuilt per-(qp, list) constants: the flat table at zero cost, or one
10456 // 64-entry build per macroblock for scaling lists (was 64 x two lookups +
10457 // a multiply per coefficient on every block).
10458 match &self.scaling8 {
10459 Some(s) => inverse_quant_8x8_dq(raster, &Dequant8Qp::build(qp, &s[list])),
10460 None => inverse_quant_8x8_dq(raster, &DQ8_FLAT[(qp as usize).min(51)]),
10461 }
10462 }
10463
10464 fn dequant_chroma_dc(&self, levels: &[i32; 4], qp: u8, list: usize) -> [i32; 4] {
10465 match &self.scaling {
10466 Some(sc) => inverse_quant_chroma_dc_weighted(levels, qp, sc[list][0]),
10467 None => inverse_quant_chroma_dc(levels, qp),
10468 }
10469 }
10470
10471 fn weight_partition(
10472 &self,
10473 pred_y: &mut [u8; 256],
10474 c_pred: &mut [[u8; 64]; 2],
10475 list: usize,
10476 refi: usize,
10477 rx: usize,
10478 ry: usize,
10479 rw: usize,
10480 rh: usize,
10481 ) {
10482 let Some(wt) = &self.weights else { return };
10483 // REFUTED, reverted: row-slicing these loops measured +28% instructions
10484 // on `weight_partition` (the extents are runtime values, so the slice
10485 // bounds cost more than the per-sample checks they replaced). The real
10486 // win for this function was hoisting the identity test to its CALLERS,
10487 // which stands.
10488 // RESOLVE ONCE, APPLY MANY. `apply_luma` re-read `self.luma[list][refi]`
10489 // — an array index, a Vec deref and an element index, two of them bounds
10490 // checked — on EVERY sample, up to 256 luma plus 128 chroma per call.
10491 // The weight is a property of the partition, not of the pixel. (The loop
10492 // SHAPE is untouched: row-slicing it is refuted above.)
10493 // MASKED, not row-sliced. `pred_y` is a fixed `[u8; 256]` and `c_pred` a
10494 // `[[u8; 64]; 2]`, so `& 255` / `& 63` are no-ops that prove the index
10495 // outright — where SLICING these loops cost +28% (the extents are runtime
10496 // values, so the slice bounds outweigh the checks). Same lesson as
10497 // `luma_centre`: the refutation was of one SHAPE, not of the goal.
10498 let (lw, lo) = wt.luma_wo(list, refi);
10499 weight_block(pred_y, 16, rx, ry, rw, rh, lw, lo, wt.luma_log2_denom);
10500 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
10501 for cc in 0..2 {
10502 let (cw, co) = wt.chroma_wo(list, refi, cc);
10503 weight_block(
10504 &mut c_pred[cc & 1],
10505 8,
10506 crx,
10507 cry,
10508 crw,
10509 crh,
10510 cw,
10511 co,
10512 wt.chroma_log2_denom,
10513 );
10514 }
10515 }
10516
10517 fn recon_p_inter(&mut self, j: &PInterJob) {
10518 crate::RefFrame::set_mc_row_need(j.mby, self.mb_h * 16);
10519 // `add_inter_residual` reads `self.cur_qp`; unlike the FrameDecoder copy
10520 // (which interleaves with parsing and must save/restore), every PixelCtx
10521 // job sets it from the job before any reader, so no restore is needed.
10522 self.cur_qp = j.qp;
10523 // ---- Recon: motion-comp (per 4×4 luma / co-located 2×2 chroma using the
10524 // committed grid MV — the 6-tap/bilinear filter is per-output-pixel, so
10525 // per-block MC is bit-identical to per-partition MC) + residual add via the
10526 // SAME reconstruct_4x4 as intra, with the MC output as the prediction.
10527 let mut pred_y = [0u8; 256];
10528 let mut c_pred = [[0u8; 64]; 2];
10529 {
10530 // MC-CALL COALESCING (side-by-side descent, dec target #2): the old
10531 // loop paid 16 mc_luma(4×4) + 32 mc_chroma(2×2) per MB regardless of
10532 // partitioning — 48 calls even for a single-MV 16×16 MB, and the
10533 // per-call glue around 2.4M calls was ~40% of decoding real-world
10534 // (x264) streams. The 6-tap/bilinear filters are per-output-pixel,
10535 // so merging blocks with equal (mv, ref) into one wider MC call is
10536 // BIT-IDENTICAL; the rect ladder mirrors the partition shapes.
10537 let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
10538 let (rh16, cch) = (self.mb_h * 16, self.mb_h * 8);
10539 // E2: the worker owns no syntax grids — the job carries the
10540 // committed per-block motion (filled at parse time).
10541 let gmv = j.gmv;
10542 let mut gref = [0usize; 16];
10543 for k in 0..16 {
10544 gref[k] = (j.gref[k] as usize).min(self.refs.len() - 1);
10545 }
10546 // All blocks of the rect (in 4×4-block units) match its top-left?
10547 let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
10548 let t = y4 * 4 + x4;
10549 (0..h4).all(|dy| {
10550 (0..w4).all(|dx| {
10551 let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
10552 gmv[b] == gmv[t] && gref[b] == gref[t]
10553 })
10554 })
10555 };
10556 let refs = &self.refs;
10557 let (cw, ccw) = (self.cw, self.ccw);
10558 let mc_rect = |x4: usize,
10559 y4: usize,
10560 w4: usize,
10561 h4: usize,
10562 pred_y: &mut [u8; 256],
10563 c_pred: &mut [[u8; 64]; 2]| {
10564 let b = y4 * 4 + x4;
10565 let (mv, gr) = (gmv[b & 15], gref[b & 15]);
10566 // `gref` holds a reference-list slot; a slot the list
10567 // does not have means there is nothing to predict from,
10568 // so the rect is left as it stands.
10569 let Some(reference) = refs.get(gr) else {
10570 return;
10571 };
10572 let (w, h) = (w4 * 4, h4 * 4);
10573 // A FULL-WIDTH rect (w == 16, so x4 == 0) occupies contiguous
10574 // whole rows of `pred_y` — the MC output layout and the
10575 // destination layout coincide, so MC writes the prediction
10576 // buffer DIRECTLY. The staging copy exists only for narrow
10577 // rects, whose rows really are strided in `pred_y`. This is
10578 // the diagnosis's "stage-boundary materialization" tax paid
10579 // by the dominant 16×16/16×8 shapes: 256 B of `t` zeroing
10580 // plus a 256 B copy per rect, for nothing.
10581 if w == 16 {
10582 rusty_h264_common::inter::with_mc_scratch(|scr| {
10583 rusty_h264_common::inter::mc_luma_padded_pre(
10584 scr,
10585 &*reference.luma_guard(reference.ch),
10586 reference.lstride(),
10587 crate::LPAD,
10588 cw,
10589 rh16,
10590 j.mbx * 16,
10591 j.mby * 16 + y4 * 4,
10592 w,
10593 h,
10594 mv.0,
10595 mv.1,
10596 &mut pred_y[y4 * 64..y4 * 64 + w * h],
10597 )
10598 });
10599 } else {
10600 let mut t = [0u8; 256];
10601 rusty_h264_common::inter::with_mc_scratch(|scr| {
10602 rusty_h264_common::inter::mc_luma_padded_pre(
10603 scr,
10604 &*reference.luma_guard(reference.ch),
10605 reference.lstride(),
10606 crate::LPAD,
10607 cw,
10608 rh16,
10609 j.mbx * 16 + x4 * 4,
10610 j.mby * 16 + y4 * 4,
10611 w,
10612 h,
10613 mv.0,
10614 mv.1,
10615 &mut t[..w * h],
10616 )
10617 });
10618 let _pb =
10619 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
10620 for dy in 0..h {
10621 pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
10622 .copy_from_slice(&t[dy * w..dy * w + w]);
10623 }
10624 }
10625 let (cw4, ch4) = (w4 * 2, h4 * 2);
10626 let nc = cw4 * ch4;
10627 let (gu, gv) = (
10628 reference.chroma_guard(0, reference.ch),
10629 reference.chroma_guard(1, reference.ch),
10630 );
10631 // U+V paired: one setup serves both planes (see
10632 // mc_chroma_padded_pair). Full-width coincidence:
10633 // cw4 == 8 rows are contiguous in the 8-wide plane.
10634 if cw4 == 8 {
10635 let [cu, cv] = &mut *c_pred;
10636 rusty_h264_common::inter::mc_chroma_padded_pair(
10637 &gu,
10638 &gv,
10639 reference.cstride(),
10640 crate::CPAD,
10641 ccw,
10642 cch,
10643 j.mbx * 8,
10644 j.mby * 8 + y4 * 2,
10645 cw4,
10646 ch4,
10647 mv.0,
10648 mv.1,
10649 &mut cu[y4 * 16..y4 * 16 + nc],
10650 &mut cv[y4 * 16..y4 * 16 + nc],
10651 );
10652 } else {
10653 let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
10654 rusty_h264_common::inter::mc_chroma_padded_pair(
10655 &gu,
10656 &gv,
10657 reference.cstride(),
10658 crate::CPAD,
10659 ccw,
10660 cch,
10661 j.mbx * 8 + x4 * 2,
10662 j.mby * 8 + y4 * 2,
10663 cw4,
10664 ch4,
10665 mv.0,
10666 mv.1,
10667 &mut tu[..nc],
10668 &mut tv[..nc],
10669 );
10670 let _pb =
10671 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
10672 for (cc, tc) in [(0usize, &tu), (1, &tv)] {
10673 for dy in 0..ch4 {
10674 c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
10675 .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
10676 }
10677 }
10678 }
10679 };
10680 if rect_eq(0, 0, 4, 4) {
10681 mc_rect(0, 0, 4, 4, &mut pred_y, &mut c_pred);
10682 } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
10683 mc_rect(0, 0, 4, 2, &mut pred_y, &mut c_pred);
10684 mc_rect(0, 2, 4, 2, &mut pred_y, &mut c_pred);
10685 } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
10686 mc_rect(0, 0, 2, 4, &mut pred_y, &mut c_pred);
10687 mc_rect(2, 0, 2, 4, &mut pred_y, &mut c_pred);
10688 } else {
10689 for q in 0..4usize {
10690 let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
10691 if rect_eq(qx, qy, 2, 2) {
10692 mc_rect(qx, qy, 2, 2, &mut pred_y, &mut c_pred);
10693 } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
10694 mc_rect(qx, qy, 2, 1, &mut pred_y, &mut c_pred);
10695 mc_rect(qx, qy + 1, 2, 1, &mut pred_y, &mut c_pred);
10696 } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
10697 mc_rect(qx, qy, 1, 2, &mut pred_y, &mut c_pred);
10698 mc_rect(qx + 1, qy, 1, 2, &mut pred_y, &mut c_pred);
10699 } else {
10700 for j in 0..4usize {
10701 mc_rect(qx + (j % 2), qy + (j / 2), 1, 1, &mut pred_y, &mut c_pred);
10702 }
10703 }
10704 }
10705 }
10706 // EXPLICIT WEIGHTED PREDICTION (spec 8.4.2.3). The CAVLC inter
10707 // path weights each partition after MC; the MC-call-coalescing
10708 // rewrite of this CABAC path lost it, and nothing caught that
10709 // because the effect is invisible unless a stream actually
10710 // carries non-default weights. x264's `weightp` DUPLICATES a
10711 // reference and distinguishes the copy ONLY by its weights, so
10712 // every macroblock picking the weighted index decoded unweighted
10713 // -- a silent, accumulating luma drift.
10714 //
10715 // Applied per 4x4 block rather than per partition: the weight
10716 // depends solely on the block's reference index, so the two are
10717 // equivalent, and `gref` already holds it for every block
10718 // regardless of which rect ladder rung ran.
10719 if self.weights.is_some() {
10720 for by in 0..4usize {
10721 for bx in 0..4usize {
10722 let refi = gref[by * 4 + bx];
10723 self.weight_partition(
10724 &mut pred_y,
10725 &mut c_pred,
10726 0,
10727 refi,
10728 bx * 4,
10729 by * 4,
10730 4,
10731 4,
10732 );
10733 }
10734 }
10735 }
10736 }
10737 // Residual add — the SAME helper the B path uses (this inline
10738 // copy was a duplicate; deduped when the zero-block fast path
10739 // landed so both paths share it).
10740 self.add_inter_residual(
10741 j.mbx,
10742 j.mby,
10743 &pred_y,
10744 &c_pred,
10745 Some(&j.luma_scan),
10746 j.t8.then_some(&j.luma8),
10747 &j.cdc,
10748 Some(&j.cac),
10749 j.cbp_chroma,
10750 &j.nnzs,
10751 );
10752 }
10753
10754 /// D9b: P inter with `cbp == 0` — MC + plane copy (worker twin of FrameDecoder).
10755 fn recon_p_inter_nores(&mut self, j: &PInterNoResJob) {
10756 let mut pred_y = [0u8; 256];
10757 let mut c_pred = [[0u8; 64]; 2];
10758 // `self.refs.len() - 1` was re-loaded on every one of the sixteen
10759 // iterations; it is a per-macroblock invariant. Written once, not
10760 // zeroed-then-filled.
10761 let nrefs = self.refs.len() - 1;
10762 let gref: [usize; 16] = core::array::from_fn(|k| (j.gref[k] as usize).min(nrefs));
10763 coalesce_p_inter_mc(
10764 &self.refs,
10765 self.cw,
10766 self.ccw,
10767 self.mb_h,
10768 j.mbx,
10769 j.mby,
10770 &j.gmv,
10771 &gref,
10772 &mut pred_y,
10773 &mut c_pred,
10774 );
10775 // The identity early-out lives INSIDE `weight_partition`, so an x264
10776 // stream - which carries a pred_weight_table in EVERY P slice, identity
10777 // outside fades - still paid sixteen calls per macroblock to be told
10778 // there was nothing to do. Hoisted to one test.
10779 if self.weights.is_some() && !self.weights_l0id {
10780 for by in 0..4usize {
10781 for bx in 0..4usize {
10782 let refi = gref[by * 4 + bx];
10783 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4);
10784 }
10785 }
10786 }
10787 // ONE span per plane: the destination is a stack of contiguous runs a
10788 // fixed stride apart, so the rows share a single bounds check instead of
10789 // one each (16 + 8 + 8 of them).
10790 let (cw, ccw) = (self.cw, self.ccw);
10791 let ybase = (j.mby * 16) * cw + j.mbx * 16;
10792 let ywin = &mut self.rec_y[ybase..ybase + 15 * cw + 16];
10793 for dy in 0..16 {
10794 ywin[dy * cw..dy * cw + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
10795 }
10796 let cbase = (j.mby * 8) * ccw + j.mbx * 8;
10797 for c in 0..2 {
10798 let plane = if c == 0 {
10799 &mut self.rec_u
10800 } else {
10801 &mut self.rec_v
10802 };
10803 let cwin = &mut plane[cbase..cbase + 7 * ccw + 8];
10804 for dy in 0..8 {
10805 cwin[dy * ccw..dy * ccw + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
10806 }
10807 }
10808 }
10809
10810 fn recon_p_skip(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32)) {
10811 crate::RefFrame::set_mc_row_need(mb_y, self.mb_h * 16);
10812 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
10813
10814 let mut pred = [0u8; 256];
10815 let Some(rf0) = self.refs.first() else { return };
10816 mc_luma_padded(
10817 &*rf0.luma_guard(rf0.ch),
10818 rf0.lstride(),
10819 crate::LPAD,
10820 self.cw,
10821 ch,
10822 mb_x * 16,
10823 mb_y * 16,
10824 16,
10825 16,
10826 mv.0,
10827 mv.1,
10828 &mut pred,
10829 );
10830 if let Some(wt) = &self.weights {
10831 for p in pred.iter_mut() {
10832 *p = wt.apply_luma(*p, 0, 0);
10833 }
10834 }
10835 {
10836 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
10837 // ONE span for the sixteen rows (see `recon_p_inter_nores`).
10838 let (cw, base) = (self.cw, (mb_y * 16) * self.cw + mb_x * 16);
10839 let win = &mut self.rec_y[base..base + 15 * cw + 16];
10840 for dy in 0..16 {
10841 win[dy * cw..dy * cw + 16].copy_from_slice(&pred[dy * 16..dy * 16 + 16]);
10842 }
10843 }
10844 for c in 0..2 {
10845 let mut pc = [0u8; 64];
10846 let Some(rf0) = self.refs.first() else { return };
10847 let rc = if c == 0 {
10848 &*rf0.chroma_guard(0, rf0.ch)
10849 } else {
10850 &*rf0.chroma_guard(1, rf0.ch)
10851 };
10852 mc_chroma_padded(
10853 rc,
10854 rf0.cstride(),
10855 crate::CPAD,
10856 self.ccw,
10857 cch,
10858 mb_x * 8,
10859 mb_y * 8,
10860 8,
10861 8,
10862 mv.0,
10863 mv.1,
10864 &mut pc,
10865 );
10866 if let Some(wt) = &self.weights {
10867 for p in pc.iter_mut() {
10868 *p = wt.apply_chroma(*p, 0, 0, c);
10869 }
10870 }
10871 let plane = if c == 0 {
10872 &mut self.rec_u
10873 } else {
10874 &mut self.rec_v
10875 };
10876 for dy in 0..8 {
10877 let d = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
10878 plane[d..d + 8].copy_from_slice(&pc[dy * 8..dy * 8 + 8]);
10879 }
10880 }
10881 }
10882
10883 fn add_inter_residual(
10884 &mut self,
10885 mb_x: usize,
10886 mb_y: usize,
10887 pred_y: &[u8; 256],
10888 c_pred: &[[u8; 64]; 2],
10889 luma_scan: Option<&[[i32; 16]; 16]>,
10890 // `Some` when the macroblock carries transform_size_8x8_flag: four 8x8
10891 // blocks in 8x8 scan order, replacing the sixteen 4x4 luma blocks.
10892 luma8: Option<&[[i32; 64]; 4]>,
10893 cdc: &[[i32; 4]; 2],
10894 cac: Option<&[[[i32; 16]; 4]; 2]>,
10895 cbp_chroma: u32,
10896 // Parsed totalCoeff per block, indexed exactly as the parse's `iz`:
10897 // [0..16] luma 4x4 z-order (for t8, the 8x8 count sits at `id8*4`),
10898 // [16..24] chroma AC as `16 + c*4 + id4`. The parser already counted
10899 // every significant coefficient; re-deriving the counts here scanned
10900 // 16-64 array elements per block (~400 loads/MB) for information the
10901 // caller was holding — the diagnosis's stage-boundary re-derivation tax.
10902 nnzs: &[u8; 24],
10903 ) {
10904 // A `None` field means the parse wrote nothing there; every read below
10905 // is guarded by a zero-count test, so the shared zero plane is
10906 // read-equivalent to the per-macroblock zeroed stack array it replaces.
10907 let luma_scan = luma_scan.unwrap_or(&ZERO_LUMA_SCAN);
10908 let cac = cac.unwrap_or(&ZERO_CAC);
10909 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecResidAdd);
10910 let qp = self.cur_qp;
10911 let qpc = self.chroma_qp_for(qp);
10912 if let Some(l8) = luma8 {
10913 // INTER 8x8 luma: same primitives the I_8x8 and CAVLC paths use.
10914 for b8 in 0..4usize {
10915 let (b8x, b8y) = (b8 % 2, b8 / 2);
10916 // Summed, not slot 0: with per-4x4 counts in the CAVLC case, slot 0
10917 // is only the first sub-block and can be 0 while the block is coded.
10918 let nnz: u32 = (0..4).map(|k| nnzs[b8 * 4 + k] as u32).sum();
10919 let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
10920 if nnz == 0 {
10921 // Zero residual: recon == pred — same shortcut as the
10922 // FrameDecoder twin.
10923 edcstat::bump(&edcstat::T8_ZERO, 1);
10924 for dy in 0..8 {
10925 let d = (py + dy) * self.cw + px;
10926 let po = (b8y * 8 + dy) * 16 + b8x * 8;
10927 self.rec_y[d..d + 8].copy_from_slice(&pred_y[po..po + 8]);
10928 }
10929 } else {
10930 let raster = un_scan_8x8(&l8[b8]);
10931 // list 1 = INTER 8x8 luma scaling list (0 is the intra one).
10932 let res8 = self.inv_quant8(&raster, qp, 1);
10933 let predb: [i32; 64] = core::array::from_fn(|i| {
10934 pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
10935 });
10936 let recon = add_residual_8x8(&res8, &predb);
10937 // Eight row copies. This wrote SIXTY-FOUR individually
10938 // bounds-checked samples into the luma plane per coded 8x8
10939 // block — the same shape whose fix carried `recon_i8_block`.
10940 // Present in BOTH the main path and the EDC worker twin.
10941 for dy in 0..8 {
10942 let d = (py + dy) * self.cw + px;
10943 self.rec_y[d..][..8].copy_from_slice(&recon[dy * 8..][..8]);
10944 }
10945 }
10946 }
10947 }
10948 // RESIDUAL LADDER, then the SIMD IDCT+add kernel per parked block (SIMD census
10949 // 2026-09-05). The zero and DC-only ladders write the plane in the loop;
10950 // every other coded block parks its dequantised coefficients here and is
10951 // reconstructed by `reconstruct_4x4_into` = accel `idct4x4_add` (in-register
10952 // transposes, exact on i32). A lane-per-block BATCHED form was tried first
10953 // and lost on all-intra content: its scalar gathers cost more than the
10954 // butterflies they vectorised.
10955 let dq3 = self.dq_const(qp, 3);
10956 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
10957 if luma8.is_some() {
10958 break;
10959 }
10960 let nnz = nnzs[blk];
10961 let cw = self.cw;
10962 let p_off = (lby * 4) * 16 + lbx * 4;
10963 let r_off = (mb_y * 4 + lby) * 4 * cw + (mb_x * 4 + lbx) * 4;
10964 if nnz == 0 {
10965 // Zero residual → recon == prediction EXACTLY (the integer IDCT is
10966 // linear so zeros map to zeros, and pred is already 0..=255) — copy
10967 // the pred rows straight into the plane. On real (sparse-cbp)
10968 // streams this is MOST of the 4×4 blocks.
10969 for r in 0..4 {
10970 self.rec_y[r_off + r * cw..r_off + r * cw + 4]
10971 .copy_from_slice(&pred_y[p_off + r * 16..p_off + r * 16 + 4]);
10972 }
10973 continue;
10974 }
10975 // DC-ONLY: the sole significant coefficient is scan position 0 (the
10976 // zig-zag starts at DC, and un_scan keeps it at raster 0), so the
10977 // whole dequant + IDCT collapses to one multiply and a flat add.
10978 if nnz == 1 && luma_scan[blk][0] != 0 {
10979 let f = self.dequant_dc4(luma_scan[blk][0], qp, 3);
10980 reconstruct_4x4_dc_into(
10981 (f + 32) >> 6,
10982 pred_y,
10983 p_off,
10984 16,
10985 &mut self.rec_y,
10986 r_off,
10987 cw,
10988 );
10989 } else {
10990 // Fused un-scan + dequant over ONLY the significant coefficients,
10991 // then IDCT + add + clip straight into the plane — no `qb`, no
10992 // `deq`-from-dense, no `predb` gather, no `s`, no `store` call.
10993 //
10994 // HYBRID: the scatter walks scan positions with a data-dependent
10995 // branch per slot, which beats the branchless dense 16-multiply
10996 // loop only while the block is SPARSE. The DC/zero fast paths
10997 // already removed the sparsest blocks, so the population here
10998 // skews denser — above ~6 coefficients the dense loop wins.
10999 // FUSED: un-scan + dequant + IDCT + add in one kernel call (dense-over-scatter round).
11000 reconstruct_4x4_scan_into::<false>(
11001 &luma_scan[blk],
11002 &dq3,
11003 0,
11004 pred_y,
11005 p_off,
11006 16,
11007 &mut self.rec_y,
11008 r_off,
11009 cw,
11010 );
11011 }
11012 }
11013 let mut c_dc = [[0i32; 4]; 2];
11014 if cbp_chroma != 0 {
11015 for c in 0..2 {
11016 c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 4 + c);
11017 }
11018 }
11019 let dqc = [self.dq_const(qpc, 4), self.dq_const(qpc, 5)];
11020 let ccw = self.ccw;
11021 for c in 0..2 {
11022 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
11023 let mut ac_nz = false;
11024 if cbp_chroma == 2 {
11025 let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
11026 ac_nz = n != 0;
11027 }
11028 let dc = c_dc[c & 1][(by * 2 + bx) & 3];
11029 let p_off = (by * 4) * 8 + bx * 4;
11030 let r_off = (mb_y * 2 + by) * 4 * ccw + (mb_x * 2 + bx) * 4;
11031 let plane = if c == 0 {
11032 &mut self.rec_u
11033 } else {
11034 &mut self.rec_v
11035 };
11036 if dc == 0 && !ac_nz {
11037 // Zero residual (no AC, zero DC) → recon == prediction exactly.
11038 for r in 0..4 {
11039 plane[r_off + r * ccw..r_off + r * ccw + 4]
11040 .copy_from_slice(&c_pred[c][p_off + r * 8..p_off + r * 8 + 4]);
11041 }
11042 continue;
11043 }
11044 // DC-ONLY (no coded AC — covers every cbp_chroma==1 block and the
11045 // AC-empty blocks of cbp_chroma==2): the chroma DC arrives ALREADY
11046 // dequantized, so the residual is `(dc + 32) >> 6` flat.
11047 if !ac_nz {
11048 reconstruct_4x4_dc_into(
11049 (dc + 32) >> 6,
11050 &c_pred[c],
11051 p_off,
11052 8,
11053 plane,
11054 r_off,
11055 ccw,
11056 );
11057 continue;
11058 }
11059 // AC-only scan: index i is overall scan position i+1 (ac_shift=1).
11060 // Same sparse/dense hybrid as luma.
11061 reconstruct_4x4_scan_into::<true>(
11062 &cac[c & 1][(by * 2 + bx) & 3],
11063 &dqc[c & 1],
11064 dc,
11065 &c_pred[c],
11066 p_off,
11067 8,
11068 plane,
11069 r_off,
11070 ccw,
11071 );
11072 }
11073 }
11074 }
11075
11076 fn filter_row(&mut self, r: usize) {
11077 // The precomputed consumer path reads ONLY `bs` + `t8x8` (+ the qp grid
11078 // passed as a parameter) — verified when the path landed (WHYS Part 15).
11079 let info = rusty_h264_common::deblock::BlockInfo {
11080 inter: &[],
11081 nnz: &[],
11082 mv: &[],
11083 ref_id: &[],
11084 mv1: &[],
11085 ref_id1: &[],
11086 w4: self.mb_w * 4,
11087 t8x8: &self.t8_grid,
11088 bs: &self.bs_store,
11089 poc0: &[],
11090 poc1: &[],
11091 kind: &[],
11092 };
11093 rusty_h264_common::deblock::filter_frame_rows_pre(
11094 &mut self.rec_y,
11095 &mut self.rec_u,
11096 &mut self.rec_v,
11097 self.mb_w,
11098 self.mb_h,
11099 r..r + 1,
11100 &self.qp_grid,
11101 self.chroma_qp_offset,
11102 self.db_oa,
11103 self.db_ob,
11104 &info,
11105 );
11106 }
11107
11108 fn save_bak(&mut self, r: usize) {
11109 let y0 = (r * 16 + 15) * self.cw;
11110 self.bak_y.copy_from_slice(&self.rec_y[y0..y0 + self.cw]);
11111 let c0 = (r * 8 + 7) * self.ccw;
11112 self.bak_u.copy_from_slice(&self.rec_u[c0..c0 + self.ccw]);
11113 self.bak_v.copy_from_slice(&self.rec_v[c0..c0 + self.ccw]);
11114 }
11115}
11116
11117/// The pixel worker: replays jobs in parse order, filters rows as their
11118/// messages arrive, and hands the whole context to the parse thread (and
11119/// back) around intra macroblocks. Returns the context at slice end.
11120/// E2 SEAM COUNTERS (D7). Deterministic — one run is the verdict, no pinning.
11121/// `RS_H264_EDC_STATS=1` prints at decode end. Counts, not clocks: the question
11122/// "does one intra macroblock drain the pipeline" is a COUNT question.
11123pub(crate) mod edcstat {
11124 use core::sync::atomic::Ordering::Relaxed;
11125 use rusty_h264_common::atomic::AtomicU64;
11126 pub static NEEDCTX: AtomicU64 = AtomicU64::new(0);
11127 pub static JOBS: AtomicU64 = AtomicU64::new(0);
11128 pub static ROWS: AtomicU64 = AtomicU64::new(0);
11129 pub static ROWBYTES: AtomicU64 = AtomicU64::new(0);
11130 pub static MBS: AtomicU64 = AtomicU64::new(0);
11131 /// Sparse-dequant routing histogram: [nnz class 1-2/3-4/5-6/7+][L class 1-4/5-8/9-12/13-16],
11132 /// L = highest coded scan position + 1 (the scatter loop's trip count).
11133 pub static DQ: [AtomicU64; 16] = [const { AtomicU64::new(0) }; 16];
11134 #[inline(always)]
11135 #[allow(dead_code)]
11136 pub fn dq_note(nnz: u8, scan: &[i32; 16]) {
11137 #[cfg(feature = "profile")]
11138 if on() {
11139 let l = 16 - scan.iter().rev().take_while(|&&v| v == 0).count();
11140 let nc = ((nnz.max(1) as usize - 1) / 2).min(3);
11141 let lc = ((l.max(1) - 1) / 4).min(3);
11142 DQ[nc * 4 + lc].fetch_add(1, Relaxed);
11143 }
11144 #[cfg(not(feature = "profile"))]
11145 let _ = (nnz, scan);
11146 }
11147 pub static J_INTER: AtomicU64 = AtomicU64::new(0);
11148 pub static DOUBLED: AtomicU64 = AtomicU64::new(0);
11149 pub static J_NORES_SENT: AtomicU64 = AtomicU64::new(0);
11150 pub static BATCHES: AtomicU64 = AtomicU64::new(0);
11151 pub static DISPATCH_ON: AtomicU64 = AtomicU64::new(0);
11152 pub static DISPATCH_SEEN: AtomicU64 = AtomicU64::new(0);
11153 /// mb_skip_run band path (big-oppy-decoder §6 KEY glue): P_Skip MBs
11154 /// reconstructed by run-coalesced band copies, and the runs themselves.
11155 /// DETERMINISTIC WIN counters — each banded MB removed 1 luma MC call,
11156 /// 2 chroma MC calls and the 256+128-byte pred staging round-trip.
11157 pub static SKIPBAND_MBS: AtomicU64 = AtomicU64::new(0);
11158 pub static SKIPBAND_RUNS: AtomicU64 = AtomicU64::new(0);
11159 pub static SKIP_SINGLES: AtomicU64 = AtomicU64::new(0);
11160 // MEASUREMENT counters sizing the next skip-band extensions (no behavior).
11161 // P side: singles that sit in a same-row run of >=2 EQUAL nonzero MVs,
11162 // split by full-pel (band-copy-with-offset eligible) vs fractional.
11163 pub static PEQ_FP: AtomicU64 = AtomicU64::new(0);
11164 pub static PEQ_FRAC: AtomicU64 = AtomicU64::new(0);
11165 // B side: full-16x16 spatial-direct calls (B_Skip + direct-16), how many
11166 // collapse to ONE uniform rect, and of those the zero-motion bi/uni and
11167 // full-pel populations + run adjacency (previous MB same params, same row).
11168 pub static BSK_FULLMB: AtomicU64 = AtomicU64::new(0);
11169 pub static BSK_1RECT: AtomicU64 = AtomicU64::new(0);
11170 pub static BSK_ZBI: AtomicU64 = AtomicU64::new(0);
11171 pub static BSK_ZUNI: AtomicU64 = AtomicU64::new(0);
11172 pub static BSK_FP: AtomicU64 = AtomicU64::new(0);
11173 pub static BSK_RUNCONT: AtomicU64 = AtomicU64::new(0);
11174 /// DETERMINISTIC WIN counter — B_Skip MBs taken by the zero-bi fast path.
11175 pub static BSKB_FAST: AtomicU64 = AtomicU64::new(0);
11176 /// P_Skip singles taken by the full-pel direct-copy path (both planes /
11177 /// luma only, chroma still interpolating at mv%8!=0).
11178 pub static SKIP_FP_FULL: AtomicU64 = AtomicU64::new(0);
11179 pub static SKIP_FP_LUMA: AtomicU64 = AtomicU64::new(0);
11180 /// B_Skip full-pel nonzero-MV MBs taken by the offset copy/avg path.
11181 pub static BSKB_FP: AtomicU64 = AtomicU64::new(0);
11182 /// P_Skip parse side: MBs whose skip MV was FORCED (0,0) by the run
11183 /// theorem (left = just-committed (0,0) skip, or out-of-slice) vs MBs
11184 /// that ran the 3-neighbor gather + rule.
11185 pub static SKIPMV_FORCED: AtomicU64 = AtomicU64::new(0);
11186 pub static SKIPMV_DERIVED: AtomicU64 = AtomicU64::new(0);
11187 /// B_Skips whose zero-bi derivation was FORCED by the known-zero bitmap
11188 /// (left+top+topright all recorded ref0/(0,0)-both-lists) — the 6-gather
11189 /// b_direct_nbrs walk skipped entirely.
11190 pub static BSKB_FORCED: AtomicU64 = AtomicU64::new(0);
11191 /// Zero-bi fast B_Skip grid commits batched into row spans: MBs covered
11192 /// and spans flushed (fills of 4N replace N MBs x 28 per-MB fills).
11193 pub static BZ_SPAN_MBS: AtomicU64 = AtomicU64::new(0);
11194 pub static BZ_SPANS: AtomicU64 = AtomicU64::new(0);
11195 /// P_Skip (0,0) grid-commit spans (parse-side mirror of BZ_*).
11196 pub static PZ_SPAN_MBS: AtomicU64 = AtomicU64::new(0);
11197 pub static PZ_SPANS: AtomicU64 = AtomicU64::new(0);
11198 /// Census: b_mc bi-pred regions where BOTH MVs are full-pel (fusable to a
11199 /// direct offset average — sizing only, no behavior).
11200 pub static BMC_BI_FP: AtomicU64 = AtomicU64::new(0);
11201 /// weight_partition passes skipped because the whole list-0 table is
11202 /// identity (384 apply ops avoided per full-MB pass).
11203 pub static WP_SKIPPED: AtomicU64 = AtomicU64::new(0);
11204 /// Intra I_4x4 luma residual dispatch (the inter ladder, ported): how many
11205 /// blocks took the zero / DC-only / sparse-scatter / dense arms.
11206 pub static I4_ZERO: AtomicU64 = AtomicU64::new(0);
11207 pub static I4_DC: AtomicU64 = AtomicU64::new(0);
11208 pub static I4_SPARSE: AtomicU64 = AtomicU64::new(0);
11209 pub static I4_DENSE: AtomicU64 = AtomicU64::new(0);
11210 /// 8x8 residual blocks (intra + inter t8) whose residual is entirely zero:
11211 /// recon == pred, the 64-px widen + add + clip skipped.
11212 pub static T8_ZERO: AtomicU64 = AtomicU64::new(0);
11213 /// I_16x16 4x4 sub-blocks with zero AC: the residual is the (already
11214 /// Hadamard-transformed) DC alone, so the dense dequant + IDCT collapse
11215 /// to one flat add.
11216 pub static I16_DCONLY: AtomicU64 = AtomicU64::new(0);
11217 pub static J_INTER_NORES: AtomicU64 = AtomicU64::new(0);
11218 // ---- deb:derive census (derive_bs_row) — sizing the bS derivation arms.
11219 /// Rows derived, and macroblocks derived across them.
11220 pub static DBS_ROWS: AtomicU64 = AtomicU64::new(0);
11221 pub static DBS_MB: AtomicU64 = AtomicU64::new(0);
11222 /// Arm split: Intra kind-arm / Skip|InterUniform kind-arm (only under
11223 /// RS_H264_KIND_LOADS=1) / the packed `derive_mb_records` default.
11224 pub static DBS_INTRA: AtomicU64 = AtomicU64::new(0);
11225 pub static DBS_KINDARM: AtomicU64 = AtomicU64::new(0);
11226 /// Macroblocks whose kind is Skip|InterUniform: the population that used
11227 /// to evaluate the `kind_loads()` OnceLock guard once EACH.
11228 pub static DBS_KINDGUARD: AtomicU64 = AtomicU64::new(0);
11229 pub static DBS_PACKED: AtomicU64 = AtomicU64::new(0);
11230 /// Of the packed arm: macroblocks whose derivation returned `flat_inter`
11231 /// (internal strengths 0 by construction — the early-return class).
11232 pub static DBS_FLAT: AtomicU64 = AtomicU64::new(0);
11233 /// Macroblocks whose FINAL stored MbBs is all-zero: the population the
11234 /// consumer's two-16-byte-compare early-out serves, and the denominator
11235 /// for the per-MB 128-byte bs_v/bs_h zero-init that precedes it.
11236 pub static DBS_ALLZERO: AtomicU64 = AtomicU64::new(0);
11237 /// nnz_dbr maintenance: rows copied wholesale vs rows that actually
11238 /// carried a transform-8x8 macroblock needing the 8x8 OR fixup.
11239 pub static DBS_NNZ_ROWCOPY: AtomicU64 = AtomicU64::new(0);
11240 pub static DBS_T8ROW: AtomicU64 = AtomicU64::new(0);
11241 pub static DBS_T8MB: AtomicU64 = AtomicU64::new(0);
11242 /// Rows that ran the disable_deblocking_filter_idc==2 crossing-edge pass.
11243 pub static DBS_IDC2ROW: AtomicU64 = AtomicU64::new(0);
11244 #[inline]
11245 pub fn bump(c: &AtomicU64, n: u64) {
11246 #[cfg(feature = "profile")]
11247 if on() {
11248 c.fetch_add(n, Relaxed);
11249 }
11250 #[cfg(not(feature = "profile"))]
11251 let _ = (c, n);
11252 }
11253 /// Off by default, yet read on EVERY bump — up to four times per B_Skip.
11254 /// `OnceLock::get_or_init` is an acquire load plus an initialised-state
11255 /// branch behind a non-inlined call; this is the relaxed AtomicU8 tri-state
11256 /// the other knobs in this file already use, and it inlines to one load.
11257 #[inline]
11258 pub fn on() -> bool {
11259 #[cfg(not(feature = "profile"))]
11260 {
11261 return false;
11262 }
11263 #[cfg(feature = "profile")]
11264 {
11265 use core::sync::atomic::AtomicU8;
11266 static V: AtomicU8 = AtomicU8::new(0);
11267 match V.load(Relaxed) {
11268 1 => true,
11269 2 => false,
11270 _ => {
11271 let b = rusty_h264_common::knob("RS_H264_EDC_STATS").is_some();
11272 V.store(if b { 1 } else { 2 }, Relaxed);
11273 b
11274 }
11275 }
11276 }
11277 }
11278 pub fn report() {
11279 if !on() {
11280 return;
11281 }
11282 {
11283 // Routing model (census, 2026-09-05): scatter = 37 + 6*L + 9*nnz instrs;
11284 // dense = unscan 32 + dequantize ~63 (scalar) / ~28 (AVX2 twin).
11285 let (nm, lm) = ([1.5f64, 3.5, 5.5, 9.0], [2.5f64, 6.5, 10.5, 14.5]);
11286 let (mut tot, mut sc, mut d95, mut d60, mut best) =
11287 (0u64, 0.0f64, 0.0f64, 0.0f64, 0.0f64);
11288 eprintln!(
11289 "DQROUTE sparse-arm blocks by [nnz class][L class] (L = last coded position + 1):"
11290 );
11291 for nc in 0..4 {
11292 let row: Vec<u64> = (0..4).map(|lc| DQ[nc * 4 + lc].load(Relaxed)).collect();
11293 eprintln!(
11294 " nnz {:<5} L1-4={:>9} L5-8={:>9} L9-12={:>9} L13-16={:>9}",
11295 ["1-2", "3-4", "5-6", "7+"][nc],
11296 row[0],
11297 row[1],
11298 row[2],
11299 row[3]
11300 );
11301 for lc in 0..4 {
11302 let k = row[lc] as f64;
11303 let s = 37.0 + 6.0 * lm[lc] + 9.0 * nm[nc];
11304 tot += row[lc];
11305 sc += k * s;
11306 d95 += k * 95.0;
11307 d60 += k * 60.0;
11308 best += k * s.min(60.0);
11309 }
11310 }
11311 if tot > 0 {
11312 eprintln!(" modelled instrs: scatter(as routed)={:.0} all-dense-scalar={:.0} all-dense-avx2={:.0} per-bin best(scatter|avx2)={:.0} blocks={}", sc, d95, d60, best, tot);
11313 }
11314 }
11315 eprintln!(
11316 "EDCDISPATCH threaded_slices={} eligible_slices={}",
11317 DISPATCH_ON.load(Relaxed),
11318 DISPATCH_SEEN.load(Relaxed)
11319 );
11320 eprintln!(
11321 "EDCSIZE EdcMsg={} EdcJob={} PInterJob={} BJob={}",
11322 core::mem::size_of::<super::EdcMsg>(),
11323 core::mem::size_of::<super::EdcJob>(),
11324 core::mem::size_of::<super::PInterJob>(),
11325 core::mem::size_of::<super::BJob>(),
11326 );
11327 let (n, j, r, b, m) = (
11328 NEEDCTX.load(Relaxed),
11329 JOBS.load(Relaxed),
11330 ROWS.load(Relaxed),
11331 ROWBYTES.load(Relaxed),
11332 MBS.load(Relaxed),
11333 );
11334 eprintln!(
11335 "EDCSTAT needctx={n} jobs={j} rows={r} rowbytes={b} mbs={m} batches={} jobs_per_batch={:.1} needctx_per_1k_mb={:.1} jobs_per_needctx={:.1}",
11336 BATCHES.load(Relaxed),
11337 j as f64 / BATCHES.load(Relaxed).max(1) as f64,
11338 1000.0 * n as f64 / m.max(1) as f64,
11339 j as f64 / n.max(1) as f64
11340 );
11341 eprintln!(
11342 "SKIPRUN band_mbs={} band_runs={} mbs_per_run={:.1} singles={} banded_pct={:.1}",
11343 SKIPBAND_MBS.load(Relaxed),
11344 SKIPBAND_RUNS.load(Relaxed),
11345 SKIPBAND_MBS.load(Relaxed) as f64 / SKIPBAND_RUNS.load(Relaxed).max(1) as f64,
11346 SKIP_SINGLES.load(Relaxed),
11347 100.0 * SKIPBAND_MBS.load(Relaxed) as f64
11348 / (SKIPBAND_MBS.load(Relaxed) + SKIP_SINGLES.load(Relaxed)).max(1) as f64,
11349 );
11350 eprintln!(
11351 "SKIPNEXT peq_fp={} peq_frac={} bsk_fullmb={} bsk_1rect={} bsk_zbi={} bsk_zuni={} bsk_fp={} bsk_runcont={} bskb_fast={} skip_fp_full={} skip_fp_luma={} bskb_fp={} skipmv_forced={} skipmv_derived={} bskb_forced={} bz_spans={} bz_span_mbs={} pz_spans={} pz_span_mbs={} bmc_bi_fp={} wp_skipped={} i4_zero={} i4_dc={} i4_sparse={} i4_dense={} t8_zero={} i16_dconly={}",
11352 PEQ_FP.load(Relaxed), PEQ_FRAC.load(Relaxed),
11353 BSK_FULLMB.load(Relaxed), BSK_1RECT.load(Relaxed),
11354 BSK_ZBI.load(Relaxed), BSK_ZUNI.load(Relaxed),
11355 BSK_FP.load(Relaxed), BSK_RUNCONT.load(Relaxed),
11356 BSKB_FAST.load(Relaxed),
11357 SKIP_FP_FULL.load(Relaxed), SKIP_FP_LUMA.load(Relaxed),
11358 BSKB_FP.load(Relaxed),
11359 SKIPMV_FORCED.load(Relaxed), SKIPMV_DERIVED.load(Relaxed),
11360 BSKB_FORCED.load(Relaxed),
11361 BZ_SPANS.load(Relaxed), BZ_SPAN_MBS.load(Relaxed),
11362 PZ_SPANS.load(Relaxed), PZ_SPAN_MBS.load(Relaxed),
11363 BMC_BI_FP.load(Relaxed),
11364 WP_SKIPPED.load(Relaxed),
11365 I4_ZERO.load(Relaxed), I4_DC.load(Relaxed),
11366 I4_SPARSE.load(Relaxed), I4_DENSE.load(Relaxed),
11367 T8_ZERO.load(Relaxed), I16_DCONLY.load(Relaxed),
11368 );
11369 let dmb = DBS_MB.load(Relaxed).max(1);
11370 eprintln!(
11371 "DBSDERIVE rows={} mb={} intra={} kindarm={} kindguard={} packed={} flat={} ({:.1}%) allzero={} ({:.1}%) nnz_rowcopy={} t8row={} t8mb={} idc2row={}",
11372 DBS_ROWS.load(Relaxed), DBS_MB.load(Relaxed),
11373 DBS_INTRA.load(Relaxed), DBS_KINDARM.load(Relaxed),
11374 DBS_KINDGUARD.load(Relaxed), DBS_PACKED.load(Relaxed),
11375 DBS_FLAT.load(Relaxed), 100.0 * DBS_FLAT.load(Relaxed) as f64 / dmb as f64,
11376 DBS_ALLZERO.load(Relaxed), 100.0 * DBS_ALLZERO.load(Relaxed) as f64 / dmb as f64,
11377 DBS_NNZ_ROWCOPY.load(Relaxed), DBS_T8ROW.load(Relaxed),
11378 DBS_T8MB.load(Relaxed), DBS_IDC2ROW.load(Relaxed),
11379 );
11380 let (ji, jn) = (J_INTER.load(Relaxed), J_INTER_NORES.load(Relaxed));
11381 eprintln!(
11382 "EDCMIX doubled={} nores_sent={} inter={ji} inter_no_residual={jn} ({:.1}% of inter) wasted_bytes={:.1} MB of {:.1} MB total inter payload",
11383 DOUBLED.load(Relaxed),
11384 J_NORES_SENT.load(Relaxed),
11385 100.0 * jn as f64 / ji.max(1) as f64,
11386 (jn * 2784) as f64 / 1.048576e6,
11387 (ji * 2784) as f64 / 1.048576e6,
11388 );
11389 }
11390}
11391
11392#[cfg(feature = "std")]
11393fn edc_worker(
11394 mut ctx: PixelCtx,
11395 rx: crate::sync::mpsc::Receiver<EdcMsg>,
11396 ctx_tx: crate::sync::mpsc::Sender<PixelCtx>,
11397 back_rx: crate::sync::mpsc::Receiver<PixelCtx>,
11398) -> PixelCtx {
11399 while let Ok(msg) = rx.recv() {
11400 match msg {
11401 EdcMsg::Batch(jobs) => {
11402 for j in jobs {
11403 match j {
11404 EdcJob::Skip { mbx, mby, mv } => ctx.recon_p_skip(mbx, mby, mv),
11405 EdcJob::Inter(j) => ctx.recon_p_inter(&j),
11406 EdcJob::InterNoRes(j) => ctx.recon_p_inter_nores(&j),
11407 EdcJob::B(j) => ctx.recon_b(&j),
11408 EdcJob::BSkip { mbx, mby, regions } => ctx.recon_b_skip(mbx, mby, ®ions),
11409 }
11410 }
11411 }
11412 EdcMsg::Job(EdcJob::Skip { mbx, mby, mv }) => ctx.recon_p_skip(mbx, mby, mv),
11413 EdcMsg::Job(EdcJob::Inter(j)) => ctx.recon_p_inter(&j),
11414 EdcMsg::Job(EdcJob::InterNoRes(j)) => ctx.recon_p_inter_nores(&j),
11415 EdcMsg::Job(EdcJob::B(j)) => ctx.recon_b(&j),
11416 EdcMsg::Job(EdcJob::BSkip { mbx, mby, regions }) => {
11417 ctx.recon_b_skip(mbx, mby, ®ions)
11418 }
11419 EdcMsg::Row { r, bs, qp, t8 } => {
11420 let (w, base) = (ctx.mb_w, r * ctx.mb_w);
11421 ctx.bs_store[base..base + w].copy_from_slice(&bs);
11422 ctx.qp_grid[base..base + w].copy_from_slice(&qp);
11423 ctx.t8_grid[base..base + w].copy_from_slice(&t8);
11424 if ctx.db_ena {
11425 ctx.save_bak(r);
11426 ctx.filter_row(r);
11427 ctx.flt_rows = r + 1;
11428 ctx.publish_progress_rows();
11429 }
11430 }
11431 EdcMsg::NeedCtx => {
11432 ctx_tx.send(ctx).expect("parse thread alive");
11433 ctx = back_rx.recv().expect("ctx returned after intra");
11434 }
11435 }
11436 }
11437 ctx
11438}
11439
11440/// One motion-compensation region of a B macroblock, recorded at parse time
11441/// (E3). Weights are the RESOLVED implicit pair — computing them needs the
11442/// ref lists' POCs, which are parse-side state.
11443pub(crate) struct BRegion {
11444 px: usize,
11445 py: usize,
11446 rw: usize,
11447 rh: usize,
11448 refi0: i32,
11449 refi1: i32,
11450 mv0: (i32, i32),
11451 mv1: (i32, i32),
11452 w: Option<(i32, i32)>,
11453}
11454
11455/// A B macroblock's deferred pixel work: replay the regions into fresh
11456/// prediction buffers, then either copy them out (skip/direct, no residual)
11457/// or run the residual add.
11458pub(crate) struct BJob {
11459 mbx: usize,
11460 mby: usize,
11461 qp: u8,
11462 cbp_chroma: u32,
11463 skip: bool,
11464 regions: Vec<BRegion>,
11465 /// `None` when no 4x4 luma block was coded — t8 macroblocks included,
11466 /// since those carry `luma8` instead.
11467 luma_scan: Option<[[i32; 16]; 16]>,
11468 /// `Some` only under transform_size_8x8_flag.
11469 luma8: Option<[[i32; 64]; 4]>,
11470 cdc: [[i32; 4]; 2],
11471 /// `None` unless cbp_chroma == 2 (chroma AC coded).
11472 cac: Option<[[[i32; 16]; 4]; 2]>,
11473 nnzs: [u8; 24],
11474}
11475
11476impl PixelCtx {
11477 fn b_mc(
11478 &self,
11479 mb_x: usize,
11480 mb_y: usize,
11481 px: usize,
11482 py: usize,
11483 rw: usize,
11484 rh: usize,
11485 refi0: i32,
11486 mv0: (i32, i32),
11487 refi1: i32,
11488 mv1: (i32, i32),
11489 pred_y: &mut [u8; 256],
11490 c_pred: &mut [[u8; 64]; 2],
11491 wparam: Option<(i32, i32)>,
11492 ) {
11493 let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBMc);
11494 // Malformed-stream armor, mirroring the P path: now that B slices actually
11495 // PARSE ref_idx (they used to be hardcoded to 0), a mutated stream can hand
11496 // us an index past the end of either list. Clamp rather than panic — the
11497 // crate is `forbid(unsafe_code)` and fuzz-gated to never panic, and a
11498 // wrong picture on garbage input carries no conformance duty.
11499 let refi0 = if refi0 >= 0 {
11500 (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32
11501 } else {
11502 -1
11503 };
11504 let refi1 = if refi1 >= 0 {
11505 (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32
11506 } else {
11507 -1
11508 };
11509 if (refi0 >= 0 && self.refs.is_empty()) || (refi1 >= 0 && self.refs1.is_empty()) {
11510 return;
11511 }
11512 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
11513 // E3: implicit weights are PARSE-side (they read the ref lists' POCs);
11514 // the region carries the resolved pair.
11515 let weights = wparam;
11516 // Bi-prediction blend: the weights decision is LOOP-INVARIANT, so every
11517 // blend site below matches on `weights` ONCE and runs a branch-free
11518 // pixel loop — the unweighted `(p+q+1)>>1` average then autovectorizes
11519 // (the per-pixel closure this replaces hid the invariant behind a
11520 // capture, and its chroma form was a &dyn call PER PIXEL).
11521 // FULL-WIDTH regions (px == 0, rw == 16 — every 16×16/16×8 partition and
11522 // most direct regions) occupy contiguous rows of `pred_y`, so MC writes
11523 // the destination DIRECTLY: uni-pred needs no staging at all, bi-pred
11524 // stages only the second list and blends in place. The staging arrays
11525 // (512 B zeroed per call before this) now exist only on the branches
11526 // that read them. Same fusion as the P path's mc_rect (WHYS Part 8).
11527 let full = px == 0 && rw == 16;
11528 let _gl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBLuma);
11529 // One scratch borrow for the whole region — both bi-pred passes included.
11530 // The closure yields whether the arm already ran the chroma half (the
11531 // bi-pred full-width arm does, to keep its staging alive) — a plain
11532 // `return` inside would exit the CLOSURE only and chroma would run twice.
11533 let chroma_done =
11534 rusty_h264_common::inter::with_mc_scratch(|scr| match (refi0 >= 0, refi1 >= 0, full) {
11535 (true, false, true) => {
11536 let Some(rf) = self.refs.get(refi0 as usize) else {
11537 return false;
11538 };
11539 rusty_h264_common::inter::mc_luma_padded_pre(
11540 scr,
11541 &*rf.luma_guard(rf.ch),
11542 rf.lstride(),
11543 crate::LPAD,
11544 self.cw,
11545 ch,
11546 mb_x * 16,
11547 mb_y * 16 + py,
11548 rw,
11549 rh,
11550 mv0.0,
11551 mv0.1,
11552 &mut pred_y[py * 16..py * 16 + rw * rh],
11553 );
11554 false
11555 }
11556 (false, true, true) => {
11557 let Some(rf) = self.refs1.get(refi1 as usize) else {
11558 return false;
11559 };
11560 rusty_h264_common::inter::mc_luma_padded_pre(
11561 scr,
11562 &*rf.luma_guard(rf.ch),
11563 rf.lstride(),
11564 crate::LPAD,
11565 self.cw,
11566 ch,
11567 mb_x * 16,
11568 mb_y * 16 + py,
11569 rw,
11570 rh,
11571 mv1.0,
11572 mv1.1,
11573 &mut pred_y[py * 16..py * 16 + rw * rh],
11574 );
11575 false
11576 }
11577 (true, true, true) => {
11578 let Some(rf) = self.refs.get(refi0 as usize) else {
11579 return false;
11580 };
11581 rusty_h264_common::inter::mc_luma_padded_pre(
11582 scr,
11583 &*rf.luma_guard(rf.ch),
11584 rf.lstride(),
11585 crate::LPAD,
11586 self.cw,
11587 ch,
11588 mb_x * 16,
11589 mb_y * 16 + py,
11590 rw,
11591 rh,
11592 mv0.0,
11593 mv0.1,
11594 &mut pred_y[py * 16..py * 16 + rw * rh],
11595 );
11596 let mut b = [0u8; 256];
11597 let Some(rf) = self.refs1.get(refi1 as usize) else {
11598 return false;
11599 };
11600 rusty_h264_common::inter::mc_luma_padded_pre(
11601 scr,
11602 &*rf.luma_guard(rf.ch),
11603 rf.lstride(),
11604 crate::LPAD,
11605 self.cw,
11606 ch,
11607 mb_x * 16,
11608 mb_y * 16 + py,
11609 rw,
11610 rh,
11611 mv1.0,
11612 mv1.1,
11613 &mut b[..rw * rh],
11614 );
11615 drop(_gl);
11616 let _gbl =
11617 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
11618 // SLICE-then-zip: proving the bounds ONCE lets rustc emit the whole
11619 // 256-byte average as 8 straight-line vpavgb ops (verified in
11620 // isolation, x86-64-v3); the indexed form kept a per-iteration
11621 // bounds check and a loop. A hand AVX2 kernel is refuted — the
11622 // compiler already emits the ideal instruction.
11623 let dst = &mut pred_y[py * 16..py * 16 + rw * rh];
11624 match weights {
11625 None => {
11626 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
11627 *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
11628 }
11629 }
11630 Some((w0, w1)) => {
11631 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
11632 *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255)
11633 as u8;
11634 }
11635 }
11636 }
11637 let _gc =
11638 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
11639 self.b_mc_chroma(
11640 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
11641 );
11642 true
11643 }
11644 _ => {
11645 // Narrow region — rows are strided in `pred_y`; stage and copy.
11646 let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
11647 if refi0 >= 0 {
11648 let Some(rf) = self.refs.get(refi0 as usize) else {
11649 return false;
11650 };
11651 rusty_h264_common::inter::mc_luma_padded_pre(
11652 scr,
11653 &*rf.luma_guard(rf.ch),
11654 rf.lstride(),
11655 crate::LPAD,
11656 self.cw,
11657 ch,
11658 mb_x * 16 + px,
11659 mb_y * 16 + py,
11660 rw,
11661 rh,
11662 mv0.0,
11663 mv0.1,
11664 &mut a[..rw * rh],
11665 );
11666 }
11667 if refi1 >= 0 {
11668 let Some(rf) = self.refs1.get(refi1 as usize) else {
11669 return false;
11670 };
11671 rusty_h264_common::inter::mc_luma_padded_pre(
11672 scr,
11673 &*rf.luma_guard(rf.ch),
11674 rf.lstride(),
11675 crate::LPAD,
11676 self.cw,
11677 ch,
11678 mb_x * 16 + px,
11679 mb_y * 16 + py,
11680 rw,
11681 rh,
11682 mv1.0,
11683 mv1.1,
11684 &mut b[..rw * rh],
11685 );
11686 }
11687 drop(_gl);
11688 let _gbl =
11689 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
11690 match (refi0 >= 0, refi1 >= 0) {
11691 (true, true) => {
11692 for dy in 0..rh {
11693 let (ar, br) =
11694 (&a[dy * rw..dy * rw + rw], &b[dy * rw..dy * rw + rw]);
11695 let base = (py + dy) * 16 + px;
11696 let dst = &mut pred_y[base..base + rw];
11697 match weights {
11698 None => {
11699 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
11700 *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
11701 }
11702 }
11703 Some((w0, w1)) => {
11704 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
11705 *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6)
11706 .clamp(0, 255)
11707 as u8;
11708 }
11709 }
11710 }
11711 }
11712 }
11713 (true, false) => {
11714 for dy in 0..rh {
11715 let d = (py + dy) * 16 + px;
11716 pred_y[d..d + rw].copy_from_slice(&a[dy * rw..dy * rw + rw]);
11717 }
11718 }
11719 _ => {
11720 for dy in 0..rh {
11721 let d = (py + dy) * 16 + px;
11722 pred_y[d..d + rw].copy_from_slice(&b[dy * rw..dy * rw + rw]);
11723 }
11724 }
11725 }
11726 false
11727 }
11728 });
11729 if chroma_done {
11730 return;
11731 }
11732 let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
11733 self.b_mc_chroma(
11734 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
11735 );
11736 }
11737
11738 fn b_mc_chroma(
11739 &self,
11740 mb_x: usize,
11741 mb_y: usize,
11742 px: usize,
11743 py: usize,
11744 rw: usize,
11745 rh: usize,
11746 refi0: i32,
11747 mv0: (i32, i32),
11748 refi1: i32,
11749 mv1: (i32, i32),
11750 c_pred: &mut [[u8; 64]; 2],
11751 weights: Option<(i32, i32)>,
11752 cch: usize,
11753 ) {
11754 let (crx, cry, crw, crh) = (px / 2, py / 2, rw / 2, rh / 2);
11755 let full = crx == 0 && crw == 8;
11756 for c in 0..2 {
11757 match (refi0 >= 0, refi1 >= 0, full) {
11758 (true, false, true) => {
11759 let Some(rf) = self.refs.get(refi0 as usize) else {
11760 return;
11761 };
11762 let pl = if c == 0 {
11763 &*rf.chroma_guard(0, rf.ch)
11764 } else {
11765 &*rf.chroma_guard(1, rf.ch)
11766 };
11767 mc_chroma_padded(
11768 pl,
11769 rf.cstride(),
11770 crate::CPAD,
11771 self.ccw,
11772 cch,
11773 mb_x * 8,
11774 mb_y * 8 + cry,
11775 crw,
11776 crh,
11777 mv0.0,
11778 mv0.1,
11779 &mut c_pred[c][cry * 8..cry * 8 + crw * crh],
11780 );
11781 }
11782 (false, true, true) => {
11783 let Some(rf) = self.refs1.get(refi1 as usize) else {
11784 return;
11785 };
11786 let pl = if c == 0 {
11787 &*rf.chroma_guard(0, rf.ch)
11788 } else {
11789 &*rf.chroma_guard(1, rf.ch)
11790 };
11791 mc_chroma_padded(
11792 pl,
11793 rf.cstride(),
11794 crate::CPAD,
11795 self.ccw,
11796 cch,
11797 mb_x * 8,
11798 mb_y * 8 + cry,
11799 crw,
11800 crh,
11801 mv1.0,
11802 mv1.1,
11803 &mut c_pred[c][cry * 8..cry * 8 + crw * crh],
11804 );
11805 }
11806 (true, true, true) => {
11807 let Some(rf) = self.refs.get(refi0 as usize) else {
11808 return;
11809 };
11810 let pl = if c == 0 {
11811 &*rf.chroma_guard(0, rf.ch)
11812 } else {
11813 &*rf.chroma_guard(1, rf.ch)
11814 };
11815 mc_chroma_padded(
11816 pl,
11817 rf.cstride(),
11818 crate::CPAD,
11819 self.ccw,
11820 cch,
11821 mb_x * 8,
11822 mb_y * 8 + cry,
11823 crw,
11824 crh,
11825 mv0.0,
11826 mv0.1,
11827 &mut c_pred[c][cry * 8..cry * 8 + crw * crh],
11828 );
11829 let mut cb = [0u8; 64];
11830 let Some(rf) = self.refs1.get(refi1 as usize) else {
11831 return;
11832 };
11833 let pl = if c == 0 {
11834 &*rf.chroma_guard(0, rf.ch)
11835 } else {
11836 &*rf.chroma_guard(1, rf.ch)
11837 };
11838 mc_chroma_padded(
11839 pl,
11840 rf.cstride(),
11841 crate::CPAD,
11842 self.ccw,
11843 cch,
11844 mb_x * 8,
11845 mb_y * 8 + cry,
11846 crw,
11847 crh,
11848 mv1.0,
11849 mv1.1,
11850 &mut cb[..crw * crh],
11851 );
11852 let dst = &mut c_pred[c][cry * 8..cry * 8 + crw * crh];
11853 match weights {
11854 None => {
11855 for (d, s) in dst.iter_mut().zip(&cb[..crw * crh]) {
11856 *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
11857 }
11858 }
11859 Some((w0, w1)) => {
11860 for (d, s) in dst.iter_mut().zip(&cb[..crw * crh]) {
11861 *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255)
11862 as u8;
11863 }
11864 }
11865 }
11866 }
11867 _ => {
11868 let (mut ca, mut cb) = ([0u8; 64], [0u8; 64]);
11869 if refi0 >= 0 {
11870 let Some(rf) = self.refs.get(refi0 as usize) else {
11871 return;
11872 };
11873 let pl = if c == 0 {
11874 &*rf.chroma_guard(0, rf.ch)
11875 } else {
11876 &*rf.chroma_guard(1, rf.ch)
11877 };
11878 mc_chroma_padded(
11879 pl,
11880 rf.cstride(),
11881 crate::CPAD,
11882 self.ccw,
11883 cch,
11884 mb_x * 8 + crx,
11885 mb_y * 8 + cry,
11886 crw,
11887 crh,
11888 mv0.0,
11889 mv0.1,
11890 &mut ca[..crw * crh],
11891 );
11892 }
11893 if refi1 >= 0 {
11894 let Some(rf) = self.refs1.get(refi1 as usize) else {
11895 return;
11896 };
11897 let pl = if c == 0 {
11898 &*rf.chroma_guard(0, rf.ch)
11899 } else {
11900 &*rf.chroma_guard(1, rf.ch)
11901 };
11902 mc_chroma_padded(
11903 pl,
11904 rf.cstride(),
11905 crate::CPAD,
11906 self.ccw,
11907 cch,
11908 mb_x * 8 + crx,
11909 mb_y * 8 + cry,
11910 crw,
11911 crh,
11912 mv1.0,
11913 mv1.1,
11914 &mut cb[..crw * crh],
11915 );
11916 }
11917 match (refi0 >= 0, refi1 >= 0) {
11918 (true, true) => {
11919 for dy in 0..crh {
11920 let (pr, qr) =
11921 (&ca[dy * crw..dy * crw + crw], &cb[dy * crw..dy * crw + crw]);
11922 let base = (cry + dy) * 8 + crx;
11923 let dst = &mut c_pred[c][base..base + crw];
11924 match weights {
11925 None => {
11926 for ((d, p), q) in dst.iter_mut().zip(pr).zip(qr) {
11927 *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
11928 }
11929 }
11930 Some((w0, w1)) => {
11931 for ((d, p), q) in dst.iter_mut().zip(pr).zip(qr) {
11932 *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6)
11933 .clamp(0, 255)
11934 as u8;
11935 }
11936 }
11937 }
11938 }
11939 }
11940 (true, false) => {
11941 for dy in 0..crh {
11942 let d = (cry + dy) * 8 + crx;
11943 c_pred[c][d..d + crw]
11944 .copy_from_slice(&ca[dy * crw..dy * crw + crw]);
11945 }
11946 }
11947 _ => {
11948 for dy in 0..crh {
11949 let d = (cry + dy) * 8 + crx;
11950 c_pred[c][d..d + crw]
11951 .copy_from_slice(&cb[dy * crw..dy * crw + crw]);
11952 }
11953 }
11954 }
11955 }
11956 }
11957 }
11958 }
11959
11960 /// B_Skip replay: regions into fresh prediction buffers, then the plane
11961 /// copy — no residual, no coefficient arrays.
11962 fn recon_b_skip(&mut self, mbx: usize, mby: usize, regions: &[BRegion]) {
11963 crate::RefFrame::set_mc_row_need(mby, self.mb_h * 16);
11964 let mut pred_y = [0u8; 256];
11965 let mut c_pred = [[0u8; 64]; 2];
11966 for r in regions {
11967 self.b_mc(
11968 mbx,
11969 mby,
11970 r.px,
11971 r.py,
11972 r.rw,
11973 r.rh,
11974 r.refi0,
11975 r.mv0,
11976 r.refi1,
11977 r.mv1,
11978 &mut pred_y,
11979 &mut c_pred,
11980 r.w,
11981 );
11982 }
11983 for dy in 0..16 {
11984 let d = (mby * 16 + dy) * self.cw + mbx * 16;
11985 self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
11986 }
11987 for c in 0..2 {
11988 let plane = if c == 0 {
11989 &mut self.rec_u
11990 } else {
11991 &mut self.rec_v
11992 };
11993 for dy in 0..8 {
11994 let d = (mby * 8 + dy) * self.ccw + mbx * 8;
11995 plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
11996 }
11997 }
11998 }
11999
12000 /// Replays one B macroblock's regions + residual (the worker half of the
12001 /// E3 seam). Mirrors the inline order exactly: MC regions in parse order
12002 /// into the prediction buffers, then the residual add (or the skip copy).
12003 fn recon_b(&mut self, j: &BJob) {
12004 crate::RefFrame::set_mc_row_need(j.mby, self.mb_h * 16);
12005 let mut pred_y = [0u8; 256];
12006 let mut c_pred = [[0u8; 64]; 2];
12007 for r in &j.regions {
12008 self.b_mc(
12009 j.mbx,
12010 j.mby,
12011 r.px,
12012 r.py,
12013 r.rw,
12014 r.rh,
12015 r.refi0,
12016 r.mv0,
12017 r.refi1,
12018 r.mv1,
12019 &mut pred_y,
12020 &mut c_pred,
12021 r.w,
12022 );
12023 }
12024 if j.skip {
12025 for dy in 0..16 {
12026 let d = (j.mby * 16 + dy) * self.cw + j.mbx * 16;
12027 self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
12028 }
12029 for c in 0..2 {
12030 let plane = if c == 0 {
12031 &mut self.rec_u
12032 } else {
12033 &mut self.rec_v
12034 };
12035 for dy in 0..8 {
12036 let d = (j.mby * 8 + dy) * self.ccw + j.mbx * 8;
12037 plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
12038 }
12039 }
12040 } else {
12041 self.cur_qp = j.qp;
12042 self.add_inter_residual(
12043 j.mbx,
12044 j.mby,
12045 &pred_y,
12046 &c_pred,
12047 j.luma_scan.as_ref(),
12048 j.luma8.as_ref(),
12049 &j.cdc,
12050 j.cac.as_ref(),
12051 j.cbp_chroma,
12052 &j.nnzs,
12053 );
12054 }
12055 }
12056}
12057
12058/// D9b: MC-call coalescing for a P inter MB from committed per-block (mv, ref).
12059/// Shared by residual and no-residual recon — filters are per-output-pixel so
12060/// wider rects are bit-identical to sixteen 4×4 calls.
12061fn coalesce_p_inter_mc(
12062 refs: &[crate::Ref],
12063 cw: usize,
12064 ccw: usize,
12065 mb_h: usize,
12066 mbx: usize,
12067 mby: usize,
12068 gmv: &[(i32, i32); 16],
12069 gref: &[usize; 16],
12070 pred_y: &mut [u8; 256],
12071 c_pred: &mut [[u8; 64]; 2],
12072) {
12073 let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
12074 let (rh16, cch) = (mb_h * 16, mb_h * 8);
12075 let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
12076 let t = y4 * 4 + x4;
12077 (0..h4).all(|dy| {
12078 (0..w4).all(|dx| {
12079 // 4x4 block grid: the index is always < 16, but LLVM cannot
12080 // infer it from the nested ranges. `& 15` states it.
12081 let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
12082 gmv[b] == gmv[t] && gref[b] == gref[t]
12083 })
12084 })
12085 };
12086 let mut mc_rect = |x4: usize, y4: usize, w4: usize, h4: usize| {
12087 let b = y4 * 4 + x4;
12088 let (mv, gr) = (gmv[b & 15], gref[b & 15]);
12089 let Some(reference) = refs.get(gr) else {
12090 return;
12091 };
12092 let (w, h) = (w4 * 4, h4 * 4);
12093 if w == 16 {
12094 rusty_h264_common::inter::with_mc_scratch(|scr| {
12095 rusty_h264_common::inter::mc_luma_padded_pre(
12096 scr,
12097 &*reference.luma_guard(reference.ch),
12098 reference.lstride(),
12099 crate::LPAD,
12100 cw,
12101 rh16,
12102 mbx * 16,
12103 mby * 16 + y4 * 4,
12104 w,
12105 h,
12106 mv.0,
12107 mv.1,
12108 &mut pred_y[y4 * 64..y4 * 64 + w * h],
12109 )
12110 });
12111 } else {
12112 let mut t = [0u8; 256];
12113 rusty_h264_common::inter::with_mc_scratch(|scr| {
12114 rusty_h264_common::inter::mc_luma_padded_pre(
12115 scr,
12116 &*reference.luma_guard(reference.ch),
12117 reference.lstride(),
12118 crate::LPAD,
12119 cw,
12120 rh16,
12121 mbx * 16 + x4 * 4,
12122 mby * 16 + y4 * 4,
12123 w,
12124 h,
12125 mv.0,
12126 mv.1,
12127 &mut t[..w * h],
12128 )
12129 });
12130 let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
12131 for dy in 0..h {
12132 pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w].copy_from_slice(&t[dy * w..dy * w + w]);
12133 }
12134 }
12135 let (cw4, ch4) = (w4 * 2, h4 * 2);
12136 let nc = cw4 * ch4;
12137 let (gu, gv) = (
12138 reference.chroma_guard(0, reference.ch),
12139 reference.chroma_guard(1, reference.ch),
12140 );
12141 // U+V paired: one setup serves both planes (see mc_chroma_padded_pair).
12142 if cw4 == 8 {
12143 let [cu, cv] = &mut *c_pred;
12144 rusty_h264_common::inter::mc_chroma_padded_pair(
12145 &gu,
12146 &gv,
12147 reference.cstride(),
12148 crate::CPAD,
12149 ccw,
12150 cch,
12151 mbx * 8,
12152 mby * 8 + y4 * 2,
12153 cw4,
12154 ch4,
12155 mv.0,
12156 mv.1,
12157 &mut cu[y4 * 16..y4 * 16 + nc],
12158 &mut cv[y4 * 16..y4 * 16 + nc],
12159 );
12160 } else {
12161 let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
12162 rusty_h264_common::inter::mc_chroma_padded_pair(
12163 &gu,
12164 &gv,
12165 reference.cstride(),
12166 crate::CPAD,
12167 ccw,
12168 cch,
12169 mbx * 8 + x4 * 2,
12170 mby * 8 + y4 * 2,
12171 cw4,
12172 ch4,
12173 mv.0,
12174 mv.1,
12175 &mut tu[..nc],
12176 &mut tv[..nc],
12177 );
12178 let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
12179 for (cc, tc) in [(0usize, &tu), (1, &tv)] {
12180 for dy in 0..ch4 {
12181 c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
12182 .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
12183 }
12184 }
12185 }
12186 };
12187 if rect_eq(0, 0, 4, 4) {
12188 mc_rect(0, 0, 4, 4);
12189 } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
12190 mc_rect(0, 0, 4, 2);
12191 mc_rect(0, 2, 4, 2);
12192 } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
12193 mc_rect(0, 0, 2, 4);
12194 mc_rect(2, 0, 2, 4);
12195 } else {
12196 for q in 0..4usize {
12197 let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
12198 if rect_eq(qx, qy, 2, 2) {
12199 mc_rect(qx, qy, 2, 2);
12200 } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
12201 mc_rect(qx, qy, 2, 1);
12202 mc_rect(qx, qy + 1, 2, 1);
12203 } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
12204 mc_rect(qx, qy, 1, 2);
12205 mc_rect(qx + 1, qy, 1, 2);
12206 } else {
12207 for j in 0..4usize {
12208 mc_rect(qx + (j % 2), qy + (j / 2), 1, 1);
12209 }
12210 }
12211 }
12212 }
12213}
12214
12215/// One deferred pixel-reconstruction job (entropy-decouple E1 seam).
12216enum EdcJob {
12217 Skip {
12218 mbx: usize,
12219 mby: usize,
12220 mv: (i32, i32),
12221 },
12222 Inter(Box<PInterJob>),
12223 B(Box<BJob>),
12224 /// B_Skip / no-residual direct: regions only. The full `BJob` carried
12225 /// 2.6 KB of ZEROED coefficient arrays for ~60% of B macroblocks — the
12226 /// wall-time regression's main CPU tax (B-heavy MT arm measured +45% CPU).
12227 BSkip {
12228 mbx: usize,
12229 mby: usize,
12230 regions: Vec<BRegion>,
12231 },
12232 /// P inter with `cbp == 0` — the P-side twin of `BSkip` (D9).
12233 InterNoRes(Box<PInterNoResJob>),
12234}
12235
12236/// A P inter macroblock with NO residual (`cbp == 0`) — motion only.
12237///
12238/// D9. `PInterJob` is 2,784 bytes and **93% of that is coefficient arrays**
12239/// (`luma_scan` 1024 + `luma8` 1024 + `cac` 512 + `cdc` 32 = 2,592). When
12240/// `cbp == 0` every one of them is ZERO, and the seam was heap-allocating,
12241/// filling, channel-passing and freeing all 2,592 bytes of nothing —
12242/// 12.8-37.5% of inter jobs on the x264 corpus, 15.8-44.1 MB per 60-frame pass.
12243///
12244/// This is the same pathology `EdcJob::BSkip` was introduced to fix on the B
12245/// side ("2.6 KB of ZEROED coefficient arrays for ~60% of B macroblocks — the
12246/// wall-time regression's main CPU tax"). It was never ported to P.
12247///
12248/// Consumer uses `recon_p_inter_nores` (MC + plane copy) — bit-identical to
12249/// `recon_p_inter` on zero residuals, without memset of 2.5 KB coeff arrays.
12250/// (`RS_H264_NORES=0` keeps the old full-job path for A/B — routed at the
12251/// CONSTRUCTOR, so this job carries only what nores recon reads.)
12252struct PInterNoResJob {
12253 mbx: usize,
12254 mby: usize,
12255 t8: bool,
12256 gmv: [(i32, i32); 16],
12257 gref: [u8; 16],
12258}
12259
12260/// The compact inputs of one CABAC P inter macroblock's reconstruction.
12261/// Shared all-zero residual planes. An Option-shaped residual field is `None`
12262/// exactly when the parse wrote nothing into it, and every consumer read is
12263/// already guarded by a zero-count test — so a `None` reader would have seen
12264/// zeros anyway. Pointing at one static beats zero-initialising 1 KB (luma) or
12265/// 512 B (chroma AC) of stack per macroblock.
12266static ZERO_LUMA_SCAN: [[i32; 16]; 16] = [[0; 16]; 16];
12267/// `(16384 + |td|/2) / td` for every `td` the spec can present. `td` is
12268/// `.clamp(-128, 127)` at both call sites (spec 8.4.1.2.3), so the divide has
12269/// exactly 256 possible inputs and a table replaces it EXACTLY -- this is not an
12270/// approximation, it is the same arithmetic evaluated ahead of time. `td == 0`
12271/// is guarded by the caller and maps to 0 here so the table has no hole.
12272static TX_FOR_TD: [i32; 256] = {
12273 let mut t = [0i32; 256];
12274 let mut i = 0usize;
12275 while i < 256 {
12276 let td = i as i32 - 128; // index 0 => -128 .. index 255 => 127
12277 t[i] = if td == 0 {
12278 0
12279 } else {
12280 (16384 + td.abs() / 2) / td
12281 };
12282 i += 1;
12283 }
12284 t
12285};
12286
12287#[inline(always)]
12288fn tx_for_td(td: i32) -> i32 {
12289 debug_assert!((-128..=127).contains(&td), "td must be clamped before this");
12290 TX_FOR_TD[(td.clamp(-128, 127) + 128) as usize]
12291}
12292
12293/// The "no coefficients" neighbour record. `mb_nzc` reads are reached only
12294/// through `if let Some(..) = top/left`, so an out-of-range index is
12295/// unreachable; this is what the unavailable branches already imply.
12296static ZERO_NZC: [u8; 24] = [0u8; 24];
12297static ZERO_CAC: [[[i32; 16]; 4]; 2] = [[[0; 16]; 4]; 2];
12298
12299struct PInterJob {
12300 mbx: usize,
12301 mby: usize,
12302 qp: u8,
12303 cbp_chroma: u32,
12304 /// transform_size_8x8_flag: the residual is `luma8`, not `luma_scan`.
12305 t8: bool,
12306 /// The committed per-block motion, copied at parse time so the worker
12307 /// never reads the parse thread's grids (E2). Ref indices clamped by the
12308 /// consumer, kept u8 (spec max 15).
12309 gmv: [(i32, i32); 16],
12310 gref: [u8; 16],
12311 /// `None` when no 4x4 luma block was coded — t8 macroblocks included,
12312 /// since those carry `luma8` instead.
12313 /// POOLED and reused across macroblocks (see `take_pinter_job`): only the
12314 /// blocks whose `nnzs` entry is nonzero hold the data of this macroblock; every
12315 /// other block is STALE and the consumer never reads it (it copies the
12316 /// prediction when nnz == 0). The parse zeroes exactly the blocks it is
12317 /// about to write.
12318 luma_scan: [[i32; 16]; 16],
12319 luma8: [[i32; 64]; 4],
12320 cdc: [[i32; 4]; 2],
12321 cac: [[[i32; 16]; 4]; 2],
12322 nnzs: [u8; 24],
12323}
12324
12325impl PInterJob {
12326 fn blank() -> Self {
12327 PInterJob {
12328 mbx: 0,
12329 mby: 0,
12330 qp: 0,
12331 cbp_chroma: 0,
12332 t8: false,
12333 gmv: [(0, 0); 16],
12334 gref: [0; 16],
12335 luma_scan: [[0; 16]; 16],
12336 luma8: [[0; 64]; 4],
12337 cdc: [[0; 4]; 2],
12338 cac: [[[0; 16]; 4]; 2],
12339 nnzs: [0; 24],
12340 }
12341 }
12342}
12343
12344/// Entropy-decouple master knob — DEFAULT ON since 2026-08-05 (`RS_H264_EDC=0`
12345/// opts out). E1 was expected to be cost-neutral scaffolding for the E2
12346/// thread; it BANKED on its own: 13/15 pairs, z=2.84, median +4.0% (pooled
12347/// 19/24, z=2.86). Mechanism: LOOP FISSION — batching a row's parsing and
12348/// then a row's reconstruction keeps each large code path's I-cache and
12349/// branch state hot, instead of alternating two giant bodies per macroblock.
12350fn edc_on() -> bool {
12351 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12352 // constant below; the env A/B arm exists only under `--features knobs`.
12353 #[cfg(not(feature = "knobs"))]
12354 {
12355 return true;
12356 }
12357 #[cfg(feature = "knobs")]
12358 {
12359 use core::sync::atomic::{AtomicU8, Ordering};
12360 static ON: AtomicU8 = AtomicU8::new(0);
12361 match ON.load(Ordering::Relaxed) {
12362 0 => {
12363 let v = !rusty_h264_common::knob("RS_H264_EDC").is_some_and(|v| v == "0");
12364 ON.store(if v { 1 } else { 2 }, Ordering::Relaxed);
12365 v
12366 }
12367 n => n == 1,
12368 }
12369 }
12370}
12371
12372/// E2/E3 worker-thread knob.
12373///
12374/// **Default OFF (2026-08-11).** ffmpeg's parallel unit is the PICTURE
12375/// (`decode_slice` + `hl_decode_mb` on the same thread). `edc_worker` is a
12376/// nested recon thread — the wrong function:
12377/// * 1T (`ffmpeg -threads 1`, pinvs pin): two threads thrash one core
12378/// * frame-MT (`ffmpeg -threads N`): each picture worker would spawn another
12379/// Frame-MT (`RS_H264_FRAME_THREADS=N`) is the ffmpeg-shaped pool. This
12380/// pipeline stays as an explicit oracle (`RS_H264_EDC_MT=1`) / old auto-gate
12381/// (`=auto`). `=0` forces inline.
12382/// D8: run every pixel reconstruction TWICE (the second pass is discarded work,
12383/// not discarded output -- recon is idempotent, so the bytes are unchanged).
12384/// `t(double) - t(single)` IS the pixel half's cost, which is the parallel
12385/// fraction the E2 seam can address. Byte-identity is the proof the ablation
12386/// did not change the program (unlike removing the stage, which cascades).
12387/// D9 compact no-residual P inter jobs. `RS_H264_NORES=0` restores the old
12388/// always-full-payload path for A/B (the arm must PIN the value, never inherit
12389/// a default -- an "off" arm that only omits an override measures
12390/// default-vs-default and prints all zeros).
12391/// D10 row batching — **DEFAULT ON. A DELIBERATE THROUGHPUT-OVER-LATENCY TRADE.**
12392///
12393/// Ships a row's pixel jobs in one message instead of one per macroblock:
12394/// 207,949 sends -> 3,086 (**67-70x fewer**), and **~3% less CPU**.
12395///
12396/// ⚠ IT COSTS 2-4% WALL on a single stream — 0.963x / 0.980x, **11/11 pairs on
12397/// two clips**, A/B'd against itself at a fixed queue bound. That is measured,
12398/// reproducible, and ACCEPTED, not an oversight. Do not "fix" it by flipping
12399/// the default; read this first.
12400///
12401/// WHY IT COSTS WALL: batching trades pipelining for synchronisation.
12402/// Per-macroblock sends let the worker start on job 1 immediately; a row batch
12403/// makes it idle until ~70 macroblocks are parsed, then hands it a burst.
12404///
12405/// WHY IT IS STILL THE RIGHT DEFAULT: wall time here is SINGLE-STREAM LATENCY;
12406/// CPU is THROUGHPUT. A host decoding many streams concurrently is CPU-bound,
12407/// not latency-bound, so 3% less CPU is ~3% more capacity while the 2-4% wall
12408/// cost falls on a dimension that is not the constraint. The 67x drop in
12409/// channel operations also removes a park/unpark storm that scales with the
12410/// number of runnable threads — it gets better, not worse, as the box fills.
12411///
12412/// `RS_H264_BATCH=0` restores per-macroblock sends for the latency-sensitive
12413/// single-stream case (playback, seek preview, anything where first-frame time
12414/// dominates).
12415fn batch_on() -> bool {
12416 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12417 // constant below; the env A/B arm exists only under `--features knobs`.
12418 #[cfg(not(feature = "knobs"))]
12419 {
12420 return true;
12421 }
12422 #[cfg(feature = "knobs")]
12423 {
12424 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12425 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_BATCH").is_some_and(|v| v == "0"))
12426 }
12427}
12428
12429fn nores_on() -> bool {
12430 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12431 // constant below; the env A/B arm exists only under `--features knobs`.
12432 #[cfg(not(feature = "knobs"))]
12433 {
12434 return true;
12435 }
12436 #[cfg(feature = "knobs")]
12437 {
12438 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12439 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_NORES").is_some_and(|v| v == "0"))
12440 }
12441}
12442
12443/// D13 A/B: always allocate B-only CABAC neighbour grids even on P/I slices.
12444fn fat_slice_on() -> bool {
12445 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12446 // constant below; the env A/B arm exists only under `--features knobs`.
12447 #[cfg(not(feature = "knobs"))]
12448 {
12449 return false;
12450 }
12451 #[cfg(feature = "knobs")]
12452 {
12453 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12454 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_FAT_SLICE").is_some_and(|v| v == "1"))
12455 }
12456}
12457
12458/// MEASUREMENT KNOB — `RS_H264_NO_SKIPBAND=1` forces the per-MB skip path so the
12459/// mb_skip_run band coalescer can be A/B'd paired on ONE binary. Inert when unset.
12460fn no_skipband() -> bool {
12461 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12462 // constant below; the env A/B arm exists only under `--features knobs`.
12463 #[cfg(not(feature = "knobs"))]
12464 {
12465 return false;
12466 }
12467 #[cfg(feature = "knobs")]
12468 {
12469 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12470 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_SKIPBAND").is_some_and(|v| v == "1"))
12471 }
12472}
12473
12474/// MEASUREMENT KNOB — `RS_H264_NO_RUNMV=1` forces the full skip_mv derivation
12475/// on every P_Skip (disables the run-theorem forced-(0,0) branch) for paired A/B.
12476fn no_runmv() -> bool {
12477 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12478 // constant below; the env A/B arm exists only under `--features knobs`.
12479 #[cfg(not(feature = "knobs"))]
12480 {
12481 return false;
12482 }
12483 #[cfg(feature = "knobs")]
12484 {
12485 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12486 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_RUNMV").is_some_and(|v| v == "1"))
12487 }
12488}
12489
12490/// MEASUREMENT KNOB — `RS_H264_NO_SKIPFP=1` disables the P_Skip single fast
12491/// paths (full-pel direct copy + identity-weight-pass skip) for paired A/B.
12492fn no_skipfp() -> bool {
12493 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12494 // constant below; the env A/B arm exists only under `--features knobs`.
12495 #[cfg(not(feature = "knobs"))]
12496 {
12497 return false;
12498 }
12499 #[cfg(feature = "knobs")]
12500 {
12501 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12502 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_SKIPFP").is_some_and(|v| v == "1"))
12503 }
12504}
12505
12506/// MEASUREMENT KNOB — `RS_H264_NO_BSKIPFAST=1` forces the full decode_b_direct
12507/// path for B_Skip so the zero-bi fast path can be A/B'd paired on ONE binary.
12508fn no_bskipfast() -> bool {
12509 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12510 // constant below; the env A/B arm exists only under `--features knobs`.
12511 #[cfg(not(feature = "knobs"))]
12512 {
12513 return false;
12514 }
12515 #[cfg(feature = "knobs")]
12516 {
12517 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12518 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_BSKIPFAST").is_some_and(|v| v == "1"))
12519 }
12520}
12521
12522fn double_recon() -> bool {
12523 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12524 // constant below; the env A/B arm exists only under `--features knobs`.
12525 #[cfg(not(feature = "knobs"))]
12526 {
12527 return false;
12528 }
12529 #[cfg(feature = "knobs")]
12530 {
12531 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12532 // `== "1"`, not `is_some()`: the old presence test meant even
12533 // `RS_H264_DOUBLE_RECON=0` DOUBLED the recon work — the one knob in the
12534 // inventory whose "off" spelling turned it on (2026-08-27 audit, site 8).
12535 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_DOUBLE_RECON").is_some_and(|v| v == "1"))
12536 }
12537}
12538
12539fn edc_bound() -> usize {
12540 static V: rusty_h264_common::once::OnceLock<usize> = rusty_h264_common::once::OnceLock::new();
12541 *V.get_or_init(|| {
12542 rusty_h264_common::knob("RS_H264_EDC_BOUND")
12543 .and_then(|v| v.parse().ok())
12544 .unwrap_or(256)
12545 })
12546}
12547
12548/// D12 — E2 THREADING DISPATCH. Fires on `720p-or-smaller AND bits/MB > 38.4`.
12549///
12550/// The seam threads unconditionally before this, and that shipped a REGRESSION:
12551/// 8-10% slower wall on main profile for 38-56% more CPU. It cannot pay in
12552/// general — the pixel half is only ~15.6% of decode, so Amdahl caps two
12553/// threads at 1.085x — but it DOES win on some streams, so the answer is a
12554/// dispatch, not abandonment.
12555///
12556/// Fitted with `bench/examples/gate_optimizer.rs` over **28 interleaved
12557/// configurations** (12 clips x core counts 4-8, `bench/pinmtx.ps1`):
12558/// **net +29.40 of +30.70 perfect**, train +25.10 AND holdout +4.30, worst
12559/// fired class **+2.94**, precision 0.80. It forgoes +0.40 of wins to avoid
12560/// **169.90** of losses. Calibration: depth-2 **2/300** rules passed (0.67%),
12561/// so the separation carries information.
12562///
12563/// Per clause, both load-bearing (dropping either: -20.50 / -50.60, 6-7 big
12564/// losers):
12565/// * `bits/MB > 38.4` — coefficient density is the runtime proxy for PIXEL
12566/// SHARE: more coefficients means more residual work on the far side of the
12567/// seam. Threshold sits in an open gap (highest excluded 35.4, lowest firing
12568/// 41.4). Note this is the OPPOSITE direction to an earlier hand-fitted
12569/// `bits < 65`, which was fitted to one clip and falsified.
12570/// * frame <= 720p — every 1080p configuration measured loses, including a
12571/// low-density one, so density alone is not sufficient.
12572/// * `cabac` — ADDED 2026-08-07 after the CAVLC arm made those units
12573/// measurable. bits/MB DOES NOT TRANSFER ACROSS ENTROPY CODERS: CAVLC needs
12574/// more bits for the SAME coefficients, so its density (62-65) reads deep
12575/// inside the firing region while its pixel work is unchanged. Without this
12576/// clause the rule routed CAVLC into threading, where it measured 1.29-1.49x
12577/// SLOWER — net -52.30, worst class -40.85. With it, +29.40 and worst class
12578/// +2.94. `gate_optimizer` could not find this: the rule needs THREE clauses
12579/// and the search is depth-2 (both depth-2 pairs fail, -20.50 / -50.60).
12580///
12581/// The estimate comes from ALREADY-DECODED slices, so the first slice of a
12582/// stream runs INLINE (the safe arm) until a measurement exists. Both arms are
12583/// byte-identical, so the choice can never affect output.
12584/// Invoked only by `RS_H264_EDC_MT=auto` (the pre-2026-08-11 default).
12585fn edc_dispatch(mb_w: usize, mb_h: usize, bits_per_mb: f64, cabac: bool) -> bool {
12586 const BITS_MIN: f64 = 38.4;
12587 const MAX_MBS: usize = 5000; // 720p = 3600, 1080p = 8160
12588 // The `cabac` clause is NOT cosmetic — see the header note. Without it this
12589 // rule scores net -52.30 with worst class -40.85 once CAVLC units are in the
12590 // corpus, because CAVLC's bits/MB is inflated by a less efficient entropy
12591 // coder rather than by more pixel work.
12592 cabac && bits_per_mb > BITS_MIN && mb_w * mb_h <= MAX_MBS
12593}
12594
12595fn edc_mt() -> Option<bool> {
12596 static V: rusty_h264_common::once::OnceLock<Option<bool>> =
12597 rusty_h264_common::once::OnceLock::new();
12598 *V.get_or_init(
12599 || match rusty_h264_common::knob("RS_H264_EDC_MT").as_deref() {
12600 Some("0") => Some(false),
12601 Some("1") => Some(true),
12602 Some("auto") => None,
12603 _ => Some(false),
12604 },
12605 )
12606}
12607
12608/// Spawn `edc_worker`? ffmpeg never does this: the picture thread owns recon.
12609/// Frame-MT workers (`FRAME_THREADS>1`) always inline. Otherwise honor the knob
12610/// (`1` / `0` / `auto`→[`edc_dispatch`]; unset = inline).
12611fn edc_spawn_worker(mb_w: usize, mb_h: usize, bits_per_mb: f64, cabac: bool) -> bool {
12612 #[cfg(not(feature = "std"))]
12613 {
12614 let _ = (mb_w, mb_h, bits_per_mb, cabac);
12615 false
12616 }
12617 #[cfg(feature = "std")]
12618 {
12619 if crate::frame_threads() > 1 {
12620 return false;
12621 }
12622 edc_mt().unwrap_or_else(|| edc_dispatch(mb_w, mb_h, bits_per_mb, cabac))
12623 }
12624}
12625
12626/// Picture-end bS precompute (rowdb-off fallback). `RS_H264_BS_PRE=0` opts out.
12627fn bs_pre_on() -> bool {
12628 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12629 // constant below; the env A/B arm exists only under `--features knobs`.
12630 #[cfg(not(feature = "knobs"))]
12631 {
12632 return true;
12633 }
12634 #[cfg(feature = "knobs")]
12635 {
12636 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12637 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_BS_PRE").is_some_and(|v| v == "0"))
12638 }
12639}
12640
12641/// Row-interleaved deblocking master knob: `RS_H264_ROWDB=0` opts out,
12642/// restoring the picture-end pipeline (WHYS Part 17) as the A/B comparator.
12643/// MEASUREMENT KNOB — `RS_H264_KIND_LOADS=1` restores the Blk::load-based
12644/// kind arms in derive_bs_row (see the routing comment there) for paired A/B.
12645fn kind_loads() -> bool {
12646 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12647 // constant below; the env A/B arm exists only under `--features knobs`.
12648 #[cfg(not(feature = "knobs"))]
12649 {
12650 return false;
12651 }
12652 #[cfg(feature = "knobs")]
12653 {
12654 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12655 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_KIND_LOADS").is_some_and(|v| v == "1"))
12656 }
12657}
12658
12659fn rowdb_on() -> bool {
12660 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12661 // constant below; the env A/B arm exists only under `--features knobs`.
12662 #[cfg(not(feature = "knobs"))]
12663 {
12664 return true;
12665 }
12666 #[cfg(feature = "knobs")]
12667 {
12668 use core::sync::atomic::{AtomicU8, Ordering};
12669 static ON: AtomicU8 = AtomicU8::new(0);
12670 match ON.load(Ordering::Relaxed) {
12671 0 => {
12672 let v = !rusty_h264_common::knob("RS_H264_ROWDB").is_some_and(|v| v == "0");
12673 ON.store(if v { 1 } else { 2 }, Ordering::Relaxed);
12674 v
12675 }
12676 n => n == 1,
12677 }
12678 }
12679}
12680
12681/// A/B: per-MB `row_hook` body even when no row has completed (old behaviour).
12682fn rowhook_eager() -> bool {
12683 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12684 // constant below; the env A/B arm exists only under `--features knobs`.
12685 #[cfg(not(feature = "knobs"))]
12686 {
12687 return false;
12688 }
12689 #[cfg(feature = "knobs")]
12690 {
12691 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12692 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_ROWHOOK_EAGER").is_some_and(|v| v == "1"))
12693 }
12694}
12695
12696/// A/B: `RS_H264_DIRECT_MEMO=0` rewalks spatial-direct neighbours every 8×8.
12697#[inline]
12698fn direct_memo_on() -> bool {
12699 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12700 // constant below; the env A/B arm exists only under `--features knobs`.
12701 #[cfg(not(feature = "knobs"))]
12702 {
12703 return true;
12704 }
12705 #[cfg(feature = "knobs")]
12706 {
12707 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12708 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_DIRECT_MEMO").is_some_and(|v| v == "0"))
12709 }
12710}
12711
12712/// 4×4-block (z-order) → 30-entry (6-stride) mv/ref/mvd cache index (openh264
12713/// g_kCache30ScanIdx). Top neighbour = cache[idx-6], left = cache[idx-1].
12714use rusty_h264_common::cabac_tables::CACHE30;
12715
12716/// z-order 4×4-block → raster index (openh264 g_kuiScan4). Per-MB mvd/ref state is
12717/// stored raster-indexed (matching how neighbour blocks 3/7/11/15 and 12..15 are read).
12718use rusty_h264_common::cabac_tables::G_SCAN4;
12719
12720/// P `sub_mb_type` CABAC (openh264 `ParseSubMBTypeCabac`, ctx 21). 0=8×8, 1=8×4, 2=4×8, 3=4×4.
12721fn parse_sub_mb_type_p_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
12722 const S: usize = 21;
12723 let (data, mut e, ctx) = cab.view();
12724 let v = if e.decode_decision(data, ctx, S) != 0 {
12725 0
12726 } else if e.decode_decision(data, ctx, S + 1) != 0 {
12727 3 - e.decode_decision(data, ctx, S + 2)
12728 } else {
12729 1
12730 };
12731 cab.commit(e);
12732 v
12733}
12734
12735/// Intra `mb_type` sub-parse for P/B slices (openh264 `DecodeCabacIntraMbType`, `base`=32
12736/// for B), on a live engine view. Returns 0 = I_4x4, 1..=24 = I_16x16, 25 = I_PCM (in
12737/// the intra numbering).
12738#[inline(always)]
12739fn intra_mb_type_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx, base: usize) -> u32 {
12740 if e.decode_decision(data, ctx, base) == 0 {
12741 return 0; // I_4x4
12742 }
12743 if e.decode_terminate(data) {
12744 return 25; // I_PCM
12745 }
12746 let mut t = 1 + 12 * e.decode_decision(data, ctx, base + 1) as u32; // cbp_luma != 0
12747 if e.decode_decision(data, ctx, base + 2) != 0 {
12748 t += 4 + 4 * e.decode_decision(data, ctx, base + 2) as u32;
12749 }
12750 t += 2 * e.decode_decision(data, ctx, base + 3) as u32;
12751 t += e.decode_decision(data, ctx, base + 3) as u32;
12752 t
12753}
12754
12755/// B `mb_type` CABAC (openh264 `ParseMBTypeBSliceCabac`, ctx base 27). `ctx_inc` = (left
12756/// avail & !direct) + (top avail & !direct). Returns 0 = B_Direct_16x16, 1..=21 = the
12757/// L0/L1/Bi 16x16/16x8/8x16 shapes, 22 = B_8x8, 23.. = intra (mb_type - 23).
12758/// Test-only alias so the ENCODER crate can gate `cb_mb_type_b` against this
12759/// parser directly -- they are exact inverses, so a round-trip is a complete gate.
12760#[doc(hidden)]
12761pub fn parse_mb_type_b(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
12762 parse_mb_type_b_cabac(cab, ctx_inc)
12763}
12764
12765#[inline]
12766fn parse_mb_type_b_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
12767 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
12768 const B: usize = 27;
12769 let (data, mut e, ctx) = cab.view();
12770 let v = mb_type_b_eng(&mut e, data, ctx, B, ctx_inc);
12771 cab.commit(e);
12772 v
12773}
12774
12775#[inline(always)]
12776fn mb_type_b_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx, b: usize, ctx_inc: usize) -> u32 {
12777 if e.decode_decision(data, ctx, b + ctx_inc) == 0 {
12778 return 0; // B_Direct_16x16
12779 }
12780 if e.decode_decision(data, ctx, b + 3) == 0 {
12781 return 1 + e.decode_decision(data, ctx, b + 5); // 16x16 L0 / L1
12782 }
12783 let mut m = e.decode_decision(data, ctx, b + 4) << 3;
12784 m |= e.decode_decision(data, ctx, b + 5) << 2;
12785 m |= e.decode_decision(data, ctx, b + 5) << 1;
12786 m |= e.decode_decision(data, ctx, b + 5);
12787 if m < 8 {
12788 return m + 3;
12789 }
12790 if m == 13 {
12791 return intra_mb_type_eng(e, data, ctx, 32) + 23;
12792 }
12793 if m == 14 {
12794 return 11; // B_Bi_8x16
12795 }
12796 if m == 15 {
12797 return 22; // B_8x8
12798 }
12799 m = (m << 1) | e.decode_decision(data, ctx, b + 5);
12800 m - 4
12801}
12802
12803/// B `sub_mb_type` CABAC (openh264 `ParseBSubMBTypeCabac`, ctx base 36). Returns 0..=12
12804/// per spec Table 7-18 (0 = B_Direct_8x8, 1 = B_L0_8x8, ..., 12 = B_Bi_4x4).
12805fn parse_sub_mb_type_b_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
12806 const B: usize = 36;
12807 let (data, mut e, ctx) = cab.view();
12808 let v = 'v: {
12809 if e.decode_decision(data, ctx, B) == 0 {
12810 break 'v 0; // B_Direct_8x8
12811 }
12812 if e.decode_decision(data, ctx, B + 1) == 0 {
12813 break 'v 1 + e.decode_decision(data, ctx, B + 3); // B_L0_8x8 / B_L1_8x8
12814 }
12815 let mut st = 3u32;
12816 if e.decode_decision(data, ctx, B + 2) != 0 {
12817 if e.decode_decision(data, ctx, B + 3) != 0 {
12818 break 'v 11 + e.decode_decision(data, ctx, B + 3); // B_L1_4x4 / B_Bi_4x4
12819 }
12820 st += 4;
12821 }
12822 st += 2 * e.decode_decision(data, ctx, B + 3);
12823 st += e.decode_decision(data, ctx, B + 3);
12824 st
12825 };
12826 cab.commit(e);
12827 v
12828}
12829
12830/// Parse one motion partition's `mvd` (x,y) and splat it into the 30-entry cache + the
12831/// per-MB raster mvd/ref state. `part_idx` = the partition's top-left z-order block (for
12832/// the ctxInc neighbour lookup); `zblocks` = every z-order 4×4 block the partition covers.
12833fn parse_mvd_partition(
12834 cab: &mut crate::cabac::Cabac,
12835 part_idx: usize,
12836 zblocks: &[usize],
12837 mvdc: &mut [[i16; 2]; 30],
12838 refc: &mut [i8; 30],
12839 mmvd: &mut [[i16; 2]; 16],
12840 mref: &mut [i8; 16],
12841 ref_idx: i8,
12842) -> (i32, i32) {
12843 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
12844 // `CACHE30` is [usize; 16] and every entry lies in [7, 28]; the mask and
12845 // clamp are semantic no-ops that hand LLVM the ranges it cannot infer, so
12846 // the `refc[s - 6]` / `mvdc[s - 1]` indexes into the 30-entry caches become
12847 // provable instead of bounds-checked.
12848 let s = CACHE30[part_idx & 15].clamp(6, 29);
12849 // Neighbour AVAILABILITY is per-neighbour, not per-component: the old closure
12850 // re-tested both refc slots for each of x and y, four loads to answer two
12851 // questions. Resolve once, then sum each component.
12852 let (av_b, av_a) = (refc[s - 6] >= 0, refc[s - 1] >= 0);
12853 let (nb, na) = (mvdc[s - 6], mvdc[s - 1]);
12854 let ctx = |comp: usize| -> usize {
12855 let mut a = 0i32;
12856 if av_b {
12857 a += nb[comp].unsigned_abs() as i32;
12858 }
12859 if av_a {
12860 a += na[comp].unsigned_abs() as i32;
12861 }
12862 if a >= 3 {
12863 1 + (a > 32) as usize
12864 } else {
12865 0
12866 }
12867 };
12868 let (cx, cy) = (ctx(0), ctx(1));
12869 // Both components on ONE engine view (see `Cabac::view`).
12870 let (data, mut e, ctx) = cab.view();
12871 let mvx = mvd_component(&mut e, data, ctx, 0, cx);
12872 let mvy = mvd_component(&mut e, data, ctx, 1, cy);
12873 cab.commit(e);
12874 let mvd = [mvx, mvy];
12875 if zblocks.len() == 16 {
12876 // Whole-macroblock partition (the dominant B shape): every raster slot
12877 // takes the same value — one fill each instead of 16 indexed stores.
12878 mmvd.fill(mvd);
12879 mref.fill(ref_idx);
12880 // The 16 z-blocks land on FOUR contiguous 4-entry runs of the 6-stride
12881 // cache (rows 7.., 13.., 19.., 25..): eight fills instead of 32 stores.
12882 for r in [7usize, 13, 19, 25] {
12883 mvdc[r..r + 4].fill(mvd);
12884 refc[r..r + 4].fill(ref_idx);
12885 }
12886 } else {
12887 for &zb in zblocks {
12888 // Each table was being read twice per block.
12889 let (c, g) = (CACHE30[zb & 15], G_SCAN4[zb & 15]);
12890 mvdc[c] = mvd;
12891 refc[c] = ref_idx;
12892 mmvd[g] = mvd;
12893 mref[g] = ref_idx;
12894 }
12895 }
12896 (mvx as i32, mvy as i32)
12897}
12898
12899/// `ref_idx_l0` (P) CABAC — mirror of the encoder `cb_ref_idx`. Unary, ctxIdxOffset
12900/// 54: binIdx 0 → `ctx0` (condTermFlagA + 2·condTermFlagB), binIdx 1 → 4, binIdx ≥2 → 5.
12901pub fn parse_ref_idx_cabac(cab: &mut crate::cabac::Cabac, ctx0: usize) -> i8 {
12902 const B: usize = 54;
12903 let (data, mut e, ctx) = cab.view();
12904 let mut r = 0i8;
12905 let mut bin_idx = 0u32;
12906 // Cap the unary length: valid ref_idx <= 15 (16 refs max); the cap keeps a corrupt
12907 // stream from looping unboundedly. The MC clamps the index, so an over-range value
12908 // is decoded as garbage (never a panic) -- the robustness contract, not correctness.
12909 while bin_idx < 32 {
12910 let c = match bin_idx {
12911 0 => ctx0,
12912 1 => 4,
12913 _ => 5,
12914 };
12915 if e.decode_decision(data, ctx, B + c) == 0 {
12916 break;
12917 }
12918 r += 1;
12919 bin_idx += 1;
12920 }
12921 cab.commit(e);
12922 r
12923}
12924
12925/// UEG3 mvd suffix (openh264 `DecodeUEGMvCabac`): TU prefix at `base + {0,1,2,3,3,..}`
12926/// (<=7), then EG3 bypass. Inlined into `parse_mvd_partition` for the same
12927/// register-residency reason as `cabac_ueg_level`.
12928#[inline(always)]
12929fn decode_ueg_mv(e: &mut Eng, data: &[u8], ctx: &mut Ctx, base: usize) -> u32 {
12930 const P2C: [usize; 8] = [0, 1, 2, 3, 3, 3, 3, 3];
12931 if e.decode_decision(data, ctx, base) == 0 {
12932 return 0;
12933 }
12934 let mut code = 0u32;
12935 let mut count = 1usize;
12936 let mut tmp;
12937 loop {
12938 // `count` is 1..=7 here; `& 7` is the own bound of the 8-entry table.
12939 tmp = e.decode_decision(data, ctx, base + P2C[count & 7]);
12940 code += 1;
12941 count += 1;
12942 if tmp == 0 || count == 8 {
12943 break;
12944 }
12945 }
12946 if tmp != 0 {
12947 let (v, e2) = cabac_exp_bypass(*e, data, 3);
12948 *e = e2;
12949 code += v + 1;
12950 }
12951 code
12952}
12953
12954/// One `mvd` component (openh264 `ParseMvdInfoCabac`) on a live engine view.
12955/// `ctx_inc` (0/1/2) from the neighbour |mvd| sum. ctxIdxOffset 40 (x) / 47 (y).
12956#[inline(always)]
12957fn mvd_component(e: &mut Eng, data: &[u8], ctx: &mut Ctx, comp: usize, ctx_inc: usize) -> i16 {
12958 let base = 40 + comp * 7; // NEW_CTX_OFFSET_MVD + comp*CTX_NUM_MVD
12959 if e.decode_decision(data, ctx, base + ctx_inc) == 0 {
12960 return 0;
12961 }
12962 let mag = (decode_ueg_mv(e, data, ctx, base + 3) + 1) as i16;
12963 if e.decode_bypass(data) != 0 {
12964 -mag
12965 } else {
12966 mag
12967 }
12968}
12969
12970/// P-slice `mb_type` CABAC (openh264 `ParseMBTypePSliceCabac`). Returns 0..3 = inter
12971/// (P_L0_16x16 / P_16x8 / P_8x16 / P_8x8), 5 = I_4x4, 6..29 = I_16x16, 30 = I_PCM.
12972#[inline(always)]
12973fn mb_type_p_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx) -> u32 {
12974 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
12975 const S: usize = 11; // NEW_CTX_OFFSET_SKIP; P mb_type contexts hang off it
12976 let v = 'v: {
12977 if e.decode_decision(data, ctx, S + 3) == 0 {
12978 // inter
12979 break 'v if e.decode_decision(data, ctx, S + 4) != 0 {
12980 if e.decode_decision(data, ctx, S + 6) != 0 {
12981 1
12982 } else {
12983 2
12984 }
12985 } else if e.decode_decision(data, ctx, S + 5) != 0 {
12986 3
12987 } else {
12988 0
12989 };
12990 }
12991 // intra (prefix bit was 1)
12992 if e.decode_decision(data, ctx, S + 6) == 0 {
12993 break 'v 5; // I_4x4
12994 }
12995 if e.decode_terminate(data) {
12996 break 'v 30; // I_PCM
12997 }
12998 let mut t = 6 + e.decode_decision(data, ctx, S + 7) * 12;
12999 if e.decode_decision(data, ctx, S + 8) != 0 {
13000 t += 4;
13001 if e.decode_decision(data, ctx, S + 8) != 0 {
13002 t += 4;
13003 }
13004 }
13005 t += e.decode_decision(data, ctx, S + 9) << 1;
13006 t += e.decode_decision(data, ctx, S + 9);
13007 t
13008 };
13009 v
13010}
13011
13012/// I-slice `mb_type` CABAC parse (spec §9.3.2.5 / openh264 `ParseMBTypeISliceCabac`).
13013/// `ctx_inc` = (left MB is I_16x16/non-intra) + (top MB is …), i.e. 0..2; the corner
13014/// MB has no neighbours so `ctx_inc = 0`. Returns the raw mb_type: 0 = I_NxN (I_4x4/
13015/// I_8x8), 1..24 = I_16x16 (pred-mode/cbp packed), 25 = I_PCM.
13016fn parse_mb_type_i_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
13017 const O: usize = 3; // ctxIdxOffset for I-slice mb_type
13018 let (data, mut e, ctx) = cab.view();
13019 let v = 'v: {
13020 if e.decode_decision(data, ctx, O + ctx_inc) == 0 {
13021 break 'v 0; // I_NxN
13022 }
13023 if e.decode_terminate(data) {
13024 break 'v 25; // I_PCM
13025 }
13026 let mut t = 1 + e.decode_decision(data, ctx, O + 3) * 12; // CBP luma: 0 or 12
13027 if e.decode_decision(data, ctx, O + 4) != 0 {
13028 t += 4; // CBP chroma 1 or 2
13029 if e.decode_decision(data, ctx, O + 5) != 0 {
13030 t += 4;
13031 }
13032 }
13033 t += e.decode_decision(data, ctx, O + 6) << 1; // I_16x16 pred mode (2 bins)
13034 t += e.decode_decision(data, ctx, O + 7);
13035 t
13036 };
13037 cab.commit(e);
13038 v
13039}
13040
13041/// One `Intra_4x4` (or `8x8`) pred-mode parse on a live engine view (openh264
13042/// `ParseIntraPredModeLumaCabac`): `prev_intra4x4_pred_mode_flag` (ctx 68) then, if 0,
13043/// `rem_intra4x4_pred_mode` (3 bins at ctx 69). Returns `-1` for "use predicted
13044/// mode", else the 0..7 remainder. The 16 (or 4) reads of a macroblock share ONE view.
13045#[inline(always)]
13046fn intra4x4_pred_mode_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx) -> i32 {
13047 const IPR: usize = 68;
13048 if e.decode_decision(data, ctx, IPR) == 1 {
13049 return -1; // prev_intra4x4_pred_mode_flag = 1
13050 }
13051 let mut m = e.decode_decision(data, ctx, IPR + 1) as i32;
13052 m |= (e.decode_decision(data, ctx, IPR + 1) as i32) << 1;
13053 m |= (e.decode_decision(data, ctx, IPR + 1) as i32) << 2;
13054 m
13055}
13056
13057/// One `Intra_4x4` (or `8x8`) pred-mode CABAC parse (openh264 `ParseIntraPredModeLuma
13058/// Cabac`): `prev_intra4x4_pred_mode_flag` (ctx 68) then, if 0, `rem_intra4x4_pred_mode`
13059/// (3 bins at ctx 69). Returns `-1` for "use predicted mode", else the 0..7 remainder.
13060
13061/// `intra_chroma_pred_mode` CABAC parse (openh264 `ParseIntraPredModeChromaCabac`):
13062/// TU(cMax=3) — bin0 at ctx `64 + ctx_inc` (ctx_inc from neighbour chroma modes, 0 for
13063/// the corner MB), the rest at ctx 67. Returns the mode 0..3.
13064fn parse_intra_chroma_pred_mode_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
13065 const CIPR: usize = 64;
13066 if cab.decode_decision(CIPR + ctx_inc) == 0 {
13067 return 0;
13068 }
13069 if cab.decode_decision(CIPR + 3) == 0 {
13070 return 1;
13071 }
13072 if cab.decode_decision(CIPR + 3) == 0 {
13073 return 2;
13074 }
13075 3
13076}
13077
13078/// `coded_block_pattern` CABAC parse (openh264 `ParseCbpInfoCabac`), corner-MB variant
13079/// (top/left neighbours unavailable → their terms are 0). ctxIdxOffset 73 (luma) with 4
13080/// z-order 8×8 bins whose ctxInc uses the EARLIER-decoded bits within this MB, then
13081/// chroma bits at 77/81. Returns cbp: bits 0-3 = luma 8×8, bits 4-5 = chroma pattern.
13082pub fn parse_cbp_cabac(cab: &mut crate::cabac::Cabac, top: Option<u8>, left: Option<u8>) -> u32 {
13083 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
13084 const CBP: usize = 73;
13085 // Neighbour terms folded ONCE into two 4-bit masks: bit k of `tz` / `lz` is
13086 // the condTermFlag "the 8x8 block k of that neighbour is NOT coded"
13087 // (unavailable => 0). The six per-bin `Option::map_or` closures this
13088 // replaces re-matched the same two Options for every luma bin.
13089 let tz = top.map_or(0u32, |c| !(c as u32) & 0xF);
13090 let lz = left.map_or(0u32, |c| !(c as u32) & 0xF);
13091 let tc = top.map_or(0u32, |c| (c >> 4) as u32);
13092 let lc = left.map_or(0u32, |c| (c >> 4) as u32);
13093 let (data, mut e, ctx) = cab.view();
13094 // Luma, 4 8x8 blocks in z-order. Top uses cbp bits 2/3, left uses 1/3; a
13095 // block already decoded in THIS macroblock contributes `bin ^ 1`.
13096 let b0 = e.decode_decision(
13097 data,
13098 ctx,
13099 CBP + (((lz >> 1) & 1) + (((tz >> 2) & 1) << 1)) as usize,
13100 );
13101 let b1 = e.decode_decision(
13102 data,
13103 ctx,
13104 CBP + ((b0 ^ 1) + (((tz >> 3) & 1) << 1)) as usize,
13105 );
13106 let b2 = e.decode_decision(
13107 data,
13108 ctx,
13109 CBP + (((lz >> 3) & 1) + ((b0 ^ 1) << 1)) as usize,
13110 );
13111 let b3 = e.decode_decision(data, ctx, CBP + ((b2 ^ 1) + ((b1 ^ 1) << 1)) as usize);
13112 let mut cbp = b0 | (b1 << 1) | (b2 << 2) | (b3 << 3);
13113 // Chroma (4:2:0). ctxInc from neighbour chroma cbp (>>4).
13114 let (ct, cl) = ((tc != 0) as u32, (lc != 0) as u32);
13115 if e.decode_decision(data, ctx, CBP + 4 + (cl + (ct << 1)) as usize) != 0 {
13116 let (ct2, cl2) = ((tc == 2) as u32, (lc == 2) as u32);
13117 let c1 = e.decode_decision(data, ctx, CBP + 8 + (cl2 + (ct2 << 1)) as usize);
13118 cbp |= 1 << (4 + c1);
13119 }
13120 cab.commit(e);
13121 cbp
13122}
13123
13124/// Inlined: `num_ref_active` is loop-invariant at every partition site, so the
13125/// te(v) branch folds and the bit/ue read lands in the caller with no call.
13126#[inline]
13127fn read_ref_idx(r: &mut BitReader, num_ref_active: usize) -> Result<i32, OutOfData> {
13128 if num_ref_active == 2 {
13129 Ok(if r.read_bit()? { 0 } else { 1 }) // te(v): value = !bit
13130 } else {
13131 Ok(r.read_ue()? as i32)
13132 }
13133}
13134
13135/// B-partition prediction direction.
13136#[derive(Clone, Copy, PartialEq)]
13137enum BPred {
13138 L0,
13139 L1,
13140 Bi,
13141}
13142impl BPred {
13143 /// Whether this direction uses reference list `list` (0 or 1).
13144 fn uses(self, list: usize) -> bool {
13145 matches!(
13146 (self, list),
13147 (BPred::L0, 0) | (BPred::L1, 1) | (BPred::Bi, 0) | (BPred::Bi, 1)
13148 )
13149 }
13150}
13151
13152const B16X16: &[(usize, usize, usize, usize)] = &[(0, 0, 16, 16)];
13153const B16X8: &[(usize, usize, usize, usize)] = &[(0, 0, 16, 8), (0, 8, 16, 8)];
13154const B8X16: &[(usize, usize, usize, usize)] = &[(0, 0, 8, 16), (8, 0, 8, 16)];
13155
13156/// A partition region `(x, y, w, h)` in samples.
13157type Region = (usize, usize, usize, usize);
13158
13159/// B `mb_type` 1..=21 → (partition layout, MV-prediction mode 0/1/2 for 16×16/
13160/// 16×8/8×16, per-partition prediction direction) (spec Table 7-14).
13161/// Test-only view of [`b_inter_layout`] for the ENCODER crate: `(mvmode, p0, p1)`
13162/// with pred coded 1 = L0, 2 = L1, 3 = Bi — the encoder's `b_part_mb_type` is the
13163/// exact inverse, so a round-trip over 4..=21 gates the two tables against drift.
13164pub fn b_inter_shape(mb_type: u32) -> (u8, u8, u8) {
13165 let (_, mvmode, preds) = b_inter_layout(mb_type);
13166 let code = |p: BPred| match (p.uses(0), p.uses(1)) {
13167 (true, true) => 3,
13168 (true, false) => 1,
13169 _ => 2,
13170 };
13171 (mvmode, code(preds[0]), code(preds[1]))
13172}
13173
13174fn b_inter_layout(mb_type: u32) -> (&'static [Region], u8, [BPred; 2]) {
13175 use BPred::*;
13176 match mb_type {
13177 1 => (B16X16, 0, [L0, L0]),
13178 2 => (B16X16, 0, [L1, L1]),
13179 3 => (B16X16, 0, [Bi, Bi]),
13180 4 => (B16X8, 1, [L0, L0]),
13181 5 => (B8X16, 2, [L0, L0]),
13182 6 => (B16X8, 1, [L1, L1]),
13183 7 => (B8X16, 2, [L1, L1]),
13184 8 => (B16X8, 1, [L0, L1]),
13185 9 => (B8X16, 2, [L0, L1]),
13186 10 => (B16X8, 1, [L1, L0]),
13187 11 => (B8X16, 2, [L1, L0]),
13188 12 => (B16X8, 1, [L0, Bi]),
13189 13 => (B8X16, 2, [L0, Bi]),
13190 14 => (B16X8, 1, [L1, Bi]),
13191 15 => (B8X16, 2, [L1, Bi]),
13192 16 => (B16X8, 1, [Bi, L0]),
13193 17 => (B8X16, 2, [Bi, L0]),
13194 18 => (B16X8, 1, [Bi, L1]),
13195 19 => (B8X16, 2, [Bi, L1]),
13196 20 => (B16X8, 1, [Bi, Bi]),
13197 _ => (B8X16, 2, [Bi, Bi]), // 21
13198 }
13199}
13200
13201/// Whether a B `sub_mb_type` (1..=12) uses reference list `list`.
13202fn b_sub_uses(st: u32, list: usize) -> bool {
13203 let pred = match st {
13204 1 | 4 | 5 | 10 => 0, // L0
13205 2 | 6 | 7 | 11 => 1, // L1
13206 _ => 2, // Bi (3, 8, 9, 12)
13207 };
13208 (list == 0 && pred != 1) || (list == 1 && pred != 0)
13209}
13210
13211/// Sub-partition shapes within an 8×8 for a B `sub_mb_type` (1..=12).
13212fn b_sub_parts(st: u32) -> &'static [(usize, usize, usize, usize)] {
13213 match st {
13214 1..=3 => &[(0, 0, 8, 8)],
13215 4 | 6 | 8 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
13216 5 | 7 | 9 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
13217 _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)], // 10/11/12
13218 }
13219}
13220
13221/// Sub-macroblock partition layout `(x, y, w, h)` in samples within an 8×8, for
13222/// a P-slice `sub_mb_type` (0 = 8×8, 1 = 8×4, 2 = 4×8, 3 = 4×4).
13223fn sub_mb_partitions(sub_type: u32) -> &'static [(usize, usize, usize, usize)] {
13224 match sub_type {
13225 0 => &[(0, 0, 8, 8)],
13226 1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
13227 2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
13228 _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
13229 }
13230}
13231
13232/// Copy a contiguous `w`x`h` block into a strided destination at `(x0, y0)`.
13233///
13234/// The width is SPECIALISED. Written as a per-pixel loop bounded by a runtime `w`,
13235/// this lowers to a bounds-checked store per pixel — and where it is a row copy of
13236/// runtime length, to a variable-length `memcpy` CALL per row. Both are the same
13237/// codegen trap the ENCODER fixed long ago ("H-17"); the decoder's copy of it was
13238/// never fixed, and it costs the most on exactly the streams a real encoder emits,
13239/// because x264's sub-16x16 partitions call it far more often than our own
13240/// 16x16-dominated bitstreams ever did. Byte-identical to the scalar form.
13241#[inline]
13242fn restride(
13243 dst: &mut [u8],
13244 dst_stride: usize,
13245 x0: usize,
13246 y0: usize,
13247 src: &[u8],
13248 w: usize,
13249 h: usize,
13250) {
13251 macro_rules! rows {
13252 ($n:expr) => {{
13253 for dy in 0..h {
13254 dst[(y0 + dy) * dst_stride + x0..][..$n].copy_from_slice(&src[dy * $n..][..$n]);
13255 }
13256 }};
13257 }
13258 match w {
13259 16 => rows!(16),
13260 8 => rows!(8),
13261 4 => rows!(4),
13262 2 => rows!(2),
13263 _ => {
13264 for dy in 0..h {
13265 dst[(y0 + dy) * dst_stride + x0..][..w].copy_from_slice(&src[dy * w..][..w]);
13266 }
13267 }
13268 }
13269}
13270
13271/// Un-scans an 8×8 block from frame zig-zag scan order to raster (spec Table 8-12).
13272fn un_scan_8x8(scan: &[i32; 64]) -> [i32; 64] {
13273 const ZZ8: [usize; 64] = [
13274 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27,
13275 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
13276 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
13277 ];
13278 let mut out = [0i32; 64];
13279 for k in 0..64 {
13280 out[ZZ8[k]] = scan[k];
13281 }
13282 out
13283}
13284
13285#[cfg(test)]
13286mod tests {
13287 use super::*;
13288
13289 fn fd(qp: u8, offset: i32) -> FrameDecoder {
13290 FrameDecoder::new(1, 1, qp, offset, Vec::new(), 1, false, false, true)
13291 }
13292
13293 #[test]
13294 fn mb_qp_delta_accumulates_mod_52() {
13295 let mut d = fd(26, 0);
13296 assert_eq!(d.cur_qp, 26, "QPy starts at the slice QP");
13297 d.step_qp(4).unwrap();
13298 assert_eq!(d.cur_qp, 30); // 26 + 4
13299 d.step_qp(-10).unwrap();
13300 assert_eq!(d.cur_qp, 20); // carries from the previous MB, not the slice
13301 // Wrap-around: (20 + 40 + 52) % 52 = 112 % 52 = 8.
13302 d.step_qp(40).unwrap();
13303 assert_eq!(d.cur_qp, 8);
13304 // Negative wrap: (8 - 20 + 52) % 52 = 40.
13305 d.step_qp(-20).unwrap();
13306 assert_eq!(d.cur_qp, 40);
13307 }
13308
13309 #[test]
13310 fn chroma_qp_index_offset_applied_and_clamped() {
13311 // Offset 0 reproduces the bare luma->chroma table (QP30 -> 29).
13312 assert_eq!(fd(0, 0).chroma_qp_for(30), 29);
13313 // Positive offset shifts the table lookup (QP30 + 2 -> table[2] = 31).
13314 assert_eq!(fd(0, 2).chroma_qp_for(30), 31);
13315 // The qPi index is clamped into 0..=51 before the lookup.
13316 assert_eq!(fd(0, -12).chroma_qp_for(5), chroma_qp(0));
13317 assert_eq!(fd(0, 99).chroma_qp_for(40), chroma_qp(51));
13318 }
13319}