Skip to main content

oxideav_av1/decoder/
frame_driver.rs

1//! Spec-faithful frame decode driver — §7.4 `decode_frame_wrapup` order
2//! over the §5.11 `PartitionWalker` syntax walk.
3//!
4//! This is the decode path for **real encoder-produced bitstreams** (as
5//! opposed to the encoder-mirror drivers retired in r428, which
6//! accepted only this crate's own
7//! constrained encoder output). It wires the crate's spec modules end to
8//! end for one frame:
9//!
10//!   1. §5.9 frame-header derived state → [`TileDecodeParams`] +
11//!      [`QuantizerParams`] (including the §5.9.2 `CodedLossless` /
12//!      `LosslessArray` derivations and the §5.9.14 segmentation
13//!      feature plumbing).
14//!   2. §8.2.2 `init_symbol` over the tile bytes + §8.3.1 default CDF
15//!      load (`new_from_defaults` + the q-context-selected
16//!      `init_coeff_cdfs( base_q_idx )` copy).
17//!   3. §5.11.2 [`PartitionWalker::decode_tile_syntax`] — the
18//!      superblock loop over §5.11.4 `decode_partition` → §5.11.5
19//!      `decode_block` → §5.11.34 `residual`, reconstructing every
20//!      intra transform block into `CurrFrame[ plane ]`.
21//!   4. The §7.4 in-loop / post passes in decode order over the
22//!      mi-grid-padded planes: §7.14 deblock (only when a luma filter
23//!      level is nonzero) → §7.15 CDEF → §7.16 superres (both the CDEF
24//!      output and the retained post-deblock frame) → §7.17 loop
25//!      restoration → the §7.18.2 crop → §7.18.3 film grain.
26//!
27//! ## Scope
28//!
29//! * Intra-only frames (KEY / INTRA_ONLY), 8-bit output, single- and
30//!   multi-tile layouts.
31//! * 4:2:0 / 4:2:2 / 4:4:4 and monochrome layouts (the walker threads
32//!   `subsampling_x/y` + `mono_chrome`; only 8-bit output is surfaced).
33//! * Inter frames, `show_existing_frame`, and quantizer-matrix streams
34//!   return [`Error::PartitionWalkOutOfRange`] (follow-ups).
35//!
36//! Spec provenance: `docs/video/av1/av1-spec.txt` §5.9, §5.10, §5.11,
37//! §7.4, §7.12.2, §7.14, §7.15, §7.16, §7.17, §7.18.
38
39use crate::cdf::{
40    FrameInterOrderHints, InterFrameContext, InterWalkPixels, MotionFieldMvs, PartitionWalker,
41    QuantizerParams, TileCdfContext, TileDecodeParams, TileGeometry,
42};
43use crate::encoder::ivf::IvfReader;
44use crate::encoder::tile_group_obu::{parse_tile_group_obu_body, TilePayload};
45use crate::film_grain::film_grain_synthesis;
46use crate::frame_header::{
47    parse_frame_header_with_refs, FrameHeader, RefInfo, NUM_REF_FRAMES, PRIMARY_REF_NONE,
48};
49use crate::inter_pred::get_relative_dist;
50use crate::loop_filter::PlaneBuffer;
51use crate::obu::{ObuIter, ObuType};
52use crate::sequence_header::{parse_sequence_header, SequenceHeader};
53use crate::symbol_decoder::SymbolDecoder;
54use crate::uncompressed_header_tail::{
55    SegmentationParams, ALTREF_FRAME, LAST_FRAME, MAX_SEGMENTS, SEG_LVL_ALT_Q, SEG_LVL_SKIP,
56};
57use crate::Error;
58use crate::{PlaneRefSpec, RefFrameStoreEntry};
59use std::sync::Arc;
60use std::time::{Duration, Instant};
61
62/// r475 — wall time per §7.4 decode stage of the LAST temporal unit
63/// a [`SpecDecodeSession`] decoded (summed over its frames). A
64/// measurement hook for the decode-budget harness
65/// (`examples/decode_bench`); every field is a plain [`Duration`].
66#[doc(hidden)]
67#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
68#[non_exhaustive]
69pub struct DecodeStageTimings {
70    /// OBU framing + §5.5 / §5.9 header parse + §5.11.1 tile-group
71    /// splitting.
72    pub parse: Duration,
73    /// §5.11 tile syntax walk + §7.11 / §7.12 / §7.13 reconstruction
74    /// (every tile).
75    pub tiles: Duration,
76    /// §7.14 deblocking.
77    pub deblock: Duration,
78    /// §7.15 CDEF.
79    pub cdef: Duration,
80    /// §7.16 superres upscaling.
81    pub superres: Duration,
82    /// §7.17 loop restoration.
83    pub restoration: Duration,
84    /// §7.18.2 crop + the §7.20 reference-store payload (`u16`).
85    pub ref_store: Duration,
86    /// §7.18.3 film-grain synthesis.
87    pub grain: Duration,
88    /// Narrowing to the output byte layout + the §7.19 motion-field
89    /// payload.
90    pub output: Duration,
91}
92
93impl DecodeStageTimings {
94    /// The all-zero record.
95    #[must_use]
96    pub fn new() -> Self {
97        Self::default()
98    }
99
100    /// Sum of every stage.
101    #[must_use]
102    pub fn total(&self) -> Duration {
103        self.parse
104            + self.tiles
105            + self.deblock
106            + self.cdef
107            + self.superres
108            + self.restoration
109            + self.ref_store
110            + self.grain
111            + self.output
112    }
113}
114
115/// One frame decoded by the spec-faithful driver. Planes are surfaced
116/// at their §5.9.8 cropped extents (`FrameWidth` × `FrameHeight` for
117/// luma, the §5.5.2 subsampled extent for chroma), row-major, 8-bit.
118#[derive(Debug, Clone, PartialEq, Eq)]
119pub struct SpecFrame {
120    /// Luma width in pixels (`FrameWidth`, post-superres if any).
121    pub width: u32,
122    /// Luma height in pixels (`FrameHeight`).
123    pub height: u32,
124    /// Decoded planes: `[Y]` (monochrome) or `[Y, U, V]`. For
125    /// `bit_depth == 8` one byte per sample; for 10/12-bit output two
126    /// bytes per sample, little-endian (the `yuv4xxp1{0,2}le` layout).
127    pub planes: Vec<Vec<u8>>,
128    /// `(width, height)` per surfaced plane (in samples).
129    pub plane_dims: Vec<(u32, u32)>,
130    /// §5.5.2 `BitDepth` (8, 10 or 12).
131    pub bit_depth: u8,
132}
133
134/// §7.20 per-slot reference store — the decoded frame a later inter
135/// frame motion-compensates against, plus the per-mi grids the §7.9
136/// temporal projection and the §7.21 `show_existing_frame` output
137/// path consume.
138#[derive(Debug, Clone)]
139struct SpecRefSlot {
140    /// §7.20 `FrameStore[ i ][ plane ]` — the post-§7.17 (pre-§7.18.3
141    /// film-grain) planes at their §7.18.2 cropped extents
142    /// (`UpscaledWidth × FrameHeight`, per-plane subsampled), `u16`
143    /// (post-`Clip1`, so the widening from the walker's `i32` is
144    /// lossless).
145    planes: Vec<Vec<u16>>,
146    /// `(width, height)` per stored plane.
147    plane_dims: Vec<(u32, u32)>,
148    /// §7.20 `SavedMvs[ i ]` — the §7.19 `MfMvs[ row ][ col ][ 0..2 ]`
149    /// grid snapshot (2 `i16` per mi cell), the §7.9.2 projection
150    /// source.
151    mf_mvs: Vec<i16>,
152    /// §7.20 `SavedRefFrames[ i ]` — the §7.19 `MfRefFrames[ row ][
153    /// col ]` grid snapshot (1 `i8` per mi cell).
154    mf_ref_frames: Vec<i8>,
155    /// §7.20 `SavedOrderHints[ i ][ ref ]` — the stored frame's
156    /// `OrderHints[]` array (what ITS references' output order was),
157    /// consumed by the §7.9.1/§7.9.2 `SavedOrderHints[ srcIdx ][ .. ]`
158    /// reads.
159    saved_order_hints: [i32; ALTREF_FRAME + 1],
160    /// The stored frame's mi extent.
161    mi_rows: u32,
162    mi_cols: u32,
163    /// §7.20 `RefFrameType[ i ]` — `FrameIsIntra` of the stored frame
164    /// (the §7.9.2 projection skips KEY / INTRA_ONLY sources).
165    frame_is_intra: bool,
166    /// §7.20 `RefFrameType[ i ] == KEY_FRAME` — the §7.21 trigger.
167    frame_type_is_key: bool,
168    /// §7.20 `RefBitDepth[ i ]` — the stored frame's `BitDepth`.
169    bit_depth: u8,
170    /// §7.20 `save_cdfs( i )` — the frame-end CDF state (§8.4
171    /// `frame_end_update_cdf`), loaded back by §8.3.1 `load_cdfs()`
172    /// when a later frame names this slot as its primary reference.
173    cdfs: Box<TileCdfContext>,
174    /// §7.20 `SavedGmParams[ i ]` — this frame's `gm_params`.
175    gm_params: [[i32; 6]; 8],
176    /// §7.20 `save_loop_filter_params( i )` — the §5.9.11 running
177    /// delta state at the end of this frame's header parse.
178    lf_ref_deltas: [i8; 8],
179    /// Mode-delta half of `save_loop_filter_params( i )`.
180    lf_mode_deltas: [i8; 2],
181    /// §7.20 `SavedSegmentIds[ i ]` — the decoded frame's
182    /// `SegmentIds[ row ][ col ]` grid (row-major, one `i32` per mi
183    /// cell), loaded back by `load_previous_segment_ids()` as the
184    /// §5.11.21 `PrevSegmentIds` prediction source.
185    segment_ids: Vec<i32>,
186    /// §7.20 `save_grain_params( i )` — the frame's resolved §5.9.30
187    /// film-grain state (post-`load_grain_params` on the
188    /// `update_grain == 0` predicted path). `None` when the frame
189    /// carries no grain (`reset_grain_params()`). Loaded back by the
190    /// §5.9.30 `update_grain == 0` arm and by the §5.9.2
191    /// `show_existing_frame` `load_grain_params(
192    /// frame_to_show_map_idx )` output path.
193    grain_params: Option<crate::uncompressed_header_tail::FilmGrainParams>,
194}
195
196/// Cross-frame decoder session state: the §5.9.2 `RefInfo` arrays the
197/// inter `uncompressed_header()` parse consumes plus the §7.20
198/// per-slot pixel/grid stores.
199#[derive(Debug, Clone)]
200struct SpecRefState {
201    info: RefInfo,
202    /// r453 — `Arc`-shared: §7.20 stores the SAME immutable payload
203    /// into every slot `refresh_frame_flags` selects (and the §7.21
204    /// show-existing re-store copies a stored one wholesale), so an
205    /// all-refresh KEY frame keeps ONE payload alive instead of
206    /// eight deep copies of planes + grids + CDFs. Nothing mutates a
207    /// stored payload in place.
208    slots: [Option<Arc<SpecRefSlot>>; NUM_REF_FRAMES as usize],
209}
210
211impl SpecRefState {
212    fn new() -> Self {
213        Self {
214            info: RefInfo::default(),
215            slots: Default::default(),
216        }
217    }
218}
219
220/// One frame decoded by [`decode_frame_spec_full`] — the surfaced
221/// [`SpecFrame`] plus the §7.20 reference-update payload.
222struct DecodedFrameInternal {
223    /// The §7.18 output frame (post-film-grain).
224    frame: SpecFrame,
225    /// The §7.20 store payload: pre-grain cropped planes (`u16`).
226    ref_planes: Vec<Vec<u16>>,
227    /// `(width, height)` per `ref_planes` entry.
228    ref_plane_dims: Vec<(u32, u32)>,
229    /// §7.19 `MfMvs[]` grid (2 `i16` per mi cell).
230    mf_mvs: Vec<i16>,
231    /// §7.19 `MfRefFrames[]` grid (1 `i8` per mi cell).
232    mf_ref_frames: Vec<i8>,
233    /// This frame's `OrderHints[]` array (§5.9.2), the §7.20
234    /// `SavedOrderHints` payload.
235    order_hints_by_ref: [i32; ALTREF_FRAME + 1],
236    /// §8.4 frame-end CDF state (`frame_end_update_cdf` output — the
237    /// `context_update_tile_id` tile's adapted CDFs, or the frame-start
238    /// state under `disable_frame_end_update_cdf == 1`).
239    end_cdfs: Box<TileCdfContext>,
240    /// The decoded frame's mi extent.
241    mi_rows: u32,
242    mi_cols: u32,
243    /// §7.20 `SegmentIds[][]` snapshot (unwritten `-1` cells clamped
244    /// to `0`, matching the spec's always-written map).
245    segment_ids: Vec<i32>,
246    /// The frame's RESOLVED film-grain state (the §5.9.30
247    /// `update_grain == 0` predicted path replaced by the referenced
248    /// slot's saved params with this frame's `grain_seed`), the §7.20
249    /// `save_grain_params` payload.
250    grain_params: Option<crate::uncompressed_header_tail::FilmGrainParams>,
251}
252
253/// §5.9.2 `LosslessArray[ segmentId ]` — `get_qindex( 1, segmentId ) ==
254/// 0 && DeltaQYDc == 0 && DeltaQ{U,V}{Ac,Dc} == 0` for every segment.
255pub(crate) fn lossless_array(
256    qp: &crate::uncompressed_header_tail::QuantizationParams,
257    sp: &SegmentationParams,
258) -> [bool; MAX_SEGMENTS] {
259    let deltas_all_zero = qp.delta_q_y_dc == 0
260        && qp.delta_q_u_dc == 0
261        && qp.delta_q_u_ac == 0
262        && qp.delta_q_v_dc == 0
263        && qp.delta_q_v_ac == 0;
264    let mut out = [false; MAX_SEGMENTS];
265    for (segment_id, slot) in out.iter_mut().enumerate() {
266        // §7.12.2 get_qindex( ignoreDeltaQ = 1, segmentId ).
267        let qindex = if sp.enabled && sp.segment_feature_active[segment_id][SEG_LVL_ALT_Q] {
268            let data = i32::from(sp.segment_feature_data[segment_id][SEG_LVL_ALT_Q]);
269            (i32::from(qp.base_q_idx) + data).clamp(0, 255)
270        } else {
271            i32::from(qp.base_q_idx)
272        };
273        *slot = qindex == 0 && deltas_all_zero;
274    }
275    out
276}
277
278/// Decode one intra frame through the §5.11 syntax walker + the §7.4
279/// post-pass chain, given its already-parsed sequence header, frame
280/// header, and the §5.11.1 tile-group OBU body.
281pub fn decode_frame_spec(
282    seq: &SequenceHeader,
283    fh: &FrameHeader,
284    tile_group_body: &[u8],
285) -> Result<SpecFrame, Error> {
286    // The historical intra-only entry: no cross-frame reference state,
287    // so inter frames are rejected inside the full driver.
288    let mut stages = DecodeStageTimings::default();
289    Ok(decode_frame_spec_full(seq, fh, tile_group_body, None, &mut stages, 1)?.frame)
290}
291
292/// Parse one §5.11.1 tile-group OBU body against the frame's
293/// [`TileInfo`] — the shared derivation (`NumTiles`, `tileBits`,
294/// `TileSizeBytes`) both the whole-frame path and the r433
295/// multi-tile-group accumulation ride.
296fn parse_tile_group_body_with_ti(
297    ti: &crate::tile_info::TileInfo,
298    body: &[u8],
299) -> Result<crate::encoder::tile_group_obu::ParsedTileGroup, Error> {
300    let ceil_log2 = |v: u32| -> u32 {
301        if v <= 1 {
302            0
303        } else {
304            32 - (v - 1).leading_zeros()
305        }
306    };
307    if ti.mi_col_starts.len() != (ti.tile_cols as usize) + 1
308        || ti.mi_row_starts.len() != (ti.tile_rows as usize) + 1
309    {
310        return Err(Error::PartitionWalkOutOfRange);
311    }
312    parse_tile_group_obu_body(
313        body,
314        ti.tile_cols * ti.tile_rows,
315        ceil_log2(ti.tile_cols),
316        ceil_log2(ti.tile_rows),
317        u32::from(ti.tile_size_bytes),
318    )
319}
320
321/// Parse a tile-group body that must carry the WHOLE frame — the
322/// §5.10 `frame_obu` shape (§6.10.1 requires
323/// `tile_start_and_end_present_flag == 0` there, which forces
324/// `tg_start = 0` / `tg_end = NumTiles - 1`) and the historical
325/// single-tile-group packing the standalone entries accept.
326fn parse_whole_frame_tile_group(
327    ti: &crate::tile_info::TileInfo,
328    body: &[u8],
329    require_flag_zero: bool,
330) -> Result<Vec<TilePayload>, Error> {
331    let parsed = parse_tile_group_body_with_ti(ti, body)?;
332    let num_tiles = ti.tile_cols * ti.tile_rows;
333    if parsed.tg_start != 0 || parsed.tg_end + 1 != num_tiles {
334        return Err(Error::TileGroupInvalid);
335    }
336    if require_flag_zero && parsed.tile_start_and_end_present_flag {
337        return Err(Error::TileGroupInvalid);
338    }
339    Ok(parsed.tiles)
340}
341
342/// [`decode_frame_spec`] with the cross-frame reference state — the
343/// full §5.11 + §7.4 decode for one KEY / INTRA_ONLY / INTER frame
344/// whose tile-group body carries every tile, returning both the
345/// output frame and the §7.20 reference-update payload.
346fn decode_frame_spec_full(
347    seq: &SequenceHeader,
348    fh: &FrameHeader,
349    tile_group_body: &[u8],
350    refs: Option<&SpecRefState>,
351    stages: &mut DecodeStageTimings,
352    threads: usize,
353) -> Result<DecodedFrameInternal, Error> {
354    let ti = fh
355        .tile_info
356        .as_ref()
357        .ok_or(Error::PartitionWalkOutOfRange)?;
358    let t0 = Instant::now();
359    let tiles = parse_whole_frame_tile_group(ti, tile_group_body, false)?;
360    stages.parse += t0.elapsed();
361    decode_frame_spec_tiles(seq, fh, &tiles, refs, stages, threads)
362}
363
364/// The frame decode proper, over already-parsed per-tile §8.2
365/// payloads — `tiles` must hold exactly `NumTiles` entries in tile
366/// raster order (§5.11.1 `TileNum` order; the r433 multi-tile-group
367/// accumulation concatenates conformant `tg_start..=tg_end` slices to
368/// get here).
369fn decode_frame_spec_tiles(
370    seq: &SequenceHeader,
371    fh: &FrameHeader,
372    tiles: &[TilePayload],
373    refs: Option<&SpecRefState>,
374    stages: &mut DecodeStageTimings,
375    threads: usize,
376) -> Result<DecodedFrameInternal, Error> {
377    if fh.show_existing_frame {
378        // §7.21 output-existing path is the caller's (no tile group).
379        return Err(Error::PartitionWalkOutOfRange);
380    }
381    if !fh.frame_is_intra && refs.is_none() {
382        // Inter frames are undecodable without the §7.20 store.
383        return Err(Error::PartitionWalkOutOfRange);
384    }
385    let cc = &seq.color_config;
386    if !matches!(cc.bit_depth, 8 | 10 | 12) {
387        return Err(Error::PartitionWalkOutOfRange);
388    }
389    let fs = fh
390        .frame_size
391        .as_ref()
392        .ok_or(Error::PartitionWalkOutOfRange)?;
393    if fs.use_superres && fs.upscaled_width <= fs.frame_width {
394        // §5.9.8 conformance: superres only ever widens.
395        return Err(Error::PartitionWalkOutOfRange);
396    }
397    let ti = fh
398        .tile_info
399        .as_ref()
400        .ok_or(Error::PartitionWalkOutOfRange)?;
401    let qp = fh
402        .quantization_params
403        .as_ref()
404        .ok_or(Error::PartitionWalkOutOfRange)?;
405    let default_seg = SegmentationParams::disabled();
406    let sp = fh.segmentation_params.as_ref().unwrap_or(&default_seg);
407    let dq = fh.delta_q_params.unwrap_or_default();
408    let dlf = fh.delta_lf_params.unwrap_or_default();
409    let lr = fh
410        .lr_params
411        .as_ref()
412        .ok_or(Error::PartitionWalkOutOfRange)?;
413    // §5.11.57 loop-restoration state for the per-superblock `read_lr`
414    // interleave (walker-facing shape).
415    let lr_walk = crate::cdf::LrParams {
416        num_planes: cc.num_planes as usize,
417        frame_restoration_type: [
418            lr.frame_restoration_type[0] as u8,
419            lr.frame_restoration_type[1] as u8,
420            lr.frame_restoration_type[2] as u8,
421        ],
422        loop_restoration_size: lr.loop_restoration_size,
423        subsampling_x: u8::from(cc.subsampling_x),
424        subsampling_y: u8::from(cc.subsampling_y),
425        frame_height: fs.frame_height,
426        upscaled_width: fs.upscaled_width,
427        use_superres: fs.use_superres,
428        superres_denom: fs.superres_denom,
429        allow_intrabc: fh.allow_intrabc,
430    };
431
432    // §5.9.2 CodedLossless / LosslessArray.
433    let lossless = lossless_array(qp, sp);
434    let coded_lossless = lossless.iter().all(|&l| l);
435
436    // §7.12.2 quantizer state for the walk. `CurrentQIndex` starts at
437    // `base_q_idx` (§5.9.18); the walker's §5.11.13 read_delta_qindex
438    // maintains the running value internally.
439    let seg_alt_q_active = {
440        let mut a = [false; MAX_SEGMENTS];
441        for (i, slot) in a.iter_mut().enumerate() {
442            *slot = sp.segment_feature_active[i][SEG_LVL_ALT_Q];
443        }
444        a
445    };
446    let seg_alt_q_data = {
447        let mut a = [0i16; MAX_SEGMENTS];
448        for (i, slot) in a.iter_mut().enumerate() {
449            *slot = sp.segment_feature_data[i][SEG_LVL_ALT_Q];
450        }
451        a
452    };
453    // §5.9.2 `SegQMLevel[ plane ][ segmentId ]`: when `using_qmatrix`,
454    // a lossless segment takes the no-QM sentinel `15`, every other
455    // segment `[ qm_y, qm_u, qm_v ]`. With `using_qmatrix == 0` the
456    // §7.12.3 QM arm never fires and the neutral all-`15` fill stands.
457    let mut seg_qm_level = [[15u8; MAX_SEGMENTS]; 3];
458    if qp.using_qmatrix {
459        for segment_id in 0..MAX_SEGMENTS {
460            if !lossless[segment_id] {
461                seg_qm_level[0][segment_id] = qp.qm_y;
462                seg_qm_level[1][segment_id] = qp.qm_u;
463                seg_qm_level[2][segment_id] = qp.qm_v;
464            }
465        }
466    }
467    let quant = QuantizerParams {
468        base_q_idx: qp.base_q_idx,
469        delta_q_y_dc: qp.delta_q_y_dc,
470        delta_q_u_dc: qp.delta_q_u_dc,
471        delta_q_u_ac: qp.delta_q_u_ac,
472        delta_q_v_dc: qp.delta_q_v_dc,
473        delta_q_v_ac: qp.delta_q_v_ac,
474        using_qmatrix: qp.using_qmatrix,
475        bit_depth: cc.bit_depth,
476        delta_q_present: dq.delta_q_present,
477        current_q_index: qp.base_q_idx,
478        segmentation_enabled: sp.enabled,
479        seg_alt_q_active,
480        seg_alt_q_data,
481        seg_qm_level,
482    };
483
484    // r426 — per-segment SEG_LVL_SKIP active flags: the §5.11.11
485    // short-circuit derives per block from the coded segment id.
486    let seg_skip: [bool; MAX_SEGMENTS] = if sp.enabled {
487        core::array::from_fn(|s| sp.segment_feature_active[s][SEG_LVL_SKIP])
488    } else {
489        [false; MAX_SEGMENTS]
490    };
491
492    let cdef_bits = fh
493        .cdef_params
494        .as_ref()
495        .map_or(0, |c| u32::from(c.cdef_bits));
496    let tx_mode_select = matches!(
497        fh.tx_mode,
498        Some(crate::uncompressed_header_tail::TxMode::TxModeSelect)
499    );
500
501    let params = TileDecodeParams {
502        frame_is_intra: fh.frame_is_intra,
503        subsampling_x: u8::from(cc.subsampling_x),
504        subsampling_y: u8::from(cc.subsampling_y),
505        num_planes: cc.num_planes,
506        seg_id_pre_skip: sp.seg_id_pre_skip,
507        segmentation_enabled: sp.enabled,
508        seg_skip,
509        last_active_seg_id: sp.last_active_seg_id,
510        lossless_array: &lossless,
511        coded_lossless,
512        enable_cdef: seq.enable_cdef,
513        allow_intrabc: fh.allow_intrabc,
514        cdef_bits,
515        use_128x128_superblock: seq.use_128x128_superblock,
516        delta_q_res: dq.delta_q_res,
517        delta_lf_present: dlf.delta_lf_present,
518        delta_lf_multi: dlf.delta_lf_multi,
519        mono_chrome: cc.mono_chrome,
520        delta_lf_res: dlf.delta_lf_res,
521        allow_screen_content_tools: fh.allow_screen_content_tools,
522        enable_filter_intra: seq.enable_filter_intra,
523        bit_depth: cc.bit_depth,
524        tx_mode_select,
525        reduced_tx_set: fh.reduced_tx_set.unwrap_or(false),
526        enable_intra_edge_filter: seq.enable_intra_edge_filter,
527    };
528
529    // Per-tile §8.2 byte ranges — the caller parsed the §5.11.1
530    // grouping; this driver requires the full frame's tiles.
531    let num_tiles = ti.tile_cols * ti.tile_rows;
532    if ti.mi_col_starts.len() != (ti.tile_cols as usize) + 1
533        || ti.mi_row_starts.len() != (ti.tile_rows as usize) + 1
534    {
535        return Err(Error::PartitionWalkOutOfRange);
536    }
537    if tiles.len() != num_tiles as usize {
538        return Err(Error::TileGroupInvalid);
539    }
540
541    let mi_rows = fs.mi_rows;
542    let mi_cols = fs.mi_cols;
543    let mut walker = PartitionWalker::new(
544        mi_rows,
545        mi_cols,
546        TileGeometry {
547            mi_row_start: 0,
548            mi_row_end: mi_rows,
549            mi_col_start: 0,
550            mi_col_end: mi_cols,
551        },
552    )
553    .ok_or(Error::PartitionWalkOutOfRange)?;
554
555    // ---- Inter-frame state: §5.11.18 context + §7.11.3 ref pixels. ----
556    // Owned buffers first (the `InterFrameContext` borrows them), then
557    // the context itself. All of it is inert on intra frames.
558    let is_inter_frame = !fh.frame_is_intra;
559    let sub_x_u8 = u8::from(cc.subsampling_x);
560    let sub_y_u8 = u8::from(cc.subsampling_y);
561    let num_planes_usize = cc.num_planes as usize;
562    // §7.9 motion-field grid — the temporal-scan source. §5.9.2
563    // invokes `motion_field_estimation()` only when `use_ref_frame_mvs
564    // == 1`; the projection walks the §7.20-stored `SavedMvs` /
565    // `SavedRefFrames` / `SavedOrderHints` of up to four reference
566    // frames (LAST backwards, BWDREF / ALTREF2 / ALTREF forwards,
567    // LAST2 as the stack filler).
568    let mfmvs = if is_inter_frame
569        && fh
570            .inter_refs
571            .as_ref()
572            .is_some_and(|ir| ir.use_ref_frame_mvs)
573    {
574        motion_field_estimation(
575            refs.ok_or(Error::PartitionWalkOutOfRange)?,
576            fh,
577            seq,
578            mi_rows,
579            mi_cols,
580        )?
581    } else {
582        MotionFieldMvs::new_invalid(mi_rows, mi_cols)
583    };
584    // §5.9.2 `load_previous_segment_ids()` — with a primary reference
585    // whose stored mi extent matches, `PrevSegmentIds` is that slot's
586    // §7.20 `SavedSegmentIds`; otherwise all-zero (also the
587    // `setup_past_independence()` state for PRIMARY_REF_NONE).
588    let prev_segment_ids: Option<Vec<i32>> =
589        if is_inter_frame && sp.enabled && fh.primary_ref_frame != PRIMARY_REF_NONE {
590            let st = refs.ok_or(Error::PartitionWalkOutOfRange)?;
591            let ir = fh
592                .inter_refs
593                .as_ref()
594                .ok_or(Error::PartitionWalkOutOfRange)?;
595            let slot = ir.ref_frame_idx[fh.primary_ref_frame as usize] as usize;
596            match st.slots.get(slot).and_then(|s| s.as_ref()) {
597                Some(prev)
598                    if prev.mi_rows == mi_rows
599                        && prev.mi_cols == mi_cols
600                        && prev.segment_ids.len() == (mi_rows as usize) * (mi_cols as usize) =>
601                {
602                    Some(prev.segment_ids.clone())
603                }
604                _ => Some(vec![0i32; (mi_rows as usize) * (mi_cols as usize)]),
605            }
606        } else if is_inter_frame && sp.enabled {
607            Some(vec![0i32; (mi_rows as usize) * (mi_cols as usize)])
608        } else {
609            None
610        };
611    // Never-referenced placeholder for empty §7.20 slots (a conformant
612    // stream only references `RefValid` slots).
613    let dummy_plane: Vec<u16> = vec![0u16; 64];
614    let dummy_entry = || RefFrameStoreEntry {
615        plane: &dummy_plane,
616        stride: 8,
617        upscaled_width: 8,
618        width: 8,
619        height: 8,
620    };
621    // Per-plane `FrameStore[ slot ]` views over the §7.20 store.
622    let mut plane_stores: Vec<[RefFrameStoreEntry<'_>; NUM_REF_FRAMES as usize]> = Vec::new();
623    let mut ref_frame_idx = [0u8; 7];
624    let mut order_hints_by_ref = [0i32; ALTREF_FRAME + 1];
625    let mut sign_bias = [0i32; 8];
626    let mut is_scaled_per_ref = [false; 7];
627    if is_inter_frame {
628        let st = refs.ok_or(Error::PartitionWalkOutOfRange)?;
629        let ir = fh
630            .inter_refs
631            .as_ref()
632            .ok_or(Error::PartitionWalkOutOfRange)?;
633        for plane in 0..num_planes_usize {
634            let arr: [RefFrameStoreEntry<'_>; NUM_REF_FRAMES as usize] =
635                core::array::from_fn(|slot| match st.slots[slot].as_ref() {
636                    Some(s) if plane < s.planes.len() => {
637                        let (w, _h) = s.plane_dims[plane];
638                        // r405: the dimension fields carry the stored
639                        // frame's LUMA extents (§7.11.3.3 scale ratio +
640                        // §7.11.3.4/.5 clamps derive per-plane
641                        // internally); only the buffer + stride are in
642                        // this plane's own sample units.
643                        let (lw, lh) = s.plane_dims[0];
644                        RefFrameStoreEntry {
645                            plane: &s.planes[plane],
646                            stride: w as usize,
647                            upscaled_width: lw,
648                            width: lw,
649                            height: lh,
650                        }
651                    }
652                    _ => dummy_entry(),
653                });
654            plane_stores.push(arr);
655        }
656        for i in 0..7 {
657            let slot = ir.ref_frame_idx[i] as usize;
658            if slot >= NUM_REF_FRAMES as usize {
659                return Err(Error::PartitionWalkOutOfRange);
660            }
661            ref_frame_idx[i] = slot as u8;
662            // §5.9.2 `OrderHints[ LAST_FRAME + i ] =
663            // RefOrderHint[ ref_frame_idx[ i ] ]`.
664            let hint = st.info.order_hint[slot] as i32;
665            order_hints_by_ref[LAST_FRAME + i] = hint;
666            // §7.8 `RefFrameSignBias[ refFrame ] =
667            // get_relative_dist( hint, OrderHint ) > 0`.
668            if seq.enable_order_hint {
669                sign_bias[LAST_FRAME + i] = i32::from(
670                    get_relative_dist(hint, fh.order_hint as i32, u32::from(seq.order_hint_bits))
671                        > 0,
672                );
673            }
674            // §5.11.27 `is_scaled( refFrame )` — the literal spec body:
675            //
676            //   xScale = ( ( RefUpscaledWidth[ refIdx ] << REF_SCALE_SHIFT )
677            //              + ( FrameWidth / 2 ) ) / FrameWidth
678            //   yScale = ( ( RefFrameHeight[ refIdx ] << REF_SCALE_SHIFT )
679            //              + ( FrameHeight / 2 ) ) / FrameHeight
680            //   return xScale != noScale || yScale != noScale
681            //
682            // The divisor is the CODED `FrameWidth`/`FrameHeight`, NOT
683            // `UpscaledWidth` (r408 fix): a superres frame codes at
684            // `FrameWidth < UpscaledWidth` while its references store
685            // `RefUpscaledWidth == UpscaledWidth`, so every reference
686            // IS scaled even though the upscaled extents match. The
687            // pre-r408 upscaled-vs-upscaled shortcut read the §5.11.27
688            // `motion_mode` symbol where the encoder wrote `use_obmc`,
689            // desynchronising the arithmetic decoder on the first
690            // superres inter frame.
691            let no_scale = 1u64 << crate::inter_pred::REF_SCALE_SHIFT;
692            let fw = u64::from(fs.frame_width.max(1));
693            let fhh = u64::from(fs.frame_height.max(1));
694            let x_scale = ((u64::from(st.info.upscaled_width[slot])
695                << crate::inter_pred::REF_SCALE_SHIFT)
696                + fw / 2)
697                / fw;
698            let y_scale = ((u64::from(st.info.frame_height[slot])
699                << crate::inter_pred::REF_SCALE_SHIFT)
700                + fhh / 2)
701                / fhh;
702            is_scaled_per_ref[i] = x_scale != no_scale || y_scale != no_scale;
703        }
704    }
705    let order_hints = FrameInterOrderHints {
706        order_hint_bits: if seq.enable_order_hint {
707            u32::from(seq.order_hint_bits)
708        } else {
709            0
710        },
711        current_order_hint: fh.order_hint as i32,
712        order_hints_by_ref,
713    };
714    let plane_ref_specs: Vec<PlaneRefSpec<'_>> = (0..plane_stores.len())
715        .map(|p| PlaneRefSpec {
716            plane: p as u8,
717            subsampling_x: if p > 0 { sub_x_u8 } else { 0 },
718            subsampling_y: if p > 0 { sub_y_u8 } else { 0 },
719            frame_store: &plane_stores[p],
720            // r405: LUMA extents for every plane — the §7.11.3.3
721            // scale ratio is luma-derived (an odd `FrameWidth` such
722            // as a 213-wide resized frame has no faithful per-plane
723            // representation).
724            frame_width: fs.frame_width,
725            frame_height: fs.frame_height,
726        })
727        .collect();
728    let pixels = InterWalkPixels {
729        ref_frame_idx,
730        bit_depth: cc.bit_depth,
731        plane_refs: &plane_ref_specs,
732        order_hints,
733    };
734    let ictx: Option<InterFrameContext<'_>> = if is_inter_frame {
735        let ir = fh
736            .inter_refs
737            .as_ref()
738            .ok_or(Error::PartitionWalkOutOfRange)?;
739        let mut c = InterFrameContext::identity_default(&mfmvs);
740        // §5.9.14 segmentation state: the update flags, the
741        // FeatureEnabled/FeatureData tables (per-block
742        // `seg_feature_active` gates), and the §5.11.21
743        // `PrevSegmentIds` prediction source.
744        c.segmentation_update_map = sp.update_map;
745        c.segmentation_temporal_update = sp.temporal_update;
746        c.seg_feature_active = sp.segment_feature_active;
747        c.seg_feature_data = sp.segment_feature_data;
748        c.prev_segment_ids = prev_segment_ids.as_deref();
749        // §5.9.22 skip-mode state: the §5.11.10 `read_skip_mode` gate
750        // plus the fixed compound reference pair the skip-mode arm
751        // predicts from.
752        c.skip_mode_present = fh.skip_mode_present.unwrap_or(false);
753        if let Some(smf) = fh.skip_mode_frame {
754            c.skip_mode_frame = [i32::from(smf[0]), i32::from(smf[1])];
755        }
756        c.reference_select = fh.reference_select.unwrap_or(false);
757        if let Some(g) = fh.global_motion_params.as_ref() {
758            for r in 0..8 {
759                c.gm_type[r] = g.gm_type[r] as i32;
760                c.gm_params[r] = g.gm_params[r];
761            }
762        }
763        c.ref_frame_sign_bias = sign_bias;
764        c.allow_high_precision_mv = ir.allow_high_precision_mv;
765        c.force_integer_mv = fh.force_integer_mv;
766        c.use_ref_frame_mvs = ir.use_ref_frame_mvs;
767        c.is_motion_mode_switchable = ir.is_motion_mode_switchable;
768        c.allow_warped_motion = fh.allow_warped_motion.unwrap_or(false);
769        c.is_scaled_per_ref = is_scaled_per_ref;
770        c.enable_interintra_compound = seq.enable_interintra_compound;
771        c.enable_masked_compound = seq.enable_masked_compound;
772        c.enable_jnt_comp = seq.enable_jnt_comp;
773        // §8.3.2 compound-idx ctx + §7.11.3.15 DISTANCE weights both
774        // derive per block from the frame's OrderHint / OrderHints[].
775        c.order_hints = order_hints;
776        c.interpolation_filter = ir.interpolation_filter as u8;
777        c.enable_dual_filter = seq.enable_dual_filter;
778        c.pixels = Some(&pixels);
779        Some(c)
780    } else {
781        None
782    };
783
784    // §8.3.1 frame-start CDF state: `load_cdfs( ref_frame_idx[
785    // primary_ref_frame ] )` when a primary reference exists (the
786    // §7.20-saved frame-end state of that slot, coefficient CDFs
787    // included), otherwise `init_non_coeff_cdfs()` (the §9.4 defaults)
788    // + the q-context-selected `init_coeff_cdfs( base_q_idx )`.
789    let frame_start_cdfs: Box<TileCdfContext> = if fh.primary_ref_frame != PRIMARY_REF_NONE {
790        let st = refs.ok_or(Error::PartitionWalkOutOfRange)?;
791        let ir = fh
792            .inter_refs
793            .as_ref()
794            .ok_or(Error::PartitionWalkOutOfRange)?;
795        let slot = ir.ref_frame_idx[fh.primary_ref_frame as usize] as usize;
796        let slot_state = st
797            .slots
798            .get(slot)
799            .and_then(|s| s.as_ref())
800            .ok_or(Error::PartitionWalkOutOfRange)?;
801        let mut loaded = slot_state.cdfs.clone();
802        // §6.8.21 `load_cdfs`: the symbol counts restart at zero (the
803        // probabilities carry over) — the §8.3 adaptation rate depends
804        // on the per-row count.
805        loaded.zero_counts();
806        loaded
807    } else {
808        let mut c = TileCdfContext::new_from_defaults();
809        c.init_coeff_cdfs(qp.base_q_idx);
810        Box::new(c)
811    };
812
813    // §5.11.2 decode_tile() per tile, in tile order — each tile gets a
814    // fresh §8.2.2 symbol decoder and a fresh copy of the frame-start
815    // CDF state (§8.2.2 `clear_above_context` etc. are per-tile inside
816    // `begin_tile`), while the walker's frame-scope decode grids
817    // accumulate across tiles. The §5.11.57 `read_lr` interleave runs
818    // when the frame signals loop restoration. Per §8.2.4
819    // `exit_symbol` / §8.4 `frame_end_update_cdf`, the tile numbered
820    // `context_update_tile_id` donates its adapted CDFs as the
821    // frame-end state (unless `disable_frame_end_update_cdf`, which
822    // keeps the frame-start state).
823    let mut end_cdfs: Option<Box<TileCdfContext>> = None;
824    let t_tiles = Instant::now();
825    // r475 — tile-parallel walk. §5.11 scopes every context, every
826    // neighbour availability (`is_inside` is tile-relative), the
827    // intra edges, the delta-q / delta-lf / loop-restoration
828    // reference state and the entropy state to the tile, and tile
829    // boundaries are superblock aligned — so an intra tile decoded on
830    // a TILE-SIZED walker (the tile at the origin of its own frame)
831    // reconstructs the samples and stamps the grids the frame walk
832    // would, bit for bit; only §5.11.57 `read_lr` addresses a
833    // frame-level grid (`set_lr_origin`). The inter arms read the
834    // frame-level motion field / reference planes at frame positions
835    // and intra block copy reads the frame-level `CurrFrame`, so
836    // those frames keep the serial walk. Finished tiles are stitched
837    // into the frame walker as they arrive (`import_rect_from` +
838    // the planes + the restoration units), so at most `threads`
839    // tile-sized states are live beyond the frame's own.
840    let tile_parallel =
841        threads > 1 && tiles.len() > 1 && fh.frame_is_intra && !fh.allow_intrabc && !is_inter_frame;
842    if tile_parallel {
843        use std::sync::atomic::{AtomicUsize, Ordering};
844        let worker_count = threads.min(tiles.len());
845        let next = AtomicUsize::new(0);
846        let lr_walk_ref = if lr.uses_lr { Some(&lr_walk) } else { None };
847        let decode_one =
848            |tile_num: usize| -> Result<(PartitionWalker, Box<TileCdfContext>), Error> {
849                let tile = &tiles[tile_num];
850                let tile_row = (tile_num as u32) / ti.tile_cols;
851                let tile_col = (tile_num as u32) % ti.tile_cols;
852                let (r0, r1) = (
853                    ti.mi_row_starts[tile_row as usize],
854                    ti.mi_row_starts[tile_row as usize + 1],
855                );
856                let (c0, c1) = (
857                    ti.mi_col_starts[tile_col as usize],
858                    ti.mi_col_starts[tile_col as usize + 1],
859                );
860                let (h4, w4) = (r1 - r0, c1 - c0);
861                let mut local = PartitionWalker::new(
862                    h4,
863                    w4,
864                    TileGeometry {
865                        mi_row_start: 0,
866                        mi_row_end: h4,
867                        mi_col_start: 0,
868                        mi_col_end: w4,
869                    },
870                )
871                .ok_or(Error::PartitionWalkOutOfRange)?;
872                local.set_lr_origin(r0, c0);
873                local.begin_tile(TileGeometry {
874                    mi_row_start: 0,
875                    mi_row_end: h4,
876                    mi_col_start: 0,
877                    mi_col_end: w4,
878                });
879                local.set_current_q_index(i32::from(qp.base_q_idx));
880                let mut decoder = SymbolDecoder::init_symbol(
881                    &tile.bytes,
882                    tile.bytes.len(),
883                    fh.disable_cdf_update,
884                )?;
885                let mut cdfs = frame_start_cdfs.as_ref().clone();
886                local.decode_tile_syntax_with_lr(
887                    &mut decoder,
888                    &mut cdfs,
889                    &params,
890                    lr_walk_ref,
891                    None,
892                    &quant,
893                    dq.delta_q_present,
894                )?;
895                Ok((local, Box::new(cdfs)))
896            };
897        let mut first_err: Option<Error> = None;
898        std::thread::scope(|scope| {
899            let (tx, rx) = std::sync::mpsc::channel();
900            for _ in 0..worker_count {
901                let tx = tx.clone();
902                let next = &next;
903                let decode_one = &decode_one;
904                scope.spawn(move || loop {
905                    let t = next.fetch_add(1, Ordering::SeqCst);
906                    if t >= tiles.len() {
907                        break;
908                    }
909                    let out = decode_one(t);
910                    if tx.send((t, out)).is_err() {
911                        break;
912                    }
913                });
914            }
915            drop(tx);
916            for (t, out) in rx {
917                match out {
918                    Ok((local, cdfs)) => {
919                        let tile_row = (t as u32) / ti.tile_cols;
920                        let tile_col = (t as u32) % ti.tile_cols;
921                        let (r0, r1) = (
922                            ti.mi_row_starts[tile_row as usize],
923                            ti.mi_row_starts[tile_row as usize + 1],
924                        );
925                        let (c0, c1) = (
926                            ti.mi_col_starts[tile_col as usize],
927                            ti.mi_col_starts[tile_col as usize + 1],
928                        );
929                        walker.import_rect_from(
930                            &local,
931                            0,
932                            0,
933                            r0,
934                            c0,
935                            r1 - r0,
936                            c1 - c0,
937                            sub_x_u8,
938                            sub_y_u8,
939                        );
940                        walker.import_curr_frame_rect_from(&local, r0, c0, sub_x_u8, sub_y_u8);
941                        if lr.uses_lr {
942                            walker.import_lr_units_from(&local, r0, r1, c0, c1, &lr_walk);
943                        }
944                        if !fh.disable_frame_end_update_cdf && t as u32 == ti.context_update_tile_id
945                        {
946                            end_cdfs = Some(cdfs);
947                        }
948                    }
949                    Err(e) => {
950                        if first_err.is_none() {
951                            first_err = Some(e);
952                        }
953                    }
954                }
955            }
956        });
957        if let Some(e) = first_err {
958            return Err(e);
959        }
960    }
961    for (tile_num, tile) in tiles.iter().enumerate() {
962        if tile_parallel {
963            break;
964        }
965        let tile_row = (tile_num as u32) / ti.tile_cols;
966        let tile_col = (tile_num as u32) % ti.tile_cols;
967        walker.begin_tile(TileGeometry {
968            mi_row_start: ti.mi_row_starts[tile_row as usize],
969            mi_row_end: ti.mi_row_starts[tile_row as usize + 1],
970            mi_col_start: ti.mi_col_starts[tile_col as usize],
971            mi_col_end: ti.mi_col_starts[tile_col as usize + 1],
972        });
973        // §5.11.1 per-tile prologue: `CurrentQIndex = base_q_idx` —
974        // the §5.11.12 running delta-q accumulator restarts from the
975        // frame quantiser at every tile (r405: it previously started
976        // from the walker-construction zero, so every
977        // `delta_q_present` stream dequantised against a garbage
978        // base).
979        walker.set_current_q_index(i32::from(qp.base_q_idx));
980        let mut decoder =
981            SymbolDecoder::init_symbol(&tile.bytes, tile.bytes.len(), fh.disable_cdf_update)?;
982        let mut cdfs = frame_start_cdfs.as_ref().clone();
983        walker.decode_tile_syntax_with_lr(
984            &mut decoder,
985            &mut cdfs,
986            &params,
987            if lr.uses_lr { Some(&lr_walk) } else { None },
988            /* inter_ctx = */ ictx.as_ref(),
989            &quant,
990            /* read_deltas = */ dq.delta_q_present,
991        )?;
992        if !fh.disable_frame_end_update_cdf && tile_num as u32 == ti.context_update_tile_id {
993            end_cdfs = Some(Box::new(cdfs));
994        }
995    }
996    let end_cdfs = end_cdfs.unwrap_or(frame_start_cdfs);
997    stages.tiles += t_tiles.elapsed();
998
999    // ---- Take `CurrFrame[ plane ]` at its FULL mi-grid extent. ----
1000    // The spec's CurrFrame covers the padded `MiRows x MiCols` grid
1001    // (frames whose dimensions are not mi-aligned carry decoded padding
1002    // columns/rows past the crop). The §7.4 in-loop passes read that
1003    // padding — the §7.14 wide-filter taps, the §7.15 mi-bounded
1004    // availability region, and especially the §7.16 upscaler's
1005    // `Clip3(0, miW * MI_SIZE - 1, ..)` source clamp — so the whole
1006    // chain runs at the padded extent and the §7.18.2 crop to the
1007    // output extents happens at the very end.
1008    let sub_x = u8::from(cc.subsampling_x) as u32;
1009    let sub_y = u8::from(cc.subsampling_y) as u32;
1010    let num_planes = cc.num_planes as usize;
1011    let mut plane_bufs: Vec<Vec<i32>> = Vec::with_capacity(num_planes);
1012    let mut plane_dims: Vec<(u32, u32)> = Vec::with_capacity(num_planes);
1013    for plane in 0..num_planes {
1014        // r453 — MOVE the plane out of the walker (the §7.14/§7.15/
1015        // §7.16/§7.17 in-loop passes below operate on these caller-
1016        // owned buffers; nothing in the walker reads `CurrFrame`
1017        // after the tile walk), saving a full-frame `i32` copy.
1018        let (rows, cols, src) = walker
1019            .take_curr_frame_plane(plane)
1020            .ok_or(Error::PartitionWalkOutOfRange)?;
1021        let (pw, ph) = if plane == 0 {
1022            (fs.frame_width, fs.frame_height)
1023        } else {
1024            (
1025                (fs.frame_width + sub_x) >> sub_x,
1026                (fs.frame_height + sub_y) >> sub_y,
1027            )
1028        };
1029        if rows < ph || cols < pw {
1030            return Err(Error::PartitionWalkOutOfRange);
1031        }
1032        plane_bufs.push(src);
1033        plane_dims.push((cols, rows));
1034    }
1035
1036    // ---- §7.4 in-loop passes: §7.14 deblock, then §7.15 CDEF. ----
1037    // §7.4 step 1: the loop filter is invoked ONLY when
1038    // `loop_filter_level[ 0 ] != 0 || loop_filter_level[ 1 ] != 0` —
1039    // with both luma levels zero the frame is NOT deblocked at all,
1040    // even though the §7.14.4 `loop_filter_delta_enabled` ref-delta
1041    // path could otherwise lift a per-edge strength above zero.
1042    let t_deblock = Instant::now();
1043    if let Some(lf) = fh.loop_filter_params.as_ref() {
1044        if (lf.loop_filter_level[0] != 0 || lf.loop_filter_level[1] != 0)
1045            && !coded_lossless
1046            && !fh.allow_intrabc
1047        {
1048            let mut bufs: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1049            for (buf, &(pw, ph)) in plane_bufs.iter_mut().zip(plane_dims.iter()) {
1050                bufs.push(PlaneBuffer {
1051                    rows: ph,
1052                    cols: pw,
1053                    samples: buf,
1054                });
1055            }
1056            if threads > 1 {
1057                walker.loop_filter_frame_parallel_from_grid(
1058                    lf,
1059                    sp,
1060                    dlf.delta_lf_multi,
1061                    cc.num_planes,
1062                    cc.bit_depth,
1063                    sub_x as u8,
1064                    sub_y as u8,
1065                    fs.frame_width,
1066                    fs.frame_height,
1067                    &mut bufs,
1068                    threads,
1069                );
1070            } else {
1071                walker.loop_filter_frame_from_grid(
1072                    lf,
1073                    sp,
1074                    dlf.delta_lf_multi,
1075                    cc.num_planes,
1076                    cc.bit_depth,
1077                    sub_x as u8,
1078                    sub_y as u8,
1079                    fs.frame_width,
1080                    fs.frame_height,
1081                    &mut bufs,
1082                );
1083            }
1084        }
1085    }
1086    // §7.4 steps 2-5: CDEF, then loop restoration. `plane_bufs` holds
1087    // the post-deblock CurrFrame here; §7.17 needs BOTH that frame (the
1088    // `UpscaledCurrFrame` — no superres on this path) and the CDEF
1089    // output (`UpscaledCdefFrame`), so keep the pre-CDEF copy around
1090    // when restoration is active.
1091    stages.deblock += t_deblock.elapsed();
1092    let t_cdef = Instant::now();
1093    let cdef_active = fh.cdef_params.as_ref().is_some_and(|cdef| {
1094        !coded_lossless && !fh.allow_intrabc && seq.enable_cdef && !cdef.short_circuited
1095    });
1096    let superres_active = fs.use_superres && fs.upscaled_width > fs.frame_width;
1097    if !superres_active {
1098        // r475 — the band / stripe path: §7.15 CDEF runs in place over
1099        // a rolling window of the unfiltered rows its taps read (the
1100        // block ± 2, §7.15.3 `cdef_get_at` bounded by §5.11.52), and
1101        // §7.17 restores one stripe at a time from a copy of the
1102        // stripe's CDEF rows plus the pre-CDEF rows §7.17.6 reads at
1103        // the stripe's 2-row skirts, captured here from the deblocked
1104        // frame. No frame-sized copy of `CurrFrame` / `CdefFrame` is
1105        // made (the pre-r475 driver held three); every sample is
1106        // computed from the same inputs, so the output is identical.
1107        // The §7.16 superres arm below keeps the whole-frame path (it
1108        // upscales both frames).
1109        let boundary: Vec<crate::loop_restoration::StripeBoundaryRows> = if lr.uses_lr {
1110            plane_bufs
1111                .iter_mut()
1112                .zip(plane_dims.iter())
1113                .enumerate()
1114                .map(|(plane, (buf, &(pw, ph)))| {
1115                    let sy = if plane == 0 { 0 } else { sub_y };
1116                    let plane_h = (fs.frame_height + sy) >> sy;
1117                    let pb = PlaneBuffer {
1118                        rows: ph,
1119                        cols: pw,
1120                        samples: buf,
1121                    };
1122                    crate::loop_restoration::StripeBoundaryRows::capture(
1123                        &pb,
1124                        plane_h as i32 - 1,
1125                        sy as u8,
1126                    )
1127                })
1128                .collect()
1129        } else {
1130            Vec::new()
1131        };
1132        let mut bufs: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1133        for (buf, &(pw, ph)) in plane_bufs.iter_mut().zip(plane_dims.iter()) {
1134            bufs.push(PlaneBuffer {
1135                rows: ph,
1136                cols: pw,
1137                samples: buf,
1138            });
1139        }
1140        if let (true, Some(cdef)) = (cdef_active, fh.cdef_params.as_ref()) {
1141            if threads > 1 {
1142                walker.cdef_frame_parallel_from_idx(
1143                    cdef,
1144                    cc.num_planes,
1145                    cc.bit_depth,
1146                    sub_x as u8,
1147                    sub_y as u8,
1148                    &mut bufs,
1149                    threads,
1150                );
1151            } else {
1152                walker.cdef_frame_in_place_from_idx(
1153                    cdef,
1154                    cc.num_planes,
1155                    cc.bit_depth,
1156                    sub_x as u8,
1157                    sub_y as u8,
1158                    &mut bufs,
1159                );
1160            }
1161        }
1162        stages.cdef += t_cdef.elapsed();
1163        let t_lr = Instant::now();
1164        if lr.uses_lr {
1165            if threads > 1 {
1166                walker.loop_restore_frame_parallel_from_grid(
1167                    lr,
1168                    cc.num_planes,
1169                    cc.bit_depth,
1170                    sub_x as u8,
1171                    sub_y as u8,
1172                    mi_rows,
1173                    mi_cols,
1174                    fs.frame_height,
1175                    fs.upscaled_width,
1176                    &mut bufs,
1177                    &boundary,
1178                    threads,
1179                );
1180            } else {
1181                walker.loop_restore_frame_in_place_from_grid(
1182                    lr,
1183                    cc.num_planes,
1184                    cc.bit_depth,
1185                    sub_x as u8,
1186                    sub_y as u8,
1187                    mi_rows,
1188                    mi_cols,
1189                    fs.frame_height,
1190                    fs.upscaled_width,
1191                    &mut bufs,
1192                    &boundary,
1193                );
1194            }
1195        }
1196        stages.restoration += t_lr.elapsed();
1197    } else {
1198        let mut deblocked: Option<Vec<Vec<i32>>> = if lr.uses_lr {
1199            Some(plane_bufs.clone())
1200        } else {
1201            None
1202        };
1203        if let Some(cdef) = fh.cdef_params.as_ref() {
1204            if !coded_lossless && !fh.allow_intrabc && seq.enable_cdef && !cdef.short_circuited {
1205                // §7.15 filters from the deblocked frame. r453 — when
1206                // §7.17 is active the `deblocked` copy above IS that
1207                // frame (CDEF only reads its source), so borrow it
1208                // instead of cloning a third full-frame buffer.
1209                let mut fallback: Option<Vec<Vec<i32>>> = None;
1210                let src_owned: &mut Vec<Vec<i32>> = match deblocked {
1211                    Some(ref mut d) => d,
1212                    None => fallback.insert(plane_bufs.clone()),
1213                };
1214                let mut src: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1215                for (buf, &(pw, ph)) in src_owned.iter_mut().zip(plane_dims.iter()) {
1216                    src.push(PlaneBuffer {
1217                        rows: ph,
1218                        cols: pw,
1219                        samples: buf,
1220                    });
1221                }
1222                let mut dst: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1223                for (buf, &(pw, ph)) in plane_bufs.iter_mut().zip(plane_dims.iter()) {
1224                    dst.push(PlaneBuffer {
1225                        rows: ph,
1226                        cols: pw,
1227                        samples: buf,
1228                    });
1229                }
1230                walker.cdef_frame_from_idx(
1231                    cdef,
1232                    cc.num_planes,
1233                    cc.bit_depth,
1234                    sub_x as u8,
1235                    sub_y as u8,
1236                    &src,
1237                    &mut dst,
1238                );
1239            }
1240        }
1241        stages.cdef += t_cdef.elapsed();
1242        // §7.4 steps 3-4 / §7.16: horizontal superres upscaling of BOTH the
1243        // CDEF output (`plane_bufs` → UpscaledCdefFrame) and the post-
1244        // deblock copy (`deblocked` → UpscaledCurrFrame), ahead of loop
1245        // restoration. No-op when `use_superres == 0`.
1246        let t_superres = Instant::now();
1247        if fs.use_superres && fs.upscaled_width > fs.frame_width {
1248            let sr_ctx = crate::superres::SuperresFrameContext {
1249                use_superres: true,
1250                frame_width: fs.frame_width,
1251                upscaled_width: fs.upscaled_width,
1252                frame_height: fs.frame_height,
1253                mi_cols: fs.mi_cols,
1254                num_planes: cc.num_planes,
1255                bit_depth: cc.bit_depth,
1256                subsampling_x: u8::from(cc.subsampling_x),
1257                subsampling_y: u8::from(cc.subsampling_y),
1258            };
1259            // §7.16 output extents: `upscaledPlaneW x planeH` — exact
1260            // (un-padded) per-plane dimensions.
1261            let mut new_dims: Vec<(u32, u32)> = Vec::with_capacity(num_planes);
1262            for plane in 0..num_planes {
1263                let (out_w, out_h) = if plane == 0 {
1264                    (fs.upscaled_width, fs.frame_height)
1265                } else {
1266                    (
1267                        (fs.upscaled_width + sub_x) >> sub_x,
1268                        (fs.frame_height + sub_y) >> sub_y,
1269                    )
1270                };
1271                new_dims.push((out_w, out_h));
1272            }
1273            let upscale_set = |bufs: &mut Vec<Vec<i32>>| -> Result<(), Error> {
1274                let mut inputs_owned = std::mem::take(bufs);
1275                let mut inputs: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1276                for (buf, &(pw, ph)) in inputs_owned.iter_mut().zip(plane_dims.iter()) {
1277                    inputs.push(PlaneBuffer {
1278                        rows: ph,
1279                        cols: pw,
1280                        samples: buf,
1281                    });
1282                }
1283                let mut outputs_owned: Vec<Vec<i32>> = new_dims
1284                    .iter()
1285                    .map(|&(pw, ph)| vec![0i32; (pw as usize) * (ph as usize)])
1286                    .collect();
1287                {
1288                    let mut outputs: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1289                    for (buf, &(pw, ph)) in outputs_owned.iter_mut().zip(new_dims.iter()) {
1290                        outputs.push(PlaneBuffer {
1291                            rows: ph,
1292                            cols: pw,
1293                            samples: buf,
1294                        });
1295                    }
1296                    crate::superres::upscale_frame(&sr_ctx, &inputs, &mut outputs)
1297                        .map_err(|_| Error::PartitionWalkOutOfRange)?;
1298                }
1299                *bufs = outputs_owned;
1300                Ok(())
1301            };
1302            upscale_set(&mut plane_bufs)?;
1303            if let Some(d) = deblocked.as_mut() {
1304                upscale_set(d)?;
1305            }
1306            plane_dims = new_dims;
1307        }
1308
1309        stages.superres += t_superres.elapsed();
1310        // §7.4 step 5 / §7.17: loop restoration from (UpscaledCurrFrame,
1311        // UpscaledCdefFrame) into LrFrame. The §5.11.57 units were decoded
1312        // by the tile walk's `read_lr` interleave above.
1313        let t_lr = Instant::now();
1314        if let Some(mut curr_owned) = deblocked {
1315            let mut cdef_owned: Vec<Vec<i32>> = plane_bufs.clone();
1316            let mut curr: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1317            for (buf, &(pw, ph)) in curr_owned.iter_mut().zip(plane_dims.iter()) {
1318                curr.push(PlaneBuffer {
1319                    rows: ph,
1320                    cols: pw,
1321                    samples: buf,
1322                });
1323            }
1324            let mut cdef_bufs: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1325            for (buf, &(pw, ph)) in cdef_owned.iter_mut().zip(plane_dims.iter()) {
1326                cdef_bufs.push(PlaneBuffer {
1327                    rows: ph,
1328                    cols: pw,
1329                    samples: buf,
1330                });
1331            }
1332            let mut lr_out: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1333            for (buf, &(pw, ph)) in plane_bufs.iter_mut().zip(plane_dims.iter()) {
1334                lr_out.push(PlaneBuffer {
1335                    rows: ph,
1336                    cols: pw,
1337                    samples: buf,
1338                });
1339            }
1340            walker.loop_restore_frame_from_grid(
1341                lr,
1342                cc.num_planes,
1343                cc.bit_depth,
1344                sub_x as u8,
1345                sub_y as u8,
1346                mi_rows,
1347                mi_cols,
1348                fs.frame_height,
1349                fs.upscaled_width,
1350                &curr,
1351                &cdef_bufs,
1352                &mut lr_out,
1353            );
1354        }
1355        stages.restoration += t_lr.elapsed();
1356    }
1357    // ---- §7.18.2 intermediate-output crop to the surfaced extents. ----
1358    // On the superres path the buffers are already exact; otherwise
1359    // trim the mi-grid padding down to `UpscaledWidth x FrameHeight`
1360    // (per-plane subsampled). §7.18.3 film grain then applies to the
1361    // cropped output planes.
1362    let t_ref = Instant::now();
1363    for plane in 0..num_planes {
1364        let (out_w, out_h) = if plane == 0 {
1365            (fs.upscaled_width, fs.frame_height)
1366        } else {
1367            (
1368                (fs.upscaled_width + sub_x) >> sub_x,
1369                (fs.frame_height + sub_y) >> sub_y,
1370            )
1371        };
1372        let (pw, ph) = plane_dims[plane];
1373        if (pw, ph) == (out_w, out_h) {
1374            continue;
1375        }
1376        if pw < out_w || ph < out_h {
1377            return Err(Error::PartitionWalkOutOfRange);
1378        }
1379        let src = &plane_bufs[plane];
1380        let mut buf = vec![0i32; (out_w as usize) * (out_h as usize)];
1381        for y in 0..out_h as usize {
1382            let row = &src[y * pw as usize..y * pw as usize + out_w as usize];
1383            buf[y * out_w as usize..(y + 1) * out_w as usize].copy_from_slice(row);
1384        }
1385        plane_bufs[plane] = buf;
1386        plane_dims[plane] = (out_w, out_h);
1387    }
1388
1389    // ---- §7.19 motion field motion vector storage. ----
1390    // Filter the §5.11.5 `Mvs[]` / `RefFrames[]` grids down to the
1391    // `MfMvs[]` / `MfRefFrames[]` payload §7.20 stores: per cell, the
1392    // LAST candidate list whose reference lies in the past
1393    // (`get_relative_dist( RefOrderHint[ refIdx ], OrderHint ) < 0`)
1394    // and whose MV components sit within `REFMVS_LIMIT`.
1395    const REFMVS_LIMIT: i16 = (1 << 12) - 1;
1396    let cells = (mi_rows as usize) * (mi_cols as usize);
1397    let mut mf_ref_frames: Vec<i8> = vec![-1; cells];
1398    let mut mf_mvs: Vec<i16> = vec![0; cells * 2];
1399    if is_inter_frame {
1400        let st = refs.ok_or(Error::PartitionWalkOutOfRange)?;
1401        let raw_refs = walker.ref_frames();
1402        let raw_mvs = walker.mvs();
1403        let hint_bits = if seq.enable_order_hint {
1404            u32::from(seq.order_hint_bits)
1405        } else {
1406            0
1407        };
1408        for cell in 0..cells {
1409            for list in 0..2usize {
1410                let r = raw_refs[cell * 2 + list];
1411                if r > 0 {
1412                    let slot = ref_frame_idx[(r - 1) as usize] as usize;
1413                    let dist = get_relative_dist(
1414                        st.info.order_hint[slot] as i32,
1415                        fh.order_hint as i32,
1416                        hint_bits,
1417                    );
1418                    if dist < 0 {
1419                        let mv_row = raw_mvs[(cell * 2 + list) * 2];
1420                        let mv_col = raw_mvs[(cell * 2 + list) * 2 + 1];
1421                        if mv_row.abs() <= REFMVS_LIMIT && mv_col.abs() <= REFMVS_LIMIT {
1422                            mf_ref_frames[cell] = r;
1423                            mf_mvs[cell * 2] = mv_row;
1424                            mf_mvs[cell * 2 + 1] = mv_col;
1425                        }
1426                    }
1427                }
1428            }
1429        }
1430    }
1431
1432    // §7.20 `SavedSegmentIds` payload — the walker's stamped
1433    // `SegmentIds[][]` grid; unwritten `-1` cells clamp to `0` (the
1434    // spec map is always written: `segment_id = 0` when segmentation
1435    // is disabled).
1436    let segment_ids: Vec<i32> = walker.segment_ids().iter().map(|&v| v.max(0)).collect();
1437    // r475 — nothing below reads the walker: release its per-mi grids
1438    // (tens of MB at 12 MP) before the output buffers are built.
1439    drop(walker);
1440
1441    // ---- §7.20 store payload: pre-grain cropped planes (u16). ----
1442    // §7.18.3 film grain applies to the OUTPUT copy only; the §7.20
1443    // reference store keeps the grain-free frame (the samples later
1444    // frames motion-compensate against). r475 — under
1445    // `reduced_still_picture_header` the coded video sequence holds
1446    // exactly this one frame (§5.5.1: it implies `still_picture`), no
1447    // later frame can reference the store, and `show_existing_frame`
1448    // is forced off — so the planes are not materialised (36 MiB at
1449    // 12 MP 4:2:0); the empty store reads as the never-referenced
1450    // placeholder if a non-conformant stream tries.
1451    let store_planes = !seq.reduced_still_picture_header;
1452    let ref_plane_dims: Vec<(u32, u32)> = plane_dims.clone();
1453    let to_u16 = |buf: &[i32]| -> Vec<u16> { buf.iter().map(|&v| v.max(0) as u16).collect() };
1454    let max_val: i32 = (1 << cc.bit_depth) - 1;
1455    let narrow = |buf: Vec<i32>| -> Vec<u8> {
1456        if cc.bit_depth == 8 {
1457            buf.into_iter().map(|v| v.clamp(0, 255) as u8).collect()
1458        } else {
1459            let mut out = Vec::with_capacity(buf.len() * 2);
1460            for v in buf {
1461                let v = v.clamp(0, max_val) as u16;
1462                out.extend_from_slice(&v.to_le_bytes());
1463            }
1464            out
1465        }
1466    };
1467
1468    // ---- §7.18.3 film grain. ----
1469    // §5.9.30 `update_grain == 0` predicted path: `load_grain_params(
1470    // film_grain_params_ref_idx )` — every field except `grain_seed`
1471    // comes from the referenced slot's §7.20-saved state (the
1472    // `tempGrainSeed` dance keeps the newly-read seed). A slot with
1473    // no saved grain state resolves to `reset_grain_params()`.
1474    let effective_grain: Option<crate::uncompressed_header_tail::FilmGrainParams> =
1475        match fh.film_grain_params.as_ref() {
1476            Some(fg) if fg.apply_grain && fg.predicted => {
1477                let slot = fg.film_grain_params_ref_idx as usize;
1478                refs.and_then(|st| st.slots.get(slot))
1479                    .and_then(|s| s.as_ref())
1480                    .and_then(|s| s.grain_params.clone())
1481                    .map(|mut g| {
1482                        g.grain_seed = fg.grain_seed;
1483                        g.apply_grain = true;
1484                        g
1485                    })
1486            }
1487            Some(fg) if fg.apply_grain => Some(fg.clone()),
1488            _ => None,
1489        };
1490    let (ref_planes, planes): (Vec<Vec<u16>>, Vec<Vec<u8>>) = match effective_grain.as_ref() {
1491        Some(fg) if fg.apply_grain => {
1492            // Grain needs every plane at once (chroma scaling reads
1493            // luma): store first, synthesise, then narrow.
1494            let ref_planes: Vec<Vec<u16>> = if store_planes {
1495                plane_bufs.iter().map(|b| to_u16(b)).collect()
1496            } else {
1497                Vec::new()
1498            };
1499            stages.ref_store += t_ref.elapsed();
1500            let t_grain = Instant::now();
1501            let mut bufs: Vec<PlaneBuffer<'_>> = Vec::with_capacity(num_planes);
1502            for (buf, &(pw, ph)) in plane_bufs.iter_mut().zip(plane_dims.iter()) {
1503                bufs.push(PlaneBuffer {
1504                    rows: ph,
1505                    cols: pw,
1506                    samples: buf,
1507                });
1508            }
1509            film_grain_synthesis(
1510                fg,
1511                cc.bit_depth,
1512                cc.num_planes,
1513                u8::from(cc.subsampling_x),
1514                u8::from(cc.subsampling_y),
1515                cc.matrix_coefficients,
1516                &mut bufs,
1517            );
1518            stages.grain += t_grain.elapsed();
1519            let t_out = Instant::now();
1520            let planes: Vec<Vec<u8>> = plane_bufs.into_iter().map(narrow).collect();
1521            stages.output += t_out.elapsed();
1522            (ref_planes, planes)
1523        }
1524        _ => {
1525            // r475 — no grain: one plane at a time, the `i32` plane
1526            // released as soon as its `u16` store copy and output bytes
1527            // exist (the peak holds one plane three ways, not all of
1528            // them).
1529            let t_out = Instant::now();
1530            let mut ref_planes: Vec<Vec<u16>> = Vec::with_capacity(num_planes);
1531            let mut planes: Vec<Vec<u8>> = Vec::with_capacity(num_planes);
1532            for buf in plane_bufs {
1533                if store_planes {
1534                    ref_planes.push(to_u16(&buf));
1535                }
1536                planes.push(narrow(buf));
1537            }
1538            stages.ref_store += t_ref.elapsed();
1539            stages.output += t_out.elapsed();
1540            (ref_planes, planes)
1541        }
1542    };
1543
1544    Ok(DecodedFrameInternal {
1545        frame: SpecFrame {
1546            // Post-§7.16 the surfaced luma width is `UpscaledWidth`
1547            // (`== FrameWidth` when superres is off).
1548            width: fs.upscaled_width,
1549            height: fs.frame_height,
1550            planes,
1551            plane_dims,
1552            bit_depth: cc.bit_depth,
1553        },
1554        ref_planes,
1555        ref_plane_dims,
1556        mf_mvs,
1557        mf_ref_frames,
1558        order_hints_by_ref,
1559        end_cdfs,
1560        mi_rows,
1561        mi_cols,
1562        segment_ids,
1563        grain_params: effective_grain,
1564    })
1565}
1566
1567/// §7.9 `motion_field_estimation()` — adapter over the shared
1568/// [`crate::inter_pred::motion_field_estimation_core`] (the encoder's
1569/// write mirror runs the SAME core, so both sides project identical
1570/// `MotionFieldMvs`): builds one [`MotionFieldSlot`] view per stored
1571/// §7.20 slot plus the current frame's §5.9.2 scalars.
1572fn motion_field_estimation(
1573    refs: &SpecRefState,
1574    fh: &FrameHeader,
1575    seq: &SequenceHeader,
1576    mi_rows: u32,
1577    mi_cols: u32,
1578) -> Result<MotionFieldMvs, Error> {
1579    use crate::inter_pred::{motion_field_estimation_core, MotionFieldSlot};
1580    let ir = fh
1581        .inter_refs
1582        .as_ref()
1583        .ok_or(Error::PartitionWalkOutOfRange)?;
1584    let hint_bits = if seq.enable_order_hint {
1585        u32::from(seq.order_hint_bits)
1586    } else {
1587        0
1588    };
1589    let order_hint = fh.order_hint as i32;
1590    // §5.9.2 `OrderHints[ LAST_FRAME + i ]`.
1591    let mut order_hints = [0i32; ALTREF_FRAME + 1];
1592    for i in 0..7 {
1593        order_hints[LAST_FRAME + i] = refs.info.order_hint[ir.ref_frame_idx[i] as usize] as i32;
1594    }
1595    let mut slots: [Option<MotionFieldSlot<'_>>; 8] = [None; 8];
1596    for (i, slot) in refs.slots.iter().enumerate() {
1597        slots[i] = slot.as_ref().map(|st| MotionFieldSlot {
1598            mf_mvs: &st.mf_mvs,
1599            mf_ref_frames: &st.mf_ref_frames,
1600            saved_order_hints: st.saved_order_hints,
1601            mi_rows: st.mi_rows,
1602            mi_cols: st.mi_cols,
1603            frame_is_intra: st.frame_is_intra,
1604        });
1605    }
1606    Ok(motion_field_estimation_core(
1607        &slots,
1608        &ir.ref_frame_idx,
1609        &order_hints,
1610        order_hint,
1611        hint_bits,
1612        mi_rows,
1613        mi_cols,
1614    ))
1615}
1616
1617/// Decode an AV1 IVF v0 buffer through the spec-faithful frame driver.
1618///
1619/// Walks the IVF frame records, and within each temporal unit walks the
1620/// §5.2 OBU sequence: `OBU_TEMPORAL_DELIMITER` / `OBU_SEQUENCE_HEADER`
1621/// / `OBU_FRAME_HEADER` + `OBU_TILE_GROUP` / the combined `OBU_FRAME`
1622/// (§5.10: frame header + `byte_alignment()` + tile group in one OBU,
1623/// split via [`FrameHeader::bits_consumed`]). Padding and metadata OBUs
1624/// are skipped.
1625pub fn decode_av1_spec(input: &[u8]) -> Result<Vec<SpecFrame>, Error> {
1626    SpecDecodeSession::new().decode_ivf(input)
1627}
1628
1629/// r452 — default picture-size ceiling (luma samples,
1630/// `UpscaledWidth * FrameHeight`) a session accepts before reserving
1631/// frame-sized storage.
1632///
1633/// Annex A §A.3 bounds every DEFINED level's picture by `MaxPicSize`;
1634/// the largest defined value (levels 6.0–6.3) is 35 651 584 samples
1635/// (16384 × 2176, or 8192 × 4352). For `seq_level_idx = 31`
1636/// ("maximum parameters") the spec's note leaves the bound to the
1637/// decoder maker, and reserved level indices carry no table row at
1638/// all — this crate applies the largest defined `MaxPicSize` uniformly
1639/// as its documented ceiling rather than trusting the header's level
1640/// field (encoders routinely under-declare it). A header past the
1641/// ceiling surfaces [`Error::PictureSizeExceedsLimit`] before any
1642/// `MiRows * MiCols` grid or plane buffer is allocated.
1643pub const MAX_PICTURE_SIZE: u32 = 35_651_584;
1644
1645/// The Annex A picture-size gate: `UpscaledWidth * FrameHeight` must
1646/// not exceed `cap`. Runs on the parsed header before any frame-sized
1647/// reservation.
1648fn check_picture_size(fh: &FrameHeader, cap: u32) -> Result<(), Error> {
1649    if let Some(fs) = fh.frame_size.as_ref() {
1650        let samples = u64::from(fs.upscaled_width) * u64::from(fs.frame_height);
1651        if samples > u64::from(cap) {
1652            return Err(Error::PictureSizeExceedsLimit);
1653        }
1654    }
1655    Ok(())
1656}
1657
1658/// r430 — [`decode_av1_spec`] at an externally selected operating
1659/// point (§6.7.5): the §5.3.1 `drop_obu()` rule skips every OBU
1660/// outside the selected point's temporal/spatial layer set, so a
1661/// temporally scalable stream decodes to exactly the shown frames of
1662/// the surviving layers. `operating_point = 0` (the list's preferred
1663/// entry) reproduces [`decode_av1_spec`] byte for byte.
1664///
1665/// ## Errors
1666///
1667/// * [`Error::OperatingPointOutOfRange`] — `operating_point` exceeds
1668///   the sequence header's `operating_points_cnt_minus_1`.
1669/// * Every [`decode_av1_spec`] error surface otherwise.
1670pub fn decode_av1_spec_at_operating_point(
1671    input: &[u8],
1672    operating_point: u8,
1673) -> Result<Vec<SpecFrame>, Error> {
1674    let mut session = SpecDecodeSession::new();
1675    session.set_operating_point(operating_point)?;
1676    session.decode_ivf(input)
1677}
1678
1679/// Cross-packet decode session — the §7.20 reference-frame store, the
1680/// cached sequence header, and the per-slot CDF / motion-field /
1681/// segment-id state, held across successive temporal units so a
1682/// container demuxer can feed one §7.5 temporal unit (or one IVF
1683/// record payload) per packet and decode whole GOPs.
1684///
1685/// [`decode_av1_spec`] is the one-shot IVF convenience wrapper over
1686/// this type; the `oxideav-core` registry decoder drives it directly.
1687#[derive(Debug)]
1688pub struct SpecDecodeSession {
1689    seq: Option<SequenceHeader>,
1690    refs: SpecRefState,
1691    /// r430 — the operating point selected by external means (the
1692    /// §6.7.5 `choose_operating_point()` return value). Defaults to
1693    /// `0` — the earliest (preferred) entry of the sequence header's
1694    /// operating-point list.
1695    chosen_op: u8,
1696    /// §5.5.1 `OperatingPointIdc = operating_point_idc[
1697    /// operatingPoint ]` — refreshed at every sequence-header parse.
1698    /// `0` = scalability not in use (the §5.3.1 `drop_obu()` arm
1699    /// never fires).
1700    op_idc: u16,
1701    /// r452 — the picture-size ceiling (luma samples) enforced on
1702    /// every parsed frame header before frame-sized storage is
1703    /// reserved. Defaults to [`MAX_PICTURE_SIZE`].
1704    max_picture_size: u32,
1705    /// r475 — per-stage wall time of the last `decode_temporal_unit`.
1706    stages: DecodeStageTimings,
1707    /// r475 — the worker budget (`1` = serial).
1708    threads: usize,
1709}
1710
1711impl Default for SpecDecodeSession {
1712    fn default() -> Self {
1713        Self::new()
1714    }
1715}
1716
1717impl SpecDecodeSession {
1718    /// Fresh session: no sequence header, every §7.20 slot empty.
1719    #[must_use]
1720    pub fn new() -> Self {
1721        Self {
1722            seq: None,
1723            refs: SpecRefState::new(),
1724            chosen_op: 0,
1725            op_idc: 0,
1726            max_picture_size: MAX_PICTURE_SIZE,
1727            stages: DecodeStageTimings::default(),
1728            threads: 1,
1729        }
1730    }
1731
1732    /// r475 — grant the session a worker budget: a multi-tile frame
1733    /// decodes its tiles `threads`-wide (the post-filters by row
1734    /// band), byte-identical to the serial decode for any budget.
1735    /// `0` / `1` = serial.
1736    pub fn set_threads(&mut self, threads: usize) {
1737        self.threads = threads.max(1);
1738    }
1739
1740    /// The worker budget granted through [`Self::set_threads`].
1741    #[must_use]
1742    pub fn threads(&self) -> usize {
1743        self.threads
1744    }
1745
1746    /// r452 — lower (or raise) the picture-size ceiling
1747    /// ([`MAX_PICTURE_SIZE`] by default) applied to every frame
1748    /// header this session parses: a header whose
1749    /// `UpscaledWidth * FrameHeight` exceeds `max_samples` is rejected
1750    /// with [`Error::PictureSizeExceedsLimit`] before any frame-sized
1751    /// buffer is reserved. Memory-constrained hosts (and the fuzz
1752    /// harness, which runs under a hard RSS limit) bound their
1753    /// worst-case reservation here.
1754    pub fn set_max_picture_size(&mut self, max_samples: u32) {
1755        self.max_picture_size = max_samples;
1756    }
1757
1758    /// The session's current picture-size ceiling (luma samples).
1759    #[must_use]
1760    pub fn max_picture_size(&self) -> u32 {
1761        self.max_picture_size
1762    }
1763
1764    /// Decode a whole IVF v0 buffer through this session — the
1765    /// one-shot [`decode_av1_spec`] walk (every IVF record is one §7.5
1766    /// temporal unit) over a caller-configured session, so the
1767    /// operating point and picture-size ceiling apply.
1768    ///
1769    /// ## Errors
1770    ///
1771    /// [`Error::UnexpectedEnd`] on a malformed IVF wrapper, plus every
1772    /// [`Self::decode_temporal_unit`] error surface.
1773    pub fn decode_ivf(&mut self, input: &[u8]) -> Result<Vec<SpecFrame>, Error> {
1774        let reader = IvfReader::new(input).map_err(|_| Error::UnexpectedEnd)?;
1775        let records = reader.read_all().map_err(|_| Error::UnexpectedEnd)?;
1776        let mut out = Vec::new();
1777        for record in records {
1778            out.extend(self.decode_temporal_unit(&record.payload)?);
1779        }
1780        Ok(out)
1781    }
1782
1783    /// r430 — select the operating point (§6.7.5) by external means.
1784    ///
1785    /// `operating_point` is an index into the sequence header's
1786    /// operating-point list (`0..=operating_points_cnt_minus_1`; `0`
1787    /// is the list's preferred entry and the session default). The
1788    /// §5.5.1 `OperatingPointIdc` derivation re-runs at every
1789    /// sequence-header parse, so the selection naturally applies to
1790    /// each new coded video sequence; when a sequence header has
1791    /// already been parsed the selection is re-derived immediately
1792    /// against it.
1793    ///
1794    /// With a non-zero `OperatingPointIdc`, every OBU that carries an
1795    /// extension header and lies outside the operating point's
1796    /// temporal/spatial layer set is dropped per the §5.3.1
1797    /// `drop_obu()` rule — decoding a temporally scalable stream at a
1798    /// reduced operating point yields exactly the shown frames of the
1799    /// selected layer subset.
1800    ///
1801    /// ## Errors
1802    ///
1803    /// [`Error::OperatingPointOutOfRange`] when a sequence header is
1804    /// cached and `operating_point` exceeds its
1805    /// `operating_points_cnt_minus_1` (the same reject surfaces from
1806    /// [`Self::decode_temporal_unit`] at the next sequence-header
1807    /// parse otherwise).
1808    pub fn set_operating_point(&mut self, operating_point: u8) -> Result<(), Error> {
1809        self.chosen_op = operating_point;
1810        if let Some(s) = self.seq.as_ref() {
1811            let idx = usize::from(operating_point);
1812            if idx >= s.operating_points.len() {
1813                return Err(Error::OperatingPointOutOfRange);
1814            }
1815            self.op_idc = s.operating_points[idx].operating_point_idc;
1816        }
1817        Ok(())
1818    }
1819
1820    /// The current §5.5.1 `OperatingPointIdc` (`0` until a sequence
1821    /// header with a non-zero `operating_point_idc` for the selected
1822    /// operating point is parsed).
1823    #[must_use]
1824    pub fn operating_point_idc(&self) -> u16 {
1825        self.op_idc
1826    }
1827
1828    /// Decode one §7.5 temporal-unit body (a low-overhead OBU
1829    /// bytestream: temporal delimiter / sequence header / frame
1830    /// header / tile group / frame OBUs), returning every SHOWN frame
1831    /// in output order and updating the session's reference state.
1832    ///
1833    /// ## Errors
1834    ///
1835    /// Every [`decode_av1_spec`] error surface — malformed OBUs, a
1836    /// frame arriving before any sequence header, reference slots the
1837    /// stream names but never filled, or feature shapes outside the
1838    /// decoder's scope.
1839    pub fn decode_temporal_unit(&mut self, payload: &[u8]) -> Result<Vec<SpecFrame>, Error> {
1840        let mut out = Vec::new();
1841        self.stages = DecodeStageTimings::default();
1842        decode_temporal_unit_spec(
1843            payload,
1844            &mut self.seq,
1845            &mut self.refs,
1846            &mut out,
1847            self.chosen_op,
1848            &mut self.op_idc,
1849            self.max_picture_size,
1850            &mut self.stages,
1851            self.threads,
1852        )?;
1853        Ok(out)
1854    }
1855
1856    /// r475 — wall time per decode stage of the last
1857    /// [`Self::decode_temporal_unit`] call (summed over the unit's
1858    /// frames; all-zero before the first call).
1859    #[doc(hidden)]
1860    #[must_use]
1861    pub fn last_stage_timings(&self) -> DecodeStageTimings {
1862        self.stages
1863    }
1864
1865    /// Drop the §7.20 reference store (a seek discontinuity) while
1866    /// keeping the cached sequence header — the next temporal unit
1867    /// must start a new coded video sequence (a KEY frame), which
1868    /// rebuilds every slot before anything can reference it.
1869    pub fn reset_references(&mut self) {
1870        self.refs = SpecRefState::new();
1871    }
1872}
1873
1874/// §7.20 `reference_frame_update()` — store the just-decoded frame
1875/// into every slot `refresh_frame_flags` selects, updating the
1876/// §5.9.2 `RefInfo` arrays in lockstep.
1877fn reference_frame_update(
1878    refs: &mut SpecRefState,
1879    fh: &FrameHeader,
1880    decoded: DecodedFrameInternal,
1881) -> Result<SpecFrame, Error> {
1882    if fh.refresh_frame_flags == 0 {
1883        return Ok(decoded.frame);
1884    }
1885    let fs = fh
1886        .frame_size
1887        .as_ref()
1888        .ok_or(Error::PartitionWalkOutOfRange)?;
1889    // §7.20 per-slot payload: pixels + §7.19 motion-field grids +
1890    // `SavedOrderHints` + `save_cdfs` + `SavedGmParams` +
1891    // `save_loop_filter_params`.
1892    let mut gm_params = crate::uncompressed_header_tail::prev_gm_params_default();
1893    if let Some(g) = fh.global_motion_params.as_ref() {
1894        gm_params = g.gm_params;
1895    }
1896    let (lf_ref_deltas, lf_mode_deltas) = fh
1897        .loop_filter_params
1898        .as_ref()
1899        .map(|lf| (lf.loop_filter_ref_deltas, lf.loop_filter_mode_deltas))
1900        .unwrap_or((
1901            crate::uncompressed_header_tail::LOOP_FILTER_REF_DELTAS_DEFAULT,
1902            [0i8; 2],
1903        ));
1904    // §7.20 `save_segmentation_params( i )` — the current frame's
1905    // FeatureEnabled / FeatureData (post-§5.9.14, including the
1906    // `segmentation_update_data == 0` loaded values).
1907    let (seg_active, seg_data) = fh
1908        .segmentation_params
1909        .as_ref()
1910        .map(|sp| (sp.segment_feature_active, sp.segment_feature_data))
1911        .unwrap_or((
1912            [[false; crate::uncompressed_header_tail::SEG_LVL_MAX]
1913                as [bool; crate::uncompressed_header_tail::SEG_LVL_MAX];
1914                crate::uncompressed_header_tail::MAX_SEGMENTS],
1915            [[0i16; crate::uncompressed_header_tail::SEG_LVL_MAX];
1916                crate::uncompressed_header_tail::MAX_SEGMENTS],
1917        ));
1918    // r453 — the payload MOVES out of the decode result (the surfaced
1919    // output frame is a separate buffer) and is Arc-shared across the
1920    // selected slots.
1921    let DecodedFrameInternal {
1922        frame,
1923        ref_planes,
1924        ref_plane_dims,
1925        mf_mvs,
1926        mf_ref_frames,
1927        order_hints_by_ref,
1928        end_cdfs,
1929        mi_rows,
1930        mi_cols,
1931        segment_ids,
1932        grain_params,
1933    } = decoded;
1934    let payload = Arc::new(SpecRefSlot {
1935        planes: ref_planes,
1936        plane_dims: ref_plane_dims,
1937        mf_mvs,
1938        mf_ref_frames,
1939        saved_order_hints: order_hints_by_ref,
1940        mi_rows,
1941        mi_cols,
1942        frame_is_intra: fh.frame_is_intra,
1943        frame_type_is_key: matches!(fh.frame_type, crate::frame_header::FrameType::Key),
1944        bit_depth: frame.bit_depth,
1945        cdfs: end_cdfs,
1946        gm_params,
1947        lf_ref_deltas,
1948        lf_mode_deltas,
1949        segment_ids,
1950        grain_params,
1951    });
1952    for i in 0..NUM_REF_FRAMES as usize {
1953        if (fh.refresh_frame_flags >> i) & 1 != 0 {
1954            refs.info.valid[i] = true;
1955            refs.info.order_hint[i] = fh.order_hint;
1956            refs.info.frame_id[i] = fh.current_frame_id;
1957            refs.info.upscaled_width[i] = fs.upscaled_width;
1958            refs.info.frame_height[i] = fs.frame_height;
1959            refs.info.render_width[i] = fs.render_width;
1960            refs.info.render_height[i] = fs.render_height;
1961            refs.info.frame_type_is_key[i] = payload.frame_type_is_key;
1962            refs.info.saved_gm_params[i] = payload.gm_params;
1963            refs.info.saved_lf_ref_deltas[i] = payload.lf_ref_deltas;
1964            refs.info.saved_lf_mode_deltas[i] = payload.lf_mode_deltas;
1965            refs.info.saved_seg_feature_active[i] = seg_active;
1966            refs.info.saved_seg_feature_data[i] = seg_data;
1967            refs.slots[i] = Some(Arc::clone(&payload));
1968        }
1969    }
1970    Ok(frame)
1971}
1972
1973/// §7.21-adjacent `show_existing_frame` output: surface the stored
1974/// slot's planes as a [`SpecFrame`], applying §7.18.3 film grain from
1975/// the slot's §7.20-saved grain state (§5.9.2 `show_existing_frame`
1976/// runs `load_grain_params( frame_to_show_map_idx )` when
1977/// `film_grain_params_present == 1`; the stored planes themselves stay
1978/// grain-free).
1979fn output_existing_frame(
1980    refs: &SpecRefState,
1981    fh: &FrameHeader,
1982    seq: &SequenceHeader,
1983) -> Result<SpecFrame, Error> {
1984    let idx = fh
1985        .frame_to_show_map_idx
1986        .ok_or(Error::PartitionWalkOutOfRange)? as usize;
1987    if idx >= NUM_REF_FRAMES as usize {
1988        return Err(Error::PartitionWalkOutOfRange);
1989    }
1990    let slot = refs.slots[idx]
1991        .as_ref()
1992        .ok_or(Error::PartitionWalkOutOfRange)?;
1993    // §7.18.3 on the OUTPUT copy only.
1994    let mut grain_bufs: Option<Vec<Vec<i32>>> = None;
1995    if seq.film_grain_params_present {
1996        if let Some(fg) = slot.grain_params.as_ref() {
1997            if fg.apply_grain {
1998                let mut bufs: Vec<Vec<i32>> = slot
1999                    .planes
2000                    .iter()
2001                    .map(|p| p.iter().map(|&v| v as i32).collect())
2002                    .collect();
2003                let cc = &seq.color_config;
2004                let mut views: Vec<PlaneBuffer<'_>> = Vec::with_capacity(bufs.len());
2005                for (buf, &(pw, ph)) in bufs.iter_mut().zip(slot.plane_dims.iter()) {
2006                    views.push(PlaneBuffer {
2007                        rows: ph,
2008                        cols: pw,
2009                        samples: buf,
2010                    });
2011                }
2012                film_grain_synthesis(
2013                    fg,
2014                    slot.bit_depth,
2015                    cc.num_planes,
2016                    u8::from(cc.subsampling_x),
2017                    u8::from(cc.subsampling_y),
2018                    cc.matrix_coefficients,
2019                    &mut views,
2020                );
2021                grain_bufs = Some(bufs);
2022            }
2023        }
2024    }
2025    let max_val: i32 = (1i32 << slot.bit_depth) - 1;
2026    let planes: Vec<Vec<u8>> = match grain_bufs {
2027        Some(bufs) => bufs
2028            .into_iter()
2029            .map(|p| {
2030                if slot.bit_depth == 8 {
2031                    p.into_iter().map(|v| v.clamp(0, 255) as u8).collect()
2032                } else {
2033                    let mut out = Vec::with_capacity(p.len() * 2);
2034                    for v in p {
2035                        out.extend_from_slice(&(v.clamp(0, max_val) as u16).to_le_bytes());
2036                    }
2037                    out
2038                }
2039            })
2040            .collect(),
2041        None => slot
2042            .planes
2043            .iter()
2044            .map(|p| {
2045                if slot.bit_depth == 8 {
2046                    p.iter().map(|&v| v.min(255) as u8).collect()
2047                } else {
2048                    let mut out = Vec::with_capacity(p.len() * 2);
2049                    for &v in p {
2050                        out.extend_from_slice(&v.to_le_bytes());
2051                    }
2052                    out
2053                }
2054            })
2055            .collect(),
2056    };
2057    let (w, h) = *slot
2058        .plane_dims
2059        .first()
2060        .ok_or(Error::PartitionWalkOutOfRange)?;
2061    Ok(SpecFrame {
2062        width: w,
2063        height: h,
2064        planes,
2065        plane_dims: slot.plane_dims.clone(),
2066        bit_depth: slot.bit_depth,
2067    })
2068}
2069
2070/// Decode one §7.5 temporal-unit body, appending every SHOWN frame to
2071/// `out` (`show_frame == 1` coded frames and `show_existing_frame`
2072/// outputs — the §7.4 output discipline) and updating the cached
2073/// sequence header + the §7.20 reference state.
2074#[allow(clippy::too_many_arguments)]
2075fn decode_temporal_unit_spec(
2076    payload: &[u8],
2077    seq: &mut Option<SequenceHeader>,
2078    refs: &mut SpecRefState,
2079    out: &mut Vec<SpecFrame>,
2080    chosen_op: u8,
2081    op_idc: &mut u16,
2082    max_picture_size: u32,
2083    stages: &mut DecodeStageTimings,
2084    threads: usize,
2085) -> Result<(), Error> {
2086    // §5.9.1 `SeenFrameHeader` state: `Some` between a frame's
2087    // `OBU_FRAME_HEADER` and the tile group whose `tg_end ==
2088    // NumTiles - 1` (which per §5.11.1 resets `SeenFrameHeader = 0`).
2089    // r433 — a frame's tiles may arrive across SEVERAL tile-group
2090    // OBUs; §6.10.1 requires each group's `tg_start` to equal the
2091    // running `TileNum`, so the accumulator tracks the next expected
2092    // index and concatenation reconstructs tile raster order.
2093    struct PendingFrame {
2094        fh: FrameHeader,
2095        /// The original `OBU_FRAME_HEADER` payload bytes — the §6.8.1
2096        /// `frame_header_copy` rule says every
2097        /// `OBU_REDUNDANT_FRAME_HEADER` for this frame must carry
2098        /// identical contents.
2099        fh_payload: Vec<u8>,
2100        /// Accumulated §8.2 per-tile payloads, tile raster order.
2101        tiles: Vec<TilePayload>,
2102        /// §6.10.1: the `TileNum` the next tile group must start at.
2103        next_tg_start: u32,
2104    }
2105    let mut pending: Option<PendingFrame> = None;
2106    for desc in ObuIter::new(payload) {
2107        let desc = desc?;
2108        // §5.3.1 `drop_obu()`: with a non-zero OperatingPointIdc,
2109        // every OBU other than a sequence header or temporal
2110        // delimiter that carries an extension header and lies outside
2111        // the selected operating point's layer set is dropped before
2112        // any payload parse:
2113        //
2114        //   inTemporalLayer = (OperatingPointIdc >> temporal_id) & 1
2115        //   inSpatialLayer  = (OperatingPointIdc >> (spatial_id + 8)) & 1
2116        if *op_idc != 0
2117            && desc.extension_flag
2118            && !matches!(
2119                desc.obu_type,
2120                ObuType::SequenceHeader | ObuType::TemporalDelimiter
2121            )
2122        {
2123            let in_temporal = (*op_idc >> desc.temporal_id) & 1 != 0;
2124            let in_spatial = (*op_idc >> (desc.spatial_id + 8)) & 1 != 0;
2125            if !in_temporal || !in_spatial {
2126                continue;
2127            }
2128        }
2129        match desc.obu_type {
2130            ObuType::TemporalDelimiter | ObuType::Padding | ObuType::Metadata => {}
2131            // §7.3.1: tile-list decoding is a separate operating mode
2132            // whose AnchorFrames array exists only by external means
2133            // ("a decoder is recommended to support decoding of tile
2134            // list OBUs, but this is not a requirement for decoder
2135            // conformance") — the general driver skips them; the
2136            // dedicated [`crate::tile_list::decode_tile_list`] entry
2137            // decodes them against a caller-supplied anchor set.
2138            ObuType::TileList => {}
2139            ObuType::SequenceHeader => {
2140                let sh = parse_sequence_header(desc.payload)?;
2141                // §5.5.1: `operatingPoint = choose_operating_point()`
2142                // (the session's externally-selected index, default
2143                // 0); `OperatingPointIdc = operating_point_idc[
2144                // operatingPoint ]`. §6.7.5: an index outside
2145                // `0..=operating_points_cnt_minus_1` abandons the
2146                // decoding process.
2147                let idx = usize::from(chosen_op);
2148                if idx >= sh.operating_points.len() {
2149                    return Err(Error::OperatingPointOutOfRange);
2150                }
2151                *op_idc = sh.operating_points[idx].operating_point_idc;
2152                *seq = Some(sh);
2153            }
2154            ObuType::FrameHeader => {
2155                // §6.8.1: `OBU_FRAME_HEADER` requires `SeenFrameHeader
2156                // == 0` — a header arriving after this frame's tile
2157                // groups started must use the REDUNDANT type. (A
2158                // pending header with NO tiles yet is tolerated as a
2159                // replacement, preserving the historical driver's
2160                // acceptance.)
2161                if pending.as_ref().is_some_and(|p| !p.tiles.is_empty()) {
2162                    return Err(Error::TileGroupInvalid);
2163                }
2164                let s = seq.as_ref().ok_or(Error::PartitionWalkOutOfRange)?;
2165                let t_parse = Instant::now();
2166                let fh = parse_frame_header_with_refs(desc.payload, s, &refs.info)?;
2167                stages.parse += t_parse.elapsed();
2168                check_picture_size(&fh, max_picture_size)?;
2169                if fh.show_existing_frame {
2170                    out.push(output_existing_frame(refs, &fh, s)?);
2171                    // §7.4 / §7.21: a shown KEY frame re-loads the
2172                    // stored frame state and re-stores it into every
2173                    // slot (`refresh_frame_flags == allFrames` per the
2174                    // §5.9.2 show_existing arm). The wholesale slot
2175                    // clone IS the §7.21 load followed by the §7.20
2176                    // store — pixels, §7.19 grids, `SavedOrderHints`,
2177                    // CDFs, gm params and loop-filter deltas all ride
2178                    // the payload.
2179                    if fh.refresh_frame_flags != 0 {
2180                        let idx = fh
2181                            .frame_to_show_map_idx
2182                            .ok_or(Error::PartitionWalkOutOfRange)?
2183                            as usize;
2184                        let payload = refs.slots[idx]
2185                            .as_ref()
2186                            .cloned()
2187                            .ok_or(Error::PartitionWalkOutOfRange)?;
2188                        let loaded_order_hint = refs.info.order_hint[idx];
2189                        let loaded_frame_id = refs.info.frame_id[idx];
2190                        let loaded_uw = refs.info.upscaled_width[idx];
2191                        let loaded_fh = refs.info.frame_height[idx];
2192                        let loaded_rw = refs.info.render_width[idx];
2193                        let loaded_rh = refs.info.render_height[idx];
2194                        for i in 0..NUM_REF_FRAMES as usize {
2195                            if (fh.refresh_frame_flags >> i) & 1 != 0 {
2196                                refs.info.valid[i] = true;
2197                                refs.info.order_hint[i] = loaded_order_hint;
2198                                refs.info.frame_id[i] = loaded_frame_id;
2199                                refs.info.upscaled_width[i] = loaded_uw;
2200                                refs.info.frame_height[i] = loaded_fh;
2201                                refs.info.render_width[i] = loaded_rw;
2202                                refs.info.render_height[i] = loaded_rh;
2203                                refs.info.frame_type_is_key[i] = payload.frame_type_is_key;
2204                                refs.info.saved_gm_params[i] = payload.gm_params;
2205                                refs.info.saved_lf_ref_deltas[i] = payload.lf_ref_deltas;
2206                                refs.info.saved_lf_mode_deltas[i] = payload.lf_mode_deltas;
2207                                // §7.21 load + §7.20 re-store: the
2208                                // segmentation params ride the RefInfo
2209                                // copy from the loaded slot.
2210                                refs.info.saved_seg_feature_active[i] =
2211                                    refs.info.saved_seg_feature_active[idx];
2212                                refs.info.saved_seg_feature_data[i] =
2213                                    refs.info.saved_seg_feature_data[idx];
2214                                refs.slots[i] = Some(payload.clone());
2215                            }
2216                        }
2217                    }
2218                    pending = None;
2219                } else {
2220                    pending = Some(PendingFrame {
2221                        fh,
2222                        fh_payload: desc.payload.to_vec(),
2223                        tiles: Vec::new(),
2224                        next_tg_start: 0,
2225                    });
2226                }
2227            }
2228            ObuType::RedundantFrameHeader => {
2229                // §5.9.1 `frame_header_copy()`: only legal while
2230                // `SeenFrameHeader == 1` (§6.8.1), and the copy must
2231                // carry contents identical to the original
2232                // `OBU_FRAME_HEADER`. Nothing re-parses — the byte
2233                // comparison IS the identity check.
2234                let p = pending.as_ref().ok_or(Error::TileGroupInvalid)?;
2235                if desc.payload != &p.fh_payload[..] {
2236                    return Err(Error::TileGroupInvalid);
2237                }
2238            }
2239            ObuType::TileGroup => {
2240                let s = seq.as_ref().ok_or(Error::PartitionWalkOutOfRange)?;
2241                let p = pending.as_mut().ok_or(Error::TileGroupInvalid)?;
2242                let ti =
2243                    p.fh.tile_info
2244                        .as_ref()
2245                        .ok_or(Error::PartitionWalkOutOfRange)?;
2246                let num_tiles = ti.tile_cols * ti.tile_rows;
2247                let t_parse = Instant::now();
2248                let parsed = parse_tile_group_body_with_ti(ti, desc.payload)?;
2249                stages.parse += t_parse.elapsed();
2250                // §6.10.1: `tg_start` equals the running `TileNum`.
2251                if parsed.tg_start != p.next_tg_start {
2252                    return Err(Error::TileGroupInvalid);
2253                }
2254                p.next_tg_start = parsed.tg_end + 1;
2255                p.tiles.extend(parsed.tiles);
2256                // §5.11.1: the frame decodes (frame_end_update_cdf +
2257                // decode_frame_wrapup, SeenFrameHeader = 0) only once
2258                // `tg_end == NumTiles - 1`.
2259                if parsed.tg_end + 1 == num_tiles {
2260                    let pf = pending.take().expect("pending frame checked above");
2261                    let decoded =
2262                        decode_frame_spec_tiles(s, &pf.fh, &pf.tiles, Some(refs), stages, threads)?;
2263                    let frame = reference_frame_update(refs, &pf.fh, decoded)?;
2264                    if pf.fh.show_frame {
2265                        out.push(frame);
2266                    }
2267                }
2268            }
2269            ObuType::Frame => {
2270                // §5.10 frame_obu: frame_header_obu() + byte_alignment()
2271                // + tile_group_obu(). The tile group starts at the next
2272                // byte boundary after the frame header, and §6.10.1
2273                // requires `tile_start_and_end_present_flag == 0` — the
2274                // embedded group always covers the whole frame.
2275                if pending.as_ref().is_some_and(|p| !p.tiles.is_empty()) {
2276                    return Err(Error::TileGroupInvalid);
2277                }
2278                pending = None;
2279                let s = seq.as_ref().ok_or(Error::PartitionWalkOutOfRange)?;
2280                let t_parse = Instant::now();
2281                let fh = parse_frame_header_with_refs(desc.payload, s, &refs.info)?;
2282                check_picture_size(&fh, max_picture_size)?;
2283                let tg_offset = fh.bits_consumed.div_ceil(8);
2284                if tg_offset > desc.payload.len() {
2285                    return Err(Error::UnexpectedEnd);
2286                }
2287                let tg_body = &desc.payload[tg_offset..];
2288                let ti = fh
2289                    .tile_info
2290                    .as_ref()
2291                    .ok_or(Error::PartitionWalkOutOfRange)?;
2292                let tiles = parse_whole_frame_tile_group(ti, tg_body, true)?;
2293                stages.parse += t_parse.elapsed();
2294                let decoded = decode_frame_spec_tiles(s, &fh, &tiles, Some(refs), stages, threads)?;
2295                let frame = reference_frame_update(refs, &fh, decoded)?;
2296                if fh.show_frame {
2297                    out.push(frame);
2298                }
2299            }
2300            _ => return Err(Error::PartitionWalkOutOfRange),
2301        }
2302    }
2303    // §7.5 / §6.10.1: a coded frame's tile groups all live inside its
2304    // temporal unit — the last group (`tg_end == NumTiles - 1`) must
2305    // have arrived before the unit ends.
2306    if pending.as_ref().is_some_and(|p| !p.tiles.is_empty()) {
2307        return Err(Error::TileGroupInvalid);
2308    }
2309    Ok(())
2310}