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)] // MutexPf is std-only (the MV dump); LockPf is used in both
11use crate::sync::{LockPf, MutexPf};
12#[allow(unused_imports)]
13use alloc::boxed::Box;
14#[allow(unused_imports)]
15use alloc::format;
16#[allow(unused_imports)]
17use alloc::string::{String, ToString};
18#[allow(unused_imports)]
19use alloc::vec;
20#[allow(unused_imports)]
21use alloc::vec::Vec;
22use rusty_h264_common::bit_reader::OutOfData;
23use rusty_h264_common::cavlc::{decode_residual_block_into, read_cbp_inter, read_cbp_intra};
24use rusty_h264_common::inter::{
25 inter_partitions, mc_chroma_padded, mc_luma_padded, predict_mv, predict_partition_mv,
26 MvNeighbor,
27};
28use rusty_h264_common::predict::{
29 add_residual_8x8, chroma8x8_pred, chroma_qp, intra4x4_pred, intra8x8_pred, luma16x16_pred,
30 nnz_raster_from_z, reconstruct_4x4_dc_into, reconstruct_4x4_scan_into, I16Mode,
31 CHROMA_4X4_SCAN_XY, LUMA_4X4_SCAN_XY,
32};
33use rusty_h264_common::transform::{
34 inverse_quant_8x8_dq, inverse_quant_chroma_dc, inverse_quant_chroma_dc_weighted,
35 inverse_quant_luma_dc_scan, Dequant8Qp, DequantQp, DQ8_FLAT, DQ_FLAT,
36};
37use rusty_h264_common::{BitReader, YuvFrame};
38
39/// One frame's motion field, in 4x4-block raster (`mb_w*4` wide).
40///
41/// Captured from any conformant stream this decoder parses — including x264's —
42/// so a harness can compare motion fields between encoders without depending on
43/// external MV-export tooling.
44pub struct MvField {
45 pub mb_w: usize,
46 pub mb_h: usize,
47 pub mv: Vec<(i32, i32)>,
48 pub ref_idx: Vec<i32>,
49 pub inter: Vec<bool>,
50}
51
52/// Frames captured in decode order when `RFF_MV_DUMP=1`. Diagnostic only.
53#[cfg(feature = "std")]
54pub static MV_DUMP: crate::sync::Mutex<Vec<MvField>> = crate::sync::Mutex::new(Vec::new());
55
56/// `RH264_DUMP_MB` -- the per-frame/per-slice conformance-bisection dump.
57///
58/// ROUTED AT BUILD TIME, like the rest of the knob inventory. It was a bare
59/// `knob("RH264_DUMP_MB").is_some()` at four call sites, which is
60/// `std::env::var` PLUS a String allocation on EVERY slice and every DPB add --
61/// and it kept each dump body, with its `eprintln!` formatting machinery and
62/// String building, compiled into the shipping decoder. `deblock` alone carried
63/// four `Stderr::write_fmt`, four `panic_fmt` and thirteen alloc/free calls that
64/// exist only for a dump nobody enables.
65/// `RS_H264_ROUTE_DUMP` -- the per-picture content-route dump. Build-time routed
66/// for the same reason as `dump_mb_on`.
67pub(crate) fn route_dump_on() -> bool {
68 #[cfg(not(feature = "knobs"))]
69 {
70 false
71 }
72 #[cfg(feature = "knobs")]
73 {
74 static ON: rusty_h264_common::once::OnceLock<bool> =
75 rusty_h264_common::once::OnceLock::new();
76 *ON.get_or_init(|| rusty_h264_common::knob("RS_H264_ROUTE_DUMP").is_some())
77 }
78}
79
80pub(crate) fn dump_mb_on() -> bool {
81 #[cfg(not(feature = "knobs"))]
82 {
83 false
84 }
85 #[cfg(feature = "knobs")]
86 {
87 static ON: rusty_h264_common::once::OnceLock<bool> =
88 rusty_h264_common::once::OnceLock::new();
89 *ON.get_or_init(|| rusty_h264_common::knob("RH264_DUMP_MB").is_some())
90 }
91}
92
93pub fn mv_dump_on() -> bool {
94 #[cfg(not(feature = "knobs"))]
95 {
96 false
97 }
98 #[cfg(feature = "knobs")]
99 {
100 static ON: rusty_h264_common::once::OnceLock<bool> =
101 rusty_h264_common::once::OnceLock::new();
102 *ON.get_or_init(|| rusty_h264_common::knob("RFF_MV_DUMP").map_or(false, |v| v != "0"))
103 }
104}
105
106/// Copy filtered MB rows `[prev..mb_rows)` from coded-size planes into a
107/// Frame-MT progress slot's live padded planes, then bump `ready_rows`.
108/// Coarser publish: only advance the watermark every 2 MB rows (and always
109/// on the last row) to cut lock/notify traffic.
110fn publish_filtered_rows_to_slot(
111 slot: &crate::RefFrame,
112 rec_y: &[u8],
113 rec_u: &[u8],
114 rec_v: &[u8],
115 cw: usize,
116 ccw: usize,
117 ch: usize,
118 mb_rows: usize,
119) {
120 if mb_rows == 0 || slot.frozen.get().is_some() {
121 return;
122 }
123 if !crate::row_publish_on() {
124 return;
125 }
126 let mb_h = ch.div_ceil(16);
127 // Batch: defer watermark until even MB rows (or picture end).
128 if mb_rows < mb_h && (mb_rows & 1) != 0 {
129 return;
130 }
131 let Some(live) = &slot.live else {
132 slot.publish_ready_rows(mb_rows * 16);
133 return;
134 };
135 let cch = ch / 2;
136 let prev = slot.ready_rows.load(core::sync::atomic::Ordering::Acquire) / 16;
137 if mb_rows <= prev {
138 return;
139 }
140 let mut py = live.py.write_pf();
141 let mut pu = live.pu.write_pf();
142 let mut pv = live.pv.write_pf();
143 let ls = cw + 2 * crate::LPAD;
144 let cs = ccw + 2 * crate::CPAD;
145 for mr in prev..mb_rows {
146 for dy in 0..16 {
147 let y = mr * 16 + dy;
148 if y >= ch {
149 break;
150 }
151 let src = &rec_y[y * cw..(y + 1) * cw];
152 let dst_y = y + crate::LPAD;
153 // ONE row segment covering pad+picture+pad, then `split_at_mut` and
154 // two fills — the edge reads and the 2*LPAD writes were separate
155 // whole-plane indexes. Same shape as `expand_plane`.
156 let seg = &mut py[dst_y * ls..][..2 * crate::LPAD + cw];
157 let (lpad, rest) = seg.split_at_mut(crate::LPAD);
158 let (mid, rpad) = rest.split_at_mut(cw);
159 mid.copy_from_slice(src);
160 let (Some(&left), Some(&right)) = (mid.first(), mid.last()) else {
161 continue;
162 };
163 lpad.fill(left);
164 rpad[..crate::LPAD].fill(right);
165 }
166 for dy in 0..8 {
167 let cy = mr * 8 + dy;
168 if cy >= cch {
169 break;
170 }
171 for (rec, plane) in [(rec_u, &mut *pu), (rec_v, &mut *pv)] {
172 let src = &rec[cy * ccw..(cy + 1) * ccw];
173 let dst_y = cy + crate::CPAD;
174 let seg = &mut plane[dst_y * cs..][..2 * crate::CPAD + ccw];
175 let (lpad, rest) = seg.split_at_mut(crate::CPAD);
176 let (mid, rpad) = rest.split_at_mut(ccw);
177 mid.copy_from_slice(src);
178 let (Some(&left), Some(&right)) = (mid.first(), mid.last()) else {
179 continue;
180 };
181 lpad.fill(left);
182 rpad[..crate::CPAD].fill(right);
183 }
184 }
185 }
186 if prev == 0 {
187 // Copy the whole first picture ROW upward instead of walking columns:
188 // `split_at_mut` at the pad boundary proves both sides at once.
189 let (pad, rest) = py.split_at_mut(crate::LPAD * ls);
190 if let Some(first) = rest.get(..ls) {
191 for row in pad.chunks_mut(ls) {
192 row.copy_from_slice(first);
193 }
194 }
195 for plane in [&mut *pu, &mut *pv] {
196 let (pad, rest) = plane.split_at_mut(crate::CPAD * cs);
197 if let Some(first) = rest.get(..cs) {
198 for row in pad.chunks_mut(cs) {
199 row.copy_from_slice(first);
200 }
201 }
202 }
203 }
204 slot.publish_ready_rows(mb_rows * 16);
205}
206
207/// Reconstructed coded-size planes plus CAVLC `nnz` context grids.
208pub struct FrameDecoder {
209 mb_w: usize,
210 mb_h: usize,
211 /// Slice QP (`SliceQPy`) — the deblock filter's frame-level QP.
212 qp: u8,
213 /// Running luma QP (`QPy`), carried across macroblocks and stepped by each
214 /// `mb_qp_delta` (spec §7.4.5). Equals `qp` on constant-QP streams.
215 cur_qp: u8,
216 /// `chroma_qp_index_offset` from the active PPS (§8.5.8).
217 chroma_qp_offset: i32,
218 cw: usize,
219 ch: usize,
220 ccw: usize,
221 cch: usize,
222 rec_y: Vec<u8>,
223 rec_u: Vec<u8>,
224 rec_v: Vec<u8>,
225 /// Per-macroblock luma QP (`QPy`), for per-edge deblock strength.
226 mb_qp: Vec<u8>,
227 /// First macroblock address of the slice currently being decoded. Neighbors
228 /// with a lower address belong to an earlier slice and are "not available"
229 /// for prediction (spec §8.3/§8.4). Slices are contiguous raster ranges (we
230 /// reject FMO/slice-groups), so address ≥ this ⇔ same slice.
231 slice_first_mb: usize,
232 nnz_y: Vec<u8>,
233 nnz_c: [Vec<u8>; 2],
234 modes_y: Vec<u8>,
235 coded_y: Vec<bool>,
236 /// Per-4×4-block List-0 motion (mv + ref index, `-1` = no L0). For P slices
237 /// this is the only motion; B slices add the List-1 grids below.
238 /// NARROWED (i32,i32) -> (i16,i16). A motion vector is quarter-pel and
239 /// level-bounded (+/-2048 qpel at 5.1), so i16 has 16x headroom -- and
240 /// `pack_mb` ALREADY narrowed these to i16 unconditionally for the deblock
241 /// path, so the store-side assumption predates this change rather than being
242 /// introduced by it.
243 ///
244 /// Rust has no implicit numeric coercion, so the COMPILER is the proof that
245 /// no arithmetic silently happens at i16: every read site had to be given an
246 /// explicit widen, and anything left implicit would not compile.
247 mv_y: Vec<(i16, i16)>,
248 inter_y: Vec<bool>,
249 /// NARROWED i32 -> i8 (2026-09-07). A `ref_idx` is -1..31 by the spec
250 /// (7.4.5.1: num_ref_idx_active is at most 32), so three of every four
251 /// bytes were padding -- and this grid is re-armed for EVERY picture,
252 /// which the copy census priced at 69 MB over a 60-frame 720p decode.
253 ref_idx_y: Vec<i8>,
254 /// Per-4×4-block List-1 motion for B slices (`ref_idx1 = -1` = no L1).
255 mv1: Vec<(i16, i16)>,
256 ref_idx1: Vec<i8>,
257 /// `RefPicList1` and B-slice flags (unused outside B slices).
258 refs1: Vec<crate::Ref>,
259 num_ref_active1: usize,
260 is_b: bool,
261 /// True if the stream's profile permits B-slices (`profile_idc != 66`). When
262 /// false (Baseline / Constrained Baseline), `as_reference_pooled` skips the per-block
263 /// motion (mv/ref_idx/ref_poc) that only B temporal/spatial direct ever reads.
264 b_possible: bool,
265 direct_spatial: bool,
266 nnz_l_cache: [u8; 25],
267 nnz_c_cache: [[u8; 9]; 2],
268 /// Decoded-picture buffer (most-recent first); empty in I-slices. `ref_idx`
269 /// indexes into this list.
270 refs: Vec<crate::Ref>,
271 /// `num_ref_idx_l0_active` for the current slice — drives whether `ref_idx`
272 /// is coded (active > 1) and its te(v)/ue(v) form, independently of how many
273 /// reference pictures actually exist (spec §7.4.5.1, §9.1).
274 num_ref_active: usize,
275 /// `constrained_intra_pred_flag`: when set, intra prediction may only use
276 /// samples from intra-coded neighbors (inter neighbors are "not available").
277 constrained_intra: bool,
278 /// High-profile 4×4 scaling matrices in **raster** order, indexed by
279 /// `[Y-intra, Cb-intra, Cr-intra, Y-inter, Cb-inter, Cr-inter]`. `None` = flat.
280 scaling: Option<[[i32; 16]; 6]>,
281 /// High-profile 8×8 luma scaling matrices in raster order `[Y-intra, Y-inter]`
282 /// (4:2:0 has only these two). `None` = flat.
283 scaling8: Option<[[i32; 64]; 2]>,
284 /// `transform_8x8_mode_flag` from the PPS: enables `transform_size_8x8_flag`.
285 transform_8x8_mode: bool,
286 /// SPS `qpprime_y_zero_transform_bypass_flag` — refusal gate in `step_qp`.
287 transform_bypass: bool,
288 /// GATE 1 router counters (big-oppy-decoder §2): skip MBs and
289 /// residual-coded MBs this picture. Two integer adds per MB, no atomics.
290 route_skip_mbs: u32,
291 route_coded_mbs: u32,
292 /// Per-slice `(first_mb, idc == 2)` in decode order — drives the
293 /// `disable_deblocking_filter_idc == 2` cross-slice-edge suppression.
294 slice_bounds: Vec<(usize, bool)>,
295 /// The CURRENT slice's `disable_deblocking_filter_idc == 2`, latched by
296 /// `set_deblock_params` and recorded per slice by the decode loops.
297 cur_idc2: bool,
298 /// Per-macroblock `transform_size_8x8_flag` (for deblocking: internal 4×4
299 /// luma edges of 8×8-transform MBs are not filtered).
300 mb_t8x8: Vec<bool>,
301 /// Bit per macroblock: the DECODER knows this macroblock's sixteen blocks
302 /// share one motion set in both lists, so the deblock derivation need not run
303 /// the `mb_uniform` kernel to rediscover it.
304 ///
305 /// Deliberately NOT folded into `mb_kind`: that enum's `InterUniform` carries
306 /// a single-list contract the blind derivation arms rely on, and a
307 /// bi-predicted direct macroblock would violate it. This says one thing only.
308 mb_umot: Vec<bool>,
309 // ---- Row-interleaved deblocking state (docs/row-interleave-plan.md) ----
310 /// Per-MB boundary strengths, filled row-by-row as decode completes rows.
311 bs_frame: Vec<rusty_h264_common::deblock::MbBs>,
312 /// Rows whose bS is derived (watermark).
313 bs_rows: usize,
314 /// Rows already deblock-FILTERED (watermark; R3).
315 flt_rows: usize,
316 /// Two-row rolling window of packed records (prev = row r-1, cur = row r).
317 pk_prev: Vec<rusty_h264_common::deblock::MbPack>,
318 pk_cur: Vec<rusty_h264_common::deblock::MbPack>,
319 /// Transform-block coded mask (nnz with the 8x8 OR applied), filled per row.
320 nnz_dbr: Vec<u8>,
321 /// Unfiltered bottom rows of the last-filtered MB row (intra reads these).
322 bak_y: Vec<u8>,
323 bak_u: Vec<u8>,
324 bak_v: Vec<u8>,
325 /// Entropy-decouple: deferred pixel jobs + the per-slice activation flag
326 /// (CABAC slices only — the CAVLC loop has no flush hooks).
327 edc_jobs: Vec<EdcJob>,
328 /// Recycled job boxes (see `take_pinter_job`): a coded inter macroblock
329 /// used to malloc, fill, memcpy and free a 2.8 KB box every time.
330 edc_job_pool: Vec<Box<PInterJob>>,
331 edc_nores_pool: Vec<Box<PInterNoResJob>>,
332 /// Coefficient planes for the B and intra arms (which reconstruct inline,
333 /// so no job carries them): taken for a macroblock, zeroed per CODED block
334 /// by the parse, put back after recon. `None` only while in use.
335 scratch_luma: Option<Box<[[i32; 16]; 16]>>,
336 scratch_luma8: Option<Box<[[i32; 64]; 4]>>,
337 scratch_cac: Option<Box<[[[i32; 16]; 4]; 2]>>,
338 edc_active: bool,
339 // ---- E2: the worker-thread plumbing (all None outside a threaded slice) ----
340 edc_tx: Option<crate::sync::mpsc::SyncSender<EdcMsg>>,
341 edc_ctx_rx: Option<crate::sync::mpsc::Receiver<PixelCtx>>,
342 edc_back_tx: Option<crate::sync::mpsc::Sender<PixelCtx>>,
343 /// FIRST internal fault seen while decoding this picture, if any.
344 ///
345 /// The EDC worker and the parse thread talk over channels whose `send`
346 /// fails only when the peer is gone. The old code `.expect("worker
347 /// alive")`ed, which converts a dead helper thread into a panic in a
348 /// thread that did nothing wrong -- and a decoder that panics on
349 /// attacker-controlled input hands out a denial of service.
350 ///
351 /// A dropped job is NOT recoverable (the row simply would not be filtered,
352 /// so the picture would be silently wrong), so this is not fail-soft: the
353 /// fault is recorded here and `decode_slice` turns it into
354 /// `DecodeError::Internal` at picture finalize. Wrong pixels are never
355 /// returned as if they were right.
356 fault: Option<&'static str>,
357 /// While the parse thread holds the pixel context for an intra macroblock
358 /// (planes moved into `self`), the rest of the context parks here.
359 edc_parked: Option<PixelCtx>,
360 /// E3: while parsing a B macroblock in threaded mode, its MC regions
361 /// accumulate here instead of executing (the pixel side is the worker's).
362 edc_regions: Option<Vec<BRegion>>,
363 /// Measurement only: previous full-MB single-rect direct (x, y, refi0,
364 /// refi1, m0, m1) for run-adjacency counting. No decode behavior.
365 bsk_last: Option<(usize, usize, i32, i32, (i32, i32), (i32, i32))>,
366 /// D10: jobs accumulated for the current row, sent as ONE message.
367 edc_batch: Vec<EdcJob>,
368 /// D12: bits/MB carried in from previous slices (0 = not yet known).
369 bits_per_mb: f64,
370 /// Current slice's deblock parameters (set per slice by the caller).
371 db_ena: bool,
372 db_oa: i32,
373 db_ob: i32,
374 /// Per-macroblock deblock derivation CLASS (`MB_KIND_*`), so the loop filter
375 /// can skip the 24-block neighbourhood gather on macroblocks whose strengths
376 /// are determined by syntax alone. Starts UNSET; anything left UNSET simply
377 /// takes the blind path, so a missed producer site costs speed, not
378 /// correctness. Only classes that are uniform BY SYNTAX are written — notably
379 /// NOT `B_Skip`/`B_Direct`, whose direct-derived motion varies per 4×4.
380 mb_kind: Vec<u8>,
381 /// Explicit weighted-prediction tables, when active for this slice.
382 weights: Option<WeightTable>,
383 /// `frame_mt::row_progress_on()` cached at construction (see
384 /// wait_refs_for_mb).
385 row_wait: bool,
386 /// Colocated-picture fast-probe cache (set_b_context): true when refs1[0]
387 /// is frozen (1T), short-term, with motion grids — col_zero then skips
388 /// the Option + live + long_term + w4 branch chain per probe.
389 col_ok: bool,
390 col_w4: usize,
391 /// Measurement knobs cached at construction — these were OnceLock derefs
392 /// on EVERY skip macroblock (no_bskipfast per B skip, no_runmv per P
393 /// skip); one field test now.
394 /// rowdb_on()/rowhook_eager() cached at construction — these were atomic
395 /// loads inside row_hook_at on EVERY macroblock.
396 /// MEASUREMENT KNOB, cached. `kind_loads()` is a `OnceLock` deref and was
397 /// evaluated as a MATCH GUARD on every Skip/InterUniform macroblock in
398 /// `derive_bs_row` — the dominant class. The census (DBSDERIVE kindarm=0 on
399 /// 6/6 corpus streams) shows the arm it guards never fires by default, so
400 /// every one of those derefs was pure tax. Same treatment `k_rowdb` /
401 /// `k_roweager` already had.
402 /// True once ANY macroblock in this picture used the 8x8 transform. The
403 /// `nnz_dbr` row copy + the per-row t8 scan in `derive_bs_row` exist ONLY
404 /// to apply the 8x8 coded-status OR; with no t8 macroblock `nnz_dbr` is a
405 /// byte-for-byte copy of `nnz_y`, so both are skipped and the derivation
406 /// reads `nnz_y` directly. Census: t8mb=0 on every cavlc/main stream.
407 any_t8: bool,
408 /// True once any slice in this picture carries
409 /// `disable_deblocking_filter_idc == 2`. Replaces a per-ROW
410 /// `slice_bounds.iter().any(..)` scan (census: idc2row=0 on the corpus).
411 any_idc2: bool,
412 /// Raster address whose P_Skip MV is FORCED (0,0): the previous
413 /// decode_p_skip was at addr-1 and committed (0,0), so this MB's left
414 /// neighbor either fires §8.4.1.1's zero-MV rule (in-slice: ref 0,
415 /// (0,0)) or is unavailable (out-of-slice — same rule). Interior MBs of
416 /// a skip run skip the 3-neighbor gather entirely.
417 skip_zero_next: usize,
418 /// Pending B_Skip grid+recon span: (mb row, x0, n, kind) of CONSECUTIVE
419 /// fast B_Skips with IDENTICAL committed values, deferred and range-
420 /// filled/band-reconned at flush. Flushed before ANY grid or pixel
421 /// reader runs — see span_flush callers.
422 bzspan: Option<(usize, usize, usize, BzKind)>,
423 /// Pending P_Skip (0,0) grid-commit span — the P mirror of `bzspan`
424 /// (values: ref 0 list-0, mv (0,0), inter, coded, DC mode, kind SKIP).
425 /// The bool records whether the RECON is deferred too (1T, identity/no
426 /// weights) — decided at push time, not re-derived at flush (begin_slice
427 /// may have changed the fields in between).
428 pzspan: Option<(usize, usize, usize, bool)>,
429 /// Per-picture bitmap: MB decoded as a ref0/(0,0)-both-lists zero-bi fast
430 /// B_Skip. Never cleared mid-picture — false only ever means "derive
431 /// normally" — so no MB path carries a clearing duty.
432 bzero: Vec<bool>,
433 /// Pooled CABAC slice scratch — refilled (not reallocated) at slice entry;
434 /// carried across pictures via `GridPool`. Taken out of `self` at
435 /// `decode_slice_cabac_inner` entry and put back at its normal exit (an
436 /// error exit simply forfeits the pooled allocation).
437 sc_cat: Vec<u8>,
438 sc_cbp: Vec<u8>,
439 // Chroma pred mode is 0..=3 (7.4.5: intra_chroma_pred_mode), with -1 as the
440 // "no neighbour" sentinel and the only read being `(1..=3).contains(..)`.
441 // i32 was four bytes carrying two bits.
442 sc_cmode: Vec<i8>,
443 sc_nzc: Vec<[u8; 24]>,
444 sc_cbfdc: Vec<u16>,
445 sc_skip: Vec<bool>,
446 sc_ref: Vec<[i8; 16]>,
447 /// SATURATED-ABSOLUTE, `u8` (2026-09-07). This was `[[i16; 2]; 16]` -- the
448 /// signed mvd per 4x4 block -- but its ONLY consumer is the CABAC mvd
449 /// ctxInc, which computes `|nb| + |na|` and compares it against 3 and 32.
450 /// Neither the sign nor any magnitude above 33 can change that answer:
451 /// if either neighbour is >= 33 the sum already exceeds 32 both ways, and
452 /// if both are < 33 saturating is a no-op. So a byte carries exactly the
453 /// information an i16 did, at half the width -- on a grid refilled per
454 /// SLICE, which the copy census priced at 230 KB a time.
455 sc_mvd: Vec<[[u8; 2]; 16]>,
456 sc_ref1: Vec<[i8; 16]>,
457 sc_mvd1: Vec<[[u8; 2]; 16]>,
458 sc_direct: Vec<bool>,
459 /// Lazily cached `implicit_weights(0, 0)` — slice-constant (POCs of
460 /// refs[0]/refs1[0] and cur don't change within a slice). Reset in
461 /// set_b_context.
462 iw00: Option<Option<(i32, i32)>>,
463 /// Cached `weights.list_identity(0)` — all list-0 refs identity; gates the
464 /// whole weight_partition pass for P inter (one choke point, all sites).
465 weights_l0id: bool,
466 /// Cached `weights.ref0_identity()` — identity tables (the x264 weightp=2
467 /// common case) make every ref-0 weighting pass an exact no-op, and the
468 /// skip fast paths check this per MB, so it is computed once per slice.
469 weights_id0: bool,
470 /// Current picture's `PicOrderCnt` (for temporal direct + implicit weighting).
471 cur_poc: i32,
472 /// `weighted_bipred_idc` (0 = none/average, 1 = explicit, 2 = implicit).
473 weighted_bipred_idc: u8,
474 /// `direct_8x8_inference_flag` (B direct co-located sub-block selection).
475 direct_8x8_inference: bool,
476 /// Frame-MT Phase B: shared progress Arc for the picture being decoded.
477 progress: Option<crate::Ref>,
478 /// Slice-stable ref→POC tables for bS (≤16 entries). `derive_bs_row` used
479 /// to `collect()` these every row — same values for the whole slice.
480 ref_poc0: Vec<i32>,
481 ref_poc1: Vec<i32>,
482}
483
484/// Explicit weighted-prediction tables (spec §7.4.3.2 / §8.4.2.3.2). Per
485/// reference list, per ref index: a luma `(weight, offset)` and two chroma
486/// `(weight, offset)` (Cb, Cr). `log2` denominators are shared.
487#[derive(Clone, Default)]
488pub struct WeightTable {
489 pub luma_log2_denom: i32,
490 pub chroma_log2_denom: i32,
491 /// `[list][ref_idx] = (weight, offset)`.
492 pub luma: [Vec<(i32, i32)>; 2],
493 /// `[list][ref_idx][cb=0/cr=1] = (weight, offset)`.
494 pub chroma: [Vec<[(i32, i32); 2]>; 2],
495}
496
497impl WeightTable {
498 /// True when list-0 ref-0 carries identity weights (w == 1<<denom, offset 0)
499 /// for luma and both chroma planes. x264's weightp=2 puts a pred_weight_table
500 /// in EVERY P slice, but outside fades the entries are identity — and the
501 /// identity weight is an EXACT no-op ((s*2^d + 2^(d-1))>>d + 0 == s), so a
502 /// P_Skip recon may bypass the weighting pass byte-identically.
503 fn ref0_identity(&self) -> bool {
504 self.luma[0]
505 .first()
506 .is_some_and(|&(w, o)| w == 1 << self.luma_log2_denom && o == 0)
507 && self.chroma[0].first().is_some_and(|c| {
508 c.iter()
509 .all(|&(w, o)| w == 1 << self.chroma_log2_denom && o == 0)
510 })
511 }
512
513 /// True when EVERY entry of `list` (all refs, luma + both chroma) is the
514 /// identity weight — the x264 weightp=2 shape outside fades. Identity is
515 /// an exact per-pixel no-op (see ref0_identity), so the whole per-ref
516 /// weighting pass may be skipped for this list.
517 fn list_identity(&self, list: usize) -> bool {
518 self.luma[list]
519 .iter()
520 .all(|&(w, o)| w == 1 << self.luma_log2_denom && o == 0)
521 && self.chroma[list].iter().all(|c| {
522 c.iter()
523 .all(|&(w, o)| w == 1 << self.chroma_log2_denom && o == 0)
524 })
525 }
526
527 /// Applies a single-list (uni-prediction) luma weight (spec §8.4.2.3.2).
528 /// Resolves a list/ref slot's (weight, offset) ONCE. `list` is 0..1 and the
529 /// fallback is the IDENTITY weight, which is what an absent entry means.
530 #[inline]
531 fn luma_wo(&self, list: usize, refi: usize) -> (i32, i32) {
532 self.luma[list & 1]
533 .get(refi)
534 .copied()
535 .unwrap_or((1 << self.luma_log2_denom, 0))
536 }
537
538 /// Ditto for chroma component `cc`.
539 #[inline]
540 fn chroma_wo(&self, list: usize, refi: usize, cc: usize) -> (i32, i32) {
541 self.chroma[list & 1]
542 .get(refi)
543 .map(|c| c[cc & 1])
544 .unwrap_or((1 << self.chroma_log2_denom, 0))
545 }
546
547 fn apply_luma(&self, sample: u8, list: usize, refi: usize) -> u8 {
548 let (w, o) = self.luma_wo(list, refi);
549 let lwd = self.luma_log2_denom;
550 let v = if lwd >= 1 {
551 ((sample as i32 * w + (1 << (lwd - 1))) >> lwd) + o
552 } else {
553 sample as i32 * w + o
554 };
555 v.clamp(0, 255) as u8
556 }
557
558 /// Applies a single-list (uni-prediction) chroma weight for component `cc`.
559 fn apply_chroma(&self, sample: u8, list: usize, refi: usize, cc: usize) -> u8 {
560 let (w, o) = self.chroma_wo(list, refi, cc);
561 let cwd = self.chroma_log2_denom;
562 let v = if cwd >= 1 {
563 ((sample as i32 * w + (1 << (cwd - 1))) >> cwd) + o
564 } else {
565 sample as i32 * w + o
566 };
567 v.clamp(0, 255) as u8
568 }
569}
570
571/// Why a macroblock could not be decoded.
572#[derive(Debug, Clone, PartialEq, Eq)]
573pub enum MbError {
574 Truncated,
575 Unsupported(&'static str),
576}
577
578impl From<OutOfData> for MbError {
579 fn from(_: OutOfData) -> Self {
580 MbError::Truncated
581 }
582}
583
584/// Recycled per-picture scratch grids.
585///
586/// `FrameDecoder::new` used to allocate ~1.65 MB of frame-wide grids for EVERY
587/// coded picture and drop them when the picture finished. The sampled profiler
588/// prices that (stage `dec-setup`) at 6.7% of decode — larger than dequant,
589/// reconstruct and intra prediction combined, and none of it is codec work.
590///
591/// Two costs are being paid, and the allocation is the bigger one. A ~460 KB
592/// `Vec` goes straight to the OS, so every page is a fresh zero page and the
593/// decoder takes a soft page fault on FIRST TOUCH of each 4 KB — a cost charged
594/// to whatever per-macroblock stage happens to touch it first, not to the
595/// allocation. Handing the same buffers back keeps the pages mapped and warm.
596///
597/// The initialising fill is NOT skipped: these grids are read as neighbour
598/// context (`modes_y` must read 2/DC, `ref_idx_y` must read -1) before every
599/// block that writes them, so a stale value from the previous picture is a
600/// correctness bug, not a performance trade. `clear()` + `resize()` keeps the
601/// fill and drops only the allocation.
602///
603/// The reconstruction planes are deliberately NOT pooled: `into_frame` MOVES
604/// them out as the caller's output frame, so there is nothing to hand back.
605#[derive(Default)]
606pub struct GridPool {
607 /// Deferred-job boxes carried across pictures (see `take_pinter_job`).
608 job_pool: Vec<Box<PInterJob>>,
609 nores_pool: Vec<Box<PInterNoResJob>>,
610 /// D12: running bits-per-macroblock of decoded slices, the E2 dispatch's
611 /// density signal. Lives here because `GridPool` is the only state that
612 /// survives a picture (`FrameDecoder` is rebuilt per picture).
613 bits_per_mb: f64,
614 mb_qp: Vec<u8>,
615 bs_frame: Vec<rusty_h264_common::deblock::MbBs>,
616 pk_prev: Vec<rusty_h264_common::deblock::MbPack>,
617 pk_cur: Vec<rusty_h264_common::deblock::MbPack>,
618 nnz_dbr: Vec<u8>,
619 bak_y: Vec<u8>,
620 bak_u: Vec<u8>,
621 bak_v: Vec<u8>,
622 nnz_y: Vec<u8>,
623 nnz_c0: Vec<u8>,
624 nnz_c1: Vec<u8>,
625 modes_y: Vec<u8>,
626 coded_y: Vec<bool>,
627 /// NARROWED (i32,i32) -> (i16,i16). A motion vector is quarter-pel and
628 /// level-bounded (+/-2048 qpel at 5.1), so i16 has 16x headroom -- and
629 /// `pack_mb` ALREADY narrowed these to i16 unconditionally for the deblock
630 /// path, so the store-side assumption predates this change rather than being
631 /// introduced by it.
632 ///
633 /// Rust has no implicit numeric coercion, so the COMPILER is the proof that
634 /// no arithmetic silently happens at i16: every read site had to be given an
635 /// explicit widen, and anything left implicit would not compile.
636 mv_y: Vec<(i16, i16)>,
637 inter_y: Vec<bool>,
638 /// NARROWED i32 -> i8 (2026-09-07). A `ref_idx` is -1..31 by the spec
639 /// (7.4.5.1: num_ref_idx_active is at most 32), so three of every four
640 /// bytes were padding -- and this grid is re-armed for EVERY picture,
641 /// which the copy census priced at 69 MB over a 60-frame 720p decode.
642 ref_idx_y: Vec<i8>,
643 mv1: Vec<(i16, i16)>,
644 ref_idx1: Vec<i8>,
645 mb_t8x8: Vec<bool>,
646 /// Bit per macroblock: the DECODER knows this macroblock's sixteen blocks
647 /// share one motion set in both lists, so the deblock derivation need not run
648 /// the `mb_uniform` kernel to rediscover it.
649 ///
650 /// Deliberately NOT folded into `mb_kind`: that enum's `InterUniform` carries
651 /// a single-list contract the blind derivation arms rely on, and a
652 /// bi-predicted direct macroblock would violate it. This says one thing only.
653 mb_umot: Vec<bool>,
654 mb_kind: Vec<u8>,
655 bzero: Vec<bool>,
656 // CABAC slice scratch (D13 follow-through): these were fresh `vec![..]`
657 // allocations at EVERY slice entry — ~407 KB per P slice, ~700 KB per B
658 // slice at 720p, the same fresh-page class GridPool exists to kill.
659 sc_cat: Vec<u8>,
660 sc_cbp: Vec<u8>,
661 // Chroma pred mode is 0..=3 (7.4.5: intra_chroma_pred_mode), with -1 as the
662 // "no neighbour" sentinel and the only read being `(1..=3).contains(..)`.
663 // i32 was four bytes carrying two bits.
664 sc_cmode: Vec<i8>,
665 sc_nzc: Vec<[u8; 24]>,
666 sc_cbfdc: Vec<u16>,
667 sc_skip: Vec<bool>,
668 sc_ref: Vec<[i8; 16]>,
669 /// SATURATED-ABSOLUTE, `u8` (2026-09-07). This was `[[i16; 2]; 16]` -- the
670 /// signed mvd per 4x4 block -- but its ONLY consumer is the CABAC mvd
671 /// ctxInc, which computes `|nb| + |na|` and compares it against 3 and 32.
672 /// Neither the sign nor any magnitude above 33 can change that answer:
673 /// if either neighbour is >= 33 the sum already exceeds 32 both ways, and
674 /// if both are < 33 saturating is a no-op. So a byte carries exactly the
675 /// information an i16 did, at half the width -- on a grid refilled per
676 /// SLICE, which the copy census priced at 230 KB a time.
677 sc_mvd: Vec<[[u8; 2]; 16]>,
678 sc_ref1: Vec<[i8; 16]>,
679 sc_mvd1: Vec<[[u8; 2]; 16]>,
680 sc_direct: Vec<bool>,
681 // D24 (inline-execution.md 11.10): the ref-POC mirrors were the only
682 // per-picture Vecs NOT recycled - a fresh alloc + collect per picture each.
683 ref_poc0: Vec<i32>,
684 ref_poc1: Vec<i32>,
685}
686
687/// Reuse `v`'s allocation for `n` copies of `val`. Identical OBSERVABLE result to
688/// `vec![val; n]`; differs only in that it reuses the existing allocation when the
689/// capacity already suffices.
690// Saturate to the only thing the mvd ctxInc can distinguish: |mvd| capped at
691// 33. Above that the neighbour sum already exceeds 32, so the context is
692// identical and the extra magnitude is unreachable information.
693fn sat_mvd(m: [[i16; 2]; 16]) -> [[u8; 2]; 16] {
694 core::array::from_fn(|i| {
695 core::array::from_fn(|c| m[i][c].unsigned_abs().min(33) as u8)
696 })
697}
698
699#[inline]
700#[cfg_attr(feature = "profile", track_caller)]
701fn refill<T: Clone>(mut v: Vec<T>, n: usize, val: T) -> Vec<T> {
702 // The single funnel for every per-picture grid clear, so instrumenting it
703 // here prices the whole pool re-arm in one place. Pooling made the ALLOCATION
704 // free; what is left is the clear, and that is the thing to measure.
705 cpystat::note(&cpystat::POOL_CLEAR, n * core::mem::size_of::<T>());
706 #[cfg(feature = "profile")]
707 cpystat::note_line(
708 core::panic::Location::caller().line(),
709 n * core::mem::size_of::<T>(),
710 );
711 v.clear();
712 v.resize(n, val);
713 v
714}
715
716impl FrameDecoder {
717 /// Non-pooled ctor — tests only; production always enters via `with_pool`
718 /// (`GridPool` recycling, see lib.rs).
719 #[cfg(test)]
720 pub fn new(
721 mb_w: usize,
722 mb_h: usize,
723 qp: u8,
724 chroma_qp_offset: i32,
725 refs: Vec<crate::Ref>,
726 num_ref_active: usize,
727 constrained_intra: bool,
728 transform_8x8_mode: bool,
729 b_possible: bool,
730 ) -> Self {
731 Self::with_pool(
732 mb_w,
733 mb_h,
734 qp,
735 chroma_qp_offset,
736 refs,
737 num_ref_active,
738 constrained_intra,
739 transform_8x8_mode,
740 b_possible,
741 GridPool::default(),
742 )
743 }
744
745 /// As `new`, but reusing a previous picture's grid allocations. See `GridPool`.
746 pub fn with_pool(
747 mb_w: usize,
748 mb_h: usize,
749 qp: u8,
750 chroma_qp_offset: i32,
751 refs: Vec<crate::Ref>,
752 num_ref_active: usize,
753 constrained_intra: bool,
754 transform_8x8_mode: bool,
755 b_possible: bool,
756 pool: GridPool,
757 ) -> Self {
758 let (cw, ch) = (mb_w * 16, mb_h * 16);
759 let (ccw, cch) = (cw / 2, ch / 2);
760 let bits_per_mb = pool.bits_per_mb;
761 let ref_poc0 = {
762 let mut v = pool.ref_poc0;
763 v.clear();
764 v.extend(refs.iter().map(|f| f.pic_poc()));
765 v
766 };
767 Self {
768 mb_w,
769 mb_h,
770 qp,
771 cur_qp: qp,
772 chroma_qp_offset,
773 cw,
774 ch,
775 ccw,
776 cch,
777 rec_y: vec![0; cw * ch],
778 rec_u: vec![0; ccw * cch],
779 rec_v: vec![0; ccw * cch],
780 mb_qp: refill(pool.mb_qp, mb_w * mb_h, qp),
781 slice_first_mb: 0,
782 nnz_y: refill(pool.nnz_y, (mb_w * 4) * (mb_h * 4), 0),
783 nnz_c: [
784 refill(pool.nnz_c0, (mb_w * 2) * (mb_h * 2), 0),
785 refill(pool.nnz_c1, (mb_w * 2) * (mb_h * 2), 0),
786 ],
787 modes_y: refill(pool.modes_y, (mb_w * 4) * (mb_h * 4), 2),
788 coded_y: refill(pool.coded_y, (mb_w * 4) * (mb_h * 4), false),
789 mv_y: refill(pool.mv_y, (mb_w * 4) * (mb_h * 4), (0, 0)),
790 inter_y: refill(pool.inter_y, (mb_w * 4) * (mb_h * 4), false),
791 ref_idx_y: refill(pool.ref_idx_y, (mb_w * 4) * (mb_h * 4), -1i8),
792 // GATED ON b_possible: the List-1 grids are written only by B-slice
793 // paths and read only through `has1`/`ref_id1.is_empty()`, both of
794 // which are false without B slices. `as_reference_pooled` already
795 // gates its clones this way -- the pool refill did not, so a
796 // Baseline stream cleared 478 KB per picture it could never read.
797 mv1: if b_possible {
798 refill(pool.mv1, (mb_w * 4) * (mb_h * 4), (0, 0))
799 } else {
800 let mut v = pool.mv1;
801 v.clear();
802 v
803 },
804 ref_idx1: if b_possible {
805 refill(pool.ref_idx1, (mb_w * 4) * (mb_h * 4), -1i8)
806 } else {
807 let mut v = pool.ref_idx1;
808 v.clear();
809 v
810 },
811 refs1: Vec::new(),
812 num_ref_active1: 0,
813 is_b: false,
814 b_possible,
815 direct_spatial: true,
816 nnz_l_cache: [0x80; 25],
817 nnz_c_cache: [[0x80; 9]; 2],
818 refs,
819 num_ref_active,
820 constrained_intra,
821 scaling: None,
822 scaling8: None,
823 transform_8x8_mode,
824 transform_bypass: false,
825 route_skip_mbs: 0,
826 route_coded_mbs: 0,
827 slice_bounds: Vec::new(),
828 cur_idc2: false,
829 mb_t8x8: refill(pool.mb_t8x8, mb_w * mb_h, false),
830 mb_umot: refill(pool.mb_umot, mb_w * mb_h, false),
831 bs_frame: refill(pool.bs_frame, mb_w * mb_h, Default::default()),
832 bs_rows: 0,
833 flt_rows: 0,
834 pk_prev: {
835 let mut v = pool.pk_prev;
836 v.clear();
837 v
838 },
839 pk_cur: {
840 let mut v = pool.pk_cur;
841 v.clear();
842 v
843 },
844 // GATED ON THE PPS FLAG, not just on `any_t8`. The four reads of
845 // this grid all sit inside `if self.any_t8`, and `any_t8` is only
846 // ever set from `transform_size_8x8_flag`, which the PPS's
847 // `transform_8x8_mode_flag` gates. So with 8x8 off the grid is never
848 // touched -- and it was still being cleared for every picture, which
849 // the copy census priced at 17.3 MB over a 60-frame decode. Main and
850 // Baseline profiles cannot carry 8x8 at all, so this is the common
851 // case rather than an edge one.
852 nnz_dbr: if transform_8x8_mode {
853 refill(pool.nnz_dbr, (mb_w * 4) * (mb_h * 4), 0)
854 } else {
855 let mut v = pool.nnz_dbr;
856 v.clear();
857 v
858 },
859 bak_y: refill(pool.bak_y, cw, 0),
860 bak_u: refill(pool.bak_u, ccw, 0),
861 bak_v: refill(pool.bak_v, ccw, 0),
862 edc_jobs: Vec::new(),
863 edc_job_pool: pool.job_pool,
864 edc_nores_pool: pool.nores_pool,
865 scratch_luma: None,
866 scratch_luma8: None,
867 scratch_cac: None,
868 edc_active: false,
869 edc_tx: None,
870 edc_ctx_rx: None,
871 edc_back_tx: None,
872 fault: None,
873 edc_parked: None,
874 edc_regions: None,
875 bsk_last: None,
876 edc_batch: Vec::new(),
877 bits_per_mb,
878 db_ena: false,
879 db_oa: 0,
880 db_ob: 0,
881 mb_kind: refill(
882 pool.mb_kind,
883 mb_w * mb_h,
884 rusty_h264_common::deblock::MB_KIND_UNSET,
885 ),
886 weights: None,
887 row_wait: crate::row_progress_on(),
888 col_ok: false,
889 col_w4: 0,
890 any_t8: false,
891 any_idc2: false,
892 skip_zero_next: usize::MAX,
893 bzero: refill(pool.bzero, mb_w * mb_h, false),
894 sc_cat: pool.sc_cat,
895 sc_cbp: pool.sc_cbp,
896 sc_cmode: pool.sc_cmode,
897 sc_nzc: pool.sc_nzc,
898 sc_cbfdc: pool.sc_cbfdc,
899 sc_skip: pool.sc_skip,
900 sc_ref: pool.sc_ref,
901 sc_mvd: pool.sc_mvd,
902 sc_ref1: pool.sc_ref1,
903 sc_mvd1: pool.sc_mvd1,
904 sc_direct: pool.sc_direct,
905 bzspan: None,
906 pzspan: None,
907 iw00: None,
908 weights_id0: false,
909 weights_l0id: false,
910 cur_poc: 0,
911 weighted_bipred_idc: 0,
912 direct_8x8_inference: false,
913 progress: None,
914 ref_poc0,
915 ref_poc1: {
916 let mut v = pool.ref_poc1;
917 v.clear();
918 v
919 },
920 }
921 }
922
923 fn refresh_ref_pocs(&mut self) {
924 self.ref_poc0.clear();
925 self.ref_poc0.extend(self.refs.iter().map(|f| f.pic_poc()));
926 self.ref_poc1.clear();
927 self.ref_poc1.extend(self.refs1.iter().map(|f| f.pic_poc()));
928 }
929
930 /// Frame-MT Phase B: attach the shared progress Arc for row watermarks.
931 pub fn set_progress_slot(&mut self, slot: crate::Ref) {
932 self.progress = Some(slot);
933 }
934
935 /// Wait until every L0/L1 ref has enough ready luma rows for MB row `mb_y`
936 /// (pad conservatively for MV overshoot).
937 #[inline(always)]
938 fn wait_refs_for_mb(&self, mb_y: usize) {
939 // Cached at construction: in 1T (row-progress off, the default) this
940 // is one field test instead of a fn call + OnceLock read per MB.
941 if !self.row_wait {
942 return;
943 }
944 let need = crate::RefFrame::rows_needed_for_mb(mb_y, self.ch);
945 crate::RefFrame::set_mc_row_need(mb_y, self.ch);
946 for r in self.refs.iter().chain(self.refs1.iter()) {
947 // Only pay the wait when the ref may still be in-flight (Phase B).
948 if r.live.is_some() && r.frozen.get().is_none() {
949 r.wait_ready_rows(need);
950 }
951 }
952 }
953
954 fn publish_progress(&mut self) {
955 let Some(slot) = &self.progress else {
956 return;
957 };
958 // Under EDC the worker publishes (PixelCtx::publish_progress_rows).
959 if self.edc_tx.is_some() {
960 return;
961 }
962 // Cached field, not the knob function: this runs per ROW.
963 if !rowdb_on() || !self.db_ena || self.flt_rows == 0 {
964 return;
965 }
966 publish_filtered_rows_to_slot(
967 slot,
968 &self.rec_y,
969 &self.rec_u,
970 &self.rec_v,
971 self.cw,
972 self.ccw,
973 self.ch,
974 self.flt_rows,
975 );
976 }
977
978 /// Sets the explicit weighted-prediction tables for this slice.
979 pub fn set_weights(&mut self, weights: WeightTable) {
980 self.weights_id0 = weights.ref0_identity();
981 self.weights_l0id = weights.list_identity(0);
982 self.weights = Some(weights);
983 }
984
985 /// Applies explicit uni-prediction weighting to a motion-compensated partition
986 /// (luma `pred_y` region + the two chroma planes), if weighting is active.
987 /// `list` is the reference list and `refi` the partition's reference index.
988 fn weight_partition(
989 &self,
990 pred_y: &mut [u8; 256],
991 c_pred: &mut [[u8; 64]; 2],
992 list: usize,
993 refi: usize,
994 rx: usize,
995 ry: usize,
996 rw: usize,
997 rh: usize,
998 ) {
999 let Some(wt) = &self.weights else { return };
1000 // Identity list-0 table (x264 weightp=2 outside fades): the whole pass
1001 // is an exact per-pixel no-op — skip it. RS_H264_NO_SKIPFP restores it
1002 // for paired A/B (same knob family as the P_Skip weight skip).
1003 if list == 0 && self.weights_l0id && !no_skipfp() {
1004 edcstat::bump(&edcstat::WP_SKIPPED, 1);
1005 return;
1006 }
1007 // REFUTED, reverted: row-slicing these loops measured +28% instructions
1008 // on `weight_partition` (the extents are runtime values, so the slice
1009 // bounds cost more than the per-sample checks they replaced). The real
1010 // win for this function was hoisting the identity test to its CALLERS,
1011 // which stands.
1012 // RESOLVE ONCE, APPLY MANY. `apply_luma` re-read `self.luma[list][refi]`
1013 // — an array index, a Vec deref and an element index, two of them bounds
1014 // checked — on EVERY sample, up to 256 luma plus 128 chroma per call.
1015 // The weight is a property of the partition, not of the pixel. (The loop
1016 // SHAPE is untouched: row-slicing it is refuted above.)
1017 // MASKED, not row-sliced. `pred_y` is a fixed `[u8; 256]` and `c_pred` a
1018 // `[[u8; 64]; 2]`, so `& 255` / `& 63` are no-ops that prove the index
1019 // outright — where SLICING these loops cost +28% (the extents are runtime
1020 // values, so the slice bounds outweigh the checks). Same lesson as
1021 // `luma_centre`: the refutation was of one SHAPE, not of the goal.
1022 let (lw, lo) = wt.luma_wo(list, refi);
1023 weight_block(pred_y, 16, rx, ry, rw, rh, lw, lo, wt.luma_log2_denom);
1024 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
1025 for cc in 0..2 {
1026 let (cw, co) = wt.chroma_wo(list, refi, cc);
1027 weight_block(
1028 &mut c_pred[cc & 1],
1029 8,
1030 crx,
1031 cry,
1032 crw,
1033 crh,
1034 cw,
1035 co,
1036 wt.chroma_log2_denom,
1037 );
1038 }
1039 }
1040
1041 /// Sets the High-profile scaling matrices (raster order: six 4×4 lists, two
1042 /// 8×8 luma lists). The caller un-zig-zags the SPS lists. Flat is the default.
1043 /// GATE 1 router counters: (skip MBs, residual-coded MBs) this picture.
1044 pub fn route_counters(&self) -> (u32, u32) {
1045 (self.route_skip_mbs, self.route_coded_mbs)
1046 }
1047
1048 /// SPS `qpprime_y_zero_transform_bypass_flag` — see [`Self::step_qp`].
1049 pub fn set_transform_bypass(&mut self, on: bool) {
1050 self.transform_bypass = on;
1051 }
1052
1053 pub fn set_scaling(&mut self, scaling: [[i32; 16]; 6], scaling8: [[i32; 64]; 2]) {
1054 self.scaling = Some(scaling);
1055 self.scaling8 = Some(scaling8);
1056 }
1057
1058 /// Dequantizes a 4×4 AC block with scaling list `list` (flat if none active).
1059 /// Per-(qp, list) dequant constants for the fused scan-order kernel: the flat
1060 /// table at zero cost, or the scaling-list build once per macroblock.
1061 #[inline]
1062 fn dq_const(&self, qp: u8, list: usize) -> DequantQp {
1063 match &self.scaling {
1064 Some(s) => DequantQp::weighted(qp, &s[list]),
1065 None => DQ_FLAT[(qp as usize).min(51)],
1066 }
1067 }
1068
1069 /// Single-coefficient twin of `dequant` for position 0 (DC-only fast path).
1070 fn dequant_dc4(&self, level: i32, qp: u8, list: usize) -> i32 {
1071 rusty_h264_common::transform::dequantize_dc4(
1072 level,
1073 qp,
1074 self.scaling.as_ref().map(|s| s[list][0]),
1075 )
1076 }
1077
1078 /// Inverse-quantizes a chroma DC block with scaling list `list`'s DC weight.
1079 fn dequant_chroma_dc(&self, levels: &[i32; 4], qp: u8, list: usize) -> [i32; 4] {
1080 match &self.scaling {
1081 Some(s) => inverse_quant_chroma_dc_weighted(levels, qp, s[list][0]),
1082 None => inverse_quant_chroma_dc(levels, qp),
1083 }
1084 }
1085
1086 /// Sets the B-slice context for the slice about to be decoded: `RefPicList1`,
1087 /// its active count, and the direct-mode flag.
1088 pub fn set_b_context(
1089 &mut self,
1090 refs1: Vec<crate::Ref>,
1091 num_ref_active1: usize,
1092 direct_spatial: bool,
1093 cur_poc: i32,
1094 weighted_bipred_idc: u8,
1095 direct_8x8_inference: bool,
1096 ) {
1097 self.is_b = true;
1098 self.refs1 = refs1;
1099 self.num_ref_active1 = num_ref_active1;
1100 self.direct_spatial = direct_spatial;
1101 self.cur_poc = cur_poc;
1102 self.weighted_bipred_idc = weighted_bipred_idc;
1103 self.direct_8x8_inference = direct_8x8_inference;
1104 self.iw00 = None; // slice-scoped cache: lists/POC may have changed
1105 // Colocated fast-probe cache (see col_zero / col_zero_fast).
1106 self.col_ok = self
1107 .refs1
1108 .first()
1109 .is_some_and(|c| c.live.is_none() && !c.long_term && c.w4 != 0);
1110 self.col_w4 = self.refs1.first().map_or(0, |c| c.w4);
1111 self.refresh_ref_pocs();
1112 }
1113
1114 /// Cached `implicit_weights(0, 0)` — constant within a slice.
1115 fn iw00(&mut self) -> Option<(i32, i32)> {
1116 // Read the discriminant ONCE. The `is_none()` + `unwrap()` pair tested it
1117 // twice and carried an `unwrap_failed` path for a value this very function
1118 // had just written. `get_or_insert_with` cannot be used here: the closure
1119 // would need `&mut self` while the `Option` is already borrowed.
1120 match self.iw00 {
1121 Some(v) => v,
1122 None => {
1123 let v = if self.refs.is_empty() || self.refs1.is_empty() {
1124 None
1125 } else {
1126 self.implicit_weights(0, 0)
1127 };
1128 self.iw00 = Some(v);
1129 v
1130 }
1131 }
1132 }
1133
1134 /// Steps the running luma QP by a `mb_qp_delta` (spec §7.4.5, 8-bit depth):
1135 /// `QPy = (QPy_prev + delta + 52) % 52`.
1136 /// Steps QPy by `mb_qp_delta` (spec §7.4.5 modulo). Called exactly for
1137 /// residual-coded macroblocks (mb_qp_delta presence ⇔ cbp != 0 or I_16x16),
1138 /// which makes it the one chokepoint for the transform-bypass refusal:
1139 /// with `qpprime_y_zero_transform_bypass_flag` set and QP'Y == 0 the
1140 /// residual is LOSSLESS-bypassed (no transform, no quant — and DPCM intra
1141 /// forms), which this decoder does not implement. Refusing here is loud
1142 /// and exact: all-PCM lossless streams (no mb_qp_delta) still decode.
1143 fn step_qp(&mut self, delta: i32) -> Result<(), MbError> {
1144 // Router counter: step_qp fires exactly once per residual-coded MB.
1145 self.route_coded_mbs += 1;
1146 self.cur_qp = (self.cur_qp as i32 + delta + 52).rem_euclid(52) as u8;
1147 if self.transform_bypass && self.cur_qp == 0 {
1148 return Err(MbError::Unsupported(
1149 "transform-bypass (lossless) macroblock",
1150 ));
1151 }
1152 Ok(())
1153 }
1154
1155 /// Maps a luma QP to its chroma QP, applying `chroma_qp_index_offset`
1156 /// (spec §8.5.8): `QPc = qpc_table(Clip3(0, 51, QPy + offset))`.
1157 fn chroma_qp_for(&self, qp_y: u8) -> u8 {
1158 let qpi = (qp_y as i32 + self.chroma_qp_offset).clamp(0, 51) as u8;
1159 chroma_qp(qpi)
1160 }
1161
1162 /// Resets per-slice state before decoding a continuation slice of the same
1163 /// picture: the running QP (each slice carries its own `slice_qp`) and the
1164 /// reference list (each slice may reorder it).
1165 pub fn begin_slice(&mut self, slice_qp: u8, refs: Vec<crate::Ref>, num_ref_active: usize) {
1166 self.cur_qp = slice_qp;
1167 self.qp = slice_qp;
1168 self.refs = refs;
1169 self.num_ref_active = num_ref_active;
1170 self.weights = None; // re-set per slice if a pred_weight_table is present
1171 self.weights_id0 = false;
1172 self.weights_l0id = false;
1173 self.refresh_ref_pocs();
1174 }
1175
1176 /// Whether the neighbor macroblock at `(nbx, nby)` is in the slice currently
1177 /// being decoded (address ≥ the slice's first MB). For single-slice pictures
1178 /// `slice_first_mb == 0`, so this is always true and prediction is unchanged.
1179 #[inline]
1180 fn nbr_in_slice(&self, nbx: usize, nby: usize) -> bool {
1181 nby * self.mb_w + nbx >= self.slice_first_mb
1182 }
1183
1184 /// Whether the neighbor 4×4 block at `(nbx, nby)` may contribute to intra
1185 /// prediction. With `constrained_intra_pred`, an inter-coded neighbor is
1186 /// treated as unavailable (spec §8.3.1.2.{1,2}); otherwise always usable.
1187 #[inline]
1188 fn intra_nbr_ok(&self, nbx: usize, nby: usize) -> bool {
1189 // Fallible read: this is inlined at eleven sites across the intra paths,
1190 // and `constrained_intra` short-circuits it on nearly every stream, so
1191 // the check was pure tax on a value that is usually never loaded.
1192 // `unwrap_or(true)` is the conservative direction — an out-of-range
1193 // neighbour reads as inter, i.e. UNAVAILABLE, which is what the
1194 // constrained-intra rule means by a neighbour it cannot use.
1195 !self.constrained_intra
1196 || !self
1197 .inter_y
1198 .get(nby * (self.mb_w * 4) + nbx)
1199 .copied()
1200 .unwrap_or(true)
1201 }
1202
1203 fn mv_neighbors(&self, mb_x: usize, mb_y: usize) -> [MvNeighbor; 3] {
1204 let w4 = self.mb_w * 4;
1205 let get = |avail: bool, bx: isize, by: isize| {
1206 if avail {
1207 let idx = by as usize * w4 + bx as usize;
1208 match (self.mv_y.get(idx), self.ref_idx_y.get(idx)) {
1209 (Some(&m), Some(&r)) => MvNeighbor {
1210 available: true,
1211 mv: (i32::from(m.0), i32::from(m.1)),
1212 ref_idx: i32::from(r),
1213 },
1214 _ => MvNeighbor::NONE,
1215 }
1216 } else {
1217 MvNeighbor::NONE
1218 }
1219 };
1220 let (bx, by) = (mb_x as isize * 4, mb_y as isize * 4);
1221 let a = get(mb_x > 0 && self.nbr_in_slice(mb_x - 1, mb_y), bx - 1, by);
1222 let b = get(mb_y > 0 && self.nbr_in_slice(mb_x, mb_y - 1), bx, by - 1);
1223 let c = if mb_y > 0 && mb_x + 1 < self.mb_w && self.nbr_in_slice(mb_x + 1, mb_y - 1) {
1224 get(true, bx + 4, by - 1)
1225 } else {
1226 get(
1227 mb_x > 0 && mb_y > 0 && self.nbr_in_slice(mb_x - 1, mb_y - 1),
1228 bx - 1,
1229 by - 1,
1230 )
1231 };
1232 [a, b, c]
1233 }
1234
1235 fn mv_neighbors_block(&self, pbx: isize, pby: isize, pwb: isize) -> [MvNeighbor; 3] {
1236 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Neighbors);
1237 self.mv_neighbors_block_grid(pbx, pby, pwb, 0)
1238 }
1239
1240 fn skip_mv(&self, mb_x: usize, mb_y: usize) -> (i32, i32) {
1241 let [a, b, c] = self.mv_neighbors(mb_x, mb_y);
1242 if !a.available
1243 || !b.available
1244 || (a.ref_idx == 0 && a.mv == (0, 0))
1245 || (b.ref_idx == 0 && b.mv == (0, 0))
1246 {
1247 (0, 0)
1248 } else {
1249 predict_mv(a, b, c, 0)
1250 }
1251 }
1252
1253 fn set_mb_mv(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32), inter: bool, refi: i32) {
1254 // ROW FILLS. This wrote all sixteen cells of three grids one indexed
1255 // store at a time - FORTY-EIGHT separate bounds checks per call - and
1256 // re-evaluated `if inter` inside the inner loop. Each macroblock row is
1257 // four CONTIGUOUS cells, so twelve `fill`s cover it.
1258 let w4 = self.mb_w * 4;
1259 let r = if inter { refi } else { -1 };
1260 for dy in 0..4 {
1261 let a = (mb_y * 4 + dy) * w4 + mb_x * 4;
1262 self.mv_y[a..a + 4].fill((mv.0 as i16, mv.1 as i16));
1263 self.inter_y[a..a + 4].fill(inter);
1264 self.ref_idx_y[a..a + 4].fill(r as i8);
1265 }
1266 }
1267
1268 /// Commit one inter partition's motion into the 4×4 grid (ref 0, 1-ref P).
1269 /// `(rx,ry,rw,rh)` are MB-relative luma pixels; committing before the next
1270 /// partition's prediction is what lets a later partition predict from it.
1271 fn commit_inter_grid(
1272 &mut self,
1273 mb_x: usize,
1274 mb_y: usize,
1275 rx: usize,
1276 ry: usize,
1277 rw: usize,
1278 rh: usize,
1279 mv: (i32, i32),
1280 refi: i8,
1281 ) {
1282 // Row-range fills instead of the per-4x4 scatter: partitions are
1283 // rectangles, so each grid row is one contiguous run (same values).
1284 let w4 = self.mb_w * 4;
1285 let (bx0, bw) = (mb_x * 4 + rx / 4, rw / 4);
1286 for by in ry / 4..ry / 4 + rh / 4 {
1287 let a = (mb_y * 4 + by) * w4 + bx0;
1288 self.mv_y[a..a + bw].fill((mv.0 as i16, mv.1 as i16));
1289 self.inter_y[a..a + bw].fill(true);
1290 self.ref_idx_y[a..a + bw].fill(refi);
1291 self.coded_y[a..a + bw].fill(true);
1292 }
1293 }
1294
1295 /// Per-slice deblock parameters, needed DURING decode by the row-interleave
1296 /// path. `ena` is already resolved against `RFF_ABL_DEBLOCK` by the caller.
1297 pub fn set_deblock_params(&mut self, ena: bool, oa: i32, ob: i32, idc2: bool) {
1298 // Latch: the FIRST disabling slice turns row filtering off for the rest
1299 // of the picture (see `row_hook`); rows already filtered stay counted
1300 // in `flt_rows` and the picture-end tail handles the remainder.
1301 self.db_ena = ena && (self.flt_rows == 0 || self.db_ena);
1302 self.db_oa = oa;
1303 self.db_ob = ob;
1304 self.cur_idc2 = idc2;
1305 }
1306
1307 /// Slice index owning `addr` (slices are raster-contiguous, bounds ascending).
1308 fn slice_of(&self, addr: usize) -> usize {
1309 match self.slice_bounds.binary_search_by(|&(f, _)| f.cmp(&addr)) {
1310 Ok(i) => i,
1311 Err(i) => i - 1,
1312 }
1313 }
1314
1315 /// Derives bS for macroblock row `r` from the just-decoded (hot) grids into
1316 /// `bs_frame`, maintaining the two-row rolling record window (R2 of
1317 /// docs/row-interleave-plan.md).
1318 fn derive_bs_row(&mut self, r: usize) {
1319 // bS derivation reads the motion/nnz grids — flush deferred spans.
1320 self.span_flush();
1321 // Same stage label the in-filter derivation used, so profiles keep
1322 // pricing bS derivation wherever it lives.
1323 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DebDerive);
1324 use rusty_h264_common::deblock::{
1325 derive_mb_kind, BlockInfo, MbBs, MbKind,
1326 };
1327 let (mb_w, w4) = (self.mb_w, self.mb_w * 4);
1328 edcstat::bump(&edcstat::DBS_ROWS, 1);
1329 // Transform-block coded mask for this row: raw nnz, then the 8x8 OR for
1330 // t8 macroblocks (spec §8.7: the 8x8 transform's coded status is per 8x8).
1331 //
1332 // T8 GATE. `nnz_dbr` exists ONLY to carry that OR. With no 8x8
1333 // macroblock anywhere in the picture it is a byte-for-byte copy of
1334 // `nnz_y`, so both the 4-row copy and the per-row scan below are pure
1335 // work: the derivation reads `nnz_y` directly instead. Census
1336 // (DBSDERIVE) measured t8mb=0 on EVERY cavlc and main stream while
1337 // nnz_rowcopy ran 10800-16320 sub-rows per decode.
1338 if self.any_t8 {
1339 for br in r * 4..r * 4 + 4 {
1340 let a = br * w4;
1341 self.nnz_dbr[a..a + w4].copy_from_slice(&self.nnz_y[a..a + w4]);
1342 }
1343 edcstat::bump(&edcstat::DBS_NNZ_ROWCOPY, 4);
1344 // WHOLE-MACROBLOCK ROW SLICES. The 2x2-of-2x2 nest did sixteen
1345 // bounds-checked reads and sixteen bounds-checked writes per t8
1346 // macroblock; the same cells are four contiguous runs of four, so
1347 // four slice reads and four slice writes cover them. The OR is over
1348 // bytes, so `a | b | c | d > 0` is exactly the old per-cell `> 0`.
1349 let t8_row_pre = &self.mb_t8x8[r * mb_w..][..mb_w];
1350 for mb_x in 0..mb_w {
1351 if !t8_row_pre[mb_x] {
1352 continue;
1353 }
1354 edcstat::bump(&edcstat::DBS_T8MB, 1);
1355 let base = r * 4 * w4 + mb_x * 4;
1356 let mut src = [[0u8; 4]; 4];
1357 for (k, row) in src.iter_mut().enumerate() {
1358 row.copy_from_slice(&self.nnz_y[base + k * w4..][..4]);
1359 }
1360 let mut out = [[0u8; 4]; 4];
1361 for b8 in 0..4usize {
1362 let (cx, cy) = ((b8 % 2) * 2, (b8 / 2) * 2);
1363 let any =
1364 (src[cy][cx] | src[cy][cx + 1] | src[cy + 1][cx] | src[cy + 1][cx + 1]) > 0;
1365 for sy in 0..2 {
1366 for sx in 0..2 {
1367 out[cy + sy][cx + sx] = any as u8;
1368 }
1369 }
1370 }
1371 for (k, row) in out.iter().enumerate() {
1372 self.nnz_dbr[base + k * w4..][..4].copy_from_slice(row);
1373 }
1374 }
1375 }
1376 let info = BlockInfo {
1377 inter: &self.inter_y,
1378 nnz: if self.any_t8 {
1379 &self.nnz_dbr
1380 } else {
1381 &self.nnz_y
1382 },
1383 mv: &self.mv_y,
1384 ref_id: &self.ref_idx_y,
1385 mv1: &self.mv1,
1386 ref_id1: if self.ref_poc1.is_empty() {
1387 &[]
1388 } else {
1389 &self.ref_idx1
1390 },
1391 w4,
1392 t8x8: &self.mb_t8x8,
1393 bs: &[],
1394 poc0: &self.ref_poc0,
1395 poc1: &self.ref_poc1,
1396 kind: &self.mb_kind,
1397 };
1398 let has1 = !info.ref_id1.is_empty();
1399 core::mem::swap(&mut self.pk_prev, &mut self.pk_cur);
1400 // ROW-LENGTH ROWS, WRITTEN IN PLACE. These were cleared here and `push`ed
1401 // once per macroblock, so every 288-byte record was built in a stack
1402 // temporary and memcpy'd into the vector, behind a capacity test and a
1403 // `grow_one` edge. Holding both rows at `mb_w` makes the slot addressable
1404 // before the record is built. Reads are unchanged: the loop only looks at
1405 // `mb_x` and `mb_x - 1`, both already written this row.
1406 if self.pk_cur.len() != mb_w {
1407 self.pk_cur.clear();
1408 self.pk_cur
1409 .resize(mb_w, rusty_h264_common::deblock::MbPack::default());
1410 }
1411 // WIN: read the knob at the USE SITE, not through the struct field.
1412 // is under cfg(not(knobs)), so LLVM folds
1413 // this to a constant and the kind-loads arm below becomes dead -- which
1414 // is what finally lets (1543 instrs of Skip/InterUniform/
1415 // Inter gathering the decoder never runs) leave the decode binary.
1416 // Storing a build-time-constant knob in a FIELD erases the constant:
1417 // the field is a runtime value and every arm it guards stays live.
1418 let kl = kind_loads();
1419 // Census gate resolved ONCE per row: `edcstat::on()` is a relaxed atomic
1420 // load and LLVM will not CSE atomic loads, so a per-MB bump costs a load
1421 // each. Same rule as hoisting an A/B arm selector out of the loop under
1422 // test (codec-measurement 15).
1423 let stats = edcstat::on();
1424 // ROW SLICES for the three per-macroblock grids. Each was indexed
1425 // `r * mb_w + mb_x` against a whole-FRAME Vec, which the compiler
1426 // cannot prove in range; sliced to THIS row, `[mb_x]` is provably
1427 // `< mb_w`. These are disjoint FIELDS of `self`, so the `&mut` on
1428 // `bs_frame` coexists with the `&` borrows `info` holds.
1429 let row0 = r * mb_w;
1430 let bs_row = &mut self.bs_frame[row0..][..mb_w];
1431 let t8_row = &self.mb_t8x8[row0..][..mb_w];
1432 let umot_row = &self.mb_umot[row0..][..mb_w];
1433 // SLICED LIKE ITS SIBLINGS, not through an emptiness test. `mb_kind` is
1434 // built by the same `refill(pool, mb_w * mb_h, ..)` that builds `bs_frame`,
1435 // `mb_t8x8` and `mb_umot` -- the three lines directly above already slice
1436 // on exactly this invariant and would panic the same way if it broke. The
1437 // empty arm made `kind_row`'s length a union of 0 and `mb_w`, which is not
1438 // provable, so every `kind_row[mb_x]` in the per-macroblock loop below
1439 // carried a bounds check that its siblings did not.
1440 let kind_row = &self.mb_kind[row0..][..mb_w];
1441 for mb_x in 0..mb_w {
1442 // Always pack: UNSET / Inter neighbours in this row and the next
1443 // read left/top MbPack. Kind stores MbBs directly (no i32 hop).
1444 // Decided BEFORE the record is built, because `pack_mb` uses it too:
1445 // a macroblock known uniform reads block 0 once and splats it rather
1446 // than gathering the same values sixteen times.
1447 let kind_here = MbKind::from_u8(kind_row[mb_x]);
1448 let known_uniform = matches!(
1449 kind_here,
1450 Some(MbKind::Skip | MbKind::InterUniform)
1451 ) || umot_row.get(mb_x).copied().unwrap_or(false);
1452 let Some(slot) = self.pk_cur.get_mut(mb_x) else {
1453 continue;
1454 };
1455 rusty_h264_common::deblock::pack_mb_into(
1456 &info,
1457 has1,
1458 mb_x,
1459 r,
1460 known_uniform,
1461 slot,
1462 );
1463 // UNIFORMITY, DECIDED ONCE, WHERE THE RECORD IS BUILT.
1464 //
1465 // Two kinds state the answer the `mb_uniform` kernel would spend six
1466 // motion planes computing: `Skip` is P_Skip (one reference, one
1467 // vector) and `InterUniform` is P_L0_16x16, a SINGLE partition. Both
1468 // are set only on P paths, where the List-1 planes sit at their
1469 // uniform defaults. On the tt corpus that covers ~20-23% of the
1470 // macroblocks reaching the derivation.
1471 //
1472 // Recording it on the RECORD rather than inside the derivation is
1473 // what makes a macroblock's NEIGHBOURS uniformity visible, which
1474 // collapses each macroblock edge's four motion tests to one.
1475 // SAME `slot`, not a second lookup -- and not `last_mut()`. While the
1476 // rows were `push`ed, the record just built WAS the last one; once
1477 // they are held at row length that is the row's final slot instead,
1478 // which silently wrote every macroblock's uniformity onto index
1479 // mb_w - 1 and left the rest at their default `false`. Byte-identity
1480 // could not see it, because `uniform` is a hint whose false direction
1481 // takes the long path to the same strengths -- it only turned the
1482 // edge collapse off. Bound once, used twice.
1483 slot.uniform = match kind_here {
1484 // Intra records are never read for motion: a neighbour whose
1485 // `inter` is false takes the constant strength-4 arm.
1486 Some(MbKind::Intra) => false,
1487 _ if known_uniform => {
1488 rusty_h264_common::deblock::verify_uniform_hint_check(slot);
1489 true
1490 }
1491 // Only what neither the kind nor the B paths answered asks the
1492 // kernel.
1493 _ => rusty_h264_common::deblock::mb_uniform(slot),
1494 };
1495 if stats {
1496 edcstat::bump(&edcstat::DBS_MB, 1);
1497 }
1498 // KIND ECONOMICS ON THE PACKED PATH: derive_mb_kind(Skip) does 9
1499 // fresh strided Blk::loads per MB, but pack_mb already ran for this
1500 // MB (the line above) — the packed `_` arm derives from those
1501 // records with no further gathers. A B_Skip kind experiment
1502 // measured kind-arm 1.8% SLOWER (z=-2.69) with +1.07M loads, so
1503 // Skip/InterUniform route to the packed arm; Intra keeps the kind
1504 // arm (pure constants, no loads). `RS_H264_KIND_LOADS=1` restores
1505 // the old routing for paired A/B.
1506 match kind_row.get(mb_x).copied().and_then(MbKind::from_u8) {
1507 Some(MbKind::Intra) => {
1508 if stats {
1509 edcstat::bump(&edcstat::DBS_INTRA, 1);
1510 }
1511 // WIN: intra strengths are a constant table keyed on availability;
1512 // asking derive_mb_kind for them dragged its whole gathering body
1513 // (Skip/InterUniform/Inter, 1654 instrs) into the decode binary,
1514 // even though the only other arm that calls it is guarded by a
1515 // build-time false knob.
1516 bs_row[mb_x] =
1517 rusty_h264_common::deblock::intra_mb_bs(mb_x, r, t8_row[mb_x]);
1518 }
1519 Some(k @ (MbKind::Skip | MbKind::InterUniform)) if kl => {
1520 if stats {
1521 edcstat::bump(&edcstat::DBS_KINDARM, 1);
1522 }
1523 let mut m = derive_mb_kind(&info, mb_x, r, k);
1524 // This arm is behind a build-time-false knob, but it is a
1525 // PRODUCER of stored strengths and the filter no longer
1526 // re-tests t8 on the precomputed path -- so it has to encode
1527 // the same thing every other producer does.
1528 if t8_row[mb_x] {
1529 m.v[1] = [0; 4];
1530 m.v[3] = [0; 4];
1531 m.h[1] = [0; 4];
1532 m.h[3] = [0; 4];
1533 }
1534 bs_row[mb_x] = m;
1535 }
1536 _ => {
1537 let Some(cur) = self.pk_cur.get(mb_x) else {
1538 continue;
1539 };
1540 let left = if mb_x > 0 {
1541 self.pk_cur.get(mb_x - 1)
1542 } else {
1543 None
1544 };
1545 let top = if r > 0 { self.pk_prev.get(mb_x) } else { None };
1546 let mb_t8 = t8_row[mb_x];
1547 // WIN: derive DIRECTLY into the 32-byte record this loop
1548 // stores. The pair of `[[i32; 4]; 4]` scratch arrays that
1549 // used to sit here were 128 bytes zeroed per macroblock, and
1550 // every value written into them was a bS in 0..=4 that a
1551 // narrowing pass below then copied, one lane at a time, into
1552 // exactly this `MbBs`. The core is generic over the width now
1553 // (`BsElem`), so the u8 form is the same derivation, not a
1554 // second copy of it, and the i32 monomorphization stays out
1555 // of the decode binary entirely.
1556 // AND STRAIGHT INTO THE GRID SLOT, no 32-byte temporary.
1557 // `bs_row` is this row of `bs_frame`, a disjoint field from
1558 // the record rows `cur`/`left`/`top` come from, so the
1559 // derivation can write where the value is kept. Same removal
1560 // as the packed record's, one size down.
1561 let m = &mut bs_row[mb_x];
1562 *m = MbBs::default();
1563 rusty_h264_common::deblock::census_note_packed();
1564 let flat =
1565 rusty_h264_common::deblock::derive_mb_records_bs(cur, left, top, m, mb_t8);
1566 if stats {
1567 edcstat::bump(&edcstat::DBS_PACKED, 1);
1568 }
1569 // MEASUREMENT ONLY: sizes the `kind_loads()` match guard that
1570 // used to be a OnceLock deref here (now `k_kindloads`).
1571 if stats
1572 && matches!(
1573 // WIN: kind_row is already this row of mb_kind, proven in range
1574 // above, so [mb_x] needs no whole-frame bounds check. Identical
1575 // when mb_kind is empty: kind_row is &[] and both answer None.
1576 kind_row.get(mb_x).copied().and_then(MbKind::from_u8),
1577 Some(MbKind::Skip | MbKind::InterUniform)
1578 )
1579 {
1580 edcstat::bump(&edcstat::DBS_KINDGUARD, 1);
1581 }
1582 // FLAT-AWARE NARROWING. `derive_mb_records` returns early on a
1583 // flat inter macroblock having written ONLY edge 0 of each
1584 // orientation; edges 1..4 are still the caller's zero-init, so
1585 // widening all 32 entries copies 24 known zeros onto 24 known
1586 // zeros - after a `MbBs::default()` that zeroed them a third
1587 // time. Census DBSDERIVE flat: 96.8% screen_text, 90.2%
1588 // FourPeople, 82.1% akiyo - the dominant class.
1589 // FLAT-AWARE NARROWING IS GONE, not merely cheaper: with the
1590 // derivation writing `u8` in place there is nothing to narrow.
1591 // A flat macroblock (census DBSDERIVE: 96.8% screen_text,
1592 // 90.2% FourPeople, 82.1% akiyo -- the dominant class) leaves
1593 // edges 1..4 at the zero `MbBs::default()` already wrote,
1594 // which is what the widen-then-narrow pair used to spend 24
1595 // copies of a known zero arriving at.
1596 if flat && stats {
1597 edcstat::bump(&edcstat::DBS_FLAT, 1);
1598 }
1599 debug_assert!(!flat || (m.v[1..] == [[0u8; 4]; 3] && m.h[1..] == [[0u8; 4]; 3]));
1600 // MEASUREMENT ONLY: `on()` first, so the 32-byte compare
1601 // never runs in a shipped decode (it is not otherwise needed
1602 // here — the consumer in filter_frame_rows makes it).
1603 if stats && m.v == [[0u8; 4]; 4] && m.h == [[0u8; 4]; 4] {
1604 edcstat::bump(&edcstat::DBS_ALLZERO, 1);
1605 }
1606 }
1607 }
1608 }
1609 // disable_deblocking_filter_idc == 2 (spec §7.4.3): a slice may forbid
1610 // filtering ITS macroblocks' edges against OTHER slices. bS = 0 on the
1611 // crossing MB edges kills exactly those filters; interior edges keep
1612 // their derived strengths. Guarded so single-slice / idc 0-1 pictures
1613 // pay one branch per row.
1614 if self.any_idc2 && self.slice_bounds.len() > 1 {
1615 edcstat::bump(&edcstat::DBS_IDC2ROW, 1);
1616 for mb_x in 0..mb_w {
1617 let slot = r * mb_w + mb_x;
1618 let si = self.slice_of(slot);
1619 if !self.slice_bounds.get(si).is_some_and(|b| b.1) {
1620 continue;
1621 }
1622 if mb_x > 0 && self.slice_of(slot - 1) != si {
1623 if let Some(b) = self.bs_frame.get_mut(slot) {
1624 b.v[0] = [0; 4];
1625 }
1626 }
1627 if r > 0 && self.slice_of(slot - mb_w) != si {
1628 if let Some(b) = self.bs_frame.get_mut(slot) {
1629 b.h[0] = [0; 4];
1630 }
1631 }
1632 }
1633 }
1634 }
1635
1636 /// Decode-loop hook: called at each MB-loop head with the NEXT address to be
1637 /// decoded; derives AND FILTERS (R3) every fully-decoded row. Filtering a
1638 /// row here preserves the spec's raster per-MB filter order exactly (every
1639 /// MB the row's edges touch is already decoded; bottom-adjacent edges
1640 /// belong to the NEXT row's MBs, which filter later).
1641 ///
1642 /// Fast path: mid-row heads (`done <= bs_rows`) do no row work — return
1643 /// before the profiler scope (this hook is entered once per MB; scoping
1644 /// every call was measuring the timer, not the filter).
1645 /// row_hook with the caller's carried row — avoids the per-MB division.
1646 #[inline(always)]
1647 fn row_hook_at(&mut self, addr: usize, mby: usize) {
1648 if rowdb_on() {
1649 // ~44/45 of calls are mid-row: one compare, no atomic loads.
1650 if !rowhook_eager() && mby <= self.bs_rows {
1651 return;
1652 }
1653 }
1654 self.row_hook(addr, mby);
1655 }
1656
1657 /// `mby` is the row `addr` sits in. It is a PARAMETER rather than
1658 /// `addr / self.mb_w` because both callers already carry it: the slice loops
1659 /// track `(mbx, mby)` with a compare-and-wrap precisely so the per-macroblock
1660 /// div+mod never runs, and this hook -- entered PER MACROBLOCK on the
1661 /// non-rowdb arm -- was reconstructing it with an integer divide anyway.
1662 fn row_hook(&mut self, addr: usize, mby: usize) {
1663 debug_assert_eq!(mby, addr / self.mb_w, "row_hook: mby must be addr's row");
1664 // `RS_H264_ROWHOOK_EAGER=1` restores per-MB profiler scoping (A/B oracle).
1665 // READ THE KNOB FUNCTIONS, NOT THE CACHED FIELDS. The old note here said
1666 // the functions cost a OnceLock deref plus a relaxed atomic load per
1667 // entry; that was true BEFORE the routing round, and is not true now --
1668 // they are `return false` under cfg(not(knobs)), i.e. a constant. Going
1669 // through a struct FIELD erases that constant, because the field is a
1670 // runtime value, and every arm the knob guards then stays live in the
1671 // binary. That is what kept `derive_mb_kind` (1543 instrs the decoder
1672 // never runs) linked in until the same change was made for kind_loads.
1673 let eager = rowhook_eager();
1674 if !rowdb_on() {
1675 edcstat::bump(&edcstat::MBS, 1);
1676 let _rh = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecRowHook);
1677 self.edc_flush();
1678 let done = mby;
1679 if done > self.bs_rows {
1680 self.bs_rows = done;
1681 self.publish_progress();
1682 }
1683 return;
1684 }
1685 let done = mby;
1686 // ~44/45 of calls are mid-row: no derive/filter/handoff yet.
1687 if !eager && done <= self.bs_rows {
1688 return;
1689 }
1690 edcstat::bump(&edcstat::MBS, 1);
1691 let _rh = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecRowHook);
1692 if done <= self.bs_rows {
1693 return;
1694 }
1695 if self.edc_tx.is_some() {
1696 // E2: derivation stays here (it reads the syntax grids); filtering
1697 // is the worker's, fed the row's bs/qp/t8 snapshot. `flt_rows`
1698 // advances on the worker and comes home with the context.
1699 self.edc_giveback();
1700 while self.bs_rows < done {
1701 let r = self.bs_rows;
1702 self.derive_bs_row(r);
1703 self.bs_rows += 1;
1704 let base = r * self.mb_w;
1705 // ORDER: this row's pixel jobs must reach the worker BEFORE the
1706 // filter message for the same row.
1707 self.edc_flush_batch();
1708 edcstat::bump(&edcstat::ROWS, 1);
1709 edcstat::bump(
1710 &edcstat::ROWBYTES,
1711 (self.mb_w * (core::mem::size_of::<rusty_h264_common::deblock::MbBs>() + 2))
1712 as u64,
1713 );
1714 let msg = EdcMsg::Row {
1715 r,
1716 bs: self.bs_frame[base..base + self.mb_w].to_vec(),
1717 qp: self.mb_qp[base..base + self.mb_w].to_vec(),
1718 t8: self.mb_t8x8[base..base + self.mb_w].to_vec(),
1719 };
1720 self.edc_post(msg);
1721 }
1722 return;
1723 }
1724 self.edc_flush();
1725 while self.bs_rows < done {
1726 let r = self.bs_rows;
1727 self.derive_bs_row(r);
1728 self.bs_rows += 1;
1729 // Row filtering requires deblock enabled on EVERY slice so far
1730 // (`db_ena` latches false once any slice disables it): a mixed
1731 // picture falls back to the picture-end tail so "latest slice
1732 // wins" semantics are preserved.
1733 if self.db_ena {
1734 self.save_bak(r);
1735 self.filter_row(r);
1736 self.flt_rows = r + 1;
1737 }
1738 self.publish_progress();
1739 }
1740 }
1741
1742 /// Saves the UNFILTERED bottom pixel rows of MB row `r` before filtering
1743 /// modifies them: the next row's intra prediction must read pre-deblock
1744 /// samples (spec §8.3), and filtering touches the bottom three rows while
1745 /// intra reads exactly the bottom ONE (+ the corner) — so one backup row
1746 /// per plane suffices, overwritten per row.
1747 fn save_bak(&mut self, r: usize) {
1748 let y0 = (r * 16 + 15) * self.cw;
1749 self.bak_y.copy_from_slice(&self.rec_y[y0..y0 + self.cw]);
1750 let c0 = (r * 8 + 7) * self.ccw;
1751 self.bak_u.copy_from_slice(&self.rec_u[c0..c0 + self.ccw]);
1752 self.bak_v.copy_from_slice(&self.rec_v[c0..c0 + self.ccw]);
1753 }
1754
1755 /// Filters one MB row against the stored strengths, using the CURRENT
1756 /// slice's alpha/beta offsets (single-offset streams — the whole corpus —
1757 /// are bit-identical to the picture-end call; the plan's risk register
1758 /// documents the multi-offset divergence).
1759 fn filter_row(&mut self, r: usize) {
1760 let info = rusty_h264_common::deblock::BlockInfo {
1761 inter: &self.inter_y,
1762 // Same source `derive_bs_row` used (see the T8 GATE there). This
1763 // path passes a non-empty `bs`, so it takes the PRECOMPUTED arm and
1764 // never reads `nnz` at all; kept in step so the two cannot diverge.
1765 nnz: if self.any_t8 {
1766 &self.nnz_dbr
1767 } else {
1768 &self.nnz_y
1769 },
1770 mv: &self.mv_y,
1771 ref_id: &self.ref_idx_y,
1772 mv1: &self.mv1,
1773 ref_id1: &self.ref_idx1,
1774 w4: self.mb_w * 4,
1775 t8x8: &self.mb_t8x8,
1776 bs: &self.bs_frame,
1777 poc0: &[],
1778 poc1: &[],
1779 kind: &self.mb_kind,
1780 };
1781 rusty_h264_common::deblock::filter_frame_rows_pre(
1782 &mut self.rec_y,
1783 &mut self.rec_u,
1784 &mut self.rec_v,
1785 self.mb_w,
1786 self.mb_h,
1787 r..r + 1,
1788 &self.mb_qp,
1789 self.chroma_qp_offset,
1790 self.db_oa,
1791 self.db_ob,
1792 &info,
1793 );
1794 }
1795
1796 /// Top-neighbour LUMA pixel for intra prediction: reads the unfiltered
1797 /// backup row when the row above has already been deblock-filtered by the
1798 /// row-interleave (flt_rows gates it; 0 when the interleave is off, so
1799 /// this compiles to the plain read on the fallback path).
1800 #[inline]
1801 fn top_y_px(&self, py: usize, x: usize) -> u8 {
1802 // 128 is the spec's value for an unavailable sample (1 << (BitDepth - 1),
1803 // §8.3.1.2), so the fallback is the one the prediction rules already use
1804 // — and unreachable anyway, since callers gate on availability first.
1805 if py % 16 == 0 && self.flt_rows * 16 >= py {
1806 self.bak_y.get(x).copied().unwrap_or(128)
1807 } else {
1808 self.rec_y
1809 .get((py - 1) * self.cw + x)
1810 .copied()
1811 .unwrap_or(128)
1812 }
1813 }
1814
1815 /// Slice form of [`Self::top_y_px`] for the contiguous 16-wide I16 gather.
1816 #[inline]
1817 fn top_y_row(&self, py: usize, x: usize, n: usize) -> &[u8] {
1818 if py % 16 == 0 && self.flt_rows * 16 >= py {
1819 &self.bak_y[x..x + n]
1820 } else {
1821 &self.rec_y[(py - 1) * self.cw + x..][..n]
1822 }
1823 }
1824
1825 /// Top-neighbour CHROMA pixel (plane `c`: 0 = U, 1 = V).
1826 #[inline]
1827 fn top_c_px(&self, c: usize, cy: usize, x: usize) -> u8 {
1828 // 128 = the spec's unavailable sample, as in `top_y_px`.
1829 if cy % 8 == 0 && self.flt_rows * 8 >= cy {
1830 let bak = if c == 0 { &self.bak_u } else { &self.bak_v };
1831 bak.get(x).copied().unwrap_or(128)
1832 } else {
1833 let rec = if c == 0 { &self.rec_u } else { &self.rec_v };
1834 rec.get((cy - 1) * self.ccw + x).copied().unwrap_or(128)
1835 }
1836 }
1837
1838 /// Slice form of [`Self::top_c_px`] for the 8-wide chroma gather.
1839 #[inline]
1840 fn top_c_row(&self, c: usize, cy: usize, x: usize, n: usize) -> &[u8] {
1841 if cy % 8 == 0 && self.flt_rows * 8 >= cy {
1842 if c == 0 {
1843 &self.bak_u[x..x + n]
1844 } else {
1845 &self.bak_v[x..x + n]
1846 }
1847 } else {
1848 let rec = if c == 0 { &self.rec_u } else { &self.rec_v };
1849 &rec[(cy - 1) * self.ccw + x..][..n]
1850 }
1851 }
1852
1853 /// Snapshots the (deblocked) reconstruction as a reference picture, drawing
1854 /// its padded-plane allocations from `pool` (recycled
1855 /// planes of evicted DPB frames — see `Decoder::reclaim_retired`). ~1.9 MB of
1856 /// fresh allocation per reference picture otherwise (`dpb-clone` stage, 3-4%
1857 /// of decode, mostly first-touch page faults).
1858 pub fn as_reference_pooled(
1859 &self,
1860 pool: &mut Vec<Vec<u8>>,
1861 mv_pool: &mut Vec<Vec<(i16, i16)>>,
1862 ref_pool: &mut Vec<Vec<i8>>,
1863 ) -> crate::RefFrame {
1864 // MV CAPTURE (`RFF_MV_DUMP=1`) — lets a harness read the motion field any
1865 // conformant H.264 stream carries, including x264's, using this decoder as
1866 // the parser. Diagnostic only; inert unless the env var is set.
1867 #[cfg(feature = "std")]
1868 if mv_dump_on() {
1869 MV_DUMP.lock_pf().push(MvField {
1870 mb_w: self.mb_w,
1871 mb_h: self.mb_h,
1872 // MvField is a public diagnostic type a harness reads, so it
1873 // keeps its i32 shape; widened here, under RFF_MV_DUMP only.
1874 mv: self.mv_y.iter().map(|&(x, y)| (i32::from(x), i32::from(y))).collect(),
1875 // Widened rather than narrowing MvField: it is a PUBLIC diagnostic
1876 // type a harness reads, and this runs only under RFF_MV_DUMP.
1877 ref_idx: self.ref_idx_y.iter().copied().map(i32::from).collect(),
1878 inter: self.inter_y.clone(),
1879 });
1880 }
1881
1882 // The per-block motion (mv/ref_idx/ref_poc) is read ONLY by B temporal/spatial
1883 // direct (`col.mv/ref_idx/ref_poc`, guarded on `w4 != 0` + `idx < len`). On
1884 // Baseline/Constrained-Baseline streams (no B) it's pure waste — skip the two
1885 // grid clones + the per-block ref_poc resolve/alloc. `w4 = 0` makes the B
1886 // readers no-op even on malformed input.
1887 // PRICED: the four grid clones plus the ref_poc expansion, per REFERENCE
1888 // picture. `refill` (the pool-clear counter) never saw any of it -- this
1889 // is frame-scale copying that happens outside the per-picture re-arm.
1890 cpystat::note(
1891 &cpystat::REF_BUILD,
1892 if self.b_possible {
1893 // `size_of_val` on the SLICE, never `len() * size_of::<T>()`
1894 // with the width written out: this counter shipped hardcoding
1895 // `(i32, i32)` and would have reported double the moment the
1896 // grids narrowed to `(i16, i16)`. The compiler now supplies the
1897 // width, so the instrument cannot drift from the code it prices.
1898 core::mem::size_of_val(&self.mv_y[..])
1899 + core::mem::size_of_val(&self.ref_idx_y[..])
1900 + core::mem::size_of_val(&self.mv1[..])
1901 + core::mem::size_of_val(&self.ref_idx1[..])
1902 } else {
1903 0
1904 },
1905 );
1906 // RECYCLE, DON'T CLONE. The snapshot has to be a copy -- the live grid
1907 // is refilled next picture -- but `clone()` also ALLOCATES, and the
1908 // previous reference's grids come back through `reclaim_retired`. Same
1909 // mechanism as the padded planes, one field over.
1910 let mut take_mv = |src: &[(i16, i16)]| -> Vec<(i16, i16)> {
1911 let mut v = mv_pool.pop().unwrap_or_default();
1912 v.clear();
1913 v.extend_from_slice(src);
1914 v
1915 };
1916 let mut take_ref = |src: &[i8]| -> Vec<i8> {
1917 let mut v = ref_pool.pop().unwrap_or_default();
1918 v.clear();
1919 v.extend_from_slice(src);
1920 v
1921 };
1922 let (mv, ref_idx, mv1, ref_idx1, poc_lut, w4) = if self.b_possible {
1923 (
1924 take_mv(&self.mv_y),
1925 take_ref(&self.ref_idx_y),
1926 take_mv(&self.mv1),
1927 take_ref(&self.ref_idx1),
1928 // CARRY THE LUT, NOT ITS EXPANSION. This used to build the table
1929 // and then immediately spread it over all 57,600 blocks with a
1930 // `.collect()` -- a 230 KB Vec, per reference picture, cloned
1931 // again with the frame, every element of which was
1932 // `poc_lut[ref_idx[i]]`. `ref_idx` travels in the same struct,
1933 // and the one consumer (temporal direct's MapColToList0) reads a
1934 // single entry, so it does the lookup itself now. 230 KB -> 128 B.
1935 {
1936 let mut poc_lut = [i32::MIN; 32];
1937 for (i, f) in self.refs.iter().take(32).enumerate() {
1938 poc_lut[i & 31] = f.pic_poc();
1939 }
1940 poc_lut
1941 },
1942 self.mb_w * 4,
1943 )
1944 } else {
1945 (
1946 Vec::new(),
1947 Vec::new(),
1948 Vec::new(),
1949 Vec::new(),
1950 [i32::MIN; 32],
1951 0,
1952 )
1953 };
1954 // Pop an exact-size recycled buffer per plane; a miss falls back to a
1955 // fresh allocation inside `pad_plane_into`.
1956 let mut take = |len: usize| -> Vec<u8> {
1957 // THE BIGGEST COPY IN THE DECODER AND IT WAS NOT ON THE BOOKS. A
1958 // padded 720p luma plane is 1,053,696 B and the two chroma planes
1959 // 263,424 B each -- per REFERENCE picture, i.e. a larger unit again
1960 // than the per-picture re-arm `refill` prices. `REF_BUILD`'s own doc
1961 // said it excluded these; nothing counted them.
1962 cpystat::note(&cpystat::PLANE_PAD, len);
1963 match pool.iter().position(|v| v.len() == len) {
1964 Some(i) => pool.swap_remove(i),
1965 None => {
1966 // A miss is not merely an allocation. `pad_plane_into`
1967 // resizes the empty Vec, so the whole plane is ZEROED, and
1968 // its loop then writes every byte of every row -- the zero
1969 // fill is pure waste, one padded plane's worth of it.
1970 cpystat::note(&cpystat::PLANE_MISS, len);
1971 Vec::new()
1972 }
1973 }
1974 };
1975 let (lpw, lph) = (self.cw + 2 * crate::LPAD, self.ch + 2 * crate::LPAD);
1976 let (cpw, cph) = (self.ccw + 2 * crate::CPAD, self.ch / 2 + 2 * crate::CPAD);
1977 crate::RefFrame {
1978 // Pad once here (ExpandPicture) instead of extracting a clamped tile
1979 // on every MC call — same copy class as the old plane clone.
1980 py: rusty_h264_common::inter::pad_plane_into(
1981 take(lpw * lph),
1982 &self.rec_y,
1983 self.cw,
1984 self.ch,
1985 crate::LPAD,
1986 ),
1987 pu: rusty_h264_common::inter::pad_plane_into(
1988 take(cpw * cph),
1989 &self.rec_u,
1990 self.ccw,
1991 self.ch / 2,
1992 crate::CPAD,
1993 ),
1994 pv: rusty_h264_common::inter::pad_plane_into(
1995 take(cpw * cph),
1996 &self.rec_v,
1997 self.ccw,
1998 self.ch / 2,
1999 crate::CPAD,
2000 ),
2001 cw: self.cw,
2002 ch: self.ch,
2003 ready_rows: core::sync::atomic::AtomicUsize::new(0),
2004 live: None,
2005 frozen: crate::sync::Once::new(),
2006 frame_num: 0, // set by the caller (decode_slice knows frame_num)
2007 poc: 0, // set by the caller
2008 mv,
2009 ref_idx,
2010 mv1,
2011 ref_idx1,
2012 poc_lut,
2013 w4,
2014 long_term: false,
2015 long_term_idx: 0,
2016 }
2017 }
2018
2019 fn nnz_cache_load(&mut self, mb_x: usize, mb_y: usize) {
2020 let w4 = self.mb_w * 4;
2021 let top_unavail = mb_y == 0 || !self.nbr_in_slice(mb_x, mb_y - 1);
2022 let left_unavail = mb_x == 0 || !self.nbr_in_slice(mb_x - 1, mb_y);
2023 // The four TOP neighbours are one contiguous run of the nnz grid; only
2024 // the left column is strided. Hoisting the uniform `top_unavail` test out
2025 // of the loop turns four checked grid loads into one slice copy.
2026 if top_unavail {
2027 self.nnz_l_cache[1..5].fill(0x80);
2028 } else {
2029 let src = &self.nnz_y[(mb_y * 4 - 1) * w4 + mb_x * 4..][..4];
2030 self.nnz_l_cache[1..5].copy_from_slice(src);
2031 }
2032 for lby in 0..4 {
2033 self.nnz_l_cache[(lby + 1) * 5] = if left_unavail {
2034 0x80
2035 } else {
2036 self.nnz_y
2037 .get((mb_y * 4 + lby) * w4 + (mb_x * 4 - 1))
2038 .copied()
2039 .unwrap_or(0)
2040 };
2041 }
2042 }
2043 #[inline]
2044 fn nc_pred(&self, lbx: usize, lby: usize) -> i32 {
2045 // MASKED, and it pays twice over: this helper and `nnz_cache_set` are
2046 // inlined into the intra path, the inter path AND both slice loops, so
2047 // the two unprovable indexes were replicated at every call site. The
2048 // cache is a 5x5 grid in a `[u8; 25]` and every caller passes a 4x4 block
2049 // coordinate (`LUMA_4X4_SCAN_XY`, `b8*2 + s`, or a literal), so `& 3` is
2050 // a no-op that puts the worst case at 4 * 5 + 4 = 24.
2051 let (lbx, lby) = (lbx & 3, lby & 3);
2052 let left = self.nnz_l_cache[(lby + 1) * 5 + lbx] as i32;
2053 let top = self.nnz_l_cache[lby * 5 + (lbx + 1)] as i32;
2054 let r = left + top;
2055 if r < 0x80 {
2056 (r + 1) >> 1
2057 } else {
2058 r & 0x7f
2059 }
2060 }
2061 #[inline]
2062 fn nnz_cache_set(&mut self, lbx: usize, lby: usize, total: u8) {
2063 self.nnz_l_cache[((lby & 3) + 1) * 5 + ((lbx & 3) + 1)] = total;
2064 }
2065 fn chroma_cache_load(&mut self, mb_x: usize, mb_y: usize) {
2066 let w2 = self.mb_w * 2;
2067 let top_unavail = mb_y == 0 || !self.nbr_in_slice(mb_x, mb_y - 1);
2068 let left_unavail = mb_x == 0 || !self.nbr_in_slice(mb_x - 1, mb_y);
2069 for c in 0..2 {
2070 if top_unavail {
2071 self.nnz_c_cache[c][1..3].fill(0x80);
2072 } else {
2073 let src = &self.nnz_c[c][(mb_y * 2 - 1) * w2 + mb_x * 2..][..2];
2074 self.nnz_c_cache[c][1..3].copy_from_slice(src);
2075 }
2076 for by in 0..2 {
2077 self.nnz_c_cache[c][(by + 1) * 3] = if left_unavail {
2078 0x80
2079 } else {
2080 self.nnz_c[c & 1]
2081 .get((mb_y * 2 + by) * w2 + (mb_x * 2 - 1))
2082 .copied()
2083 .unwrap_or(0)
2084 };
2085 }
2086 }
2087 }
2088 #[inline]
2089 fn chroma_nc_pred(&self, c: usize, bx: usize, by: usize) -> i32 {
2090 // Same shape as `nc_pred`, one size down: a 3x3 grid in `[u8; 9]`, two
2091 // planes, and every caller passes 0..1 for all three coordinates.
2092 let (c, bx, by) = (c & 1, bx & 1, by & 1);
2093 let left = self.nnz_c_cache[c][(by + 1) * 3 + bx] as i32;
2094 let top = self.nnz_c_cache[c][by * 3 + (bx + 1)] as i32;
2095 let r = left + top;
2096 if r < 0x80 {
2097 (r + 1) >> 1
2098 } else {
2099 r & 0x7f
2100 }
2101 }
2102 #[inline]
2103 fn chroma_nnz_cache_set(&mut self, c: usize, bx: usize, by: usize, total: u8) {
2104 self.nnz_c_cache[c & 1][((by & 1) + 1) * 3 + ((bx & 1) + 1)] = total;
2105 }
2106
2107 /// Decodes one slice's macroblocks (raster order) starting at `first_mb`,
2108 /// until `more_rbsp_data()` is exhausted or the picture is full. Returns the
2109 /// next macroblock address (= total when the picture is complete). In a
2110 /// P-slice each macroblock is preceded by `mb_skip_run`.
2111 /// CABAC slice-data decode (docs/cabac-decode-plan.md), brought up brick by brick
2112 /// against the instrumented openh264 oracle. Phase 1: verify engine init; the
2113 /// syntax layer (Phase 2+) is WIP.
2114 pub fn decode_slice_data_cabac(
2115 &mut self,
2116 rbsp: &[u8],
2117 start_byte: usize,
2118 slice_qp: u8,
2119 cabac_init_idc: u32,
2120 is_i: bool,
2121 is_p: bool,
2122 first_mb: usize,
2123 ) -> Result<usize, MbError> {
2124 // E2: overlap parse (this thread) with pixel reconstruction (a scoped
2125 // worker owning the planes) for P slices. I slices and B slices keep
2126 // the inline path (their pixel coupling is per-MB); the ownership
2127 // ping-pong around intra-in-P macroblocks is `edc_intra_sync`.
2128 let eligible = edc_on() && rowdb_on() && !is_i && (is_p || self.is_b);
2129 let threaded = eligible && edc_spawn_worker(self.mb_w, self.mb_h, self.bits_per_mb, true);
2130 edcstat::bump(&edcstat::DISPATCH_ON, threaded as u64);
2131 edcstat::bump(&edcstat::DISPATCH_SEEN, eligible as u64);
2132 if !threaded {
2133 let r = self.decode_slice_cabac_inner(
2134 rbsp,
2135 start_byte,
2136 slice_qp,
2137 cabac_init_idc,
2138 is_i,
2139 is_p,
2140 first_mb,
2141 );
2142 self.note_slice_density(rbsp.len().saturating_sub(start_byte), first_mb, &r);
2143 return r;
2144 }
2145 #[cfg(feature = "std")]
2146 {
2147 let ctx = self.edc_take_ctx();
2148 // D7 PROBE: is the CPU overhead PAYLOAD (alloc/copy per job) or
2149 // SYNCHRONISATION (blocking on a full queue, park/unpark)? The bound
2150 // separates them: raising it removes send-blocking without changing a
2151 // single byte copied. `RS_H264_EDC_BOUND` sweeps it.
2152 let (tx, rx) = crate::sync::mpsc::sync_channel::<EdcMsg>(edc_bound());
2153 let (ctx_tx, ctx_rx) = crate::sync::mpsc::channel::<PixelCtx>();
2154 let (back_tx, back_rx) = crate::sync::mpsc::channel::<PixelCtx>();
2155 let (res, ctx, panicked) = std::thread::scope(|sc| {
2156 let h = sc.spawn(move || edc_worker(ctx, rx, ctx_tx, back_rx));
2157 self.edc_tx = Some(tx);
2158 self.edc_ctx_rx = Some(ctx_rx);
2159 self.edc_back_tx = Some(back_tx);
2160 // UNWIND SAFETY (found by the fuzzer as a HANG, not a failure): a
2161 // panic inside the parse loop would skip the cleanup below — but
2162 // the sender lives in `self`, which outlives the unwind, so the
2163 // channel would never close, the worker would never exit, and the
2164 // scope's join would block forever, converting a diagnosable panic
2165 // into a silent deadlock under `catch_unwind` harnesses. Catch,
2166 // clean up, join, restore the planes, THEN resume the panic.
2167 let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
2168 self.decode_slice_cabac_inner(
2169 rbsp,
2170 start_byte,
2171 slice_qp,
2172 cabac_init_idc,
2173 is_i,
2174 is_p,
2175 first_mb,
2176 )
2177 }));
2178 self.edc_flush_batch(); // ORDER: no job may outlive the channel
2179 self.edc_giveback(); // if an intra macroblock left us holding
2180 self.edc_tx = None; // closes the channel -> worker drains + returns
2181 self.edc_ctx_rx = None;
2182 self.edc_back_tx = None;
2183 match (r, h.join()) {
2184 // The worker yields `Option<PixelCtx>`: `None` means the
2185 // parse side died holding the context, so there is nothing
2186 // to restore.
2187 (Ok(res), Ok(ctx)) => (res, ctx, None),
2188 (Err(p), Ok(ctx)) => (Err(MbError::Truncated), ctx, Some(p)),
2189 (Ok(_), Err(p)) | (Err(_), Err(p)) => (Err(MbError::Truncated), None, Some(p)),
2190 }
2191 });
2192 if let Some(ctx) = ctx {
2193 self.edc_restore_ctx(ctx);
2194 }
2195 if let Some(p) = panicked {
2196 std::panic::resume_unwind(p);
2197 }
2198 self.note_slice_density(rbsp.len().saturating_sub(start_byte), first_mb, &res);
2199 res
2200 }
2201 #[cfg(not(feature = "std"))]
2202 {
2203 unreachable!("the EDC worker needs std; edc_spawn_worker is false without it")
2204 }
2205 }
2206
2207 /// Feed the D12 dispatch its density signal from a slice just decoded.
2208 /// Exponentially smoothed so one atypical slice cannot flip the arm, and
2209 /// only ever read on the NEXT slice — the current one is already committed.
2210 fn note_slice_density(&mut self, bytes: usize, first_mb: usize, r: &Result<usize, MbError>) {
2211 let Ok(end) = r else { return };
2212 let mbs = end.saturating_sub(first_mb);
2213 if mbs == 0 {
2214 return;
2215 }
2216 let bpm = (bytes * 8) as f64 / mbs as f64;
2217 self.bits_per_mb = if self.bits_per_mb == 0.0 {
2218 bpm
2219 } else {
2220 0.75 * self.bits_per_mb + 0.25 * bpm
2221 };
2222 }
2223
2224 fn decode_slice_cabac_inner(
2225 &mut self,
2226 rbsp: &[u8],
2227 start_byte: usize,
2228 slice_qp: u8,
2229 cabac_init_idc: u32,
2230 is_i: bool,
2231 is_p: bool,
2232 first_mb: usize,
2233 ) -> Result<usize, MbError> {
2234 self.edc_active = edc_on();
2235 let mut cab =
2236 crate::cabac::Cabac::new(rbsp, start_byte, slice_qp as i32, cabac_init_idc, is_i);
2237 let (range, _offset) = cab.dbg_state();
2238 // The crate already HAS a `cabac-trace` feature for this; the bare knob()
2239 // was env::var plus a String allocation per SLICE, inside the hottest
2240 // function in the decoder, and it kept every trace arm compiled in.
2241 #[cfg(not(feature = "cabac-trace"))]
2242 let trace = false;
2243 #[cfg(feature = "cabac-trace")]
2244 let trace = rusty_h264_common::knob("RH_CABAC_TRACE").is_some();
2245 debug_assert_eq!(range, 510, "CABAC init range must be 510");
2246
2247 let mbw = self.mb_w;
2248 let total = self.mb_w * self.mb_h;
2249 // Per-MB neighbour state (single-slice assumption: avail == in-bounds).
2250 // SCOPED: zero-initialised allocations sized by MB count, once per slice.
2251 // D13: B-only grids (List-1 ref/mvd + direct flags) are ~292 KB at 720p and
2252 // are ONLY written on the B branch below — allocating them on every P/I
2253 // slice was the same fresh-page class GridPool fixed for the frame grids.
2254 // `RS_H264_FAT_SLICE=1` restores the always-alloc path for A/B.
2255 let _alloc_g =
2256 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecSliceAlloc);
2257 // POOLED (was fresh `vec![..]` per slice): refill reuses the pooled
2258 // allocation with contents identical to a fresh vec, so the body below
2259 // is untouched. Taken out of `self` here; put back at the normal exit.
2260 let mut cat = refill(core::mem::take(&mut self.sc_cat), total, 255u8); // 0=I4x4, 2=I16, 255=unavailable
2261 let mut mb_cbp = refill(core::mem::take(&mut self.sc_cbp), total, 0u8);
2262 let mut cmode = refill(core::mem::take(&mut self.sc_cmode), total, -1i8); // chroma pred mode
2263 let mut mb_nzc = refill(core::mem::take(&mut self.sc_nzc), total, [0u8; 24]); // 16 luma raster + 8 chroma
2264 let mut cbf_dc = refill(core::mem::take(&mut self.sc_cbfdc), total, 0u16);
2265 let mut mb_skip = refill(core::mem::take(&mut self.sc_skip), total, false);
2266 let mut mb_ref = refill(core::mem::take(&mut self.sc_ref), total, [-1i8; 16]); // per-4×4-block List-0 ref (-1 = intra)
2267 let mut mb_mvd = refill(core::mem::take(&mut self.sc_mvd), total, [[0u8; 2]; 16]); // per-block mvd (for mvd ctxInc)
2268 // D13: B-only grids (~292 KB @720p) only on B slices (or FAT_SLICE A/B).
2269 let want_b_grids = self.is_b || fat_slice_on();
2270 let mut mb_ref1 = if want_b_grids {
2271 refill(core::mem::take(&mut self.sc_ref1), total, [-1i8; 16])
2272 } else {
2273 Vec::new()
2274 };
2275 let mut mb_mvd1 = if want_b_grids {
2276 refill(core::mem::take(&mut self.sc_mvd1), total, [[0u8; 2]; 16])
2277 } else {
2278 Vec::new()
2279 };
2280 let mut mb_direct = if want_b_grids {
2281 refill(core::mem::take(&mut self.sc_direct), total, false)
2282 } else {
2283 Vec::new()
2284 };
2285 drop(_alloc_g);
2286 // Multi-slice availability (spec §6.4.x): a macroblock before this
2287 // slice's first_mb is NOT available — for pixel-domain prediction
2288 // (`nbr_in_slice`, like the CAVLC twin sets) AND for every CABAC ctx
2289 // neighbour below. The per-slice ctx arrays' defaults are not the
2290 // "unavailable" value (cat 255 reads as available-I16, mb_cbp 0 as
2291 // all-zero-cbp, mb_t8x8 is a frame grid…), so the `left`/`top`
2292 // options themselves are gated on slice membership — one gate that
2293 // every downstream ctxIdxInc read inherits. Confirmed against ffmpeg
2294 // on an x264 `--slices 4` stream, which desynced without this.
2295 self.slice_first_mb = first_mb;
2296 self.slice_bounds.push((first_mb, self.cur_idc2));
2297 self.any_idc2 |= self.cur_idc2;
2298 let mut last_delta_qp = 0i32;
2299 let mut addr = first_mb;
2300
2301 let _mbloop_g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbLoop);
2302 // A new slice's first MBs may read grids a previous slice's deferred
2303 // spans still owe (P-after-B in one picture included).
2304 self.span_flush();
2305 // `mbw` is `pic_width_in_mbs_minus1 + 1` from the SPS. It cannot be 0 in
2306 // a conformant stream, but it is a runtime value, so this div+rem pair
2307 // carried a divide-by-zero panic reachable from a MALFORMED header --
2308 // on the untrusted-input path, at slice entry. `.max(1)` retires both
2309 // checks and cannot change a conformant decode.
2310 let mbw_nz = mbw.max(1);
2311 let (mut mbx, mut mby) = (addr % mbw_nz, addr / mbw_nz);
2312 // Slice-invariant B properties, formerly re-derived per macroblock.
2313 if self.is_b {
2314 self.edc_flush(); // drain anything a preceding P slice queued
2315 }
2316 let b_records = self.edc_tx.is_some();
2317 let b_refs_ok = !self.refs.is_empty() && !self.refs1.is_empty();
2318 // Loop-invariant: the lowest `addr` whose top neighbour is in this slice.
2319 let top_lim = first_mb + mbw;
2320 // Reused across macroblocks: MC overwrites every byte it reads back (all
2321 // B layouts cover the full macroblock), so the per-MB 384-byte zero-init
2322 // these used to pay was dead. Byte-identity is the guard.
2323 let mut pred_y = [0u8; 256];
2324 let mut c_pred = [[0u8; 64]; 2];
2325 // The loop wraps mbx at entry; bias so the first iteration is exact.
2326 // BOUND the entropy-coded loop (fuzzer-surfaced): a mutated stream can
2327 // never produce decode_terminate and the engine zero-fills forever. The
2328 // bound needs checking ONCE at entry — every in-loop path that advances
2329 // `addr` already breaks on `addr >= total` before continuing.
2330 if addr >= total {
2331 return Err(MbError::Truncated);
2332 }
2333 // BIND THE LENGTH FOR THE LOOP. Every grid below is `refill(.., total, ..)`
2334 // so its length is EXACTLY `total`, and the loop is entered only with
2335 // `addr < total` (checked above, re-asserted each turn) — but nothing
2336 // related the two, so every `[addr]` carried a check. Twenty-eight of
2337 // those became `get_mut` in the previous pass and cost ~2% on CABAC
2338 // content (crowd_run-main 1.021x, z=+2.11): a branch per write on the
2339 // decoder's hottest loop. Reborrowing at the literal `total` makes
2340 // `addr < total` and `addr < len` the SAME fact — no check, no branch.
2341 // The borrows end with this block, before the grids go back to the pool.
2342 // `mb_direct`/`mb_ref1`/`mb_mvd1` are deliberately NOT bound here: they
2343 // are `Vec::new()` on non-B slices, so slicing them to `total` would be
2344 // the very panic this campaign is removing.
2345 {
2346 let cat = &mut cat[..total];
2347 let mb_cbp = &mut mb_cbp[..total];
2348 let cmode = &mut cmode[..total];
2349 let mb_nzc = &mut mb_nzc[..total];
2350 let cbf_dc = &mut cbf_dc[..total];
2351 let mb_skip = &mut mb_skip[..total];
2352 let mb_ref = &mut mb_ref[..total];
2353 let mb_mvd = &mut mb_mvd[..total];
2354 loop {
2355 // A REAL check, not `debug_assert` — which compiles OUT in release,
2356 // so nothing carried `addr < total` across the loop's back-edge and
2357 // the reborrows above folded nothing on their own. This cannot fire
2358 // (every path that advances `addr` already breaks on `addr >= total`,
2359 // and entry is guarded above), but stating it once per macroblock
2360 // replaces FIFTEEN per-write branches with one that never taken.
2361 if addr >= total {
2362 break;
2363 }
2364 // Carried coordinates: one compare-and-wrap replaces the per-MB
2365 // div+mod pair (and row_hook's own division).
2366 if mbx == mbw {
2367 mbx = 0;
2368 mby += 1;
2369 }
2370 self.row_hook_at(addr, mby);
2371 self.wait_refs_for_mb(mby);
2372 let left = (mbx > 0 && addr > first_mb).then(|| addr - 1);
2373 let top = (mby > 0 && addr >= top_lim).then(|| addr - mbw);
2374
2375 // Brick 3.1/3.2: P-slice mb_skip_flag, then mb_type (P mb_type is neighbour-
2376 // independent; intra sub-types map to the I dispatch below).
2377
2378 let mb_type = if is_p {
2379 // Direct bool arithmetic — no Option chain on the hot path.
2380 let sctx = 11
2381 + (left.is_some() && !mb_skip.get(addr - 1).copied().unwrap_or(true))
2382 as usize
2383 + (top.is_some()
2384 && !mb_skip.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true))
2385 as usize;
2386 // ONE engine view carries mb_skip_flag, the terminate bin of a skipped
2387 // macroblock, and mb_type of a coded one (was 2-3 view/commit round
2388 // trips of 8 memory ops each per macroblock).
2389 let (data, mut e, ctx) = cab.view();
2390 if e.decode_decision(data, ctx, sctx) != 0 {
2391 if let Some(p) = mb_skip.get_mut(addr) {
2392 *p = true;
2393 }
2394 last_delta_qp = 0; // skip codes no mb_qp_delta → delta ctxInc resets
2395 // P_Skip recon reuses the entropy-free CAVLC primitive verbatim: it
2396 // takes no bit-reader (skip has no coded syntax past the flag), just
2397 // predicts the skip MV, motion-compensates, and commits the grid.
2398 self.decode_p_skip(mbx, mby)?;
2399 if let Some(p) = self.mb_qp.get_mut(addr) {
2400 *p = self.cur_qp;
2401 } // skip inherits QPy
2402 let eos = e.decode_terminate(data);
2403 cab.commit(e);
2404 addr += 1;
2405 mbx += 1;
2406 if eos || addr >= total {
2407 break;
2408 }
2409 continue;
2410 }
2411 let mbt = mb_type_p_eng(&mut e, data, ctx);
2412 cab.commit(e);
2413 // NON-SKIP P MB: the inter arms below predict MVs from the
2414 // grids (mv_neighbors_block) and the intra arm gathers — flush
2415 // the deferred P span (B span cannot be pending in a P slice,
2416 // but span_flush is one Option check each).
2417 self.span_flush();
2418 if mbt <= 3 {
2419 let _gb =
2420 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbP);
2421 // noSubMbPartSizeLessThan8x8Flag (spec 7.3.5): P_8x8 permits the
2422 // 8x8 transform only when every sub-partition is itself 8x8.
2423 let mut allow8 = true;
2424 // Inter MB (Bricks 3.3/3.4/3.5). 1-ref stream → ref_idx not coded (ref=0).
2425 // Build the 30-entry mvd/ref neighbour cache (openh264 WelsFillCacheInterCabac).
2426 let mut mvdc = [[0i16; 2]; 30];
2427 let mut refc = [-1i8; 30];
2428 // Index the neighbour's RECORD once, then read within it.
2429 // `mb_ref[l][bi]` is two bounds checks - one on the Vec, one
2430 // on the array - repeated for each of the four entries and
2431 // again for `mb_mvd`, i.e. sixteen checks per macroblock for
2432 // four neighbours. Binding the record hoists the Vec check
2433 // out; the inner index is a literal and folds.
2434 if let Some(l) = left {
2435 let (Some(lr), Some(lm)) = (mb_ref.get(l), mb_mvd.get(l)) else {
2436 return Err(MbError::Truncated);
2437 };
2438 for (ci, bi) in [(6usize, 3usize), (12, 7), (18, 11), (24, 15)] {
2439 refc[ci] = lr[bi];
2440 mvdc[ci] = [lm[bi][0] as i16, lm[bi][1] as i16];
2441 }
2442 }
2443 if let Some(t) = top {
2444 let (Some(tr), Some(tm)) = (mb_ref.get(t), mb_mvd.get(t)) else {
2445 return Err(MbError::Truncated);
2446 };
2447 for (ci, bi) in [(1usize, 12usize), (2, 13), (3, 14), (4, 15)] {
2448 refc[ci] = tr[bi];
2449 mvdc[ci] = [tm[bi][0] as i16, tm[bi][1] as i16];
2450 }
2451 }
2452 if mbx > 0 && mby > 0 {
2453 let a = addr - mbw - 1;
2454 if let (Some(r), Some(m)) = (mb_ref.get(a), mb_mvd.get(a)) {
2455 (refc[0], mvdc[0]) =
2456 (r[15], [m[15][0] as i16, m[15][1] as i16]);
2457 }
2458 }
2459 if mby > 0 && mbx + 1 < mbw {
2460 let a = addr - mbw + 1;
2461 if let (Some(r), Some(m)) = (mb_ref.get(a), mb_mvd.get(a)) {
2462 (refc[5], mvdc[5]) =
2463 (r[12], [m[12][0] as i16, m[12][1] as i16]);
2464 }
2465 }
2466 let mut mmvd = [[0i16; 2]; 16];
2467 let mut mref = [0i8; 16];
2468 // mb_pred (spec 7.3.5.1): all ref_idx_l0 FIRST (only when >1 active
2469 // ref), then all mvd + ref-aware predict + commit. `refidx!` parses one
2470 // partition's ref_idx (ctxIdxOffset 54, ctx from neighbour refc) and
2471 // seeds refc so a later partition's ref/mvd context sees it — mirror
2472 // of the encoder's two-phase emit_mb_cabac_p_inter.
2473 macro_rules! refidx {
2474 ($pi:expr, $zb:expr) => {{
2475 if self.num_ref_active > 1 {
2476 let s = CACHE30[$pi & 15].clamp(6, 29);
2477 let c0 =
2478 (refc[s - 1] > 0) as usize + 2 * (refc[s - 6] > 0) as usize;
2479 let r = parse_ref_idx_cabac(&mut cab, c0);
2480 for &zb in $zb.iter() {
2481 refc[CACHE30[zb & 15]] = r;
2482 }
2483 r
2484 } else {
2485 0i8
2486 }
2487 }};
2488 }
2489 macro_rules! part {
2490 ($pi:expr, $zb:expr, $pred:expr, $rx:expr, $ry:expr, $rw:expr, $rh:expr, $refi:expr) => {{
2491 let (mvx, mvy) = parse_mvd_partition(
2492 &mut cab, $pi, $zb, &mut mvdc, &mut refc, &mut mmvd, &mut mref,
2493 $refi,
2494 );
2495 let [na, nb, nc] = self.mv_neighbors_block(
2496 (mbx * 4 + $rx / 4) as isize,
2497 (mby * 4 + $ry / 4) as isize,
2498 ($rw / 4) as isize,
2499 );
2500 let pmv = $pred(na, nb, nc);
2501 self.commit_inter_grid(
2502 mbx,
2503 mby,
2504 $rx,
2505 $ry,
2506 $rw,
2507 $rh,
2508 (pmv.0 + mvx, pmv.1 + mvy),
2509 $refi,
2510 );
2511 }};
2512 }
2513 match mbt {
2514 0 => {
2515 // Sibling of CAVLC P_16x16: one (ref, mv) for all 16
2516 // blocks → every internal edge is strength 0 (§8.7.2.1).
2517 // Without this, CABAC Main/High P_16x16 stayed UNSET and
2518 // paid the blind 24-block bS gather.
2519 if let Some(k) = self.mb_kind.get_mut(mby * self.mb_w + mbx) {
2520 *k = rusty_h264_common::deblock::MB_KIND_INTER_UNIFORM;
2521 }
2522 let r0 = refidx!(
2523 0,
2524 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
2525 );
2526 part!(
2527 0,
2528 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
2529 |a, b, c| predict_partition_mv(0, 0, a, b, c, r0 as i32),
2530 0,
2531 0,
2532 16,
2533 16,
2534 r0
2535 );
2536 }
2537 1 => {
2538 let r0 = refidx!(0, &[0, 1, 2, 3, 4, 5, 6, 7]);
2539 let r1 = refidx!(8, &[8, 9, 10, 11, 12, 13, 14, 15]);
2540 part!(
2541 0,
2542 &[0, 1, 2, 3, 4, 5, 6, 7],
2543 |a, b, c| predict_partition_mv(1, 0, a, b, c, r0 as i32),
2544 0,
2545 0,
2546 16,
2547 8,
2548 r0
2549 );
2550 part!(
2551 8,
2552 &[8, 9, 10, 11, 12, 13, 14, 15],
2553 |a, b, c| predict_partition_mv(1, 1, a, b, c, r1 as i32),
2554 0,
2555 8,
2556 16,
2557 8,
2558 r1
2559 );
2560 }
2561 2 => {
2562 let r0 = refidx!(0, &[0, 1, 2, 3, 8, 9, 10, 11]);
2563 let r1 = refidx!(4, &[4, 5, 6, 7, 12, 13, 14, 15]);
2564 part!(
2565 0,
2566 &[0, 1, 2, 3, 8, 9, 10, 11],
2567 |a, b, c| predict_partition_mv(2, 0, a, b, c, r0 as i32),
2568 0,
2569 0,
2570 8,
2571 16,
2572 r0
2573 );
2574 part!(
2575 4,
2576 &[4, 5, 6, 7, 12, 13, 14, 15],
2577 |a, b, c| predict_partition_mv(2, 1, a, b, c, r1 as i32),
2578 8,
2579 0,
2580 8,
2581 16,
2582 r1
2583 );
2584 }
2585 _ => {
2586 // P_8x8: 4 sub_mb_types, then 4 ref_idx (one per 8×8), then mvd.
2587 let mut subt = [0u32; 4];
2588 for st in &mut subt {
2589 *st = parse_sub_mb_type_p_cabac(&mut cab);
2590 }
2591 allow8 = subt.iter().all(|&t| t == 0);
2592 let mut pr = [0i8; 4];
2593 for (i, r) in pr.iter_mut().enumerate() {
2594 let b = i * 4;
2595 *r = refidx!(b, &[b, b + 1, b + 2, b + 3]);
2596 }
2597 for i in 0..4usize {
2598 let b = i * 4;
2599 let (ox, oy) = ((i % 2) * 8, (i / 2) * 8); // 8×8 pixel origin in MB
2600 let ri = pr[i];
2601 match subt[i] {
2602 0 => part!(
2603 b,
2604 &[b, b + 1, b + 2, b + 3],
2605 |a, b, c| predict_mv(a, b, c, ri as i32),
2606 ox,
2607 oy,
2608 8,
2609 8,
2610 ri
2611 ),
2612 1 => {
2613 part!(
2614 b,
2615 &[b, b + 1],
2616 |a, b, c| predict_mv(a, b, c, ri as i32),
2617 ox,
2618 oy,
2619 8,
2620 4,
2621 ri
2622 );
2623 part!(
2624 b + 2,
2625 &[b + 2, b + 3],
2626 |a, b, c| predict_mv(a, b, c, ri as i32),
2627 ox,
2628 oy + 4,
2629 8,
2630 4,
2631 ri
2632 );
2633 }
2634 2 => {
2635 part!(
2636 b,
2637 &[b, b + 2],
2638 |a, b, c| predict_mv(a, b, c, ri as i32),
2639 ox,
2640 oy,
2641 4,
2642 8,
2643 ri
2644 );
2645 part!(
2646 b + 1,
2647 &[b + 1, b + 3],
2648 |a, b, c| predict_mv(a, b, c, ri as i32),
2649 ox + 4,
2650 oy,
2651 4,
2652 8,
2653 ri
2654 );
2655 }
2656 _ => {
2657 for j in 0..4usize {
2658 let (sx, sy) = ((j % 2) * 4, (j / 2) * 4);
2659 part!(
2660 b + j,
2661 &[b + j],
2662 |a, b, c| predict_mv(a, b, c, ri as i32),
2663 ox + sx,
2664 oy + sy,
2665 4,
2666 4,
2667 ri
2668 );
2669 }
2670 }
2671 }
2672 }
2673 }
2674 }
2675 if let Some(p) = mb_ref.get_mut(addr) {
2676 *p = mref;
2677 }
2678 if let Some(p) = mb_mvd.get_mut(addr) {
2679 *p = sat_mvd(mmvd);
2680 }
2681
2682 // Inter cbp + residual (is_intra = false → cbf default nA=nB=0).
2683 let cbp = parse_cbp_cabac(
2684 &mut cab,
2685 top.and_then(|a| mb_cbp.get(a).copied()),
2686 left.and_then(|a| mb_cbp.get(a).copied()),
2687 );
2688 if let Some(p) = mb_cbp.get_mut(addr) {
2689 *p = cbp as u8;
2690 }
2691 // H-49: an INTER macroblock carries transform_size_8x8_flag AFTER cbp
2692 // (spec 7.3.5), present only when CodedBlockPatternLuma > 0 and
2693 // noSubMbPartSizeLessThan8x8Flag. Same context as the intra read.
2694 let t8 = self.transform_8x8_mode && (cbp & 15) != 0 && allow8 && {
2695 let a = left
2696 .map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2697 let b =
2698 top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
2699 cab.decode_decision(399 + a + b) != 0
2700 };
2701 if let Some(p) = self.mb_t8x8.get_mut(addr) {
2702 *p = t8;
2703 }
2704 self.any_t8 |= t8;
2705 // D9c: cbp==0 never parses residuals — skip the 2.5 KB coeff
2706 // zero-init + PInterJob entirely when NORES is on (default).
2707 // Current-MB nzc slots stay unset under cbp==0 and export as 0
2708 // (same as the 0xff→0 scrub below), so mb_nzc = [0;24] is exact.
2709 if cbp == 0 && nores_on() {
2710 last_delta_qp = 0;
2711 if let Some(p) = self.mb_qp.get_mut(addr) {
2712 *p = self.cur_qp;
2713 }
2714 if let Some(p) = cbf_dc.get_mut(addr) {
2715 *p = 0;
2716 }
2717 if let Some(p) = mb_nzc.get_mut(addr) {
2718 *p = [0u8; 24];
2719 }
2720 if self.refs.is_empty() {
2721 return Err(MbError::Unsupported("inter without reference"));
2722 }
2723 let (mut jgmv, mut jgref) = ([(0i16, 0i16); 16], [0u8; 16]);
2724 {
2725 let w4r = self.mb_w * 4;
2726 for by in 0..4usize {
2727 // Row-contiguous — see the coded-inter gather.
2728 let row = (mby * 4 + by) * w4r + mbx * 4;
2729 jgmv[by * 4..by * 4 + 4]
2730 .copy_from_slice(&self.mv_y[row..row + 4]);
2731 // Row-slice the ref grid too - it was the only
2732 // one still indexed per block.
2733 let ridx = &self.ref_idx_y[row..row + 4];
2734 for bx in 0..4usize {
2735 jgref[by * 4 + bx] = ridx[bx].clamp(0, 15) as u8;
2736 }
2737 }
2738 }
2739 let pj = PInterNoResJob {
2740 mbx,
2741 mby,
2742 t8,
2743 gmv: jgmv,
2744 gref: jgref,
2745 };
2746 if self.edc_tx.is_some() {
2747 self.edc_giveback();
2748 self.edc_commit_nnz(mbx, mby, t8, &[0u8; 24], 0);
2749 if edcstat::on() {
2750 edcstat::bump(&edcstat::J_INTER, 1);
2751 edcstat::bump(&edcstat::J_INTER_NORES, 1);
2752 }
2753 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2754 // POOLED, like the other three construction
2755 // sites. These two used `Box::new` and were the
2756 // hot ones: 693,630 allocations on a 1260-frame
2757 // decode, 87% of every allocation the decoder
2758 // made, against a pool that already existed and
2759 // was already being refilled at the drain.
2760 let b = self.take_nores_job(pj);
2761 self.edc_send_job(EdcJob::InterNoRes(b));
2762 } else if self.edc_active {
2763 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2764 let b = self.take_nores_job(pj);
2765 self.edc_jobs.push(EdcJob::InterNoRes(b));
2766 } else {
2767 self.recon_p_inter_nores(&pj);
2768 if double_recon() {
2769 self.recon_p_inter_nores(&pj);
2770 }
2771 }
2772 let eos = cab.decode_terminate();
2773 addr += 1;
2774 mbx += 1;
2775 if eos || addr >= total {
2776 break;
2777 }
2778 continue;
2779 }
2780 // POOLED JOB, BUILT IN PLACE: the residual parse writes straight into
2781 // the boxed job (no 2.8 KB stack-then-heap copy, no malloc/free per
2782 // macroblock), and only the blocks the cbp says will be parsed are
2783 // zeroed -- the consumer reads a block only when its nnz is nonzero.
2784 let mut job = self.take_pinter_job();
2785 job.t8 = t8;
2786 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
2787 let mut nzc = [0xffu8; 48];
2788 if let Some(t) = top {
2789 let tnz = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
2790 nzc[1..5].copy_from_slice(&tnz[12..16]);
2791 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
2792 (nzc[6], nzc[7], nzc[30], nzc[31]) =
2793 (tnz[20], tnz[21], tnz[22], tnz[23]);
2794 }
2795 if let Some(l) = left {
2796 let lnz = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
2797 (nzc[8], nzc[16], nzc[24], nzc[32]) =
2798 (lnz[3], lnz[7], lnz[11], lnz[15]);
2799 (nzc[13], nzc[21], nzc[37], nzc[45]) =
2800 (lnz[17], lnz[21], lnz[19], lnz[23]);
2801 }
2802 let mut cbfdc = 0u16;
2803 job.nnzs = [0u8; 24]; // parsed totalCoeff per block (see add_inter_residual)
2804 // A cbp==0 MB codes no mb_qp_delta → the next MB's delta ctxInc sees 0.
2805 if cbp == 0 {
2806 last_delta_qp = 0;
2807 }
2808 if cbp != 0 {
2809 let ndc = (
2810 top.and_then(|a| cbf_dc.get(a).copied()),
2811 left.and_then(|a| cbf_dc.get(a).copied()),
2812 );
2813 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
2814 self.step_qp(qpd)?;
2815 parse_mb_residual_cabac::<false>(
2816 &mut cab,
2817 &mut nzc,
2818 &mut cbfdc,
2819 ndc,
2820 cbp_luma,
2821 cbp_chroma,
2822 t8,
2823 ResidualOut {
2824 luma: &mut job.luma_scan,
2825 luma8: &mut job.luma8,
2826 cdc: &mut job.cdc,
2827 cac: &mut job.cac,
2828 nnzs: &mut job.nnzs,
2829 },
2830 );
2831 }
2832 if let Some(p) = self.mb_qp.get_mut(addr) {
2833 *p = self.cur_qp;
2834 }
2835 if let Some(p) = cbf_dc.get_mut(addr) {
2836 *p = cbfdc;
2837 }
2838 let _sc = rusty_h264_common::prof::scope(
2839 rusty_h264_common::prof::Stage::DecStateCache,
2840 );
2841 let mn = nnz_raster_from_z(&job.nnzs);
2842 if let Some(p) = mb_nzc.get_mut(addr) {
2843 *p = mn;
2844 }
2845 drop(_sc);
2846
2847 if self.refs.is_empty() {
2848 return Err(MbError::Unsupported("inter without reference"));
2849 }
2850 // NOTE (measured, do not "optimise" this away): EDC is
2851 // DEFAULT ON (`edc_on()`, opt out with RS_H264_EDC=0), so
2852 // even single-threaded this job is built and deferred —
2853 // that batching is the E1 loop-fission win, not overhead.
2854 // A "skip the job in 1T" bypass added here could never fire
2855 // and was removed.
2856 let (mut jgmv, mut jgref) = ([(0i16, 0i16); 16], [0u8; 16]);
2857 {
2858 let w4r = self.mb_w * 4;
2859 for by in 0..4usize {
2860 // Row-contiguous: one slice copy for the MVs
2861 // instead of four bounds-checked strided loads.
2862 let row = (mby * 4 + by) * w4r + mbx * 4;
2863 jgmv[by * 4..by * 4 + 4].copy_from_slice(&self.mv_y[row..row + 4]);
2864 // Row-slice the ref grid too - it was the only one
2865 // still indexed per block.
2866 let ridx = &self.ref_idx_y[row..row + 4];
2867 for bx in 0..4usize {
2868 jgref[by * 4 + bx] = ridx[bx].clamp(0, 15) as u8;
2869 }
2870 }
2871 }
2872 // D9c: when `cbp == 0`, never materialise the 2.5 KB coeff
2873 // arrays into a `PInterJob` — ship `InterNoRes` (or call
2874 // `recon_p_inter_nores` inline). `RS_H264_NORES=0` keeps the
2875 // old full-job path for A/B.
2876 let nores = cbp == 0 && nores_on();
2877 if nores {
2878 let pj = PInterNoResJob {
2879 mbx,
2880 mby,
2881 t8,
2882 gmv: jgmv,
2883 gref: jgref,
2884 };
2885 if self.edc_tx.is_some() {
2886 self.edc_giveback();
2887 self.edc_commit_nnz(mbx, mby, t8, &[0u8; 24], 0);
2888 if edcstat::on() {
2889 edcstat::bump(&edcstat::J_INTER, 1);
2890 edcstat::bump(&edcstat::J_INTER_NORES, 1);
2891 }
2892 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2893 let b = self.take_nores_job(pj);
2894 self.edc_send_job(EdcJob::InterNoRes(b));
2895 } else if self.edc_active {
2896 edcstat::bump(&edcstat::J_NORES_SENT, 1);
2897 let b = self.take_nores_job(pj);
2898 self.edc_jobs.push(EdcJob::InterNoRes(b));
2899 } else {
2900 self.recon_p_inter_nores(&pj);
2901 if double_recon() {
2902 self.recon_p_inter_nores(&pj);
2903 }
2904 }
2905 let eos = cab.decode_terminate();
2906 addr += 1;
2907 mbx += 1;
2908 if eos || addr >= total {
2909 break;
2910 }
2911 continue;
2912 }
2913 job.mbx = mbx;
2914 job.mby = mby;
2915 job.qp = self.cur_qp;
2916 job.cbp_chroma = cbp_chroma;
2917 job.gmv = jgmv;
2918 job.gref = jgref;
2919 if self.edc_tx.is_some() {
2920 self.edc_giveback();
2921 self.edc_commit_nnz(mbx, mby, t8, &job.nnzs, cbp_chroma);
2922 if edcstat::on() {
2923 edcstat::bump(&edcstat::J_INTER, 1);
2924 }
2925 self.edc_send_job(EdcJob::Inter(job));
2926 } else if self.edc_active {
2927 self.edc_jobs.push(EdcJob::Inter(job));
2928 } else {
2929 self.recon_p_inter(&job);
2930 if double_recon() {
2931 self.recon_p_inter(&job);
2932 }
2933 self.edc_job_pool.push(job);
2934 }
2935
2936 let eos = cab.decode_terminate();
2937 addr += 1;
2938 mbx += 1;
2939 if eos || addr >= total {
2940 break;
2941 }
2942 continue;
2943 }
2944 mbt - 5 // 5→0 (I_4x4), 6..29→1..24 (I_16x16)
2945 } else if self.is_b {
2946 // NOTE: the per-macroblock edc_flush() that used to sit here is
2947 // hoisted to slice entry (see below) — nothing inside a B slice
2948 // pushes to edc_jobs.
2949 if b_records {
2950 // E3: this B macroblock's MC regions record instead of executing.
2951 self.edc_regions = Some(Vec::with_capacity(8));
2952 }
2953 let _gb =
2954 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbB);
2955 // noSubMbPartSizeLessThan8x8Flag for B: direct MBs qualify only under
2956 // direct_8x8_inference_flag; B_8x8 needs every sub-partition 8x8.
2957 let mut allow8 = true;
2958 // B-slice: mb_skip_flag (ctx 24 + neighbour-not-skip), then B mb_type.
2959 let (hl, ht) = (left.is_some(), top.is_some());
2960 let sctx = 24
2961 + (hl && !mb_skip.get(addr - 1).copied().unwrap_or(true)) as usize
2962 + (ht && !mb_skip.get(addr.wrapping_sub(mbw)).copied().unwrap_or(true))
2963 as usize;
2964 // ONE engine view carries mb_skip_flag, the terminate bin of a skipped
2965 // macroblock, and mb_type of a coded one (was 2-3 view/commit round
2966 // trips of 8 memory ops each per macroblock).
2967 let (data, mut e, ctx) = cab.view();
2968 if e.decode_decision(data, ctx, sctx) != 0 {
2969 if let (Some(sk), Some(di)) =
2970 (mb_skip.get_mut(addr), mb_direct.get_mut(addr))
2971 {
2972 (*sk, *di) = (true, true);
2973 }
2974 last_delta_qp = 0; // skip codes no mb_qp_delta → delta ctxInc resets
2975 // B_Skip recon reuses the entropy-free CAVLC primitive (spatial/temporal
2976 // direct with no residual), which also commits the motion grid.
2977 // Hot prefix inline: forced run continuations skip the call.
2978 if !self.b_skip_hot(mbx, mby) {
2979 self.decode_b_skip(mbx, mby)?;
2980 }
2981 if let Some(p) = self.mb_qp.get_mut(addr) {
2982 *p = self.cur_qp;
2983 }
2984 // Skip/direct blocks contribute mvd 0 to a later MB's mvd ctxInc; the
2985 // ref stays in-list so |mvd|=0 is summed (same result either way).
2986 // mb_ref/mb_ref1 stay at their -1 init: every reader is
2987 // either a `> 0` context test (-1 and 0 both false) or the
2988 // mvd-sum's `>= 0` gate — and a skip's mvd is (0,0), so
2989 // exclusion (-1) and inclusion-of-zero (0) give the same
2990 // sum. The 64-byte per-skip zero-fill was pure waste.
2991 let eos = e.decode_terminate(data);
2992 cab.commit(e);
2993 addr += 1;
2994 mbx += 1;
2995 if eos || addr >= total {
2996 break;
2997 }
2998 continue;
2999 }
3000 // NON-SKIP B MB: every arm below reads the grids INLINE
3001 // (H-48: CABAC never routes through decode_b_mb) — flush
3002 // the deferred spans. Caught by the tempete ARM-DIFF.
3003 self.span_flush();
3004 let bci = (hl && !mb_direct.get(addr - 1).copied().unwrap_or(true)) as usize
3005 + (ht
3006 && !mb_direct
3007 .get(addr.wrapping_sub(mbw))
3008 .copied()
3009 .unwrap_or(true)) as usize;
3010 let bmt = {
3011 let _s = rusty_h264_common::prof::scope(
3012 rusty_h264_common::prof::Stage::BTypeParse,
3013 );
3014 mb_type_b_eng(&mut e, data, ctx, 27, bci)
3015 };
3016 cab.commit(e);
3017 if bmt < 23 {
3018 // ---- B inter: parse motion (mvd L0/L1; ref not coded on this 1-ref
3019 // stream) + residual. Recon (b_mc/direct) deferred to B.3. ----
3020 let mut mvdc0 = [[0i16; 2]; 30];
3021 let mut refc0 = [-1i8; 30];
3022 let mut mvdc1 = [[0i16; 2]; 30];
3023 let mut refc1 = [-1i8; 30];
3024 // WelsFillCacheInterCabac, per list (L0 = mb_ref/mb_mvd, L1 = mb_ref1/mb_mvd1).
3025 // The corner addresses and their guards are macroblock
3026 // properties, resolved ONCE here rather than rebuilt inside
3027 // each list expansion; each neighbour grid row is borrowed
3028 // once instead of re-indexed per entry.
3029 let tl = (mbx > 0 && mby > 0).then(|| addr - mbw - 1);
3030 let tr = (mby > 0 && mbx + 1 < mbw).then(|| addr - mbw + 1);
3031 macro_rules! fill {
3032 ($mrf:expr, $mmv:expr, $rc:expr, $mc:expr) => {{
3033 // `.get` PER PARALLEL GRID. The ref and mvd grids are
3034 // separate Vecs indexed by the same macroblock address,
3035 // so `$mrf[l]` proved nothing about `$mmv[l]` and each
3036 // of the four neighbour slots carried two panic paths —
3037 // doubled again because this macro expands once per
3038 // list. A `None` here degrades to exactly what the
3039 // cache already means by "neighbour unavailable" (the
3040 // `-1` ref it is initialised to), so the fallible form
3041 // is the honest one as well as the cheap one.
3042 if let Some(l) = left {
3043 if let (Some(rr), Some(mm)) = ($mrf.get(l), $mmv.get(l)) {
3044 for (ci, bi) in
3045 [(6usize, 3usize), (12, 7), (18, 11), (24, 15)]
3046 {
3047 $rc[ci] = rr[bi];
3048 $mc[ci] = [mm[bi][0] as i16, mm[bi][1] as i16];
3049 }
3050 }
3051 }
3052 if let Some(t) = top {
3053 if let (Some(rr), Some(mm)) = ($mrf.get(t), $mmv.get(t)) {
3054 for (ci, bi) in
3055 [(1usize, 12usize), (2, 13), (3, 14), (4, 15)]
3056 {
3057 $rc[ci] = rr[bi];
3058 $mc[ci] = [mm[bi][0] as i16, mm[bi][1] as i16];
3059 }
3060 }
3061 }
3062 if let Some(a) = tl {
3063 if let (Some(rr), Some(mm)) = ($mrf.get(a), $mmv.get(a)) {
3064 ($rc[0], $mc[0]) = (rr[15], [mm[15][0] as i16, mm[15][1] as i16]);
3065 }
3066 }
3067 if let Some(a) = tr {
3068 if let (Some(rr), Some(mm)) = ($mrf.get(a), $mmv.get(a)) {
3069 ($rc[5], $mc[5]) = (rr[12], [mm[12][0] as i16, mm[12][1] as i16]);
3070 }
3071 }
3072 }};
3073 }
3074 {
3075 let _s = rusty_h264_common::prof::scope(
3076 rusty_h264_common::prof::Stage::BFillCache,
3077 );
3078 fill!(mb_ref, mb_mvd, refc0, mvdc0);
3079 fill!(mb_ref1, mb_mvd1, refc1, mvdc1);
3080 }
3081 let mut _smv = Some(rusty_h264_common::prof::scope(
3082 rusty_h264_common::prof::Stage::BMvdParse,
3083 ));
3084 let mut mmvd0 = [[0i16; 2]; 16];
3085 let mut mref0 = [-1i8; 16];
3086 let mut mmvd1 = [[0i16; 2]; 16];
3087 let mut mref1 = [-1i8; 16];
3088 if !b_refs_ok {
3089 return Err(MbError::Unsupported("B without references"));
3090 }
3091 // Recon (mirrors CAVLC decode_b_mb / decode_b_8x8): predict each list's
3092 // MV off the committed grid + the CABAC-parsed mvd, commit, MC (bi-pred
3093 // blend), then add the residual. Prediction reads mmvd0/mmvd1 (the mvd
3094 // per raster block, splatted during the parse above).
3095 // pred_y / c_pred are slice-scope scratch (see the loop head).
3096 // Recording is a per-macroblock property — ask once, not per
3097 // partition, so the 1T path calls b_mc straight through.
3098 let rec_mode = self.edc_regions.is_some();
3099
3100 if bmt == 0 {
3101 // B_Direct_16x16: no coded motion. A direct block contributes mvd 0
3102 // to a later MB's mvd ctxInc with its ref in-list (|0| summed).
3103 if let Some(p) = mb_direct.get_mut(addr) {
3104 *p = true;
3105 }
3106 allow8 = self.direct_8x8_inference;
3107 (mref0, mref1) = ([0i8; 16], [0i8; 16]);
3108 // FORCED derivation via the zero-bi bitmap (same triple
3109 // as b_skip_hot): a direct-16 whose left/top/topright are
3110 // recorded ref0/(0,0) committers derives (0,0)/(0,0) bi —
3111 // skip the gather + derivation, run the region half
3112 // directly. Its own commit is zero-bi too, so it EXTENDS
3113 // forcing chains through coded direct MBs.
3114 // `mbw` is already carried by the loop, and the row-above
3115 // address is one subtraction, not three.
3116 let up = addr.wrapping_sub(mbw);
3117 let forced = !b_records
3118 && mbx > 0
3119 && mby > 0
3120 && mbx + 1 < mbw
3121 && up >= self.slice_first_mb
3122 && match self.bzero.get(..=addr) {
3123 // A slice ENDING at `addr` proves all three
3124 // neighbour reads at once — the guards above put
3125 // every one of them below `addr`. Same shape as
3126 // `b_skip_hot`.
3127 Some(bz) => {
3128 bz.get(addr - 1).copied().unwrap_or(false)
3129 && bz.get(up).copied().unwrap_or(false)
3130 && bz.get(up + 1).copied().unwrap_or(false)
3131 }
3132 None => false,
3133 };
3134 if forced {
3135 self.b_direct_region(
3136 mbx,
3137 mby,
3138 0,
3139 0,
3140 16,
3141 16,
3142 &mut pred_y,
3143 &mut c_pred,
3144 (0, 0, (0, 0), (0, 0), false),
3145 );
3146 if let Some(b) = self.bzero.get_mut(addr) {
3147 *b = true;
3148 }
3149 } else {
3150 self.decode_b_direct(
3151 mbx,
3152 mby,
3153 0,
3154 0,
3155 16,
3156 16,
3157 &mut pred_y,
3158 &mut c_pred,
3159 );
3160 }
3161 } else if bmt == 22 {
3162 // B_8x8: 4 sub_mb_types, (ref not coded on 1-ref), then mvd
3163 // list-major → sub-MB → sub-partition (openh264 order).
3164 let mut subt = [0u32; 4];
3165 for s in &mut subt {
3166 *s = parse_sub_mb_type_b_cabac(&mut cab);
3167 }
3168 let d8i = self.direct_8x8_inference;
3169 allow8 =
3170 subt.iter()
3171 .all(|&t| if t == 0 { d8i } else { (1..=3).contains(&t) });
3172 // uses(list) depends only on the sub_mb_type — resolve the
3173 // whole 4x2 table once here; the ref_idx, mvd and recon
3174 // loops below all read it instead of re-asking.
3175 let su = [
3176 [b_sub_uses(subt[0], 0), b_sub_uses(subt[0], 1)],
3177 [b_sub_uses(subt[1], 0), b_sub_uses(subt[1], 1)],
3178 [b_sub_uses(subt[2], 0), b_sub_uses(subt[2], 1)],
3179 [b_sub_uses(subt[3], 0), b_sub_uses(subt[3], 1)],
3180 ];
3181 // A direct sub-partition contributes mvd 0 / ref in-list to the
3182 // ctxInc — both the per-MB export and the within-MB 30-cache that a
3183 // later (non-direct) sub in this MB reads.
3184 for i in 0..4usize {
3185 if subt[i] == 0 {
3186 let b = i * 4;
3187 for zb in b..b + 4 {
3188 // each table read once, not twice
3189 let (g, c) = (G_SCAN4[zb & 15], CACHE30[zb & 15]);
3190 (mref0[g], mref1[g]) = (0, 0);
3191 (refc0[c], refc1[c]) = (0, 0);
3192 }
3193 }
3194 }
3195 // ref_idx_l0 for all four 8x8s, then ref_idx_l1, then the mvds
3196 // (spec 7.3.5.2 sub_mb_pred). ONE ref per 8x8 -- never per
3197 // sub-partition -- and B_Direct_8x8 codes none.
3198 let mut sref = [[0i8; 2]; 4]; // [sub-MB][list]
3199 for list in 0..2usize {
3200 // Single-reference lists code no ref_idx at all.
3201 if (if list == 0 {
3202 self.num_ref_active
3203 } else {
3204 self.num_ref_active1
3205 }) <= 1
3206 {
3207 continue;
3208 }
3209 let rc = if list == 0 { &mut refc0 } else { &mut refc1 };
3210 for i in 0..4usize {
3211 let st = subt[i];
3212 if st == 0 || !su[i][list] {
3213 continue;
3214 }
3215 let b = i * 4;
3216 let s = CACHE30[b & 15].clamp(6, 29);
3217 let c0 =
3218 (rc[s - 1] > 0) as usize + 2 * (rc[s - 6] > 0) as usize;
3219 let r = parse_ref_idx_cabac(&mut cab, c0);
3220 for &zb in &[b, b + 1, b + 2, b + 3] {
3221 rc[CACHE30[zb]] = r;
3222 }
3223 sref[i][list] = r;
3224 }
3225 }
3226 for list in 0..2usize {
3227 let (mmv, mrf, mc, rc) = if list == 0 {
3228 (&mut mmvd0, &mut mref0, &mut mvdc0, &mut refc0)
3229 } else {
3230 (&mut mmvd1, &mut mref1, &mut mvdc1, &mut refc1)
3231 };
3232 for i in 0..4usize {
3233 let st = subt[i];
3234 if st == 0 || !su[i][list] {
3235 continue;
3236 }
3237 let b = i * 4;
3238 for &(sx, sy, sw, sh) in b_sub_parts(st) {
3239 // Shapes are 8x8 / 8x4 / 4x8 / 4x4, so the block
3240 // list is closed-form: `step` is 1 when the part
3241 // spans both columns, 2 when it spans both rows.
3242 let (w4b, h4b) = (sw / 4, sh / 4);
3243 let base = b + (sy / 4) * 2 + sx / 4;
3244 let step = if w4b == 2 { 1 } else { 2 };
3245 let zb = [base, base + step, base + 2, base + 3];
3246 let n = w4b * h4b;
3247 parse_mvd_partition(
3248 &mut cab,
3249 zb[0],
3250 &zb[..n],
3251 mc,
3252 rc,
3253 mmv,
3254 mrf,
3255 sref[i][list],
3256 );
3257 }
3258 }
3259 }
3260 // Recon each 8×8: direct sub → decode_b_direct; else per sub-part
3261 // predict (median) + commit + MC.
3262 // Spatial-direct A/B/C are MB-level — walk once if any sub is
3263 // direct. `dmemo=0` rewalks every direct 8×8 (A/B oracle).
3264 // Derivation hoist: A/B/C AND the rid/median result are
3265 // MB-level — derive once, run only the per-8x8 region half
3266 // (czg differs per sub) for every direct sub.
3267 let hoisted = if self.direct_spatial
3268 && direct_memo_on()
3269 && subt.contains(&0)
3270 {
3271 let (n0, n1) = self.b_direct_nbrs(mbx, mby);
3272 Some(Self::b_direct_refs_mvs(&n0, &n1))
3273 } else {
3274 None
3275 };
3276 for (p, &st) in subt.iter().enumerate() {
3277 let (b8x, b8y) = ((p % 2) * 8, (p / 2) * 8);
3278 if st == 0 {
3279 match hoisted {
3280 Some(derived) => self.b_direct_region(
3281 mbx,
3282 mby,
3283 b8x,
3284 b8y,
3285 8,
3286 8,
3287 &mut pred_y,
3288 &mut c_pred,
3289 derived,
3290 ),
3291 None => self.decode_b_direct(
3292 mbx,
3293 mby,
3294 b8x,
3295 b8y,
3296 8,
3297 8,
3298 &mut pred_y,
3299 &mut c_pred,
3300 ),
3301 }
3302 continue;
3303 }
3304 // From the table built at the top of this arm.
3305 let (u0, u1) = (su[p & 3][0], su[p & 3][1]);
3306 for &(sx, sy, sw, sh) in b_sub_parts(st) {
3307 let (px, py) = (b8x + sx, b8y + sy);
3308 let mut mv = [(0i32, 0i32); 2];
3309 let (bx4, by4) =
3310 ((mbx * 4 + px / 4) as isize, (mby * 4 + py / 4) as isize);
3311 let didx = (py / 4) * 4 + px / 4;
3312 // A BI sub-part gathers both lists at ONE position, so the
3313 // availability work (bounds + coded + slice) is shared —
3314 // the fusion the 16x16 layout path already had.
3315 if u0 && u1 {
3316 let (n0, n1) =
3317 self.mv_neighbors_both(bx4, by4, (sw / 4) as isize);
3318 for (list, nb) in [(0usize, &n0), (1, &n1)] {
3319 let d = (if list == 0 { &mmvd0 } else { &mmvd1 })
3320 [didx & 15];
3321 let pmv = predict_mv(
3322 nb[0],
3323 nb[1],
3324 nb[2],
3325 sref[p & 3][list & 1] as i32,
3326 );
3327 mv[list & 1] =
3328 (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3329 }
3330 } else {
3331 for list in 0..2usize {
3332 if if list == 0 { u0 } else { u1 } {
3333 let d = (if list == 0 { &mmvd0 } else { &mmvd1 })
3334 [didx & 15];
3335 let nb = self.mv_neighbors_list(
3336 bx4,
3337 by4,
3338 (sw / 4) as isize,
3339 list,
3340 );
3341 let pmv = predict_mv(
3342 nb[0],
3343 nb[1],
3344 nb[2],
3345 sref[p & 3][list & 1] as i32,
3346 );
3347 mv[list & 1] =
3348 (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3349 }
3350 }
3351 }
3352 let refi0 = if u0 { sref[p & 3][0] as i32 } else { -1 };
3353 let refi1 = if u1 { sref[p & 3][1] as i32 } else { -1 };
3354 self.b_set_motion(
3355 mbx, mby, px, py, sw, sh, refi0, mv[0], refi1, mv[1],
3356 );
3357 if rec_mode {
3358 self.b_mc_or_record(
3359 mbx,
3360 mby,
3361 px,
3362 py,
3363 sw,
3364 sh,
3365 refi0,
3366 mv[0],
3367 refi1,
3368 mv[1],
3369 &mut pred_y,
3370 &mut c_pred,
3371 );
3372 } else {
3373 self.b_mc(
3374 mbx,
3375 mby,
3376 px,
3377 py,
3378 sw,
3379 sh,
3380 refi0,
3381 mv[0],
3382 refi1,
3383 mv[1],
3384 &mut pred_y,
3385 &mut c_pred,
3386 );
3387 }
3388 }
3389 }
3390 } else {
3391 // SINGLE 16x16 B PARTITION => UNIFORM MOTION. `mvmode == 0`
3392 // is one partition carrying one (ref0, ref1, mv0, mv1)
3393 // for all sixteen blocks, in BOTH lists -- exactly what
3394 // the deblock derivation's `mb_uniform` kernel spends six
3395 // motion planes establishing. It is the class the earlier
3396 // markings missed: of the calls that survived them, 70-75%
3397 // still returned TRUE, i.e. found uniformity no syntax
3398 // source had declared.
3399 let (layout, mvmode, preds) = b_inter_layout(bmt);
3400 if mvmode == 0 {
3401 if let Some(p) = self.mb_umot.get_mut(mby * self.mb_w + mbx) {
3402 *p = true;
3403 }
3404 }
3405 let parts: &[(usize, &[usize])] = match mvmode {
3406 0 => {
3407 &[(0, &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15])]
3408 }
3409 1 => &[
3410 (0, &[0, 1, 2, 3, 4, 5, 6, 7]),
3411 (8, &[8, 9, 10, 11, 12, 13, 14, 15]),
3412 ],
3413 _ => &[
3414 (0, &[0, 1, 2, 3, 8, 9, 10, 11]),
3415 (4, &[4, 5, 6, 7, 12, 13, 14, 15]),
3416 ],
3417 };
3418 // ref_idx_l0 for EVERY partition, then ref_idx_l1, then the mvds
3419 // (spec 7.3.5.1 macroblock_prediction). This was missing entirely
3420 // -- the B path assumed a single reference -- so any B slice with
3421 // more than one active reference in either list desynced the
3422 // arithmetic decoder at the first partition that codes a ref_idx,
3423 // and the slice ended early at a phantom end_of_slice_flag.
3424 let mut pref = [[0i8; 2]; 2]; // [partition][list]
3425 for list in 0..2usize {
3426 let active = if list == 0 {
3427 self.num_ref_active
3428 } else {
3429 self.num_ref_active1
3430 };
3431 if active <= 1 {
3432 continue;
3433 }
3434 let rc = if list == 0 { &mut refc0 } else { &mut refc1 };
3435 for (p, &(pidx, zb)) in parts.iter().enumerate() {
3436 if !preds[p & 1].uses(list) {
3437 continue;
3438 }
3439 let s = CACHE30[pidx & 15].clamp(6, 29);
3440 let c0 =
3441 (rc[s - 1] > 0) as usize + 2 * (rc[s - 6] > 0) as usize;
3442 let r = parse_ref_idx_cabac(&mut cab, c0);
3443 // Seed the cache so a later partition's ref/mvd ctxInc sees it.
3444 for &zbi in zb.iter() {
3445 rc[CACHE30[zbi & 15]] = r;
3446 }
3447 pref[p & 1][list & 1] = r;
3448 }
3449 }
3450 // mvd parse order: list-major, partition-minor (openh264
3451 // ParseInterBMotionInfoCabac); the ctxInc reads the same-list cache.
3452 for list in 0..2usize {
3453 let (mmv, mrf, mc, rc) = if list == 0 {
3454 (&mut mmvd0, &mut mref0, &mut mvdc0, &mut refc0)
3455 } else {
3456 (&mut mmvd1, &mut mref1, &mut mvdc1, &mut refc1)
3457 };
3458 for (p, &(pidx, zb)) in parts.iter().enumerate() {
3459 if preds[p & 1].uses(list) {
3460 parse_mvd_partition(
3461 &mut cab,
3462 pidx,
3463 zb,
3464 mc,
3465 rc,
3466 mmv,
3467 mrf,
3468 pref[p & 1][list & 1],
3469 );
3470 }
3471 }
3472 }
3473 // Per-partition recon: predict each list's MV, commit, MC.
3474 let (mbb_x, mbb_y) = (mbx * 4, mby * 4);
3475 for (p, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
3476 let mut mv = [(0i32, 0i32); 2];
3477 // uses(list) is a property of the layout — resolve both
3478 // once instead of five times per partition, and build
3479 // the neighbour coordinates once instead of per list.
3480 let (u0, u1) = (preds[p & 1].uses(0), preds[p & 1].uses(1));
3481 let (nx, ny) =
3482 ((mbb_x + rx / 4) as isize, (mbb_y + ry / 4) as isize);
3483 let (nw, didx) = ((rw / 4) as isize, (ry / 4) * 4 + rx / 4);
3484 // Bi partitions gather both lists at ONE position —
3485 // fuse the availability work (same trick as
3486 // b_direct_nbrs); uni partitions keep the single
3487 // gather.
3488 if u0 && u1 {
3489 let (n0, n1) = self.mv_neighbors_both(nx, ny, nw);
3490 for (list, n) in [(0usize, &n0), (1, &n1)] {
3491 let d =
3492 (if list == 0 { &mmvd0 } else { &mmvd1 })[didx & 15];
3493 let pmv = predict_partition_mv(
3494 mvmode,
3495 p,
3496 n[0],
3497 n[1],
3498 n[2],
3499 pref[p & 1][list & 1] as i32,
3500 );
3501 mv[list & 1] = (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3502 }
3503 } else {
3504 for list in 0..2usize {
3505 if if list == 0 { u0 } else { u1 } {
3506 let d = (if list == 0 { &mmvd0 } else { &mmvd1 })
3507 [didx & 15];
3508 let n = self.mv_neighbors_list(nx, ny, nw, list);
3509 let pmv = predict_partition_mv(
3510 mvmode,
3511 p,
3512 n[0],
3513 n[1],
3514 n[2],
3515 pref[p & 1][list & 1] as i32,
3516 );
3517 mv[list & 1] =
3518 (pmv.0 + d[0] as i32, pmv.1 + d[1] as i32);
3519 }
3520 }
3521 }
3522 let refi0 = if u0 { pref[p & 1][0] as i32 } else { -1 };
3523 let refi1 = if u1 { pref[p & 1][1] as i32 } else { -1 };
3524 self.b_set_motion(
3525 mbx, mby, rx, ry, rw, rh, refi0, mv[0], refi1, mv[1],
3526 );
3527 // Proper spec bi-prediction (average of L0+L1). NOTE: the CAVLC
3528 // decode_b_mb replicates an openh264 bug here for a Bi 16×8/8×16
3529 // partition; our pixel gate is ffmpeg (spec-correct), so we do NOT.
3530 if rec_mode {
3531 self.b_mc_or_record(
3532 mbx,
3533 mby,
3534 rx,
3535 ry,
3536 rw,
3537 rh,
3538 refi0,
3539 mv[0],
3540 refi1,
3541 mv[1],
3542 &mut pred_y,
3543 &mut c_pred,
3544 );
3545 } else {
3546 self.b_mc(
3547 mbx,
3548 mby,
3549 rx,
3550 ry,
3551 rw,
3552 rh,
3553 refi0,
3554 mv[0],
3555 refi1,
3556 mv[1],
3557 &mut pred_y,
3558 &mut c_pred,
3559 );
3560 }
3561 }
3562 }
3563 // Paired stores: one index expression per grid pair.
3564 // Four per-macroblock grids at one address; each is its own
3565 // Vec, so each assignment carried its own check.
3566 if let (Some(r0), Some(d0)) = (mb_ref.get_mut(addr), mb_mvd.get_mut(addr)) {
3567 (*r0, *d0) = (mref0, sat_mvd(mmvd0));
3568 }
3569 if let (Some(r1), Some(d1)) = (mb_ref1.get_mut(addr), mb_mvd1.get_mut(addr))
3570 {
3571 (*r1, *d1) = (mref1, sat_mvd(mmvd1));
3572 }
3573 _smv = None; // close b:mvd-parse; the residual half follows
3574 let _sres =
3575 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BResid);
3576
3577 // Inter cbp + residual (identical to the P path).
3578 let cbp = parse_cbp_cabac(
3579 &mut cab,
3580 top.and_then(|a| mb_cbp.get(a).copied()),
3581 left.and_then(|a| mb_cbp.get(a).copied()),
3582 );
3583 if let Some(p) = mb_cbp.get_mut(addr) {
3584 *p = cbp as u8;
3585 }
3586 // H-49: an INTER macroblock carries transform_size_8x8_flag AFTER cbp
3587 // (spec 7.3.5), present only when CodedBlockPatternLuma > 0 and
3588 // noSubMbPartSizeLessThan8x8Flag. Same context as the intra read.
3589 let t8 = self.transform_8x8_mode && (cbp & 15) != 0 && allow8 && {
3590 let a = left
3591 .map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3592 let b =
3593 top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
3594 cab.decode_decision(399 + a + b) != 0
3595 };
3596 if let Some(p) = self.mb_t8x8.get_mut(addr) {
3597 *p = t8;
3598 }
3599 self.any_t8 |= t8;
3600 // D9c-B: coded B with cbp==0 never parses residuals — skip the
3601 // ~2.5 KB coeff zero-init + fat BJob when NORES is on (default).
3602 // MC already filled pred_y / edc_regions; recon == pred (same as
3603 // B_Skip / P InterNoRes). t8 is always false here ((cbp&15)==0).
3604 if cbp == 0 && nores_on() {
3605 last_delta_qp = 0;
3606 if let Some(p) = self.mb_qp.get_mut(addr) {
3607 *p = self.cur_qp;
3608 }
3609 if let Some(p) = cbf_dc.get_mut(addr) {
3610 *p = 0;
3611 }
3612 if let Some(p) = mb_nzc.get_mut(addr) {
3613 *p = [0u8; 24];
3614 }
3615 if let Some(regions) = self.edc_regions.take() {
3616 self.edc_giveback();
3617 self.edc_commit_nnz(mbx, mby, false, &[0u8; 24], 0);
3618 edcstat::bump(&edcstat::J_NORES_SENT, 1);
3619 self.edc_send_job(EdcJob::BSkip { mbx, mby, regions });
3620 } else {
3621 // Inline twin of decode_b_skip's plane copy (MC already done).
3622 for dy in 0..16 {
3623 let d = (mby * 16 + dy) * self.cw + mbx * 16;
3624 self.rec_y[d..d + 16]
3625 .copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
3626 }
3627 for c in 0..2 {
3628 let plane = if c == 0 {
3629 &mut self.rec_u
3630 } else {
3631 &mut self.rec_v
3632 };
3633 for dy in 0..8 {
3634 let d = (mby * 8 + dy) * self.ccw + mbx * 8;
3635 plane[d..d + 8]
3636 .copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
3637 }
3638 }
3639 let w4 = self.mb_w * 4;
3640 for dy in 0..4 {
3641 self.nnz_y[(mby * 4 + dy) * w4 + mbx * 4..][..4].fill(0);
3642 }
3643 }
3644 let eos = cab.decode_terminate();
3645 addr += 1;
3646 mbx += 1;
3647 if eos || addr >= total {
3648 break;
3649 }
3650 continue;
3651 }
3652 // Scratch planes (see `scratch_luma`): zeroed per coded block by the parse.
3653 let mut luma8 = self
3654 .scratch_luma8
3655 .take()
3656 .unwrap_or_else(|| Box::new([[0i32; 64]; 4]));
3657 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
3658 let mut nzc = [0xffu8; 48];
3659 if let Some(t) = top {
3660 let tnz = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3661 nzc[1..5].copy_from_slice(&tnz[12..16]);
3662 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3663 (nzc[6], nzc[7], nzc[30], nzc[31]) =
3664 (tnz[20], tnz[21], tnz[22], tnz[23]);
3665 }
3666 if let Some(l) = left {
3667 let lnz = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3668 (nzc[8], nzc[16], nzc[24], nzc[32]) =
3669 (lnz[3], lnz[7], lnz[11], lnz[15]);
3670 (nzc[13], nzc[21], nzc[37], nzc[45]) =
3671 (lnz[17], lnz[21], lnz[19], lnz[23]);
3672 }
3673 let mut cbfdc = 0u16;
3674 let mut nnzs = [0u8; 24]; // parsed totalCoeff per block
3675 let mut luma_scan = self
3676 .scratch_luma
3677 .take()
3678 .unwrap_or_else(|| Box::new([[0i32; 16]; 16]));
3679 let mut cdc = [[0i32; 4]; 2];
3680 let mut cac = self
3681 .scratch_cac
3682 .take()
3683 .unwrap_or_else(|| Box::new([[[0i32; 16]; 4]; 2]));
3684 if cbp == 0 {
3685 last_delta_qp = 0;
3686 }
3687 if cbp != 0 {
3688 let ndc = (
3689 top.and_then(|a| cbf_dc.get(a).copied()),
3690 left.and_then(|a| cbf_dc.get(a).copied()),
3691 );
3692 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3693 self.step_qp(qpd)?;
3694 parse_mb_residual_cabac::<false>(
3695 &mut cab,
3696 &mut nzc,
3697 &mut cbfdc,
3698 ndc,
3699 cbp_luma,
3700 cbp_chroma,
3701 t8,
3702 ResidualOut {
3703 luma: &mut luma_scan,
3704 luma8: &mut luma8,
3705 cdc: &mut cdc,
3706 cac: &mut cac,
3707 nnzs: &mut nnzs,
3708 },
3709 );
3710 }
3711 if let Some(p) = self.mb_qp.get_mut(addr) {
3712 *p = self.cur_qp;
3713 }
3714 if let Some(p) = cbf_dc.get_mut(addr) {
3715 *p = cbfdc;
3716 }
3717 let _sc = rusty_h264_common::prof::scope(
3718 rusty_h264_common::prof::Stage::DecStateCache,
3719 );
3720 let mn = nnz_raster_from_z(&nnzs);
3721 if let Some(p) = mb_nzc.get_mut(addr) {
3722 *p = mn;
3723 }
3724 drop(_sc);
3725 if let Some(regions) = self.edc_regions.take() {
3726 self.edc_giveback();
3727 self.edc_commit_nnz(mbx, mby, t8, &nnzs, cbp_chroma);
3728 let job = BJob {
3729 mbx,
3730 mby,
3731 qp: self.cur_qp,
3732 cbp_chroma,
3733 skip: false,
3734 regions,
3735 // MT-only copies out of the scratch planes (the worker owns its job).
3736 luma_scan: (!t8 && cbp_luma != 0).then_some(*luma_scan),
3737 luma8: t8.then_some(*luma8),
3738 cdc,
3739 cac: (cbp_chroma == 2).then_some(*cac),
3740 nnzs,
3741 };
3742 self.edc_send_job(EdcJob::B(Box::new(job)));
3743 } else {
3744 self.add_inter_residual(
3745 mbx,
3746 mby,
3747 &pred_y,
3748 &c_pred,
3749 Some(&*luma_scan),
3750 t8.then_some(&*luma8),
3751 &cdc,
3752 (cbp_chroma == 2).then_some(&*cac),
3753 cbp_chroma,
3754 &nnzs,
3755 );
3756 }
3757 self.scratch_luma = Some(luma_scan);
3758 self.scratch_luma8 = Some(luma8);
3759 self.scratch_cac = Some(cac);
3760
3761 let eos = cab.decode_terminate();
3762 addr += 1;
3763 mbx += 1;
3764 if eos || addr >= total {
3765 break;
3766 }
3767 continue;
3768 }
3769 bmt - 23 // 23→0 (I_4x4), 24..=47→1..24 (I_16x16), 48→25 (PCM)
3770 } else {
3771 let li = left.map_or(0, |a| cat.get(a).is_some_and(|&c| c >= 2) as usize);
3772 let ti = top.map_or(0, |a| cat.get(a).is_some_and(|&c| c >= 2) as usize);
3773 parse_mb_type_i_cabac(&mut cab, li + ti)
3774 };
3775 // H-48: the CABAC intra path is INLINED in this loop, not routed through
3776 // `decode_intra_mb` (which only the CAVLC readers call) — wiring the scope
3777 // there reported ZERO calls against 480,510 intra-pred calls. All three
3778 // intra entries (I-slice, P-slice mb_type>3, B-slice bmt>=23) converge
3779 // here, so this is the one point that sees every intra MB.
3780 self.edc_intra_sync(); // intra reconstruction reads neighbour PIXELS
3781 // Intra prediction ALSO reads the coded_y/modes_y/inter_y grids a
3782 // deferred span still owes (this arm is inline — it never passes
3783 // decode_b_mb's flush). Caught by the tempete ARM-DIFF.
3784 self.span_flush();
3785 let _gi = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbI);
3786 // Intra bS is a constant pattern (4 on MB edges, 3 internal) — written
3787 // here because CABAC inlines I recon and never calls decode_intra_mb.
3788 if let Some(p) = self.mb_kind.get_mut(mby * self.mb_w + mbx) {
3789 *p = rusty_h264_common::deblock::MB_KIND_INTRA;
3790 }
3791 if mb_type == 25 {
3792 // ---- I_PCM (spec §7.3.5): 384 raw byte-aligned sample bytes inside
3793 // the CABAC stream. The PCM marker was a terminate bin, so the engine
3794 // has stopped; DecodeFlush + pcm_alignment_zero_bit put the samples
3795 // at `pcm_start_byte()`, and the engine re-initialises after them
3796 // with its CONTEXTS KEPT (§9.3.1). All three slice types (I, P via
3797 // mbt 30, B via bmt 48) reach here as mb_type 25.
3798 let pcm = cab.pcm_start_byte();
3799 let end = pcm + 384;
3800 if end > rbsp.len() {
3801 return Err(MbError::Truncated);
3802 }
3803 let mut pr = BitReader::new(&rbsp[pcm..end]);
3804 self.decode_ipcm(&mut pr, mbx, mby)?;
3805 cab.reinit_at(end);
3806 // Neighbour context (§7.4.5 + §9.3.3.1.1.x inferences): intra, QPy
3807 // unchanged (no mb_qp_delta — its ctxInc resets), CodedBlockPattern
3808 // luma/chroma inferred 15/2, every coded_block_flag (incl. DC) 1,
3809 // nnz 16, chroma pred mode 0.
3810 if let Some(p) = self.mb_qp.get_mut(addr) {
3811 *p = self.cur_qp;
3812 }
3813 if let Some(p) = cat.get_mut(addr) {
3814 *p = 25;
3815 }
3816 if let Some(p) = cmode.get_mut(addr) {
3817 *p = 0;
3818 }
3819 if let Some(p) = mb_cbp.get_mut(addr) {
3820 *p = 0x2f;
3821 }
3822 if let Some(p) = cbf_dc.get_mut(addr) {
3823 *p = (1 << RP_I16_DC) | (1 << RP_CHROMA_DC) | (1 << (RP_CHROMA_DC + 1));
3824 }
3825 if let Some(p) = mb_nzc.get_mut(addr) {
3826 *p = [16u8; 24];
3827 }
3828 last_delta_qp = 0;
3829 let eos = cab.decode_terminate();
3830 addr += 1;
3831 mbx += 1;
3832 if eos || addr >= total {
3833 break;
3834 }
3835 continue;
3836 }
3837 // chroma-pred-mode ctxInc from neighbour chroma modes (1..=3).
3838 let cci = left.map_or(0, |a| {
3839 cmode.get(a).is_some_and(|c| (1..=3).contains(c)) as usize
3840 }) + top.map_or(0, |a| {
3841 cmode.get(a).is_some_and(|c| (1..=3).contains(c)) as usize
3842 });
3843
3844 if mb_type != 0 {
3845 // ---- I_16x16 (mb_type 1..=24): pred mode & cbp DERIVED from mb_type;
3846 // luma DC always coded. Syntax order: intra_chroma_pred_mode, mb_qp_delta,
3847 // luma DC (Hadamard), luma AC (if cbp_luma), chroma DC/AC. Mirrors the CAVLC
3848 // decode_i16, driven by the CABAC residual. ----
3849 let mt = mb_type - 1;
3850 let pred_mode = I16Mode::from_id(mt % 4);
3851 let cbp_chroma = (mt % 12) / 4;
3852 let cbp_luma_15 = mt / 12 == 1;
3853 let chroma_mode = parse_intra_chroma_pred_mode_cabac(&mut cab, cci) as u8;
3854 if let Some(p) = cmode.get_mut(addr) {
3855 *p = chroma_mode as i8;
3856 }
3857 if let Some(p) = cat.get_mut(addr) {
3858 *p = 2;
3859 }
3860 if let Some(p) = mb_cbp.get_mut(addr) {
3861 *p = ((cbp_chroma as u8) << 4) | if cbp_luma_15 { 15 } else { 0 };
3862 }
3863 let w4 = self.mb_w * 4;
3864
3865 let mut nzc = [0xffu8; 48];
3866 if let Some(t) = top {
3867 let tn = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
3868 nzc[1..5].copy_from_slice(&tn[12..16]);
3869 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
3870 (nzc[6], nzc[7]) = (tn[20], tn[21]);
3871 (nzc[30], nzc[31]) = (tn[22], tn[23]);
3872 }
3873 if let Some(l) = left {
3874 let ln = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
3875 (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
3876 (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
3877 }
3878
3879 let ndc = (
3880 top.and_then(|a| cbf_dc.get(a).copied()),
3881 left.and_then(|a| cbf_dc.get(a).copied()),
3882 );
3883 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
3884 self.step_qp(qpd)?;
3885 let qp = self.cur_qp;
3886 let mut cbfdc = 0u16;
3887 let nd = resolve_ndc(ndc, true);
3888 let (data, mut e, ctx) = cab.view();
3889
3890 // Luma DC (iz=0, category I16_LUMA_DC, 16 coeffs) → Hadamard dequant.
3891 let mut dc_scan = [0i32; 16];
3892 residual_block_eng::<RP_I16_DC, 16>(
3893 &mut e,
3894 data,
3895 ctx,
3896 &mut nzc,
3897 &mut cbfdc,
3898 0,
3899 0,
3900 true,
3901 nd,
3902 &mut dc_scan,
3903 );
3904 // FUSED scan-order DC: un-scan + Hadamard + scale in one kernel (was 32 moves + 253 instrs).
3905 let recon_dc = inverse_quant_luma_dc_scan(
3906 &dc_scan,
3907 qp,
3908 self.scaling.as_ref().map(|s| s[0][0]),
3909 );
3910
3911 // Luma AC (iz 0..15, category I16_LUMA_AC, 15 coeffs) when cbp_luma set.
3912 // Materialised only when AC is actually coded — a DC-only
3913 // I_16x16 zeroed 1 KB of stack for nothing.
3914 let mut q_blocks: Option<[[i32; 16]; 16]> = None;
3915 let mut coded16 = 0u16;
3916 for (iz, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
3917 let total = if cbp_luma_15 {
3918 let mut ac = [0i32; 16];
3919 let t = residual_block_eng::<RP_I16_AC, 16>(
3920 &mut e, data, ctx, &mut nzc, &mut cbfdc, iz, 0, true, nd, &mut ac,
3921 );
3922 q_blocks.get_or_insert([[0i32; 16]; 16])
3923 [(lby & 3) * 4 + (lbx & 3)] = ac; // SCAN order (fused kernel)
3924 t as u8
3925 } else {
3926 nzc[NZC_CACHE[iz.min(23)].min(47)] = 0;
3927 0
3928 };
3929 coded16 |= ((total != 0) as u16) << ((lby & 3) * 4 + (lbx & 3));
3930 if let Some(p) = self.nnz_y.get_mut((mby * 4 + lby) * w4 + (mbx * 4 + lbx))
3931 {
3932 *p = total;
3933 }
3934 }
3935
3936 let mut cdc = [[0i32; 4]; 2];
3937 let mut cac: Option<[[[i32; 16]; 4]; 2]> = None;
3938 let mut cnnz = [0u8; 8];
3939 if cbp_chroma >= 1 {
3940 for i in 0..2usize {
3941 residual_block_eng::<RP_CHROMA_DC, 4>(
3942 &mut e,
3943 data,
3944 ctx,
3945 &mut nzc,
3946 &mut cbfdc,
3947 16 + i * 4,
3948 i,
3949 true,
3950 nd,
3951 &mut cdc[i],
3952 );
3953 }
3954 }
3955 if cbp_chroma == 2 {
3956 let cacm = cac.get_or_insert([[[0i32; 16]; 4]; 2]);
3957 for i in 0..2usize {
3958 for id4 in 0..4usize {
3959 cnnz[(i * 4 + id4) & 7] = residual_block_eng::<RP_CHROMA_AC, 16>(
3960 &mut e,
3961 data,
3962 ctx,
3963 &mut nzc,
3964 &mut cbfdc,
3965 16 + i * 4 + id4,
3966 i,
3967 true,
3968 nd,
3969 &mut cacm[i][id4],
3970 ) as u8;
3971 }
3972 }
3973 }
3974
3975 cab.commit(e);
3976 // Luma recon: 16×16 intra prediction, then per-4×4 (dequant AC + injected DC).
3977 let top_ok = mby > 0
3978 && self.nbr_in_slice(mbx, mby - 1)
3979 && self.intra_nbr_ok(mbx * 4, mby * 4 - 1);
3980 let left_ok = mbx > 0
3981 && self.nbr_in_slice(mbx - 1, mby)
3982 && self.intra_nbr_ok(mbx * 4 - 1, mby * 4);
3983 self.recon_i16_luma(
3984 mbx,
3985 mby,
3986 pred_mode,
3987 top_ok,
3988 left_ok,
3989 q_blocks.as_ref(),
3990 coded16,
3991 &recon_dc,
3992 qp,
3993 );
3994 self.recon_chroma_cabac(
3995 mbx,
3996 mby,
3997 chroma_mode,
3998 &cdc,
3999 cac.as_ref(),
4000 &cnnz,
4001 cbp_chroma,
4002 top_ok,
4003 left_ok,
4004 );
4005
4006 if let Some(p) = self.mb_qp.get_mut(addr) {
4007 *p = self.cur_qp;
4008 }
4009 if let Some(p) = cbf_dc.get_mut(addr) {
4010 *p = cbfdc;
4011 }
4012 let mut mn = [0u8; 24];
4013 // Four CONTIGUOUS runs of four -- slice copies, not sixteen
4014 // bounds-checked element loads.
4015 mn[0..4].copy_from_slice(&nzc[9..13]);
4016 mn[4..8].copy_from_slice(&nzc[17..21]);
4017 mn[8..12].copy_from_slice(&nzc[25..29]);
4018 mn[12..16].copy_from_slice(&nzc[33..37]);
4019 (mn[16], mn[17], mn[20], mn[21]) = (nzc[14], nzc[15], nzc[22], nzc[23]);
4020 (mn[18], mn[19], mn[22], mn[23]) = (nzc[38], nzc[39], nzc[46], nzc[47]);
4021 for v in mn.iter_mut() {
4022 if *v == 0xff {
4023 *v = 0;
4024 }
4025 }
4026 if let Some(p) = mb_nzc.get_mut(addr) {
4027 *p = mn;
4028 }
4029
4030 let eos = cab.decode_terminate();
4031 addr += 1;
4032 mbx += 1;
4033 if eos || addr >= total {
4034 break;
4035 }
4036 continue;
4037 }
4038 if let Some(p) = cat.get_mut(addr) {
4039 *p = 0;
4040 }
4041 let w4 = self.mb_w * 4;
4042 // H-49: transform_size_8x8_flag. For I_NxN it precedes the intra pred
4043 // modes (spec §7.3.5); ctxIdx = 399 + condTermFlagA + condTermFlagB,
4044 // each 1 when that neighbour MB carries the flag. Omitting this read is
4045 // what desynced every High-profile stream.
4046 let t8 = self.transform_8x8_mode && {
4047 let a = left.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
4048 let b = top.map_or(0, |x| self.mb_t8x8.get(x).is_some_and(|&f| f) as usize);
4049 cab.decode_decision(399 + a + b) != 0
4050 };
4051 if let Some(p) = self.mb_t8x8.get_mut(addr) {
4052 *p = t8;
4053 }
4054 self.any_t8 |= t8;
4055 // Hoisted ABOVE the mode loop (it was computed after it) so the
4056 // per-block mode prediction can use it - see `predict_i4_mode_fast`.
4057 let top_ok = mby > 0
4058 && self.nbr_in_slice(mbx, mby - 1)
4059 && self.intra_nbr_ok(mbx * 4, mby * 4 - 1);
4060 let left_ok = mbx > 0
4061 && self.nbr_in_slice(mbx - 1, mby)
4062 && self.intra_nbr_ok(mbx * 4 - 1, mby * 4);
4063 // Brick 2.4 + recon: derive & store each intra mode (prev-flag → the
4064 // neighbour-predicted mode, else rem), exactly as the CAVLC path.
4065 let mut modes = [2u8; 16]; // raster [lby*4+lbx]
4066 let mut modes8 = [2u8; 4]; // one per 8×8 when t8
4067 // ONE engine view for all 16 (or 4) pred-mode reads of the macroblock.
4068 let (data, mut e, ctx) = cab.view();
4069 if t8 {
4070 // One mode per 8×8, broadcast to its four 4×4 cells so neighbour
4071 // mode prediction keeps working unchanged.
4072 for b8 in 0..4usize {
4073 let (b8x, b8y) = (b8 % 2, b8 / 2);
4074 let (bx, by) = (mbx * 4 + b8x * 2, mby * 4 + b8y * 2);
4075 let predicted =
4076 self.predict_i4_mode_fast(bx, by, b8x * 2, b8y * 2, top_ok, left_ok);
4077 let rr = intra4x4_pred_mode_eng(&mut e, data, ctx);
4078 let actual = if rr < 0 {
4079 predicted
4080 } else {
4081 let rem = rr as u8;
4082 if rem < predicted {
4083 rem
4084 } else {
4085 rem + 1
4086 }
4087 };
4088 modes8[b8] = actual;
4089 for dy in 0..2 {
4090 for dx in 0..2 {
4091 if let Some(p) = self.modes_y.get_mut((by + dy) * w4 + (bx + dx)) {
4092 *p = actual;
4093 }
4094 modes[(b8y * 2 + dy) * 4 + (b8x * 2 + dx)] = actual;
4095 }
4096 }
4097 }
4098 } else {
4099 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
4100 let (bx, by) = (mbx * 4 + lbx, mby * 4 + lby);
4101 let predicted =
4102 self.predict_i4_mode_fast(bx, by, lbx, lby, top_ok, left_ok);
4103 let rr = intra4x4_pred_mode_eng(&mut e, data, ctx);
4104 let actual = if rr < 0 {
4105 predicted
4106 } else {
4107 let rem = rr as u8;
4108 if rem < predicted {
4109 rem
4110 } else {
4111 rem + 1
4112 }
4113 };
4114 if let Some(m) = self.modes_y.get_mut(by * w4 + bx) {
4115 *m = actual;
4116 }
4117 modes[(lby & 3) * 4 + (lbx & 3)] = actual;
4118 }
4119 }
4120 cab.commit(e);
4121 let chroma_mode = parse_intra_chroma_pred_mode_cabac(&mut cab, cci) as u8;
4122 if let Some(p) = cmode.get_mut(addr) {
4123 *p = chroma_mode as i8;
4124 }
4125 let cbp = parse_cbp_cabac(
4126 &mut cab,
4127 top.and_then(|a| mb_cbp.get(a).copied()),
4128 left.and_then(|a| mb_cbp.get(a).copied()),
4129 );
4130 if let Some(p) = mb_cbp.get_mut(addr) {
4131 *p = cbp as u8;
4132 }
4133 let (cbp_luma, cbp_chroma) = (cbp & 15, cbp >> 4);
4134
4135 // Build the padded nzc cache from neighbours (openh264 WelsFillCacheNonZeroCount).
4136 let mut nzc = [0xffu8; 48];
4137 if let Some(t) = top {
4138 let tn = mb_nzc.get(t).unwrap_or(&ZERO_NZC);
4139 nzc[1..5].copy_from_slice(&tn[12..16]);
4140 (nzc[0], nzc[5], nzc[29]) = (0, 0, 0);
4141 (nzc[6], nzc[7]) = (tn[20], tn[21]);
4142 (nzc[30], nzc[31]) = (tn[22], tn[23]);
4143 }
4144 if let Some(l) = left {
4145 let ln = mb_nzc.get(l).unwrap_or(&ZERO_NZC);
4146 (nzc[8], nzc[16], nzc[24], nzc[32]) = (ln[3], ln[7], ln[11], ln[15]);
4147 (nzc[13], nzc[21], nzc[37], nzc[45]) = (ln[17], ln[21], ln[19], ln[23]);
4148 }
4149
4150 // Bricks 2.6 + 2.7: mb_qp_delta + residual (I_4x4 luma 4×4 + chroma DC/AC),
4151 // storing scan-order coefficients for recon.
4152 let mut cbfdc = 0u16;
4153 // Both materialised only when the parse writes them: an I_8x8
4154 // macroblock never touches luma_scan (it carries luma8), and
4155 // cbp_chroma < 2 never touches cac.
4156 // Scratch planes (see `scratch_luma`): zeroed per coded block by the parse.
4157 let mut luma_scan = self
4158 .scratch_luma
4159 .take()
4160 .unwrap_or_else(|| Box::new([[0i32; 16]; 16]));
4161 let mut luma8 = self
4162 .scratch_luma8
4163 .take()
4164 .unwrap_or_else(|| Box::new([[0i32; 64]; 4]));
4165 let mut cdc = [[0i32; 4]; 2];
4166 let mut cac = self
4167 .scratch_cac
4168 .take()
4169 .unwrap_or_else(|| Box::new([[[0i32; 16]; 4]; 2]));
4170 let mut nnzs = [0u8; 24]; // parse-side per-block coeff counts
4171 if cbp == 0 {
4172 last_delta_qp = 0;
4173 }
4174 if cbp != 0 {
4175 let ndc = (
4176 top.and_then(|a| cbf_dc.get(a).copied()),
4177 left.and_then(|a| cbf_dc.get(a).copied()),
4178 );
4179 let qpd = parse_mb_qp_delta_cabac(&mut cab, &mut last_delta_qp);
4180 self.step_qp(qpd)?;
4181 parse_mb_residual_cabac::<true>(
4182 &mut cab,
4183 &mut nzc,
4184 &mut cbfdc,
4185 ndc,
4186 cbp_luma,
4187 cbp_chroma,
4188 t8,
4189 ResidualOut {
4190 luma: &mut luma_scan,
4191 luma8: &mut luma8,
4192 cdc: &mut cdc,
4193 cac: &mut cac,
4194 nnzs: &mut nnzs,
4195 },
4196 );
4197 if t8 {
4198 // Per-cell nnz for the deblock/neighbour readers: the 8x8 total
4199 // (0 for an uncoded 8x8) in each of its four 4x4 cells.
4200 for id8 in 0..4usize {
4201 let n = nnzs[id8 * 4];
4202 let (b8x, b8y) = (id8 % 2, id8 / 2);
4203 for sy in 0..2 {
4204 for sx in 0..2 {
4205 if let Some(p) = self.nnz_y.get_mut(
4206 (mby * 4 + b8y * 2 + sy) * w4 + (mbx * 4 + b8x * 2 + sx),
4207 ) {
4208 *p = n;
4209 }
4210 }
4211 }
4212 }
4213 }
4214 }
4215 if let Some(p) = self.mb_qp.get_mut(addr) {
4216 *p = self.cur_qp;
4217 }
4218 if let Some(p) = cbf_dc.get_mut(addr) {
4219 *p = cbfdc;
4220 }
4221 // Extract the MB's nzc (raster luma + chroma) for future neighbours.
4222 let mn = nnz_raster_from_z(&nnzs);
4223 if let Some(p) = mb_nzc.get_mut(addr) {
4224 *p = mn;
4225 }
4226
4227 // ---- Brick 4.3a: recon (I_4x4 luma + chroma) via the CAVLC-proven primitives.
4228 let qp = self.cur_qp;
4229
4230 if t8 {
4231 // I_8x8 recon, reusing the CAVLC-proven primitives verbatim
4232 // (un_scan_8x8 / inv_quant8 / gather_i8 / intra8x8_pred /
4233 // add_residual_8x8). Only the ENTROPY half differed.
4234 for b8 in 0..4usize {
4235 let (b8x, b8y) = (b8 % 2, b8 / 2);
4236 let (bx, by) = (mbx * 4 + b8x * 2, mby * 4 + b8y * 2);
4237 let coded = cbp_luma & (1 << b8) != 0;
4238 let avail_top = b8y > 0 || top_ok;
4239 let avail_left = b8x > 0 || left_ok;
4240 self.recon_i8_block(
4241 bx,
4242 by,
4243 modes8[b8],
4244 avail_top,
4245 avail_left,
4246 coded.then_some(&luma8[b8]),
4247 qp,
4248 );
4249 for sy in 0..2 {
4250 // Row fill: the 2x2 cell block is two contiguous PAIRS.
4251 self.coded_y[(by + sy) * w4 + bx..][..2].fill(true);
4252 }
4253 }
4254 }
4255 // The `t8` guard was INSIDE the loop (breaking on the first
4256 // iteration) and the `luma_scan` Option was re-resolved on EVERY one
4257 // of the sixteen blocks. Both are macroblock-invariant.
4258 if !t8 {
4259 let scans = &*luma_scan;
4260 // Residual ladders decided up front (`i4_prepare`), then the serial
4261 // predict+add walk with the SIMD IDCT+add kernel per coded block.
4262 let lnnz: [u8; 16] = core::array::from_fn(|i| nnzs[i]);
4263 let kinds = self.i4_prepare(scans, &lnnz, qp);
4264 let dq0 = self.dq_const(qp, 0);
4265 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4266 let (bx, by) = (mbx * 4 + lbx, mby * 4 + lby);
4267 let at = lby > 0 || top_ok;
4268 let al = lbx > 0 || left_ok;
4269 self.recon_i4_block_res(
4270 bx,
4271 by,
4272 modes[(lby & 3) * 4 + (lbx & 3)],
4273 at,
4274 al,
4275 kinds[blk & 15],
4276 scans,
4277 &dq0,
4278 );
4279 }
4280 // Per-MB ROW COPIES for nnz_y (from the raster `mn` already built) and
4281 // coded_y (routing round): sixteen + sixteen checked scattered stores
4282 // per macroblock -> four + four row operations. Nothing inside this
4283 // macroblock reads either grid for its own blocks (interior top-right
4284 // availability is the z-order constant now).
4285 for ry in 0..4usize {
4286 let a = (mby * 4 + ry) * w4 + mbx * 4;
4287 self.nnz_y[a..a + 4].copy_from_slice(&mn[ry * 4..ry * 4 + 4]);
4288 self.coded_y[a..a + 4].fill(true);
4289 }
4290 }
4291 // `nnzs` is a fixed `[u8; 24]`, so its last eight ARE a sub-array:
4292 // build it by index instead of a fallible slice conversion, which
4293 // the compiler cannot see is infallible.
4294 let chroma_nnz: [u8; 8] = core::array::from_fn(|i| nnzs[16 + i]);
4295 self.recon_chroma_cabac(
4296 mbx,
4297 mby,
4298 chroma_mode,
4299 &cdc,
4300 (cbp_chroma == 2).then_some(&*cac),
4301 &chroma_nnz,
4302 cbp_chroma,
4303 top_ok,
4304 left_ok,
4305 );
4306 self.scratch_luma = Some(luma_scan);
4307 self.scratch_luma8 = Some(luma8);
4308 self.scratch_cac = Some(cac);
4309
4310 // Brick 2.1: end_of_slice_flag.
4311 let eos = cab.decode_terminate();
4312 addr += 1;
4313 mbx += 1;
4314 if eos || addr >= total {
4315 break;
4316 }
4317 }
4318 }
4319 if trace {
4320 eprintln!("# CABAC decoded {} MBs (of {total})", addr - first_mb);
4321 }
4322 self.edc_flush(); // slice end: no job crosses a slice boundary
4323 // Return the pooled slice scratch (an error exit forfeits it — the
4324 // next slice then refills fresh allocations, correctness unchanged).
4325 self.sc_cat = cat;
4326 self.sc_cbp = mb_cbp;
4327 self.sc_cmode = cmode;
4328 self.sc_nzc = mb_nzc;
4329 self.sc_cbfdc = cbf_dc;
4330 self.sc_skip = mb_skip;
4331 self.sc_ref = mb_ref;
4332 self.sc_mvd = mb_mvd;
4333 if want_b_grids {
4334 self.sc_ref1 = mb_ref1;
4335 self.sc_mvd1 = mb_mvd1;
4336 self.sc_direct = mb_direct;
4337 }
4338 Ok(addr)
4339 }
4340
4341 /// CABAC chroma recon (mirrors `decode_chroma`'s reconstruction, driven by the
4342 /// CABAC-parsed DC/AC coefficients). `cdc[c]` = 2×2 DC (scan order); `cac[c][blk]`
4343 /// = 15 AC per 4×4 block (scan order).
4344 /// Add a CABAC-parsed inter residual to an already-built motion-comp prediction
4345 /// (`pred_y`/`c_pred`), writing the reconstruction. Shared by the P and B inter
4346 /// paths — same `reconstruct_4x4` as intra, MC output as the prediction, inter
4347 /// scaling lists (luma 3 / chroma 4+c). `luma_scan[z]`/`cdc`/`cac` are the
4348 /// scan-order coefficients; uncoded blocks are zero so recon == prediction.
4349 fn add_inter_residual(
4350 &mut self,
4351 mb_x: usize,
4352 mb_y: usize,
4353 pred_y: &[u8; 256],
4354 c_pred: &[[u8; 64]; 2],
4355 luma_scan: Option<&[[i32; 16]; 16]>,
4356 // `Some` when the macroblock carries transform_size_8x8_flag: four 8x8
4357 // blocks in 8x8 scan order, replacing the sixteen 4x4 luma blocks.
4358 luma8: Option<&[[i32; 64]; 4]>,
4359 cdc: &[[i32; 4]; 2],
4360 cac: Option<&[[[i32; 16]; 4]; 2]>,
4361 cbp_chroma: u32,
4362 // Parsed totalCoeff per block, indexed exactly as the parse's `iz`:
4363 // [0..16] luma 4x4 z-order (for t8, the 8x8 count sits at `id8*4`),
4364 // [16..24] chroma AC as `16 + c*4 + id4`. The parser already counted
4365 // every significant coefficient; re-deriving the counts here scanned
4366 // 16-64 array elements per block (~400 loads/MB) for information the
4367 // caller was holding — the diagnosis's stage-boundary re-derivation tax.
4368 nnzs: &[u8; 24],
4369 ) {
4370 // A `None` field means the parse wrote nothing there; every read below
4371 // is guarded by a zero-count test, so the shared zero plane is
4372 // read-equivalent to the per-macroblock zeroed stack array it replaces.
4373 let luma_scan = luma_scan.unwrap_or(&ZERO_LUMA_SCAN);
4374 let cac = cac.unwrap_or(&ZERO_CAC);
4375 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecResidAdd);
4376 let qp = self.cur_qp;
4377 let qpc = self.chroma_qp_for(qp);
4378 let (w4r, w2r) = (self.mb_w * 4, self.mb_w * 2);
4379 if let Some(l8) = luma8 {
4380 // INTER 8x8 luma: same primitives the I_8x8 and CAVLC paths use.
4381 for b8 in 0..4usize {
4382 let (b8x, b8y) = (b8 % 2, b8 / 2);
4383 // PER-CELL, not one aggregate broadcast over all four cells. CAVLC
4384 // codes an 8x8 block as four 4x4 sub-blocks and its nC predictor
4385 // reads these per-4x4 counts from `nnz_y`, so the broadcast
4386 // corrupted the NEXT macroblock's nC and desynced the parse -- which
4387 // is why CAVLC 8x8 streams ffmpeg accepts would not decode here. The
4388 // worker copy of this function never wrote `nnz_y` at all, so the
4389 // threaded path was unaffected and hid the defect. CABAC has no
4390 // per-4x4 counts, so its callers put the 8x8 total in all four slots.
4391 let nnz: u32 = (0..4).map(|k| nnzs[b8 * 4 + k] as u32).sum();
4392 // Two row slices, not four whole-grid indexed stores. The pair
4393 // written per row is contiguous and `nnzs` is a fixed 24-entry
4394 // array, so both sides become provable.
4395 for sy in 0..2 {
4396 let base = (mb_y * 4 + b8y * 2 + sy) * w4r + mb_x * 4 + b8x * 2;
4397 self.nnz_y[base..][..2].copy_from_slice(&nnzs[b8 * 4 + sy * 2..][..2]);
4398 }
4399 // The 4x4 inter path marks coded_y per block; the 8x8 branch must too,
4400 // or a later intra macroblock's neighbour availability is wrong.
4401 for sy in 0..2 {
4402 let base = (mb_y * 4 + b8y * 2 + sy) * w4r + mb_x * 4 + b8x * 2;
4403 self.coded_y[base..][..2].fill(true);
4404 }
4405 let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
4406 if nnz == 0 {
4407 // Zero residual: recon == pred — the 4x4 arm zero shortcut,
4408 // ported to the 8x8 branch.
4409 edcstat::bump(&edcstat::T8_ZERO, 1);
4410 for dy in 0..8 {
4411 let d = (py + dy) * self.cw + px;
4412 let po = (b8y * 8 + dy) * 16 + b8x * 8;
4413 self.rec_y[d..d + 8].copy_from_slice(&pred_y[po..po + 8]);
4414 }
4415 } else {
4416 let raster = un_scan_8x8(&l8[b8]);
4417 // list 1 = INTER 8x8 luma scaling list (0 is the intra one).
4418 let res8 = self.inv_quant8(&raster, qp, 1);
4419 let predb: [i32; 64] = core::array::from_fn(|i| {
4420 pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
4421 });
4422 let recon = add_residual_8x8(&res8, &predb);
4423 // Eight row copies. This wrote SIXTY-FOUR individually
4424 // bounds-checked samples into the luma plane per coded 8x8
4425 // block — the same shape whose fix carried `recon_i8_block`.
4426 // Present in BOTH the main path and the EDC worker twin.
4427 for dy in 0..8 {
4428 let d = (py + dy) * self.cw + px;
4429 self.rec_y[d..][..8].copy_from_slice(&recon[dy * 8..][..8]);
4430 }
4431 }
4432 }
4433 }
4434 // RESIDUAL LADDER, then the SIMD IDCT+add kernel per parked block (SIMD census
4435 // 2026-09-05). The zero and DC-only ladders write the plane in the loop;
4436 // every other coded block parks its dequantised coefficients here and is
4437 // reconstructed by `reconstruct_4x4_into` = accel `idct4x4_add` (in-register
4438 // transposes, exact on i32). A lane-per-block BATCHED form was tried first
4439 // and lost on all-intra content: its scalar gathers cost more than the
4440 // butterflies they vectorised.
4441 let dq3 = self.dq_const(qp, 3);
4442 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
4443 if luma8.is_some() {
4444 break;
4445 }
4446 let nnz = nnzs[blk];
4447 if let Some(p) = self
4448 .nnz_y
4449 .get_mut((mb_y * 4 + lby) * w4r + (mb_x * 4 + lbx))
4450 {
4451 *p = nnz;
4452 }
4453 let cw = self.cw;
4454 let p_off = (lby * 4) * 16 + lbx * 4;
4455 let r_off = (mb_y * 4 + lby) * 4 * cw + (mb_x * 4 + lbx) * 4;
4456 if nnz == 0 {
4457 // Zero residual → recon == prediction EXACTLY (the integer IDCT is
4458 // linear so zeros map to zeros, and pred is already 0..=255) — copy
4459 // the pred rows straight into the plane. On real (sparse-cbp)
4460 // streams this is MOST of the 4×4 blocks.
4461 for r in 0..4 {
4462 self.rec_y[r_off + r * cw..r_off + r * cw + 4]
4463 .copy_from_slice(&pred_y[p_off + r * 16..p_off + r * 16 + 4]);
4464 }
4465 continue;
4466 }
4467 // DC-ONLY: the sole significant coefficient is scan position 0 (the
4468 // zig-zag starts at DC, and un_scan keeps it at raster 0), so the
4469 // whole dequant + IDCT collapses to one multiply and a flat add.
4470 if nnz == 1 && luma_scan[blk][0] != 0 {
4471 let f = self.dequant_dc4(luma_scan[blk][0], qp, 3);
4472 reconstruct_4x4_dc_into(
4473 (f + 32) >> 6,
4474 pred_y,
4475 p_off,
4476 16,
4477 &mut self.rec_y,
4478 r_off,
4479 cw,
4480 );
4481 } else {
4482 // Fused un-scan + dequant over ONLY the significant coefficients,
4483 // then IDCT + add + clip straight into the plane — no `qb`, no
4484 // `deq`-from-dense, no `predb` gather, no `s`, no `store` call.
4485 //
4486 // HYBRID: the scatter walks scan positions with a data-dependent
4487 // branch per slot, which beats the branchless dense 16-multiply
4488 // loop only while the block is SPARSE. The DC/zero fast paths
4489 // already removed the sparsest blocks, so the population here
4490 // skews denser — above ~6 coefficients the dense loop wins.
4491 // FUSED: un-scan + dequant + IDCT + add in one kernel call (dense-over-scatter round).
4492 reconstruct_4x4_scan_into::<false>(
4493 &luma_scan[blk],
4494 &dq3,
4495 0,
4496 pred_y,
4497 p_off,
4498 16,
4499 &mut self.rec_y,
4500 r_off,
4501 cw,
4502 );
4503 }
4504 }
4505 let mut c_dc = [[0i32; 4]; 2];
4506 if cbp_chroma != 0 {
4507 for c in 0..2 {
4508 c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 4 + c);
4509 }
4510 }
4511 let dqc = [self.dq_const(qpc, 4), self.dq_const(qpc, 5)];
4512 let ccw = self.ccw;
4513 for c in 0..2 {
4514 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4515 let mut ac_nz = false;
4516 if cbp_chroma == 2 {
4517 let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
4518 if let Some(p) =
4519 self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2r + (mb_x * 2 + bx))
4520 {
4521 *p = n;
4522 }
4523 ac_nz = n != 0;
4524 }
4525 let dc = c_dc[c & 1][(by * 2 + bx) & 3];
4526 let p_off = (by * 4) * 8 + bx * 4;
4527 let r_off = (mb_y * 2 + by) * 4 * ccw + (mb_x * 2 + bx) * 4;
4528 let plane = if c == 0 {
4529 &mut self.rec_u
4530 } else {
4531 &mut self.rec_v
4532 };
4533 if dc == 0 && !ac_nz {
4534 // Zero residual (no AC, zero DC) → recon == prediction exactly.
4535 for r in 0..4 {
4536 plane[r_off + r * ccw..r_off + r * ccw + 4]
4537 .copy_from_slice(&c_pred[c][p_off + r * 8..p_off + r * 8 + 4]);
4538 }
4539 continue;
4540 }
4541 // DC-ONLY (no coded AC — covers every cbp_chroma==1 block and the
4542 // AC-empty blocks of cbp_chroma==2): the chroma DC arrives ALREADY
4543 // dequantized, so the residual is `(dc + 32) >> 6` flat.
4544 if !ac_nz {
4545 reconstruct_4x4_dc_into(
4546 (dc + 32) >> 6,
4547 &c_pred[c],
4548 p_off,
4549 8,
4550 plane,
4551 r_off,
4552 ccw,
4553 );
4554 continue;
4555 }
4556 // AC-only scan: index i is overall scan position i+1 (ac_shift=1).
4557 // Same sparse/dense hybrid as luma.
4558 reconstruct_4x4_scan_into::<true>(
4559 &cac[c & 1][(by * 2 + bx) & 3],
4560 &dqc[c & 1],
4561 dc,
4562 &c_pred[c],
4563 p_off,
4564 8,
4565 plane,
4566 r_off,
4567 ccw,
4568 );
4569 }
4570 }
4571 }
4572
4573 // ── SHARED INTRA RECON PRIMITIVES ──────────────────────────────────
4574 // ONE implementation per block family, called by BOTH entropy arms.
4575 // The per-coder recon twins are where routing rot lived: the residual
4576 // ladder and the DC-only collapse each had to be found and ported
4577 // twin-by-twin. The parse halves stay per-coder (that is the real
4578 // difference between the coders); the pixel halves live here — the
4579 // same convergence D14 gave the inter path via add_inter_residual.
4580
4581 /// The residual ladder of an intra 4x4 block, decided BEFORE the serial
4582 /// predict+add walk (SIMD census 2026-09-05); each coded block is then
4583 /// reconstructed by the SIMD IDCT+add kernel.
4584 /// Zero -> recon == pred; Flat -> DC-only, one value; Idx -> a slot in the
4585 /// batched residual array.
4586 fn i4_prepare(&self, scans: &[[i32; 16]; 16], nnzs: &[u8; 16], qp: u8) -> [I4Res; 16] {
4587 let mut kinds = [I4Res::Zero; 16];
4588 for blk in 0..16 {
4589 let (nnz, scan) = (nnzs[blk], &scans[blk]);
4590 kinds[blk] = if nnz == 0 {
4591 edcstat::bump(&edcstat::I4_ZERO, 1);
4592 I4Res::Zero
4593 } else if nnz == 1 && scan[0] != 0 {
4594 edcstat::bump(&edcstat::I4_DC, 1);
4595 let f = self.dequant_dc4(scan[0], qp, 0);
4596 I4Res::Flat((f + 32) >> 6)
4597 } else {
4598 edcstat::bump(&edcstat::I4_DENSE, 1);
4599 I4Res::Idx(blk as u8) // dense: the fused kernel takes the scan block
4600 };
4601 }
4602 kinds
4603 }
4604
4605 /// Intra 4x4 luma block with its residual PRE-TRANSFORMED by [`Self::i4_prepare`]:
4606 /// gather + predict (serial, depends on the previous block's recon) + add.
4607 fn recon_i4_block_res(
4608 &mut self,
4609 bx: usize,
4610 by: usize,
4611 mode: u8,
4612 at: bool,
4613 al: bool,
4614 kind: I4Res,
4615 scans: &[[i32; 16]; 16],
4616 dq: &DequantQp,
4617 ) {
4618 let (px, py) = (bx * 4, by * 4);
4619 let (t, l, corner) = self.gather_i4(px, py, at, al, bx, by);
4620 let pred = intra4x4_pred(mode, at, al, &t, &l, corner);
4621 let r_off = py * self.cw + px;
4622 let cw = self.cw;
4623 match kind {
4624 I4Res::Zero => {
4625 let win = &mut self.rec_y[r_off..r_off + 3 * cw + 4];
4626 for r in 0..4 {
4627 win[r * cw..r * cw + 4].copy_from_slice(&pred[r * 4..r * 4 + 4]);
4628 }
4629 }
4630 I4Res::Flat(v) => reconstruct_4x4_dc_into(v, &pred, 0, 4, &mut self.rec_y, r_off, cw),
4631 I4Res::Idx(i) => reconstruct_4x4_scan_into::<false>(
4632 &scans[i as usize & 15],
4633 dq,
4634 0,
4635 &pred,
4636 0,
4637 4,
4638 &mut self.rec_y,
4639 r_off,
4640 cw,
4641 ),
4642 }
4643 // coded_y is filled per macroblock row by the callers (routing round).
4644 }
4645
4646 /// Intra 8x8 luma block: predict + zero-arm or un-scan/quant/add.
4647 /// `coeffs` = SCAN-order 8x8 coefficients, `None` when the cbp bit is
4648 /// unset. `coded_y` marking stays with the callers (their orders differ).
4649 fn recon_i8_block(
4650 &mut self,
4651 bx: usize,
4652 by: usize,
4653 mode: u8,
4654 avail_top: bool,
4655 avail_left: bool,
4656 coeffs: Option<&[i32; 64]>,
4657 qp: u8,
4658 ) {
4659 let (px, py) = (bx * 4, by * 4);
4660 let (t, l, corner, avail_corner) = self.gather_i8(px, py, avail_top, avail_left, bx, by);
4661 let pred = intra8x8_pred(mode, avail_top, avail_left, avail_corner, &t, &l, corner);
4662 match coeffs {
4663 None => {
4664 // Zero residual: recon == pred exactly.
4665 edcstat::bump(&edcstat::T8_ZERO, 1);
4666 let (cw, base) = (self.cw, py * self.cw + px);
4667 let win = &mut self.rec_y[base..base + 7 * cw + 8];
4668 for dy in 0..8 {
4669 win[dy * cw..dy * cw + 8].copy_from_slice(&pred[dy * 8..dy * 8 + 8]);
4670 }
4671 }
4672 Some(scan8) => {
4673 let raster = un_scan_8x8(scan8);
4674 let res8 = self.inv_quant8(&raster, qp, 0);
4675 // Written once rather than zeroed-then-filled.
4676 let predb: [i32; 64] = core::array::from_fn(|i| pred[i] as i32);
4677 let recon = add_residual_8x8(&res8, &predb);
4678 // ROW COPIES, NOT PER-PIXEL STORES. This wrote all 64 samples
4679 // one at a time, each separately bounds-checked, where the
4680 // destination is eight contiguous 8-byte runs `cw` apart.
4681 // REFUTED, do not retry: replacing this WINDOW + sub-slice with
4682 // eight direct `rec_y[d..][..8]` slices left the panic count
4683 // EXACTLY unchanged (12) and cost +3.0% instructions. Neither
4684 // form folds the check, but the window amortises the base
4685 // address computation across the eight rows. (Contrast
4686 // `gather_tile`, where a window cost +57.6% — a window pays only
4687 // when the rows it spans are written in one tight loop.)
4688 let (cw, base) = (self.cw, py * self.cw + px);
4689 let win = &mut self.rec_y[base..base + 7 * cw + 8];
4690 for dy in 0..8 {
4691 win[dy * cw..dy * cw + 8].copy_from_slice(&recon[dy * 8..dy * 8 + 8]);
4692 }
4693 }
4694 }
4695 }
4696
4697 /// I_16x16 luma: neighbor gather + prediction + the 16 4x4 AC blocks
4698 /// with the DC-only collapse. `q_blocks` = RASTER AC (slot 0 unused),
4699 /// `recon_dc` = the Hadamard-dequantized DC per block. Marks modes_y
4700 /// (I_16x16 predicts as DC for neighbors) + coded_y.
4701 fn recon_i16_luma(
4702 &mut self,
4703 mbx: usize,
4704 mby: usize,
4705 pred_mode: rusty_h264_common::predict::I16Mode,
4706 top_ok: bool,
4707 left_ok: bool,
4708 q_blocks: Option<&[[i32; 16]; 16]>,
4709 coded: u16,
4710 recon_dc: &[i32; 16],
4711 qp: u8,
4712 ) {
4713 // `None` on a DC-only I_16x16 macroblock (CodedBlockPatternLuma == 0):
4714 // no AC was parsed, so the shared zero plane is read-equivalent to the
4715 // 1 KB of stack this used to zero per macroblock.
4716 let q_blocks = q_blocks.unwrap_or(&ZERO_LUMA_SCAN);
4717 let w4 = self.mb_w * 4;
4718 let (lx, ly) = (mbx * 16, mby * 16);
4719 let mut t16 = [0u8; 16];
4720 let mut l16 = [0u8; 16];
4721 if top_ok {
4722 t16.copy_from_slice(self.top_y_row(ly, lx, 16));
4723 }
4724 if left_ok {
4725 // ONE check for the 16-sample column (see `gather_i4`).
4726 // `step_by` walk rather than a span + `col[i * cw]` (see `gather_i8`):
4727 // the span form's inner index stayed checked on all sixteen samples.
4728 let (cw, base) = (self.cw, ly * self.cw + lx - 1);
4729 for (s, &v) in l16.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
4730 *s = v;
4731 }
4732 }
4733 let corner = if top_ok && left_ok {
4734 self.top_y_px(ly, lx - 1)
4735 } else {
4736 0
4737 };
4738 let pred_l = luma16x16_pred(pred_mode, top_ok, left_ok, &t16, &l16, corner);
4739 // AC blocks parked, then the SIMD IDCT+add kernel per block (the pred is whole).
4740 let dq0 = self.dq_const(qp, 0);
4741 for by in 0..4 {
4742 for bx in 0..4 {
4743 let p_off = (by * 4) * 16 + bx * 4;
4744 let r_off = (ly + by * 4) * self.cw + lx + bx * 4;
4745 // ROUTED ON THE PARSE`S CODED MASK (routing round): was a 16-word compare.
4746 if coded & (1u16 << ((by & 3) * 4 + (bx & 3))) == 0 {
4747 // Zero AC: the residual is the Hadamard DC alone.
4748 edcstat::bump(&edcstat::I16_DCONLY, 1);
4749 reconstruct_4x4_dc_into(
4750 (recon_dc[by * 4 + bx] + 32) >> 6,
4751 &pred_l,
4752 p_off,
4753 16,
4754 &mut self.rec_y,
4755 r_off,
4756 self.cw,
4757 );
4758 } else {
4759 // q_blocks holds the SCAN-order AC; the fused kernel un-scans, dequantises
4760 // and inserts the Hadamard DC.
4761 reconstruct_4x4_scan_into::<true>(
4762 &q_blocks[(by & 3) * 4 + (bx & 3)],
4763 &dq0,
4764 recon_dc[by * 4 + bx],
4765 &pred_l,
4766 p_off,
4767 16,
4768 &mut self.rec_y,
4769 r_off,
4770 self.cw,
4771 );
4772 }
4773 }
4774 }
4775 // ROW FILLS. The mode/coded grids were written one 4x4 cell at a time
4776 // inside the recon loop; each macroblock row is four CONTIGUOUS cells,
4777 // so four fills replace sixteen indexed stores.
4778 for by in 0..4 {
4779 let r = (mby * 4 + by) * w4 + mbx * 4;
4780 self.modes_y[r..r + 4].fill(2);
4781 self.coded_y[r..r + 4].fill(true);
4782 }
4783 }
4784
4785 /// Chroma 8x8 recon, both planes: prediction + four 4x4 blocks per
4786 /// plane with the DC-only collapse. `qac` = RASTER AC per plane/block
4787 /// (all-zero when uncoded), `dc` = the 2x2-Hadamard-dequantized DC.
4788 fn recon_chroma_blocks(
4789 &mut self,
4790 mb_x: usize,
4791 mb_y: usize,
4792 chroma_mode: u8,
4793 avail_top: bool,
4794 avail_left: bool,
4795 qac: &[[[i32; 16]; 4]; 2],
4796 coded: [u8; 2],
4797 dc: &[[i32; 4]; 2],
4798 qpc: u8,
4799 ) {
4800 let (cx, cy) = (mb_x * 8, mb_y * 8);
4801 let dqc = [self.dq_const(qpc, 1), self.dq_const(qpc, 2)];
4802 for c in 0..2 {
4803 let mut ctop = [0u8; 8];
4804 let mut cleft = [0u8; 8];
4805 let mut ccorner = 0u8;
4806 {
4807 let rec_c = if c == 0 { &self.rec_u } else { &self.rec_v };
4808 if avail_top {
4809 ctop.copy_from_slice(self.top_c_row(c, cy, cx, 8));
4810 }
4811 if avail_left {
4812 // Strided WALK, as in `gather_i4`/`gather_i8`.
4813 let cbase = cy * self.ccw + cx - 1;
4814 for (o, &v) in cleft
4815 .iter_mut()
4816 .zip(rec_c[cbase..].iter().step_by(self.ccw))
4817 {
4818 *o = v;
4819 }
4820 }
4821 if avail_top && avail_left {
4822 ccorner = self.top_c_px(c, cy, cx - 1);
4823 }
4824 }
4825 let pred8 = chroma8x8_pred(chroma_mode, avail_top, avail_left, &ctop, &cleft, ccorner);
4826 // Coded AC blocks parked (up to 4), then the SIMD IDCT+add kernel per block.
4827 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4828 let p_off = (by * 4) * 8 + bx * 4;
4829 let ccw = self.ccw;
4830 let r_off = (cy + by * 4) * ccw + cx + bx * 4;
4831 if coded[c & 1] & (1u8 << ((by * 2 + bx) & 3)) == 0 {
4832 // Zero AC (cbp_chroma <= 1, the common case): DC-alone flat add.
4833 edcstat::bump(&edcstat::I16_DCONLY, 1);
4834 let plane = if c == 0 {
4835 &mut self.rec_u
4836 } else {
4837 &mut self.rec_v
4838 };
4839 reconstruct_4x4_dc_into(
4840 (dc[c & 1][(by * 2 + bx) & 3] + 32) >> 6,
4841 &pred8,
4842 p_off,
4843 8,
4844 plane,
4845 r_off,
4846 ccw,
4847 );
4848 } else {
4849 let plane = if c == 0 {
4850 &mut self.rec_u
4851 } else {
4852 &mut self.rec_v
4853 };
4854 reconstruct_4x4_scan_into::<true>(
4855 &qac[c & 1][(by * 2 + bx) & 3],
4856 &dqc[c & 1],
4857 dc[c & 1][(by * 2 + bx) & 3],
4858 &pred8,
4859 p_off,
4860 8,
4861 plane,
4862 r_off,
4863 ccw,
4864 );
4865 }
4866 }
4867 }
4868 }
4869
4870 fn recon_chroma_cabac(
4871 &mut self,
4872 mb_x: usize,
4873 mb_y: usize,
4874 chroma_mode: u8,
4875 cdc: &[[i32; 4]; 2],
4876 cac: Option<&[[[i32; 16]; 4]; 2]>,
4877 cnnz: &[u8; 8], // parse-side AC counts, c * 4 + by * 2 + bx (routing round: was a 128-word rescan)
4878 cbp_chroma: u32,
4879 avail_top: bool,
4880 avail_left: bool,
4881 ) {
4882 // `None` = no chroma AC was parsed; every read below is already gated
4883 // on cbp_chroma == 2, so the shared zero plane is read-equivalent.
4884 let cac = cac.unwrap_or(&ZERO_CAC);
4885 let qpc = self.chroma_qp_for(self.cur_qp);
4886 let mut c_dc = [[0i32; 4]; 2];
4887 if cbp_chroma != 0 {
4888 for c in 0..2 {
4889 c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 1 + c);
4890 }
4891 }
4892 // ENTROPY-SIDE half: un-scan the AC into raster + commit nnz_c; the
4893 // pixel half is the SHARED recon_chroma_blocks.
4894 let w2 = self.mb_w * 2;
4895 let mut ccoded = [0u8; 2];
4896 if cbp_chroma == 2 {
4897 for c in 0..2 {
4898 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
4899 let cnt = cnnz[(c * 4 + by * 2 + bx) & 7];
4900 ccoded[c & 1] |= ((cnt != 0) as u8) << ((by * 2 + bx) & 3);
4901 if let Some(p) =
4902 self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2 + (mb_x * 2 + bx))
4903 {
4904 *p = cnt;
4905 }
4906 }
4907 }
4908 }
4909 self.recon_chroma_blocks(
4910 mb_x,
4911 mb_y,
4912 chroma_mode,
4913 avail_top,
4914 avail_left,
4915 cac,
4916 ccoded,
4917 &c_dc,
4918 qpc,
4919 );
4920 }
4921
4922 /// D14 — the CAVLC E-seam (P3 item 5). Mirrors `decode_slice_data_cabac`:
4923 /// overlap parse (this thread) with pixel reconstruction (a scoped worker
4924 /// owning the planes). Now possible because the CAVLC inter recon was
4925 /// converged onto `add_inter_residual`, so both entropy coders emit the SAME
4926 /// `PInterJob` and share one worker recon.
4927 pub fn decode_slice_data(
4928 &mut self,
4929 r: &mut BitReader,
4930 is_p: bool,
4931 first_mb: usize,
4932 ) -> Result<usize, MbError> {
4933 // Same cross-slice guard as the CABAC loop head.
4934 self.span_flush();
4935 let eligible = edc_on() && rowdb_on() && (is_p || self.is_b);
4936 let threaded = eligible && edc_spawn_worker(self.mb_w, self.mb_h, self.bits_per_mb, false);
4937 edcstat::bump(&edcstat::DISPATCH_ON, threaded as u64);
4938 edcstat::bump(&edcstat::DISPATCH_SEEN, eligible as u64);
4939 if !threaded {
4940 return self.decode_slice_cavlc_inner(r, is_p, first_mb);
4941 }
4942 #[cfg(feature = "std")]
4943 {
4944 let ctx = self.edc_take_ctx();
4945 let (tx, rx) = crate::sync::mpsc::sync_channel::<EdcMsg>(edc_bound());
4946 let (ctx_tx, ctx_rx) = crate::sync::mpsc::channel::<PixelCtx>();
4947 let (back_tx, back_rx) = crate::sync::mpsc::channel::<PixelCtx>();
4948 let (res, ctx, panicked) = std::thread::scope(|sc| {
4949 let h = sc.spawn(move || edc_worker(ctx, rx, ctx_tx, back_rx));
4950 self.edc_tx = Some(tx);
4951 self.edc_ctx_rx = Some(ctx_rx);
4952 self.edc_back_tx = Some(back_tx);
4953 // UNWIND SAFETY (same trap the CABAC wrapper documents): the sender
4954 // lives in `self`, which outlives an unwind, so a panic in the parse
4955 // loop would leave the channel open, the worker alive and the scope
4956 // join blocking forever — turning a diagnosable panic into a silent
4957 // deadlock. Catch, clean up, join, restore, THEN resume.
4958 let r2 = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
4959 self.decode_slice_cavlc_inner(r, is_p, first_mb)
4960 }));
4961 self.edc_flush_batch();
4962 self.edc_giveback();
4963 self.edc_tx = None;
4964 self.edc_ctx_rx = None;
4965 self.edc_back_tx = None;
4966 match (r2, h.join()) {
4967 (Ok(res), Ok(ctx)) => (res, ctx, None),
4968 (Err(pn), Ok(ctx)) => (Err(MbError::Truncated), ctx, Some(pn)),
4969 (Ok(_), Err(pn)) | (Err(_), Err(pn)) => {
4970 (Err(MbError::Truncated), None, Some(pn))
4971 }
4972 }
4973 });
4974 if let Some(ctx) = ctx {
4975 self.edc_restore_ctx(ctx);
4976 }
4977 if let Some(pn) = panicked {
4978 std::panic::resume_unwind(pn);
4979 }
4980 res
4981 }
4982 #[cfg(not(feature = "std"))]
4983 {
4984 unreachable!("the EDC worker needs std; edc_spawn_worker is false without it")
4985 }
4986 }
4987
4988 fn decode_slice_cavlc_inner(
4989 &mut self,
4990 r: &mut BitReader,
4991 is_p: bool,
4992 first_mb: usize,
4993 ) -> Result<usize, MbError> {
4994 let total = self.mb_w * self.mb_h;
4995 self.slice_first_mb = first_mb;
4996 self.slice_bounds.push((first_mb, self.cur_idc2));
4997 self.any_idc2 |= self.cur_idc2;
4998 self.edc_active = edc_on();
4999 let mut addr = first_mb;
5000 // Same malformed-header divide-by-zero as the sibling slice loop above.
5001 let mbw_nz = self.mb_w.max(1);
5002 let (mut mbx, mut mby) = (addr % mbw_nz, addr / mbw_nz);
5003 let mbw_c = self.mb_w;
5004 while addr < total {
5005 if mbx == mbw_c {
5006 mbx = 0;
5007 mby += 1;
5008 }
5009 self.row_hook_at(addr, mby);
5010 if is_p || self.is_b {
5011 let skip_run = {
5012 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
5013 r.read_ue()?
5014 } as usize;
5015 // A run past the picture end is a corrupt stream (ffmpeg errors
5016 // here too); a run TO the end is legal. Silently clamping used
5017 // to fill the remainder with skip MBs.
5018 if skip_run > total - addr {
5019 return Err(MbError::Truncated);
5020 }
5021 // The run length is KNOWN here (CAVLC codes it as one syntax
5022 // element). P runs: after the first skip commits (0,0), every
5023 // later run MB is FORCED (0,0) by the zero-MV rule — process
5024 // the remainder SEGMENT-WISE: one span extension + one mb_qp
5025 // fill per row segment instead of a per-MB call + span match +
5026 // store. Non-(0,0) runs and B runs keep the per-MB loop. The
5027 // per-MB `addr >= total` check is gone: the run was validated
5028 // against `total - addr` above.
5029 let mut remaining = skip_run;
5030 while remaining > 0 {
5031 debug_assert!(addr < total);
5032 if mbx == mbw_c {
5033 mbx = 0;
5034 mby += 1;
5035 }
5036 if self.is_b {
5037 if !self.b_skip_hot(mbx, mby) {
5038 self.decode_b_skip(mbx, mby)?;
5039 }
5040 if let Some(p) = self.mb_qp.get_mut(addr) {
5041 *p = self.cur_qp;
5042 } // skip inherits QPy
5043 addr += 1;
5044 mbx += 1;
5045 remaining -= 1;
5046 continue;
5047 }
5048 self.decode_p_skip(mbx, mby)?;
5049 if let Some(p) = self.mb_qp.get_mut(addr) {
5050 *p = self.cur_qp;
5051 }
5052 addr += 1;
5053 mbx += 1;
5054 remaining -= 1;
5055 // Segment-wise remainder: forced (0,0) continuation holds
5056 // exactly while skip_zero_next tracks addr (decode_p_skip
5057 // set it iff this skip committed (0,0)).
5058 // Worker mode (edc_tx) must route per-MB jobs through the
5059 // channel — the parse thread no longer owns the planes.
5060 while remaining > 0
5061 && self.skip_zero_next == addr
5062 && !no_runmv()
5063 && self.edc_tx.is_none()
5064 {
5065 if mbx == mbw_c {
5066 mbx = 0;
5067 mby += 1;
5068 }
5069 // Row segment: run to row end or run end.
5070 let seg = remaining.min(mbw_c - mbx);
5071 let recon =
5072 self.edc_tx.is_none() && (self.weights.is_none() || self.weights_id0);
5073 for k in 0..seg {
5074 self.pz_push(mbx + k, mby, recon);
5075 }
5076 edcstat::bump(&edcstat::SKIPMV_FORCED, seg as u64);
5077 self.route_skip_mbs += seg as u32;
5078 self.mb_qp[addr..addr + seg].fill(self.cur_qp);
5079 if !recon {
5080 for k in 0..seg {
5081 self.edc_jobs.push(EdcJob::Skip {
5082 mbx: mbx + k,
5083 mby,
5084 mv: (0, 0),
5085 });
5086 }
5087 }
5088 addr += seg;
5089 mbx += seg;
5090 remaining -= seg;
5091 self.skip_zero_next = addr;
5092 }
5093 }
5094 if addr >= total {
5095 break;
5096 }
5097 // A trailing skip run with no following macroblock ends the slice.
5098 if skip_run > 0 && !r.more_rbsp_data() {
5099 break;
5100 }
5101 }
5102 // The skip run above may have crossed a row boundary within this
5103 // iteration — wrap before the non-skip MB decodes.
5104 if mbx == mbw_c {
5105 mbx = 0;
5106 mby += 1;
5107 }
5108 if self.is_b {
5109 // ORDER: B reconstructs inline (not seam-ready).
5110 self.edc_intra_sync();
5111 self.decode_b_mb(r, mbx, mby)?;
5112 } else {
5113 // Non-skip CAVLC MB: inter MV prediction + intra gathers
5114 // read the grids — flush deferred spans.
5115 self.span_flush();
5116 self.decode_mb(r, mbx, mby, is_p)?;
5117 }
5118 if let Some(p) = self.mb_qp.get_mut(addr) {
5119 *p = self.cur_qp;
5120 }
5121 addr += 1;
5122 mbx += 1;
5123 // CAVLC slice end: no more data after this macroblock.
5124 if !r.more_rbsp_data() {
5125 break;
5126 }
5127 }
5128 self.edc_flush(); // slice end: no job crosses a slice boundary
5129 Ok(addr)
5130 }
5131
5132 fn decode_mb(
5133 &mut self,
5134 r: &mut BitReader,
5135 mb_x: usize,
5136 mb_y: usize,
5137 is_p: bool,
5138 ) -> Result<(), MbError> {
5139 self.wait_refs_for_mb(mb_y);
5140 let mut mb_type = {
5141 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
5142 r.read_ue()?
5143 };
5144 if is_p {
5145 // In P-slices, mb_type 0/1/2 are inter (16×16, 16×8, 8×16),
5146 // 3 = P_8x8, 4 = P_8x8ref0 (ref_idx forced 0), 5+ intra.
5147 if mb_type <= 2 {
5148 return self.decode_inter(r, mb_x, mb_y, mb_type as u8);
5149 }
5150 if mb_type == 3 || mb_type == 4 {
5151 return self.decode_p8x8(r, mb_x, mb_y, mb_type == 4);
5152 }
5153 mb_type -= 5;
5154 }
5155 // ORDER: intra reconstruction reads neighbour PIXELS, so the worker
5156 // must have applied every deferred job before this point.
5157 self.edc_intra_sync();
5158 self.decode_intra_mb(r, mb_x, mb_y, mb_type)
5159 }
5160
5161 /// Decodes an intra macroblock given its intra `mb_type` (0 = I_4x4,
5162 /// 1..=24 = I_16x16, 25 = I_PCM) — shared by I-, P- and B-slice paths.
5163 fn decode_intra_mb(
5164 &mut self,
5165 r: &mut BitReader,
5166 mb_x: usize,
5167 mb_y: usize,
5168 mb_type: u32,
5169 ) -> Result<(), MbError> {
5170 // H-48: this scope was DECLARED and never wired, which is precisely why the
5171 // stage table left 19.8% unaccounted — 66,120 of 475,200 macroblocks on the
5172 // reference stream are I-type and had no scope at all.
5173 let _gi = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMbI);
5174 if let Some(p) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
5175 *p = rusty_h264_common::deblock::MB_KIND_INTRA;
5176 }
5177 if mb_type == 0 {
5178 // I_NxN: transform_size_8x8_flag (when enabled) selects I_8x8 vs I_4x4.
5179 if self.transform_8x8_mode && r.read_bit()? {
5180 self.decode_i8x8(r, mb_x, mb_y)?;
5181 } else {
5182 self.decode_i4x4(r, mb_x, mb_y)?;
5183 }
5184 } else if (1..=24).contains(&mb_type) {
5185 self.decode_i16(r, mb_x, mb_y, mb_type - 1)?;
5186 } else if mb_type == 25 {
5187 // ORDER: I_PCM writes pixels directly.
5188 self.edc_intra_sync();
5189 self.decode_ipcm(r, mb_x, mb_y)?;
5190 } else {
5191 return Err(MbError::Unsupported(
5192 "only I_4x4 / I_16x16 / I_PCM macroblocks",
5193 ));
5194 }
5195 // Mark all luma blocks coded for the next macroblock's top-right.
5196 // Four contiguous cells per macroblock row - `fill`, not sixteen
5197 // separately bounds-checked indexed stores.
5198 let w4 = self.mb_w * 4;
5199 for lby in 0..4usize {
5200 let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
5201 self.coded_y[a..a + 4].fill(true);
5202 }
5203 Ok(())
5204 }
5205
5206 /// Reconstructs an inter macroblock (`mode` 0 = P_L0_16x16, 1 = P_16x8,
5207 /// 2 = P_8x16): parse the per-partition motion vectors and residual,
5208 /// motion-compensate each partition, and add the residual.
5209 fn decode_inter(
5210 &mut self,
5211 r: &mut BitReader,
5212 mb_x: usize,
5213 mb_y: usize,
5214 mode: u8,
5215 ) -> Result<(), MbError> {
5216 if self.refs.is_empty() {
5217 return Err(MbError::Unsupported("inter without reference"));
5218 }
5219 // DEBLOCK CLASS: mode 0 is P_L0_16x16 — ONE partition, so all 16 blocks
5220 // share a reference and motion vector and no internal edge can reach
5221 // strength 1. Internal strengths then follow from coefficients alone, i.e.
5222 // 16 nnz bytes instead of a 24-block gather across 5-7 grids. Modes 1/2
5223 // (P_16x8 / P_8x16) have two partitions with independent motion and stay
5224 // UNSET (blind path).
5225 if mode == 0 {
5226 if let Some(k) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
5227 *k = rusty_h264_common::deblock::MB_KIND_INTER_UNIFORM;
5228 }
5229 }
5230 // QP (qp/qpc) is bound after mb_qp_delta is read below.
5231 let w4 = self.mb_w * 4;
5232 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
5233 let num_refs = self.refs.len();
5234 let layout = inter_partitions(mode);
5235
5236 // mb_pred order (spec 7.3.5.1): all ref_idx_l0 first (only when more than
5237 // one reference is active), then all mvd_l0.
5238 let nparts = layout.len();
5239 let mut ref_idxs = [0i32; 4];
5240 if self.num_ref_active > 1 {
5241 for ri in ref_idxs[..nparts].iter_mut() {
5242 *ri = read_ref_idx(r, self.num_ref_active)?;
5243 if *ri as usize >= num_refs {
5244 return Err(MbError::Truncated); // references a non-existent picture
5245 }
5246 }
5247 }
5248
5249 // Phase 1: per partition, ref-aware MV prediction + mvd, committing the
5250 // motion grid so a later partition predicts from an earlier one.
5251 let mut part_mv = [(0i32, (0i32, 0i32)); 4];
5252 {
5253 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
5254 for (part, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
5255 let refi = ref_idxs[part];
5256 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
5257 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (rw / 4) as isize);
5258 let pmv = predict_partition_mv(mode, part, a, b, c, refi);
5259 let mvd_x = read_mvd(r)?;
5260 let mvd_y = read_mvd(r)?;
5261 let mv = (pmv.0 + mvd_x, pmv.1 + mvd_y);
5262 part_mv[part] = (refi, mv);
5263 for by in ry / 4..ry / 4 + rh / 4 {
5264 for bx in rx / 4..rx / 4 + rw / 4 {
5265 let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
5266 if let (Some(m), Some(it), Some(rf), Some(cd)) = (
5267 self.mv_y.get_mut(idx),
5268 self.inter_y.get_mut(idx),
5269 self.ref_idx_y.get_mut(idx),
5270 self.coded_y.get_mut(idx),
5271 ) {
5272 (*m, *it, *rf, *cd) = ((mv.0 as i16, mv.1 as i16), true, refi as i8, true);
5273 }
5274 }
5275 }
5276 }
5277 }
5278
5279 // Phase 2: motion-compensate each partition from its reference.
5280 let mut pred_y = [0u8; 256];
5281 let mut c_pred = [[0u8; 64]; 2];
5282 // D8-CAVLC: the double-stage ablation, extended to the CAVLC loop. The
5283 // CABAC measurement could not run here at all (`edc_active` is false on
5284 // this path, so nothing replays through `edc_flush` and `doubled` read
5285 // 0) — yet CAVLC is exactly the population P3 item 5 targets, and its
5286 // cheaper parse should make the PIXEL share larger. Doubling the whole
5287 // partition loop is idempotent: pass 2 overwrites `pred_y` with fresh MC
5288 // BEFORE `weight_partition` runs, so weighting cannot apply twice.
5289 // This doubles MC only (not the residual add inside `inter_finish`), so
5290 // it is a LOWER BOUND on the CAVLC pixel share.
5291 // D14 (CAVLC E-seam): when the seam is live the WORKER motion-compensates
5292 // from the committed MV grids, so skip MC here rather than computing a
5293 // prediction that would be discarded.
5294 let defer = self.edc_tx.is_some() || self.edc_active;
5295 let mc_passes = if defer {
5296 0
5297 } else if double_recon() {
5298 2
5299 } else {
5300 1
5301 };
5302 for _pass in 0..mc_passes {
5303 if _pass > 0 {
5304 edcstat::bump(&edcstat::DOUBLED, 1);
5305 }
5306 for (part, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
5307 let (refi, mv) = part_mv[part & 3];
5308 let Some(reference) = self.refs.get(refi as usize) else {
5309 continue;
5310 };
5311 let mut tmp = [0u8; 256];
5312 mc_luma_padded(
5313 &reference.luma_guard(reference.ch),
5314 reference.lstride(),
5315 crate::LPAD,
5316 self.cw,
5317 ch,
5318 mb_x * 16 + rx,
5319 mb_y * 16 + ry,
5320 rw,
5321 rh,
5322 mv.0,
5323 mv.1,
5324 &mut tmp,
5325 );
5326 {
5327 let _g =
5328 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
5329 restride(&mut pred_y, 16, rx, ry, &tmp, rw, rh);
5330 }
5331 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
5332 for cc in 0..2 {
5333 let rc = if cc == 0 {
5334 &*reference.chroma_guard(0, reference.ch)
5335 } else {
5336 &*reference.chroma_guard(1, reference.ch)
5337 };
5338 let mut tc = [0u8; 64];
5339 mc_chroma_padded(
5340 rc,
5341 reference.cstride(),
5342 crate::CPAD,
5343 self.ccw,
5344 cch,
5345 mb_x * 8 + crx,
5346 mb_y * 8 + cry,
5347 crw,
5348 crh,
5349 mv.0,
5350 mv.1,
5351 &mut tc,
5352 );
5353 {
5354 let _g =
5355 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
5356 restride(&mut c_pred[cc], 8, crx, cry, &tc, crw, crh);
5357 }
5358 }
5359 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi as usize, rx, ry, rw, rh);
5360 }
5361 }
5362
5363 // 16×16/16×8/8×16 partitions are all ≥ 8×8, so the 8×8 transform is allowed.
5364 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, true, defer)
5365 }
5366
5367 /// Shared inter tail: parse `coded_block_pattern` + `mb_qp_delta`, decode the
5368 /// luma/chroma residual, and add it to the already-built motion-compensated
5369 /// prediction. Used by both the 16×16/16×8/8×16 path and `P_8x8`.
5370 fn inter_finish(
5371 &mut self,
5372 r: &mut BitReader,
5373 mb_x: usize,
5374 mb_y: usize,
5375 pred_y: &[u8; 256],
5376 c_pred: &[[u8; 64]; 2],
5377 allow_8x8: bool,
5378 // D14: emit a worker job instead of reconstructing. Only the CAVLC
5379 // 16x16/16x8/8x16 path sets this; B and P_8x8 stay inline.
5380 defer: bool,
5381 ) -> Result<(), MbError> {
5382 let w4 = self.mb_w * 4;
5383 let cbp = {
5384 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
5385 read_cbp_inter(r)?
5386 };
5387 let cbp_luma = cbp & 15;
5388 let cbp_chroma = cbp >> 4;
5389 // transform_size_8x8_flag follows cbp (before mb_qp_delta) when luma has
5390 // coefficients, the 8×8 transform is enabled, and every partition ≥ 8×8.
5391 let t8x8 = cbp_luma > 0 && self.transform_8x8_mode && allow_8x8 && r.read_bit()?;
5392 if t8x8 {
5393 if let Some(f) = self.mb_t8x8.get_mut(mb_y * self.mb_w + mb_x) {
5394 *f = true;
5395 }
5396 self.any_t8 = true;
5397 }
5398 if cbp != 0 {
5399 self.step_qp(r.read_se()?)?;
5400 }
5401 let (qp, _qpc) = (self.cur_qp, self.chroma_qp_for(self.cur_qp));
5402
5403 // ---- luma residual ----
5404 self.nnz_cache_load(mb_x, mb_y);
5405 let mut nnzs = [0u8; 24];
5406 // PARSE STRAIGHT INTO THE DESTINATION. The deferred arm used to parse into
5407 // 1.5 KB of zeroed locals and then copy them into the pooled job (another
5408 // 1.5 KB, +1 KB each for t8x8); the inline arm zeroed the same locals.
5409 // Both now decode into a pre-owned plane set -- the pooled `PInterJob`
5410 // when deferring, the decoder's scratch boxes otherwise -- and zero only
5411 // the blocks the cbp codes. Uncoded slots stay dirty on purpose: the
5412 // consumer (`add_inter_residual` / the worker) skips every block whose
5413 // `nnzs` count is 0 and every chroma AC plane unless cbp_chroma == 2,
5414 // the contract the CABAC P arm has shipped on since round 4.
5415 let nores = cbp == 0 && nores_on();
5416 let mut job = (defer && !nores).then(|| self.take_pinter_job());
5417 let mut s_luma = if job.is_none() {
5418 self.scratch_luma
5419 .take()
5420 .or_else(|| Some(Box::new([[0i32; 16]; 16])))
5421 } else {
5422 None
5423 };
5424 let mut s_luma8 = if job.is_none() {
5425 self.scratch_luma8
5426 .take()
5427 .or_else(|| Some(Box::new([[0i32; 64]; 4])))
5428 } else {
5429 None
5430 };
5431 let mut s_cac = if job.is_none() {
5432 self.scratch_cac
5433 .take()
5434 .or_else(|| Some(Box::new([[[0i32; 16]; 4]; 2])))
5435 } else {
5436 None
5437 };
5438 let (luma_scan, luma8, cac): (
5439 &mut [[i32; 16]; 16],
5440 &mut [[i32; 64]; 4],
5441 &mut [[[i32; 16]; 4]; 2],
5442 ) = match job.as_deref_mut() {
5443 Some(j) => (&mut j.luma_scan, &mut j.luma8, &mut j.cac),
5444 // `get_or_insert_with` rather than `expect`: the scratch is
5445 // created unconditionally above, but nothing in the types says so.
5446 // This makes it structural, and the closures are LAZY, so the
5447 // already-Some case allocates nothing.
5448 None => (
5449 &mut **s_luma.get_or_insert_with(|| Box::new([[0i32; 16]; 16])),
5450 &mut **s_luma8.get_or_insert_with(|| Box::new([[0i32; 64]; 4])),
5451 &mut **s_cac.get_or_insert_with(|| Box::new([[[0i32; 16]; 4]; 2])),
5452 ),
5453 };
5454 // BUILD THE MACROBLOCK'S nnz RASTER ON THE STACK, COPY IT ROW-WISE ONCE.
5455 // Both arms below scattered sixteen individually bounds-checked stores
5456 // into the frame grid while the entropy parse ran. Nothing reads
5457 // `nnz_y` for THIS macroblock before the function returns - `nc_pred`
5458 // predicts from the separate `nnz_cache` - so the writes can be
5459 // deferred. The zero arm then costs NOTHING: the raster already holds
5460 // zeros.
5461 let mut nnz_raster = [0u8; 16];
5462 if t8x8 {
5463 for b8 in 0..4 {
5464 let (b8x, b8y) = (b8 % 2, b8 / 2);
5465 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
5466 let _ = (bx, by);
5467 if cbp_luma & (1 << b8) != 0 {
5468 // Zero this 8x8 slot only (256 B) -- was a 1 KB `[[0;64];4]` insert + a 256 B copy.
5469 luma8[b8 & 3] = [0i32; 64];
5470 for sub in 0..4 {
5471 let (sx, sy) = (sub % 2, sub / 2);
5472 let (cx, cy) = (b8x * 2 + sx, b8y * 2 + sy);
5473 let nc = self.nc_pred(cx, cy);
5474 let mut blk = [0i32; 16];
5475 let total = decode_residual_block_into::<16, 16>(r, nc, &mut blk)?;
5476 self.nnz_cache_set(cx, cy, total);
5477 nnz_raster[cy * 4 + cx] = total;
5478 // The PER-SUB-BLOCK count the next macroblock's nC prediction
5479 // depends on -- summing these into one slot and letting the
5480 // recon helper broadcast it back is what broke CAVLC 8x8.
5481 // WIN: b8 < 4 and sub < 4, so the index is 0..=15 and the
5482 // mask is a no-op that PROVES it -- the same idiom the
5483 // neighbouring luma8[b8 & 3] / luma_scan[blk & 15] already use.
5484 // Folds a panic_bounds_check out of inter_finish.
5485 nnzs[(b8 * 4 + sub) & 15] = total;
5486 // RAW 8x8 scan: coeff k of sub-block s at 4k + s (spec 7.3.5.3.2);
5487 // `add_inter_residual` un-scans + dequantises, as for CABAC.
5488 for k in 0..16 {
5489 luma8[b8 & 3][(4 * k + sub) & 63] = blk[k];
5490 }
5491 }
5492 } else {
5493 for sub in 0..4 {
5494 let (sx, sy) = (sub % 2, sub / 2);
5495 self.nnz_cache_set(b8x * 2 + sx, b8y * 2 + sy, 0);
5496 // `nnz_raster` is already zero here - no store needed.
5497 }
5498 }
5499 }
5500 } else {
5501 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
5502 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
5503 let total = if cbp_luma & (1 << (blk / 4)) != 0 {
5504 let nc = self.nc_pred(lbx, lby);
5505 // Decoded STRAIGHT into the destination plane (RAW scan order, like
5506 // CABAC); zero only this coded block first (the slot is pooled/dirty).
5507 luma_scan[blk & 15] = [0i32; 16];
5508 decode_residual_block_into::<16, 16>(r, nc, &mut luma_scan[blk & 15])?
5509 } else {
5510 0
5511 };
5512 self.nnz_cache_set(lbx, lby, total);
5513 let _ = (bx, by);
5514 nnz_raster[(lby & 3) * 4 + (lbx & 3)] = total;
5515 // WIN: luma 4x4 index, 0..=15 (luma_scan[blk & 15] above relies on the
5516 // same range); the mask folds the second bounds check.
5517 nnzs[blk & 15] = total;
5518 }
5519 }
5520 // ONE contiguous copy per macroblock row.
5521 for by in 0..4usize {
5522 let a = (mb_y * 4 + by) * w4 + mb_x * 4;
5523 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
5524 }
5525
5526 // ---- chroma residual ----
5527 let mut c_recon_dc = [[0i32; 4]; 2];
5528 if cbp_chroma != 0 {
5529 for slot in c_recon_dc.iter_mut() {
5530 // RAW, straight into the 4-word slot (dequantised in the helper).
5531 decode_residual_block_into::<4, 4>(r, -1, slot)?;
5532 }
5533 }
5534 if cbp_chroma == 2 {
5535 self.chroma_cache_load(mb_x, mb_y);
5536 let w2 = self.mb_w * 2;
5537 let mut cnnz = [[0u8; 4]; 2];
5538 for c in 0..2 {
5539 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
5540 let nc = self.chroma_nc_pred(c, bx, by);
5541 let slot = &mut cac[c & 1][(by * 2 + bx) & 3];
5542 *slot = [0i32; 16];
5543 let total = decode_residual_block_into::<15, 16>(r, nc, slot)?; // RAW scan order
5544 self.chroma_nnz_cache_set(c, bx, by, total);
5545 // WIN: by < 2 and bx < 2, so 0..=3 -- the mask proves it against
5546 // the [[u8; 4]; 2], exactly as the cac[c & 1][(by * 2 + bx) & 3]
5547 // slot two lines above already does. Folds the last
5548 // panic_bounds_check out of inter_finish.
5549 cnnz[c & 1][(by * 2 + bx) & 3] = total;
5550 // Masks are the proof: max index 16 + 4 + 2 + 1 = 23 < 24 (was a `.min(23)`).
5551 nnzs[16 + (c & 1) * 4 + (by & 1) * 2 + (bx & 1)] = total;
5552 }
5553 for by in 0..2usize {
5554 let a = (mb_y * 2 + by) * w2 + mb_x * 2;
5555 self.nnz_c[c][a..a + 2].copy_from_slice(&cnnz[c][by * 2..by * 2 + 2]);
5556 }
5557 }
5558 }
5559
5560 // ---- reconstruction ----
5561 //
5562 // D13: this used to be a 109-line hand-rolled copy of the residual add.
5563 // It now calls the SAME `add_inter_residual` the CABAC path uses, which
5564 // is what makes the CAVLC E-seam possible at all: the two paths had
5565 // different residual representations (CAVLC pre-applied un_scan and
5566 // inv_quant at PARSE time; CABAC carries raw scan-order coefficients and
5567 // dequantises inside the helper), so no job could be shared. Carrying the
5568 // raw forms — which CAVLC already had in hand — converges them, deletes
5569 // a duplicate implementation, and lets a deferred job reuse the existing
5570 // worker recon instead of needing a second copy that could drift.
5571 if defer {
5572 // The residual representation now matches CABAC exactly (the
5573 // convergence commit), so the SAME `PInterJob` and the SAME worker
5574 // `recon_p_inter` serve both entropy coders — no second recon
5575 // implementation exists that could drift.
5576 let (mut gmv, mut gref) = ([(0i16, 0i16); 16], [0u8; 16]);
5577 let w4r = self.mb_w * 4;
5578 // ROW SLICES over BOTH grids. This was the single densest panic site
5579 // left in the decoder crate: sixteen iterations reading two PARALLEL
5580 // frame grids at the same index, and `mv_y`'s bound proved nothing
5581 // about `ref_idx_y`, so all thirty-two loads carried their own check.
5582 // Each macroblock row is four contiguous cells in both.
5583 for by in 0..4usize {
5584 let base = (mb_y * 4 + by) * w4r + mb_x * 4;
5585 let mvr = &self.mv_y[base..][..4];
5586 let rfr = &self.ref_idx_y[base..][..4];
5587 gmv[by * 4..][..4].copy_from_slice(mvr);
5588 for bx in 0..4usize {
5589 gref[by * 4 + bx] = rfr[bx].clamp(0, 15) as u8;
5590 }
5591 }
5592 // D9 applies here too: `cbp == 0` means all 2,592 coefficient bytes
5593 // of the 2,784-byte job are ZERO, so ship the 176-byte motion-only
5594 // form. Discovered on the CABAC path; it transfers for free because
5595 // the CAVLC arm now emits the SAME job type.
5596 let ej = if nores {
5597 edcstat::bump(&edcstat::J_NORES_SENT, 1);
5598 let b = self.take_nores_job(PInterNoResJob {
5599 mbx: mb_x,
5600 mby: mb_y,
5601 t8: t8x8,
5602 gmv,
5603 gref,
5604 });
5605 EdcJob::InterNoRes(b)
5606 } else {
5607 // Pooled box (see `take_pinter_job`); the CAVLC arm still parses into
5608 // locals, so this is a copy, not an allocation.
5609 // The pool hands out a blank job when empty, so the fallback
5610 // here is the same thing `take_pinter_job` would have produced.
5611 // Reached only if the `defer && !nores` binding above did not
5612 // run, which it always does on this branch -- but that is an
5613 // argument, not a type, so do not spend a panic on it.
5614 let mut job = job.take().unwrap_or_else(|| Box::new(PInterJob::blank()));
5615 job.mbx = mb_x;
5616 job.mby = mb_y;
5617 job.qp = qp;
5618 job.cbp_chroma = cbp_chroma;
5619 job.t8 = t8x8;
5620 job.gmv = gmv;
5621 job.gref = gref;
5622 // luma_scan / luma8 / cac were parsed in place.
5623 job.cdc = c_recon_dc;
5624 job.nnzs = nnzs;
5625 EdcJob::Inter(job)
5626 };
5627 if self.edc_tx.is_some() {
5628 self.edc_giveback();
5629 self.edc_send_job(ej);
5630 } else {
5631 self.edc_jobs.push(ej);
5632 }
5633 } else {
5634 self.add_inter_residual(
5635 mb_x,
5636 mb_y,
5637 pred_y,
5638 c_pred,
5639 Some(&*luma_scan),
5640 t8x8.then_some(&*luma8),
5641 &c_recon_dc,
5642 Some(&*cac),
5643 cbp_chroma,
5644 &nnzs,
5645 );
5646 // Give the scratch planes back (taken only on this arm).
5647 self.scratch_luma = s_luma;
5648 self.scratch_luma8 = s_luma8;
5649 self.scratch_cac = s_cac;
5650 }
5651
5652 // MV grid + coded flags were set per partition; mark modes as DC.
5653 // Four contiguous cells per row - `fill`, not sixteen indexed stores.
5654 for lby in 0..4usize {
5655 let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
5656 self.modes_y[a..a + 4].fill(2);
5657 }
5658 Ok(())
5659 }
5660
5661 // ---------------------------------------------------------------------
5662 // B-slice macroblock decoding
5663 // ---------------------------------------------------------------------
5664
5665 /// Per-list (`list` 0 or 1) MV-prediction neighbors for the block region at
5666 /// `(pbx, pby)` of width `pwb` blocks — the L0/L1 analogue of
5667 /// `mv_neighbors_block`.
5668 fn mv_neighbors_list(
5669 &self,
5670 pbx: isize,
5671 pby: isize,
5672 pwb: isize,
5673 list: usize,
5674 ) -> [MvNeighbor; 3] {
5675 self.mv_neighbors_block_grid(pbx, pby, pwb, list)
5676 }
5677
5678 /// Spatial-direct A/B/C at the MB origin — same for every 8×8 in the MB
5679 /// (spec §8.4.1.2.2). B_8x8 callers hoist this once; 16×16 skip/direct walk
5680 /// once inside `decode_b_direct`.
5681 #[inline]
5682 fn b_direct_nbrs(&self, mb_x: usize, mb_y: usize) -> ([MvNeighbor; 3], [MvNeighbor; 3]) {
5683 self.mv_neighbors_both((mb_x * 4) as isize, (mb_y * 4) as isize, 4)
5684 }
5685
5686 /// FUSED dual-list gather at arbitrary partition geometry: A/B/C positions
5687 /// and their availability (bounds + coded + slice) are list-independent —
5688 /// compute once, load both lists' grids from the same resolved index.
5689 fn mv_neighbors_both(
5690 &self,
5691 pbx: isize,
5692 pby: isize,
5693 pwb: isize,
5694 ) -> ([MvNeighbor; 3], [MvNeighbor; 3]) {
5695 let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
5696 let get2 = |bx: isize, by: isize| -> (MvNeighbor, MvNeighbor) {
5697 if bx < 0
5698 || by < 0
5699 || bx >= w4
5700 || by >= h4
5701 || !self
5702 .coded_y
5703 .get((by * w4 + bx) as usize)
5704 .copied()
5705 .unwrap_or(false)
5706 || !self.nbr_in_slice(bx as usize / 4, by as usize / 4)
5707 {
5708 (MvNeighbor::NONE, MvNeighbor::NONE)
5709 } else {
5710 // FOUR PARALLEL GRIDS at one index. The `coded_y` guard above
5711 // bounds none of them — they are separate Vecs — so each carried
5712 // its own panic path. `MvNeighbor::NONE` is the established
5713 // "no such neighbour" value, so the fallible form degrades into
5714 // the branch directly above it.
5715 let idx = (by * w4 + bx) as usize;
5716 match (
5717 self.mv_y.get(idx),
5718 self.ref_idx_y.get(idx),
5719 self.mv1.get(idx),
5720 self.ref_idx1.get(idx),
5721 ) {
5722 (Some(&m0), Some(&r0), Some(&m1), Some(&r1)) => (
5723 MvNeighbor {
5724 available: true,
5725 mv: (i32::from(m0.0), i32::from(m0.1)),
5726 ref_idx: i32::from(r0),
5727 },
5728 MvNeighbor {
5729 available: true,
5730 mv: (i32::from(m1.0), i32::from(m1.1)),
5731 ref_idx: i32::from(r1),
5732 },
5733 ),
5734 _ => (MvNeighbor::NONE, MvNeighbor::NONE),
5735 }
5736 }
5737 };
5738 let (a0, a1) = get2(pbx - 1, pby);
5739 let (b0, b1) = get2(pbx, pby - 1);
5740 let (mut c0, mut c1) = get2(pbx + pwb, pby - 1);
5741 if !c0.available {
5742 // The C fallback is position-driven (topright unavailable ⇒
5743 // topleft), so both lists fall back together — same decision the
5744 // two per-list gathers made independently.
5745 let t = get2(pbx - 1, pby - 1);
5746 c0 = t.0;
5747 c1 = t.1;
5748 }
5749 ([a0, b0, c0], [a1, b1, c1])
5750 }
5751
5752 fn mv_neighbors_block_grid(
5753 &self,
5754 pbx: isize,
5755 pby: isize,
5756 pwb: isize,
5757 list: usize,
5758 ) -> [MvNeighbor; 3] {
5759 let (w4, h4) = ((self.mb_w * 4) as isize, (self.mb_h * 4) as isize);
5760 let (mvg, refg) = if list == 0 {
5761 (&self.mv_y, &self.ref_idx_y)
5762 } else {
5763 (&self.mv1, &self.ref_idx1)
5764 };
5765 let get = |bx: isize, by: isize| -> MvNeighbor {
5766 if bx < 0
5767 || by < 0
5768 || bx >= w4
5769 || by >= h4
5770 || !self
5771 .coded_y
5772 .get((by * w4 + bx) as usize)
5773 .copied()
5774 .unwrap_or(false)
5775 || !self.nbr_in_slice(bx as usize / 4, by as usize / 4)
5776 {
5777 MvNeighbor::NONE
5778 } else {
5779 let idx = (by * w4 + bx) as usize;
5780 match (mvg.get(idx), refg.get(idx)) {
5781 (Some(&m), Some(&r)) => MvNeighbor {
5782 available: true,
5783 mv: (i32::from(m.0), i32::from(m.1)),
5784 ref_idx: i32::from(r),
5785 },
5786 _ => MvNeighbor::NONE,
5787 }
5788 }
5789 };
5790 let a = get(pbx - 1, pby);
5791 let b = get(pbx, pby - 1);
5792 let mut c = get(pbx + pwb, pby - 1);
5793 if !c.available {
5794 c = get(pbx - 1, pby - 1);
5795 }
5796 [a, b, c]
5797 }
5798
5799 /// `colZeroFlag` for the 4×4 block at absolute block coords `(bx, by)`: true
5800 /// when `RefPicList1[0]` is a short-term picture whose co-located block uses
5801 /// reference 0 with a near-zero motion vector (spec §8.4.1.2.2).
5802 /// Co-located 4x4 block coords for the current block's `(bx4, by4)` within the
5803 /// macroblock, per spec 8.4.1.2.1. Under `direct_8x8_inference_flag` every 4x4
5804 /// in an 8x8 takes that 8x8's OUTER CORNER (`luma4x4BlkIdx = 5 * mbPartIdx`,
5805 /// i.e. (0,0) (3,0) (0,3) (3,3)); otherwise motion is genuinely per-4x4.
5806 ///
5807 /// 8.4.1.2.1 is SHARED by both direct modes, so spatial and temporal must map
5808 /// identically. They did not: temporal mapped the corner and spatial read the
5809 /// block's own coords, which is invisible while every 4x4 in the co-located 8x8
5810 /// carries the same motion -- true of every stream until sub-8x8 P partitions
5811 /// (x264 `--partitions p4x4`) make them differ. Hence one function.
5812 #[inline]
5813 fn col_block(&self, bx4: usize, by4: usize) -> (usize, usize) {
5814 if self.direct_8x8_inference {
5815 ((bx4 / 2) * 3, (by4 / 2) * 3)
5816 } else {
5817 (bx4, by4)
5818 }
5819 }
5820
5821 fn col_zero(&self, bx: usize, by: usize) -> bool {
5822 // Fast path (1T frozen colocated, the default): the invariant checks
5823 // were hoisted to set_b_context; two grid loads + the threshold test.
5824 if self.col_ok {
5825 let Some(col) = self.refs1.first() else {
5826 return false;
5827 };
5828 let idx = by * self.col_w4 + bx;
5829 // `.get` on EVERY parallel array, not a length test on one of them.
5830 // The `idx >= ref_idx.len()` guard proved nothing about `mv`,
5831 // `ref_idx1` or `mv1` — separate Vecs with independent lengths — so
5832 // each of those reads carried its own panic path. They are built
5833 // together and always agree; the fallible form states that instead of
5834 // asserting it, and keeps the "no panic on malformed input" contract.
5835 let (cref, cmv) = match col.ref_idx.get(idx) {
5836 Some(&r0) if r0 >= 0 => match col.mv.get(idx) {
5837 Some(&m) => (r0, m),
5838 None => return false,
5839 },
5840 Some(_) => match (col.ref_idx1.get(idx), col.mv1.get(idx)) {
5841 (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
5842 _ => return false,
5843 },
5844 None => return false,
5845 };
5846 return cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1;
5847 }
5848 let Some(col) = self.refs1.first() else {
5849 return false;
5850 };
5851 if let Some(live) = col.live.as_ref() {
5852 col.wait_motion_ready();
5853 let meta = live.meta.read_pf();
5854 if meta.long_term || meta.w4 == 0 {
5855 return false;
5856 }
5857 let idx = by * meta.w4 + bx;
5858 // Same `.get`-per-array shape as the frozen-colocated path above.
5859 let (cref, cmv) = match meta.ref_idx.get(idx) {
5860 Some(&r0) if r0 >= 0 => match meta.mv.get(idx) {
5861 Some(&m) => (r0, m),
5862 None => return false,
5863 },
5864 Some(_) => match (meta.ref_idx1.get(idx), meta.mv1.get(idx)) {
5865 (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
5866 _ => return false,
5867 },
5868 None => return false,
5869 };
5870 return cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1;
5871 }
5872 if col.long_term || col.w4 == 0 {
5873 return false;
5874 }
5875 let idx = by * col.w4 + bx;
5876 if idx >= col.ref_idx.len() {
5877 return false;
5878 }
5879 let (cref, cmv) = match col.ref_idx.get(idx) {
5880 Some(&r0) if r0 >= 0 => match col.mv.get(idx) {
5881 Some(&m) => (r0, m),
5882 None => return false,
5883 },
5884 Some(_) => match (col.ref_idx1.get(idx), col.mv1.get(idx)) {
5885 (Some(&r1), Some(&m)) if r1 >= 0 => (r1, m),
5886 _ => return false,
5887 },
5888 None => return false,
5889 };
5890 cref == 0 && cmv.0.abs() <= 1 && cmv.1.abs() <= 1
5891 }
5892
5893 /// Implicit bi-prediction weights `(w0, w1)` from POC distances (spec
5894 /// §8.4.2.3.2), or `None` for the plain average (idc≠2, uni-pred, or the
5895 /// equidistant / out-of-range fall-back to 32:32 which equals the average).
5896 fn implicit_weights(&self, refi0: i32, refi1: i32) -> Option<(i32, i32)> {
5897 if self.weighted_bipred_idc != 2 || refi0 < 0 || refi1 < 0 {
5898 return None;
5899 }
5900 let (Some(r0), Some(r1)) = (
5901 self.refs.get(refi0 as usize),
5902 self.refs1.get(refi1 as usize),
5903 ) else {
5904 return None;
5905 };
5906 let td = (r1.pic_poc() - r0.pic_poc()).clamp(-128, 127);
5907 let tb = (self.cur_poc - r0.pic_poc()).clamp(-128, 127);
5908 if td == 0 || r0.long_term || r1.long_term {
5909 return None; // 32:32 → identical to the average
5910 }
5911 let tx = tx_for_td(td);
5912 let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
5913 let w1 = dsf >> 2;
5914 if !(-64..=128).contains(&w1) {
5915 return None; // out of range → 32:32 average
5916 }
5917 Some((64 - w1, w1))
5918 }
5919
5920 /// Motion-compensates a region with the given per-list refs/MVs. Bi-prediction
5921 /// is the simple `(a+b+1)>>1` average, or POC-weighted when implicit weighting
5922 /// (idc 2) is active. Writes into `pred_y`/`c_pred`.
5923 fn b_mc(
5924 &self,
5925 mb_x: usize,
5926 mb_y: usize,
5927 px: usize,
5928 py: usize,
5929 rw: usize,
5930 rh: usize,
5931 refi0: i32,
5932 mv0: (i32, i32),
5933 refi1: i32,
5934 mv1: (i32, i32),
5935 pred_y: &mut [u8; 256],
5936 c_pred: &mut [[u8; 64]; 2],
5937 ) {
5938 let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBMc);
5939 // Malformed-stream armor, mirroring the P path: now that B slices actually
5940 // PARSE ref_idx (they used to be hardcoded to 0), a mutated stream can hand
5941 // us an index past the end of either list. Clamp rather than panic — the
5942 // crate is `forbid(unsafe_code)` and fuzz-gated to never panic, and a
5943 // wrong picture on garbage input carries no conformance duty.
5944 let refi0 = if refi0 >= 0 {
5945 (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32
5946 } else {
5947 -1
5948 };
5949 let refi1 = if refi1 >= 0 {
5950 (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32
5951 } else {
5952 -1
5953 };
5954 if (refi0 >= 0 && self.refs.is_empty()) || (refi1 >= 0 && self.refs1.is_empty()) {
5955 return;
5956 }
5957 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
5958 let weights = {
5959 let _gw = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBWeights);
5960 self.implicit_weights(refi0, refi1)
5961 };
5962 // Bi-prediction blend: the weights decision is LOOP-INVARIANT, so every
5963 // blend site below matches on `weights` ONCE and runs a branch-free
5964 // pixel loop — the unweighted `(p+q+1)>>1` average then autovectorizes
5965 // (the per-pixel closure this replaces hid the invariant behind a
5966 // capture, and its chroma form was a &dyn call PER PIXEL).
5967 // FULL-WIDTH regions (px == 0, rw == 16 — every 16×16/16×8 partition and
5968 // most direct regions) occupy contiguous rows of `pred_y`, so MC writes
5969 // the destination DIRECTLY: uni-pred needs no staging at all, bi-pred
5970 // stages only the second list and blends in place. The staging arrays
5971 // (512 B zeroed per call before this) now exist only on the branches
5972 // that read them. Same fusion as the P path's mc_rect (WHYS Part 8).
5973 let full = px == 0 && rw == 16;
5974 if refi0 >= 0
5975 && refi1 >= 0
5976 && mv0.0 % 4 == 0
5977 && mv0.1 % 4 == 0
5978 && mv1.0 % 4 == 0
5979 && mv1.1 % 4 == 0
5980 {
5981 edcstat::bump(&edcstat::BMC_BI_FP, 1);
5982 }
5983 let _gl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBLuma);
5984 // One scratch borrow for the whole region — both bi-pred passes included.
5985 // The closure yields whether the arm already ran the chroma half (the
5986 // bi-pred full-width arm does, to keep its staging alive) — a plain
5987 // `return` inside would exit the CLOSURE only and chroma would run twice.
5988 let chroma_done =
5989 rusty_h264_common::inter::with_mc_scratch(|scr| match (refi0 >= 0, refi1 >= 0, full) {
5990 (true, false, true) => {
5991 let Some(rf) = self.refs.get(refi0 as usize) else {
5992 return false;
5993 };
5994 rusty_h264_common::inter::mc_luma_padded_pre(
5995 scr,
5996 &rf.luma_guard(rf.ch),
5997 rf.lstride(),
5998 crate::LPAD,
5999 self.cw,
6000 ch,
6001 mb_x * 16,
6002 mb_y * 16 + py,
6003 rw,
6004 rh,
6005 mv0.0,
6006 mv0.1,
6007 &mut pred_y[py * 16..py * 16 + rw * rh],
6008 );
6009 false
6010 }
6011 (false, true, true) => {
6012 let Some(rf) = self.refs1.get(refi1 as usize) else {
6013 return false;
6014 };
6015 rusty_h264_common::inter::mc_luma_padded_pre(
6016 scr,
6017 &rf.luma_guard(rf.ch),
6018 rf.lstride(),
6019 crate::LPAD,
6020 self.cw,
6021 ch,
6022 mb_x * 16,
6023 mb_y * 16 + py,
6024 rw,
6025 rh,
6026 mv1.0,
6027 mv1.1,
6028 &mut pred_y[py * 16..py * 16 + rw * rh],
6029 );
6030 false
6031 }
6032 (true, true, true) => {
6033 let Some(rf) = self.refs.get(refi0 as usize) else {
6034 return false;
6035 };
6036 rusty_h264_common::inter::mc_luma_padded_pre(
6037 scr,
6038 &rf.luma_guard(rf.ch),
6039 rf.lstride(),
6040 crate::LPAD,
6041 self.cw,
6042 ch,
6043 mb_x * 16,
6044 mb_y * 16 + py,
6045 rw,
6046 rh,
6047 mv0.0,
6048 mv0.1,
6049 &mut pred_y[py * 16..py * 16 + rw * rh],
6050 );
6051 let mut b = [0u8; 256];
6052 let Some(rf) = self.refs1.get(refi1 as usize) else {
6053 return false;
6054 };
6055 rusty_h264_common::inter::mc_luma_padded_pre(
6056 scr,
6057 &rf.luma_guard(rf.ch),
6058 rf.lstride(),
6059 crate::LPAD,
6060 self.cw,
6061 ch,
6062 mb_x * 16,
6063 mb_y * 16 + py,
6064 rw,
6065 rh,
6066 mv1.0,
6067 mv1.1,
6068 &mut b[..rw * rh],
6069 );
6070 drop(_gl);
6071 let _gbl =
6072 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
6073 // SLICE-then-zip: proving the bounds ONCE lets rustc emit the whole
6074 // 256-byte average as 8 straight-line vpavgb ops (verified in
6075 // isolation, x86-64-v3); the indexed form kept a per-iteration
6076 // bounds check and a loop. A hand AVX2 kernel is refuted — the
6077 // compiler already emits the ideal instruction.
6078 let dst = &mut pred_y[py * 16..py * 16 + rw * rh];
6079 match weights {
6080 None => {
6081 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
6082 *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
6083 }
6084 }
6085 Some((w0, w1)) => {
6086 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
6087 *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255)
6088 as u8;
6089 }
6090 }
6091 }
6092 let _gc =
6093 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
6094 self.b_mc_chroma(
6095 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
6096 );
6097 true
6098 }
6099 _ => {
6100 // Narrow region — rows are strided in `pred_y`; stage and copy.
6101 let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
6102 if refi0 >= 0 {
6103 let Some(rf) = self.refs.get(refi0 as usize) else {
6104 return false;
6105 };
6106 rusty_h264_common::inter::mc_luma_padded_pre(
6107 scr,
6108 &rf.luma_guard(rf.ch),
6109 rf.lstride(),
6110 crate::LPAD,
6111 self.cw,
6112 ch,
6113 mb_x * 16 + px,
6114 mb_y * 16 + py,
6115 rw,
6116 rh,
6117 mv0.0,
6118 mv0.1,
6119 &mut a[..rw * rh],
6120 );
6121 }
6122 if refi1 >= 0 {
6123 let Some(rf) = self.refs1.get(refi1 as usize) else {
6124 return false;
6125 };
6126 rusty_h264_common::inter::mc_luma_padded_pre(
6127 scr,
6128 &rf.luma_guard(rf.ch),
6129 rf.lstride(),
6130 crate::LPAD,
6131 self.cw,
6132 ch,
6133 mb_x * 16 + px,
6134 mb_y * 16 + py,
6135 rw,
6136 rh,
6137 mv1.0,
6138 mv1.1,
6139 &mut b[..rw * rh],
6140 );
6141 }
6142 drop(_gl);
6143 let _gbl =
6144 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
6145 match (refi0 >= 0, refi1 >= 0) {
6146 (true, true) => {
6147 for dy in 0..rh {
6148 let (ar, br) =
6149 (&a[dy * rw..dy * rw + rw], &b[dy * rw..dy * rw + rw]);
6150 let base = (py + dy) * 16 + px;
6151 let dst = &mut pred_y[base..base + rw];
6152 match weights {
6153 None => {
6154 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
6155 *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
6156 }
6157 }
6158 Some((w0, w1)) => {
6159 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
6160 *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6)
6161 .clamp(0, 255)
6162 as u8;
6163 }
6164 }
6165 }
6166 }
6167 }
6168 (true, false) => {
6169 for dy in 0..rh {
6170 let d = (py + dy) * 16 + px;
6171 pred_y[d..d + rw].copy_from_slice(&a[dy * rw..dy * rw + rw]);
6172 }
6173 }
6174 _ => {
6175 for dy in 0..rh {
6176 let d = (py + dy) * 16 + px;
6177 pred_y[d..d + rw].copy_from_slice(&b[dy * rw..dy * rw + rw]);
6178 }
6179 }
6180 }
6181 false
6182 }
6183 });
6184 if chroma_done {
6185 return;
6186 }
6187 let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
6188 self.b_mc_chroma(
6189 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
6190 );
6191 }
6192
6193 /// Chroma half of `b_mc`, with the same full-width direct-write fusion
6194 /// (crw == 8 rows are contiguous in the 8-wide `c_pred` planes).
6195 /// Chroma half of `b_mc`. U and V share every piece of MC geometry, so
6196 /// each list is ONE `mc_chroma_padded_pair` call (setup + range check paid
6197 /// once, kernels unchanged) instead of two per-plane calls — the per-plane
6198 /// pixel math is byte-identical to the old per-plane flow.
6199 fn b_mc_chroma(
6200 &self,
6201 mb_x: usize,
6202 mb_y: usize,
6203 px: usize,
6204 py: usize,
6205 rw: usize,
6206 rh: usize,
6207 refi0: i32,
6208 mv0: (i32, i32),
6209 refi1: i32,
6210 mv1: (i32, i32),
6211 c_pred: &mut [[u8; 64]; 2],
6212 weights: Option<(i32, i32)>,
6213 cch: usize,
6214 ) {
6215 use rusty_h264_common::inter::mc_chroma_padded_pair;
6216 let (crx, cry, crw, crh) = (px / 2, py / 2, rw / 2, rh / 2);
6217 let full = crx == 0 && crw == 8;
6218 let n = crw * crh;
6219 // RESOLVE BOTH SLOTS ONCE, then let the match patterns bind them. Every
6220 // arm below re-indexed `refs`/`refs1` by a raw `refi` — six checked Vec
6221 // indexes across the four arms — although `a0`/`a1` already meant
6222 // exactly "this slot is present". Carrying `Option<&RefPic>` instead of
6223 // a bool makes "present" and "in range" the same fact.
6224 let rf0 = (refi0 >= 0)
6225 .then(|| self.refs.get(refi0 as usize))
6226 .flatten();
6227 let rf1 = (refi1 >= 0)
6228 .then(|| self.refs1.get(refi1 as usize))
6229 .flatten();
6230 let (a0, a1) = (rf0.is_some(), rf1.is_some());
6231 let [cu, cv] = c_pred;
6232 match (a0, a1, full) {
6233 (true, false, true) | (false, true, true) => {
6234 let (Some(rf), mv) = (if a0 { rf0 } else { rf1 }, if a0 { mv0 } else { mv1 })
6235 else {
6236 return;
6237 };
6238 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6239 mc_chroma_padded_pair(
6240 &gu,
6241 &gv,
6242 rf.cstride(),
6243 crate::CPAD,
6244 self.ccw,
6245 cch,
6246 mb_x * 8,
6247 mb_y * 8 + cry,
6248 crw,
6249 crh,
6250 mv.0,
6251 mv.1,
6252 &mut cu[cry * 8..cry * 8 + n],
6253 &mut cv[cry * 8..cry * 8 + n],
6254 );
6255 }
6256 (true, true, true) => {
6257 let (Some(rfa), Some(rfb)) = (rf0, rf1) else {
6258 return;
6259 };
6260 {
6261 let rf = rfa;
6262 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6263 mc_chroma_padded_pair(
6264 &gu,
6265 &gv,
6266 rf.cstride(),
6267 crate::CPAD,
6268 self.ccw,
6269 cch,
6270 mb_x * 8,
6271 mb_y * 8 + cry,
6272 crw,
6273 crh,
6274 mv0.0,
6275 mv0.1,
6276 &mut cu[cry * 8..cry * 8 + n],
6277 &mut cv[cry * 8..cry * 8 + n],
6278 );
6279 }
6280 let (mut bu, mut bv) = ([0u8; 64], [0u8; 64]);
6281 {
6282 let rf = rfb;
6283 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6284 mc_chroma_padded_pair(
6285 &gu,
6286 &gv,
6287 rf.cstride(),
6288 crate::CPAD,
6289 self.ccw,
6290 cch,
6291 mb_x * 8,
6292 mb_y * 8 + cry,
6293 crw,
6294 crh,
6295 mv1.0,
6296 mv1.1,
6297 &mut bu[..n],
6298 &mut bv[..n],
6299 );
6300 }
6301 for (dst, stage) in [(&mut *cu, &bu), (&mut *cv, &bv)] {
6302 let d = &mut dst[cry * 8..cry * 8 + n];
6303 match weights {
6304 None => {
6305 for (o, q) in d.iter_mut().zip(&stage[..n]) {
6306 *o = ((*o as u16 + *q as u16 + 1) >> 1) as u8;
6307 }
6308 }
6309 Some((w0, w1)) => {
6310 for (o, q) in d.iter_mut().zip(&stage[..n]) {
6311 *o = ((*o as i32 * w0 + *q as i32 * w1 + 32) >> 6).clamp(0, 255)
6312 as u8;
6313 }
6314 }
6315 }
6316 }
6317 }
6318 _ => {
6319 // Narrow region — rows are strided in the 8-wide pred planes;
6320 // stage per list (paired), then copy/blend strided per plane.
6321 let (mut au, mut av, mut bu, mut bv) = ([0u8; 64], [0u8; 64], [0u8; 64], [0u8; 64]);
6322 if let Some(rf) = rf0 {
6323 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6324 mc_chroma_padded_pair(
6325 &gu,
6326 &gv,
6327 rf.cstride(),
6328 crate::CPAD,
6329 self.ccw,
6330 cch,
6331 mb_x * 8 + crx,
6332 mb_y * 8 + cry,
6333 crw,
6334 crh,
6335 mv0.0,
6336 mv0.1,
6337 &mut au[..n],
6338 &mut av[..n],
6339 );
6340 }
6341 if let Some(rf) = rf1 {
6342 let (gu, gv) = (rf.chroma_guard(0, rf.ch), rf.chroma_guard(1, rf.ch));
6343 mc_chroma_padded_pair(
6344 &gu,
6345 &gv,
6346 rf.cstride(),
6347 crate::CPAD,
6348 self.ccw,
6349 cch,
6350 mb_x * 8 + crx,
6351 mb_y * 8 + cry,
6352 crw,
6353 crh,
6354 mv1.0,
6355 mv1.1,
6356 &mut bu[..n],
6357 &mut bv[..n],
6358 );
6359 }
6360 for (dst, sa, sb) in [(&mut *cu, &au, &bu), (&mut *cv, &av, &bv)] {
6361 for dy in 0..crh {
6362 let base = (cry + dy) * 8 + crx;
6363 let d = &mut dst[base..base + crw];
6364 match (a0, a1) {
6365 (true, true) => {
6366 let (pr, qr) =
6367 (&sa[dy * crw..dy * crw + crw], &sb[dy * crw..dy * crw + crw]);
6368 match weights {
6369 None => {
6370 for ((o, pp), q) in d.iter_mut().zip(pr).zip(qr) {
6371 *o = ((*pp as u16 + *q as u16 + 1) >> 1) as u8;
6372 }
6373 }
6374 Some((w0, w1)) => {
6375 for ((o, pp), q) in d.iter_mut().zip(pr).zip(qr) {
6376 *o = ((*pp as i32 * w0 + *q as i32 * w1 + 32) >> 6)
6377 .clamp(0, 255)
6378 as u8;
6379 }
6380 }
6381 }
6382 }
6383 (true, false) => d.copy_from_slice(&sa[dy * crw..dy * crw + crw]),
6384 _ => d.copy_from_slice(&sb[dy * crw..dy * crw + crw]),
6385 }
6386 }
6387 }
6388 }
6389 }
6390 }
6391
6392 /// Commits a region's per-list motion to the 4×4 grids (and marks coded).
6393 fn b_set_motion(
6394 &mut self,
6395 mb_x: usize,
6396 mb_y: usize,
6397 px: usize,
6398 py: usize,
6399 rw: usize,
6400 rh: usize,
6401 refi0: i32,
6402 mv0: (i32, i32),
6403 refi1: i32,
6404 mv1: (i32, i32),
6405 ) {
6406 let _gs = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBSet);
6407 let w4 = self.mb_w * 4;
6408 let mv0w = if refi0 >= 0 { mv0 } else { (0, 0) };
6409 let mv1w = if refi1 >= 0 { mv1 } else { (0, 0) };
6410 let by0 = mb_y * 4 + py / 4;
6411 let bx0 = mb_x * 4 + px / 4;
6412 let (bw, bh) = (rw / 4, rh / 4);
6413 // Contiguous per-row slices: fill instead of per-4x4 stores (same values).
6414 // SEVEN parallel grids, seven separate range checks — they are disjoint
6415 // fields of `self`, so one `get_mut` apiece resolves them all together
6416 // and a short row simply writes nothing rather than panicking.
6417 for by in by0..by0 + bh {
6418 let row = by * w4 + bx0;
6419 let end = row + bw;
6420 let (Some(r0), Some(m0), Some(r1), Some(m1)) = (
6421 self.ref_idx_y.get_mut(row..end),
6422 self.mv_y.get_mut(row..end),
6423 self.ref_idx1.get_mut(row..end),
6424 self.mv1.get_mut(row..end),
6425 ) else {
6426 continue;
6427 };
6428 r0.fill(refi0 as i8);
6429 m0.fill((mv0w.0 as i16, mv0w.1 as i16));
6430 r1.fill(refi1 as i8);
6431 m1.fill((mv1w.0 as i16, mv1w.1 as i16));
6432 let (Some(it), Some(cd), Some(md)) = (
6433 self.inter_y.get_mut(row..end),
6434 self.coded_y.get_mut(row..end),
6435 self.modes_y.get_mut(row..end),
6436 ) else {
6437 continue;
6438 };
6439 it.fill(true);
6440 cd.fill(true);
6441 md.fill(2);
6442 }
6443 }
6444
6445 /// Spatial direct prediction for a region (whole MB or an 8×8): derives the
6446 /// per-list reference indices and base MVs, then motion-compensates each 4×4
6447 /// sub-block (applying `colZeroFlag`) and commits the motion (spec §8.4.1.2.2).
6448 /// Splits a `w`×`h` block region (4×4-block units) into the fewest rectangles
6449 /// whose contents are `uniform`, preferring partition-shaped cuts (whole →
6450 /// horizontal halves → vertical halves → quadrants). Emits at most w·h rects
6451 /// (the all-different worst case degenerates to per-block, i.e. the old loop).
6452 fn coalesce_region<
6453 U: Fn(usize, usize, usize, usize) -> bool,
6454 E: FnMut(usize, usize, usize, usize),
6455 >(
6456 x: usize,
6457 y: usize,
6458 w: usize,
6459 h: usize,
6460 uniform: &U,
6461 emit: &mut E,
6462 ) {
6463 if uniform(x, y, w, h) {
6464 emit(x, y, w, h);
6465 return;
6466 }
6467 if h > 1 && uniform(x, y, w, h / 2) && uniform(x, y + h / 2, w, h / 2) {
6468 emit(x, y, w, h / 2);
6469 emit(x, y + h / 2, w, h / 2);
6470 return;
6471 }
6472 if w > 1 && uniform(x, y, w / 2, h) && uniform(x + w / 2, y, w / 2, h) {
6473 emit(x, y, w / 2, h);
6474 emit(x + w / 2, y, w / 2, h);
6475 return;
6476 }
6477 match (w > 1, h > 1) {
6478 (true, true) => {
6479 for q in 0..4usize {
6480 Self::coalesce_region(
6481 x + (q % 2) * (w / 2),
6482 y + (q / 2) * (h / 2),
6483 w / 2,
6484 h / 2,
6485 uniform,
6486 emit,
6487 );
6488 }
6489 }
6490 (true, false) => {
6491 Self::coalesce_region(x, y, w / 2, h, uniform, emit);
6492 Self::coalesce_region(x + w / 2, y, w / 2, h, uniform, emit);
6493 }
6494 (false, true) => {
6495 Self::coalesce_region(x, y, w, h / 2, uniform, emit);
6496 Self::coalesce_region(x, y + h / 2, w, h / 2, uniform, emit);
6497 }
6498 (false, false) => emit(x, y, 1, 1),
6499 }
6500 }
6501
6502 fn decode_b_direct(
6503 &mut self,
6504 mb_x: usize,
6505 mb_y: usize,
6506 px: usize,
6507 py: usize,
6508 rw: usize,
6509 rh: usize,
6510 pred_y: &mut [u8; 256],
6511 c_pred: &mut [[u8; 64]; 2],
6512 ) {
6513 if !self.direct_spatial {
6514 return self.decode_b_direct_temporal(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred);
6515 }
6516 let (n0, n1) = self.b_direct_nbrs(mb_x, mb_y);
6517 self.decode_b_direct_n(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred, n0, n1);
6518 }
6519
6520 /// Spec §8.4.1.2.2: the spatial-direct reference indices (min positive over
6521 /// the three neighbors, per list) and the predicted MVs. `direct_zero` is
6522 /// the both-lists-unavailable case, which forces ref 0 / mv (0,0). Shared
6523 /// by `decode_b_direct_n` and the B_Skip zero-bi fast path — ONE derivation,
6524 /// no drift.
6525 #[inline]
6526 fn b_direct_refs_mvs(
6527 n0: &[MvNeighbor; 3],
6528 n1: &[MvNeighbor; 3],
6529 ) -> (i32, i32, (i32, i32), (i32, i32), bool) {
6530 let min_pos = |a: i32, b: i32| {
6531 if a < 0 {
6532 b
6533 } else if b < 0 {
6534 a
6535 } else {
6536 a.min(b)
6537 }
6538 };
6539 let rid = |n: &[MvNeighbor; 3]| min_pos(min_pos(n[0].ref_idx, n[1].ref_idx), n[2].ref_idx);
6540 let (mut refi0, mut refi1) = (rid(n0), rid(n1));
6541 let direct_zero = refi0 < 0 && refi1 < 0;
6542 if direct_zero {
6543 refi0 = 0;
6544 refi1 = 0;
6545 }
6546 let mv0 = if refi0 >= 0 && !direct_zero {
6547 predict_mv(n0[0], n0[1], n0[2], refi0)
6548 } else {
6549 (0, 0)
6550 };
6551 let mv1 = if refi1 >= 0 && !direct_zero {
6552 predict_mv(n1[0], n1[1], n1[2], refi1)
6553 } else {
6554 (0, 0)
6555 };
6556 (refi0, refi1, mv0, mv1, direct_zero)
6557 }
6558
6559 fn decode_b_direct_n(
6560 &mut self,
6561 mb_x: usize,
6562 mb_y: usize,
6563 px: usize,
6564 py: usize,
6565 rw: usize,
6566 rh: usize,
6567 pred_y: &mut [u8; 256],
6568 c_pred: &mut [[u8; 64]; 2],
6569 n0: [MvNeighbor; 3],
6570 n1: [MvNeighbor; 3],
6571 ) {
6572 let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBDirect);
6573 // H-48: DERIVATION-ONLY scope, dropped before the MC loop below. DecBDirect
6574 // wraps this function whole and therefore INCLUDES the `b_mc` calls it makes,
6575 // so its 1460 ns/call was never "MV derivation is slow" — that read was wrong.
6576 // This guard is what separates the two.
6577 let gd = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBDeriv);
6578 let derived = Self::b_direct_refs_mvs(&n0, &n1);
6579 drop(gd);
6580 self.b_direct_region(mb_x, mb_y, px, py, rw, rh, pred_y, c_pred, derived);
6581 }
6582
6583 /// The post-derivation half of spatial direct: czg probing (gated), region
6584 /// coalescing, MC + motion commit. Split out so B_8x8 direct subs derive
6585 /// ONCE per MB (the A/B/C neighbours and the rid/median result are
6586 /// MB-level; only czg varies per 8x8).
6587 fn b_direct_region(
6588 &mut self,
6589 mb_x: usize,
6590 mb_y: usize,
6591 px: usize,
6592 py: usize,
6593 rw: usize,
6594 rh: usize,
6595 pred_y: &mut [u8; 256],
6596 c_pred: &mut [[u8; 64]; 2],
6597 derived: (i32, i32, (i32, i32), (i32, i32), bool),
6598 ) {
6599 let (refi0, refi1, mv0, mv1, direct_zero) = derived;
6600 // Per 4×4 sub-block: colZeroFlag zeroes the ref-0 motion vector. cz is the
6601 // ONLY per-block variable (two possible (m0,m1) values for the region), and
6602 // the MC filters + bi-blend are per-output-pixel — so sub-blocks with equal
6603 // cz coalesce into one wider `b_mc`, BIT-IDENTICAL. A 16×16 direct MB paid
6604 // 16 bi-pred b_mc calls (~96 MC kernel entries) before this; typically 1 now.
6605 let (bx0, by0, bw, bh) = (px / 4, py / 4, rw / 4, rh / 4);
6606 // MASKED at every use below. The loop bounds are the region's `bw`/`bh` in
6607 // 4x4 units, which are always <= 4 for a macroblock but are RUNTIME values,
6608 // so none of the eight indexes into this fixed 4x4 grid was provable.
6609 let mut czg = [[false; 4]; 4]; // region-local, [dy][dx]
6610 // colZeroFlag can only change a list whose ref is 0 AND whose predicted
6611 // MV is nonzero (it zeroes MVs; zeroing (0,0) is a no-op). When neither
6612 // list qualifies, skip the probing entirely: czg stays false, the
6613 // region is uniform, and the m values are identical — the only change
6614 // is FEWER b_mc calls where the old czg would have split rects with
6615 // equal values (tiles of the same math, byte-identical).
6616 let cz_matters = (refi0 == 0 && mv0 != (0, 0)) || (refi1 == 0 && mv1 != (0, 0));
6617 // Uniformity is KNOWN after the probe fill — when every probe agreed,
6618 // the 16-bool coalesce scan and its recursion are skipped outright.
6619 let mut cz_mixed = false;
6620 if cz_matters && !direct_zero && self.direct_8x8_inference {
6621 // Under direct_8x8_inference every 4×4 in an 8×8 shares one colZeroFlag
6622 // (col_block collapses to the MB-corner). Probe once per 8×8 — same
6623 // czg, fewer wait_motion_ready / meta locks on the live-ref path.
6624 let mut oy = 0usize;
6625 while oy < bh {
6626 let h = (bh - oy).min(2);
6627 let mut ox = 0usize;
6628 while ox < bw {
6629 let w = (bw - ox).min(2);
6630 let (colx, coly) = self.col_block(bx0 + ox, by0 + oy);
6631 let cz = self.col_zero(mb_x * 4 + colx, mb_y * 4 + coly);
6632 cz_mixed |= cz != czg[0][0] && !(ox == 0 && oy == 0);
6633 for dy in oy..oy + h {
6634 for dx in ox..ox + w {
6635 czg[dy & 3][dx & 3] = cz;
6636 }
6637 }
6638 ox += w;
6639 }
6640 oy += h;
6641 }
6642 } else if cz_matters && !direct_zero {
6643 for dy in 0..bh {
6644 for dx in 0..bw {
6645 let (colx, coly) = self.col_block(bx0 + dx, by0 + dy);
6646 let cz = self.col_zero(mb_x * 4 + colx, mb_y * 4 + coly);
6647 cz_mixed |= cz != czg[0][0] && !(dx == 0 && dy == 0);
6648 czg[dy & 3][dx & 3] = cz;
6649 }
6650 }
6651 }
6652 let mut rects: [(usize, usize, usize, usize); 16] = [(0, 0, 0, 0); 16];
6653 let mut n = 0usize;
6654 if !cz_mixed {
6655 rects[0] = (0, 0, bw, bh);
6656 n = 1;
6657 } else {
6658 let uniform = |x: usize, y: usize, w: usize, h: usize| -> bool {
6659 let t = czg[y & 3][x & 3];
6660 (y..y + h).all(|dy| (x..x + w).all(|dx| czg[dy & 3][dx & 3] == t))
6661 };
6662 Self::coalesce_region(0, 0, bw, bh, &uniform, &mut |x, y, w, h| {
6663 rects[n & 15] = (x, y, w, h);
6664 n += 1;
6665 });
6666 }
6667 let recording = self.edc_regions.is_some();
6668 if rw == 16 && rh == 16 {
6669 edcstat::bump(&edcstat::BSK_FULLMB, 1);
6670 if n == 1 {
6671 edcstat::bump(&edcstat::BSK_1RECT, 1);
6672 // WHOLE-MACROBLOCK, SINGLE RECTANGLE: `derived` is one motion set
6673 // for the region, `cz` is the only per-block variable, and `n == 1`
6674 // means every block agreed on it. So all sixteen blocks share one
6675 // (ref0, ref1, mv0, mv1) -- exactly what `mb_uniform` scans six
6676 // motion planes to establish. Record it and the derivation skips
6677 // that kernel. Asserted against the kernel on the whole corpus by
6678 // bench/verify_hint_sweep.sh.
6679 if let Some(p) = self.mb_umot.get_mut(mb_y * self.mb_w + mb_x) {
6680 *p = true;
6681 }
6682 let cz = czg[0][0];
6683 let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
6684 let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
6685 let (a0, a1) = (refi0 >= 0, refi1 >= 0);
6686 if a0 && a1 && m0 == (0, 0) && m1 == (0, 0) {
6687 edcstat::bump(&edcstat::BSK_ZBI, 1);
6688 } else if (a0 != a1) && (if a0 { m0 } else { m1 }) == (0, 0) {
6689 edcstat::bump(&edcstat::BSK_ZUNI, 1);
6690 } else if (!a0 || (m0.0 % 4 == 0 && m0.1 % 4 == 0))
6691 && (!a1 || (m1.0 % 4 == 0 && m1.1 % 4 == 0))
6692 {
6693 edcstat::bump(&edcstat::BSK_FP, 1);
6694 }
6695 if self.bsk_last == Some((mb_x.wrapping_sub(1), mb_y, refi0, refi1, m0, m1)) {
6696 edcstat::bump(&edcstat::BSK_RUNCONT, 1);
6697 }
6698 self.bsk_last = Some((mb_x, mb_y, refi0, refi1, m0, m1));
6699 } else {
6700 self.bsk_last = None;
6701 }
6702 }
6703 for &(x, y, w, h) in &rects[..n] {
6704 let cz = czg[y & 3][x & 3];
6705 let m0 = if refi0 == 0 && cz { (0, 0) } else { mv0 };
6706 let m1 = if refi1 == 0 && cz { (0, 0) } else { mv1 };
6707 let (lx, ly, lw, lh) = ((bx0 + x) * 4, (by0 + y) * 4, w * 4, h * 4);
6708 if recording {
6709 self.b_mc_or_record(
6710 mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1, pred_y, c_pred,
6711 );
6712 } else {
6713 self.b_mc(
6714 mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1, pred_y, c_pred,
6715 );
6716 }
6717 self.b_set_motion(mb_x, mb_y, lx, ly, lw, lh, refi0, m0, refi1, m1);
6718 }
6719 }
6720
6721 /// Temporal direct prediction for a region (spec §8.4.1.2.3): for each 4×4
6722 /// (or per-8×8 corner under `direct_8x8_inference`), take the co-located
6723 /// List-0 motion from `RefPicList1[0]`, map its reference into the current
6724 /// List-0 by POC, and scale the motion vector by the POC distances.
6725 fn decode_b_direct_temporal(
6726 &mut self,
6727 mb_x: usize,
6728 mb_y: usize,
6729 px: usize,
6730 py: usize,
6731 rw: usize,
6732 rh: usize,
6733 pred_y: &mut [u8; 256],
6734 c_pred: &mut [[u8; 64]; 2],
6735 ) {
6736 let poc1 = self.refs1.first().map_or(0, |f| f.pic_poc());
6737 let infer = self.direct_8x8_inference;
6738 // Under direct_8x8_inference every 4×4 in an 8×8 takes the same MB-corner
6739 // co-located motion, so motion-compensate the whole 8×8 in one call — this
6740 // hits the width-8 MC asm and pays the per-call tile/blend setup 4× less.
6741 // Without inference, motion is genuinely per-4×4. Bit-identical either way
6742 // (MC of an 8×8 with one MV == four 4×4 MCs with that same MV).
6743 let step = if infer { 8 } else { 4 };
6744 let mut sy = py;
6745 while sy < py + rh {
6746 let mut sx = px;
6747 while sx < px + rw {
6748 // Co-located 4×4 (the 8×8's MB-corner under inference) — shared with
6749 // the spatial path's colZeroFlag, which must map identically.
6750 let (colx, coly) = self.col_block(sx / 4, sy / 4);
6751 let (mvcol, refpoc) = {
6752 let Some(col) = self.refs1.first() else {
6753 return;
6754 };
6755 if let Some(live) = col.live.as_ref() {
6756 col.wait_motion_ready();
6757 let meta = live.meta.read_pf();
6758 let idx = (mb_y * 4 + coly) * meta.w4 + (mb_x * 4 + colx);
6759 // `mv` and `ref_poc` are PARALLEL Vecs: the `mv.len()`
6760 // guard said nothing about `ref_poc`.
6761 // `ref_poc[idx]` was `poc_lut[ref_idx[idx]]` materialised
6762 // per block; do the lookup instead. The bounds test is the
6763 // same one the expansion applied when it was built, so an
6764 // out-of-range index still reads i32::MIN.
6765 match (meta.mv.get(idx), meta.ref_idx.get(idx)) {
6766 (Some(&m), Some(&r)) if meta.w4 != 0 => {
6767 let pc = if (0..32).contains(&r) {
6768 meta.poc_lut[r as usize & 31]
6769 } else {
6770 i32::MIN
6771 };
6772 if pc != i32::MIN {
6773 (m, pc)
6774 } else {
6775 ((0, 0), i32::MIN)
6776 }
6777 }
6778 _ => ((0, 0), i32::MIN),
6779 }
6780 } else {
6781 let idx = (mb_y * 4 + coly) * col.w4 + (mb_x * 4 + colx);
6782 // intra co-located → zero motion, refIdxL0 = 0
6783 match (col.mv.get(idx), col.ref_idx.get(idx)) {
6784 (Some(&m), Some(&r)) if col.w4 != 0 => {
6785 let pc = if (0..32).contains(&r) {
6786 col.poc_lut[r as usize & 31]
6787 } else {
6788 i32::MIN
6789 };
6790 if pc != i32::MIN {
6791 (m, pc)
6792 } else {
6793 ((0, 0), i32::MIN)
6794 }
6795 }
6796 _ => ((0, 0), i32::MIN),
6797 }
6798 }
6799 };
6800 // MapColToList0: the current-list index of the co-located reference.
6801 let (refi0, mvc) = if refpoc == i32::MIN {
6802 (0, (0, 0))
6803 } else {
6804 let r = self
6805 .refs
6806 .iter()
6807 .position(|f| f.pic_poc() == refpoc)
6808 .unwrap_or(0) as i32;
6809 (r, mvcol)
6810 };
6811 let Some(r0) = self.refs.get(refi0 as usize) else {
6812 return;
6813 };
6814 let poc0 = r0.pic_poc();
6815 let td = (poc1 - poc0).clamp(-128, 127);
6816 let tb = (self.cur_poc - poc0).clamp(-128, 127);
6817 let (mv0, mv1) = if td == 0 || r0.long_term {
6818 ((i32::from(mvc.0), i32::from(mvc.1)), (0, 0))
6819 } else {
6820 let tx = tx_for_td(td);
6821 let dsf = ((tb * tx + 32) >> 6).clamp(-1024, 1023);
6822 let m0 = ((dsf * i32::from(mvc.0) + 128) >> 8, (dsf * i32::from(mvc.1) + 128) >> 8);
6823 (m0, (m0.0 - i32::from(mvc.0), m0.1 - i32::from(mvc.1)))
6824 };
6825 self.b_mc_or_record(
6826 mb_x, mb_y, sx, sy, step, step, refi0, mv0, 0, mv1, pred_y, c_pred,
6827 );
6828 self.b_set_motion(mb_x, mb_y, sx, sy, step, step, refi0, mv0, 0, mv1);
6829 sx += step;
6830 }
6831 sy += step;
6832 }
6833 }
6834
6835 /// Reads `ref_idx_lX` for a B partition (te(v)/ue(v) by the list's active
6836 /// count), bounds-checked against the available reference count.
6837 fn read_b_ref(&self, r: &mut BitReader, list: usize) -> Result<i32, MbError> {
6838 let (active, avail) = if list == 0 {
6839 (self.num_ref_active, self.refs.len())
6840 } else {
6841 (self.num_ref_active1, self.refs1.len())
6842 };
6843 let v = if active > 1 {
6844 read_ref_idx(r, active)?
6845 } else {
6846 0
6847 };
6848 if v as usize >= avail {
6849 return Err(MbError::Truncated);
6850 }
6851 Ok(v)
6852 }
6853
6854 /// Reconstructs a `B_Skip` macroblock: spatial-direct prediction, no residual.
6855 /// Defer one fast B_Skip's grid commit + recon into the pending row span.
6856 /// Continuation requires position adjacency AND kind equality.
6857 #[inline]
6858 fn bz_push(&mut self, mb_x: usize, mb_y: usize, kind: BzKind) {
6859 // EVERY `BzKind` IS A UNIFORM WHOLE-MACROBLOCK MOTION SET, by its own
6860 // definition: `ZeroBi` is ref 0 in both lists at (0,0), `ZeroUni` is one
6861 // active list at (0,0), and `Fp` is the uniform full-pel direct case. So
6862 // the sixteen blocks of any macroblock queued here agree in all six motion
6863 // planes -- exactly what the deblock derivation's `mb_uniform` kernel
6864 // scans 256 bytes to establish. Marked at the single push site so no
6865 // caller can add a fourth kind and silently miss it.
6866 //
6867 // This is the class the B-skip band path carries, and on a talking-head
6868 // stream it is the LARGE one: FourPeople routes 404,755 macroblocks
6869 // through here against 24,525 through the direct-region marking.
6870 if let Some(p) = self.mb_umot.get_mut(mb_y * self.mb_w + mb_x) {
6871 *p = true;
6872 }
6873 match self.bzspan {
6874 Some((row, x0, ref mut n, k)) if row == mb_y && x0 + *n == mb_x && k == kind => *n += 1,
6875 _ => {
6876 self.bz_flush();
6877 self.bzspan = Some((mb_y, mb_x, 1, kind));
6878 }
6879 }
6880 }
6881
6882 /// Range-fill the pending span's grids. MUST run before anything reads
6883 /// the motion/coded/mode/nnz grids of a deferred MB: decode_b_mb entry,
6884 /// decode_b_skip's b_direct_nbrs, bS derivation (derive_bs_row callers
6885 /// via row_hook + deblock), and slice end (edc_flush covers it).
6886 #[inline(always)]
6887 fn bz_flush(&mut self) {
6888 if self.bzspan.is_some() {
6889 self.bz_flush_slow();
6890 }
6891 }
6892
6893 fn bz_flush_slow(&mut self) {
6894 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSpanRecon);
6895 let Some((row, x0, n, kind)) = self.bzspan.take() else {
6896 return;
6897 };
6898 edcstat::bump(&edcstat::BZ_SPANS, 1);
6899 edcstat::bump(&edcstat::BZ_SPAN_MBS, n as u64);
6900 // Grid values per kind (mirrors b_set_motion's inactive-list zeroing).
6901 let (g_r0, g_r1, g_m0, g_m1): (i32, i32, (i32, i32), (i32, i32)) = match kind {
6902 BzKind::ZeroBi => (0, 0, (0, 0), (0, 0)),
6903 BzKind::ZeroUni(0, ri) => (ri as i32, -1, (0, 0), (0, 0)),
6904 BzKind::ZeroUni(_, ri) => (-1, ri as i32, (0, 0), (0, 0)),
6905 BzKind::Fp { r0, r1, m0, m1 } => {
6906 let mm0 = if r0 >= 0 {
6907 (m0.0 as i32, m0.1 as i32)
6908 } else {
6909 (0, 0)
6910 };
6911 let mm1 = if r1 >= 0 {
6912 (m1.0 as i32, m1.1 as i32)
6913 } else {
6914 (0, 0)
6915 };
6916 (r0 as i32, r1 as i32, mm0, mm1)
6917 }
6918 };
6919 let w4 = self.mb_w * 4;
6920 let (b0, len) = (x0 * 4, n * 4);
6921 for dy in 0..4 {
6922 let a = (row * 4 + dy) * w4 + b0;
6923 self.ref_idx_y[a..a + len].fill(g_r0 as i8);
6924 self.mv_y[a..a + len].fill((g_m0.0 as i16, g_m0.1 as i16));
6925 self.ref_idx1[a..a + len].fill(g_r1 as i8);
6926 self.mv1[a..a + len].fill((g_m1.0 as i16, g_m1.1 as i16));
6927 self.inter_y[a..a + len].fill(true);
6928 self.coded_y[a..a + len].fill(true);
6929 self.modes_y[a..a + len].fill(2);
6930 self.nnz_y[a..a + len].fill(0);
6931 }
6932 match kind {
6933 BzKind::ZeroBi => self.bz_recon_band_bi(row, x0, n),
6934 BzKind::ZeroUni(list, ri) => {
6935 self.bz_recon_band_copy(row, x0, n, list as usize, ri as usize, (0, 0))
6936 }
6937 BzKind::Fp { r0, r1, m0, m1 } => {
6938 let mv0 = (m0.0 as i32, m0.1 as i32);
6939 let mv1 = (m1.0 as i32, m1.1 as i32);
6940 match (r0 >= 0, r1 >= 0) {
6941 (true, true) => {
6942 self.bz_recon_band_fp_bi(row, x0, n, r0 as usize, r1 as usize, mv0, mv1)
6943 }
6944 (true, false) => self.bz_recon_band_copy(row, x0, n, 0, r0 as usize, mv0),
6945 _ => self.bz_recon_band_copy(row, x0, n, 1, r1 as usize, mv1),
6946 }
6947 }
6948 }
6949 }
6950
6951 /// ZeroBi band: rows of 16n averaged from both padded refs (index 0).
6952 fn bz_recon_band_bi(&mut self, row: usize, x0: usize, n: usize) {
6953 // Byte-identical to n recon_b_skip_zero_bi(_, _, 0, 0) calls: each
6954 // output byte is the same (a + b + 1) >> 1 of the same source bytes.
6955 let w = n * 16;
6956 let Some(rf0) = self.refs.first() else { return };
6957 let Some(rf1) = self.refs1.first() else {
6958 return;
6959 };
6960 let (l0st, l1st) = (rf0.lstride(), rf1.lstride());
6961 {
6962 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
6963 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
6964 for dy in 0..16 {
6965 let y = row * 16 + dy;
6966 let s0 = (y + crate::LPAD) * l0st + crate::LPAD + x0 * 16;
6967 let s1 = (y + crate::LPAD) * l1st + crate::LPAD + x0 * 16;
6968 let d = y * self.cw + x0 * 16;
6969 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
6970 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
6971 // span in kernel-legal widths.
6972 rusty_h264_common::inter::avg_row_into(
6973 &ly0[s0..s0 + w],
6974 &ly1[s1..s1 + w],
6975 w,
6976 &mut self.rec_y[d..d + w],
6977 );
6978 }
6979 }
6980 let (c0st, c1st) = (rf0.cstride(), rf1.cstride());
6981 let wc = n * 8;
6982 for c in 0..2 {
6983 let rc0 = rf0.chroma_guard(c, rf0.ch);
6984 let rc1 = rf1.chroma_guard(c, rf1.ch);
6985 let plane = if c == 0 {
6986 &mut self.rec_u
6987 } else {
6988 &mut self.rec_v
6989 };
6990 for dy in 0..8 {
6991 let y = row * 8 + dy;
6992 let s0 = (y + crate::CPAD) * c0st + crate::CPAD + x0 * 8;
6993 let s1 = (y + crate::CPAD) * c1st + crate::CPAD + x0 * 8;
6994 let d = y * self.ccw + x0 * 8;
6995 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
6996 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
6997 // span in kernel-legal widths.
6998 rusty_h264_common::inter::avg_row_into(
6999 &rc0[s0..s0 + wc],
7000 &rc1[s1..s1 + wc],
7001 wc,
7002 &mut plane[d..d + wc],
7003 );
7004 }
7005 }
7006 }
7007
7008 /// Full-pel offset copy band from ONE list's padded planes. The caller
7009 /// prevalidated (per MB, at push): mv%8 == 0 both components and luma +
7010 /// chroma windows inside the padded planes — contiguous MB windows tile,
7011 /// so the span's union window is valid by induction.
7012 fn bz_recon_band_copy(
7013 &mut self,
7014 row: usize,
7015 x0: usize,
7016 n: usize,
7017 list: usize,
7018 ri: usize,
7019 mv: (i32, i32),
7020 ) {
7021 let Some(rf) = (if list == 0 {
7022 self.refs.get(ri)
7023 } else {
7024 self.refs1.get(ri)
7025 }) else {
7026 return;
7027 };
7028 let lst = rf.lstride();
7029 let (dx, dy) = ((mv.0 / 4) as isize, (mv.1 / 4) as isize);
7030 let w = n * 16;
7031 {
7032 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
7033 let ly = rf.luma_guard(rf.ch);
7034 for r in 0..16 {
7035 let y = (row * 16 + r) as isize + dy;
7036 let src = ((y + crate::LPAD as isize) * lst as isize
7037 + crate::LPAD as isize
7038 + x0 as isize * 16
7039 + dx) as usize;
7040 let d = (row * 16 + r) * self.cw + x0 * 16;
7041 self.rec_y[d..d + w].copy_from_slice(&ly[src..src + w]);
7042 }
7043 }
7044 let cst = rf.cstride();
7045 let (cdx, cdy) = ((mv.0 / 8) as isize, (mv.1 / 8) as isize);
7046 let wc = n * 8;
7047 for c in 0..2 {
7048 let rc = rf.chroma_guard(c, rf.ch);
7049 let plane = if c == 0 {
7050 &mut self.rec_u
7051 } else {
7052 &mut self.rec_v
7053 };
7054 for r in 0..8 {
7055 let y = (row * 8 + r) as isize + cdy;
7056 let src = ((y + crate::CPAD as isize) * cst as isize
7057 + crate::CPAD as isize
7058 + x0 as isize * 8
7059 + cdx) as usize;
7060 let d = (row * 8 + r) * self.ccw + x0 * 8;
7061 plane[d..d + wc].copy_from_slice(&rc[src..src + wc]);
7062 }
7063 }
7064 }
7065
7066 /// Full-pel bi band: rows of 16n averaged from two offset windows
7067 /// (prevalidated as above; implicit weights None/(32,32) guaranteed by
7068 /// the pushing arm).
7069 fn bz_recon_band_fp_bi(
7070 &mut self,
7071 row: usize,
7072 x0: usize,
7073 n: usize,
7074 r0: usize,
7075 r1: usize,
7076 mv0: (i32, i32),
7077 mv1: (i32, i32),
7078 ) {
7079 let (Some(rf0), Some(rf1)) = (self.refs.get(r0), self.refs1.get(r1)) else {
7080 return;
7081 };
7082 let (l0, l1) = (rf0.lstride(), rf1.lstride());
7083 let off = |st: usize, pad: isize, yy: isize, xx: isize| {
7084 (((yy + pad) * st as isize) + pad + xx) as usize
7085 };
7086 let w = n * 16;
7087 {
7088 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
7089 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
7090 for r in 0..16 {
7091 let y = (row * 16 + r) as isize;
7092 let s0 = off(
7093 l0,
7094 crate::LPAD as isize,
7095 y + (mv0.1 / 4) as isize,
7096 x0 as isize * 16 + (mv0.0 / 4) as isize,
7097 );
7098 let s1 = off(
7099 l1,
7100 crate::LPAD as isize,
7101 y + (mv1.1 / 4) as isize,
7102 x0 as isize * 16 + (mv1.0 / 4) as isize,
7103 );
7104 let d = (row * 16 + r) * self.cw + x0 * 16;
7105 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
7106 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
7107 // span in kernel-legal widths.
7108 rusty_h264_common::inter::avg_row_into(
7109 &ly0[s0..s0 + w],
7110 &ly1[s1..s1 + w],
7111 w,
7112 &mut self.rec_y[d..d + w],
7113 );
7114 }
7115 }
7116 let (c0, c1) = (rf0.cstride(), rf1.cstride());
7117 let wc = n * 8;
7118 for c in 0..2 {
7119 let rc0 = rf0.chroma_guard(c, rf0.ch);
7120 let rc1 = rf1.chroma_guard(c, rf1.ch);
7121 let plane = if c == 0 {
7122 &mut self.rec_u
7123 } else {
7124 &mut self.rec_v
7125 };
7126 for r in 0..8 {
7127 let y = (row * 8 + r) as isize;
7128 let s0 = off(
7129 c0,
7130 crate::CPAD as isize,
7131 y + (mv0.1 / 8) as isize,
7132 x0 as isize * 8 + (mv0.0 / 8) as isize,
7133 );
7134 let s1 = off(
7135 c1,
7136 crate::CPAD as isize,
7137 y + (mv1.1 / 8) as isize,
7138 x0 as isize * 8 + (mv1.0 / 8) as isize,
7139 );
7140 let d = (row * 8 + r) * self.ccw + x0 * 8;
7141 // WIN: this bi-average was a scalar byte loop; `pixel_avg` computes
7142 // (a + b + 1) >> 1 exactly with pavgb, and the row helper walks the
7143 // span in kernel-legal widths.
7144 rusty_h264_common::inter::avg_row_into(
7145 &rc0[s0..s0 + wc],
7146 &rc1[s1..s1 + wc],
7147 wc,
7148 &mut plane[d..d + wc],
7149 );
7150 }
7151 }
7152 }
7153
7154 /// Per-MB prevalidation for an Fp span push: mv%8 both components (chroma
7155 /// copies at mv/8) and luma + chroma windows inside the padded planes for
7156 /// every active list. False ⇒ the arm recons immediately instead.
7157 fn bz_fp_valid(
7158 &self,
7159 mbx: usize,
7160 mby: usize,
7161 r0: Option<usize>,
7162 r1: Option<usize>,
7163 mv0: (i32, i32),
7164 mv1: (i32, i32),
7165 ) -> bool {
7166 let chk = |rf: &crate::Ref, mv: (i32, i32)| -> bool {
7167 if mv.0 % 8 != 0 || mv.1 % 8 != 0 {
7168 return false;
7169 }
7170 let lst = rf.lstride();
7171 let lpad = crate::LPAD as isize;
7172 let cx = mbx as isize * 16 + (mv.0 / 4) as isize + lpad;
7173 let cy = mby as isize * 16 + (mv.1 / 4) as isize + lpad;
7174 let lrows = rf.lrows() as isize;
7175 if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
7176 return false;
7177 }
7178 let cst = rf.cstride();
7179 let cpad = crate::CPAD as isize;
7180 let ccx = mbx as isize * 8 + (mv.0 / 8) as isize + cpad;
7181 let ccy = mby as isize * 8 + (mv.1 / 8) as isize + cpad;
7182 let crows = rf.crows() as isize;
7183 ccx >= 0 && ccx + 8 <= cst as isize && ccy >= 0 && ccy + 8 <= crows
7184 };
7185 r0.is_none_or(|i| self.refs.get(i).is_some_and(|r| chk(r, mv0)))
7186 && r1.is_none_or(|i| self.refs1.get(i).is_some_and(|r| chk(r, mv1)))
7187 }
7188
7189 /// Defer one (0,0) P_Skip's grid commit into the pending P span.
7190 #[inline]
7191 fn pz_push(&mut self, mb_x: usize, mb_y: usize, recon: bool) {
7192 match self.pzspan {
7193 Some((row, x0, ref mut n, r)) if row == mb_y && x0 + *n == mb_x && r == recon => {
7194 *n += 1
7195 }
7196 _ => {
7197 self.pz_flush();
7198 self.pzspan = Some((mb_y, mb_x, 1, recon));
7199 }
7200 }
7201 }
7202
7203 /// Range-fill the pending P span's grids (the deferral constants of
7204 /// decode_p_skip's commit: list-0 ref 0, mv (0,0), inter, coded, DC mode,
7205 /// deblock kind SKIP). Same flush points as the B span (span_flush).
7206 #[inline(always)]
7207 fn pz_flush(&mut self) {
7208 if self.pzspan.is_some() {
7209 self.pz_flush_slow();
7210 }
7211 }
7212
7213 fn pz_flush_slow(&mut self) {
7214 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSpanRecon);
7215 let Some((row, x0, n, recon)) = self.pzspan.take() else {
7216 return;
7217 };
7218 edcstat::bump(&edcstat::PZ_SPANS, 1);
7219 edcstat::bump(&edcstat::PZ_SPAN_MBS, n as u64);
7220 let w4 = self.mb_w * 4;
7221 let (b0, len) = (x0 * 4, n * 4);
7222 for dy in 0..4 {
7223 let a = (row * 4 + dy) * w4 + b0;
7224 self.mv_y[a..a + len].fill((0, 0));
7225 self.inter_y[a..a + len].fill(true);
7226 self.ref_idx_y[a..a + len].fill(0);
7227 self.coded_y[a..a + len].fill(true);
7228 self.modes_y[a..a + len].fill(2);
7229 }
7230 self.mb_kind[row * self.mb_w + x0..row * self.mb_w + x0 + n]
7231 .fill(rusty_h264_common::deblock::MB_KIND_SKIP);
7232 if recon {
7233 // The recon deferred with the commit: one band copy from ref[0]
7234 // replaces n queued EdcJob::Skip pushes AND the flush-time
7235 // run-coalescing scan that used to rediscover this very run.
7236 self.recon_p_skip_band(x0, row, n);
7237 }
7238 }
7239
7240 /// Flush BOTH pending grid spans — the one call every grid reader makes.
7241 #[inline]
7242 fn span_flush(&mut self) {
7243 self.bz_flush();
7244 self.pz_flush();
7245 }
7246
7247 /// Hot prefix of decode_b_skip: the FORCED zero-bi run continuation,
7248 /// small enough to inline into the slice loops. Returns true when the MB
7249 /// was fully handled (deferred into the span); false falls to the cold
7250 /// body. Exactly the forced arm's conditions — no behavior change.
7251 #[inline(always)]
7252 fn b_skip_hot(&mut self, mb_x: usize, mb_y: usize) -> bool {
7253 if !self.direct_spatial || self.edc_tx.is_some() || no_bskipfast() {
7254 return false;
7255 }
7256 let mbw = self.mb_w;
7257 let addr = mb_y * mbw + mb_x;
7258 // ONE slice that ENDS at `addr` proves all three neighbour reads. Each
7259 // was a separate check against the whole `bzero` grid even though every
7260 // index is strictly below `addr` (the guards above establish that), and
7261 // the trailing `bzero[addr] = true` a fourth. `get(..=addr)` states the
7262 // bound once, in the form the neighbour indexes are already inside.
7263 // The `get(..=addr)` slice did NOT fold these: it bounds the reads from
7264 // above, but LLVM still has to chain `mb_x > 0 => addr >= 1` and
7265 // `mb_y > 0 => addr >= mbw` through the `&&` sequence, and it will not.
7266 // Fallible reads state each one where it is made; a missing neighbour is
7267 // "not zero", which is the conservative answer this guard wants.
7268 let up = addr.wrapping_sub(mbw);
7269 if !(mb_x > 0
7270 && mb_y > 0
7271 && mb_x + 1 < mbw
7272 && up >= self.slice_first_mb
7273 && self.bzero.get(addr - 1).copied().unwrap_or(false)
7274 && self.bzero.get(up).copied().unwrap_or(false)
7275 && self.bzero.get(up + 1).copied().unwrap_or(false))
7276 {
7277 return false;
7278 }
7279 let wgt = self.iw00();
7280 if !(wgt.is_none() || wgt == Some((32, 32))) {
7281 return false;
7282 }
7283 self.route_skip_mbs += 1;
7284 edcstat::bump(&edcstat::BSKB_FORCED, 1);
7285 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSkipHot);
7286 edcstat::bump(&edcstat::BSKB_FAST, 1);
7287 self.bz_push(mb_x, mb_y, BzKind::ZeroBi);
7288 if let Some(b) = self.bzero.get_mut(addr) {
7289 *b = true;
7290 }
7291 true
7292 }
7293
7294 /// Zero this macroblock's 16 luma nnz entries, walking rows by adding `w4`
7295 /// instead of recomputing `(mb_y * 4 + dy) * w4 + mb_x * 4` four times.
7296 /// Shared by the three sites in decode_b_skip that had it open-coded.
7297 #[inline]
7298 fn clear_mb_nnz(&mut self, mb_x: usize, mb_y: usize, w4: usize) {
7299 let mut a = (mb_y * 4) * w4 + mb_x * 4;
7300 for _ in 0..4 {
7301 self.nnz_y[a..a + 4].fill(0);
7302 a += w4;
7303 }
7304 }
7305
7306 /// The B_Skip SLOW half: full spatial-direct recon plus the zero-residual
7307 /// plane copy. Split out so `pred_y`/`c_pred` (384 B) are built ONLY when a
7308 /// macroblock actually reaches it — the zero-bi / zero-uni / full-pel fast
7309 /// paths in decode_b_skip all return before this, and on LIGHT streams they
7310 /// take 90%+ of the calls.
7311 fn b_skip_slow(
7312 &mut self,
7313 mb_x: usize,
7314 mb_y: usize,
7315 nbrs: Option<([MvNeighbor; 3], [MvNeighbor; 3])>,
7316 w4: usize,
7317 ) -> Result<(), MbError> {
7318 let mut pred_y = [0u8; 256];
7319 let mut c_pred = [[0u8; 64]; 2];
7320 match nbrs {
7321 // Reuses the already-probed neighbours — the grid walk is the
7322 // expensive half of the derivation and paying it twice leaned on
7323 // fall-through-heavy streams (stockholm).
7324 Some((n0, n1)) => {
7325 self.decode_b_direct_n(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred, n0, n1)
7326 }
7327 None => self.decode_b_direct(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred),
7328 }
7329 if let Some(regions) = self.edc_regions.take() {
7330 // nnz clears are PARSE state; the pixel copy is the worker's.
7331 self.clear_mb_nnz(mb_x, mb_y, w4);
7332 self.edc_giveback();
7333 self.edc_send_job(EdcJob::BSkip {
7334 mbx: mb_x,
7335 mby: mb_y,
7336 regions,
7337 });
7338 return Ok(());
7339 }
7340 // Zero residual: the prediction IS the reconstruction — copy it row-wise,
7341 // stepping the destination by the stride rather than multiplying per row.
7342 let mut d = (mb_y * 16) * self.cw + mb_x * 16;
7343 for dy in 0..16 {
7344 self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
7345 d += self.cw;
7346 }
7347 let (ccw, cx0) = (self.ccw, mb_x * 8);
7348 for c in 0..2 {
7349 let plane = if c == 0 {
7350 &mut self.rec_u
7351 } else {
7352 &mut self.rec_v
7353 };
7354 let mut d = (mb_y * 8) * ccw + cx0;
7355 for dy in 0..8 {
7356 plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
7357 d += ccw;
7358 }
7359 }
7360 // nnz stays 0 (no residual) — clear the grids for neighbour context.
7361 self.clear_mb_nnz(mb_x, mb_y, w4);
7362 Ok(())
7363 }
7364
7365 fn decode_b_skip(&mut self, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7366 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::BSkipCold);
7367 self.route_skip_mbs += 1;
7368 self.wait_refs_for_mb(mb_y);
7369 // Each list length read ONCE: the emptiness test and every later
7370 // `len() - 1` clamp (six reads in all) come from these two.
7371 let (nref0, nref1) = (self.refs.len(), self.refs1.len());
7372 if nref0 == 0 || nref1 == 0 {
7373 return Err(MbError::Unsupported("B without references"));
7374 }
7375 let (lref0, lref1) = (nref0 - 1, nref1 - 1);
7376 let mbw = self.mb_w;
7377 let addr = mb_y * mbw + mb_x;
7378 let w4 = mbw * 4;
7379 // ZERO-BI FAST PATH: when spatial direct derives (0,0)/(0,0) bi motion,
7380 // colZeroFlag is IRRELEVANT (it only zeroes MVs that are already zero),
7381 // so the colocated probing, region split, b_mc staging and the pred
7382 // round-trip all collapse into one fused row-average from the two
7383 // padded refs straight into the recon planes. b_mc's bi blend applies
7384 // only IMPLICIT weights, so `None` or (32,32) (both exactly
7385 // (a+b+1)>>1) is the whole identity condition. FourPeople-class LIGHT
7386 // streams put 90%+ of their B_Skips here (BSK_ZBI counter).
7387 // pred_y / c_pred now live in `b_skip_slow` — see there.
7388 if self.direct_spatial && self.edc_tx.is_none() && !no_bskipfast() {
7389 // (The FORCED zero-bi run continuation lives in b_skip_hot, inlined
7390 // at the loop call sites — this cold body only sees non-forced MBs.)
7391 // A pending span can only occupy the LEFT gather position, and the
7392 // left member's committed grid values are fully determined by the
7393 // span KIND — synthesize them instead of flushing, for EVERY kind
7394 // (the same mapping bz_flush writes).
7395 let patch = match self.bzspan {
7396 Some((r, x0, n, k)) if r == mb_y && x0 + n == mb_x => Some(k),
7397 _ => None,
7398 };
7399 if patch.is_none() {
7400 self.bz_flush();
7401 }
7402 let (mut n0, mut n1) = self.b_direct_nbrs(mb_x, mb_y);
7403 if let Some(k) = patch {
7404 let (r0, r1, m0, m1): (i32, i32, (i32, i32), (i32, i32)) = match k {
7405 BzKind::ZeroBi => (0, 0, (0, 0), (0, 0)),
7406 BzKind::ZeroUni(0, ri) => (ri as i32, -1, (0, 0), (0, 0)),
7407 BzKind::ZeroUni(_, ri) => (-1, ri as i32, (0, 0), (0, 0)),
7408 BzKind::Fp { r0, r1, m0, m1 } => {
7409 let mm0 = if r0 >= 0 {
7410 (m0.0 as i32, m0.1 as i32)
7411 } else {
7412 (0, 0)
7413 };
7414 let mm1 = if r1 >= 0 {
7415 (m1.0 as i32, m1.1 as i32)
7416 } else {
7417 (0, 0)
7418 };
7419 (r0 as i32, r1 as i32, mm0, mm1)
7420 }
7421 };
7422 n0[0] = MvNeighbor {
7423 available: true,
7424 mv: m0,
7425 ref_idx: r0,
7426 };
7427 n1[0] = MvNeighbor {
7428 available: true,
7429 mv: m1,
7430 ref_idx: r1,
7431 };
7432 }
7433 let (refi0, refi1, mv0, mv1, _dz) = Self::b_direct_refs_mvs(&n0, &n1);
7434 let (a0, a1) = (refi0 >= 0, refi1 >= 0);
7435 let fast = if a0 && a1 && mv0 == (0, 0) && mv1 == (0, 0) {
7436 // Same malformed-stream ref clamp as b_mc.
7437 let r0 = (refi0 as usize).min(lref0);
7438 let r1 = (refi1 as usize).min(lref1);
7439 // (0,0) dominates here and is already cached slice-wide by iw00.
7440 let wgt = if r0 == 0 && r1 == 0 {
7441 self.iw00()
7442 } else {
7443 self.implicit_weights(r0 as i32, r1 as i32)
7444 };
7445 if wgt.is_none() || wgt == Some((32, 32)) {
7446 if refi0 == 0 && refi1 == 0 {
7447 if let Some(b) = self.bzero.get_mut(addr) {
7448 *b = true;
7449 }
7450 // Same constants as the forced arm: recon AND commit
7451 // both defer into the span.
7452 self.bz_push(mb_x, mb_y, BzKind::ZeroBi);
7453 edcstat::bump(&edcstat::BSKB_FAST, 1);
7454 return Ok(());
7455 }
7456 self.recon_b_skip_zero_bi(mb_x, mb_y, r0, r1);
7457 true
7458 } else {
7459 false
7460 }
7461 } else if a0 != a1 && (if a0 { mv0 } else { mv1 }) == (0, 0) {
7462 // ZERO-UNI: one active list at (0,0) — b_mc's uni arms are plain
7463 // unweighted copies. Defer as a memcpy-band span.
7464 let (list, ri) = if a0 {
7465 (0u8, (refi0 as usize).min(lref0) as u8)
7466 } else {
7467 (1u8, (refi1 as usize).min(lref1) as u8)
7468 };
7469 self.bz_push(mb_x, mb_y, BzKind::ZeroUni(list, ri));
7470 edcstat::bump(&edcstat::BSKB_FAST, 1);
7471 return Ok(());
7472 } else if (!a0 || (mv0.0 % 4 == 0 && mv0.1 % 4 == 0))
7473 && (!a1 || (mv1.0 % 4 == 0 && mv1.1 % 4 == 0))
7474 && self.direct_8x8_inference
7475 {
7476 // FULL-PEL arm (the pan case — shields): nonzero MVs make
7477 // colZeroFlag matter, so probe the four 8x8 corners; a UNIFORM
7478 // czg keeps the MB one region and the MC is an offset read.
7479 // Non-uniform czg or an out-of-pad window falls through.
7480 let mut cz_ok = true;
7481 let mut czv = false;
7482 // Same cz-relevance gate as decode_b_direct_n: only a ref-0
7483 // list with a nonzero MV can be changed by colZeroFlag (fp-arm
7484 // MVs are nonzero by construction, so refs alone decide).
7485 let cz_need = refi0 == 0 || refi1 == 0;
7486 // LOOP-INVARIANT: this gated a `break` INSIDE the probe loop, so
7487 // a fact known before the loop was re-tested on every probe.
7488 if cz_need {
7489 // Absolute block base, hoisted out of the four probes.
7490 let (bx4, by4) = (mb_x * 4, mb_y * 4);
7491 for (k, &(ox, oy)) in [(0usize, 0usize), (2, 0), (0, 2), (2, 2)]
7492 .iter()
7493 .enumerate()
7494 {
7495 // col_block takes MB-LOCAL block coords (the whole MB is
7496 // the region here); col_zero takes absolute — same
7497 // convention as decode_b_direct_n's probe loop.
7498 let (colx, coly) = self.col_block(ox, oy);
7499 let cz = self.col_zero(bx4 + colx, by4 + coly);
7500 if k == 0 {
7501 czv = cz;
7502 } else if cz != czv {
7503 cz_ok = false;
7504 break;
7505 }
7506 }
7507 }
7508 if cz_ok {
7509 let m0 = if refi0 == 0 && czv { (0, 0) } else { mv0 };
7510 let m1 = if refi1 == 0 && czv { (0, 0) } else { mv1 };
7511 let r0c = (refi0 as usize).min(lref0);
7512 let r1c = (refi1 as usize).min(lref1);
7513 let (or0, or1) = (a0.then_some(r0c), a1.then_some(r1c));
7514 let wgt_ok = !(a0 && a1) || {
7515 let wgt = self.implicit_weights(r0c as i32, r1c as i32);
7516 wgt.is_none() || wgt == Some((32, 32))
7517 };
7518 // SPAN path: prevalidated windows + %8 chroma ⇒ defer as
7519 // an offset copy/avg band; otherwise the immediate per-MB
7520 // recon below (which handles interpolating chroma).
7521 if wgt_ok && self.bz_fp_valid(mb_x, mb_y, or0, or1, m0, m1) {
7522 let kind = BzKind::Fp {
7523 r0: if a0 { r0c as i8 } else { -1 },
7524 r1: if a1 { r1c as i8 } else { -1 },
7525 m0: (m0.0 as i16, m0.1 as i16),
7526 m1: (m1.0 as i16, m1.1 as i16),
7527 };
7528 self.bz_push(mb_x, mb_y, kind);
7529 edcstat::bump(&edcstat::BSKB_FP, 1);
7530 return Ok(());
7531 }
7532 let ok = wgt_ok && self.recon_b_skip_fp(mb_x, mb_y, or0, or1, m0, m1);
7533 if ok {
7534 edcstat::bump(&edcstat::BSKB_FP, 1);
7535 self.b_set_motion(mb_x, mb_y, 0, 0, 16, 16, refi0, m0, refi1, m1);
7536 self.clear_mb_nnz(mb_x, mb_y, w4);
7537 return Ok(());
7538 }
7539 }
7540 false
7541 } else {
7542 false
7543 };
7544 if fast {
7545 edcstat::bump(&edcstat::BSKB_FAST, 1);
7546 self.b_set_motion(mb_x, mb_y, 0, 0, 16, 16, refi0, (0, 0), refi1, (0, 0));
7547 self.clear_mb_nnz(mb_x, mb_y, w4);
7548 return Ok(());
7549 }
7550 // Fall-through hands the ALREADY-probed neighbours to the slow
7551 // half so the grid walk is not paid twice.
7552 self.b_skip_slow(mb_x, mb_y, Some((n0, n1)), w4)
7553 } else {
7554 if self.edc_tx.is_some() {
7555 self.edc_regions = Some(Vec::with_capacity(4));
7556 }
7557 self.b_skip_slow(mb_x, mb_y, None, w4)
7558 }
7559 }
7560
7561 /// Reconstructs a B macroblock (spec Table 7-14): direct, L0/L1/Bi partitions,
7562 /// `B_8x8`, or intra.
7563 fn decode_b_mb(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7564 // Non-skip B MBs gather neighbor motion/modes — flush any deferred
7565 // spans before those grids are read.
7566 self.span_flush();
7567 self.wait_refs_for_mb(mb_y);
7568 let mb_type = r.read_ue()?;
7569 if mb_type >= 23 {
7570 return self.decode_intra_mb(r, mb_x, mb_y, mb_type - 23);
7571 }
7572 if self.refs.is_empty() || self.refs1.is_empty() {
7573 return Err(MbError::Unsupported("B without references"));
7574 }
7575 let mut pred_y = [0u8; 256];
7576 let mut c_pred = [[0u8; 64]; 2];
7577
7578 if mb_type == 0 {
7579 // B_Direct_16x16 — 8×8 transform allowed only with direct_8x8_inference.
7580 // FORCED derivation via the zero-bi bitmap (CAVLC parity with the
7581 // CABAC direct arm): gather + rid/median skipped, chains extended.
7582 let addr_f = mb_y * self.mb_w + mb_x;
7583 let forced = self.direct_spatial
7584 && self.edc_tx.is_none()
7585 && mb_x > 0
7586 && mb_y > 0
7587 && mb_x + 1 < self.mb_w
7588 && addr_f - self.mb_w >= self.slice_first_mb
7589 && self.bzero.get(addr_f - 1).copied().unwrap_or(false)
7590 && self
7591 .bzero
7592 .get(addr_f.wrapping_sub(self.mb_w))
7593 .copied()
7594 .unwrap_or(false)
7595 && self
7596 .bzero
7597 .get(addr_f.wrapping_sub(self.mb_w) + 1)
7598 .copied()
7599 .unwrap_or(false);
7600 if forced {
7601 self.b_direct_region(
7602 mb_x,
7603 mb_y,
7604 0,
7605 0,
7606 16,
7607 16,
7608 &mut pred_y,
7609 &mut c_pred,
7610 (0, 0, (0, 0), (0, 0), false),
7611 );
7612 if let Some(b) = self.bzero.get_mut(addr_f) {
7613 *b = true;
7614 }
7615 } else {
7616 self.decode_b_direct(mb_x, mb_y, 0, 0, 16, 16, &mut pred_y, &mut c_pred);
7617 }
7618 return self.inter_finish(
7619 r,
7620 mb_x,
7621 mb_y,
7622 &pred_y,
7623 &c_pred,
7624 self.direct_8x8_inference,
7625 false,
7626 );
7627 }
7628 if mb_type == 22 {
7629 return self.decode_b_8x8(r, mb_x, mb_y);
7630 }
7631
7632 // 16x16 / 16x8 / 8x16 partitions with per-partition L0/L1/Bi.
7633 let (layout, mvmode, preds) = b_inter_layout(mb_type);
7634 // Single 16x16 partition => one motion set for all sixteen blocks in both
7635 // lists; see the CABAC twin of this marking.
7636 if mvmode == 0 {
7637 if let Some(p) = self.mb_umot.get_mut(mb_y * self.mb_w + mb_x) {
7638 *p = true;
7639 }
7640 }
7641 // mb_pred order: ref_idx_l0 (all L0 parts), ref_idx_l1, mvd_l0, mvd_l1.
7642 let mut refi = [[-1i32; 2]; 2]; // [part][list]
7643 for (p, &(_, _, _, _)) in layout.iter().enumerate() {
7644 if preds[p & 1].uses(0) {
7645 refi[p][0] = self.read_b_ref(r, 0)?;
7646 }
7647 }
7648 for (p, _) in layout.iter().enumerate() {
7649 if preds[p & 1].uses(1) {
7650 refi[p][1] = self.read_b_ref(r, 1)?;
7651 }
7652 }
7653 let mut mvd = [[(0i32, 0i32); 2]; 2];
7654 for (p, _) in layout.iter().enumerate() {
7655 if preds[p & 1].uses(0) {
7656 mvd[p][0] = (read_mvd(r)?, read_mvd(r)?);
7657 }
7658 }
7659 for (p, _) in layout.iter().enumerate() {
7660 if preds[p & 1].uses(1) {
7661 mvd[p][1] = (read_mvd(r)?, read_mvd(r)?);
7662 }
7663 }
7664 // Per partition: predict + commit each list's MV, then motion-compensate.
7665 for (p, &(rx, ry, rw, rh)) in layout.iter().enumerate() {
7666 let (pbx, pby) = ((mb_x * 4 + rx / 4) as isize, (mb_y * 4 + ry / 4) as isize);
7667 let pwb = (rw / 4) as isize;
7668 let mut mv = [(0i32, 0i32); 2];
7669 for list in 0..2 {
7670 if refi[p][list] >= 0 {
7671 let n = self.mv_neighbors_list(pbx, pby, pwb, list);
7672 let pmv = predict_partition_mv(mvmode, p, n[0], n[1], n[2], refi[p][list]);
7673 mv[list] = (pmv.0 + mvd[p][list].0, pmv.1 + mvd[p][list].1);
7674 }
7675 }
7676 self.b_set_motion(
7677 mb_x,
7678 mb_y,
7679 rx,
7680 ry,
7681 rw,
7682 rh,
7683 refi[p & 3][0],
7684 mv[0],
7685 refi[p & 3][1],
7686 mv[1],
7687 );
7688 // Spec-correct bi-prediction (average of L0 and L1), matching the CABAC
7689 // path. This used to replicate an openh264 bug for a Bi 16x8/8x16
7690 // partition -- openh264 mis-handles the destination buffer there, so
7691 // partition 0 came out List-1-only and partition 1 List-0-only. That was
7692 // deliberate when openh264's h264dec WAS the conformance oracle, but the
7693 // gate is ffmpeg now and the CABAC path already went spec-correct; the
7694 // CAVLC path was simply left behind. Measured: mb_type 12..21 (every B
7695 // 16x8/8x16 with at least one Bi partition) were 100% wrong vs ffmpeg,
7696 // while 1..11 (no Bi partition) were only collaterally damaged.
7697 self.b_mc_or_record(
7698 mb_x,
7699 mb_y,
7700 rx,
7701 ry,
7702 rw,
7703 rh,
7704 refi[p & 3][0],
7705 mv[0],
7706 refi[p & 3][1],
7707 mv[1],
7708 &mut pred_y,
7709 &mut c_pred,
7710 );
7711 }
7712 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, true, false)
7713 }
7714
7715 /// Reconstructs a `B_8x8` macroblock: four 8×8 sub-macroblock partitions, each
7716 /// direct or L0/L1/Bi with its own sub-partitioning (spec Table 7-18).
7717 fn decode_b_8x8(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
7718 let mut sub = [0u32; 4];
7719 for s in sub.iter_mut() {
7720 let v = r.read_ue()?;
7721 if v > 12 {
7722 return Err(MbError::Unsupported("invalid B sub_mb_type"));
7723 }
7724 *s = v;
7725 }
7726 let mut pred_y = [0u8; 256];
7727 let mut c_pred = [[0u8; 64]; 2];
7728 // ref_idx for all 8×8 partitions (L0 batch, then L1 batch), for the
7729 // non-direct sub-partitions.
7730 let mut refi = [[-1i32; 2]; 4];
7731 for (p, &st) in sub.iter().enumerate() {
7732 if st != 0 && b_sub_uses(st, 0) {
7733 refi[p][0] = self.read_b_ref(r, 0)?;
7734 }
7735 }
7736 for (p, &st) in sub.iter().enumerate() {
7737 if st != 0 && b_sub_uses(st, 1) {
7738 refi[p][1] = self.read_b_ref(r, 1)?;
7739 }
7740 }
7741 // mvd: all mvd_l0 (partition-major, sub-partition order), then all mvd_l1.
7742 //
7743 // FIXED ARRAYS, NOT `Vec::new()` + push. These are per-MACROBLOCK on every
7744 // B_8x8, and a growing Vec allocated (and reallocated) twice per MB — on a
7745 // B-heavy stream that is thousands of allocations per frame for data whose
7746 // maximum size is a compile-time constant: 4 partitions x at most 4
7747 // sub-partitions = 16 entries. Indexing a fixed array cannot exceed that, and
7748 // an out-of-range index would panic rather than misbehave, so the bound is
7749 // enforced either way and this crate stays forbid(unsafe).
7750 const MAX_MVD: usize = 16;
7751 let mut mvd0 = [(0i32, 0i32); MAX_MVD];
7752 let mut mvd1 = [(0i32, 0i32); MAX_MVD];
7753 let (mut n0, mut n1) = (0usize, 0usize);
7754 for &st in &sub {
7755 if st != 0 && b_sub_uses(st, 0) {
7756 for _ in b_sub_parts(st) {
7757 mvd0[n0] = (read_mvd(r)?, read_mvd(r)?);
7758 n0 += 1;
7759 }
7760 }
7761 }
7762 for &st in &sub {
7763 if st != 0 && b_sub_uses(st, 1) {
7764 for _ in b_sub_parts(st) {
7765 mvd1[n1] = (read_mvd(r)?, read_mvd(r)?);
7766 n1 += 1;
7767 }
7768 }
7769 }
7770 // Decode each 8×8 partition.
7771 // Spatial-direct A/B/C are MB-level — walk once if any sub is direct.
7772 // `dmemo=0` rewalks every direct 8×8 (A/B oracle).
7773 let hoisted = if self.direct_spatial && direct_memo_on() && sub.contains(&0) {
7774 Some(self.b_direct_nbrs(mb_x, mb_y))
7775 } else {
7776 None
7777 };
7778 let (mut i0, mut i1) = (0usize, 0usize);
7779 for (p, &st) in sub.iter().enumerate() {
7780 let (b8x, b8y) = ((p % 2) * 8, (p / 2) * 8);
7781 if st == 0 {
7782 match hoisted {
7783 Some((n0, n1)) => self.decode_b_direct_n(
7784 mb_x,
7785 mb_y,
7786 b8x,
7787 b8y,
7788 8,
7789 8,
7790 &mut pred_y,
7791 &mut c_pred,
7792 n0,
7793 n1,
7794 ),
7795 None => {
7796 self.decode_b_direct(mb_x, mb_y, b8x, b8y, 8, 8, &mut pred_y, &mut c_pred)
7797 }
7798 }
7799 continue;
7800 }
7801 for &(sx, sy, sw, sh) in b_sub_parts(st) {
7802 let (px, py) = (b8x + sx, b8y + sy);
7803 let (pbx, pby) = ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
7804 let pwb = (sw / 4) as isize;
7805 let mut mv = [(0i32, 0i32); 2];
7806 if b_sub_uses(st, 0) {
7807 let n = self.mv_neighbors_list(pbx, pby, pwb, 0);
7808 let pmv = predict_mv(n[0], n[1], n[2], refi[p & 3][0]);
7809 let d = mvd0[i0 & 15];
7810 i0 += 1;
7811 mv[0] = (pmv.0 + d.0, pmv.1 + d.1);
7812 }
7813 if b_sub_uses(st, 1) {
7814 let n = self.mv_neighbors_list(pbx, pby, pwb, 1);
7815 let pmv = predict_mv(n[0], n[1], n[2], refi[p & 3][1]);
7816 let d = mvd1[i1 & 15];
7817 i1 += 1;
7818 mv[1] = (pmv.0 + d.0, pmv.1 + d.1);
7819 }
7820 self.b_set_motion(
7821 mb_x,
7822 mb_y,
7823 px,
7824 py,
7825 sw,
7826 sh,
7827 refi[p & 3][0],
7828 mv[0],
7829 refi[p & 3][1],
7830 mv[1],
7831 );
7832 self.b_mc_or_record(
7833 mb_x,
7834 mb_y,
7835 px,
7836 py,
7837 sw,
7838 sh,
7839 refi[p & 3][0],
7840 mv[0],
7841 refi[p & 3][1],
7842 mv[1],
7843 &mut pred_y,
7844 &mut c_pred,
7845 );
7846 }
7847 }
7848 // noSubMbPartSizeLessThan8x8: each sub-partition must be ≥ 8×8 (direct
7849 // counts only with the 8×8 inference flag).
7850 let allow_8x8 = sub.iter().all(|&st| {
7851 if st == 0 {
7852 self.direct_8x8_inference
7853 } else {
7854 st <= 3
7855 }
7856 });
7857 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, allow_8x8, false)
7858 }
7859
7860 /// Reconstructs a `P_8x8` macroblock: four 8×8 sub-macroblock partitions,
7861 /// each independently split (8×8 / 8×4 / 4×8 / 4×4) with its own motion
7862 /// vector(s). `ref0` is `P_8x8ref0` (every `ref_idx` forced to 0, not coded).
7863 fn decode_p8x8(
7864 &mut self,
7865 r: &mut BitReader,
7866 mb_x: usize,
7867 mb_y: usize,
7868 ref0: bool,
7869 ) -> Result<(), MbError> {
7870 if self.refs.is_empty() {
7871 return Err(MbError::Unsupported("inter without reference"));
7872 }
7873 let w4 = self.mb_w * 4;
7874 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
7875 let num_refs = self.refs.len();
7876
7877 // mb_pred order (spec §7.3.5.2): all sub_mb_type, then all ref_idx_l0,
7878 // then all mvd_l0 (partition-major, sub-partition order within each).
7879 let mut sub_types = [0u32; 4];
7880 for st in sub_types.iter_mut() {
7881 let v = r.read_ue()?;
7882 if v > 3 {
7883 return Err(MbError::Unsupported("B-slice / invalid sub_mb_type"));
7884 }
7885 *st = v;
7886 }
7887 let mut ref_idxs = [0i32; 4];
7888 if self.num_ref_active > 1 && !ref0 {
7889 for ri in ref_idxs.iter_mut() {
7890 *ri = read_ref_idx(r, self.num_ref_active)?;
7891 if *ri as usize >= num_refs {
7892 return Err(MbError::Truncated); // references a non-existent picture
7893 }
7894 }
7895 }
7896
7897 // Per sub-partition (in decoding order): median MV prediction from the
7898 // committed neighbor grid, mvd, commit, then motion-compensate. Committing
7899 // before the next prediction is what lets sub-partitions chain correctly.
7900 // D14b: P_8x8 defers too. Syncing before it instead cost 9,772 pipeline
7901 // drains per stream (45 per 1000 MBs, vs CABAC's 3.3) because P_8x8 is
7902 // common in CAVLC P slices — and the seam measured 1.65-1.97x SLOWER
7903 // for it. Deferring is byte-identical for sub-partitions: the worker
7904 // motion-compensates per 4x4 from the committed grids, which is exactly
7905 // how the CABAC path already handles P_8x8, and a 6-tap filter applied
7906 // per-4x4 with the same MV gives the same pixels as one 8x8 call.
7907 let defer = self.edc_tx.is_some() || self.edc_active;
7908
7909 // ── PHASE 1: PARSE + COMMIT. Must always run to completion. ──────────
7910 //
7911 // These two are interleaved BY NECESSITY: each sub-partition's MV
7912 // prediction reads the grids the previous one committed, so they cannot
7913 // be separated from each other. But they consume BITSTREAM, so nothing
7914 // here may ever be skipped conditionally.
7915 //
7916 // This phase split is a STRUCTURAL guard, not a tidy-up. When MC lived
7917 // inside this loop, deferring it was written as a `break` — which also
7918 // skipped the `read_se` mvd reads, desynced the bitstream, and
7919 // mis-parsed as a B-slice sub_mb_type on a Baseline stream. It happened
7920 // to crash; a desync that stayed IN RANGE would have produced plausible
7921 // garbage instead. With MC in its own pass below, skipping pixel work
7922 // cannot reach the bitstream at all — the mistake is unavailable.
7923 let mut regions: [(usize, usize, usize, usize, i32, (i32, i32)); 16] =
7924 [(0, 0, 0, 0, 0, (0, 0)); 16];
7925 let mut nreg = 0usize;
7926 for part in 0..4usize {
7927 let refi = ref_idxs[part];
7928 let (b8x, b8y) = ((part % 2) * 8, (part / 2) * 8);
7929 for &(srx, sry, srw, srh) in sub_mb_partitions(sub_types[part]) {
7930 let (px, py) = (b8x + srx, b8y + sry);
7931 let (pbx, pby) = ((mb_x * 4 + px / 4) as isize, (mb_y * 4 + py / 4) as isize);
7932 let [a, b, c] = self.mv_neighbors_block(pbx, pby, (srw / 4) as isize);
7933 let pmv = predict_mv(a, b, c, refi);
7934 let mvd_x = read_mvd(r)?;
7935 let mvd_y = read_mvd(r)?;
7936 let mv = (pmv.0 + mvd_x, pmv.1 + mvd_y);
7937 for by in py / 4..py / 4 + srh / 4 {
7938 for bx in px / 4..px / 4 + srw / 4 {
7939 let idx = (mb_y * 4 + by) * w4 + (mb_x * 4 + bx);
7940 if let (Some(m), Some(it), Some(rf), Some(cd)) = (
7941 self.mv_y.get_mut(idx),
7942 self.inter_y.get_mut(idx),
7943 self.ref_idx_y.get_mut(idx),
7944 self.coded_y.get_mut(idx),
7945 ) {
7946 (*m, *it, *rf, *cd) = ((mv.0 as i16, mv.1 as i16), true, refi as i8, true);
7947 }
7948 }
7949 }
7950 regions[nreg & 15] = (px, py, srw, srh, refi, mv);
7951 nreg += 1;
7952 }
7953 }
7954
7955 // ── PHASE 2: PIXEL WORK ONLY. Reads no bitstream; safe to skip. ──────
7956 let mut pred_y = [0u8; 256];
7957 let mut c_pred = [[0u8; 64]; 2];
7958 if !defer {
7959 for &(px, py, srw, srh, refi, mv) in ®ions[..nreg] {
7960 let Some(reference) = self.refs.get(refi as usize) else {
7961 continue;
7962 };
7963 let mut tmp = [0u8; 256];
7964 mc_luma_padded(
7965 &reference.luma_guard(reference.ch),
7966 reference.lstride(),
7967 crate::LPAD,
7968 self.cw,
7969 ch,
7970 mb_x * 16 + px,
7971 mb_y * 16 + py,
7972 srw,
7973 srh,
7974 mv.0,
7975 mv.1,
7976 &mut tmp,
7977 );
7978 restride(&mut pred_y, 16, px, py, &tmp, srw, srh);
7979 let (crx, cry, crw, crh) = (px / 2, py / 2, srw / 2, srh / 2);
7980 for cc in 0..2 {
7981 let rc = if cc == 0 {
7982 &*reference.chroma_guard(0, reference.ch)
7983 } else {
7984 &*reference.chroma_guard(1, reference.ch)
7985 };
7986 let mut tc = [0u8; 64];
7987 mc_chroma_padded(
7988 rc,
7989 reference.cstride(),
7990 crate::CPAD,
7991 self.ccw,
7992 cch,
7993 mb_x * 8 + crx,
7994 mb_y * 8 + cry,
7995 crw,
7996 crh,
7997 mv.0,
7998 mv.1,
7999 &mut tc,
8000 );
8001 restride(&mut c_pred[cc], 8, crx, cry, &tc, crw, crh);
8002 }
8003 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi as usize, px, py, srw, srh);
8004 }
8005 }
8006
8007 // P_8x8 allows the 8×8 transform only when every sub-partition is 8×8.
8008 let allow_8x8 = sub_types.iter().all(|&t| t == 0);
8009 self.inter_finish(r, mb_x, mb_y, &pred_y, &c_pred, allow_8x8, defer)
8010 }
8011
8012 /// Reconstructs a `P_Skip` macroblock: motion-compensate from the reference
8013 /// at the skip MV, with no residual.
8014 /// Records a B MC region (threaded mode) or executes it inline — the SAME
8015 /// arguments as `b_mc`; the recorded arm resolves the implicit weights at
8016 /// parse time (identical function, parse-side data).
8017 fn b_mc_or_record(
8018 &mut self,
8019 mb_x: usize,
8020 mb_y: usize,
8021 px: usize,
8022 py: usize,
8023 rw: usize,
8024 rh: usize,
8025 refi0: i32,
8026 mv0: (i32, i32),
8027 refi1: i32,
8028 mv1: (i32, i32),
8029 pred_y: &mut [u8; 256],
8030 c_pred: &mut [[u8; 64]; 2],
8031 ) {
8032 if self.edc_regions.is_some() {
8033 // Mirror `b_mc`'s malformed-stream armor EXACTLY before touching the
8034 // ref lists: the inline path clamps the indices and bails on empty
8035 // lists BEFORE computing weights; calling `implicit_weights` with the
8036 // raw indices re-introduced the panic the armor exists to prevent
8037 // (found by the fuzzer, via the unwind-guard that turned the
8038 // resulting worker deadlock back into a diagnosable failure).
8039 let cr0 = if refi0 >= 0 {
8040 (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32
8041 } else {
8042 -1
8043 };
8044 let cr1 = if refi1 >= 0 {
8045 (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32
8046 } else {
8047 -1
8048 };
8049 let w = if (cr0 >= 0 && self.refs.is_empty()) || (cr1 >= 0 && self.refs1.is_empty()) {
8050 None // the worker's port returns before reading the weights
8051 } else {
8052 self.implicit_weights(cr0, cr1)
8053 };
8054 // Cannot bind above: `implicit_weights` needs `&mut self` between the
8055 // guard and here. The guard already proved this is `Some`, so the
8056 // `else` is unreachable -- but an unreachable no-op is the correct
8057 // shape for armor code, not an `unwrap` that can abort a decode.
8058 if let Some(regions) = self.edc_regions.as_mut() {
8059 regions.push(BRegion {
8060 px,
8061 py,
8062 rw,
8063 rh,
8064 refi0,
8065 refi1,
8066 mv0,
8067 mv1,
8068 w,
8069 });
8070 }
8071 return;
8072 }
8073 self.b_mc(
8074 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, pred_y, c_pred,
8075 );
8076 }
8077
8078 /// Builds the worker's owned pixel context from `self` (planes MOVED out,
8079 /// shared read-only state cloned, filter inputs snapshotted).
8080 fn edc_take_ctx(&mut self) -> PixelCtx {
8081 PixelCtx {
8082 rec_y: core::mem::take(&mut self.rec_y),
8083 rec_u: core::mem::take(&mut self.rec_u),
8084 rec_v: core::mem::take(&mut self.rec_v),
8085 bak_y: core::mem::take(&mut self.bak_y),
8086 bak_u: core::mem::take(&mut self.bak_u),
8087 bak_v: core::mem::take(&mut self.bak_v),
8088 refs: self.refs.clone(),
8089 refs1: self.refs1.clone(),
8090 weights: self.weights.clone(),
8091 weights_l0id: self.weights_l0id,
8092 scaling: self.scaling,
8093 scaling8: self.scaling8,
8094 cw: self.cw,
8095 ccw: self.ccw,
8096 mb_w: self.mb_w,
8097 mb_h: self.mb_h,
8098 chroma_qp_offset: self.chroma_qp_offset,
8099 flt_rows: self.flt_rows,
8100 db_ena: self.db_ena,
8101 db_oa: self.db_oa,
8102 db_ob: self.db_ob,
8103 cur_qp: self.cur_qp,
8104 qp_grid: self.mb_qp.clone(),
8105 t8_grid: self.mb_t8x8.clone(),
8106 bs_store: self.bs_frame.clone(),
8107 progress: self.progress.clone(),
8108 }
8109 }
8110
8111 /// Restores the planes (and the filter watermark) from a returned context.
8112 fn edc_restore_ctx(&mut self, ctx: PixelCtx) {
8113 self.rec_y = ctx.rec_y;
8114 self.rec_u = ctx.rec_u;
8115 self.rec_v = ctx.rec_v;
8116 self.bak_y = ctx.bak_y;
8117 self.bak_u = ctx.bak_u;
8118 self.bak_v = ctx.bak_v;
8119 self.flt_rows = ctx.flt_rows;
8120 }
8121
8122 /// Intra macroblocks read neighbour PIXELS: fetch the context from the
8123 /// worker (which drains all prior jobs first — the channel is FIFO) and
8124 /// install the planes so the inline intra path runs unchanged. The context
8125 /// is given back lazily at the next job/row/slice-end (`edc_giveback`), so
8126 /// consecutive intra macroblocks pay ONE round-trip.
8127 /// Queue a pixel job for the worker (D10). Batched per row; see `EdcMsg::Batch`.
8128 /// Record the FIRST internal fault; later ones do not overwrite it, so the
8129 /// reported cause is the original one rather than its consequences.
8130 #[inline]
8131 fn note_fault(&mut self, what: &'static str) {
8132 if self.fault.is_none() {
8133 self.fault = Some(what);
8134 }
8135 }
8136
8137 /// The picture's internal fault, if one was recorded.
8138 #[inline]
8139 pub fn fault(&self) -> Option<&'static str> {
8140 self.fault
8141 }
8142
8143 /// Post to the EDC worker, recording a fault rather than panicking when the
8144 /// worker is gone or the channel was never built.
8145 #[inline]
8146 fn edc_post(&mut self, msg: EdcMsg) {
8147 let dead = match self.edc_tx.as_ref() {
8148 Some(tx) => tx.send(msg).is_err(),
8149 None => true,
8150 };
8151 if dead {
8152 self.note_fault("EDC worker gone; a deblock job was not delivered");
8153 }
8154 }
8155
8156 #[inline]
8157 fn edc_send_job(&mut self, job: EdcJob) {
8158 edcstat::bump(&edcstat::JOBS, 1);
8159 if !batch_on() {
8160 self.edc_post(EdcMsg::Job(job));
8161 return;
8162 }
8163 self.edc_batch.push(job);
8164 }
8165
8166 /// Ship the accumulated row batch. MUST be called before anything that
8167 /// depends on those jobs having been applied: the row's `Row` filter
8168 /// message, a `NeedCtx` handover, and slice end.
8169 fn edc_flush_batch(&mut self) {
8170 if self.edc_batch.is_empty() {
8171 return;
8172 }
8173 // Replace with a PRE-RESERVED buffer rather than the empty Vec
8174 // `mem::take` would leave: otherwise each row reallocs and regrows from
8175 // zero, trading 208k channel sends for ~7 reallocs x 3k rows.
8176 let cap = self.edc_batch.capacity().max(self.mb_w);
8177 let jobs = core::mem::replace(&mut self.edc_batch, Vec::with_capacity(cap));
8178 edcstat::bump(&edcstat::BATCHES, 1);
8179 self.edc_post(EdcMsg::Batch(jobs));
8180 }
8181
8182 fn edc_intra_sync(&mut self) {
8183 if self.edc_tx.is_none() {
8184 self.edc_flush();
8185 return;
8186 }
8187 if self.edc_parked.is_some() {
8188 return; // already holding
8189 }
8190 // ORDER: drain the batch before taking the planes, or those jobs would
8191 // be applied to a context the parse thread is concurrently holding.
8192 self.edc_flush_batch();
8193 edcstat::bump(&edcstat::NEEDCTX, 1);
8194 self.edc_post(EdcMsg::NeedCtx);
8195 // The worker owes us the pixel context back. If it is gone there is
8196 // nothing to continue with, so record it and leave the state untouched
8197 // -- finalize turns this into `DecodeError::Internal`.
8198 let Some(mut ctx) = self.edc_ctx_rx.as_ref().and_then(|rx| rx.recv().ok()) else {
8199 self.note_fault("EDC worker did not return the pixel context");
8200 return;
8201 };
8202 self.rec_y = core::mem::take(&mut ctx.rec_y);
8203 self.rec_u = core::mem::take(&mut ctx.rec_u);
8204 self.rec_v = core::mem::take(&mut ctx.rec_v);
8205 self.bak_y = core::mem::take(&mut ctx.bak_y);
8206 self.bak_u = core::mem::take(&mut ctx.bak_u);
8207 self.bak_v = core::mem::take(&mut ctx.bak_v);
8208 self.flt_rows = ctx.flt_rows;
8209 self.edc_parked = Some(ctx);
8210 }
8211
8212 /// Returns a held context to the worker (inverse of `edc_intra_sync`).
8213 /// NOTE (D10): this is called per-macroblock on the job paths, so it must
8214 /// NOT flush the batch — that would undo the batching. It is safe: the
8215 /// parked state is only ever entered through `edc_intra_sync`, which
8216 /// flushes before taking the planes, so nothing can be queued-but-unsent
8217 /// while the parse thread holds them.
8218 fn edc_giveback(&mut self) {
8219 if let Some(mut parked) = self.edc_parked.take() {
8220 parked.rec_y = core::mem::take(&mut self.rec_y);
8221 parked.rec_u = core::mem::take(&mut self.rec_u);
8222 parked.rec_v = core::mem::take(&mut self.rec_v);
8223 parked.bak_y = core::mem::take(&mut self.bak_y);
8224 parked.bak_u = core::mem::take(&mut self.bak_u);
8225 parked.bak_v = core::mem::take(&mut self.bak_v);
8226 parked.flt_rows = self.flt_rows;
8227 // Fail-soft: on the unwind path the worker may already be gone;
8228 // dropping the parked context is acceptable there (the planes are
8229 // lost, but the panic is being propagated anyway).
8230 if let Some(tx) = self.edc_back_tx.as_ref() {
8231 let _ = tx.send(parked);
8232 }
8233 }
8234 }
8235
8236 /// Parse-side twin of the nnz/coded grid writes `add_inter_residual` does
8237 /// inline — the worker's port omits them (they are PARSE state: deblock
8238 /// derivation and the CAVLC nC contexts read them), so the threaded path
8239 /// commits them here, from the same parsed counts (their equality with the
8240 /// recon-side recount is the Part 8 nnz-threading brick's own invariant).
8241 fn edc_commit_nnz(
8242 &mut self,
8243 mbx: usize,
8244 mby: usize,
8245 t8: bool,
8246 nnzs: &[u8; 24],
8247 cbp_chroma: u32,
8248 ) {
8249 // BUILD THE RASTER LOCALLY, THEN COPY ROW-WISE. Both luma arms scattered
8250 // sixteen individually bounds-checked stores into the frame grid (the t8
8251 // arm through a 2x2-of-2x2 nest, the other through the z-order scan
8252 // table); chroma scattered eight more. Assembling the macroblock's own
8253 // 4x4 raster on the stack first turns that into four contiguous row
8254 // copies per plane.
8255 let (w4r, w2r) = (self.mb_w * 4, self.mb_w * 2);
8256 let mut raster = [0u8; 16];
8257 if t8 {
8258 for b8 in 0..4usize {
8259 let (b8x, b8y) = (b8 % 2, b8 / 2);
8260 let n = nnzs[b8 * 4];
8261 for sy in 0..2 {
8262 for sx in 0..2 {
8263 raster[(b8y * 2 + sy) * 4 + b8x * 2 + sx] = n;
8264 }
8265 }
8266 }
8267 } else {
8268 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
8269 raster[lby * 4 + lbx] = nnzs[blk];
8270 }
8271 }
8272 for by in 0..4usize {
8273 let a = (mby * 4 + by) * w4r + mbx * 4;
8274 self.nnz_y[a..a + 4].copy_from_slice(&raster[by * 4..by * 4 + 4]);
8275 }
8276 if cbp_chroma == 2 {
8277 for c in 0..2usize {
8278 let mut cr = [0u8; 4];
8279 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
8280 cr[by * 2 + bx] = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
8281 }
8282 for by in 0..2usize {
8283 let a = (mby * 2 + by) * w2r + mbx * 2;
8284 self.nnz_c[c][a..a + 2].copy_from_slice(&cr[by * 2..by * 2 + 2]);
8285 }
8286 }
8287 }
8288 }
8289
8290 /// Flush the entropy-decouple job queue: replay every deferred pixel job
8291 /// in parse order. Called before any intra macroblock (its reconstruction
8292 /// reads neighbour PIXELS), before row filtering, at B-branch entry, at
8293 /// slice end, and at `deblock()` as a backstop.
8294 /// Per-macroblock guard: the B arm calls this once per B MB — the empty
8295 /// test inlines, the drain body stays outlined.
8296 /// A job box from the pool (or a fresh one when the pool is empty -- the
8297 /// first row of a picture, or MT mode where boxes cross to the worker).
8298 #[inline]
8299 fn take_pinter_job(&mut self) -> Box<PInterJob> {
8300 self.edc_job_pool
8301 .pop()
8302 .unwrap_or_else(|| Box::new(PInterJob::blank()))
8303 }
8304
8305 #[inline]
8306 fn take_nores_job(&mut self, pj: PInterNoResJob) -> Box<PInterNoResJob> {
8307 match self.edc_nores_pool.pop() {
8308 Some(mut b) => {
8309 *b = pj;
8310 b
8311 }
8312 None => Box::new(pj),
8313 }
8314 }
8315
8316 #[inline(always)]
8317 fn edc_flush(&mut self) {
8318 if self.edc_jobs.is_empty() {
8319 return;
8320 }
8321 self.edc_flush_slow();
8322 }
8323
8324 fn edc_flush_slow(&mut self) {
8325 let mut jobs = core::mem::take(&mut self.edc_jobs);
8326 // Band identity holds unweighted OR under identity weights (the x264
8327 // weightp=2 common case: a table in every P slice, identity outside fades).
8328 let band_ok = self.weights.is_none() || self.weights_id0;
8329 // Measurement: nonzero-MV skips sitting in same-row runs of >=2 EQUAL
8330 // MVs (the next band candidate). counted_until stops double-counting.
8331 let mut counted_until = 0usize;
8332 let mut i = 0usize;
8333 while i < jobs.len() {
8334 let j = &jobs[i];
8335 match j {
8336 EdcJob::Skip { mbx, mby, mv } => {
8337 // mb_skip_run band coalescing: gather the maximal run of
8338 // consecutive same-row (0,0)-skips and recon it as ONE
8339 // band copy (weighted-P falls back — the copy identity
8340 // only holds unweighted).
8341 if *mv == (0, 0) && band_ok && !no_skipband() {
8342 let (x0, y0) = (*mbx, *mby);
8343 let mut n = 1usize;
8344 while let Some(EdcJob::Skip {
8345 mbx: nx,
8346 mby: ny,
8347 mv: nmv,
8348 }) = jobs.get(i + n)
8349 {
8350 if *ny == y0 && *nx == x0 + n && *nmv == (0, 0) {
8351 n += 1;
8352 } else {
8353 break;
8354 }
8355 }
8356 self.recon_p_skip_band(x0, y0, n);
8357 if double_recon() {
8358 edcstat::bump(&edcstat::DOUBLED, n as u64);
8359 self.recon_p_skip_band(x0, y0, n);
8360 }
8361 i += n;
8362 continue;
8363 }
8364 edcstat::bump(&edcstat::SKIP_SINGLES, 1);
8365 if *mv != (0, 0) && i >= counted_until && edcstat::on() {
8366 let (x0, y0) = (*mbx, *mby);
8367 let mut n2 = 1usize;
8368 while let Some(EdcJob::Skip {
8369 mbx: nx,
8370 mby: ny,
8371 mv: nmv,
8372 }) = jobs.get(i + n2)
8373 {
8374 if *ny == y0 && *nx == x0 + n2 && nmv == mv {
8375 n2 += 1;
8376 } else {
8377 break;
8378 }
8379 }
8380 if n2 >= 2 {
8381 let fp = mv.0 % 4 == 0 && mv.1 % 4 == 0;
8382 edcstat::bump(
8383 if fp {
8384 &edcstat::PEQ_FP
8385 } else {
8386 &edcstat::PEQ_FRAC
8387 },
8388 n2 as u64,
8389 );
8390 }
8391 counted_until = i + n2;
8392 }
8393 self.recon_p_skip(*mbx, *mby, *mv);
8394 if double_recon() {
8395 edcstat::bump(&edcstat::DOUBLED, 1);
8396 self.recon_p_skip(*mbx, *mby, *mv);
8397 }
8398 }
8399 EdcJob::Inter(job) => {
8400 self.recon_p_inter(job);
8401 if double_recon() {
8402 edcstat::bump(&edcstat::DOUBLED, 1);
8403 self.recon_p_inter(job);
8404 }
8405 }
8406 EdcJob::InterNoRes(job) => {
8407 // D9b: do NOT to_full() — that memset ~2.5 KB of zeros per MB
8408 // then walked add_inter_residual's all-zero path. MC + plane copy.
8409 self.recon_p_inter_nores(job);
8410 if double_recon() {
8411 edcstat::bump(&edcstat::DOUBLED, 1);
8412 self.recon_p_inter_nores(job);
8413 }
8414 }
8415 // B jobs exist only in worker (MT) mode and are never queued
8416 // here — the single-thread seam keeps B inline. Not reachable
8417 // from any input (the push sites are gated on `edc_tx`).
8418 EdcJob::B(_) | EdcJob::BSkip { .. } => unreachable!("B jobs are worker-only"),
8419 }
8420 i += 1;
8421 }
8422 // RECYCLE the job boxes (was: drop = one `free` per coded inter MB).
8423 for j in jobs.drain(..) {
8424 match j {
8425 EdcJob::Inter(b) => self.edc_job_pool.push(b),
8426 EdcJob::InterNoRes(b) => self.edc_nores_pool.push(b),
8427 _ => {}
8428 }
8429 }
8430 // Hand the (now empty) Vec back so its allocation is reused.
8431 self.edc_jobs = jobs;
8432 }
8433
8434 /// Reconstructs one CABAC P inter macroblock from its parse job — the
8435 /// pixel half of the entropy-decouple seam (docs/entropy-decouple-plan.md
8436 /// E1). Reads NOTHING from parse state except the frame grids this MB's
8437 /// parse already committed (its own block MVs/refs, re-gathered below —
8438 /// stable after commit) and the immutable DPB; called either inline
8439 /// (seam off / flush disabled) or in-order at a flush point. Byte-
8440 /// identical to the former inline block by construction: replay order
8441 /// equals inline order at every pixel-observable point (intra reads, row
8442 /// filtering) because flushes precede both.
8443 /// The P-inter reconstruction body, taking its inputs directly rather than
8444 /// through a `PInterJob`. Split out so the residual planes can be passed as
8445 /// `Option`s (absent == nothing was parsed) instead of forcing every caller
8446 /// to own a zeroed copy. NOTE: it re-gathers `gmv`/`gref` from the frame
8447 /// grids rather than reading the job's copies — those are for the worker,
8448 /// which must not touch the parse thread's grids.
8449 fn recon_p_inter_parts(
8450 &mut self,
8451 mbx: usize,
8452 mby: usize,
8453 qp: u8,
8454 cbp_chroma: u32,
8455 luma_scan: Option<&[[i32; 16]; 16]>,
8456 luma8: Option<&[[i32; 64]; 4]>,
8457 cdc: &[[i32; 4]; 2],
8458 cac: Option<&[[[i32; 16]; 4]; 2]>,
8459 nnzs: &[u8; 24],
8460 ) {
8461 let mbw = self.mb_w;
8462 // `add_inter_residual` (and anything under it) reads `self.cur_qp`,
8463 // which at FLUSH time belongs to a later macroblock — replay must
8464 // restore this MB's qp. The x264 corpus (near-constant QP) could not
8465 // see this; the encoder's delta-QP roundtrip stream caught it.
8466 let saved_qp = self.cur_qp;
8467 self.cur_qp = qp;
8468 // ---- Recon: motion-comp (per 4×4 luma / co-located 2×2 chroma using the
8469 // committed grid MV — the 6-tap/bilinear filter is per-output-pixel, so
8470 // per-block MC is bit-identical to per-partition MC) + residual add via the
8471 // SAME reconstruct_4x4 as intra, with the MC output as the prediction.
8472 let w4r = mbw * 4;
8473 let mut pred_y = [0u8; 256];
8474 let mut c_pred = [[0u8; 64]; 2];
8475 {
8476 // MC-CALL COALESCING (side-by-side descent, dec target #2): the old
8477 // loop paid 16 mc_luma(4×4) + 32 mc_chroma(2×2) per MB regardless of
8478 // partitioning — 48 calls even for a single-MV 16×16 MB, and the
8479 // per-call glue around 2.4M calls was ~40% of decoding real-world
8480 // (x264) streams. The 6-tap/bilinear filters are per-output-pixel,
8481 // so merging blocks with equal (mv, ref) into one wider MC call is
8482 // BIT-IDENTICAL; the rect ladder mirrors the partition shapes.
8483 let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
8484 let (rh16, cch) = (self.mb_h * 16, self.mb_h * 8);
8485 let mut gmv = [(0i16, 0i16); 16];
8486 let mut gref = [0usize; 16];
8487 let _gg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::MvGrid);
8488 let nrefs = self.refs.len() - 1;
8489 for by in 0..4usize {
8490 // Row-contiguous: one slice copy for the MVs.
8491 let row = (mby * 4 + by) * w4r + mbx * 4;
8492 gmv[by * 4..by * 4 + 4].copy_from_slice(&self.mv_y[row..row + 4]);
8493 let ridx = &self.ref_idx_y[row..row + 4];
8494 for bx in 0..4usize {
8495 // Per-block reference (multi-ref P): ref_idx_l0 committed to the
8496 // grid. Clamp — a corrupt stream can over-range it (never panic).
8497 gref[by * 4 + bx] = (ridx[bx].max(0) as usize).min(nrefs);
8498 }
8499 }
8500 drop(_gg);
8501 // All blocks of the rect (in 4×4-block units) match its top-left?
8502 let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
8503 let t = y4 * 4 + x4;
8504 (0..h4).all(|dy| {
8505 (0..w4).all(|dx| {
8506 let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
8507 gmv[b] == gmv[t] && gref[b] == gref[t]
8508 })
8509 })
8510 };
8511 let refs = &self.refs;
8512 let (cw, ccw) = (self.cw, self.ccw);
8513 let mc_rect = |x4: usize,
8514 y4: usize,
8515 w4: usize,
8516 h4: usize,
8517 pred_y: &mut [u8; 256],
8518 c_pred: &mut [[u8; 64]; 2]| {
8519 let b = y4 * 4 + x4;
8520 let (mv, gr) = (gmv[b & 15], gref[b & 15]);
8521 // `gref` holds a reference-list slot; a slot the list
8522 // does not have means there is nothing to predict from,
8523 // so the rect is left as it stands.
8524 let Some(reference) = refs.get(gr) else {
8525 return;
8526 };
8527 let (w, h) = (w4 * 4, h4 * 4);
8528 // A FULL-WIDTH rect (w == 16, so x4 == 0) occupies contiguous
8529 // whole rows of `pred_y` — the MC output layout and the
8530 // destination layout coincide, so MC writes the prediction
8531 // buffer DIRECTLY. The staging copy exists only for narrow
8532 // rects, whose rows really are strided in `pred_y`. This is
8533 // the diagnosis's "stage-boundary materialization" tax paid
8534 // by the dominant 16×16/16×8 shapes: 256 B of `t` zeroing
8535 // plus a 256 B copy per rect, for nothing.
8536 if w == 16 {
8537 rusty_h264_common::inter::with_mc_scratch(|scr| {
8538 rusty_h264_common::inter::mc_luma_padded_pre(
8539 scr,
8540 &reference.luma_guard(reference.ch),
8541 reference.lstride(),
8542 crate::LPAD,
8543 cw,
8544 rh16,
8545 mbx * 16,
8546 mby * 16 + y4 * 4,
8547 w,
8548 h,
8549 i32::from(mv.0),
8550 i32::from(mv.1),
8551 &mut pred_y[y4 * 64..y4 * 64 + w * h],
8552 )
8553 });
8554 } else {
8555 let mut t = [0u8; 256];
8556 rusty_h264_common::inter::with_mc_scratch(|scr| {
8557 rusty_h264_common::inter::mc_luma_padded_pre(
8558 scr,
8559 &reference.luma_guard(reference.ch),
8560 reference.lstride(),
8561 crate::LPAD,
8562 cw,
8563 rh16,
8564 mbx * 16 + x4 * 4,
8565 mby * 16 + y4 * 4,
8566 w,
8567 h,
8568 i32::from(mv.0),
8569 i32::from(mv.1),
8570 &mut t[..w * h],
8571 )
8572 });
8573 let _pb =
8574 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
8575 for dy in 0..h {
8576 pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
8577 .copy_from_slice(&t[dy * w..dy * w + w]);
8578 }
8579 }
8580 let (cw4, ch4) = (w4 * 2, h4 * 2);
8581 let nc = cw4 * ch4;
8582 let (gu, gv) = (
8583 reference.chroma_guard(0, reference.ch),
8584 reference.chroma_guard(1, reference.ch),
8585 );
8586 // U+V paired: one setup serves both planes (see
8587 // mc_chroma_padded_pair). Full-width coincidence:
8588 // cw4 == 8 rows are contiguous in the 8-wide plane.
8589 if cw4 == 8 {
8590 let [cu, cv] = &mut *c_pred;
8591 rusty_h264_common::inter::mc_chroma_padded_pair(
8592 &gu,
8593 &gv,
8594 reference.cstride(),
8595 crate::CPAD,
8596 ccw,
8597 cch,
8598 mbx * 8,
8599 mby * 8 + y4 * 2,
8600 cw4,
8601 ch4,
8602 i32::from(mv.0),
8603 i32::from(mv.1),
8604 &mut cu[y4 * 16..y4 * 16 + nc],
8605 &mut cv[y4 * 16..y4 * 16 + nc],
8606 );
8607 } else {
8608 let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
8609 rusty_h264_common::inter::mc_chroma_padded_pair(
8610 &gu,
8611 &gv,
8612 reference.cstride(),
8613 crate::CPAD,
8614 ccw,
8615 cch,
8616 mbx * 8 + x4 * 2,
8617 mby * 8 + y4 * 2,
8618 cw4,
8619 ch4,
8620 i32::from(mv.0),
8621 i32::from(mv.1),
8622 &mut tu[..nc],
8623 &mut tv[..nc],
8624 );
8625 let _pb =
8626 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
8627 for (cc, tc) in [(0usize, &tu), (1, &tv)] {
8628 for dy in 0..ch4 {
8629 c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
8630 .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
8631 }
8632 }
8633 }
8634 };
8635 if rect_eq(0, 0, 4, 4) {
8636 mc_rect(0, 0, 4, 4, &mut pred_y, &mut c_pred);
8637 } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
8638 mc_rect(0, 0, 4, 2, &mut pred_y, &mut c_pred);
8639 mc_rect(0, 2, 4, 2, &mut pred_y, &mut c_pred);
8640 } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
8641 mc_rect(0, 0, 2, 4, &mut pred_y, &mut c_pred);
8642 mc_rect(2, 0, 2, 4, &mut pred_y, &mut c_pred);
8643 } else {
8644 for q in 0..4usize {
8645 let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
8646 if rect_eq(qx, qy, 2, 2) {
8647 mc_rect(qx, qy, 2, 2, &mut pred_y, &mut c_pred);
8648 } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
8649 mc_rect(qx, qy, 2, 1, &mut pred_y, &mut c_pred);
8650 mc_rect(qx, qy + 1, 2, 1, &mut pred_y, &mut c_pred);
8651 } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
8652 mc_rect(qx, qy, 1, 2, &mut pred_y, &mut c_pred);
8653 mc_rect(qx + 1, qy, 1, 2, &mut pred_y, &mut c_pred);
8654 } else {
8655 for j in 0..4usize {
8656 mc_rect(qx + (j % 2), qy + (j / 2), 1, 1, &mut pred_y, &mut c_pred);
8657 }
8658 }
8659 }
8660 }
8661 // EXPLICIT WEIGHTED PREDICTION (spec 8.4.2.3). The CAVLC inter
8662 // path weights each partition after MC; the MC-call-coalescing
8663 // rewrite of this CABAC path lost it, and nothing caught that
8664 // because the effect is invisible unless a stream actually
8665 // carries non-default weights. x264's `weightp` DUPLICATES a
8666 // reference and distinguishes the copy ONLY by its weights, so
8667 // every macroblock picking the weighted index decoded unweighted
8668 // -- a silent, accumulating luma drift.
8669 //
8670 // Applied per 4x4 block rather than per partition: the weight
8671 // depends solely on the block's reference index, so the two are
8672 // equivalent, and `gref` already holds it for every block
8673 // regardless of which rect ladder rung ran.
8674 if self.weights.is_some() {
8675 for by in 0..4usize {
8676 for bx in 0..4usize {
8677 let refi = gref[by * 4 + bx];
8678 self.weight_partition(
8679 &mut pred_y,
8680 &mut c_pred,
8681 0,
8682 refi,
8683 bx * 4,
8684 by * 4,
8685 4,
8686 4,
8687 );
8688 }
8689 }
8690 }
8691 }
8692 // Residual add — the SAME helper the B path uses (this inline
8693 // copy was a duplicate; deduped when the zero-block fast path
8694 // landed so both paths share it).
8695 self.add_inter_residual(
8696 mbx, mby, &pred_y, &c_pred, luma_scan, luma8, cdc, cac, cbp_chroma, nnzs,
8697 );
8698 self.cur_qp = saved_qp;
8699 }
8700
8701 /// Job-shaped entry point (EDC replay + the `double_recon` A/B knob).
8702 fn recon_p_inter(&mut self, j: &PInterJob) {
8703 self.recon_p_inter_parts(
8704 j.mbx,
8705 j.mby,
8706 j.qp,
8707 j.cbp_chroma,
8708 Some(&j.luma_scan),
8709 j.t8.then_some(&j.luma8),
8710 &j.cdc,
8711 Some(&j.cac),
8712 &j.nnzs,
8713 );
8714 }
8715
8716 /// D9b: P inter with `cbp == 0` — MC + plane copy, no coeff memset / residual walk.
8717 /// Byte-identical to `recon_p_inter` on a zero-residual job: prediction is the recon.
8718 fn recon_p_inter_nores(&mut self, j: &PInterNoResJob) {
8719 let w4r = self.mb_w * 4;
8720 // Parse-side nnz for single-thread EDC flush (MT commits earlier via edc_commit_nnz).
8721 // ROW FILLS. Both arms touch the same sixteen cells - four contiguous
8722 // per macroblock row - and wrote them one indexed store at a time (the
8723 // t8 arm through a 2x2-of-2x2 nest, the other through the z-order scan).
8724 for by in 0..4usize {
8725 let r = (j.mby * 4 + by) * w4r + j.mbx * 4;
8726 self.nnz_y[r..r + 4].fill(0);
8727 if j.t8 {
8728 self.coded_y[r..r + 4].fill(true);
8729 }
8730 }
8731 let mut pred_y = [0u8; 256];
8732 let mut c_pred = [[0u8; 64]; 2];
8733 // `self.refs.len() - 1` was re-loaded on every one of the sixteen
8734 // iterations; it is a per-macroblock invariant. Written once, not
8735 // zeroed-then-filled.
8736 let nrefs = self.refs.len() - 1;
8737 let gref: [usize; 16] = core::array::from_fn(|k| (j.gref[k] as usize).min(nrefs));
8738 coalesce_p_inter_mc(
8739 &self.refs,
8740 self.cw,
8741 self.ccw,
8742 self.mb_h,
8743 j.mbx,
8744 j.mby,
8745 &j.gmv,
8746 &gref,
8747 &mut pred_y,
8748 &mut c_pred,
8749 );
8750 // The identity early-out lives INSIDE `weight_partition`, so an x264
8751 // stream - which carries a pred_weight_table in EVERY P slice, identity
8752 // outside fades - still paid sixteen calls per macroblock to be told
8753 // there was nothing to do. Hoisted to one test.
8754 if self.weights.is_some() && !self.weights_l0id {
8755 for by in 0..4usize {
8756 for bx in 0..4usize {
8757 let refi = gref[by * 4 + bx];
8758 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4);
8759 }
8760 }
8761 }
8762 // ONE span per plane: the destination is a stack of contiguous runs a
8763 // fixed stride apart, so the rows share a single bounds check instead of
8764 // one each (16 + 8 + 8 of them).
8765 let (cw, ccw) = (self.cw, self.ccw);
8766 let ybase = (j.mby * 16) * cw + j.mbx * 16;
8767 let ywin = &mut self.rec_y[ybase..ybase + 15 * cw + 16];
8768 for dy in 0..16 {
8769 ywin[dy * cw..dy * cw + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
8770 }
8771 let cbase = (j.mby * 8) * ccw + j.mbx * 8;
8772 for c in 0..2 {
8773 let plane = if c == 0 {
8774 &mut self.rec_u
8775 } else {
8776 &mut self.rec_v
8777 };
8778 let cwin = &mut plane[cbase..cbase + 7 * ccw + 8];
8779 for dy in 0..8 {
8780 cwin[dy * ccw..dy * ccw + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
8781 }
8782 }
8783 }
8784
8785 fn decode_p_skip(&mut self, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
8786 self.route_skip_mbs += 1;
8787 self.wait_refs_for_mb(mb_y);
8788 // DEBLOCK CLASS: a P_Skip macroblock carries no coefficients and one
8789 // (ref, mv) for all 16 blocks, so every internal boundary strength is 0 by
8790 // §8.7.2.1 and the loop filter needs 9 block loads instead of 24. This is
8791 // the single highest-value classification: the MB-kind census measures Skip
8792 // at 36.4% (CAVLC) / 65.0% (main) / 57.8% (high) of real x264 corpora.
8793 // Written HERE because both the CAVLC and the CABAC slice loops funnel
8794 // through this one function.
8795 //
8796 // Deliberately NOT done for `B_Skip` — its motion is direct-derived and can
8797 // differ per 4×4 sub-block, so its internal edges can legally reach
8798 // strength 1. B_Skip stays UNSET and takes the blind path.
8799 // (kind commit moved into the span/immediate arms below.)
8800 // P_Skip always references index 0 (the most recent picture). Borrow it —
8801 // a full-frame `.cloned()` here was ~86% of total decode time (one ~3 MB
8802 // plane copy per skip MB, thousands per frame).
8803 if self.refs.is_empty() {
8804 return Err(MbError::Unsupported("P_Skip without reference"));
8805 }
8806 let addr = mb_y * self.mb_w + mb_x;
8807 // mb_x == 0: the left neighbor is off-frame, so §8.4.1.1's
8808 // unavailability rule forces (0,0) with no gather at all.
8809 let mv = if (mb_x == 0 || self.skip_zero_next == addr) && !no_runmv() {
8810 edcstat::bump(&edcstat::SKIPMV_FORCED, 1);
8811 (0, 0)
8812 } else {
8813 // The gather reads the grids; the pending spans cannot contain
8814 // this MB's left (a non-forced skip means the previous MB was not
8815 // a (0,0) committer) — flush both before deriving.
8816 self.span_flush();
8817 edcstat::bump(&edcstat::SKIPMV_DERIVED, 1);
8818 self.skip_mv(mb_x, mb_y)
8819 };
8820 self.skip_zero_next = if mv == (0, 0) { addr + 1 } else { usize::MAX };
8821 // Grid commits are PARSE state — (0,0) skips defer them into the P
8822 // span (the deferral constants); nonzero-MV skips commit immediately.
8823 if mv == (0, 0) {
8824 // Recon defers too when the band identity holds (1T, no/identity
8825 // weights) — the span flush band-copies instead of queueing jobs.
8826 let recon = self.edc_tx.is_none() && (self.weights.is_none() || self.weights_id0);
8827 self.pz_push(mb_x, mb_y, recon);
8828 if recon {
8829 return Ok(());
8830 }
8831 } else {
8832 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8833 if let Some(p) = self.mb_kind.get_mut(mb_y * self.mb_w + mb_x) {
8834 *p = rusty_h264_common::deblock::MB_KIND_SKIP;
8835 }
8836 self.set_mb_mv(mb_x, mb_y, mv, true, 0);
8837 let w4 = self.mb_w * 4;
8838 for dy in 0..4 {
8839 let a = (mb_y * 4 + dy) * w4 + mb_x * 4;
8840 self.coded_y[a..a + 4].fill(true);
8841 self.modes_y[a..a + 4].fill(2);
8842 }
8843 }
8844 if self.edc_tx.is_some() {
8845 self.edc_giveback();
8846 self.edc_send_job(EdcJob::Skip {
8847 mbx: mb_x,
8848 mby: mb_y,
8849 mv,
8850 });
8851 return Ok(());
8852 }
8853 if self.edc_active {
8854 self.edc_jobs.push(EdcJob::Skip {
8855 mbx: mb_x,
8856 mby: mb_y,
8857 mv,
8858 });
8859 return Ok(());
8860 }
8861 self.recon_p_skip(mb_x, mb_y, mv);
8862 if double_recon() {
8863 self.recon_p_skip(mb_x, mb_y, mv);
8864 }
8865 Ok(())
8866 }
8867
8868 /// Pixel half of P_Skip (see the E1 seam note on `recon_p_inter`).
8869 /// Run-coalesced P_Skip reconstruction: `n` consecutive skip MBs on one
8870 /// row, all with `mv == (0,0)` and NO weighted prediction. At full-pel
8871 /// zero motion, MC is the identity read of `refs[0]` — so the recon is a
8872 /// straight band copy from the padded reference plane, no staging, no MC
8873 /// calls. Byte-identical to `n` calls of `recon_p_skip` by construction
8874 /// (mc_luma_padded/mc_chroma_padded at mv 0 return exactly these bytes).
8875 ///
8876 /// The (0,0) run theorem that makes ONE derivation cover the run: once a
8877 /// skip MB commits (ref 0, mv (0,0)), every later MB of the run derives
8878 /// (0,0) too — its left neighbour (or a row-start's missing neighbour)
8879 /// triggers §8.4.1.1's zero-MV rule in `skip_mv`.
8880 fn recon_p_skip_band(&mut self, mbx0: usize, mby: usize, n: usize) {
8881 let w = n * 16;
8882 let Some(rf0) = self.refs.first() else { return };
8883 let lst = rf0.lstride();
8884 {
8885 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8886 let ly = rf0.luma_guard(rf0.ch);
8887 for dy in 0..16 {
8888 let y = mby * 16 + dy;
8889 let src = (y + crate::LPAD) * lst + crate::LPAD + mbx0 * 16;
8890 let dst = y * self.cw + mbx0 * 16;
8891 // ARMOR (fuzz find, inline-execution.md 11.11): on a MALFORMED
8892 // stream a reference can carry geometry that does not match the
8893 // open picture (mutated parameter sets / truncated ref planes),
8894 // so the in-frame band's ref window can leave the guard slice.
8895 // The band REFUSES rather than panics; a conformant stream
8896 // cannot take this arm (repro: scratchpad repro.264, plane 6400
8897 // vs index 6416). Output on malformed input is unspecified —
8898 // absence of panic is the contract the fuzz gate enforces.
8899 let (Some(d), Some(sr)) = (self.rec_y.get_mut(dst..dst + w), ly.get(src..src + w))
8900 else {
8901 return;
8902 };
8903 d.copy_from_slice(sr);
8904 }
8905 }
8906 let cst = rf0.cstride();
8907 let wc = n * 8;
8908 for c in 0..2 {
8909 let rc = rf0.chroma_guard(c, rf0.ch);
8910 let plane = if c == 0 {
8911 &mut self.rec_u
8912 } else {
8913 &mut self.rec_v
8914 };
8915 for dy in 0..8 {
8916 let y = mby * 8 + dy;
8917 let src = (y + crate::CPAD) * cst + crate::CPAD + mbx0 * 8;
8918 let dst = y * self.ccw + mbx0 * 8;
8919 // Same armor as the luma band above.
8920 let (Some(d), Some(sr)) = (plane.get_mut(dst..dst + wc), rc.get(src..src + wc))
8921 else {
8922 return;
8923 };
8924 d.copy_from_slice(sr);
8925 }
8926 }
8927 edcstat::bump(&edcstat::SKIPBAND_MBS, n as u64);
8928 edcstat::bump(&edcstat::SKIPBAND_RUNS, 1);
8929 }
8930
8931 /// B_Skip zero-bi recon: rec = avg(ref0, ref1') row-wise, straight from the
8932 /// padded planes. Byte-identical to b_mc at mv (0,0)/(0,0): full-pel MC is
8933 /// an identity read and the unweighted bi blend is exactly (a+b+1)>>1 —
8934 /// which the compiler emits as pavgb over the sliced zips, same as b_mc's
8935 /// blend site.
8936 fn recon_b_skip_zero_bi(&mut self, mbx: usize, mby: usize, r0i: usize, r1i: usize) {
8937 let (Some(rf0), Some(rf1)) = (self.refs.get(r0i), self.refs1.get(r1i)) else {
8938 return;
8939 };
8940 let (l0st, l1st) = (rf0.lstride(), rf1.lstride());
8941 {
8942 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
8943 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
8944 for dy in 0..16 {
8945 let y = mby * 16 + dy;
8946 let s0 = (y + crate::LPAD) * l0st + crate::LPAD + mbx * 16;
8947 let s1 = (y + crate::LPAD) * l1st + crate::LPAD + mbx * 16;
8948 let d = y * self.cw + mbx * 16;
8949 // WIN: STRIDED destination, so rustc cannot do what it does for the
8950 // contiguous pred_y averages (which are already ideal vpavgb and are
8951 // left alone). Route the row through the pixel_avg kernel instead.
8952 rusty_h264_common::inter::avg_row_into(
8953 &ly0[s0..s0 + 16],
8954 &ly1[s1..s1 + 16],
8955 16,
8956 &mut self.rec_y[d..d + 16],
8957 );
8958 }
8959 }
8960 let (c0st, c1st) = (rf0.cstride(), rf1.cstride());
8961 for c in 0..2 {
8962 let rc0 = rf0.chroma_guard(c, rf0.ch);
8963 let rc1 = rf1.chroma_guard(c, rf1.ch);
8964 let plane = if c == 0 {
8965 &mut self.rec_u
8966 } else {
8967 &mut self.rec_v
8968 };
8969 for dy in 0..8 {
8970 let y = mby * 8 + dy;
8971 let s0 = (y + crate::CPAD) * c0st + crate::CPAD + mbx * 8;
8972 let s1 = (y + crate::CPAD) * c1st + crate::CPAD + mbx * 8;
8973 let d = y * self.ccw + mbx * 8;
8974 // WIN: STRIDED destination, so rustc cannot do what it does for the
8975 // contiguous pred_y averages (which are already ideal vpavgb and are
8976 // left alone). Route the row through the pixel_avg kernel instead.
8977 rusty_h264_common::inter::avg_row_into(
8978 &rc0[s0..s0 + 8],
8979 &rc1[s1..s1 + 8],
8980 8,
8981 &mut plane[d..d + 8],
8982 );
8983 }
8984 }
8985 }
8986
8987 /// Full-pel P_Skip single: rec window = ref\[0\] window at an integer offset.
8988 /// Returns false (touching nothing) when any window leaves the padded
8989 /// plane — mc's edge clamping takes over there. Luma needs mv%4==0;
8990 /// chroma needs mv%8==0 (its frac is mv&7), so odd full-pel MVs copy luma
8991 /// and interpolate chroma.
8992 fn recon_p_skip_fullpel(&mut self, mbx: usize, mby: usize, mv: (i32, i32)) -> bool {
8993 let (dx, dy) = ((mv.0 / 4) as isize, (mv.1 / 4) as isize);
8994 let Some(rf0) = self.refs.first() else {
8995 return false;
8996 };
8997 let lst = rf0.lstride();
8998 let lpad = crate::LPAD as isize;
8999 let cx = mbx as isize * 16 + dx + lpad;
9000 let cy = mby as isize * 16 + dy + lpad;
9001 {
9002 let ly = rf0.luma_guard(rf0.ch);
9003 let lrows = rf0.lrows() as isize;
9004 if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
9005 return false;
9006 }
9007 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
9008 // ONE span each side: sixteen rows a fixed stride apart, so the
9009 // source and destination checks are paid once instead of per row.
9010 let (cw, sb) = (self.cw, cy as usize * lst + cx as usize);
9011 let db = (mby * 16) * cw + mbx * 16;
9012 let sw = &ly[sb..sb + 15 * lst + 16];
9013 let dw = &mut self.rec_y[db..db + 15 * cw + 16];
9014 for r in 0..16 {
9015 dw[r * cw..r * cw + 16].copy_from_slice(&sw[r * lst..r * lst + 16]);
9016 }
9017 }
9018 let cst = rf0.cstride();
9019 let cpad = crate::CPAD as isize;
9020 if mv.0 % 8 == 0 && mv.1 % 8 == 0 {
9021 let ccx = mbx as isize * 8 + (mv.0 / 8) as isize + cpad;
9022 let ccy = mby as isize * 8 + (mv.1 / 8) as isize + cpad;
9023 let crows = rf0.crows() as isize;
9024 if ccx >= 0 && ccx + 8 <= cst as isize && ccy >= 0 && ccy + 8 <= crows {
9025 for c in 0..2 {
9026 let rc = rf0.chroma_guard(c, rf0.ch);
9027 let plane = if c == 0 {
9028 &mut self.rec_u
9029 } else {
9030 &mut self.rec_v
9031 };
9032 let (ccw, sb) = (self.ccw, ccy as usize * cst + ccx as usize);
9033 let db = (mby * 8) * ccw + mbx * 8;
9034 let sw = &rc[sb..sb + 7 * cst + 8];
9035 let dw = &mut plane[db..db + 7 * ccw + 8];
9036 for r in 0..8 {
9037 dw[r * ccw..r * ccw + 8].copy_from_slice(&sw[r * cst..r * cst + 8]);
9038 }
9039 }
9040 edcstat::bump(&edcstat::SKIP_FP_FULL, 1);
9041 return true;
9042 }
9043 }
9044 // Odd full-pel (or chroma window out of pad): luma is already copied,
9045 // chroma still interpolates — identical to the mc path's chroma half.
9046 let cch = self.mb_h * 8;
9047 for c in 0..2 {
9048 let mut pc = [0u8; 64];
9049 let rc = if c == 0 {
9050 &*rf0.chroma_guard(0, rf0.ch)
9051 } else {
9052 &*rf0.chroma_guard(1, rf0.ch)
9053 };
9054 mc_chroma_padded(
9055 rc,
9056 cst,
9057 crate::CPAD,
9058 self.ccw,
9059 cch,
9060 mbx * 8,
9061 mby * 8,
9062 8,
9063 8,
9064 mv.0,
9065 mv.1,
9066 &mut pc,
9067 );
9068 let plane = if c == 0 {
9069 &mut self.rec_u
9070 } else {
9071 &mut self.rec_v
9072 };
9073 for r in 0..8 {
9074 let d = (mby * 8 + r) * self.ccw + mbx * 8;
9075 plane[d..d + 8].copy_from_slice(&pc[r * 8..r * 8 + 8]);
9076 }
9077 }
9078 edcstat::bump(&edcstat::SKIP_FP_LUMA, 1);
9079 true
9080 }
9081
9082 /// B_Skip full-pel recon with per-list integer offsets: uni = offset row
9083 /// copy, bi = offset row average ((a+b+1)>>1). Luma always copies here
9084 /// (caller guarantees mv%4==0); chroma copies at mv%8==0 and otherwise
9085 /// interpolates via b_mc_chroma — identical to the b_mc path's chroma
9086 /// half with no implicit weights. Returns false untouched when a luma
9087 /// window leaves the padded plane.
9088 fn recon_b_skip_fp(
9089 &mut self,
9090 mbx: usize,
9091 mby: usize,
9092 r0: Option<usize>,
9093 r1: Option<usize>,
9094 mv0: (i32, i32),
9095 mv1: (i32, i32),
9096 ) -> bool {
9097 let lpad = crate::LPAD as isize;
9098 // (start-row, start-col, stride, rows) of each active list's luma window.
9099 let win = |rf: &crate::RefFrame, mv: (i32, i32)| -> Option<(usize, usize)> {
9100 let lst = rf.lstride();
9101 let cx = mbx as isize * 16 + (mv.0 / 4) as isize + lpad;
9102 let cy = mby as isize * 16 + (mv.1 / 4) as isize + lpad;
9103 let lrows = rf.lrows() as isize;
9104 if cx < 0 || cx + 16 > lst as isize || cy < 0 || cy + 16 > lrows {
9105 None
9106 } else {
9107 Some((cy as usize * lst + cx as usize, lst))
9108 }
9109 };
9110 let w0 = match r0 {
9111 Some(i) => match self.refs.get(i).and_then(|rr| win(rr, mv0)) {
9112 Some(w) => Some(w),
9113 None => return false,
9114 },
9115 None => None,
9116 };
9117 let w1 = match r1 {
9118 Some(i) => match self.refs1.get(i).and_then(|rr| win(rr, mv1)) {
9119 Some(w) => Some(w),
9120 None => return false,
9121 },
9122 None => None,
9123 };
9124 {
9125 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
9126 match (w0, w1) {
9127 (Some((s0, l0)), Some((s1, l1))) => {
9128 let Some(rf0) = r0.and_then(|i| self.refs.get(i)) else {
9129 return false;
9130 };
9131 // Same shape as the `rf0` line above: `w1` is `Some` only when
9132 // `r1` is `Some(i)` AND `refs1.get(i)` returned a frame, so the
9133 // `else` is unreachable -- but it is a REFUSAL, not a panic, and
9134 // `recon_b_skip_fp` already answers `false` for every case it
9135 // cannot fast-path. Indexing a `Vec` with an unwrapped `Option`
9136 // spent a discriminant test, an `unwrap_failed` call and a bounds
9137 // check to restate an invariant the caller had already proven.
9138 let Some(rf1) = r1.and_then(|i| self.refs1.get(i)) else {
9139 return false;
9140 };
9141 let (ly0, ly1) = (rf0.luma_guard(rf0.ch), rf1.luma_guard(rf1.ch));
9142 // WIN: this was 256 scalar (p + q + 1) >> 1 per macroblock, three
9143 // modules from the kernel that computes exactly that
9144 // with and already takes both source strides. One kernel
9145 // call over the 16x16, then sixteen row copies out to the strided
9146 // recon plane.
9147 for r in 0..16 {
9148 let d = (mby * 16 + r) * self.cw + mbx * 16;
9149 rusty_h264_common::inter::avg_row_into(
9150 &ly0[s0 + r * l0..],
9151 &ly1[s1 + r * l1..],
9152 16,
9153 &mut self.rec_y[d..d + 16],
9154 );
9155 }
9156 }
9157 (Some((s0, l0)), None) => {
9158 let Some(rf0) = r0.and_then(|i| self.refs.get(i)) else {
9159 return false;
9160 };
9161 let ly = rf0.luma_guard(rf0.ch);
9162 for r in 0..16 {
9163 let d = (mby * 16 + r) * self.cw + mbx * 16;
9164 self.rec_y[d..d + 16].copy_from_slice(&ly[s0 + r * l0..s0 + r * l0 + 16]);
9165 }
9166 }
9167 (None, Some((s1, l1))) => {
9168 // Same shape as the `rf0` line above: `w1` is `Some` only when
9169 // `r1` is `Some(i)` AND `refs1.get(i)` returned a frame, so the
9170 // `else` is unreachable -- but it is a REFUSAL, not a panic, and
9171 // `recon_b_skip_fp` already answers `false` for every case it
9172 // cannot fast-path. Indexing a `Vec` with an unwrapped `Option`
9173 // spent a discriminant test, an `unwrap_failed` call and a bounds
9174 // check to restate an invariant the caller had already proven.
9175 let Some(rf1) = r1.and_then(|i| self.refs1.get(i)) else {
9176 return false;
9177 };
9178 let ly = rf1.luma_guard(rf1.ch);
9179 for r in 0..16 {
9180 let d = (mby * 16 + r) * self.cw + mbx * 16;
9181 self.rec_y[d..d + 16].copy_from_slice(&ly[s1 + r * l1..s1 + r * l1 + 16]);
9182 }
9183 }
9184 (None, None) => unreachable!("caller guarantees an active list"),
9185 }
9186 }
9187 // Chroma: interpolating half — route through b_mc_chroma into a stage
9188 // (weights None: the caller only enters at implicit None/(32,32), and
9189 // (32,32) IS the plain average b_mc_chroma computes for None).
9190 let cch = self.mb_h * 8;
9191 let mut c_pred = [[0u8; 64]; 2];
9192 let (ri0, ri1) = (r0.map_or(-1, |i| i as i32), r1.map_or(-1, |i| i as i32));
9193 self.b_mc_chroma(
9194 mbx,
9195 mby,
9196 0,
9197 0,
9198 16,
9199 16,
9200 ri0,
9201 mv0,
9202 ri1,
9203 mv1,
9204 &mut c_pred,
9205 None,
9206 cch,
9207 );
9208 for c in 0..2 {
9209 let plane = if c == 0 {
9210 &mut self.rec_u
9211 } else {
9212 &mut self.rec_v
9213 };
9214 for r in 0..8 {
9215 let d = (mby * 8 + r) * self.ccw + mbx * 8;
9216 plane[d..d + 8].copy_from_slice(&c_pred[c][r * 8..r * 8 + 8]);
9217 }
9218 }
9219 true
9220 }
9221
9222 fn recon_p_skip(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32)) {
9223 // Full-pel + identity/no weights: MC is a pure offset read, so copy the
9224 // ref window straight into rec — no pred staging, no weight pass. The
9225 // window must sit inside the PADDED plane (outside it, mc's clamping
9226 // differs from a raw offset read, so fall through).
9227 if (self.weights.is_none() || self.weights_id0)
9228 && mv.0 % 4 == 0
9229 && mv.1 % 4 == 0
9230 && !no_skipfp()
9231 && self.recon_p_skip_fullpel(mb_x, mb_y, mv)
9232 {
9233 return;
9234 }
9235 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
9236
9237 let mut pred = [0u8; 256];
9238 let Some(rf0) = self.refs.first() else { return };
9239 mc_luma_padded(
9240 &rf0.luma_guard(rf0.ch),
9241 rf0.lstride(),
9242 crate::LPAD,
9243 self.cw,
9244 ch,
9245 mb_x * 16,
9246 mb_y * 16,
9247 16,
9248 16,
9249 mv.0,
9250 mv.1,
9251 &mut pred,
9252 );
9253 if let Some(wt) = &self.weights {
9254 if !self.weights_id0 || no_skipfp() {
9255 for p in pred.iter_mut() {
9256 *p = wt.apply_luma(*p, 0, 0);
9257 }
9258 }
9259 }
9260 {
9261 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
9262 // ONE span for the sixteen rows (see `recon_p_inter_nores`).
9263 let (cw, base) = (self.cw, (mb_y * 16) * self.cw + mb_x * 16);
9264 let win = &mut self.rec_y[base..base + 15 * cw + 16];
9265 for dy in 0..16 {
9266 win[dy * cw..dy * cw + 16].copy_from_slice(&pred[dy * 16..dy * 16 + 16]);
9267 }
9268 }
9269 let (mut pu, mut pv) = ([0u8; 64], [0u8; 64]);
9270 {
9271 let Some(rf0) = self.refs.first() else { return };
9272 let (gu, gv) = (rf0.chroma_guard(0, rf0.ch), rf0.chroma_guard(1, rf0.ch));
9273 rusty_h264_common::inter::mc_chroma_padded_pair(
9274 &gu,
9275 &gv,
9276 rf0.cstride(),
9277 crate::CPAD,
9278 self.ccw,
9279 cch,
9280 mb_x * 8,
9281 mb_y * 8,
9282 8,
9283 8,
9284 mv.0,
9285 mv.1,
9286 &mut pu,
9287 &mut pv,
9288 );
9289 }
9290 if let Some(wt) = &self.weights {
9291 if !self.weights_id0 || no_skipfp() {
9292 for p in pu.iter_mut() {
9293 *p = wt.apply_chroma(*p, 0, 0, 0);
9294 }
9295 for p in pv.iter_mut() {
9296 *p = wt.apply_chroma(*p, 0, 0, 1);
9297 }
9298 }
9299 }
9300 for (pc, plane) in [(&pu, &mut self.rec_u), (&pv, &mut self.rec_v)] {
9301 for dy in 0..8 {
9302 let d = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
9303 plane[d..d + 8].copy_from_slice(&pc[dy * 8..dy * 8 + 8]);
9304 }
9305 }
9306 }
9307
9308 /// Predicted `Intra_4x4` mode for the block at absolute coords `(bx, by)`.
9309 /// If either the left or top neighbor is outside the frame or in another
9310 /// slice, the prediction is DC (mode 2) (spec §8.3.1.1).
9311 /// `predict_i4_mode` with the MACROBLOCK-level availability already
9312 /// resolved by the caller (`top_ok` / `left_ok`).
9313 ///
9314 /// Twelve of a macroblock's sixteen blocks have BOTH neighbours inside the
9315 /// same macroblock, so their slice test is a foregone conclusion - yet the
9316 /// general form recomputed two `nbr_in_slice` products and two
9317 /// `intra_nbr_ok` tests for every one of them, ~1M times on all-intra
9318 /// content.
9319 ///
9320 /// `constrained_intra` is excluded deliberately: there the general form
9321 /// tests THIS macroblock's own `inter_y` cells for interior blocks, which
9322 /// the macroblock-level flags do not model, so that case defers unchanged.
9323 #[inline]
9324 fn predict_i4_mode_fast(
9325 &self,
9326 bx: usize,
9327 by: usize,
9328 lbx: usize,
9329 lby: usize,
9330 top_ok: bool,
9331 left_ok: bool,
9332 ) -> u8 {
9333 if self.constrained_intra {
9334 return self.predict_i4_mode(bx, by);
9335 }
9336 if !((lbx > 0 || left_ok) && (lby > 0 || top_ok)) {
9337 return 2;
9338 }
9339 let w4 = self.mb_w * 4;
9340 // Fallible reads: two checks on the same grid at two indexes. `2` is DC,
9341 // which is exactly what every unavailable-neighbour path above already
9342 // returns, so the fallback is the function's own existing semantics.
9343 let l = self.modes_y.get(by * w4 + (bx - 1)).copied().unwrap_or(2);
9344 let t = self.modes_y.get((by - 1) * w4 + bx).copied().unwrap_or(2);
9345 l.min(t)
9346 }
9347
9348 fn predict_i4_mode(&self, bx: usize, by: usize) -> u8 {
9349 if bx == 0 || by == 0 {
9350 return 2;
9351 }
9352 // Left neighbor block (bx-1,by); top neighbor block (bx,by-1). A neighbor
9353 // in another slice — or, under constrained_intra, an inter neighbor — is
9354 // unavailable, forcing the predicted mode to DC.
9355 if !self.nbr_in_slice((bx - 1) / 4, by / 4)
9356 || !self.nbr_in_slice(bx / 4, (by - 1) / 4)
9357 || !self.intra_nbr_ok(bx - 1, by)
9358 || !self.intra_nbr_ok(bx, by - 1)
9359 {
9360 return 2;
9361 }
9362 let w4 = self.mb_w * 4;
9363 // Fallible reads: two checks on the same grid at two indexes. `2` is DC,
9364 // which is exactly what every unavailable-neighbour path above already
9365 // returns, so the fallback is the function's own existing semantics.
9366 let l = self.modes_y.get(by * w4 + (bx - 1)).copied().unwrap_or(2);
9367 let t = self.modes_y.get((by - 1) * w4 + bx).copied().unwrap_or(2);
9368 l.min(t)
9369 }
9370
9371 /// Gathers 4×4 luma intra neighbors at pixel `(px, py)` from `rec_y`.
9372 fn gather_i4(
9373 &self,
9374 px: usize,
9375 py: usize,
9376 avail_top: bool,
9377 avail_left: bool,
9378 bx: usize,
9379 by: usize,
9380 ) -> ([u8; 8], [u8; 4], u8) {
9381 let (cw, w4) = (self.cw, self.mb_w * 4);
9382 let mut top = [0u8; 8];
9383 let mut left = [0u8; 4];
9384 let mut corner = 0;
9385 let (lbx, lby) = (bx & 3, by & 3);
9386 if avail_top {
9387 // Row-slice loads: the bak-vs-rec source branch runs once per row
9388 // segment instead of once per PIXEL (top_y_px paid it 8 times).
9389 top[..4].copy_from_slice(self.top_y_row(py, px, 4));
9390 // ROUTED BY POSITION (routing round): below the macroblock's top row the
9391 // top-right neighbour is inside this macroblock (or the undecoded right
9392 // neighbour), so its availability is the z-order constant I4_TR_IN_MB --
9393 // no grid load, no slice test, no constrained-intra call.
9394 let tr_avail = if lby > 0 {
9395 lbx < 3 && (I4_TR_IN_MB >> I4_Z_OF_XY[lby][lbx]) & 1 == 1
9396 } else {
9397 bx + 1 < w4
9398 && self
9399 .coded_y
9400 .get((by - 1) * w4 + (bx + 1))
9401 .copied()
9402 .unwrap_or(false)
9403 && self.nbr_in_slice((bx + 1) / 4, (by - 1) / 4)
9404 && self.intra_nbr_ok(bx + 1, by - 1)
9405 };
9406 if tr_avail {
9407 top[4..8].copy_from_slice(self.top_y_row(py, px + 4, 4));
9408 } else {
9409 let t3 = top[3];
9410 top[4..8].fill(t3);
9411 }
9412 }
9413 if avail_left {
9414 // STRIDED WALK. The span form this replaces claimed `i * cw <= 3 * cw`
9415 // was provable against the span's length; it is not — the same shape
9416 // left five live checks on `recon_i16_luma`'s sixteen-sample column.
9417 // `step_by` does the stride with no index, and `zip` bounds the count.
9418 let base = py * cw + px - 1;
9419 for (s, &v) in left.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
9420 *s = v;
9421 }
9422 }
9423 // The above-left corner has its own availability (block D); under
9424 // constrained_intra it is gone if that block is inter.
9425 // Interior corner is this macroblock's own (intra) sample: no constrained-intra call.
9426 if avail_top && avail_left && ((lbx > 0 && lby > 0) || self.intra_nbr_ok(bx - 1, by - 1)) {
9427 corner = self.top_y_px(py, px - 1);
9428 }
9429 (top, left, corner)
9430 }
9431
9432 /// Reconstructs an `I_PCM` macroblock: byte-aligned raw 8-bit samples, no
9433 /// prediction/transform/quant (spec §7.3.5, §8.3.5).
9434 fn decode_ipcm(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
9435 r.align_to_byte()?;
9436 // ROW SLICES throughout. A PCM macroblock wrote 384 samples one at a
9437 // time, each a separately bounds-checked index into a whole plane, and
9438 // then 104 more scattered context stores below. Every extent here is a
9439 // literal (16, 8, 4, 2), so one slice per row makes the inner index
9440 // provable and the stores become a straight walk.
9441 let (lx, ly) = (mb_x * 16, mb_y * 16);
9442 for dy in 0..16 {
9443 let row = &mut self.rec_y[(ly + dy) * self.cw + lx..][..16];
9444 for px in row.iter_mut() {
9445 *px = r.read_bits(8)? as u8;
9446 }
9447 }
9448 let (cx, cy) = (mb_x * 8, mb_y * 8);
9449 let ccw = self.ccw;
9450 for plane in [&mut self.rec_u, &mut self.rec_v] {
9451 for dy in 0..8 {
9452 let row = &mut plane[(cy + dy) * ccw + cx..][..8];
9453 for px in row.iter_mut() {
9454 *px = r.read_bits(8)? as u8;
9455 }
9456 }
9457 }
9458 // Neighbor context: an I_PCM block contributes TotalCoeff = 16, counts as
9459 // intra with DC mode for prediction, and has no motion (§9.2.1, §8.3.1.2.2).
9460 let (w4, w2) = (self.mb_w * 4, self.mb_w * 2);
9461 // The scan order is irrelevant when every written value is a constant:
9462 // the sixteen `LUMA_4X4_SCAN_XY` positions are exactly the macroblock's
9463 // 4x4 rectangle, so four row fills per array cover them.
9464 for ry in 0..4 {
9465 let base = (mb_y * 4 + ry) * w4 + mb_x * 4;
9466 self.nnz_y[base..][..4].fill(16);
9467 self.modes_y[base..][..4].fill(2);
9468 self.coded_y[base..][..4].fill(true);
9469 self.inter_y[base..][..4].fill(false);
9470 self.ref_idx_y[base..][..4].fill(-1);
9471 self.mv_y[base..][..4].fill((0, 0));
9472 }
9473 for c in 0..2 {
9474 for by in 0..2 {
9475 self.nnz_c[c][(mb_y * 2 + by) * w2 + mb_x * 2..][..2].fill(16);
9476 }
9477 }
9478 Ok(())
9479 }
9480
9481 fn decode_i4x4(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
9482 let w4 = self.mb_w * 4;
9483
9484 // intra4x4 mode signalling
9485 let mut modes = [2u8; 16]; // raster [lby*4+lbx]
9486 for &(lbx, lby) in &LUMA_4X4_SCAN_XY {
9487 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
9488 let predicted = self.predict_i4_mode(bx, by);
9489 let actual = if r.read_bit()? {
9490 predicted
9491 } else {
9492 let rem = r.read_bits(3)? as u8;
9493 if rem < predicted {
9494 rem
9495 } else {
9496 rem + 1
9497 }
9498 };
9499 if let Some(p) = self.modes_y.get_mut(by * w4 + bx) {
9500 *p = actual;
9501 }
9502 modes[(lby & 3) * 4 + (lbx & 3)] = actual;
9503 }
9504
9505 // VALIDATED, not merely narrowed. `read_ue` is exp-Golomb and therefore
9506 // UNBOUNDED, so `as u8` silently truncates: a stream sending 260 would
9507 // arrive here as 4. §7.4.5 bounds intra_chroma_pred_mode to 0..=3, so
9508 // anything else is a malformed stream and is rejected rather than
9509 // predicted with an undefined mode. (Found by the H-17 cast review: this
9510 // was the only one of the decoder's ten narrowing casts on a parsed
9511 // value that could truncate -- the other nine are `read_bits(N) as u8`
9512 // with N <= 8, which is exact by construction.)
9513 let chroma_mode = match r.read_ue()? {
9514 m @ 0..=3 => m as u8,
9515 _ => return Err(MbError::Unsupported("intra_chroma_pred_mode out of range (7.4.5: 0..=3)")),
9516 };
9517 let cbp = read_cbp_intra(r)?;
9518 let cbp_luma = cbp & 15;
9519 let cbp_chroma = cbp >> 4;
9520 if cbp != 0 {
9521 self.step_qp(r.read_se()?)?;
9522 }
9523 let qp = self.cur_qp;
9524
9525 // luma residuals + serial reconstruction. Cross-MB neighbors are only
9526 // available when the adjacent macroblock is in this slice (and, under
9527 // constrained_intra_pred, is itself intra-coded).
9528 let top_mb_avail = mb_y > 0
9529 && self.nbr_in_slice(mb_x, mb_y - 1)
9530 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9531 let left_mb_avail = mb_x > 0
9532 && self.nbr_in_slice(mb_x - 1, mb_y)
9533 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9534 self.nnz_cache_load(mb_x, mb_y);
9535 let mut nnz_raster = [0u8; 16];
9536 // PARSE all sixteen blocks first (nC prediction needs only the counts), so
9537 // the residual transforms run as ONE batched IDCT before the serial
9538 // predict+add walk. Parse and recon were interleaved per block before.
9539 let mut scans = [[0i32; 16]; 16];
9540 let mut totals = [0u8; 16];
9541 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
9542 let total = if cbp_luma & (1 << (blk / 4)) != 0 {
9543 let nc = self.nc_pred(lbx, lby);
9544 decode_residual_block_into::<16, 16>(r, nc, &mut scans[blk & 15])?
9545 } else {
9546 0
9547 };
9548 self.nnz_cache_set(lbx, lby, total);
9549 nnz_raster[(lby & 3) * 4 + (lbx & 3)] = total;
9550 totals[blk & 15] = total;
9551 }
9552 let kinds = self.i4_prepare(&scans, &totals, qp);
9553 let dq0 = self.dq_const(qp, 0);
9554 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
9555 let (bx, by) = (mb_x * 4 + lbx, mb_y * 4 + lby);
9556 let avail_top = lby > 0 || top_mb_avail;
9557 let avail_left = lbx > 0 || left_mb_avail;
9558 self.recon_i4_block_res(
9559 bx,
9560 by,
9561 modes[(lby & 3) * 4 + (lbx & 3)],
9562 avail_top,
9563 avail_left,
9564 kinds[blk & 15],
9565 &scans,
9566 &dq0,
9567 );
9568 }
9569 for ry in 0..4usize {
9570 self.coded_y[(mb_y * 4 + ry) * w4 + mb_x * 4..][..4].fill(true);
9571 }
9572 // Deferred: `recon_i4_block` gathers from `coded_y` / `rec_y`, never
9573 // from `nnz_y`, so one contiguous copy per row replaces sixteen stores.
9574 for by in 0..4usize {
9575 let a = (mb_y * 4 + by) * w4 + mb_x * 4;
9576 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
9577 }
9578
9579 self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
9580 }
9581
9582 /// Decodes an `I_8x8` macroblock (High profile): four 8×8 luma blocks, each
9583 /// with its own intra mode, 8×8 transform residual (CAVLC = four interleaved
9584 /// 4×4 blocks), and 8×8 intra prediction.
9585 fn decode_i8x8(&mut self, r: &mut BitReader, mb_x: usize, mb_y: usize) -> Result<(), MbError> {
9586 let w4 = self.mb_w * 4;
9587 if let Some(p) = self.mb_t8x8.get_mut(mb_y * self.mb_w + mb_x) {
9588 *p = true;
9589 }
9590 self.any_t8 = true;
9591
9592 // intra8x8 mode signalling — one mode per 8×8 block (raster 0..3),
9593 // stored into all four of its 4×4 cells so neighbors can read it.
9594 let mut modes8 = [2u8; 4];
9595 for (b8, mode) in modes8.iter_mut().enumerate() {
9596 let (b8x, b8y) = (b8 % 2, b8 / 2);
9597 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
9598 let predicted = self.predict_i4_mode(bx, by);
9599 let actual = if r.read_bit()? {
9600 predicted
9601 } else {
9602 let rem = r.read_bits(3)? as u8;
9603 if rem < predicted {
9604 rem
9605 } else {
9606 rem + 1
9607 }
9608 };
9609 *mode = actual;
9610 for sy in 0..2 {
9611 // Row fill: the 2x2 cell block is two contiguous PAIRS.
9612 self.modes_y[(by + sy) * w4 + bx..][..2].fill(actual);
9613 }
9614 }
9615
9616 // VALIDATED, not merely narrowed. `read_ue` is exp-Golomb and therefore
9617 // UNBOUNDED, so `as u8` silently truncates: a stream sending 260 would
9618 // arrive here as 4. §7.4.5 bounds intra_chroma_pred_mode to 0..=3, so
9619 // anything else is a malformed stream and is rejected rather than
9620 // predicted with an undefined mode. (Found by the H-17 cast review: this
9621 // was the only one of the decoder's ten narrowing casts on a parsed
9622 // value that could truncate -- the other nine are `read_bits(N) as u8`
9623 // with N <= 8, which is exact by construction.)
9624 let chroma_mode = match r.read_ue()? {
9625 m @ 0..=3 => m as u8,
9626 _ => return Err(MbError::Unsupported("intra_chroma_pred_mode out of range (7.4.5: 0..=3)")),
9627 };
9628 let cbp = read_cbp_intra(r)?;
9629 let cbp_luma = cbp & 15;
9630 let cbp_chroma = cbp >> 4;
9631 if cbp != 0 {
9632 self.step_qp(r.read_se()?)?;
9633 }
9634 let qp = self.cur_qp;
9635
9636 let top_mb_avail = mb_y > 0
9637 && self.nbr_in_slice(mb_x, mb_y - 1)
9638 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9639 let left_mb_avail = mb_x > 0
9640 && self.nbr_in_slice(mb_x - 1, mb_y)
9641 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9642 self.nnz_cache_load(mb_x, mb_y);
9643
9644 let mut nnz_raster = [0u8; 16];
9645 for b8 in 0..4 {
9646 let (b8x, b8y) = (b8 % 2, b8 / 2);
9647 let (bx, by) = (mb_x * 4 + b8x * 2, mb_y * 4 + b8y * 2);
9648
9649 // residual: 8×8 CAVLC = four 4×4 sub-blocks, coeff k of sub-block s
9650 // mapping to 8×8 scan position 4·k + s (spec §7.3.5.3.2).
9651 let mut scan8 = [0i32; 64];
9652 let coded8 = cbp_luma & (1 << b8) != 0;
9653 if coded8 {
9654 for sub in 0..4 {
9655 let (sx, sy) = (sub % 2, sub / 2);
9656 let (cx, cy) = (b8x * 2 + sx, b8y * 2 + sy);
9657 let nc = self.nc_pred(cx, cy);
9658 let mut blk = [0i32; 16];
9659 let total = decode_residual_block_into::<16, 16>(r, nc, &mut blk)?;
9660 self.nnz_cache_set(cx, cy, total);
9661 // Deferred to one row copy per MB row after the loop (`recon_i8_block`
9662 // gathers from `coded_y` / `rec_y`, never `nnz_y`) -- was a checked store per sub.
9663 nnz_raster[(cy & 3) * 4 + (cx & 3)] = total;
9664 for k in 0..16 {
9665 scan8[4 * k + sub] = blk[k];
9666 }
9667 }
9668 } else {
9669 for sub in 0..4 {
9670 let (sx, sy) = (sub % 2, sub / 2);
9671 self.nnz_cache_set(b8x * 2 + sx, b8y * 2 + sy, 0);
9672 // `nnz_raster` is already zero here.
9673 }
9674 }
9675
9676 let avail_top = b8y > 0 || top_mb_avail;
9677 let avail_left = b8x > 0 || left_mb_avail;
9678 self.recon_i8_block(
9679 bx,
9680 by,
9681 modes8[b8],
9682 avail_top,
9683 avail_left,
9684 coded8.then_some(&scan8),
9685 qp,
9686 );
9687 for sy in 0..2 {
9688 // Row fill: the 2x2 cell block is two contiguous PAIRS.
9689 self.coded_y[(by + sy) * w4 + bx..][..2].fill(true);
9690 }
9691 }
9692
9693 // ONE contiguous copy per macroblock row (see the I4x4 arm).
9694 for ry in 0..4usize {
9695 let a = (mb_y * 4 + ry) * w4 + mb_x * 4;
9696 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[ry * 4..ry * 4 + 4]);
9697 }
9698
9699 self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
9700 }
9701
9702 /// Dequantizes + inverse-transforms an 8×8 luma block, applying the scaling
9703 /// matrix `list` (0 = intra, 1 = inter) or flat weights.
9704 fn inv_quant8(&self, raster: &[i32; 64], qp: u8, list: usize) -> [i32; 64] {
9705 // Prebuilt per-(qp, list) constants: the flat table at zero cost, or one
9706 // 64-entry build per macroblock for scaling lists (was 64 x two lookups +
9707 // a multiply per coefficient on every block).
9708 match &self.scaling8 {
9709 Some(s) => inverse_quant_8x8_dq(raster, &Dequant8Qp::build(qp, &s[list])),
9710 None => inverse_quant_8x8_dq(raster, &DQ8_FLAT[(qp as usize).min(51)]),
9711 }
9712 }
9713
9714 /// Gathers the 8×8 luma intra reference samples at pixel `(px, py)`: the 16
9715 /// top samples (8..15 substituted from the last when no top-right), 8 left
9716 /// samples, the above-left corner, and whether the corner is available.
9717 fn gather_i8(
9718 &self,
9719 px: usize,
9720 py: usize,
9721 avail_top: bool,
9722 avail_left: bool,
9723 bx: usize,
9724 by: usize,
9725 ) -> ([u8; 16], [u8; 8], u8, bool) {
9726 let (cw, w4) = (self.cw, self.mb_w * 4);
9727 let mut top = [0u8; 16];
9728 let mut left = [0u8; 8];
9729 let mut corner = 0;
9730 if avail_top {
9731 // Row-slice loads: one source branch per segment, not per pixel.
9732 top[..8].copy_from_slice(self.top_y_row(py, px, 8));
9733 let tr_avail = bx + 2 < w4
9734 && self
9735 .coded_y
9736 .get((by - 1) * w4 + (bx + 2))
9737 .copied()
9738 .unwrap_or(false)
9739 && self.nbr_in_slice((bx + 2) / 4, (by - 1) / 4)
9740 && self.intra_nbr_ok(bx + 2, by - 1);
9741 if tr_avail {
9742 top[8..16].copy_from_slice(self.top_y_row(py, px + 8, 8));
9743 } else {
9744 let t7 = top[7];
9745 top[8..16].fill(t7);
9746 }
9747 }
9748 if avail_left {
9749 // STRIDED WALK, NOT STRIDED INDEXING. A span plus `col[i * cw]` still
9750 // checks, because `i * cw <= 7 * cw` is not something LLVM derives
9751 // against the span's own length; `step_by` on the iterator does the
9752 // stride with no index at all, and `zip` bounds the count.
9753 let base = py * cw + px - 1;
9754 for (s, &v) in left.iter_mut().zip(self.rec_y[base..].iter().step_by(cw)) {
9755 *s = v;
9756 }
9757 }
9758 let avail_corner = avail_top && avail_left && self.intra_nbr_ok(bx - 1, by - 1);
9759 if avail_corner {
9760 corner = self.top_y_px(py, px - 1);
9761 }
9762 (top, left, corner, avail_corner)
9763 }
9764
9765 fn decode_i16(
9766 &mut self,
9767 r: &mut BitReader,
9768 mb_x: usize,
9769 mb_y: usize,
9770 mt: u32,
9771 ) -> Result<(), MbError> {
9772 let pred_mode = I16Mode::from_id(mt % 4);
9773 let cbp_chroma = (mt % 12) / 4;
9774 let cbp_luma_15 = mt / 12 == 1;
9775 // VALIDATED, not merely narrowed. `read_ue` is exp-Golomb and therefore
9776 // UNBOUNDED, so `as u8` silently truncates: a stream sending 260 would
9777 // arrive here as 4. §7.4.5 bounds intra_chroma_pred_mode to 0..=3, so
9778 // anything else is a malformed stream and is rejected rather than
9779 // predicted with an undefined mode. (Found by the H-17 cast review: this
9780 // was the only one of the decoder's ten narrowing casts on a parsed
9781 // value that could truncate -- the other nine are `read_bits(N) as u8`
9782 // with N <= 8, which is exact by construction.)
9783 let chroma_mode = match r.read_ue()? {
9784 m @ 0..=3 => m as u8,
9785 _ => return Err(MbError::Unsupported("intra_chroma_pred_mode out of range (7.4.5: 0..=3)")),
9786 };
9787 self.step_qp(r.read_se()?)?;
9788 let qp = self.cur_qp;
9789 let w4 = self.mb_w * 4;
9790
9791 // luma DC
9792 self.nnz_cache_load(mb_x, mb_y);
9793 let nc_dc = self.nc_pred(0, 0);
9794 let mut dc_scan = [0i32; 16];
9795 decode_residual_block_into::<16, 16>(r, nc_dc, &mut dc_scan)?;
9796 let recon_dc =
9797 inverse_quant_luma_dc_scan(&dc_scan, qp, self.scaling.as_ref().map(|s| s[0][0]));
9798
9799 // luma AC (nnz set for all 16 blocks: 0 when DC-only, matching the encoder)
9800 let mut nnz_raster = [0u8; 16];
9801 let mut q_blocks = [[0i32; 16]; 16];
9802 for &(bx, by) in &LUMA_4X4_SCAN_XY {
9803 let total = if cbp_luma_15 {
9804 let nc = self.nc_pred(bx, by);
9805 let mut ac = [0i32; 16];
9806 let t = decode_residual_block_into::<15, 16>(r, nc, &mut ac)?;
9807 // Zero-skip: an empty AC block leaves the fresh-zero raster
9808 // block untouched (un-scanning 16 zeros wrote zeros on zeros).
9809 if t != 0 {
9810 q_blocks[(by & 3) * 4 + (bx & 3)] = ac; // SCAN order (fused kernel)
9811 }
9812 t
9813 } else {
9814 0
9815 };
9816 self.nnz_cache_set(bx, by, total);
9817 nnz_raster[(by & 3) * 4 + (bx & 3)] = total;
9818 }
9819 let coded16 = nnz_raster
9820 .iter()
9821 .enumerate()
9822 .fold(0u16, |m, (i, &n)| m | (((n != 0) as u16) << i));
9823 // ONE contiguous copy per row (nothing reads `nnz_y` for this
9824 // macroblock in between - `nc_pred` predicts from `nnz_cache`).
9825 for by in 0..4usize {
9826 let a = (mb_y * 4 + by) * w4 + mb_x * 4;
9827 self.nnz_y[a..a + 4].copy_from_slice(&nnz_raster[by * 4..by * 4 + 4]);
9828 }
9829
9830 // prediction + reconstruction
9831 let avail_top = mb_y > 0
9832 && self.nbr_in_slice(mb_x, mb_y - 1)
9833 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9834 let avail_left = mb_x > 0
9835 && self.nbr_in_slice(mb_x - 1, mb_y)
9836 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9837 self.recon_i16_luma(
9838 mb_x,
9839 mb_y,
9840 pred_mode,
9841 avail_top,
9842 avail_left,
9843 Some(&q_blocks),
9844 coded16,
9845 &recon_dc,
9846 qp,
9847 );
9848 // I_16x16 blocks are treated as DC for neighbor mode prediction.
9849 for lby in 0..4usize {
9850 let a = (mb_y * 4 + lby) * w4 + mb_x * 4;
9851 self.modes_y[a..a + 4].fill(2);
9852 }
9853
9854 self.decode_chroma(r, mb_x, mb_y, cbp_chroma, chroma_mode)
9855 }
9856
9857 /// Reads and reconstructs the chroma residual (shared by both luma types).
9858 fn decode_chroma(
9859 &mut self,
9860 r: &mut BitReader,
9861 mb_x: usize,
9862 mb_y: usize,
9863 cbp_chroma: u32,
9864 chroma_mode: u8,
9865 ) -> Result<(), MbError> {
9866 let qpc = self.chroma_qp_for(self.cur_qp);
9867 let avail_top = mb_y > 0
9868 && self.nbr_in_slice(mb_x, mb_y - 1)
9869 && self.intra_nbr_ok(mb_x * 4, mb_y * 4 - 1);
9870 let avail_left = mb_x > 0
9871 && self.nbr_in_slice(mb_x - 1, mb_y)
9872 && self.intra_nbr_ok(mb_x * 4 - 1, mb_y * 4);
9873
9874 let mut c_recon_dc = [[0i32; 4]; 2];
9875 if cbp_chroma != 0 {
9876 for (c, slot) in c_recon_dc.iter_mut().enumerate() {
9877 let mut dc = [0i32; 4];
9878 decode_residual_block_into::<4, 4>(r, -1, &mut dc)?;
9879 *slot = self.dequant_chroma_dc(&dc, qpc, 1 + c);
9880 }
9881 }
9882 let mut c_q_blocks = [[[0i32; 16]; 4]; 2];
9883 let mut ccoded = [0u8; 2];
9884 if cbp_chroma == 2 {
9885 self.chroma_cache_load(mb_x, mb_y);
9886 let w2 = self.mb_w * 2;
9887 for c in 0..2 {
9888 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
9889 let nc = self.chroma_nc_pred(c, bx, by);
9890 let mut ac = [0i32; 16];
9891 let total = decode_residual_block_into::<15, 16>(r, nc, &mut ac)?;
9892 self.chroma_nnz_cache_set(c, bx, by, total);
9893 if let Some(n) =
9894 self.nnz_c[c & 1].get_mut((mb_y * 2 + by) * w2 + (mb_x * 2 + bx))
9895 {
9896 *n = total;
9897 }
9898 // Zero-skip: empty AC leaves the fresh-zero raster block.
9899 if total != 0 {
9900 ccoded[c & 1] |= 1u8 << ((by * 2 + bx) & 3);
9901 // WIN: c < 2, by < 2, bx < 2, so both indexes are in range and
9902 // the masks PROVE it -- the ccoded[c & 1] |= 1 << ((by*2+bx) & 3)
9903 // on the line above already uses exactly these. This was the last
9904 // panic_bounds_check on the decoder hot path.
9905 c_q_blocks[c & 1][(by * 2 + bx) & 3] = ac; // SCAN order (fused kernel)
9906 }
9907 }
9908 }
9909 }
9910 self.recon_chroma_blocks(
9911 mb_x,
9912 mb_y,
9913 chroma_mode,
9914 avail_top,
9915 avail_left,
9916 &c_q_blocks,
9917 ccoded,
9918 &c_recon_dc,
9919 qpc,
9920 );
9921 Ok(())
9922 }
9923
9924 /// Applies the in-loop deblocking filter to the reconstructed frame, with
9925 /// the slice's `FilterOffsetA`/`FilterOffsetB` (each = the coded `*_div2`
9926 /// value × 2).
9927 /// Per-frame per-MB dump for conformance bisection, keyed on `RH264_DUMP_MB`.
9928 /// Prints one char per macroblock: `i` = intra, otherwise the List-0 reference
9929 /// index of the MB's top-left 4x4 block. Directly comparable with ffmpeg's
9930 /// `-debug mb_type` map, which is the only per-MB ground truth we can get out
9931 /// of the reference decoder.
9932 fn dump_mb_map(&self) {
9933 if !dump_mb_on() {
9934 return;
9935 }
9936 let w4 = self.mb_w * 4;
9937 let mut hist = [0usize; 4];
9938 eprintln!("--- frame poc {} ---", self.cur_poc);
9939 for mb_y in 0..self.mb_h {
9940 let mut row = String::new();
9941 for mb_x in 0..self.mb_w {
9942 let b = (mb_y * 4) * w4 + mb_x * 4;
9943 let Some(&r) = self.ref_idx_y.get(b) else {
9944 continue;
9945 };
9946 if r < 0 {
9947 row.push('i');
9948 } else {
9949 if (r as usize) < 4 {
9950 hist[r as usize] += 1;
9951 }
9952 row.push((b'0' + (r as u8).min(9)) as char);
9953 }
9954 }
9955 eprintln!("{row}");
9956 }
9957 eprintln!(
9958 "ref histogram: {hist:?} num_ref_active={} refs.len()={} OUT-OF-RANGE={}",
9959 self.num_ref_active,
9960 self.refs.len(),
9961 hist.iter().skip(self.refs.len()).sum::<usize>()
9962 );
9963 let list: Vec<String> = self
9964 .refs
9965 .iter()
9966 .enumerate()
9967 .map(|(i, f)| {
9968 // A synthesized frame_num-gap frame is uniform grey with w4 == 0;
9969 // flag it, because it silently displaces real pictures in the list.
9970 let synth = if f.w4 == 0 { " SYNTH-GREY" } else { "" };
9971 format!("[{i}] poc={} fn={}{synth}", f.poc, f.frame_num)
9972 })
9973 .collect();
9974 eprintln!(" RefPicList0: {}", list.join(" "));
9975 }
9976
9977 /// Finish the work `deblock` would have flushed first, for a picture with the
9978 /// loop filter off (`disable_deblocking_filter_idc == 1`). Without it a slice
9979 /// ending in a run of P_Skip macroblocks leaves its last deferred span
9980 /// unreconstructed: zero samples in the bottom rows of the picture.
9981 pub fn flush_deferred(&mut self) {
9982 self.span_flush();
9983 self.edc_flush();
9984 }
9985
9986 pub fn deblock(&mut self, offset_a: i32, offset_b: i32) {
9987 self.span_flush(); // deferred grid spans feed bS derivation below
9988 self.edc_flush(); // backstop: no pixel job may survive to filtering
9989 self.dump_mb_map();
9990 // ROW MODE: finish any rows not derived during decode (mid-row slice
9991 // ends, error paths) FIRST, while `self` is still mutably borrowable.
9992 if rowdb_on() {
9993 while self.bs_rows < self.mb_h {
9994 let r = self.bs_rows;
9995 self.derive_bs_row(r);
9996 self.bs_rows += 1;
9997 }
9998 }
9999 // Deblock boundary strength uses the *transform block's* coded status. For
10000 // an 8×8-transform macroblock the unit is the whole 8×8, so every 4×4 cell
10001 // shares the 8×8's coefficient presence (OR of its four sub-block counts)
10002 // — distinct from the per-sub-block `nnz_y` used for the CAVLC nC context.
10003 // Only differs from `nnz_y` when some MB uses the 8×8 transform (High
10004 // profile). On Baseline (no 8×8) it's identical — skip the clone + rewrite.
10005 // Row-interleave already derived bS into `bs_frame`; the filter then
10006 // reads only `bs`+`t8x8`+qp — do not clone nnz or rebuild POC maps.
10007 let rowdb = rowdb_on();
10008 let nnz_db_storage;
10009 let nnz_db: &[u8] = if rowdb {
10010 &[]
10011 } else if self.mb_t8x8.iter().any(|&t| t) {
10012 let mut n = self.nnz_y.clone();
10013 let w4 = self.mb_w * 4;
10014 for mb_y in 0..self.mb_h {
10015 for mb_x in 0..self.mb_w {
10016 if !self
10017 .mb_t8x8
10018 .get(mb_y * self.mb_w + mb_x)
10019 .copied()
10020 .unwrap_or(false)
10021 {
10022 continue;
10023 }
10024 for b8 in 0..4 {
10025 let (bx, by) = (mb_x * 4 + (b8 % 2) * 2, mb_y * 4 + (b8 / 2) * 2);
10026 let any = (0..2).any(|sy| {
10027 self.nnz_y[(by + sy) * w4 + bx..][..2]
10028 .iter()
10029 .any(|&v| v > 0)
10030 });
10031 for sy in 0..2 {
10032 // Row fill: the 2x2 cell block is two contiguous PAIRS.
10033 n[(by + sy) * w4 + bx..][..2].fill(u8::from(any));
10034 }
10035 }
10036 }
10037 }
10038 nnz_db_storage = n;
10039 &nnz_db_storage
10040 } else {
10041 &self.nnz_y
10042 };
10043 let mut info = rusty_h264_common::deblock::BlockInfo {
10044 inter: if rowdb { &[] } else { &self.inter_y },
10045 nnz: nnz_db,
10046 mv: if rowdb { &[] } else { &self.mv_y },
10047 ref_id: if rowdb { &[] } else { &self.ref_idx_y },
10048 mv1: if rowdb { &[] } else { &self.mv1 },
10049 ref_id1: if rowdb || self.ref_poc1.is_empty() {
10050 &[]
10051 } else {
10052 &self.ref_idx1
10053 },
10054 w4: self.mb_w * 4,
10055 t8x8: &self.mb_t8x8,
10056 bs: &[],
10057 poc0: if rowdb { &[] } else { &self.ref_poc0 },
10058 poc1: if rowdb { &[] } else { &self.ref_poc1 },
10059 kind: &self.mb_kind,
10060 };
10061 // ROW MODE (R2): rows were derived during decode; the remainder was
10062 // finished above (before `info` borrowed the grids). Fallback: the
10063 // Part 16/17 picture-end precompute; `RS_H264_BS_PRE=0` further falls
10064 // back to the pack-then-derive-in-loop pipeline.
10065 let bs_store;
10066 if rowdb {
10067 bs_store = core::mem::take(&mut self.bs_frame);
10068 info.bs = &bs_store;
10069 } else if bs_pre_on() {
10070 let mut buf = Vec::new();
10071 rusty_h264_common::deblock::precompute_bs_frame(&info, self.mb_w, self.mb_h, &mut buf);
10072 bs_store = buf;
10073 info.bs = &bs_store;
10074 } else {
10075 bs_store = Vec::new();
10076 }
10077 let first_row = if rowdb { self.flt_rows } else { 0 };
10078 rusty_h264_common::deblock::filter_frame_rows_pre(
10079 &mut self.rec_y,
10080 &mut self.rec_u,
10081 &mut self.rec_v,
10082 self.mb_w,
10083 self.mb_h,
10084 first_row..self.mb_h,
10085 &self.mb_qp,
10086 self.chroma_qp_offset,
10087 offset_a,
10088 offset_b,
10089 &info,
10090 );
10091 drop(info);
10092 if rowdb {
10093 self.bs_frame = bs_store;
10094 }
10095 }
10096
10097 /// Crops the reconstructed coded-size planes to the display window.
10098 /// `into_frame`, additionally handing the per-picture grids back for reuse by
10099 /// the next picture. See `GridPool` for why this is worth doing.
10100 pub fn into_frame_recycle(mut self, crop_r: usize, crop_b: usize) -> (YuvFrame, GridPool) {
10101 let [c0, c1] = core::mem::take(&mut self.nnz_c);
10102 let pool = GridPool {
10103 job_pool: core::mem::take(&mut self.edc_job_pool),
10104 nores_pool: core::mem::take(&mut self.edc_nores_pool),
10105 bits_per_mb: self.bits_per_mb,
10106 mb_qp: core::mem::take(&mut self.mb_qp),
10107 bs_frame: core::mem::take(&mut self.bs_frame),
10108 pk_prev: core::mem::take(&mut self.pk_prev),
10109 pk_cur: core::mem::take(&mut self.pk_cur),
10110 nnz_dbr: core::mem::take(&mut self.nnz_dbr),
10111 bak_y: core::mem::take(&mut self.bak_y),
10112 bak_u: core::mem::take(&mut self.bak_u),
10113 bak_v: core::mem::take(&mut self.bak_v),
10114 nnz_y: core::mem::take(&mut self.nnz_y),
10115 nnz_c0: c0,
10116 nnz_c1: c1,
10117 modes_y: core::mem::take(&mut self.modes_y),
10118 coded_y: core::mem::take(&mut self.coded_y),
10119 mv_y: core::mem::take(&mut self.mv_y),
10120 inter_y: core::mem::take(&mut self.inter_y),
10121 ref_idx_y: core::mem::take(&mut self.ref_idx_y),
10122 mv1: core::mem::take(&mut self.mv1),
10123 ref_idx1: core::mem::take(&mut self.ref_idx1),
10124 mb_t8x8: core::mem::take(&mut self.mb_t8x8),
10125 mb_umot: core::mem::take(&mut self.mb_umot),
10126 mb_kind: core::mem::take(&mut self.mb_kind),
10127 bzero: core::mem::take(&mut self.bzero),
10128 sc_cat: core::mem::take(&mut self.sc_cat),
10129 sc_cbp: core::mem::take(&mut self.sc_cbp),
10130 sc_cmode: core::mem::take(&mut self.sc_cmode),
10131 sc_nzc: core::mem::take(&mut self.sc_nzc),
10132 sc_cbfdc: core::mem::take(&mut self.sc_cbfdc),
10133 sc_skip: core::mem::take(&mut self.sc_skip),
10134 sc_ref: core::mem::take(&mut self.sc_ref),
10135 sc_mvd: core::mem::take(&mut self.sc_mvd),
10136 sc_ref1: core::mem::take(&mut self.sc_ref1),
10137 sc_mvd1: core::mem::take(&mut self.sc_mvd1),
10138 sc_direct: core::mem::take(&mut self.sc_direct),
10139 ref_poc0: core::mem::take(&mut self.ref_poc0),
10140 ref_poc1: core::mem::take(&mut self.ref_poc1),
10141 };
10142 (self.into_frame(crop_r, crop_b), pool)
10143 }
10144
10145 pub fn into_frame(self, crop_r: usize, crop_b: usize) -> YuvFrame {
10146 // No cropping (the common case): the reconstruction planes ARE the output —
10147 // move them out instead of allocating + copying three full planes per frame.
10148 if crop_r == 0 && crop_b == 0 {
10149 return YuvFrame {
10150 width: self.cw,
10151 height: self.ch,
10152 y: self.rec_y,
10153 u: self.rec_u,
10154 v: self.rec_v,
10155 };
10156 }
10157 let dw = self.cw - 2 * crop_r;
10158 let dh = self.ch - 2 * crop_b;
10159 let mut y = vec![0u8; dw * dh];
10160 for row in 0..dh {
10161 y[row * dw..row * dw + dw]
10162 .copy_from_slice(&self.rec_y[row * self.cw..row * self.cw + dw]);
10163 }
10164 let (cdw, cdh) = (dw / 2, dh / 2);
10165 let mut u = vec![0u8; cdw * cdh];
10166 let mut v = vec![0u8; cdw * cdh];
10167 for row in 0..cdh {
10168 u[row * cdw..row * cdw + cdw]
10169 .copy_from_slice(&self.rec_u[row * self.ccw..row * self.ccw + cdw]);
10170 v[row * cdw..row * cdw + cdw]
10171 .copy_from_slice(&self.rec_v[row * self.ccw..row * self.ccw + cdw]);
10172 }
10173 let _ = self.cch;
10174 YuvFrame {
10175 width: dw,
10176 height: dh,
10177 y,
10178 u,
10179 v,
10180 }
10181 }
10182}
10183
10184/// Reads `ref_idx_l0` as `te(v)` with range `num_ref_active - 1`: a single flag
10185/// when exactly two references are active (cMax == 1), else `ue(v)`.
10186// ---- CABAC binarization engine helpers (openh264 cabac_decoder.cpp) ----
10187
10188/// Unary bin (`DecodeUnaryBinCabac`): bin0 at `ctx`; if 1, count bins at `ctx+off`
10189/// (including the terminating 0) until a 0.
10190/// CAVLC `mvd_lX` with a sanity bound. `se(v)` can legally code ±2^31-1, but
10191/// every profile/level caps |MV| far below ±2^17 quarter-pel units; beyond
10192/// that is a corrupt stream, and the unchecked `pmv + mvd` addition would
10193/// overflow i32 (debug panic / release wrap into an absurd vector).
10194fn read_mvd(r: &mut BitReader) -> Result<i32, MbError> {
10195 let v = r.read_se()?;
10196 if v.unsigned_abs() > (1 << 17) {
10197 return Err(MbError::Truncated);
10198 }
10199 Ok(v)
10200}
10201
10202type Eng = crate::cabac::Engine;
10203type Ctx = crate::cabac::Ctx;
10204
10205fn cabac_unary(e: &mut Eng, data: &[u8], ctx: &mut Ctx, c: usize, off: usize) -> u32 {
10206 if e.decode_decision(data, ctx, c) == 0 {
10207 return 0;
10208 }
10209 let mut sym = 0;
10210 loop {
10211 let bin = e.decode_decision(data, ctx, c + off);
10212 sym += 1;
10213 // Cap the unary run: no valid H.264 element coded through this helper
10214 // (mb_qp_delta) exceeds a few dozen bins, but on malformed / buffer-exhausted
10215 // input the arithmetic engine keeps yielding 1s (it zero-fills past the end),
10216 // which would loop forever. 512 is far beyond any legal value.
10217 if bin == 0 || sym >= 512 {
10218 break;
10219 }
10220 }
10221 sym
10222}
10223
10224/// k-th order Exp-Golomb in bypass (`DecodeExpBypassCabac`). Out of line (it is the
10225/// rare escape arm of levels and mvds), so it takes the engine BY VALUE and hands
10226/// it back: a `&mut Engine` here would be the one address escape in the hot
10227/// callers and LLVM would then home their engine copy on the stack for EVERY bin.
10228#[inline(never)]
10229fn cabac_exp_bypass(mut e: Eng, data: &[u8], mut count: i32) -> (u32, Eng) {
10230 let mut sym = 0u32;
10231 loop {
10232 let c = e.decode_bypass(data);
10233 if c == 1 {
10234 sym += 1 << count;
10235 count += 1;
10236 }
10237 if c == 0 || count == 16 {
10238 break;
10239 }
10240 }
10241 let mut sym2 = 0u32;
10242 while count > 0 {
10243 count -= 1;
10244 if e.decode_bypass(data) != 0 {
10245 sym2 |= 1 << count;
10246 }
10247 }
10248 (sym + sym2, e)
10249}
10250
10251/// UEG0 coeff-level suffix (`DecodeUEGLevelCabac`): TU prefix at `c` (<=13) then an
10252/// EG0 bypass suffix. INLINED ALWAYS into the four residual-parser instantiations:
10253/// out of line it took `&mut Engine`, which put the engine back in memory for
10254/// every level >= 2 and spilled the caller around the call.
10255#[inline(always)]
10256fn cabac_ueg_level(e: &mut Eng, data: &[u8], ctx: &mut Ctx, c: usize) -> u32 {
10257 if e.decode_decision(data, ctx, c) == 0 {
10258 return 0;
10259 }
10260 let mut code = 0u32;
10261 let mut tmp;
10262 loop {
10263 tmp = e.decode_decision(data, ctx, c);
10264 code += 1;
10265 if tmp == 0 || code == 12 {
10266 break;
10267 }
10268 }
10269 if tmp != 0 {
10270 let (v, e2) = cabac_exp_bypass(*e, data, 0);
10271 *e = e2;
10272 code += v + 1;
10273 }
10274 code
10275}
10276
10277/// `mb_qp_delta` CABAC (`ParseDeltaQpCabac`): ctxIdxOffset 60, ctxInc = (prev delta != 0).
10278pub fn parse_mb_qp_delta_cabac(cab: &mut crate::cabac::Cabac, last_delta_qp: &mut i32) -> i32 {
10279 const O: usize = 60;
10280 let ctx_inc = (*last_delta_qp != 0) as usize;
10281 let mut qp_delta = 0;
10282 let (data, mut e, ctx) = cab.view();
10283 if e.decode_decision(data, ctx, O + ctx_inc) != 0 {
10284 let code = cabac_unary(&mut e, data, ctx, O + 2, 1) + 1;
10285 qp_delta = ((code + 1) >> 1) as i32;
10286 if code & 1 == 0 {
10287 qp_delta = -qp_delta;
10288 }
10289 }
10290 cab.commit(e);
10291 *last_delta_qp = qp_delta;
10292 qp_delta
10293}
10294
10295// Shared CABAC residual glue tables (NZC_CACHE, RES_*) now live in
10296// `cabac_tables` — both coders read the ONE copy.
10297use rusty_h264_common::cabac_tables::{
10298 NZC_CACHE, RES_CBF, RES_MAP, RES_MAXC2, RES_MAXPOS, RES_ONE,
10299};
10300// res-property values (post GetMbResProperty, CABAC): the ctx-table index.
10301const RP_I16_DC: usize = 1;
10302const RP_I16_AC: usize = 2;
10303const RP_LUMA_4X4: usize = 3;
10304const RP_CHROMA_DC: usize = 7; // U (V=8, same offsets)
10305const RP_CHROMA_AC: usize = 9; // U (V=10, same offsets)
10306/// Luma 8×8 (ctxBlockCat 5). Its RES_MAP/RES_CBF entries stay 0: cat 5 does NOT
10307/// share the `105 + off` / `166 + off` context bases the 4×4 categories use — it
10308/// has its own absolute bases (402 sig, 417 last) and its own per-position
10309/// ctxIdxInc maps below. RES_ONE[6] = 199 IS used, because 227 + 199 = 426 and
10310/// 232 + 199 = 431 reproduce the spec's coeff_abs_level_minus1 base exactly, so
10311/// the level loop needs no special case at all.
10312const RP_LUMA_8X8: usize = 6;
10313
10314// SIG8X8 / LAST8X8 moved to `rusty_h264_common::cabac_tables` (R6-1) so the encoder's
10315// ctxBlockCat 5 writer shares the exact spec data this reader is validated against.
10316use rusty_h264_common::cabac_tables::{LAST8X8, SIG8X8};
10317
10318/// One residual block (openh264 `ParseResidualBlockCabac`), generic over the 5 CABAC
10319/// block categories. `rp` selects the context offsets. DC categories (I16 luma DC,
10320/// chroma DC) take the cbf context from the per-MB `cbf_dc` bitmask + neighbour MB DC
10321/// cbf; AC categories from the padded nzc cache. Returns totalCoeffNum.
10322#[inline(always)]
10323fn residual_block_eng<const RP: usize, const N: usize>(
10324 e: &mut Eng,
10325 data: &[u8],
10326 ctx: &mut Ctx,
10327 nzc: &mut [u8; 48],
10328 cbf_dc: &mut u16,
10329 iz: usize,
10330 plane: usize,
10331 is_intra: bool,
10332 ndc: (u16, u16), // resolved neighbour DC cbf words (see `resolve_ndc`)
10333 out: &mut [i32; N], // scan-order coefficients written here (fresh-zero on entry)
10334) -> u32 {
10335 // CONST-GENERIC over the block category (`RP`, one of the RP_* literals every
10336 // call site already passed) and the block length (`N` = 4 / 16 / 64): the
10337 // category tests, the five `RES_*` table reads, the 8x8 map selects and the
10338 // output bound all fold at compile time. Chroma U/V share every table
10339 // entry (RES_*[7] == RES_*[8], RES_*[9] == RES_*[10]); only the cbf_dc bit
10340 // differs, so the plane rides in `plane` (0 for luma categories).
10341 const { assert!(N == 4 || N == 16 || N == 64) };
10342 // The CABAC residual parse IS the entropy stage of the decoder on Main-profile
10343 // streams -- it was invisible (a ~47% residue) until this scope named it.
10344 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Entropy);
10345 // ---- coded_block_flag ----
10346 // ctxBlockCat 5 is the ONLY category with no coded_block_flag: its presence is
10347 // inferred from CodedBlockPatternLuma, so parsing one here would desync.
10348 let is8 = RP == RP_LUMA_8X8;
10349 let is_dc = RP == RP_I16_DC || RP == RP_CHROMA_DC;
10350 let bit = RP + plane;
10351 let (mut na, mut nb) = (is_intra as u8, is_intra as u8);
10352 // `nzc` is [u8; 48] and every `NZC_CACHE` entry lies in [9, 47], so this
10353 // clamp is a semantic no-op that hands LLVM BOTH bounds - which is what
10354 // makes `nzc[scan]`, `nzc[scan - 8]` and `nzc[scan - 1]` below provable
10355 // instead of three bounds checks. Same trick as `parse_mvd_partition`.
10356 let scan = NZC_CACHE[iz.min(23)].clamp(8, 47);
10357 if is_dc {
10358 nb = ((ndc.0 >> bit) & 1) as u8;
10359 na = ((ndc.1 >> bit) & 1) as u8;
10360 } else {
10361 let (nbc, nac) = (nzc[scan - 8], nzc[scan - 1]);
10362 if nbc != 0xff {
10363 nb = (nbc != 0) as u8;
10364 }
10365 if nac != 0xff {
10366 na = (nac != 0) as u8;
10367 }
10368 }
10369 if !is8 {
10370 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntCbf);
10371 let cbf = e.decode_decision(data, ctx, 85 + RES_CBF[RP] + (na + (nb << 1)) as usize);
10372 if cbf == 0 {
10373 if !is_dc {
10374 nzc[scan] = 0;
10375 }
10376 return 0;
10377 }
10378 if is_dc {
10379 *cbf_dc |= 1 << bit;
10380 }
10381 }
10382 // ---- significance map ----
10383 let maxpos = RES_MAXPOS[RP] as usize;
10384 debug_assert!(maxpos < N);
10385 // cat 5 uses its own absolute bases; the 4x4 categories share 105/166 + offset.
10386 let (map, last) = if is8 {
10387 (402, 417)
10388 } else {
10389 let m = RES_MAP[RP];
10390 (105 + m, 166 + m)
10391 };
10392 // SPARSE significance map: record each significant POSITION in `pos[..n]`.
10393 // Bin ORDER is unchanged: levels are decoded at descending significant
10394 // positions, which is exactly `pos[..n]` reversed. `pos` is sized to the
10395 // block (N), not 64: a 4x4 block zeroes 16 bytes, not 64.
10396 //
10397 // CONTRACT with the callers (all 10 sites): `out` is freshly zeroed, so
10398 // writing only the significant entries leaves the same contents a dense
10399 // copy would produce. A reused non-zero `out` would be a correctness bug.
10400 // Significant positions as a BITMASK (bit i = scan position i): one OR per
10401 // significant coefficient instead of a store + count increment into a
10402 // zero-initialised array; the level loop walks the set bits from the top
10403 // (descending positions = the spec order).
10404 let mut sig = 0u64;
10405 let mut last_hit = false;
10406 let _sg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntSig);
10407 // 4x4: ctxIdxInc IS the scan position. 8x8: it comes from the folded maps.
10408 // NOTE (4:2:2 landmine): `(i, i)` is correct for every 4:2:0 category
10409 // only because chroma-DC (cat 3) has NumC8x8 == 1 here; spec 9.3.3.1.3
10410 // wants `Min(i / NumC8x8, 2)` for its sig/last ctxIdxInc, which
10411 // diverges the day 4:2:2 (NumC8x8 == 2) is admitted.
10412 for i in 0..maxpos {
10413 // Both maps are [u8; 64] and `i < maxpos <= 63`: `& 63` folds the checks.
10414 let (mi, li) = if is8 {
10415 (
10416 (SIG8X8[i & 63] & 15) as usize,
10417 (LAST8X8[i & 63] & 15) as usize,
10418 )
10419 } else {
10420 (i, i)
10421 };
10422 if e.decode_decision(data, ctx, map + mi) != 0 {
10423 // `n <= maxpos < N` at every write: `& (N - 1)` is the own bound of `pos`.
10424 sig |= 1u64 << i;
10425 if e.decode_decision(data, ctx, last + li) != 0 {
10426 last_hit = true;
10427 break;
10428 }
10429 }
10430 }
10431 if !last_hit {
10432 sig |= 1u64 << maxpos;
10433 }
10434 let coeff_num = sig.count_ones();
10435 // ---- levels ----
10436 let one = 227 + RES_ONE[RP];
10437 let abs = one + 5;
10438 // `usize` with visible `min` bounds: as i32 the casts hid the ranges and every
10439 // `one + c1` / `abs + c2` context index kept its `& 511` (one op per bin).
10440 let maxc2 = RES_MAXC2[RP] as usize;
10441 let (mut c1, mut c2) = (1usize, 0usize);
10442 drop(_sg);
10443 let _lg = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::EntLvl);
10444 let mut m = sig;
10445 while m != 0 {
10446 let i = 63 - m.leading_zeros() as usize;
10447 m &= !(1u64 << i);
10448 let mut level = 1 + e.decode_decision(data, ctx, one + c1) as i32;
10449 if level == 2 {
10450 level += cabac_ueg_level(e, data, ctx, abs + c2) as i32;
10451 c2 = (c2 + 1).min(maxc2);
10452 c1 = 0;
10453 } else if c1 != 0 {
10454 c1 = (c1 + 1).min(4);
10455 }
10456 if e.decode_bypass_sign() != 0 {
10457 level = -level;
10458 }
10459 // `out` is `[i32; N]` and every recorded position is <= maxpos < N, so
10460 // `& (N - 1)` is the own bound of the array -- a proof, not a relocation
10461 // (the runtime-length-slice form this replaces needed `.get_mut`).
10462 out[i & (N - 1)] = level;
10463 }
10464 if is8 {
10465 // One 8x8 covers four consecutive z-order 4x4 cells. Every later
10466 // coded_block_flag ctxIdxInc reads this cache, so all four must carry the
10467 // count -- writing only `scan` would corrupt the contexts of the NEXT
10468 // macroblock.
10469 let cn = coeff_num as u8;
10470 for k in 0..4 {
10471 nzc[NZC_CACHE[(iz + k).min(23)].min(47)] = cn;
10472 }
10473 } else if !is_dc {
10474 nzc[scan] = coeff_num as u8;
10475 }
10476 coeff_num
10477}
10478
10479/// Where a macroblock residual parse writes: plain arrays whose UNCODED blocks
10480/// are stale by contract (every consumer reads a block only when its `nnzs`
10481/// entry is nonzero, or copies the prediction). The parse zeroes exactly the
10482/// blocks it is about to write.
10483struct ResidualOut<'a> {
10484 luma: &'a mut [[i32; 16]; 16],
10485 luma8: &'a mut [[i32; 64]; 4],
10486 cdc: &'a mut [[i32; 4]; 2],
10487 cac: &'a mut [[[i32; 16]; 4]; 2],
10488 /// Per-block totalCoeff, `iz`-indexed; must be zero on entry.
10489 nnzs: &'a mut [u8; 24],
10490}
10491
10492/// The neighbour-record form of a macroblock per-block coefficient counts:
10493/// luma in RASTER order (from the z-order `nnzs`, via G_SCAN4, which is its own
10494/// inverse) and chroma Cb/Cr in the interleaved slot order the readers expect.
10495/// Uncoded blocks are already 0 in `nnzs`, so the 0xff->0 scrub of the old
10496/// cache-based export is gone with the 24 scattered cache reads.
10497/// Explicit-weighted-prediction sample: `((s*w + round) >> denom) + o`, clipped.
10498#[inline(always)]
10499fn weight_sample(sample: u8, w: i32, o: i32, denom: i32) -> u8 {
10500 let v = if denom >= 1 {
10501 ((sample as i32 * w + (1 << (denom - 1))) >> denom) + o
10502 } else {
10503 sample as i32 * w + o
10504 };
10505 v.clamp(0, 255) as u8
10506}
10507
10508/// CONST-WIDTH rows of the weighted-prediction loop (SIMD census 2026-09-05,
10509/// finding #9). The two recon twins carried the IDENTICAL runtime-width loop
10510/// (`for dx in 0..rw` over a masked index) and LLVM vectorised it to 500 packed
10511/// ops in one inlining context and 53 in the other. A partition width is one
10512/// of 16/8/4 (chroma 8/4/2); handing LLVM the trip count makes both contexts
10513/// vectorise the same way. One row slice per row instead of a masked index per
10514/// sample; the sample formula is unchanged.
10515#[inline(always)]
10516fn weight_rows<const W: usize>(
10517 plane: &mut [u8],
10518 stride: usize,
10519 rx: usize,
10520 ry: usize,
10521 rh: usize,
10522 w: i32,
10523 o: i32,
10524 denom: i32,
10525) {
10526 for dy in 0..rh {
10527 let row = &mut plane[(ry + dy) * stride + rx..][..W];
10528 for s in row.iter_mut() {
10529 *s = weight_sample(*s, w, o, denom);
10530 }
10531 }
10532}
10533
10534#[inline]
10535fn weight_block(
10536 plane: &mut [u8],
10537 stride: usize,
10538 rx: usize,
10539 ry: usize,
10540 rw: usize,
10541 rh: usize,
10542 w: i32,
10543 o: i32,
10544 denom: i32,
10545) {
10546 match rw {
10547 16 => weight_rows::<16>(plane, stride, rx, ry, rh, w, o, denom),
10548 8 => weight_rows::<8>(plane, stride, rx, ry, rh, w, o, denom),
10549 4 => weight_rows::<4>(plane, stride, rx, ry, rh, w, o, denom),
10550 2 => weight_rows::<2>(plane, stride, rx, ry, rh, w, o, denom),
10551 _ => {
10552 for dy in 0..rh {
10553 for dx in 0..rw {
10554 let i = (ry + dy) * stride + rx + dx;
10555 if let Some(s) = plane.get_mut(i) {
10556 *s = weight_sample(*s, w, o, denom);
10557 }
10558 }
10559 }
10560 }
10561 }
10562}
10563
10564/// Residual class of an intra 4x4 block (see `FrameDecoder::i4_prepare`).
10565#[derive(Clone, Copy)]
10566enum I4Res {
10567 Zero,
10568 Flat(i32),
10569 Idx(u8),
10570}
10571
10572/// z-order index of the 4x4 block at raster (x, y) -- inverse of `LUMA_4X4_SCAN_XY`.
10573const I4_Z_OF_XY: [[usize; 4]; 4] = {
10574 let mut t = [[0usize; 4]; 4];
10575 let mut i = 0;
10576 while i < 16 {
10577 let (x, y) = LUMA_4X4_SCAN_XY[i];
10578 t[y][x] = i;
10579 i += 1;
10580 }
10581 t
10582};
10583/// Bit i set iff the top-right 4x4 neighbour of z-order block i lies INSIDE the
10584/// macroblock and is already reconstructed when block i is (spec 6.4.11.4 via the
10585/// decode order): the classic i4x4 top-right availability table, here derived from
10586/// the scan table at compile time instead of hand-typed.
10587const I4_TR_IN_MB: u16 = {
10588 let mut m = 0u16;
10589 let mut i = 0;
10590 while i < 16 {
10591 let (x, y) = LUMA_4X4_SCAN_XY[i];
10592 if y > 0 && x < 3 && I4_Z_OF_XY[y - 1][x + 1] < i {
10593 m |= 1 << i;
10594 }
10595 i += 1;
10596 }
10597 m
10598};
10599
10600// SPARSE-vs-DENSE DEQUANT ROUTE (routing round C, 2026-09-05): the DQROUTE census
10601// (scatter = 37 + 6L + 9nnz vs dense-AVX2 ~60; crowd 134.8M -> 80.3M instrs / 60 f)
10602// retired the scan-walking scatter; every coded block now takes the FUSED
10603// scan-order kernel (`reconstruct_4x4_scan_into`: un-scan + dequant + IDCT + add).
10604// The (nnz, L) histogram tap `edcstat::dq_note` stays for the next route question.
10605
10606/// Neighbour DC coded_block_flags as plain bit words: an unavailable neighbour
10607/// contributes the intra default for EVERY category, so the per-block
10608/// `Option` test becomes one shift. Resolved once per macroblock.
10609#[inline(always)]
10610fn resolve_ndc(ndc: (Option<u16>, Option<u16>), is_intra: bool) -> (u16, u16) {
10611 let d = if is_intra { 0xFFFF } else { 0 };
10612 (ndc.0.unwrap_or(d), ndc.1.unwrap_or(d))
10613}
10614
10615/// The WHOLE residual of one non-I16 macroblock (luma 4x4 or 8x8 by cbp bit,
10616/// chroma DC, chroma AC) on ONE engine view. The block body is inlined here,
10617/// so a macroblock costs one call and one engine load/store, where each block
10618/// used to pay a call, eight arguments (five on the stack), an eight-register
10619/// prologue/epilogue and its own view/commit round trip -- ~40 instructions
10620/// per block beside the bins, on 7-24 blocks per coded macroblock.
10621fn parse_mb_residual_cabac<const INTRA: bool>(
10622 cab: &mut crate::cabac::Cabac,
10623 nzc: &mut [u8; 48],
10624 cbf_dc: &mut u16,
10625 ndc: (Option<u16>, Option<u16>),
10626 cbp_luma: u32,
10627 cbp_chroma: u32,
10628 t8: bool,
10629 o: ResidualOut,
10630) {
10631 // INTRA is a const generic: the cbf defaults and the neighbour-word resolve
10632 // fold per instantiation (every call site passes a literal).
10633 let is_intra = INTRA;
10634 let nd = resolve_ndc(ndc, INTRA);
10635 let (data, mut e, ctx) = cab.view();
10636 for id8 in 0..4usize {
10637 if cbp_luma & (1 << id8) != 0 {
10638 if t8 {
10639 // ctxBlockCat 5: one 64-coefficient block per 8x8, no cbf. All
10640 // four nnzs slots carry the 8x8 total (the recon reads one per cell).
10641 o.luma8[id8] = [0i32; 64];
10642 let n = residual_block_eng::<RP_LUMA_8X8, 64>(
10643 &mut e,
10644 data,
10645 ctx,
10646 nzc,
10647 cbf_dc,
10648 id8 * 4,
10649 0,
10650 is_intra,
10651 nd,
10652 &mut o.luma8[id8],
10653 ) as u8;
10654 o.nnzs[id8 * 4..id8 * 4 + 4].fill(n);
10655 } else {
10656 o.luma[id8 * 4..id8 * 4 + 4].fill([0i32; 16]);
10657 for id4 in 0..4usize {
10658 let iz = id8 * 4 + id4;
10659 o.nnzs[iz] = residual_block_eng::<RP_LUMA_4X4, 16>(
10660 &mut e,
10661 data,
10662 ctx,
10663 nzc,
10664 cbf_dc,
10665 iz,
10666 0,
10667 is_intra,
10668 nd,
10669 &mut o.luma[iz],
10670 ) as u8;
10671 }
10672 }
10673 } else {
10674 for k in 0..4 {
10675 nzc[NZC_CACHE[(id8 * 4 + k).min(23)].min(47)] = 0;
10676 }
10677 }
10678 }
10679 if cbp_chroma >= 1 {
10680 *o.cdc = [[0i32; 4]; 2];
10681 for i in 0..2usize {
10682 residual_block_eng::<RP_CHROMA_DC, 4>(
10683 &mut e,
10684 data,
10685 ctx,
10686 nzc,
10687 cbf_dc,
10688 16 + i * 4,
10689 i,
10690 is_intra,
10691 nd,
10692 &mut o.cdc[i],
10693 );
10694 }
10695 }
10696 if cbp_chroma == 2 {
10697 *o.cac = [[[0i32; 16]; 4]; 2];
10698 for i in 0..2usize {
10699 for id4 in 0..4usize {
10700 o.nnzs[16 + i * 4 + id4] = residual_block_eng::<RP_CHROMA_AC, 16>(
10701 &mut e,
10702 data,
10703 ctx,
10704 nzc,
10705 cbf_dc,
10706 16 + i * 4 + id4,
10707 i,
10708 is_intra,
10709 nd,
10710 &mut o.cac[i][id4],
10711 ) as u8;
10712 }
10713 }
10714 }
10715 cab.commit(e);
10716}
10717
10718// ============================================================================
10719// Entropy-decouple E2: the OWNED pixel context that crosses the thread
10720// boundary (docs/entropy-decouple-plan.md). The worker owns the planes, the
10721// backup rows, the DPB Arcs and its own qp/t8/bs grids (fed by Row messages);
10722// the parse thread keeps every syntax grid. The methods below are ports of
10723// the FrameDecoder pixel halves — grid writes removed (parse commits its own
10724// grids), motion carried in the job instead of re-gathered.
10725// ============================================================================
10726
10727/// The committed-value class of a deferred B_Skip span. Continuation
10728/// requires kind EQUALITY: the flush range-fills grids and recons the band
10729/// from these values alone.
10730#[derive(Clone, Copy, PartialEq, Eq)]
10731enum BzKind {
10732 /// ref 0 both lists, (0,0)/(0,0) — band = avg of both padded refs.
10733 ZeroBi,
10734 /// One active list at (0,0): (list, clamped ref index) — band = memcpy.
10735 ZeroUni(u8, u8),
10736 /// Uniform full-pel direct: clamped refs (-1 = inactive) + adjusted MVs,
10737 /// windows PREVALIDATED per MB at push (contiguous tiles ⇒ the union
10738 /// window is valid) — band = offset copy/avg. Chroma requires mv%8==0
10739 /// (also prevalidated).
10740 Fp {
10741 r0: i8,
10742 r1: i8,
10743 m0: (i16, i16),
10744 m1: (i16, i16),
10745 },
10746}
10747
10748/// Messages from the parse thread to the pixel worker.
10749enum EdcMsg {
10750 Job(EdcJob),
10751 /// A ROW's worth of pixel jobs in one message (D10).
10752 ///
10753 /// The seam sent ONE message per macroblock: 208k sends per 60-frame pass
10754 /// against 2,596 rows. The overhead is per-JOB (channel lock, park/unpark)
10755 /// while the prize is proportional to pixel WORK, so the per-job send was
10756 /// dividing the payoff by ~80 for nothing. Batching per row cuts the
10757 /// synchronisation events by that factor and moves not one byte of pixel
10758 /// work off the worker.
10759 ///
10760 /// ORDER IS THE CORRECTNESS CONDITION: the worker must see a row's jobs
10761 /// before that row's `Row` filter message, so the batch is flushed at every
10762 /// row boundary, before `NeedCtx`, and at slice end.
10763 Batch(Vec<EdcJob>),
10764 /// A macroblock row finished parsing: install its qp/t8/bs and filter it.
10765 Row {
10766 r: usize,
10767 bs: Vec<rusty_h264_common::deblock::MbBs>,
10768 qp: Vec<u8>,
10769 t8: Vec<bool>,
10770 },
10771 /// An intra macroblock needs the planes on the parse thread: send the
10772 /// context over and wait for it to come back.
10773 NeedCtx,
10774}
10775
10776pub(crate) struct PixelCtx {
10777 rec_y: Vec<u8>,
10778 rec_u: Vec<u8>,
10779 rec_v: Vec<u8>,
10780 bak_y: Vec<u8>,
10781 bak_u: Vec<u8>,
10782 bak_v: Vec<u8>,
10783 refs: Vec<crate::Ref>,
10784 refs1: Vec<crate::Ref>,
10785 weights: Option<WeightTable>,
10786 /// `weights.list_identity(0)`, cached - mirrors `FrameDecoder::weights_l0id`
10787 /// so the worker twin does not have to walk the table per macroblock.
10788 weights_l0id: bool,
10789 scaling: Option<[[i32; 16]; 6]>,
10790 scaling8: Option<[[i32; 64]; 2]>,
10791 cw: usize,
10792 ccw: usize,
10793 mb_w: usize,
10794 mb_h: usize,
10795 chroma_qp_offset: i32,
10796 flt_rows: usize,
10797 db_ena: bool,
10798 db_oa: i32,
10799 db_ob: i32,
10800 cur_qp: u8,
10801 qp_grid: Vec<u8>,
10802 t8_grid: Vec<bool>,
10803 bs_store: Vec<rusty_h264_common::deblock::MbBs>,
10804 /// Frame-MT Phase B progress Arc (row publish from the EDC worker).
10805 progress: Option<crate::Ref>,
10806}
10807
10808impl PixelCtx {
10809 /// Frame-MT Phase B: copy filtered MB rows into the shared progress Arc.
10810 fn publish_progress_rows(&self) {
10811 let Some(slot) = &self.progress else {
10812 return;
10813 };
10814 if !self.db_ena || self.flt_rows == 0 {
10815 return;
10816 }
10817 publish_filtered_rows_to_slot(
10818 slot,
10819 &self.rec_y,
10820 &self.rec_u,
10821 &self.rec_v,
10822 self.cw,
10823 self.ccw,
10824 self.mb_h * 16,
10825 self.flt_rows,
10826 );
10827 }
10828
10829 fn chroma_qp_for(&self, qp: u8) -> u8 {
10830 rusty_h264_common::predict::chroma_qp(
10831 ((qp as i32 + self.chroma_qp_offset).clamp(0, 51)) as u8,
10832 )
10833 }
10834
10835 /// Per-(qp, list) dequant constants for the fused scan-order kernel (worker twin).
10836 #[inline]
10837 fn dq_const(&self, qp: u8, list: usize) -> DequantQp {
10838 match &self.scaling {
10839 Some(s) => DequantQp::weighted(qp, &s[list]),
10840 None => DQ_FLAT[(qp as usize).min(51)],
10841 }
10842 }
10843
10844 fn dequant_dc4(&self, level: i32, qp: u8, list: usize) -> i32 {
10845 rusty_h264_common::transform::dequantize_dc4(
10846 level,
10847 qp,
10848 self.scaling.as_ref().map(|sc| sc[list][0]),
10849 )
10850 }
10851
10852 fn inv_quant8(&self, raster: &[i32; 64], qp: u8, list: usize) -> [i32; 64] {
10853 // Prebuilt per-(qp, list) constants: the flat table at zero cost, or one
10854 // 64-entry build per macroblock for scaling lists (was 64 x two lookups +
10855 // a multiply per coefficient on every block).
10856 match &self.scaling8 {
10857 Some(s) => inverse_quant_8x8_dq(raster, &Dequant8Qp::build(qp, &s[list])),
10858 None => inverse_quant_8x8_dq(raster, &DQ8_FLAT[(qp as usize).min(51)]),
10859 }
10860 }
10861
10862 fn dequant_chroma_dc(&self, levels: &[i32; 4], qp: u8, list: usize) -> [i32; 4] {
10863 match &self.scaling {
10864 Some(sc) => inverse_quant_chroma_dc_weighted(levels, qp, sc[list][0]),
10865 None => inverse_quant_chroma_dc(levels, qp),
10866 }
10867 }
10868
10869 fn weight_partition(
10870 &self,
10871 pred_y: &mut [u8; 256],
10872 c_pred: &mut [[u8; 64]; 2],
10873 list: usize,
10874 refi: usize,
10875 rx: usize,
10876 ry: usize,
10877 rw: usize,
10878 rh: usize,
10879 ) {
10880 let Some(wt) = &self.weights else { return };
10881 // REFUTED, reverted: row-slicing these loops measured +28% instructions
10882 // on `weight_partition` (the extents are runtime values, so the slice
10883 // bounds cost more than the per-sample checks they replaced). The real
10884 // win for this function was hoisting the identity test to its CALLERS,
10885 // which stands.
10886 // RESOLVE ONCE, APPLY MANY. `apply_luma` re-read `self.luma[list][refi]`
10887 // — an array index, a Vec deref and an element index, two of them bounds
10888 // checked — on EVERY sample, up to 256 luma plus 128 chroma per call.
10889 // The weight is a property of the partition, not of the pixel. (The loop
10890 // SHAPE is untouched: row-slicing it is refuted above.)
10891 // MASKED, not row-sliced. `pred_y` is a fixed `[u8; 256]` and `c_pred` a
10892 // `[[u8; 64]; 2]`, so `& 255` / `& 63` are no-ops that prove the index
10893 // outright — where SLICING these loops cost +28% (the extents are runtime
10894 // values, so the slice bounds outweigh the checks). Same lesson as
10895 // `luma_centre`: the refutation was of one SHAPE, not of the goal.
10896 let (lw, lo) = wt.luma_wo(list, refi);
10897 weight_block(pred_y, 16, rx, ry, rw, rh, lw, lo, wt.luma_log2_denom);
10898 let (crx, cry, crw, crh) = (rx / 2, ry / 2, rw / 2, rh / 2);
10899 for cc in 0..2 {
10900 let (cw, co) = wt.chroma_wo(list, refi, cc);
10901 weight_block(
10902 &mut c_pred[cc & 1],
10903 8,
10904 crx,
10905 cry,
10906 crw,
10907 crh,
10908 cw,
10909 co,
10910 wt.chroma_log2_denom,
10911 );
10912 }
10913 }
10914
10915 fn recon_p_inter(&mut self, j: &PInterJob) {
10916 crate::RefFrame::set_mc_row_need(j.mby, self.mb_h * 16);
10917 // `add_inter_residual` reads `self.cur_qp`; unlike the FrameDecoder copy
10918 // (which interleaves with parsing and must save/restore), every PixelCtx
10919 // job sets it from the job before any reader, so no restore is needed.
10920 self.cur_qp = j.qp;
10921 // ---- Recon: motion-comp (per 4×4 luma / co-located 2×2 chroma using the
10922 // committed grid MV — the 6-tap/bilinear filter is per-output-pixel, so
10923 // per-block MC is bit-identical to per-partition MC) + residual add via the
10924 // SAME reconstruct_4x4 as intra, with the MC output as the prediction.
10925 let mut pred_y = [0u8; 256];
10926 let mut c_pred = [[0u8; 64]; 2];
10927 {
10928 // MC-CALL COALESCING (side-by-side descent, dec target #2): the old
10929 // loop paid 16 mc_luma(4×4) + 32 mc_chroma(2×2) per MB regardless of
10930 // partitioning — 48 calls even for a single-MV 16×16 MB, and the
10931 // per-call glue around 2.4M calls was ~40% of decoding real-world
10932 // (x264) streams. The 6-tap/bilinear filters are per-output-pixel,
10933 // so merging blocks with equal (mv, ref) into one wider MC call is
10934 // BIT-IDENTICAL; the rect ladder mirrors the partition shapes.
10935 let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
10936 let (rh16, cch) = (self.mb_h * 16, self.mb_h * 8);
10937 // E2: the worker owns no syntax grids — the job carries the
10938 // committed per-block motion (filled at parse time).
10939 let gmv = j.gmv;
10940 let mut gref = [0usize; 16];
10941 for k in 0..16 {
10942 gref[k] = (j.gref[k] as usize).min(self.refs.len() - 1);
10943 }
10944 // All blocks of the rect (in 4×4-block units) match its top-left?
10945 let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
10946 let t = y4 * 4 + x4;
10947 (0..h4).all(|dy| {
10948 (0..w4).all(|dx| {
10949 let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
10950 gmv[b] == gmv[t] && gref[b] == gref[t]
10951 })
10952 })
10953 };
10954 let refs = &self.refs;
10955 let (cw, ccw) = (self.cw, self.ccw);
10956 let mc_rect = |x4: usize,
10957 y4: usize,
10958 w4: usize,
10959 h4: usize,
10960 pred_y: &mut [u8; 256],
10961 c_pred: &mut [[u8; 64]; 2]| {
10962 let b = y4 * 4 + x4;
10963 let (mv, gr) = (gmv[b & 15], gref[b & 15]);
10964 // `gref` holds a reference-list slot; a slot the list
10965 // does not have means there is nothing to predict from,
10966 // so the rect is left as it stands.
10967 let Some(reference) = refs.get(gr) else {
10968 return;
10969 };
10970 let (w, h) = (w4 * 4, h4 * 4);
10971 // A FULL-WIDTH rect (w == 16, so x4 == 0) occupies contiguous
10972 // whole rows of `pred_y` — the MC output layout and the
10973 // destination layout coincide, so MC writes the prediction
10974 // buffer DIRECTLY. The staging copy exists only for narrow
10975 // rects, whose rows really are strided in `pred_y`. This is
10976 // the diagnosis's "stage-boundary materialization" tax paid
10977 // by the dominant 16×16/16×8 shapes: 256 B of `t` zeroing
10978 // plus a 256 B copy per rect, for nothing.
10979 if w == 16 {
10980 rusty_h264_common::inter::with_mc_scratch(|scr| {
10981 rusty_h264_common::inter::mc_luma_padded_pre(
10982 scr,
10983 &reference.luma_guard(reference.ch),
10984 reference.lstride(),
10985 crate::LPAD,
10986 cw,
10987 rh16,
10988 j.mbx * 16,
10989 j.mby * 16 + y4 * 4,
10990 w,
10991 h,
10992 i32::from(mv.0),
10993 i32::from(mv.1),
10994 &mut pred_y[y4 * 64..y4 * 64 + w * h],
10995 )
10996 });
10997 } else {
10998 let mut t = [0u8; 256];
10999 rusty_h264_common::inter::with_mc_scratch(|scr| {
11000 rusty_h264_common::inter::mc_luma_padded_pre(
11001 scr,
11002 &reference.luma_guard(reference.ch),
11003 reference.lstride(),
11004 crate::LPAD,
11005 cw,
11006 rh16,
11007 j.mbx * 16 + x4 * 4,
11008 j.mby * 16 + y4 * 4,
11009 w,
11010 h,
11011 i32::from(mv.0),
11012 i32::from(mv.1),
11013 &mut t[..w * h],
11014 )
11015 });
11016 let _pb =
11017 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
11018 for dy in 0..h {
11019 pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w]
11020 .copy_from_slice(&t[dy * w..dy * w + w]);
11021 }
11022 }
11023 let (cw4, ch4) = (w4 * 2, h4 * 2);
11024 let nc = cw4 * ch4;
11025 let (gu, gv) = (
11026 reference.chroma_guard(0, reference.ch),
11027 reference.chroma_guard(1, reference.ch),
11028 );
11029 // U+V paired: one setup serves both planes (see
11030 // mc_chroma_padded_pair). Full-width coincidence:
11031 // cw4 == 8 rows are contiguous in the 8-wide plane.
11032 if cw4 == 8 {
11033 let [cu, cv] = &mut *c_pred;
11034 rusty_h264_common::inter::mc_chroma_padded_pair(
11035 &gu,
11036 &gv,
11037 reference.cstride(),
11038 crate::CPAD,
11039 ccw,
11040 cch,
11041 j.mbx * 8,
11042 j.mby * 8 + y4 * 2,
11043 cw4,
11044 ch4,
11045 i32::from(mv.0),
11046 i32::from(mv.1),
11047 &mut cu[y4 * 16..y4 * 16 + nc],
11048 &mut cv[y4 * 16..y4 * 16 + nc],
11049 );
11050 } else {
11051 let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
11052 rusty_h264_common::inter::mc_chroma_padded_pair(
11053 &gu,
11054 &gv,
11055 reference.cstride(),
11056 crate::CPAD,
11057 ccw,
11058 cch,
11059 j.mbx * 8 + x4 * 2,
11060 j.mby * 8 + y4 * 2,
11061 cw4,
11062 ch4,
11063 i32::from(mv.0),
11064 i32::from(mv.1),
11065 &mut tu[..nc],
11066 &mut tv[..nc],
11067 );
11068 let _pb =
11069 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
11070 for (cc, tc) in [(0usize, &tu), (1, &tv)] {
11071 for dy in 0..ch4 {
11072 c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
11073 .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
11074 }
11075 }
11076 }
11077 };
11078 if rect_eq(0, 0, 4, 4) {
11079 mc_rect(0, 0, 4, 4, &mut pred_y, &mut c_pred);
11080 } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
11081 mc_rect(0, 0, 4, 2, &mut pred_y, &mut c_pred);
11082 mc_rect(0, 2, 4, 2, &mut pred_y, &mut c_pred);
11083 } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
11084 mc_rect(0, 0, 2, 4, &mut pred_y, &mut c_pred);
11085 mc_rect(2, 0, 2, 4, &mut pred_y, &mut c_pred);
11086 } else {
11087 for q in 0..4usize {
11088 let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
11089 if rect_eq(qx, qy, 2, 2) {
11090 mc_rect(qx, qy, 2, 2, &mut pred_y, &mut c_pred);
11091 } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
11092 mc_rect(qx, qy, 2, 1, &mut pred_y, &mut c_pred);
11093 mc_rect(qx, qy + 1, 2, 1, &mut pred_y, &mut c_pred);
11094 } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
11095 mc_rect(qx, qy, 1, 2, &mut pred_y, &mut c_pred);
11096 mc_rect(qx + 1, qy, 1, 2, &mut pred_y, &mut c_pred);
11097 } else {
11098 for j in 0..4usize {
11099 mc_rect(qx + (j % 2), qy + (j / 2), 1, 1, &mut pred_y, &mut c_pred);
11100 }
11101 }
11102 }
11103 }
11104 // EXPLICIT WEIGHTED PREDICTION (spec 8.4.2.3). The CAVLC inter
11105 // path weights each partition after MC; the MC-call-coalescing
11106 // rewrite of this CABAC path lost it, and nothing caught that
11107 // because the effect is invisible unless a stream actually
11108 // carries non-default weights. x264's `weightp` DUPLICATES a
11109 // reference and distinguishes the copy ONLY by its weights, so
11110 // every macroblock picking the weighted index decoded unweighted
11111 // -- a silent, accumulating luma drift.
11112 //
11113 // Applied per 4x4 block rather than per partition: the weight
11114 // depends solely on the block's reference index, so the two are
11115 // equivalent, and `gref` already holds it for every block
11116 // regardless of which rect ladder rung ran.
11117 if self.weights.is_some() {
11118 for by in 0..4usize {
11119 for bx in 0..4usize {
11120 let refi = gref[by * 4 + bx];
11121 self.weight_partition(
11122 &mut pred_y,
11123 &mut c_pred,
11124 0,
11125 refi,
11126 bx * 4,
11127 by * 4,
11128 4,
11129 4,
11130 );
11131 }
11132 }
11133 }
11134 }
11135 // Residual add — the SAME helper the B path uses (this inline
11136 // copy was a duplicate; deduped when the zero-block fast path
11137 // landed so both paths share it).
11138 self.add_inter_residual(
11139 j.mbx,
11140 j.mby,
11141 &pred_y,
11142 &c_pred,
11143 Some(&j.luma_scan),
11144 j.t8.then_some(&j.luma8),
11145 &j.cdc,
11146 Some(&j.cac),
11147 j.cbp_chroma,
11148 &j.nnzs,
11149 );
11150 }
11151
11152 /// D9b: P inter with `cbp == 0` — MC + plane copy (worker twin of FrameDecoder).
11153 fn recon_p_inter_nores(&mut self, j: &PInterNoResJob) {
11154 let mut pred_y = [0u8; 256];
11155 let mut c_pred = [[0u8; 64]; 2];
11156 // `self.refs.len() - 1` was re-loaded on every one of the sixteen
11157 // iterations; it is a per-macroblock invariant. Written once, not
11158 // zeroed-then-filled.
11159 let nrefs = self.refs.len() - 1;
11160 let gref: [usize; 16] = core::array::from_fn(|k| (j.gref[k] as usize).min(nrefs));
11161 coalesce_p_inter_mc(
11162 &self.refs,
11163 self.cw,
11164 self.ccw,
11165 self.mb_h,
11166 j.mbx,
11167 j.mby,
11168 &j.gmv,
11169 &gref,
11170 &mut pred_y,
11171 &mut c_pred,
11172 );
11173 // The identity early-out lives INSIDE `weight_partition`, so an x264
11174 // stream - which carries a pred_weight_table in EVERY P slice, identity
11175 // outside fades - still paid sixteen calls per macroblock to be told
11176 // there was nothing to do. Hoisted to one test.
11177 if self.weights.is_some() && !self.weights_l0id {
11178 for by in 0..4usize {
11179 for bx in 0..4usize {
11180 let refi = gref[by * 4 + bx];
11181 self.weight_partition(&mut pred_y, &mut c_pred, 0, refi, bx * 4, by * 4, 4, 4);
11182 }
11183 }
11184 }
11185 // ONE span per plane: the destination is a stack of contiguous runs a
11186 // fixed stride apart, so the rows share a single bounds check instead of
11187 // one each (16 + 8 + 8 of them).
11188 let (cw, ccw) = (self.cw, self.ccw);
11189 let ybase = (j.mby * 16) * cw + j.mbx * 16;
11190 let ywin = &mut self.rec_y[ybase..ybase + 15 * cw + 16];
11191 for dy in 0..16 {
11192 ywin[dy * cw..dy * cw + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
11193 }
11194 let cbase = (j.mby * 8) * ccw + j.mbx * 8;
11195 for c in 0..2 {
11196 let plane = if c == 0 {
11197 &mut self.rec_u
11198 } else {
11199 &mut self.rec_v
11200 };
11201 let cwin = &mut plane[cbase..cbase + 7 * ccw + 8];
11202 for dy in 0..8 {
11203 cwin[dy * ccw..dy * ccw + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
11204 }
11205 }
11206 }
11207
11208 fn recon_p_skip(&mut self, mb_x: usize, mb_y: usize, mv: (i32, i32)) {
11209 crate::RefFrame::set_mc_row_need(mb_y, self.mb_h * 16);
11210 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
11211
11212 let mut pred = [0u8; 256];
11213 let Some(rf0) = self.refs.first() else { return };
11214 mc_luma_padded(
11215 &rf0.luma_guard(rf0.ch),
11216 rf0.lstride(),
11217 crate::LPAD,
11218 self.cw,
11219 ch,
11220 mb_x * 16,
11221 mb_y * 16,
11222 16,
11223 16,
11224 mv.0,
11225 mv.1,
11226 &mut pred,
11227 );
11228 if let Some(wt) = &self.weights {
11229 for p in pred.iter_mut() {
11230 *p = wt.apply_luma(*p, 0, 0);
11231 }
11232 }
11233 {
11234 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::SkipRecon);
11235 // ONE span for the sixteen rows (see `recon_p_inter_nores`).
11236 let (cw, base) = (self.cw, (mb_y * 16) * self.cw + mb_x * 16);
11237 let win = &mut self.rec_y[base..base + 15 * cw + 16];
11238 for dy in 0..16 {
11239 win[dy * cw..dy * cw + 16].copy_from_slice(&pred[dy * 16..dy * 16 + 16]);
11240 }
11241 }
11242 for c in 0..2 {
11243 let mut pc = [0u8; 64];
11244 let Some(rf0) = self.refs.first() else { return };
11245 let rc = if c == 0 {
11246 &*rf0.chroma_guard(0, rf0.ch)
11247 } else {
11248 &*rf0.chroma_guard(1, rf0.ch)
11249 };
11250 mc_chroma_padded(
11251 rc,
11252 rf0.cstride(),
11253 crate::CPAD,
11254 self.ccw,
11255 cch,
11256 mb_x * 8,
11257 mb_y * 8,
11258 8,
11259 8,
11260 mv.0,
11261 mv.1,
11262 &mut pc,
11263 );
11264 if let Some(wt) = &self.weights {
11265 for p in pc.iter_mut() {
11266 *p = wt.apply_chroma(*p, 0, 0, c);
11267 }
11268 }
11269 let plane = if c == 0 {
11270 &mut self.rec_u
11271 } else {
11272 &mut self.rec_v
11273 };
11274 for dy in 0..8 {
11275 let d = (mb_y * 8 + dy) * self.ccw + mb_x * 8;
11276 plane[d..d + 8].copy_from_slice(&pc[dy * 8..dy * 8 + 8]);
11277 }
11278 }
11279 }
11280
11281 fn add_inter_residual(
11282 &mut self,
11283 mb_x: usize,
11284 mb_y: usize,
11285 pred_y: &[u8; 256],
11286 c_pred: &[[u8; 64]; 2],
11287 luma_scan: Option<&[[i32; 16]; 16]>,
11288 // `Some` when the macroblock carries transform_size_8x8_flag: four 8x8
11289 // blocks in 8x8 scan order, replacing the sixteen 4x4 luma blocks.
11290 luma8: Option<&[[i32; 64]; 4]>,
11291 cdc: &[[i32; 4]; 2],
11292 cac: Option<&[[[i32; 16]; 4]; 2]>,
11293 cbp_chroma: u32,
11294 // Parsed totalCoeff per block, indexed exactly as the parse's `iz`:
11295 // [0..16] luma 4x4 z-order (for t8, the 8x8 count sits at `id8*4`),
11296 // [16..24] chroma AC as `16 + c*4 + id4`. The parser already counted
11297 // every significant coefficient; re-deriving the counts here scanned
11298 // 16-64 array elements per block (~400 loads/MB) for information the
11299 // caller was holding — the diagnosis's stage-boundary re-derivation tax.
11300 nnzs: &[u8; 24],
11301 ) {
11302 // A `None` field means the parse wrote nothing there; every read below
11303 // is guarded by a zero-count test, so the shared zero plane is
11304 // read-equivalent to the per-macroblock zeroed stack array it replaces.
11305 let luma_scan = luma_scan.unwrap_or(&ZERO_LUMA_SCAN);
11306 let cac = cac.unwrap_or(&ZERO_CAC);
11307 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecResidAdd);
11308 let qp = self.cur_qp;
11309 let qpc = self.chroma_qp_for(qp);
11310 if let Some(l8) = luma8 {
11311 // INTER 8x8 luma: same primitives the I_8x8 and CAVLC paths use.
11312 for b8 in 0..4usize {
11313 let (b8x, b8y) = (b8 % 2, b8 / 2);
11314 // Summed, not slot 0: with per-4x4 counts in the CAVLC case, slot 0
11315 // is only the first sub-block and can be 0 while the block is coded.
11316 let nnz: u32 = (0..4).map(|k| nnzs[b8 * 4 + k] as u32).sum();
11317 let (px, py) = (mb_x * 16 + b8x * 8, mb_y * 16 + b8y * 8);
11318 if nnz == 0 {
11319 // Zero residual: recon == pred — same shortcut as the
11320 // FrameDecoder twin.
11321 edcstat::bump(&edcstat::T8_ZERO, 1);
11322 for dy in 0..8 {
11323 let d = (py + dy) * self.cw + px;
11324 let po = (b8y * 8 + dy) * 16 + b8x * 8;
11325 self.rec_y[d..d + 8].copy_from_slice(&pred_y[po..po + 8]);
11326 }
11327 } else {
11328 let raster = un_scan_8x8(&l8[b8]);
11329 // list 1 = INTER 8x8 luma scaling list (0 is the intra one).
11330 let res8 = self.inv_quant8(&raster, qp, 1);
11331 let predb: [i32; 64] = core::array::from_fn(|i| {
11332 pred_y[(b8y * 8 + i / 8) * 16 + (b8x * 8 + i % 8)] as i32
11333 });
11334 let recon = add_residual_8x8(&res8, &predb);
11335 // Eight row copies. This wrote SIXTY-FOUR individually
11336 // bounds-checked samples into the luma plane per coded 8x8
11337 // block — the same shape whose fix carried `recon_i8_block`.
11338 // Present in BOTH the main path and the EDC worker twin.
11339 for dy in 0..8 {
11340 let d = (py + dy) * self.cw + px;
11341 self.rec_y[d..][..8].copy_from_slice(&recon[dy * 8..][..8]);
11342 }
11343 }
11344 }
11345 }
11346 // RESIDUAL LADDER, then the SIMD IDCT+add kernel per parked block (SIMD census
11347 // 2026-09-05). The zero and DC-only ladders write the plane in the loop;
11348 // every other coded block parks its dequantised coefficients here and is
11349 // reconstructed by `reconstruct_4x4_into` = accel `idct4x4_add` (in-register
11350 // transposes, exact on i32). A lane-per-block BATCHED form was tried first
11351 // and lost on all-intra content: its scalar gathers cost more than the
11352 // butterflies they vectorised.
11353 let dq3 = self.dq_const(qp, 3);
11354 for (blk, &(lbx, lby)) in LUMA_4X4_SCAN_XY.iter().enumerate() {
11355 if luma8.is_some() {
11356 break;
11357 }
11358 let nnz = nnzs[blk];
11359 let cw = self.cw;
11360 let p_off = (lby * 4) * 16 + lbx * 4;
11361 let r_off = (mb_y * 4 + lby) * 4 * cw + (mb_x * 4 + lbx) * 4;
11362 if nnz == 0 {
11363 // Zero residual → recon == prediction EXACTLY (the integer IDCT is
11364 // linear so zeros map to zeros, and pred is already 0..=255) — copy
11365 // the pred rows straight into the plane. On real (sparse-cbp)
11366 // streams this is MOST of the 4×4 blocks.
11367 for r in 0..4 {
11368 self.rec_y[r_off + r * cw..r_off + r * cw + 4]
11369 .copy_from_slice(&pred_y[p_off + r * 16..p_off + r * 16 + 4]);
11370 }
11371 continue;
11372 }
11373 // DC-ONLY: the sole significant coefficient is scan position 0 (the
11374 // zig-zag starts at DC, and un_scan keeps it at raster 0), so the
11375 // whole dequant + IDCT collapses to one multiply and a flat add.
11376 if nnz == 1 && luma_scan[blk][0] != 0 {
11377 let f = self.dequant_dc4(luma_scan[blk][0], qp, 3);
11378 reconstruct_4x4_dc_into(
11379 (f + 32) >> 6,
11380 pred_y,
11381 p_off,
11382 16,
11383 &mut self.rec_y,
11384 r_off,
11385 cw,
11386 );
11387 } else {
11388 // Fused un-scan + dequant over ONLY the significant coefficients,
11389 // then IDCT + add + clip straight into the plane — no `qb`, no
11390 // `deq`-from-dense, no `predb` gather, no `s`, no `store` call.
11391 //
11392 // HYBRID: the scatter walks scan positions with a data-dependent
11393 // branch per slot, which beats the branchless dense 16-multiply
11394 // loop only while the block is SPARSE. The DC/zero fast paths
11395 // already removed the sparsest blocks, so the population here
11396 // skews denser — above ~6 coefficients the dense loop wins.
11397 // FUSED: un-scan + dequant + IDCT + add in one kernel call (dense-over-scatter round).
11398 reconstruct_4x4_scan_into::<false>(
11399 &luma_scan[blk],
11400 &dq3,
11401 0,
11402 pred_y,
11403 p_off,
11404 16,
11405 &mut self.rec_y,
11406 r_off,
11407 cw,
11408 );
11409 }
11410 }
11411 let mut c_dc = [[0i32; 4]; 2];
11412 if cbp_chroma != 0 {
11413 for c in 0..2 {
11414 c_dc[c] = self.dequant_chroma_dc(&cdc[c], qpc, 4 + c);
11415 }
11416 }
11417 let dqc = [self.dq_const(qpc, 4), self.dq_const(qpc, 5)];
11418 let ccw = self.ccw;
11419 for c in 0..2 {
11420 for &(bx, by) in &CHROMA_4X4_SCAN_XY {
11421 let mut ac_nz = false;
11422 if cbp_chroma == 2 {
11423 let n = nnzs[(16 + c * 4 + by * 2 + bx).min(23)];
11424 ac_nz = n != 0;
11425 }
11426 let dc = c_dc[c & 1][(by * 2 + bx) & 3];
11427 let p_off = (by * 4) * 8 + bx * 4;
11428 let r_off = (mb_y * 2 + by) * 4 * ccw + (mb_x * 2 + bx) * 4;
11429 let plane = if c == 0 {
11430 &mut self.rec_u
11431 } else {
11432 &mut self.rec_v
11433 };
11434 if dc == 0 && !ac_nz {
11435 // Zero residual (no AC, zero DC) → recon == prediction exactly.
11436 for r in 0..4 {
11437 plane[r_off + r * ccw..r_off + r * ccw + 4]
11438 .copy_from_slice(&c_pred[c][p_off + r * 8..p_off + r * 8 + 4]);
11439 }
11440 continue;
11441 }
11442 // DC-ONLY (no coded AC — covers every cbp_chroma==1 block and the
11443 // AC-empty blocks of cbp_chroma==2): the chroma DC arrives ALREADY
11444 // dequantized, so the residual is `(dc + 32) >> 6` flat.
11445 if !ac_nz {
11446 reconstruct_4x4_dc_into(
11447 (dc + 32) >> 6,
11448 &c_pred[c],
11449 p_off,
11450 8,
11451 plane,
11452 r_off,
11453 ccw,
11454 );
11455 continue;
11456 }
11457 // AC-only scan: index i is overall scan position i+1 (ac_shift=1).
11458 // Same sparse/dense hybrid as luma.
11459 reconstruct_4x4_scan_into::<true>(
11460 &cac[c & 1][(by * 2 + bx) & 3],
11461 &dqc[c & 1],
11462 dc,
11463 &c_pred[c],
11464 p_off,
11465 8,
11466 plane,
11467 r_off,
11468 ccw,
11469 );
11470 }
11471 }
11472 }
11473
11474 fn filter_row(&mut self, r: usize) {
11475 // The precomputed consumer path reads ONLY `bs` + `t8x8` (+ the qp grid
11476 // passed as a parameter) — verified when the path landed (WHYS Part 15).
11477 let info = rusty_h264_common::deblock::BlockInfo {
11478 inter: &[],
11479 nnz: &[],
11480 mv: &[],
11481 ref_id: &[],
11482 mv1: &[],
11483 ref_id1: &[],
11484 w4: self.mb_w * 4,
11485 t8x8: &self.t8_grid,
11486 bs: &self.bs_store,
11487 poc0: &[],
11488 poc1: &[],
11489 kind: &[],
11490 };
11491 rusty_h264_common::deblock::filter_frame_rows_pre(
11492 &mut self.rec_y,
11493 &mut self.rec_u,
11494 &mut self.rec_v,
11495 self.mb_w,
11496 self.mb_h,
11497 r..r + 1,
11498 &self.qp_grid,
11499 self.chroma_qp_offset,
11500 self.db_oa,
11501 self.db_ob,
11502 &info,
11503 );
11504 }
11505
11506 fn save_bak(&mut self, r: usize) {
11507 let y0 = (r * 16 + 15) * self.cw;
11508 self.bak_y.copy_from_slice(&self.rec_y[y0..y0 + self.cw]);
11509 let c0 = (r * 8 + 7) * self.ccw;
11510 self.bak_u.copy_from_slice(&self.rec_u[c0..c0 + self.ccw]);
11511 self.bak_v.copy_from_slice(&self.rec_v[c0..c0 + self.ccw]);
11512 }
11513}
11514
11515/// The pixel worker: replays jobs in parse order, filters rows as their
11516/// messages arrive, and hands the whole context to the parse thread (and
11517/// back) around intra macroblocks. Returns the context at slice end.
11518/// E2 SEAM COUNTERS (D7). Deterministic — one run is the verdict, no pinning.
11519/// `RS_H264_EDC_STATS=1` prints at decode end. Counts, not clocks: the question
11520/// "does one intra macroblock drain the pipeline" is a COUNT question.
11521/// COPY CENSUS (feature = "profile"): bytes moved, per site class.
11522///
11523/// The static inventory says the decoder has 101 `copy_from_slice` sites. That
11524/// number ranks nothing -- `b_skip_slow`'s 384-byte hop and the nnz grid's 4-byte
11525/// one look identical in a grep, and one of them fires on the rare path. This
11526/// counts BYTES and CALLS per class on a real stream, so "the top of the list"
11527/// means the top by bytes rather than the top by line number.
11528///
11529/// Read the result against the output size, not in isolation: a decoder that
11530/// writes 384 bytes per macroblock and copies 384 is at 1.0 and irreducible;
11531/// one at 2.0 has a hop to find.
11532///
11533/// **And price the SITE, not the bytes.** A memcpy streams at ~10-20 GB/s; if
11534/// anything else fires on the same trigger -- a clear, a re-prime, a scatter --
11535/// the copy is the cheapest member of that bundle and the census is measuring
11536/// the wrong thing. Enumerate what else runs before concluding "not copy-bound".
11537#[allow(dead_code)]
11538pub(crate) mod cpystat {
11539 use core::sync::atomic::Ordering::Relaxed;
11540 use rusty_h264_common::atomic::AtomicU64;
11541
11542 /// One counter pair per class: (calls, bytes).
11543 macro_rules! classes {
11544 ($($id:ident => $label:expr),* $(,)?) => {
11545 $(pub static $id: [AtomicU64; 2] = [const { AtomicU64::new(0) }; 2];)*
11546 pub static ALL: &[(&str, &[AtomicU64; 2])] = &[$(($label, &$id)),*];
11547 };
11548 }
11549
11550 classes! {
11551 POOL_CLEAR => "per-picture grid re-arm (refill: clear + resize)",
11552 REF_BUILD => "per-REFERENCE-picture motion clone (ref_poc expansion GONE)",
11553 PROGRESS_SLOT => "progress-slot motion grids, allocated then replaced",
11554 PRED_REC => "pred->rec plane store (the MB's own pixels)",
11555 RECON_REC => "recon->rec plane store (residual path)",
11556 MC_STAGE => "MC staging buffer (inter prediction into a local)",
11557 NNZ_GRID => "nnz grid maintenance",
11558 MV_GRID => "motion-vector grid",
11559 PLANE_PAD => "per-REFERENCE-picture padded plane copy (pad_plane_into)",
11560 PLANE_MISS => "padded-plane pool MISS: fresh alloc, zeroed then fully overwritten",
11561 BAK_ROW => "deblock row backup (bak_y/u/v)",
11562 NEIGH => "neighbour row/col caches (top_y_row etc)",
11563 EDC_ROW => "EDC worker row messages",
11564 OTHER => "everything else instrumented",
11565 }
11566
11567 #[inline(always)]
11568 pub fn note(c: &[AtomicU64; 2], bytes: usize) {
11569 #[cfg(feature = "profile")]
11570 {
11571 c[0].fetch_add(1, Relaxed);
11572 c[1].fetch_add(bytes as u64, Relaxed);
11573 }
11574 #[cfg(not(feature = "profile"))]
11575 let _ = (c, bytes);
11576 }
11577
11578 /// Per-CALLER-LINE byte totals, so "which refill dominates" is a lookup rather
11579 /// than an argument. A single funnel prices the whole re-arm; only the line
11580 /// buckets say WHICH grid is worth narrowing.
11581 #[cfg(feature = "profile")]
11582 pub static BY_LINE: std::sync::Mutex<Option<std::collections::BTreeMap<u32, (u64, u64)>>> =
11583 std::sync::Mutex::new(None);
11584
11585 #[cfg(feature = "profile")]
11586 pub fn note_line(line: u32, bytes: usize) {
11587 if let Ok(mut g) = BY_LINE.lock() {
11588 let m = g.get_or_insert_with(Default::default);
11589 let e = m.entry(line).or_insert((0, 0));
11590 e.0 += 1;
11591 e.1 += bytes as u64;
11592 }
11593 }
11594
11595 /// Output bytes produced, so the census can be read as copies-per-output-byte.
11596 pub static OUT_BYTES: AtomicU64 = AtomicU64::new(0);
11597
11598 pub fn report() {
11599 let out = OUT_BYTES.load(Relaxed).max(1);
11600 let tot: u64 = ALL.iter().map(|(_, c)| c[1].load(Relaxed)).sum();
11601 eprintln!(
11602 "CPYSTAT output={out} B instrumented_copies={tot} B ratio={:.3} B/out",
11603 tot as f64 / out as f64
11604 );
11605 #[cfg(feature = "profile")]
11606 if let Ok(g) = BY_LINE.lock() {
11607 if let Some(m) = g.as_ref() {
11608 let mut v: Vec<_> = m.iter().map(|(l, (n, b))| (*b, *n, *l)).collect();
11609 v.sort_by(|a, b| b.0.cmp(&a.0));
11610 eprintln!(" --- refill by caller line (top 14) ---");
11611 for (b, n, l) in v.iter().take(14) {
11612 eprintln!(
11613 " mb16.rs:{:<6} calls={:<8} bytes={:<13} {:>8.1} KB/call",
11614 l,
11615 n,
11616 b,
11617 *b as f64 / *n as f64 / 1024.0
11618 );
11619 }
11620 }
11621 }
11622 for (name, c) in ALL {
11623 let (n, b) = (c[0].load(Relaxed), c[1].load(Relaxed));
11624 if n == 0 {
11625 continue;
11626 }
11627 eprintln!(
11628 " {:<46} calls={:<12} bytes={:<14} {:>6.3} B/out {:>5.1} B/call",
11629 name,
11630 n,
11631 b,
11632 b as f64 / out as f64,
11633 b as f64 / n as f64
11634 );
11635 }
11636 }
11637}
11638
11639pub(crate) mod edcstat {
11640 use core::sync::atomic::Ordering::Relaxed;
11641 use rusty_h264_common::atomic::AtomicU64;
11642 pub static NEEDCTX: AtomicU64 = AtomicU64::new(0);
11643 pub static JOBS: AtomicU64 = AtomicU64::new(0);
11644 pub static ROWS: AtomicU64 = AtomicU64::new(0);
11645 pub static ROWBYTES: AtomicU64 = AtomicU64::new(0);
11646 pub static MBS: AtomicU64 = AtomicU64::new(0);
11647 /// Sparse-dequant routing histogram: [nnz class 1-2/3-4/5-6/7+][L class 1-4/5-8/9-12/13-16],
11648 /// L = highest coded scan position + 1 (the scatter loop's trip count).
11649 pub static DQ: [AtomicU64; 16] = [const { AtomicU64::new(0) }; 16];
11650 #[inline(always)]
11651 #[allow(dead_code)]
11652 pub fn dq_note(nnz: u8, scan: &[i32; 16]) {
11653 #[cfg(feature = "profile")]
11654 if on() {
11655 let l = 16 - scan.iter().rev().take_while(|&&v| v == 0).count();
11656 let nc = ((nnz.max(1) as usize - 1) / 2).min(3);
11657 let lc = ((l.max(1) - 1) / 4).min(3);
11658 DQ[nc * 4 + lc].fetch_add(1, Relaxed);
11659 }
11660 #[cfg(not(feature = "profile"))]
11661 let _ = (nnz, scan);
11662 }
11663 pub static J_INTER: AtomicU64 = AtomicU64::new(0);
11664 pub static DOUBLED: AtomicU64 = AtomicU64::new(0);
11665 pub static J_NORES_SENT: AtomicU64 = AtomicU64::new(0);
11666 pub static BATCHES: AtomicU64 = AtomicU64::new(0);
11667 pub static DISPATCH_ON: AtomicU64 = AtomicU64::new(0);
11668 pub static DISPATCH_SEEN: AtomicU64 = AtomicU64::new(0);
11669 /// mb_skip_run band path (big-oppy-decoder §6 KEY glue): P_Skip MBs
11670 /// reconstructed by run-coalesced band copies, and the runs themselves.
11671 /// DETERMINISTIC WIN counters — each banded MB removed 1 luma MC call,
11672 /// 2 chroma MC calls and the 256+128-byte pred staging round-trip.
11673 pub static SKIPBAND_MBS: AtomicU64 = AtomicU64::new(0);
11674 pub static SKIPBAND_RUNS: AtomicU64 = AtomicU64::new(0);
11675 pub static SKIP_SINGLES: AtomicU64 = AtomicU64::new(0);
11676 // MEASUREMENT counters sizing the next skip-band extensions (no behavior).
11677 // P side: singles that sit in a same-row run of >=2 EQUAL nonzero MVs,
11678 // split by full-pel (band-copy-with-offset eligible) vs fractional.
11679 pub static PEQ_FP: AtomicU64 = AtomicU64::new(0);
11680 pub static PEQ_FRAC: AtomicU64 = AtomicU64::new(0);
11681 // B side: full-16x16 spatial-direct calls (B_Skip + direct-16), how many
11682 // collapse to ONE uniform rect, and of those the zero-motion bi/uni and
11683 // full-pel populations + run adjacency (previous MB same params, same row).
11684 pub static BSK_FULLMB: AtomicU64 = AtomicU64::new(0);
11685 pub static BSK_1RECT: AtomicU64 = AtomicU64::new(0);
11686 pub static BSK_ZBI: AtomicU64 = AtomicU64::new(0);
11687 pub static BSK_ZUNI: AtomicU64 = AtomicU64::new(0);
11688 pub static BSK_FP: AtomicU64 = AtomicU64::new(0);
11689 pub static BSK_RUNCONT: AtomicU64 = AtomicU64::new(0);
11690 /// DETERMINISTIC WIN counter — B_Skip MBs taken by the zero-bi fast path.
11691 pub static BSKB_FAST: AtomicU64 = AtomicU64::new(0);
11692 /// P_Skip singles taken by the full-pel direct-copy path (both planes /
11693 /// luma only, chroma still interpolating at mv%8!=0).
11694 pub static SKIP_FP_FULL: AtomicU64 = AtomicU64::new(0);
11695 pub static SKIP_FP_LUMA: AtomicU64 = AtomicU64::new(0);
11696 /// B_Skip full-pel nonzero-MV MBs taken by the offset copy/avg path.
11697 pub static BSKB_FP: AtomicU64 = AtomicU64::new(0);
11698 /// P_Skip parse side: MBs whose skip MV was FORCED (0,0) by the run
11699 /// theorem (left = just-committed (0,0) skip, or out-of-slice) vs MBs
11700 /// that ran the 3-neighbor gather + rule.
11701 pub static SKIPMV_FORCED: AtomicU64 = AtomicU64::new(0);
11702 pub static SKIPMV_DERIVED: AtomicU64 = AtomicU64::new(0);
11703 /// B_Skips whose zero-bi derivation was FORCED by the known-zero bitmap
11704 /// (left+top+topright all recorded ref0/(0,0)-both-lists) — the 6-gather
11705 /// b_direct_nbrs walk skipped entirely.
11706 pub static BSKB_FORCED: AtomicU64 = AtomicU64::new(0);
11707 /// Zero-bi fast B_Skip grid commits batched into row spans: MBs covered
11708 /// and spans flushed (fills of 4N replace N MBs x 28 per-MB fills).
11709 pub static BZ_SPAN_MBS: AtomicU64 = AtomicU64::new(0);
11710 pub static BZ_SPANS: AtomicU64 = AtomicU64::new(0);
11711 /// P_Skip (0,0) grid-commit spans (parse-side mirror of BZ_*).
11712 pub static PZ_SPAN_MBS: AtomicU64 = AtomicU64::new(0);
11713 pub static PZ_SPANS: AtomicU64 = AtomicU64::new(0);
11714 /// Census: b_mc bi-pred regions where BOTH MVs are full-pel (fusable to a
11715 /// direct offset average — sizing only, no behavior).
11716 pub static BMC_BI_FP: AtomicU64 = AtomicU64::new(0);
11717 /// weight_partition passes skipped because the whole list-0 table is
11718 /// identity (384 apply ops avoided per full-MB pass).
11719 pub static WP_SKIPPED: AtomicU64 = AtomicU64::new(0);
11720 /// Intra I_4x4 luma residual dispatch (the inter ladder, ported): how many
11721 /// blocks took the zero / DC-only / sparse-scatter / dense arms.
11722 pub static I4_ZERO: AtomicU64 = AtomicU64::new(0);
11723 pub static I4_DC: AtomicU64 = AtomicU64::new(0);
11724 pub static I4_SPARSE: AtomicU64 = AtomicU64::new(0);
11725 pub static I4_DENSE: AtomicU64 = AtomicU64::new(0);
11726 /// 8x8 residual blocks (intra + inter t8) whose residual is entirely zero:
11727 /// recon == pred, the 64-px widen + add + clip skipped.
11728 pub static T8_ZERO: AtomicU64 = AtomicU64::new(0);
11729 /// I_16x16 4x4 sub-blocks with zero AC: the residual is the (already
11730 /// Hadamard-transformed) DC alone, so the dense dequant + IDCT collapse
11731 /// to one flat add.
11732 pub static I16_DCONLY: AtomicU64 = AtomicU64::new(0);
11733 pub static J_INTER_NORES: AtomicU64 = AtomicU64::new(0);
11734 // ---- deb:derive census (derive_bs_row) — sizing the bS derivation arms.
11735 /// Rows derived, and macroblocks derived across them.
11736 pub static DBS_ROWS: AtomicU64 = AtomicU64::new(0);
11737 pub static DBS_MB: AtomicU64 = AtomicU64::new(0);
11738 /// Arm split: Intra kind-arm / Skip|InterUniform kind-arm (only under
11739 /// RS_H264_KIND_LOADS=1) / the packed `derive_mb_records` default.
11740 pub static DBS_INTRA: AtomicU64 = AtomicU64::new(0);
11741 pub static DBS_KINDARM: AtomicU64 = AtomicU64::new(0);
11742 /// Macroblocks whose kind is Skip|InterUniform: the population that used
11743 /// to evaluate the `kind_loads()` OnceLock guard once EACH.
11744 pub static DBS_KINDGUARD: AtomicU64 = AtomicU64::new(0);
11745 pub static DBS_PACKED: AtomicU64 = AtomicU64::new(0);
11746 /// Of the packed arm: macroblocks whose derivation returned `flat_inter`
11747 /// (internal strengths 0 by construction — the early-return class).
11748 pub static DBS_FLAT: AtomicU64 = AtomicU64::new(0);
11749 /// Macroblocks whose FINAL stored MbBs is all-zero: the population the
11750 /// consumer's two-16-byte-compare early-out serves, and the denominator
11751 /// for the per-MB 128-byte bs_v/bs_h zero-init that precedes it.
11752 pub static DBS_ALLZERO: AtomicU64 = AtomicU64::new(0);
11753 /// nnz_dbr maintenance: rows copied wholesale vs rows that actually
11754 /// carried a transform-8x8 macroblock needing the 8x8 OR fixup.
11755 pub static DBS_NNZ_ROWCOPY: AtomicU64 = AtomicU64::new(0);
11756 pub static DBS_T8ROW: AtomicU64 = AtomicU64::new(0);
11757 pub static DBS_T8MB: AtomicU64 = AtomicU64::new(0);
11758 /// Rows that ran the disable_deblocking_filter_idc==2 crossing-edge pass.
11759 pub static DBS_IDC2ROW: AtomicU64 = AtomicU64::new(0);
11760 #[inline]
11761 pub fn bump(c: &AtomicU64, n: u64) {
11762 #[cfg(feature = "profile")]
11763 if on() {
11764 c.fetch_add(n, Relaxed);
11765 }
11766 #[cfg(not(feature = "profile"))]
11767 let _ = (c, n);
11768 }
11769 /// Off by default, yet read on EVERY bump — up to four times per B_Skip.
11770 /// `OnceLock::get_or_init` is an acquire load plus an initialised-state
11771 /// branch behind a non-inlined call; this is the relaxed AtomicU8 tri-state
11772 /// the other knobs in this file already use, and it inlines to one load.
11773 #[inline]
11774 pub fn on() -> bool {
11775 #[cfg(not(feature = "profile"))]
11776 {
11777 false
11778 }
11779 #[cfg(feature = "profile")]
11780 {
11781 use core::sync::atomic::AtomicU8;
11782 static V: AtomicU8 = AtomicU8::new(0);
11783 match V.load(Relaxed) {
11784 1 => true,
11785 2 => false,
11786 _ => {
11787 let b = rusty_h264_common::knob("RS_H264_EDC_STATS").is_some();
11788 V.store(if b { 1 } else { 2 }, Relaxed);
11789 b
11790 }
11791 }
11792 }
11793 }
11794 pub fn report() {
11795 if !on() {
11796 return;
11797 }
11798 super::cpystat::report();
11799 {
11800 // Routing model (census, 2026-09-05): scatter = 37 + 6*L + 9*nnz instrs;
11801 // dense = unscan 32 + dequantize ~63 (scalar) / ~28 (AVX2 twin).
11802 let (nm, lm) = ([1.5f64, 3.5, 5.5, 9.0], [2.5f64, 6.5, 10.5, 14.5]);
11803 let (mut tot, mut sc, mut d95, mut d60, mut best) =
11804 (0u64, 0.0f64, 0.0f64, 0.0f64, 0.0f64);
11805 eprintln!(
11806 "DQROUTE sparse-arm blocks by [nnz class][L class] (L = last coded position + 1):"
11807 );
11808 for nc in 0..4 {
11809 // `alloc::` spelled out: `Vec` is not in the no_std prelude.
11810 let row: alloc::vec::Vec<u64> =
11811 (0..4).map(|lc| DQ[nc * 4 + lc].load(Relaxed)).collect();
11812 eprintln!(
11813 " nnz {:<5} L1-4={:>9} L5-8={:>9} L9-12={:>9} L13-16={:>9}",
11814 ["1-2", "3-4", "5-6", "7+"][nc],
11815 row[0],
11816 row[1],
11817 row[2],
11818 row[3]
11819 );
11820 for lc in 0..4 {
11821 let k = row[lc] as f64;
11822 let s = 37.0 + 6.0 * lm[lc] + 9.0 * nm[nc];
11823 tot += row[lc];
11824 sc += k * s;
11825 d95 += k * 95.0;
11826 d60 += k * 60.0;
11827 best += k * s.min(60.0);
11828 }
11829 }
11830 if tot > 0 {
11831 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);
11832 }
11833 }
11834 eprintln!(
11835 "EDCDISPATCH threaded_slices={} eligible_slices={}",
11836 DISPATCH_ON.load(Relaxed),
11837 DISPATCH_SEEN.load(Relaxed)
11838 );
11839 eprintln!(
11840 "EDCSIZE EdcMsg={} EdcJob={} PInterJob={} BJob={}",
11841 core::mem::size_of::<super::EdcMsg>(),
11842 core::mem::size_of::<super::EdcJob>(),
11843 core::mem::size_of::<super::PInterJob>(),
11844 core::mem::size_of::<super::BJob>(),
11845 );
11846 let (n, j, r, b, m) = (
11847 NEEDCTX.load(Relaxed),
11848 JOBS.load(Relaxed),
11849 ROWS.load(Relaxed),
11850 ROWBYTES.load(Relaxed),
11851 MBS.load(Relaxed),
11852 );
11853 eprintln!(
11854 "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}",
11855 BATCHES.load(Relaxed),
11856 j as f64 / BATCHES.load(Relaxed).max(1) as f64,
11857 1000.0 * n as f64 / m.max(1) as f64,
11858 j as f64 / n.max(1) as f64
11859 );
11860 eprintln!(
11861 "SKIPRUN band_mbs={} band_runs={} mbs_per_run={:.1} singles={} banded_pct={:.1}",
11862 SKIPBAND_MBS.load(Relaxed),
11863 SKIPBAND_RUNS.load(Relaxed),
11864 SKIPBAND_MBS.load(Relaxed) as f64 / SKIPBAND_RUNS.load(Relaxed).max(1) as f64,
11865 SKIP_SINGLES.load(Relaxed),
11866 100.0 * SKIPBAND_MBS.load(Relaxed) as f64
11867 / (SKIPBAND_MBS.load(Relaxed) + SKIP_SINGLES.load(Relaxed)).max(1) as f64,
11868 );
11869 eprintln!(
11870 "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={}",
11871 PEQ_FP.load(Relaxed), PEQ_FRAC.load(Relaxed),
11872 BSK_FULLMB.load(Relaxed), BSK_1RECT.load(Relaxed),
11873 BSK_ZBI.load(Relaxed), BSK_ZUNI.load(Relaxed),
11874 BSK_FP.load(Relaxed), BSK_RUNCONT.load(Relaxed),
11875 BSKB_FAST.load(Relaxed),
11876 SKIP_FP_FULL.load(Relaxed), SKIP_FP_LUMA.load(Relaxed),
11877 BSKB_FP.load(Relaxed),
11878 SKIPMV_FORCED.load(Relaxed), SKIPMV_DERIVED.load(Relaxed),
11879 BSKB_FORCED.load(Relaxed),
11880 BZ_SPANS.load(Relaxed), BZ_SPAN_MBS.load(Relaxed),
11881 PZ_SPANS.load(Relaxed), PZ_SPAN_MBS.load(Relaxed),
11882 BMC_BI_FP.load(Relaxed),
11883 WP_SKIPPED.load(Relaxed),
11884 I4_ZERO.load(Relaxed), I4_DC.load(Relaxed),
11885 I4_SPARSE.load(Relaxed), I4_DENSE.load(Relaxed),
11886 T8_ZERO.load(Relaxed), I16_DCONLY.load(Relaxed),
11887 );
11888 let dmb = DBS_MB.load(Relaxed).max(1);
11889 eprintln!(
11890 "DBSDERIVE rows={} mb={} intra={} kindarm={} kindguard={} packed={} flat={} ({:.1}%) allzero={} ({:.1}%) nnz_rowcopy={} t8row={} t8mb={} idc2row={}",
11891 DBS_ROWS.load(Relaxed), DBS_MB.load(Relaxed),
11892 DBS_INTRA.load(Relaxed), DBS_KINDARM.load(Relaxed),
11893 DBS_KINDGUARD.load(Relaxed), DBS_PACKED.load(Relaxed),
11894 DBS_FLAT.load(Relaxed), 100.0 * DBS_FLAT.load(Relaxed) as f64 / dmb as f64,
11895 DBS_ALLZERO.load(Relaxed), 100.0 * DBS_ALLZERO.load(Relaxed) as f64 / dmb as f64,
11896 DBS_NNZ_ROWCOPY.load(Relaxed), DBS_T8ROW.load(Relaxed),
11897 DBS_T8MB.load(Relaxed), DBS_IDC2ROW.load(Relaxed),
11898 );
11899 let (ji, jn) = (J_INTER.load(Relaxed), J_INTER_NORES.load(Relaxed));
11900 eprintln!(
11901 "EDCMIX doubled={} nores_sent={} inter={ji} inter_no_residual={jn} ({:.1}% of inter) wasted_bytes={:.1} MB of {:.1} MB total inter payload",
11902 DOUBLED.load(Relaxed),
11903 J_NORES_SENT.load(Relaxed),
11904 100.0 * jn as f64 / ji.max(1) as f64,
11905 (jn * 2784) as f64 / 1.048576e6,
11906 (ji * 2784) as f64 / 1.048576e6,
11907 );
11908 }
11909}
11910
11911#[cfg(feature = "std")]
11912fn edc_worker(
11913 mut ctx: PixelCtx,
11914 rx: crate::sync::mpsc::Receiver<EdcMsg>,
11915 ctx_tx: crate::sync::mpsc::Sender<PixelCtx>,
11916 back_rx: crate::sync::mpsc::Receiver<PixelCtx>,
11917) -> Option<PixelCtx> {
11918 while let Ok(msg) = rx.recv() {
11919 match msg {
11920 EdcMsg::Batch(jobs) => {
11921 for j in jobs {
11922 match j {
11923 EdcJob::Skip { mbx, mby, mv } => ctx.recon_p_skip(mbx, mby, mv),
11924 EdcJob::Inter(j) => ctx.recon_p_inter(&j),
11925 EdcJob::InterNoRes(j) => ctx.recon_p_inter_nores(&j),
11926 EdcJob::B(j) => ctx.recon_b(&j),
11927 EdcJob::BSkip { mbx, mby, regions } => ctx.recon_b_skip(mbx, mby, ®ions),
11928 }
11929 }
11930 }
11931 EdcMsg::Job(EdcJob::Skip { mbx, mby, mv }) => ctx.recon_p_skip(mbx, mby, mv),
11932 EdcMsg::Job(EdcJob::Inter(j)) => ctx.recon_p_inter(&j),
11933 EdcMsg::Job(EdcJob::InterNoRes(j)) => ctx.recon_p_inter_nores(&j),
11934 EdcMsg::Job(EdcJob::B(j)) => ctx.recon_b(&j),
11935 EdcMsg::Job(EdcJob::BSkip { mbx, mby, regions }) => {
11936 ctx.recon_b_skip(mbx, mby, ®ions)
11937 }
11938 EdcMsg::Row { r, bs, qp, t8 } => {
11939 let (w, base) = (ctx.mb_w, r * ctx.mb_w);
11940 ctx.bs_store[base..base + w].copy_from_slice(&bs);
11941 ctx.qp_grid[base..base + w].copy_from_slice(&qp);
11942 ctx.t8_grid[base..base + w].copy_from_slice(&t8);
11943 if ctx.db_ena {
11944 ctx.save_bak(r);
11945 ctx.filter_row(r);
11946 ctx.flt_rows = r + 1;
11947 ctx.publish_progress_rows();
11948 }
11949 }
11950 EdcMsg::NeedCtx => {
11951 // Inside the worker thread, so there is no caller to return an
11952 // error to and a panic here would abort a thread that did
11953 // nothing wrong. If the parse side has gone there is simply no
11954 // one left to serve: hand the context back where we still hold
11955 // it (`SendError` returns the value) and end the thread.
11956 match ctx_tx.send(ctx) {
11957 Err(e) => return Some(e.0),
11958 Ok(()) => match back_rx.recv() {
11959 Ok(c) => ctx = c,
11960 // The parse side took the context and died with it.
11961 Err(_) => return None,
11962 },
11963 }
11964 }
11965 }
11966 }
11967 Some(ctx)
11968}
11969
11970/// One motion-compensation region of a B macroblock, recorded at parse time
11971/// (E3). Weights are the RESOLVED implicit pair — computing them needs the
11972/// ref lists' POCs, which are parse-side state.
11973pub(crate) struct BRegion {
11974 px: usize,
11975 py: usize,
11976 rw: usize,
11977 rh: usize,
11978 refi0: i32,
11979 refi1: i32,
11980 mv0: (i32, i32),
11981 mv1: (i32, i32),
11982 w: Option<(i32, i32)>,
11983}
11984
11985/// A B macroblock's deferred pixel work: replay the regions into fresh
11986/// prediction buffers, then either copy them out (skip/direct, no residual)
11987/// or run the residual add.
11988pub(crate) struct BJob {
11989 mbx: usize,
11990 mby: usize,
11991 qp: u8,
11992 cbp_chroma: u32,
11993 skip: bool,
11994 regions: Vec<BRegion>,
11995 /// `None` when no 4x4 luma block was coded — t8 macroblocks included,
11996 /// since those carry `luma8` instead.
11997 luma_scan: Option<[[i32; 16]; 16]>,
11998 /// `Some` only under transform_size_8x8_flag.
11999 luma8: Option<[[i32; 64]; 4]>,
12000 cdc: [[i32; 4]; 2],
12001 /// `None` unless cbp_chroma == 2 (chroma AC coded).
12002 cac: Option<[[[i32; 16]; 4]; 2]>,
12003 nnzs: [u8; 24],
12004}
12005
12006impl PixelCtx {
12007 fn b_mc(
12008 &self,
12009 mb_x: usize,
12010 mb_y: usize,
12011 px: usize,
12012 py: usize,
12013 rw: usize,
12014 rh: usize,
12015 refi0: i32,
12016 mv0: (i32, i32),
12017 refi1: i32,
12018 mv1: (i32, i32),
12019 pred_y: &mut [u8; 256],
12020 c_pred: &mut [[u8; 64]; 2],
12021 wparam: Option<(i32, i32)>,
12022 ) {
12023 let _gb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBMc);
12024 // Malformed-stream armor, mirroring the P path: now that B slices actually
12025 // PARSE ref_idx (they used to be hardcoded to 0), a mutated stream can hand
12026 // us an index past the end of either list. Clamp rather than panic — the
12027 // crate is `forbid(unsafe_code)` and fuzz-gated to never panic, and a
12028 // wrong picture on garbage input carries no conformance duty.
12029 let refi0 = if refi0 >= 0 {
12030 (refi0 as usize).min(self.refs.len().saturating_sub(1)) as i32
12031 } else {
12032 -1
12033 };
12034 let refi1 = if refi1 >= 0 {
12035 (refi1 as usize).min(self.refs1.len().saturating_sub(1)) as i32
12036 } else {
12037 -1
12038 };
12039 if (refi0 >= 0 && self.refs.is_empty()) || (refi1 >= 0 && self.refs1.is_empty()) {
12040 return;
12041 }
12042 let (ch, cch) = (self.mb_h * 16, self.mb_h * 8);
12043 // E3: implicit weights are PARSE-side (they read the ref lists' POCs);
12044 // the region carries the resolved pair.
12045 let weights = wparam;
12046 // Bi-prediction blend: the weights decision is LOOP-INVARIANT, so every
12047 // blend site below matches on `weights` ONCE and runs a branch-free
12048 // pixel loop — the unweighted `(p+q+1)>>1` average then autovectorizes
12049 // (the per-pixel closure this replaces hid the invariant behind a
12050 // capture, and its chroma form was a &dyn call PER PIXEL).
12051 // FULL-WIDTH regions (px == 0, rw == 16 — every 16×16/16×8 partition and
12052 // most direct regions) occupy contiguous rows of `pred_y`, so MC writes
12053 // the destination DIRECTLY: uni-pred needs no staging at all, bi-pred
12054 // stages only the second list and blends in place. The staging arrays
12055 // (512 B zeroed per call before this) now exist only on the branches
12056 // that read them. Same fusion as the P path's mc_rect (WHYS Part 8).
12057 let full = px == 0 && rw == 16;
12058 let _gl = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBLuma);
12059 // One scratch borrow for the whole region — both bi-pred passes included.
12060 // The closure yields whether the arm already ran the chroma half (the
12061 // bi-pred full-width arm does, to keep its staging alive) — a plain
12062 // `return` inside would exit the CLOSURE only and chroma would run twice.
12063 let chroma_done =
12064 rusty_h264_common::inter::with_mc_scratch(|scr| match (refi0 >= 0, refi1 >= 0, full) {
12065 (true, false, true) => {
12066 let Some(rf) = self.refs.get(refi0 as usize) else {
12067 return false;
12068 };
12069 rusty_h264_common::inter::mc_luma_padded_pre(
12070 scr,
12071 &rf.luma_guard(rf.ch),
12072 rf.lstride(),
12073 crate::LPAD,
12074 self.cw,
12075 ch,
12076 mb_x * 16,
12077 mb_y * 16 + py,
12078 rw,
12079 rh,
12080 mv0.0,
12081 mv0.1,
12082 &mut pred_y[py * 16..py * 16 + rw * rh],
12083 );
12084 false
12085 }
12086 (false, true, true) => {
12087 let Some(rf) = self.refs1.get(refi1 as usize) else {
12088 return false;
12089 };
12090 rusty_h264_common::inter::mc_luma_padded_pre(
12091 scr,
12092 &rf.luma_guard(rf.ch),
12093 rf.lstride(),
12094 crate::LPAD,
12095 self.cw,
12096 ch,
12097 mb_x * 16,
12098 mb_y * 16 + py,
12099 rw,
12100 rh,
12101 mv1.0,
12102 mv1.1,
12103 &mut pred_y[py * 16..py * 16 + rw * rh],
12104 );
12105 false
12106 }
12107 (true, true, true) => {
12108 let Some(rf) = self.refs.get(refi0 as usize) else {
12109 return false;
12110 };
12111 rusty_h264_common::inter::mc_luma_padded_pre(
12112 scr,
12113 &rf.luma_guard(rf.ch),
12114 rf.lstride(),
12115 crate::LPAD,
12116 self.cw,
12117 ch,
12118 mb_x * 16,
12119 mb_y * 16 + py,
12120 rw,
12121 rh,
12122 mv0.0,
12123 mv0.1,
12124 &mut pred_y[py * 16..py * 16 + rw * rh],
12125 );
12126 let mut b = [0u8; 256];
12127 let Some(rf) = self.refs1.get(refi1 as usize) else {
12128 return false;
12129 };
12130 rusty_h264_common::inter::mc_luma_padded_pre(
12131 scr,
12132 &rf.luma_guard(rf.ch),
12133 rf.lstride(),
12134 crate::LPAD,
12135 self.cw,
12136 ch,
12137 mb_x * 16,
12138 mb_y * 16 + py,
12139 rw,
12140 rh,
12141 mv1.0,
12142 mv1.1,
12143 &mut b[..rw * rh],
12144 );
12145 drop(_gl);
12146 let _gbl =
12147 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
12148 // SLICE-then-zip: proving the bounds ONCE lets rustc emit the whole
12149 // 256-byte average as 8 straight-line vpavgb ops (verified in
12150 // isolation, x86-64-v3); the indexed form kept a per-iteration
12151 // bounds check and a loop. A hand AVX2 kernel is refuted — the
12152 // compiler already emits the ideal instruction.
12153 let dst = &mut pred_y[py * 16..py * 16 + rw * rh];
12154 match weights {
12155 None => {
12156 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
12157 *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
12158 }
12159 }
12160 Some((w0, w1)) => {
12161 for (d, s) in dst.iter_mut().zip(&b[..rw * rh]) {
12162 *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255)
12163 as u8;
12164 }
12165 }
12166 }
12167 let _gc =
12168 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
12169 self.b_mc_chroma(
12170 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
12171 );
12172 true
12173 }
12174 _ => {
12175 // Narrow region — rows are strided in `pred_y`; stage and copy.
12176 let (mut a, mut b) = ([0u8; 256], [0u8; 256]);
12177 if refi0 >= 0 {
12178 let Some(rf) = self.refs.get(refi0 as usize) else {
12179 return false;
12180 };
12181 rusty_h264_common::inter::mc_luma_padded_pre(
12182 scr,
12183 &rf.luma_guard(rf.ch),
12184 rf.lstride(),
12185 crate::LPAD,
12186 self.cw,
12187 ch,
12188 mb_x * 16 + px,
12189 mb_y * 16 + py,
12190 rw,
12191 rh,
12192 mv0.0,
12193 mv0.1,
12194 &mut a[..rw * rh],
12195 );
12196 }
12197 if refi1 >= 0 {
12198 let Some(rf) = self.refs1.get(refi1 as usize) else {
12199 return false;
12200 };
12201 rusty_h264_common::inter::mc_luma_padded_pre(
12202 scr,
12203 &rf.luma_guard(rf.ch),
12204 rf.lstride(),
12205 crate::LPAD,
12206 self.cw,
12207 ch,
12208 mb_x * 16 + px,
12209 mb_y * 16 + py,
12210 rw,
12211 rh,
12212 mv1.0,
12213 mv1.1,
12214 &mut b[..rw * rh],
12215 );
12216 }
12217 drop(_gl);
12218 let _gbl =
12219 rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBBlend);
12220 match (refi0 >= 0, refi1 >= 0) {
12221 (true, true) => {
12222 for dy in 0..rh {
12223 let (ar, br) =
12224 (&a[dy * rw..dy * rw + rw], &b[dy * rw..dy * rw + rw]);
12225 let base = (py + dy) * 16 + px;
12226 let dst = &mut pred_y[base..base + rw];
12227 match weights {
12228 None => {
12229 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
12230 *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
12231 }
12232 }
12233 Some((w0, w1)) => {
12234 for ((d, p), q) in dst.iter_mut().zip(ar).zip(br) {
12235 *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6)
12236 .clamp(0, 255)
12237 as u8;
12238 }
12239 }
12240 }
12241 }
12242 }
12243 (true, false) => {
12244 for dy in 0..rh {
12245 let d = (py + dy) * 16 + px;
12246 pred_y[d..d + rw].copy_from_slice(&a[dy * rw..dy * rw + rw]);
12247 }
12248 }
12249 _ => {
12250 for dy in 0..rh {
12251 let d = (py + dy) * 16 + px;
12252 pred_y[d..d + rw].copy_from_slice(&b[dy * rw..dy * rw + rw]);
12253 }
12254 }
12255 }
12256 false
12257 }
12258 });
12259 if chroma_done {
12260 return;
12261 }
12262 let _gc = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecBChroma);
12263 self.b_mc_chroma(
12264 mb_x, mb_y, px, py, rw, rh, refi0, mv0, refi1, mv1, c_pred, weights, cch,
12265 );
12266 }
12267
12268 fn b_mc_chroma(
12269 &self,
12270 mb_x: usize,
12271 mb_y: usize,
12272 px: usize,
12273 py: usize,
12274 rw: usize,
12275 rh: usize,
12276 refi0: i32,
12277 mv0: (i32, i32),
12278 refi1: i32,
12279 mv1: (i32, i32),
12280 c_pred: &mut [[u8; 64]; 2],
12281 weights: Option<(i32, i32)>,
12282 cch: usize,
12283 ) {
12284 let (crx, cry, crw, crh) = (px / 2, py / 2, rw / 2, rh / 2);
12285 let full = crx == 0 && crw == 8;
12286 for c in 0..2 {
12287 match (refi0 >= 0, refi1 >= 0, full) {
12288 (true, false, true) => {
12289 let Some(rf) = self.refs.get(refi0 as usize) else {
12290 return;
12291 };
12292 let pl = if c == 0 {
12293 &*rf.chroma_guard(0, rf.ch)
12294 } else {
12295 &*rf.chroma_guard(1, rf.ch)
12296 };
12297 mc_chroma_padded(
12298 pl,
12299 rf.cstride(),
12300 crate::CPAD,
12301 self.ccw,
12302 cch,
12303 mb_x * 8,
12304 mb_y * 8 + cry,
12305 crw,
12306 crh,
12307 mv0.0,
12308 mv0.1,
12309 &mut c_pred[c][cry * 8..cry * 8 + crw * crh],
12310 );
12311 }
12312 (false, true, true) => {
12313 let Some(rf) = self.refs1.get(refi1 as usize) else {
12314 return;
12315 };
12316 let pl = if c == 0 {
12317 &*rf.chroma_guard(0, rf.ch)
12318 } else {
12319 &*rf.chroma_guard(1, rf.ch)
12320 };
12321 mc_chroma_padded(
12322 pl,
12323 rf.cstride(),
12324 crate::CPAD,
12325 self.ccw,
12326 cch,
12327 mb_x * 8,
12328 mb_y * 8 + cry,
12329 crw,
12330 crh,
12331 mv1.0,
12332 mv1.1,
12333 &mut c_pred[c][cry * 8..cry * 8 + crw * crh],
12334 );
12335 }
12336 (true, true, true) => {
12337 let Some(rf) = self.refs.get(refi0 as usize) else {
12338 return;
12339 };
12340 let pl = if c == 0 {
12341 &*rf.chroma_guard(0, rf.ch)
12342 } else {
12343 &*rf.chroma_guard(1, rf.ch)
12344 };
12345 mc_chroma_padded(
12346 pl,
12347 rf.cstride(),
12348 crate::CPAD,
12349 self.ccw,
12350 cch,
12351 mb_x * 8,
12352 mb_y * 8 + cry,
12353 crw,
12354 crh,
12355 mv0.0,
12356 mv0.1,
12357 &mut c_pred[c][cry * 8..cry * 8 + crw * crh],
12358 );
12359 let mut cb = [0u8; 64];
12360 let Some(rf) = self.refs1.get(refi1 as usize) else {
12361 return;
12362 };
12363 let pl = if c == 0 {
12364 &*rf.chroma_guard(0, rf.ch)
12365 } else {
12366 &*rf.chroma_guard(1, rf.ch)
12367 };
12368 mc_chroma_padded(
12369 pl,
12370 rf.cstride(),
12371 crate::CPAD,
12372 self.ccw,
12373 cch,
12374 mb_x * 8,
12375 mb_y * 8 + cry,
12376 crw,
12377 crh,
12378 mv1.0,
12379 mv1.1,
12380 &mut cb[..crw * crh],
12381 );
12382 let dst = &mut c_pred[c][cry * 8..cry * 8 + crw * crh];
12383 match weights {
12384 None => {
12385 for (d, s) in dst.iter_mut().zip(&cb[..crw * crh]) {
12386 *d = ((*d as u16 + *s as u16 + 1) >> 1) as u8;
12387 }
12388 }
12389 Some((w0, w1)) => {
12390 for (d, s) in dst.iter_mut().zip(&cb[..crw * crh]) {
12391 *d = ((*d as i32 * w0 + *s as i32 * w1 + 32) >> 6).clamp(0, 255)
12392 as u8;
12393 }
12394 }
12395 }
12396 }
12397 _ => {
12398 let (mut ca, mut cb) = ([0u8; 64], [0u8; 64]);
12399 if refi0 >= 0 {
12400 let Some(rf) = self.refs.get(refi0 as usize) else {
12401 return;
12402 };
12403 let pl = if c == 0 {
12404 &*rf.chroma_guard(0, rf.ch)
12405 } else {
12406 &*rf.chroma_guard(1, rf.ch)
12407 };
12408 mc_chroma_padded(
12409 pl,
12410 rf.cstride(),
12411 crate::CPAD,
12412 self.ccw,
12413 cch,
12414 mb_x * 8 + crx,
12415 mb_y * 8 + cry,
12416 crw,
12417 crh,
12418 mv0.0,
12419 mv0.1,
12420 &mut ca[..crw * crh],
12421 );
12422 }
12423 if refi1 >= 0 {
12424 let Some(rf) = self.refs1.get(refi1 as usize) else {
12425 return;
12426 };
12427 let pl = if c == 0 {
12428 &*rf.chroma_guard(0, rf.ch)
12429 } else {
12430 &*rf.chroma_guard(1, rf.ch)
12431 };
12432 mc_chroma_padded(
12433 pl,
12434 rf.cstride(),
12435 crate::CPAD,
12436 self.ccw,
12437 cch,
12438 mb_x * 8 + crx,
12439 mb_y * 8 + cry,
12440 crw,
12441 crh,
12442 mv1.0,
12443 mv1.1,
12444 &mut cb[..crw * crh],
12445 );
12446 }
12447 match (refi0 >= 0, refi1 >= 0) {
12448 (true, true) => {
12449 for dy in 0..crh {
12450 let (pr, qr) =
12451 (&ca[dy * crw..dy * crw + crw], &cb[dy * crw..dy * crw + crw]);
12452 let base = (cry + dy) * 8 + crx;
12453 let dst = &mut c_pred[c][base..base + crw];
12454 match weights {
12455 None => {
12456 for ((d, p), q) in dst.iter_mut().zip(pr).zip(qr) {
12457 *d = ((*p as u16 + *q as u16 + 1) >> 1) as u8;
12458 }
12459 }
12460 Some((w0, w1)) => {
12461 for ((d, p), q) in dst.iter_mut().zip(pr).zip(qr) {
12462 *d = ((*p as i32 * w0 + *q as i32 * w1 + 32) >> 6)
12463 .clamp(0, 255)
12464 as u8;
12465 }
12466 }
12467 }
12468 }
12469 }
12470 (true, false) => {
12471 for dy in 0..crh {
12472 let d = (cry + dy) * 8 + crx;
12473 c_pred[c][d..d + crw]
12474 .copy_from_slice(&ca[dy * crw..dy * crw + crw]);
12475 }
12476 }
12477 _ => {
12478 for dy in 0..crh {
12479 let d = (cry + dy) * 8 + crx;
12480 c_pred[c][d..d + crw]
12481 .copy_from_slice(&cb[dy * crw..dy * crw + crw]);
12482 }
12483 }
12484 }
12485 }
12486 }
12487 }
12488 }
12489
12490 /// B_Skip replay: regions into fresh prediction buffers, then the plane
12491 /// copy — no residual, no coefficient arrays.
12492 fn recon_b_skip(&mut self, mbx: usize, mby: usize, regions: &[BRegion]) {
12493 crate::RefFrame::set_mc_row_need(mby, self.mb_h * 16);
12494 let mut pred_y = [0u8; 256];
12495 let mut c_pred = [[0u8; 64]; 2];
12496 for r in regions {
12497 self.b_mc(
12498 mbx,
12499 mby,
12500 r.px,
12501 r.py,
12502 r.rw,
12503 r.rh,
12504 r.refi0,
12505 r.mv0,
12506 r.refi1,
12507 r.mv1,
12508 &mut pred_y,
12509 &mut c_pred,
12510 r.w,
12511 );
12512 }
12513 for dy in 0..16 {
12514 let d = (mby * 16 + dy) * self.cw + mbx * 16;
12515 self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
12516 }
12517 for c in 0..2 {
12518 let plane = if c == 0 {
12519 &mut self.rec_u
12520 } else {
12521 &mut self.rec_v
12522 };
12523 for dy in 0..8 {
12524 let d = (mby * 8 + dy) * self.ccw + mbx * 8;
12525 plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
12526 }
12527 }
12528 }
12529
12530 /// Replays one B macroblock's regions + residual (the worker half of the
12531 /// E3 seam). Mirrors the inline order exactly: MC regions in parse order
12532 /// into the prediction buffers, then the residual add (or the skip copy).
12533 fn recon_b(&mut self, j: &BJob) {
12534 crate::RefFrame::set_mc_row_need(j.mby, self.mb_h * 16);
12535 let mut pred_y = [0u8; 256];
12536 let mut c_pred = [[0u8; 64]; 2];
12537 for r in &j.regions {
12538 self.b_mc(
12539 j.mbx,
12540 j.mby,
12541 r.px,
12542 r.py,
12543 r.rw,
12544 r.rh,
12545 r.refi0,
12546 r.mv0,
12547 r.refi1,
12548 r.mv1,
12549 &mut pred_y,
12550 &mut c_pred,
12551 r.w,
12552 );
12553 }
12554 if j.skip {
12555 for dy in 0..16 {
12556 let d = (j.mby * 16 + dy) * self.cw + j.mbx * 16;
12557 self.rec_y[d..d + 16].copy_from_slice(&pred_y[dy * 16..dy * 16 + 16]);
12558 }
12559 for c in 0..2 {
12560 let plane = if c == 0 {
12561 &mut self.rec_u
12562 } else {
12563 &mut self.rec_v
12564 };
12565 for dy in 0..8 {
12566 let d = (j.mby * 8 + dy) * self.ccw + j.mbx * 8;
12567 plane[d..d + 8].copy_from_slice(&c_pred[c][dy * 8..dy * 8 + 8]);
12568 }
12569 }
12570 } else {
12571 self.cur_qp = j.qp;
12572 self.add_inter_residual(
12573 j.mbx,
12574 j.mby,
12575 &pred_y,
12576 &c_pred,
12577 j.luma_scan.as_ref(),
12578 j.luma8.as_ref(),
12579 &j.cdc,
12580 j.cac.as_ref(),
12581 j.cbp_chroma,
12582 &j.nnzs,
12583 );
12584 }
12585 }
12586}
12587
12588/// D9b: MC-call coalescing for a P inter MB from committed per-block (mv, ref).
12589/// Shared by residual and no-residual recon — filters are per-output-pixel so
12590/// wider rects are bit-identical to sixteen 4×4 calls.
12591fn coalesce_p_inter_mc(
12592 refs: &[crate::Ref],
12593 cw: usize,
12594 ccw: usize,
12595 mb_h: usize,
12596 mbx: usize,
12597 mby: usize,
12598 // Grid-width motion: widened by the callee where the MC kernels need i32.
12599 gmv: &[(i16, i16); 16],
12600 gref: &[usize; 16],
12601 pred_y: &mut [u8; 256],
12602 c_pred: &mut [[u8; 64]; 2],
12603) {
12604 let _ms = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::DecMcStage);
12605 let (rh16, cch) = (mb_h * 16, mb_h * 8);
12606 let rect_eq = |x4: usize, y4: usize, w4: usize, h4: usize| -> bool {
12607 let t = y4 * 4 + x4;
12608 (0..h4).all(|dy| {
12609 (0..w4).all(|dx| {
12610 // 4x4 block grid: the index is always < 16, but LLVM cannot
12611 // infer it from the nested ranges. `& 15` states it.
12612 let b = ((y4 + dy) * 4 + (x4 + dx)) & 15;
12613 gmv[b] == gmv[t] && gref[b] == gref[t]
12614 })
12615 })
12616 };
12617 let mut mc_rect = |x4: usize, y4: usize, w4: usize, h4: usize| {
12618 let b = y4 * 4 + x4;
12619 let (mv, gr) = (gmv[b & 15], gref[b & 15]);
12620 let Some(reference) = refs.get(gr) else {
12621 return;
12622 };
12623 let (w, h) = (w4 * 4, h4 * 4);
12624 if w == 16 {
12625 rusty_h264_common::inter::with_mc_scratch(|scr| {
12626 rusty_h264_common::inter::mc_luma_padded_pre(
12627 scr,
12628 &reference.luma_guard(reference.ch),
12629 reference.lstride(),
12630 crate::LPAD,
12631 cw,
12632 rh16,
12633 mbx * 16,
12634 mby * 16 + y4 * 4,
12635 w,
12636 h,
12637 i32::from(mv.0),
12638 i32::from(mv.1),
12639 &mut pred_y[y4 * 64..y4 * 64 + w * h],
12640 )
12641 });
12642 } else {
12643 let mut t = [0u8; 256];
12644 rusty_h264_common::inter::with_mc_scratch(|scr| {
12645 rusty_h264_common::inter::mc_luma_padded_pre(
12646 scr,
12647 &reference.luma_guard(reference.ch),
12648 reference.lstride(),
12649 crate::LPAD,
12650 cw,
12651 rh16,
12652 mbx * 16 + x4 * 4,
12653 mby * 16 + y4 * 4,
12654 w,
12655 h,
12656 i32::from(mv.0),
12657 i32::from(mv.1),
12658 &mut t[..w * h],
12659 )
12660 });
12661 let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
12662 for dy in 0..h {
12663 pred_y[(y4 * 4 + dy) * 16 + x4 * 4..][..w].copy_from_slice(&t[dy * w..dy * w + w]);
12664 }
12665 }
12666 let (cw4, ch4) = (w4 * 2, h4 * 2);
12667 let nc = cw4 * ch4;
12668 let (gu, gv) = (
12669 reference.chroma_guard(0, reference.ch),
12670 reference.chroma_guard(1, reference.ch),
12671 );
12672 // U+V paired: one setup serves both planes (see mc_chroma_padded_pair).
12673 if cw4 == 8 {
12674 let [cu, cv] = &mut *c_pred;
12675 rusty_h264_common::inter::mc_chroma_padded_pair(
12676 &gu,
12677 &gv,
12678 reference.cstride(),
12679 crate::CPAD,
12680 ccw,
12681 cch,
12682 mbx * 8,
12683 mby * 8 + y4 * 2,
12684 cw4,
12685 ch4,
12686 i32::from(mv.0),
12687 i32::from(mv.1),
12688 &mut cu[y4 * 16..y4 * 16 + nc],
12689 &mut cv[y4 * 16..y4 * 16 + nc],
12690 );
12691 } else {
12692 let (mut tu, mut tv) = ([0u8; 64], [0u8; 64]);
12693 rusty_h264_common::inter::mc_chroma_padded_pair(
12694 &gu,
12695 &gv,
12696 reference.cstride(),
12697 crate::CPAD,
12698 ccw,
12699 cch,
12700 mbx * 8 + x4 * 2,
12701 mby * 8 + y4 * 2,
12702 cw4,
12703 ch4,
12704 i32::from(mv.0),
12705 i32::from(mv.1),
12706 &mut tu[..nc],
12707 &mut tv[..nc],
12708 );
12709 let _pb = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::PredBuf);
12710 for (cc, tc) in [(0usize, &tu), (1, &tv)] {
12711 for dy in 0..ch4 {
12712 c_pred[cc][(y4 * 2 + dy) * 8 + x4 * 2..][..cw4]
12713 .copy_from_slice(&tc[dy * cw4..dy * cw4 + cw4]);
12714 }
12715 }
12716 }
12717 };
12718 if rect_eq(0, 0, 4, 4) {
12719 mc_rect(0, 0, 4, 4);
12720 } else if rect_eq(0, 0, 4, 2) && rect_eq(0, 2, 4, 2) {
12721 mc_rect(0, 0, 4, 2);
12722 mc_rect(0, 2, 4, 2);
12723 } else if rect_eq(0, 0, 2, 4) && rect_eq(2, 0, 2, 4) {
12724 mc_rect(0, 0, 2, 4);
12725 mc_rect(2, 0, 2, 4);
12726 } else {
12727 for q in 0..4usize {
12728 let (qx, qy) = ((q % 2) * 2, (q / 2) * 2);
12729 if rect_eq(qx, qy, 2, 2) {
12730 mc_rect(qx, qy, 2, 2);
12731 } else if rect_eq(qx, qy, 2, 1) && rect_eq(qx, qy + 1, 2, 1) {
12732 mc_rect(qx, qy, 2, 1);
12733 mc_rect(qx, qy + 1, 2, 1);
12734 } else if rect_eq(qx, qy, 1, 2) && rect_eq(qx + 1, qy, 1, 2) {
12735 mc_rect(qx, qy, 1, 2);
12736 mc_rect(qx + 1, qy, 1, 2);
12737 } else {
12738 for j in 0..4usize {
12739 mc_rect(qx + (j % 2), qy + (j / 2), 1, 1);
12740 }
12741 }
12742 }
12743 }
12744}
12745
12746/// One deferred pixel-reconstruction job (entropy-decouple E1 seam).
12747enum EdcJob {
12748 Skip {
12749 mbx: usize,
12750 mby: usize,
12751 mv: (i32, i32),
12752 },
12753 Inter(Box<PInterJob>),
12754 B(Box<BJob>),
12755 /// B_Skip / no-residual direct: regions only. The full `BJob` carried
12756 /// 2.6 KB of ZEROED coefficient arrays for ~60% of B macroblocks — the
12757 /// wall-time regression's main CPU tax (B-heavy MT arm measured +45% CPU).
12758 BSkip {
12759 mbx: usize,
12760 mby: usize,
12761 regions: Vec<BRegion>,
12762 },
12763 /// P inter with `cbp == 0` — the P-side twin of `BSkip` (D9).
12764 InterNoRes(Box<PInterNoResJob>),
12765}
12766
12767/// A P inter macroblock with NO residual (`cbp == 0`) — motion only.
12768///
12769/// D9. `PInterJob` is 2,784 bytes and **93% of that is coefficient arrays**
12770/// (`luma_scan` 1024 + `luma8` 1024 + `cac` 512 + `cdc` 32 = 2,592). When
12771/// `cbp == 0` every one of them is ZERO, and the seam was heap-allocating,
12772/// filling, channel-passing and freeing all 2,592 bytes of nothing —
12773/// 12.8-37.5% of inter jobs on the x264 corpus, 15.8-44.1 MB per 60-frame pass.
12774///
12775/// This is the same pathology `EdcJob::BSkip` was introduced to fix on the B
12776/// side ("2.6 KB of ZEROED coefficient arrays for ~60% of B macroblocks — the
12777/// wall-time regression's main CPU tax"). It was never ported to P.
12778///
12779/// Consumer uses `recon_p_inter_nores` (MC + plane copy) — bit-identical to
12780/// `recon_p_inter` on zero residuals, without memset of 2.5 KB coeff arrays.
12781/// (`RS_H264_NORES=0` keeps the old full-job path for A/B — routed at the
12782/// CONSTRUCTOR, so this job carries only what nores recon reads.)
12783struct PInterNoResJob {
12784 mbx: usize,
12785 mby: usize,
12786 t8: bool,
12787 gmv: [(i16, i16); 16],
12788 gref: [u8; 16],
12789}
12790
12791/// The compact inputs of one CABAC P inter macroblock's reconstruction.
12792/// Shared all-zero residual planes. An Option-shaped residual field is `None`
12793/// exactly when the parse wrote nothing into it, and every consumer read is
12794/// already guarded by a zero-count test — so a `None` reader would have seen
12795/// zeros anyway. Pointing at one static beats zero-initialising 1 KB (luma) or
12796/// 512 B (chroma AC) of stack per macroblock.
12797static ZERO_LUMA_SCAN: [[i32; 16]; 16] = [[0; 16]; 16];
12798/// `(16384 + |td|/2) / td` for every `td` the spec can present. `td` is
12799/// `.clamp(-128, 127)` at both call sites (spec 8.4.1.2.3), so the divide has
12800/// exactly 256 possible inputs and a table replaces it EXACTLY -- this is not an
12801/// approximation, it is the same arithmetic evaluated ahead of time. `td == 0`
12802/// is guarded by the caller and maps to 0 here so the table has no hole.
12803static TX_FOR_TD: [i32; 256] = {
12804 let mut t = [0i32; 256];
12805 let mut i = 0usize;
12806 while i < 256 {
12807 let td = i as i32 - 128; // index 0 => -128 .. index 255 => 127
12808 t[i] = if td == 0 {
12809 0
12810 } else {
12811 (16384 + td.abs() / 2) / td
12812 };
12813 i += 1;
12814 }
12815 t
12816};
12817
12818#[inline(always)]
12819fn tx_for_td(td: i32) -> i32 {
12820 debug_assert!((-128..=127).contains(&td), "td must be clamped before this");
12821 TX_FOR_TD[(td.clamp(-128, 127) + 128) as usize]
12822}
12823
12824/// The "no coefficients" neighbour record. `mb_nzc` reads are reached only
12825/// through `if let Some(..) = top/left`, so an out-of-range index is
12826/// unreachable; this is what the unavailable branches already imply.
12827static ZERO_NZC: [u8; 24] = [0u8; 24];
12828static ZERO_CAC: [[[i32; 16]; 4]; 2] = [[[0; 16]; 4]; 2];
12829
12830struct PInterJob {
12831 mbx: usize,
12832 mby: usize,
12833 qp: u8,
12834 cbp_chroma: u32,
12835 /// transform_size_8x8_flag: the residual is `luma8`, not `luma_scan`.
12836 t8: bool,
12837 /// The committed per-block motion, copied at parse time so the worker
12838 /// never reads the parse thread's grids (E2). Ref indices clamped by the
12839 /// consumer, kept u8 (spec max 15).
12840 gmv: [(i16, i16); 16],
12841 gref: [u8; 16],
12842 /// `None` when no 4x4 luma block was coded — t8 macroblocks included,
12843 /// since those carry `luma8` instead.
12844 /// POOLED and reused across macroblocks (see `take_pinter_job`): only the
12845 /// blocks whose `nnzs` entry is nonzero hold the data of this macroblock; every
12846 /// other block is STALE and the consumer never reads it (it copies the
12847 /// prediction when nnz == 0). The parse zeroes exactly the blocks it is
12848 /// about to write.
12849 luma_scan: [[i32; 16]; 16],
12850 luma8: [[i32; 64]; 4],
12851 cdc: [[i32; 4]; 2],
12852 cac: [[[i32; 16]; 4]; 2],
12853 nnzs: [u8; 24],
12854}
12855
12856impl PInterJob {
12857 fn blank() -> Self {
12858 PInterJob {
12859 mbx: 0,
12860 mby: 0,
12861 qp: 0,
12862 cbp_chroma: 0,
12863 t8: false,
12864 gmv: [(0i16, 0i16); 16],
12865 gref: [0; 16],
12866 luma_scan: [[0; 16]; 16],
12867 luma8: [[0; 64]; 4],
12868 cdc: [[0; 4]; 2],
12869 cac: [[[0; 16]; 4]; 2],
12870 nnzs: [0; 24],
12871 }
12872 }
12873}
12874
12875/// Entropy-decouple master knob — DEFAULT ON since 2026-08-05 (`RS_H264_EDC=0`
12876/// opts out). E1 was expected to be cost-neutral scaffolding for the E2
12877/// thread; it BANKED on its own: 13/15 pairs, z=2.84, median +4.0% (pooled
12878/// 19/24, z=2.86). Mechanism: LOOP FISSION — batching a row's parsing and
12879/// then a row's reconstruction keeps each large code path's I-cache and
12880/// branch state hot, instead of alternating two giant bodies per macroblock.
12881fn edc_on() -> bool {
12882 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12883 // constant below; the env A/B arm exists only under `--features knobs`.
12884 #[cfg(not(feature = "knobs"))]
12885 {
12886 true
12887 }
12888 #[cfg(feature = "knobs")]
12889 {
12890 use core::sync::atomic::{AtomicU8, Ordering};
12891 static ON: AtomicU8 = AtomicU8::new(0);
12892 match ON.load(Ordering::Relaxed) {
12893 0 => {
12894 let v = !rusty_h264_common::knob("RS_H264_EDC").is_some_and(|v| v == "0");
12895 ON.store(if v { 1 } else { 2 }, Ordering::Relaxed);
12896 v
12897 }
12898 n => n == 1,
12899 }
12900 }
12901}
12902
12903/// E2/E3 worker-thread knob.
12904///
12905/// **Default OFF (2026-08-11).** ffmpeg's parallel unit is the PICTURE
12906/// (`decode_slice` + `hl_decode_mb` on the same thread). `edc_worker` is a
12907/// nested recon thread — the wrong function:
12908/// * 1T (`ffmpeg -threads 1`, pinvs pin): two threads thrash one core
12909/// * frame-MT (`ffmpeg -threads N`): each picture worker would spawn another
12910/// Frame-MT (`RS_H264_FRAME_THREADS=N`) is the ffmpeg-shaped pool. This
12911/// pipeline stays as an explicit oracle (`RS_H264_EDC_MT=1`) / old auto-gate
12912/// (`=auto`). `=0` forces inline.
12913/// D8: run every pixel reconstruction TWICE (the second pass is discarded work,
12914/// not discarded output -- recon is idempotent, so the bytes are unchanged).
12915/// `t(double) - t(single)` IS the pixel half's cost, which is the parallel
12916/// fraction the E2 seam can address. Byte-identity is the proof the ablation
12917/// did not change the program (unlike removing the stage, which cascades).
12918/// D9 compact no-residual P inter jobs. `RS_H264_NORES=0` restores the old
12919/// always-full-payload path for A/B (the arm must PIN the value, never inherit
12920/// a default -- an "off" arm that only omits an override measures
12921/// default-vs-default and prints all zeros).
12922/// D10 row batching — **DEFAULT ON. A DELIBERATE THROUGHPUT-OVER-LATENCY TRADE.**
12923///
12924/// Ships a row's pixel jobs in one message instead of one per macroblock:
12925/// 207,949 sends -> 3,086 (**67-70x fewer**), and **~3% less CPU**.
12926///
12927/// ⚠ IT COSTS 2-4% WALL on a single stream — 0.963x / 0.980x, **11/11 pairs on
12928/// two clips**, A/B'd against itself at a fixed queue bound. That is measured,
12929/// reproducible, and ACCEPTED, not an oversight. Do not "fix" it by flipping
12930/// the default; read this first.
12931///
12932/// WHY IT COSTS WALL: batching trades pipelining for synchronisation.
12933/// Per-macroblock sends let the worker start on job 1 immediately; a row batch
12934/// makes it idle until ~70 macroblocks are parsed, then hands it a burst.
12935///
12936/// WHY IT IS STILL THE RIGHT DEFAULT: wall time here is SINGLE-STREAM LATENCY;
12937/// CPU is THROUGHPUT. A host decoding many streams concurrently is CPU-bound,
12938/// not latency-bound, so 3% less CPU is ~3% more capacity while the 2-4% wall
12939/// cost falls on a dimension that is not the constraint. The 67x drop in
12940/// channel operations also removes a park/unpark storm that scales with the
12941/// number of runnable threads — it gets better, not worse, as the box fills.
12942///
12943/// `RS_H264_BATCH=0` restores per-macroblock sends for the latency-sensitive
12944/// single-stream case (playback, seek preview, anything where first-frame time
12945/// dominates).
12946fn batch_on() -> bool {
12947 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12948 // constant below; the env A/B arm exists only under `--features knobs`.
12949 #[cfg(not(feature = "knobs"))]
12950 {
12951 true
12952 }
12953 #[cfg(feature = "knobs")]
12954 {
12955 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12956 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_BATCH").is_some_and(|v| v == "0"))
12957 }
12958}
12959
12960fn nores_on() -> bool {
12961 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12962 // constant below; the env A/B arm exists only under `--features knobs`.
12963 #[cfg(not(feature = "knobs"))]
12964 {
12965 true
12966 }
12967 #[cfg(feature = "knobs")]
12968 {
12969 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12970 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_NORES").is_some_and(|v| v == "0"))
12971 }
12972}
12973
12974/// D13 A/B: always allocate B-only CABAC neighbour grids even on P/I slices.
12975fn fat_slice_on() -> bool {
12976 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12977 // constant below; the env A/B arm exists only under `--features knobs`.
12978 #[cfg(not(feature = "knobs"))]
12979 {
12980 false
12981 }
12982 #[cfg(feature = "knobs")]
12983 {
12984 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
12985 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_FAT_SLICE").is_some_and(|v| v == "1"))
12986 }
12987}
12988
12989/// MEASUREMENT KNOB — `RS_H264_NO_SKIPBAND=1` forces the per-MB skip path so the
12990/// mb_skip_run band coalescer can be A/B'd paired on ONE binary. Inert when unset.
12991fn no_skipband() -> bool {
12992 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
12993 // constant below; the env A/B arm exists only under `--features knobs`.
12994 #[cfg(not(feature = "knobs"))]
12995 {
12996 false
12997 }
12998 #[cfg(feature = "knobs")]
12999 {
13000 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13001 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_SKIPBAND").is_some_and(|v| v == "1"))
13002 }
13003}
13004
13005/// MEASUREMENT KNOB — `RS_H264_NO_RUNMV=1` forces the full skip_mv derivation
13006/// on every P_Skip (disables the run-theorem forced-(0,0) branch) for paired A/B.
13007fn no_runmv() -> bool {
13008 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13009 // constant below; the env A/B arm exists only under `--features knobs`.
13010 #[cfg(not(feature = "knobs"))]
13011 {
13012 false
13013 }
13014 #[cfg(feature = "knobs")]
13015 {
13016 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13017 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_RUNMV").is_some_and(|v| v == "1"))
13018 }
13019}
13020
13021/// MEASUREMENT KNOB — `RS_H264_NO_SKIPFP=1` disables the P_Skip single fast
13022/// paths (full-pel direct copy + identity-weight-pass skip) for paired A/B.
13023fn no_skipfp() -> bool {
13024 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13025 // constant below; the env A/B arm exists only under `--features knobs`.
13026 #[cfg(not(feature = "knobs"))]
13027 {
13028 false
13029 }
13030 #[cfg(feature = "knobs")]
13031 {
13032 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13033 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_SKIPFP").is_some_and(|v| v == "1"))
13034 }
13035}
13036
13037/// MEASUREMENT KNOB — `RS_H264_NO_BSKIPFAST=1` forces the full decode_b_direct
13038/// path for B_Skip so the zero-bi fast path can be A/B'd paired on ONE binary.
13039fn no_bskipfast() -> bool {
13040 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13041 // constant below; the env A/B arm exists only under `--features knobs`.
13042 #[cfg(not(feature = "knobs"))]
13043 {
13044 false
13045 }
13046 #[cfg(feature = "knobs")]
13047 {
13048 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13049 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_NO_BSKIPFAST").is_some_and(|v| v == "1"))
13050 }
13051}
13052
13053fn double_recon() -> bool {
13054 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13055 // constant below; the env A/B arm exists only under `--features knobs`.
13056 #[cfg(not(feature = "knobs"))]
13057 {
13058 false
13059 }
13060 #[cfg(feature = "knobs")]
13061 {
13062 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13063 // `== "1"`, not `is_some()`: the old presence test meant even
13064 // `RS_H264_DOUBLE_RECON=0` DOUBLED the recon work — the one knob in the
13065 // inventory whose "off" spelling turned it on (2026-08-27 audit, site 8).
13066 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_DOUBLE_RECON").is_some_and(|v| v == "1"))
13067 }
13068}
13069
13070fn edc_bound() -> usize {
13071 static V: rusty_h264_common::once::OnceLock<usize> = rusty_h264_common::once::OnceLock::new();
13072 *V.get_or_init(|| {
13073 rusty_h264_common::knob("RS_H264_EDC_BOUND")
13074 .and_then(|v| v.parse().ok())
13075 .unwrap_or(256)
13076 })
13077}
13078
13079/// D12 — E2 THREADING DISPATCH. Fires on `720p-or-smaller AND bits/MB > 38.4`.
13080///
13081/// The seam threads unconditionally before this, and that shipped a REGRESSION:
13082/// 8-10% slower wall on main profile for 38-56% more CPU. It cannot pay in
13083/// general — the pixel half is only ~15.6% of decode, so Amdahl caps two
13084/// threads at 1.085x — but it DOES win on some streams, so the answer is a
13085/// dispatch, not abandonment.
13086///
13087/// Fitted with `bench/examples/gate_optimizer.rs` over **28 interleaved
13088/// configurations** (12 clips x core counts 4-8, `bench/pinmtx.ps1`):
13089/// **net +29.40 of +30.70 perfect**, train +25.10 AND holdout +4.30, worst
13090/// fired class **+2.94**, precision 0.80. It forgoes +0.40 of wins to avoid
13091/// **169.90** of losses. Calibration: depth-2 **2/300** rules passed (0.67%),
13092/// so the separation carries information.
13093///
13094/// Per clause, both load-bearing (dropping either: -20.50 / -50.60, 6-7 big
13095/// losers):
13096/// * `bits/MB > 38.4` — coefficient density is the runtime proxy for PIXEL
13097/// SHARE: more coefficients means more residual work on the far side of the
13098/// seam. Threshold sits in an open gap (highest excluded 35.4, lowest firing
13099/// 41.4). Note this is the OPPOSITE direction to an earlier hand-fitted
13100/// `bits < 65`, which was fitted to one clip and falsified.
13101/// * frame <= 720p — every 1080p configuration measured loses, including a
13102/// low-density one, so density alone is not sufficient.
13103/// * `cabac` — ADDED 2026-08-07 after the CAVLC arm made those units
13104/// measurable. bits/MB DOES NOT TRANSFER ACROSS ENTROPY CODERS: CAVLC needs
13105/// more bits for the SAME coefficients, so its density (62-65) reads deep
13106/// inside the firing region while its pixel work is unchanged. Without this
13107/// clause the rule routed CAVLC into threading, where it measured 1.29-1.49x
13108/// SLOWER — net -52.30, worst class -40.85. With it, +29.40 and worst class
13109/// +2.94. `gate_optimizer` could not find this: the rule needs THREE clauses
13110/// and the search is depth-2 (both depth-2 pairs fail, -20.50 / -50.60).
13111///
13112/// The estimate comes from ALREADY-DECODED slices, so the first slice of a
13113/// stream runs INLINE (the safe arm) until a measurement exists. Both arms are
13114/// byte-identical, so the choice can never affect output.
13115/// Invoked only by `RS_H264_EDC_MT=auto` (the pre-2026-08-11 default).
13116fn edc_dispatch(mb_w: usize, mb_h: usize, bits_per_mb: f64, cabac: bool) -> bool {
13117 const BITS_MIN: f64 = 38.4;
13118 const MAX_MBS: usize = 5000; // 720p = 3600, 1080p = 8160
13119 // The `cabac` clause is NOT cosmetic — see the header note. Without it this
13120 // rule scores net -52.30 with worst class -40.85 once CAVLC units are in the
13121 // corpus, because CAVLC's bits/MB is inflated by a less efficient entropy
13122 // coder rather than by more pixel work.
13123 cabac && bits_per_mb > BITS_MIN && mb_w * mb_h <= MAX_MBS
13124}
13125
13126fn edc_mt() -> Option<bool> {
13127 static V: rusty_h264_common::once::OnceLock<Option<bool>> =
13128 rusty_h264_common::once::OnceLock::new();
13129 *V.get_or_init(
13130 || match rusty_h264_common::knob("RS_H264_EDC_MT").as_deref() {
13131 Some("0") => Some(false),
13132 Some("1") => Some(true),
13133 Some("auto") => None,
13134 _ => Some(false),
13135 },
13136 )
13137}
13138
13139/// Spawn `edc_worker`? ffmpeg never does this: the picture thread owns recon.
13140/// Frame-MT workers (`FRAME_THREADS>1`) always inline. Otherwise honor the knob
13141/// (`1` / `0` / `auto`→[`edc_dispatch`]; unset = inline).
13142fn edc_spawn_worker(mb_w: usize, mb_h: usize, bits_per_mb: f64, cabac: bool) -> bool {
13143 #[cfg(not(feature = "std"))]
13144 {
13145 let _ = (mb_w, mb_h, bits_per_mb, cabac);
13146 false
13147 }
13148 #[cfg(feature = "std")]
13149 {
13150 if crate::frame_threads() > 1 {
13151 return false;
13152 }
13153 edc_mt().unwrap_or_else(|| edc_dispatch(mb_w, mb_h, bits_per_mb, cabac))
13154 }
13155}
13156
13157/// Picture-end bS precompute (rowdb-off fallback). `RS_H264_BS_PRE=0` opts out.
13158fn bs_pre_on() -> bool {
13159 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13160 // constant below; the env A/B arm exists only under `--features knobs`.
13161 #[cfg(not(feature = "knobs"))]
13162 {
13163 true
13164 }
13165 #[cfg(feature = "knobs")]
13166 {
13167 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13168 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_BS_PRE").is_some_and(|v| v == "0"))
13169 }
13170}
13171
13172/// Row-interleaved deblocking master knob: `RS_H264_ROWDB=0` opts out,
13173/// restoring the picture-end pipeline (WHYS Part 17) as the A/B comparator.
13174/// MEASUREMENT KNOB — `RS_H264_KIND_LOADS=1` restores the Blk::load-based
13175/// kind arms in derive_bs_row (see the routing comment there) for paired A/B.
13176fn kind_loads() -> bool {
13177 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13178 // constant below; the env A/B arm exists only under `--features knobs`.
13179 #[cfg(not(feature = "knobs"))]
13180 {
13181 false
13182 }
13183 #[cfg(feature = "knobs")]
13184 {
13185 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13186 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_KIND_LOADS").is_some_and(|v| v == "1"))
13187 }
13188}
13189
13190fn rowdb_on() -> bool {
13191 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13192 // constant below; the env A/B arm exists only under `--features knobs`.
13193 #[cfg(not(feature = "knobs"))]
13194 {
13195 true
13196 }
13197 #[cfg(feature = "knobs")]
13198 {
13199 use core::sync::atomic::{AtomicU8, Ordering};
13200 static ON: AtomicU8 = AtomicU8::new(0);
13201 match ON.load(Ordering::Relaxed) {
13202 0 => {
13203 let v = !rusty_h264_common::knob("RS_H264_ROWDB").is_some_and(|v| v == "0");
13204 ON.store(if v { 1 } else { 2 }, Ordering::Relaxed);
13205 v
13206 }
13207 n => n == 1,
13208 }
13209 }
13210}
13211
13212/// A/B: per-MB `row_hook` body even when no row has completed (old behaviour).
13213fn rowhook_eager() -> bool {
13214 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13215 // constant below; the env A/B arm exists only under `--features knobs`.
13216 #[cfg(not(feature = "knobs"))]
13217 {
13218 false
13219 }
13220 #[cfg(feature = "knobs")]
13221 {
13222 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13223 *V.get_or_init(|| rusty_h264_common::knob("RS_H264_ROWHOOK_EAGER").is_some_and(|v| v == "1"))
13224 }
13225}
13226
13227/// A/B: `RS_H264_DIRECT_MEMO=0` rewalks spatial-direct neighbours every 8×8.
13228#[inline]
13229fn direct_memo_on() -> bool {
13230 // ROUTED AT BUILD TIME (routing round 2026-09-05): the shipped arm is the
13231 // constant below; the env A/B arm exists only under `--features knobs`.
13232 #[cfg(not(feature = "knobs"))]
13233 {
13234 true
13235 }
13236 #[cfg(feature = "knobs")]
13237 {
13238 static V: rusty_h264_common::once::OnceLock<bool> = rusty_h264_common::once::OnceLock::new();
13239 *V.get_or_init(|| !rusty_h264_common::knob("RS_H264_DIRECT_MEMO").is_some_and(|v| v == "0"))
13240 }
13241}
13242
13243/// 4×4-block (z-order) → 30-entry (6-stride) mv/ref/mvd cache index (openh264
13244/// g_kCache30ScanIdx). Top neighbour = cache[idx-6], left = cache[idx-1].
13245use rusty_h264_common::cabac_tables::CACHE30;
13246
13247/// z-order 4×4-block → raster index (openh264 g_kuiScan4). Per-MB mvd/ref state is
13248/// stored raster-indexed (matching how neighbour blocks 3/7/11/15 and 12..15 are read).
13249use rusty_h264_common::cabac_tables::G_SCAN4;
13250
13251/// P `sub_mb_type` CABAC (openh264 `ParseSubMBTypeCabac`, ctx 21). 0=8×8, 1=8×4, 2=4×8, 3=4×4.
13252fn parse_sub_mb_type_p_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
13253 const S: usize = 21;
13254 let (data, mut e, ctx) = cab.view();
13255 let v = if e.decode_decision(data, ctx, S) != 0 {
13256 0
13257 } else if e.decode_decision(data, ctx, S + 1) != 0 {
13258 3 - e.decode_decision(data, ctx, S + 2)
13259 } else {
13260 1
13261 };
13262 cab.commit(e);
13263 v
13264}
13265
13266/// Intra `mb_type` sub-parse for P/B slices (openh264 `DecodeCabacIntraMbType`, `base`=32
13267/// for B), on a live engine view. Returns 0 = I_4x4, 1..=24 = I_16x16, 25 = I_PCM (in
13268/// the intra numbering).
13269#[inline(always)]
13270fn intra_mb_type_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx, base: usize) -> u32 {
13271 if e.decode_decision(data, ctx, base) == 0 {
13272 return 0; // I_4x4
13273 }
13274 if e.decode_terminate(data) {
13275 return 25; // I_PCM
13276 }
13277 let mut t = 1 + 12 * e.decode_decision(data, ctx, base + 1); // cbp_luma != 0
13278 if e.decode_decision(data, ctx, base + 2) != 0 {
13279 t += 4 + 4 * e.decode_decision(data, ctx, base + 2);
13280 }
13281 t += 2 * e.decode_decision(data, ctx, base + 3);
13282 t += e.decode_decision(data, ctx, base + 3);
13283 t
13284}
13285
13286/// B `mb_type` CABAC (openh264 `ParseMBTypeBSliceCabac`, ctx base 27). `ctx_inc` = (left
13287/// avail & !direct) + (top avail & !direct). Returns 0 = B_Direct_16x16, 1..=21 = the
13288/// L0/L1/Bi 16x16/16x8/8x16 shapes, 22 = B_8x8, 23.. = intra (mb_type - 23).
13289/// Test-only alias so the ENCODER crate can gate `cb_mb_type_b` against this
13290/// parser directly -- they are exact inverses, so a round-trip is a complete gate.
13291#[doc(hidden)]
13292pub fn parse_mb_type_b(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
13293 parse_mb_type_b_cabac(cab, ctx_inc)
13294}
13295
13296#[inline]
13297fn parse_mb_type_b_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
13298 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
13299 const B: usize = 27;
13300 let (data, mut e, ctx) = cab.view();
13301 let v = mb_type_b_eng(&mut e, data, ctx, B, ctx_inc);
13302 cab.commit(e);
13303 v
13304}
13305
13306#[inline(always)]
13307fn mb_type_b_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx, b: usize, ctx_inc: usize) -> u32 {
13308 if e.decode_decision(data, ctx, b + ctx_inc) == 0 {
13309 return 0; // B_Direct_16x16
13310 }
13311 if e.decode_decision(data, ctx, b + 3) == 0 {
13312 return 1 + e.decode_decision(data, ctx, b + 5); // 16x16 L0 / L1
13313 }
13314 let mut m = e.decode_decision(data, ctx, b + 4) << 3;
13315 m |= e.decode_decision(data, ctx, b + 5) << 2;
13316 m |= e.decode_decision(data, ctx, b + 5) << 1;
13317 m |= e.decode_decision(data, ctx, b + 5);
13318 if m < 8 {
13319 return m + 3;
13320 }
13321 if m == 13 {
13322 return intra_mb_type_eng(e, data, ctx, 32) + 23;
13323 }
13324 if m == 14 {
13325 return 11; // B_Bi_8x16
13326 }
13327 if m == 15 {
13328 return 22; // B_8x8
13329 }
13330 m = (m << 1) | e.decode_decision(data, ctx, b + 5);
13331 m - 4
13332}
13333
13334/// B `sub_mb_type` CABAC (openh264 `ParseBSubMBTypeCabac`, ctx base 36). Returns 0..=12
13335/// per spec Table 7-18 (0 = B_Direct_8x8, 1 = B_L0_8x8, ..., 12 = B_Bi_4x4).
13336fn parse_sub_mb_type_b_cabac(cab: &mut crate::cabac::Cabac) -> u32 {
13337 const B: usize = 36;
13338 let (data, mut e, ctx) = cab.view();
13339 let v = 'v: {
13340 if e.decode_decision(data, ctx, B) == 0 {
13341 break 'v 0; // B_Direct_8x8
13342 }
13343 if e.decode_decision(data, ctx, B + 1) == 0 {
13344 break 'v 1 + e.decode_decision(data, ctx, B + 3); // B_L0_8x8 / B_L1_8x8
13345 }
13346 let mut st = 3u32;
13347 if e.decode_decision(data, ctx, B + 2) != 0 {
13348 if e.decode_decision(data, ctx, B + 3) != 0 {
13349 break 'v 11 + e.decode_decision(data, ctx, B + 3); // B_L1_4x4 / B_Bi_4x4
13350 }
13351 st += 4;
13352 }
13353 st += 2 * e.decode_decision(data, ctx, B + 3);
13354 st += e.decode_decision(data, ctx, B + 3);
13355 st
13356 };
13357 cab.commit(e);
13358 v
13359}
13360
13361/// Parse one motion partition's `mvd` (x,y) and splat it into the 30-entry cache + the
13362/// per-MB raster mvd/ref state. `part_idx` = the partition's top-left z-order block (for
13363/// the ctxInc neighbour lookup); `zblocks` = every z-order 4×4 block the partition covers.
13364fn parse_mvd_partition(
13365 cab: &mut crate::cabac::Cabac,
13366 part_idx: usize,
13367 zblocks: &[usize],
13368 mvdc: &mut [[i16; 2]; 30],
13369 refc: &mut [i8; 30],
13370 mmvd: &mut [[i16; 2]; 16],
13371 mref: &mut [i8; 16],
13372 ref_idx: i8,
13373) -> (i32, i32) {
13374 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
13375 // `CACHE30` is [usize; 16] and every entry lies in [7, 28]; the mask and
13376 // clamp are semantic no-ops that hand LLVM the ranges it cannot infer, so
13377 // the `refc[s - 6]` / `mvdc[s - 1]` indexes into the 30-entry caches become
13378 // provable instead of bounds-checked.
13379 let s = CACHE30[part_idx & 15].clamp(6, 29);
13380 // Neighbour AVAILABILITY is per-neighbour, not per-component: the old closure
13381 // re-tested both refc slots for each of x and y, four loads to answer two
13382 // questions. Resolve once, then sum each component.
13383 let (av_b, av_a) = (refc[s - 6] >= 0, refc[s - 1] >= 0);
13384 let (nb, na) = (mvdc[s - 6], mvdc[s - 1]);
13385 let ctx = |comp: usize| -> usize {
13386 let mut a = 0i32;
13387 if av_b {
13388 a += nb[comp].unsigned_abs() as i32;
13389 }
13390 if av_a {
13391 a += na[comp].unsigned_abs() as i32;
13392 }
13393 if a >= 3 {
13394 1 + (a > 32) as usize
13395 } else {
13396 0
13397 }
13398 };
13399 let (cx, cy) = (ctx(0), ctx(1));
13400 // Both components on ONE engine view (see `Cabac::view`).
13401 let (data, mut e, ctx) = cab.view();
13402 let mvx = mvd_component(&mut e, data, ctx, 0, cx);
13403 let mvy = mvd_component(&mut e, data, ctx, 1, cy);
13404 cab.commit(e);
13405 let mvd = [mvx, mvy];
13406 if zblocks.len() == 16 {
13407 // Whole-macroblock partition (the dominant B shape): every raster slot
13408 // takes the same value — one fill each instead of 16 indexed stores.
13409 mmvd.fill(mvd);
13410 mref.fill(ref_idx);
13411 // The 16 z-blocks land on FOUR contiguous 4-entry runs of the 6-stride
13412 // cache (rows 7.., 13.., 19.., 25..): eight fills instead of 32 stores.
13413 for r in [7usize, 13, 19, 25] {
13414 mvdc[r..r + 4].fill(mvd);
13415 refc[r..r + 4].fill(ref_idx);
13416 }
13417 } else {
13418 for &zb in zblocks {
13419 // Each table was being read twice per block.
13420 let (c, g) = (CACHE30[zb & 15], G_SCAN4[zb & 15]);
13421 mvdc[c] = mvd;
13422 refc[c] = ref_idx;
13423 mmvd[g] = mvd;
13424 mref[g] = ref_idx;
13425 }
13426 }
13427 (mvx as i32, mvy as i32)
13428}
13429
13430/// `ref_idx_l0` (P) CABAC — mirror of the encoder `cb_ref_idx`. Unary, ctxIdxOffset
13431/// 54: binIdx 0 → `ctx0` (condTermFlagA + 2·condTermFlagB), binIdx 1 → 4, binIdx ≥2 → 5.
13432pub fn parse_ref_idx_cabac(cab: &mut crate::cabac::Cabac, ctx0: usize) -> i8 {
13433 const B: usize = 54;
13434 let (data, mut e, ctx) = cab.view();
13435 let mut r = 0i8;
13436 let mut bin_idx = 0u32;
13437 // Cap the unary length: valid ref_idx <= 15 (16 refs max); the cap keeps a corrupt
13438 // stream from looping unboundedly. The MC clamps the index, so an over-range value
13439 // is decoded as garbage (never a panic) -- the robustness contract, not correctness.
13440 while bin_idx < 32 {
13441 let c = match bin_idx {
13442 0 => ctx0,
13443 1 => 4,
13444 _ => 5,
13445 };
13446 if e.decode_decision(data, ctx, B + c) == 0 {
13447 break;
13448 }
13449 r += 1;
13450 bin_idx += 1;
13451 }
13452 cab.commit(e);
13453 r
13454}
13455
13456/// UEG3 mvd suffix (openh264 `DecodeUEGMvCabac`): TU prefix at `base + {0,1,2,3,3,..}`
13457/// (<=7), then EG3 bypass. Inlined into `parse_mvd_partition` for the same
13458/// register-residency reason as `cabac_ueg_level`.
13459#[inline(always)]
13460fn decode_ueg_mv(e: &mut Eng, data: &[u8], ctx: &mut Ctx, base: usize) -> u32 {
13461 const P2C: [usize; 8] = [0, 1, 2, 3, 3, 3, 3, 3];
13462 if e.decode_decision(data, ctx, base) == 0 {
13463 return 0;
13464 }
13465 let mut code = 0u32;
13466 let mut count = 1usize;
13467 let mut tmp;
13468 loop {
13469 // `count` is 1..=7 here; `& 7` is the own bound of the 8-entry table.
13470 tmp = e.decode_decision(data, ctx, base + P2C[count & 7]);
13471 code += 1;
13472 count += 1;
13473 if tmp == 0 || count == 8 {
13474 break;
13475 }
13476 }
13477 if tmp != 0 {
13478 let (v, e2) = cabac_exp_bypass(*e, data, 3);
13479 *e = e2;
13480 code += v + 1;
13481 }
13482 code
13483}
13484
13485/// One `mvd` component (openh264 `ParseMvdInfoCabac`) on a live engine view.
13486/// `ctx_inc` (0/1/2) from the neighbour |mvd| sum. ctxIdxOffset 40 (x) / 47 (y).
13487#[inline(always)]
13488fn mvd_component(e: &mut Eng, data: &[u8], ctx: &mut Ctx, comp: usize, ctx_inc: usize) -> i16 {
13489 let base = 40 + comp * 7; // NEW_CTX_OFFSET_MVD + comp*CTX_NUM_MVD
13490 if e.decode_decision(data, ctx, base + ctx_inc) == 0 {
13491 return 0;
13492 }
13493 let mag = (decode_ueg_mv(e, data, ctx, base + 3) + 1) as i16;
13494 if e.decode_bypass(data) != 0 {
13495 -mag
13496 } else {
13497 mag
13498 }
13499}
13500
13501/// P-slice `mb_type` CABAC (openh264 `ParseMBTypePSliceCabac`). Returns 0..3 = inter
13502/// (P_L0_16x16 / P_16x8 / P_8x16 / P_8x8), 5 = I_4x4, 6..29 = I_16x16, 30 = I_PCM.
13503#[inline(always)]
13504fn mb_type_p_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx) -> u32 {
13505 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
13506 const S: usize = 11; // NEW_CTX_OFFSET_SKIP; P mb_type contexts hang off it
13507
13508 'v: {
13509 if e.decode_decision(data, ctx, S + 3) == 0 {
13510 // inter
13511 break 'v if e.decode_decision(data, ctx, S + 4) != 0 {
13512 if e.decode_decision(data, ctx, S + 6) != 0 {
13513 1
13514 } else {
13515 2
13516 }
13517 } else if e.decode_decision(data, ctx, S + 5) != 0 {
13518 3
13519 } else {
13520 0
13521 };
13522 }
13523 // intra (prefix bit was 1)
13524 if e.decode_decision(data, ctx, S + 6) == 0 {
13525 break 'v 5; // I_4x4
13526 }
13527 if e.decode_terminate(data) {
13528 break 'v 30; // I_PCM
13529 }
13530 let mut t = 6 + e.decode_decision(data, ctx, S + 7) * 12;
13531 if e.decode_decision(data, ctx, S + 8) != 0 {
13532 t += 4;
13533 if e.decode_decision(data, ctx, S + 8) != 0 {
13534 t += 4;
13535 }
13536 }
13537 t += e.decode_decision(data, ctx, S + 9) << 1;
13538 t += e.decode_decision(data, ctx, S + 9);
13539 t
13540 }
13541}
13542
13543/// I-slice `mb_type` CABAC parse (spec §9.3.2.5 / openh264 `ParseMBTypeISliceCabac`).
13544/// `ctx_inc` = (left MB is I_16x16/non-intra) + (top MB is …), i.e. 0..2; the corner
13545/// MB has no neighbours so `ctx_inc = 0`. Returns the raw mb_type: 0 = I_NxN (I_4x4/
13546/// I_8x8), 1..24 = I_16x16 (pred-mode/cbp packed), 25 = I_PCM.
13547fn parse_mb_type_i_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
13548 const O: usize = 3; // ctxIdxOffset for I-slice mb_type
13549 let (data, mut e, ctx) = cab.view();
13550 let v = 'v: {
13551 if e.decode_decision(data, ctx, O + ctx_inc) == 0 {
13552 break 'v 0; // I_NxN
13553 }
13554 if e.decode_terminate(data) {
13555 break 'v 25; // I_PCM
13556 }
13557 let mut t = 1 + e.decode_decision(data, ctx, O + 3) * 12; // CBP luma: 0 or 12
13558 if e.decode_decision(data, ctx, O + 4) != 0 {
13559 t += 4; // CBP chroma 1 or 2
13560 if e.decode_decision(data, ctx, O + 5) != 0 {
13561 t += 4;
13562 }
13563 }
13564 t += e.decode_decision(data, ctx, O + 6) << 1; // I_16x16 pred mode (2 bins)
13565 t += e.decode_decision(data, ctx, O + 7);
13566 t
13567 };
13568 cab.commit(e);
13569 v
13570}
13571
13572/// One `Intra_4x4` (or `8x8`) pred-mode parse on a live engine view (openh264
13573/// `ParseIntraPredModeLumaCabac`): `prev_intra4x4_pred_mode_flag` (ctx 68) then, if 0,
13574/// `rem_intra4x4_pred_mode` (3 bins at ctx 69). Returns `-1` for "use predicted
13575/// mode", else the 0..7 remainder. The 16 (or 4) reads of a macroblock share ONE view.
13576#[inline(always)]
13577fn intra4x4_pred_mode_eng(e: &mut Eng, data: &[u8], ctx: &mut Ctx) -> i32 {
13578 const IPR: usize = 68;
13579 if e.decode_decision(data, ctx, IPR) == 1 {
13580 return -1; // prev_intra4x4_pred_mode_flag = 1
13581 }
13582 let mut m = e.decode_decision(data, ctx, IPR + 1) as i32;
13583 m |= (e.decode_decision(data, ctx, IPR + 1) as i32) << 1;
13584 m |= (e.decode_decision(data, ctx, IPR + 1) as i32) << 2;
13585 m
13586}
13587
13588/// One `Intra_4x4` (or `8x8`) pred-mode CABAC parse (openh264 `ParseIntraPredModeLuma
13589/// Cabac`): `prev_intra4x4_pred_mode_flag` (ctx 68) then, if 0, `rem_intra4x4_pred_mode`
13590/// (3 bins at ctx 69). Returns `-1` for "use predicted mode", else the 0..7 remainder.
13591
13592/// `intra_chroma_pred_mode` CABAC parse (openh264 `ParseIntraPredModeChromaCabac`):
13593/// TU(cMax=3) — bin0 at ctx `64 + ctx_inc` (ctx_inc from neighbour chroma modes, 0 for
13594/// the corner MB), the rest at ctx 67. Returns the mode 0..3.
13595fn parse_intra_chroma_pred_mode_cabac(cab: &mut crate::cabac::Cabac, ctx_inc: usize) -> u32 {
13596 const CIPR: usize = 64;
13597 if cab.decode_decision(CIPR + ctx_inc) == 0 {
13598 return 0;
13599 }
13600 if cab.decode_decision(CIPR + 3) == 0 {
13601 return 1;
13602 }
13603 if cab.decode_decision(CIPR + 3) == 0 {
13604 return 2;
13605 }
13606 3
13607}
13608
13609/// `coded_block_pattern` CABAC parse (openh264 `ParseCbpInfoCabac`), corner-MB variant
13610/// (top/left neighbours unavailable → their terms are 0). ctxIdxOffset 73 (luma) with 4
13611/// z-order 8×8 bins whose ctxInc uses the EARLIER-decoded bits within this MB, then
13612/// chroma bits at 77/81. Returns cbp: bits 0-3 = luma 8×8, bits 4-5 = chroma pattern.
13613pub fn parse_cbp_cabac(cab: &mut crate::cabac::Cabac, top: Option<u8>, left: Option<u8>) -> u32 {
13614 let _g = rusty_h264_common::prof::scope(rusty_h264_common::prof::Stage::Syntax);
13615 const CBP: usize = 73;
13616 // Neighbour terms folded ONCE into two 4-bit masks: bit k of `tz` / `lz` is
13617 // the condTermFlag "the 8x8 block k of that neighbour is NOT coded"
13618 // (unavailable => 0). The six per-bin `Option::map_or` closures this
13619 // replaces re-matched the same two Options for every luma bin.
13620 let tz = top.map_or(0u32, |c| !(c as u32) & 0xF);
13621 let lz = left.map_or(0u32, |c| !(c as u32) & 0xF);
13622 let tc = top.map_or(0u32, |c| (c >> 4) as u32);
13623 let lc = left.map_or(0u32, |c| (c >> 4) as u32);
13624 let (data, mut e, ctx) = cab.view();
13625 // Luma, 4 8x8 blocks in z-order. Top uses cbp bits 2/3, left uses 1/3; a
13626 // block already decoded in THIS macroblock contributes `bin ^ 1`.
13627 let b0 = e.decode_decision(
13628 data,
13629 ctx,
13630 CBP + (((lz >> 1) & 1) + (((tz >> 2) & 1) << 1)) as usize,
13631 );
13632 let b1 = e.decode_decision(
13633 data,
13634 ctx,
13635 CBP + ((b0 ^ 1) + (((tz >> 3) & 1) << 1)) as usize,
13636 );
13637 let b2 = e.decode_decision(
13638 data,
13639 ctx,
13640 CBP + (((lz >> 3) & 1) + ((b0 ^ 1) << 1)) as usize,
13641 );
13642 let b3 = e.decode_decision(data, ctx, CBP + ((b2 ^ 1) + ((b1 ^ 1) << 1)) as usize);
13643 let mut cbp = b0 | (b1 << 1) | (b2 << 2) | (b3 << 3);
13644 // Chroma (4:2:0). ctxInc from neighbour chroma cbp (>>4).
13645 let (ct, cl) = ((tc != 0) as u32, (lc != 0) as u32);
13646 if e.decode_decision(data, ctx, CBP + 4 + (cl + (ct << 1)) as usize) != 0 {
13647 let (ct2, cl2) = ((tc == 2) as u32, (lc == 2) as u32);
13648 let c1 = e.decode_decision(data, ctx, CBP + 8 + (cl2 + (ct2 << 1)) as usize);
13649 cbp |= 1 << (4 + c1);
13650 }
13651 cab.commit(e);
13652 cbp
13653}
13654
13655/// Inlined: `num_ref_active` is loop-invariant at every partition site, so the
13656/// te(v) branch folds and the bit/ue read lands in the caller with no call.
13657#[inline]
13658fn read_ref_idx(r: &mut BitReader, num_ref_active: usize) -> Result<i32, OutOfData> {
13659 if num_ref_active == 2 {
13660 Ok(if r.read_bit()? { 0 } else { 1 }) // te(v): value = !bit
13661 } else {
13662 Ok(r.read_ue()? as i32)
13663 }
13664}
13665
13666/// B-partition prediction direction.
13667#[derive(Clone, Copy, PartialEq)]
13668enum BPred {
13669 L0,
13670 L1,
13671 Bi,
13672}
13673impl BPred {
13674 /// Whether this direction uses reference list `list` (0 or 1).
13675 fn uses(self, list: usize) -> bool {
13676 matches!(
13677 (self, list),
13678 (BPred::L0, 0) | (BPred::L1, 1) | (BPred::Bi, 0) | (BPred::Bi, 1)
13679 )
13680 }
13681}
13682
13683const B16X16: &[(usize, usize, usize, usize)] = &[(0, 0, 16, 16)];
13684const B16X8: &[(usize, usize, usize, usize)] = &[(0, 0, 16, 8), (0, 8, 16, 8)];
13685const B8X16: &[(usize, usize, usize, usize)] = &[(0, 0, 8, 16), (8, 0, 8, 16)];
13686
13687/// A partition region `(x, y, w, h)` in samples.
13688type Region = (usize, usize, usize, usize);
13689
13690/// B `mb_type` 1..=21 → (partition layout, MV-prediction mode 0/1/2 for 16×16/
13691/// 16×8/8×16, per-partition prediction direction) (spec Table 7-14).
13692/// Test-only view of [`b_inter_layout`] for the ENCODER crate: `(mvmode, p0, p1)`
13693/// with pred coded 1 = L0, 2 = L1, 3 = Bi — the encoder's `b_part_mb_type` is the
13694/// exact inverse, so a round-trip over 4..=21 gates the two tables against drift.
13695pub fn b_inter_shape(mb_type: u32) -> (u8, u8, u8) {
13696 let (_, mvmode, preds) = b_inter_layout(mb_type);
13697 let code = |p: BPred| match (p.uses(0), p.uses(1)) {
13698 (true, true) => 3,
13699 (true, false) => 1,
13700 _ => 2,
13701 };
13702 (mvmode, code(preds[0]), code(preds[1]))
13703}
13704
13705fn b_inter_layout(mb_type: u32) -> (&'static [Region], u8, [BPred; 2]) {
13706 use BPred::*;
13707 match mb_type {
13708 1 => (B16X16, 0, [L0, L0]),
13709 2 => (B16X16, 0, [L1, L1]),
13710 3 => (B16X16, 0, [Bi, Bi]),
13711 4 => (B16X8, 1, [L0, L0]),
13712 5 => (B8X16, 2, [L0, L0]),
13713 6 => (B16X8, 1, [L1, L1]),
13714 7 => (B8X16, 2, [L1, L1]),
13715 8 => (B16X8, 1, [L0, L1]),
13716 9 => (B8X16, 2, [L0, L1]),
13717 10 => (B16X8, 1, [L1, L0]),
13718 11 => (B8X16, 2, [L1, L0]),
13719 12 => (B16X8, 1, [L0, Bi]),
13720 13 => (B8X16, 2, [L0, Bi]),
13721 14 => (B16X8, 1, [L1, Bi]),
13722 15 => (B8X16, 2, [L1, Bi]),
13723 16 => (B16X8, 1, [Bi, L0]),
13724 17 => (B8X16, 2, [Bi, L0]),
13725 18 => (B16X8, 1, [Bi, L1]),
13726 19 => (B8X16, 2, [Bi, L1]),
13727 20 => (B16X8, 1, [Bi, Bi]),
13728 _ => (B8X16, 2, [Bi, Bi]), // 21
13729 }
13730}
13731
13732/// Whether a B `sub_mb_type` (1..=12) uses reference list `list`.
13733fn b_sub_uses(st: u32, list: usize) -> bool {
13734 let pred = match st {
13735 1 | 4 | 5 | 10 => 0, // L0
13736 2 | 6 | 7 | 11 => 1, // L1
13737 _ => 2, // Bi (3, 8, 9, 12)
13738 };
13739 (list == 0 && pred != 1) || (list == 1 && pred != 0)
13740}
13741
13742/// Sub-partition shapes within an 8×8 for a B `sub_mb_type` (1..=12).
13743fn b_sub_parts(st: u32) -> &'static [(usize, usize, usize, usize)] {
13744 match st {
13745 1..=3 => &[(0, 0, 8, 8)],
13746 4 | 6 | 8 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
13747 5 | 7 | 9 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
13748 _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)], // 10/11/12
13749 }
13750}
13751
13752/// Sub-macroblock partition layout `(x, y, w, h)` in samples within an 8×8, for
13753/// a P-slice `sub_mb_type` (0 = 8×8, 1 = 8×4, 2 = 4×8, 3 = 4×4).
13754fn sub_mb_partitions(sub_type: u32) -> &'static [(usize, usize, usize, usize)] {
13755 match sub_type {
13756 0 => &[(0, 0, 8, 8)],
13757 1 => &[(0, 0, 8, 4), (0, 4, 8, 4)],
13758 2 => &[(0, 0, 4, 8), (4, 0, 4, 8)],
13759 _ => &[(0, 0, 4, 4), (4, 0, 4, 4), (0, 4, 4, 4), (4, 4, 4, 4)],
13760 }
13761}
13762
13763/// Copy a contiguous `w`x`h` block into a strided destination at `(x0, y0)`.
13764///
13765/// The width is SPECIALISED. Written as a per-pixel loop bounded by a runtime `w`,
13766/// this lowers to a bounds-checked store per pixel — and where it is a row copy of
13767/// runtime length, to a variable-length `memcpy` CALL per row. Both are the same
13768/// codegen trap the ENCODER fixed long ago ("H-17"); the decoder's copy of it was
13769/// never fixed, and it costs the most on exactly the streams a real encoder emits,
13770/// because x264's sub-16x16 partitions call it far more often than our own
13771/// 16x16-dominated bitstreams ever did. Byte-identical to the scalar form.
13772#[inline]
13773fn restride(
13774 dst: &mut [u8],
13775 dst_stride: usize,
13776 x0: usize,
13777 y0: usize,
13778 src: &[u8],
13779 w: usize,
13780 h: usize,
13781) {
13782 macro_rules! rows {
13783 ($n:expr) => {{
13784 for dy in 0..h {
13785 dst[(y0 + dy) * dst_stride + x0..][..$n].copy_from_slice(&src[dy * $n..][..$n]);
13786 }
13787 }};
13788 }
13789 match w {
13790 16 => rows!(16),
13791 8 => rows!(8),
13792 4 => rows!(4),
13793 2 => rows!(2),
13794 _ => {
13795 for dy in 0..h {
13796 dst[(y0 + dy) * dst_stride + x0..][..w].copy_from_slice(&src[dy * w..][..w]);
13797 }
13798 }
13799 }
13800}
13801
13802/// Un-scans an 8×8 block from frame zig-zag scan order to raster (spec Table 8-12).
13803fn un_scan_8x8(scan: &[i32; 64]) -> [i32; 64] {
13804 const ZZ8: [usize; 64] = [
13805 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,
13806 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51,
13807 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
13808 ];
13809 let mut out = [0i32; 64];
13810 for k in 0..64 {
13811 out[ZZ8[k]] = scan[k];
13812 }
13813 out
13814}
13815
13816#[cfg(test)]
13817mod tests {
13818 use super::*;
13819
13820 fn fd(qp: u8, offset: i32) -> FrameDecoder {
13821 FrameDecoder::new(1, 1, qp, offset, Vec::new(), 1, false, false, true)
13822 }
13823
13824 #[test]
13825 fn mb_qp_delta_accumulates_mod_52() {
13826 let mut d = fd(26, 0);
13827 assert_eq!(d.cur_qp, 26, "QPy starts at the slice QP");
13828 d.step_qp(4).unwrap();
13829 assert_eq!(d.cur_qp, 30); // 26 + 4
13830 d.step_qp(-10).unwrap();
13831 assert_eq!(d.cur_qp, 20); // carries from the previous MB, not the slice
13832 // Wrap-around: (20 + 40 + 52) % 52 = 112 % 52 = 8.
13833 d.step_qp(40).unwrap();
13834 assert_eq!(d.cur_qp, 8);
13835 // Negative wrap: (8 - 20 + 52) % 52 = 40.
13836 d.step_qp(-20).unwrap();
13837 assert_eq!(d.cur_qp, 40);
13838 }
13839
13840 #[test]
13841 fn chroma_qp_index_offset_applied_and_clamped() {
13842 // Offset 0 reproduces the bare luma->chroma table (QP30 -> 29).
13843 assert_eq!(fd(0, 0).chroma_qp_for(30), 29);
13844 // Positive offset shifts the table lookup (QP30 + 2 -> table[2] = 31).
13845 assert_eq!(fd(0, 2).chroma_qp_for(30), 31);
13846 // The qPi index is clamped into 0..=51 before the lookup.
13847 assert_eq!(fd(0, -12).chroma_qp_for(5), chroma_qp(0));
13848 assert_eq!(fd(0, 99).chroma_qp_for(40), chroma_qp(51));
13849 }
13850}